Compare commits
No commits in common. "main" and "main" have entirely different histories.
@ -1,61 +0,0 @@
|
|||||||
name: Deploy Snowball frontend
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY_USER_KEY: ${{ secrets.REGISTRY_USER_KEY }}
|
|
||||||
REGISTRY_BOND_ID: ${{ secrets.REGISTRY_BOND_ID }}
|
|
||||||
DEPLOYER_LRN: lrn://vaasl-provider/deployers/webapp-deployer-api.apps.vaasl.io
|
|
||||||
AUTHORITY: laconic-deploy
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [20.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: Download yarn
|
|
||||||
run: |
|
|
||||||
curl -fsSL -o /usr/local/bin/yarn https://github.com/yarnpkg/yarn/releases/download/v1.22.21/yarn-1.22.21.js
|
|
||||||
chmod +x /usr/local/bin/yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
- name: Set up environment
|
|
||||||
run: |
|
|
||||||
# Create a .env file with the necessary variables
|
|
||||||
echo "REGISTRY_BOND_ID=$REGISTRY_BOND_ID" > packages/deployer/.env
|
|
||||||
echo "DEPLOYER_LRN=$DEPLOYER_LRN" >> packages/deployer/.env
|
|
||||||
echo "AUTHORITY=$AUTHORITY" >> packages/deployer/.env
|
|
||||||
|
|
||||||
# Create a config file with necessary endpoints and secrets
|
|
||||||
cat > packages/deployer/config.yml <<EOF
|
|
||||||
services:
|
|
||||||
registry:
|
|
||||||
rpcEndpoint: https://laconicd-mainnet-1.laconic.com
|
|
||||||
gqlEndpoint: https://laconicd-mainnet-1.laconic.com/api
|
|
||||||
userKey: $REGISTRY_USER_KEY
|
|
||||||
bondId: $REGISTRY_BOND_ID
|
|
||||||
chainId: laconic-mainnet
|
|
||||||
gasPrice: 0.001alnt
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Run deploy script
|
|
||||||
run: |
|
|
||||||
cd packages/deployer
|
|
||||||
./deploy-frontend.sh
|
|
@ -5,7 +5,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- staging
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
@ -14,12 +14,13 @@ VITE_SERVER_URL = 'LACONIC_HOSTED_CONFIG_server_url'
|
|||||||
VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
|
VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
|
||||||
VITE_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_pwa_templaterepo'
|
VITE_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_pwa_templaterepo'
|
||||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo'
|
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo'
|
||||||
VITE_GITHUB_NEXT_APP_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_next_app_templaterepo'
|
VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
|
||||||
VITE_WALLET_IFRAME_URL = 'LACONIC_HOSTED_CONFIG_wallet_iframe_url'
|
VITE_LACONICD_CHAIN_ID = 'LACONIC_HOSTED_CONFIG_laconicd_chain_id'
|
||||||
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
|
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
|
||||||
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
|
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
|
||||||
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
|
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
|
||||||
VITE_TURNKEY_API_BASE_URL = 'LACONIC_HOSTED_CONFIG_turnkey_api_base_url'
|
VITE_TURNKEY_API_BASE_URL = 'LACONIC_HOSTED_CONFIG_turnkey_api_base_url'
|
||||||
|
VITE_TURNKEY_ORGANIZATION_ID = 'LACONIC_HOSTED_CONFIG_turnkey_organization_id'
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
yarn || exit 1
|
yarn || exit 1
|
||||||
|
@ -14,6 +14,5 @@
|
|||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"build": "lerna run build --stream",
|
"build": "lerna run build --stream",
|
||||||
"lint": "lerna run lint --stream"
|
"lint": "lerna run lint --stream"
|
||||||
},
|
}
|
||||||
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
|
}
|
||||||
}
|
|
@ -27,7 +27,6 @@
|
|||||||
"nanoid": "3",
|
"nanoid": "3",
|
||||||
"nanoid-dictionary": "^5.0.0-beta.1",
|
"nanoid-dictionary": "^5.0.0-beta.1",
|
||||||
"octokit": "^3.1.2",
|
"octokit": "^3.1.2",
|
||||||
"openpgp": "^6.0.1",
|
|
||||||
"reflect-metadata": "^0.2.1",
|
"reflect-metadata": "^0.2.1",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
FindOneOptions,
|
FindOneOptions,
|
||||||
FindOptionsWhere,
|
FindOptionsWhere,
|
||||||
IsNull,
|
IsNull,
|
||||||
Not,
|
Not
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
@ -16,15 +16,14 @@ import { lowercase, numbers } from 'nanoid-dictionary';
|
|||||||
import { DatabaseConfig } from './config';
|
import { DatabaseConfig } from './config';
|
||||||
import { User } from './entity/User';
|
import { User } from './entity/User';
|
||||||
import { Organization } from './entity/Organization';
|
import { Organization } from './entity/Organization';
|
||||||
import { AuctionStatus, Project } from './entity/Project';
|
import { Project } from './entity/Project';
|
||||||
import { Deployment, DeploymentStatus } from './entity/Deployment';
|
import { Deployment } from './entity/Deployment';
|
||||||
import { ProjectMember } from './entity/ProjectMember';
|
import { ProjectMember } from './entity/ProjectMember';
|
||||||
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
import { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { getEntities, loadAndSaveData } from './utils';
|
import { getEntities, loadAndSaveData } from './utils';
|
||||||
import { UserOrganization } from './entity/UserOrganization';
|
import { UserOrganization } from './entity/UserOrganization';
|
||||||
import { Deployer } from './entity/Deployer';
|
import { Deployer } from './entity/Deployer';
|
||||||
import { DNSRecordAttributes } from './types';
|
|
||||||
|
|
||||||
const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json';
|
const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json';
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ export class Database {
|
|||||||
database: dbPath,
|
database: dbPath,
|
||||||
entities: [path.join(__dirname, '/entity/*')],
|
entities: [path.join(__dirname, '/entity/*')],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
logging: false,
|
logging: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,28 +49,12 @@ export class Database {
|
|||||||
await this.dataSource.initialize();
|
await this.dataSource.initialize();
|
||||||
log('database initialized');
|
log('database initialized');
|
||||||
|
|
||||||
let organizations = await this.getOrganizations({});
|
const organizations = await this.getOrganizations({});
|
||||||
|
|
||||||
// Load an organization if none exist
|
// Load an organization if none exist
|
||||||
if (!organizations.length) {
|
if (!organizations.length) {
|
||||||
const orgEntities = await getEntities(
|
const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH));
|
||||||
path.resolve(__dirname, ORGANIZATION_DATA_PATH),
|
await loadAndSaveData(Organization, this.dataSource, [orgEntities[0]]);
|
||||||
);
|
|
||||||
organizations = await loadAndSaveData(Organization, this.dataSource, [
|
|
||||||
orgEntities[0],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hotfix for updating old DB data
|
|
||||||
if (organizations[0].slug === 'snowball-tools-1') {
|
|
||||||
const [orgEntity] = await getEntities(
|
|
||||||
path.resolve(__dirname, ORGANIZATION_DATA_PATH),
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.updateOrganization(organizations[0].id, {
|
|
||||||
slug: orgEntity.slug as string,
|
|
||||||
name: orgEntity.name as string,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +81,7 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getOrganizations(
|
async getOrganizations(
|
||||||
options: FindManyOptions<Organization>,
|
options: FindManyOptions<Organization>
|
||||||
): Promise<Organization[]> {
|
): Promise<Organization[]> {
|
||||||
const organizationRepository = this.dataSource.getRepository(Organization);
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
const organizations = await organizationRepository.find(options);
|
const organizations = await organizationRepository.find(options);
|
||||||
@ -107,7 +90,7 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getOrganization(
|
async getOrganization(
|
||||||
options: FindOneOptions<Organization>,
|
options: FindOneOptions<Organization>
|
||||||
): Promise<Organization | null> {
|
): Promise<Organization | null> {
|
||||||
const organizationRepository = this.dataSource.getRepository(Organization);
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
const organization = await organizationRepository.findOne(options);
|
const organization = await organizationRepository.findOne(options);
|
||||||
@ -122,39 +105,22 @@ export class Database {
|
|||||||
where: {
|
where: {
|
||||||
userOrganizations: {
|
userOrganizations: {
|
||||||
member: {
|
member: {
|
||||||
id: userId,
|
id: userId
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return userOrgs;
|
return userOrgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUserOrganization(
|
async addUserOrganization(data: DeepPartial<UserOrganization>): Promise<UserOrganization> {
|
||||||
data: DeepPartial<UserOrganization>,
|
const userOrganizationRepository = this.dataSource.getRepository(UserOrganization);
|
||||||
): Promise<UserOrganization> {
|
|
||||||
const userOrganizationRepository =
|
|
||||||
this.dataSource.getRepository(UserOrganization);
|
|
||||||
const newUserOrganization = await userOrganizationRepository.save(data);
|
const newUserOrganization = await userOrganizationRepository.save(data);
|
||||||
|
|
||||||
return newUserOrganization;
|
return newUserOrganization;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOrganization(
|
|
||||||
organizationId: string,
|
|
||||||
data: DeepPartial<Organization>,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const organizationRepository = this.dataSource.getRepository(Organization);
|
|
||||||
const updateResult = await organizationRepository.update(
|
|
||||||
{ id: organizationId },
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
assert(updateResult.affected);
|
|
||||||
|
|
||||||
return updateResult.affected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProjects(options: FindManyOptions<Project>): Promise<Project[]> {
|
async getProjects(options: FindManyOptions<Project>): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
const projects = await projectRepository.find(options);
|
const projects = await projectRepository.find(options);
|
||||||
@ -170,15 +136,16 @@ export class Database {
|
|||||||
.leftJoinAndSelect(
|
.leftJoinAndSelect(
|
||||||
'project.deployments',
|
'project.deployments',
|
||||||
'deployments',
|
'deployments',
|
||||||
'deployments.isCurrent = true AND deployments.isCanonical = true',
|
'deployments.isCurrent = true'
|
||||||
)
|
)
|
||||||
.leftJoinAndSelect('deployments.createdBy', 'user')
|
.leftJoinAndSelect('deployments.createdBy', 'user')
|
||||||
|
.leftJoinAndSelect('deployments.domain', 'domain')
|
||||||
.leftJoinAndSelect('deployments.deployer', 'deployer')
|
.leftJoinAndSelect('deployments.deployer', 'deployer')
|
||||||
.leftJoinAndSelect('project.owner', 'owner')
|
.leftJoinAndSelect('project.owner', 'owner')
|
||||||
.leftJoinAndSelect('project.deployers', 'deployers')
|
.leftJoinAndSelect('project.deployers', 'deployers')
|
||||||
.leftJoinAndSelect('project.organization', 'organization')
|
.leftJoinAndSelect('project.organization', 'organization')
|
||||||
.where('project.id = :projectId', {
|
.where('project.id = :projectId', {
|
||||||
projectId,
|
projectId
|
||||||
})
|
})
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
@ -186,28 +153,26 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async allProjectsWithoutDeployments(): Promise<Project[]> {
|
async allProjectsWithoutDeployments(): Promise<Project[]> {
|
||||||
// Fetch all projects with auction not completed and wihout any deployments
|
|
||||||
const allProjects = await this.getProjects({
|
const allProjects = await this.getProjects({
|
||||||
where: {
|
where: {
|
||||||
auctionId: Not(IsNull()),
|
auctionId: Not(IsNull()),
|
||||||
auctionStatus: Not(AuctionStatus.Completed),
|
|
||||||
},
|
},
|
||||||
relations: ['deployments'],
|
relations: ['deployments'],
|
||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeProjectsWithoutDeployments = allProjects.filter((project) => {
|
const projects = allProjects.filter(project => {
|
||||||
if (project.deletedAt !== null) return false;
|
if (project.deletedAt !== null) return false;
|
||||||
|
|
||||||
return project.deployments.length === 0;
|
return project.deployments.length === 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return activeProjectsWithoutDeployments;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectsInOrganization(
|
async getProjectsInOrganization(
|
||||||
userId: string,
|
userId: string,
|
||||||
organizationSlug: string,
|
organizationSlug: string
|
||||||
): Promise<Project[]> {
|
): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
@ -216,16 +181,17 @@ export class Database {
|
|||||||
.leftJoinAndSelect(
|
.leftJoinAndSelect(
|
||||||
'project.deployments',
|
'project.deployments',
|
||||||
'deployments',
|
'deployments',
|
||||||
'deployments.isCurrent = true AND deployments.isCanonical = true',
|
'deployments.isCurrent = true'
|
||||||
)
|
)
|
||||||
|
.leftJoinAndSelect('deployments.domain', 'domain')
|
||||||
.leftJoin('project.projectMembers', 'projectMembers')
|
.leftJoin('project.projectMembers', 'projectMembers')
|
||||||
.leftJoin('project.organization', 'organization')
|
.leftJoin('project.organization', 'organization')
|
||||||
.where(
|
.where(
|
||||||
'(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug',
|
'(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug',
|
||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
organizationSlug,
|
organizationSlug
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
@ -236,7 +202,7 @@ export class Database {
|
|||||||
* Get deployments with specified filter
|
* Get deployments with specified filter
|
||||||
*/
|
*/
|
||||||
async getDeployments(
|
async getDeployments(
|
||||||
options: FindManyOptions<Deployment>,
|
options: FindManyOptions<Deployment>
|
||||||
): Promise<Deployment[]> {
|
): Promise<Deployment[]> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const deployments = await deploymentRepository.find(options);
|
const deployments = await deploymentRepository.find(options);
|
||||||
@ -248,43 +214,23 @@ export class Database {
|
|||||||
return this.getDeployments({
|
return this.getDeployments({
|
||||||
relations: {
|
relations: {
|
||||||
project: true,
|
project: true,
|
||||||
|
domain: true,
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
deployer: true,
|
deployer: true,
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
project: {
|
project: {
|
||||||
id: projectId,
|
id: projectId
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
createdAt: 'DESC',
|
createdAt: 'DESC'
|
||||||
},
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNonCanonicalDeploymentsByProjectId(
|
|
||||||
projectId: string,
|
|
||||||
): Promise<Deployment[]> {
|
|
||||||
return this.getDeployments({
|
|
||||||
relations: {
|
|
||||||
project: true,
|
|
||||||
createdBy: true,
|
|
||||||
deployer: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
project: {
|
|
||||||
id: projectId,
|
|
||||||
},
|
|
||||||
isCanonical: false,
|
|
||||||
},
|
|
||||||
order: {
|
|
||||||
createdAt: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeployment(
|
async getDeployment(
|
||||||
options: FindOneOptions<Deployment>,
|
options: FindOneOptions<Deployment>
|
||||||
): Promise<Deployment | null> {
|
): Promise<Deployment | null> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const deployment = await deploymentRepository.findOne(options);
|
const deployment = await deploymentRepository.findOne(options);
|
||||||
@ -306,7 +252,7 @@ export class Database {
|
|||||||
|
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...data,
|
...data,
|
||||||
id,
|
id
|
||||||
};
|
};
|
||||||
const deployment = await deploymentRepository.save(updatedData);
|
const deployment = await deploymentRepository.save(updatedData);
|
||||||
|
|
||||||
@ -314,7 +260,7 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getProjectMembersByProjectId(
|
async getProjectMembersByProjectId(
|
||||||
projectId: string,
|
projectId: string
|
||||||
): Promise<ProjectMember[]> {
|
): Promise<ProjectMember[]> {
|
||||||
const projectMemberRepository =
|
const projectMemberRepository =
|
||||||
this.dataSource.getRepository(ProjectMember);
|
this.dataSource.getRepository(ProjectMember);
|
||||||
@ -322,13 +268,13 @@ export class Database {
|
|||||||
const projectMembers = await projectMemberRepository.find({
|
const projectMembers = await projectMemberRepository.find({
|
||||||
relations: {
|
relations: {
|
||||||
project: true,
|
project: true,
|
||||||
member: true,
|
member: true
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
project: {
|
project: {
|
||||||
id: projectId,
|
id: projectId
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return projectMembers;
|
return projectMembers;
|
||||||
@ -336,7 +282,7 @@ export class Database {
|
|||||||
|
|
||||||
async getEnvironmentVariablesByProjectId(
|
async getEnvironmentVariablesByProjectId(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
filter?: FindOptionsWhere<EnvironmentVariable>,
|
filter?: FindOptionsWhere<EnvironmentVariable>
|
||||||
): Promise<EnvironmentVariable[]> {
|
): Promise<EnvironmentVariable[]> {
|
||||||
const environmentVariableRepository =
|
const environmentVariableRepository =
|
||||||
this.dataSource.getRepository(EnvironmentVariable);
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
@ -344,10 +290,10 @@ export class Database {
|
|||||||
const environmentVariables = await environmentVariableRepository.find({
|
const environmentVariables = await environmentVariableRepository.find({
|
||||||
where: {
|
where: {
|
||||||
project: {
|
project: {
|
||||||
id: projectId,
|
id: projectId
|
||||||
},
|
},
|
||||||
...filter,
|
...filter
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return environmentVariables;
|
return environmentVariables;
|
||||||
@ -358,7 +304,7 @@ export class Database {
|
|||||||
this.dataSource.getRepository(ProjectMember);
|
this.dataSource.getRepository(ProjectMember);
|
||||||
|
|
||||||
const deleteResult = await projectMemberRepository.delete({
|
const deleteResult = await projectMemberRepository.delete({
|
||||||
id: projectMemberId,
|
id: projectMemberId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deleteResult.affected) {
|
if (deleteResult.affected) {
|
||||||
@ -370,20 +316,20 @@ export class Database {
|
|||||||
|
|
||||||
async updateProjectMemberById(
|
async updateProjectMemberById(
|
||||||
projectMemberId: string,
|
projectMemberId: string,
|
||||||
data: DeepPartial<ProjectMember>,
|
data: DeepPartial<ProjectMember>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const projectMemberRepository =
|
const projectMemberRepository =
|
||||||
this.dataSource.getRepository(ProjectMember);
|
this.dataSource.getRepository(ProjectMember);
|
||||||
const updateResult = await projectMemberRepository.update(
|
const updateResult = await projectMemberRepository.update(
|
||||||
{ id: projectMemberId },
|
{ id: projectMemberId },
|
||||||
data,
|
data
|
||||||
);
|
);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProjectMember(
|
async addProjectMember(
|
||||||
data: DeepPartial<ProjectMember>,
|
data: DeepPartial<ProjectMember>
|
||||||
): Promise<ProjectMember> {
|
): Promise<ProjectMember> {
|
||||||
const projectMemberRepository =
|
const projectMemberRepository =
|
||||||
this.dataSource.getRepository(ProjectMember);
|
this.dataSource.getRepository(ProjectMember);
|
||||||
@ -393,7 +339,7 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addEnvironmentVariables(
|
async addEnvironmentVariables(
|
||||||
data: DeepPartial<EnvironmentVariable>[],
|
data: DeepPartial<EnvironmentVariable>[]
|
||||||
): Promise<EnvironmentVariable[]> {
|
): Promise<EnvironmentVariable[]> {
|
||||||
const environmentVariableRepository =
|
const environmentVariableRepository =
|
||||||
this.dataSource.getRepository(EnvironmentVariable);
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
@ -405,25 +351,25 @@ export class Database {
|
|||||||
|
|
||||||
async updateEnvironmentVariable(
|
async updateEnvironmentVariable(
|
||||||
environmentVariableId: string,
|
environmentVariableId: string,
|
||||||
data: DeepPartial<EnvironmentVariable>,
|
data: DeepPartial<EnvironmentVariable>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const environmentVariableRepository =
|
const environmentVariableRepository =
|
||||||
this.dataSource.getRepository(EnvironmentVariable);
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
const updateResult = await environmentVariableRepository.update(
|
const updateResult = await environmentVariableRepository.update(
|
||||||
{ id: environmentVariableId },
|
{ id: environmentVariableId },
|
||||||
data,
|
data
|
||||||
);
|
);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteEnvironmentVariable(
|
async deleteEnvironmentVariable(
|
||||||
environmentVariableId: string,
|
environmentVariableId: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const environmentVariableRepository =
|
const environmentVariableRepository =
|
||||||
this.dataSource.getRepository(EnvironmentVariable);
|
this.dataSource.getRepository(EnvironmentVariable);
|
||||||
const deleteResult = await environmentVariableRepository.delete({
|
const deleteResult = await environmentVariableRepository.delete({
|
||||||
id: environmentVariableId,
|
id: environmentVariableId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deleteResult.affected) {
|
if (deleteResult.affected) {
|
||||||
@ -440,13 +386,13 @@ export class Database {
|
|||||||
const projectMemberWithProject = await projectMemberRepository.find({
|
const projectMemberWithProject = await projectMemberRepository.find({
|
||||||
relations: {
|
relations: {
|
||||||
project: {
|
project: {
|
||||||
owner: true,
|
owner: true
|
||||||
},
|
},
|
||||||
member: true,
|
member: true
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
id: projectMemberId,
|
id: projectMemberId
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (projectMemberWithProject.length === 0) {
|
if (projectMemberWithProject.length === 0) {
|
||||||
@ -458,7 +404,7 @@ export class Database {
|
|||||||
|
|
||||||
async getProjectsBySearchText(
|
async getProjectsBySearchText(
|
||||||
userId: string,
|
userId: string,
|
||||||
searchText: string,
|
searchText: string
|
||||||
): Promise<Project[]> {
|
): Promise<Project[]> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
@ -470,8 +416,8 @@ export class Database {
|
|||||||
'(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText',
|
'(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText',
|
||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
searchText: `%${searchText}%`,
|
searchText: `%${searchText}%`
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
@ -480,14 +426,14 @@ export class Database {
|
|||||||
|
|
||||||
async updateDeploymentById(
|
async updateDeploymentById(
|
||||||
deploymentId: string,
|
deploymentId: string,
|
||||||
data: DeepPartial<Deployment>,
|
data: DeepPartial<Deployment>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return this.updateDeployment({ id: deploymentId }, data);
|
return this.updateDeployment({ id: deploymentId }, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateDeployment(
|
async updateDeployment(
|
||||||
criteria: FindOptionsWhere<Deployment>,
|
criteria: FindOptionsWhere<Deployment>,
|
||||||
data: DeepPartial<Deployment>,
|
data: DeepPartial<Deployment>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const updateResult = await deploymentRepository.update(criteria, data);
|
const updateResult = await deploymentRepository.update(criteria, data);
|
||||||
@ -497,7 +443,7 @@ export class Database {
|
|||||||
|
|
||||||
async updateDeploymentsByProjectIds(
|
async updateDeploymentsByProjectIds(
|
||||||
projectIds: string[],
|
projectIds: string[],
|
||||||
data: DeepPartial<Deployment>,
|
data: DeepPartial<Deployment>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
|
|
||||||
@ -515,8 +461,8 @@ export class Database {
|
|||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||||
const deployment = await deploymentRepository.findOneOrFail({
|
const deployment = await deploymentRepository.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
id: deploymentId,
|
id: deploymentId
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteResult = await deploymentRepository.softRemove(deployment);
|
const deleteResult = await deploymentRepository.softRemove(deployment);
|
||||||
@ -524,11 +470,7 @@ export class Database {
|
|||||||
return Boolean(deleteResult);
|
return Boolean(deleteResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addProject(
|
async addProject(user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
|
||||||
user: User,
|
|
||||||
organizationId: string,
|
|
||||||
data: DeepPartial<Project>,
|
|
||||||
): Promise<Project> {
|
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
|
|
||||||
// TODO: Check if organization exists
|
// TODO: Check if organization exists
|
||||||
@ -541,7 +483,7 @@ export class Database {
|
|||||||
newProject.owner = user;
|
newProject.owner = user;
|
||||||
|
|
||||||
newProject.organization = Object.assign(new Organization(), {
|
newProject.organization = Object.assign(new Organization(), {
|
||||||
id: organizationId,
|
id: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return projectRepository.save(newProject);
|
return projectRepository.save(newProject);
|
||||||
@ -555,12 +497,12 @@ export class Database {
|
|||||||
|
|
||||||
async updateProjectById(
|
async updateProjectById(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
data: DeepPartial<Project>,
|
data: DeepPartial<Project>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
const updateResult = await projectRepository.update(
|
const updateResult = await projectRepository.update(
|
||||||
{ id: projectId },
|
{ id: projectId },
|
||||||
data,
|
data
|
||||||
);
|
);
|
||||||
|
|
||||||
return Boolean(updateResult.affected);
|
return Boolean(updateResult.affected);
|
||||||
@ -570,11 +512,11 @@ export class Database {
|
|||||||
const projectRepository = this.dataSource.getRepository(Project);
|
const projectRepository = this.dataSource.getRepository(Project);
|
||||||
const project = await projectRepository.findOneOrFail({
|
const project = await projectRepository.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
id: projectId,
|
id: projectId
|
||||||
},
|
},
|
||||||
relations: {
|
relations: {
|
||||||
projectMembers: true,
|
projectMembers: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteResult = await projectRepository.softRemove(project);
|
const deleteResult = await projectRepository.softRemove(project);
|
||||||
@ -610,7 +552,7 @@ export class Database {
|
|||||||
|
|
||||||
async updateDomainById(
|
async updateDomainById(
|
||||||
domainId: string,
|
domainId: string,
|
||||||
data: DeepPartial<Domain>,
|
data: DeepPartial<Domain>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const domainRepository = this.dataSource.getRepository(Domain);
|
const domainRepository = this.dataSource.getRepository(Domain);
|
||||||
const updateResult = await domainRepository.update({ id: domainId }, data);
|
const updateResult = await domainRepository.update({ id: domainId }, data);
|
||||||
@ -620,66 +562,25 @@ export class Database {
|
|||||||
|
|
||||||
async getDomainsByProjectId(
|
async getDomainsByProjectId(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
filter?: FindOptionsWhere<Domain>,
|
filter?: FindOptionsWhere<Domain>
|
||||||
): Promise<Domain[]> {
|
): Promise<Domain[]> {
|
||||||
const domainRepository = this.dataSource.getRepository(Domain);
|
const domainRepository = this.dataSource.getRepository(Domain);
|
||||||
|
|
||||||
const domains = await domainRepository.find({
|
const domains = await domainRepository.find({
|
||||||
relations: {
|
relations: {
|
||||||
redirectTo: true,
|
redirectTo: true
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
project: {
|
project: {
|
||||||
id: projectId,
|
id: projectId
|
||||||
},
|
},
|
||||||
...filter,
|
...filter
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return domains;
|
return domains;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOldestDomainByProjectId(projectId: string): Promise<Domain | null> {
|
|
||||||
const domainRepository = this.dataSource.getRepository(Domain);
|
|
||||||
|
|
||||||
const domain = await domainRepository.findOne({
|
|
||||||
where: {
|
|
||||||
project: {
|
|
||||||
id: projectId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
order: {
|
|
||||||
createdAt: 'ASC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLatestDNSRecordByProjectId(
|
|
||||||
projectId: string,
|
|
||||||
): Promise<DNSRecordAttributes | null> {
|
|
||||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
|
||||||
|
|
||||||
const deployment = await deploymentRepository.findOne({
|
|
||||||
where: {
|
|
||||||
project: {
|
|
||||||
id: projectId,
|
|
||||||
},
|
|
||||||
status: DeploymentStatus.Ready,
|
|
||||||
},
|
|
||||||
order: {
|
|
||||||
createdAt: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deployment === null) {
|
|
||||||
throw new Error(`No deployment found for project ${projectId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return deployment.dnsRecordData;
|
|
||||||
}
|
|
||||||
|
|
||||||
async addDeployer(data: DeepPartial<Deployer>): Promise<Deployer> {
|
async addDeployer(data: DeepPartial<Deployer>): Promise<Deployer> {
|
||||||
const deployerRepository = this.dataSource.getRepository(Deployer);
|
const deployerRepository = this.dataSource.getRepository(Deployer);
|
||||||
const newDomain = await deployerRepository.save(data);
|
const newDomain = await deployerRepository.save(data);
|
||||||
@ -695,9 +596,7 @@ export class Database {
|
|||||||
|
|
||||||
async getDeployerByLRN(deployerLrn: string): Promise<Deployer | null> {
|
async getDeployerByLRN(deployerLrn: string): Promise<Deployer | null> {
|
||||||
const deployerRepository = this.dataSource.getRepository(Deployer);
|
const deployerRepository = this.dataSource.getRepository(Deployer);
|
||||||
const deployer = await deployerRepository.findOne({
|
const deployer = await deployerRepository.findOne({ where: { deployerLrn } });
|
||||||
where: { deployerLrn },
|
|
||||||
});
|
|
||||||
|
|
||||||
return deployer;
|
return deployer;
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,6 @@ export class Deployer {
|
|||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
baseDomain!: string;
|
baseDomain!: string;
|
||||||
|
|
||||||
@Column('varchar', { nullable: true})
|
|
||||||
publicKey!: string | null;
|
|
||||||
|
|
||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
minimumPayment!: string | null;
|
minimumPayment!: string | null;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import { Project } from './Project';
|
|||||||
import { Domain } from './Domain';
|
import { Domain } from './Domain';
|
||||||
import { User } from './User';
|
import { User } from './User';
|
||||||
import { Deployer } from './Deployer';
|
import { Deployer } from './Deployer';
|
||||||
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes, DNSRecordAttributes } from '../types';
|
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
|
||||||
|
|
||||||
export enum Environment {
|
export enum Environment {
|
||||||
Production = 'Production',
|
Production = 'Production',
|
||||||
@ -39,7 +39,6 @@ export interface ApplicationDeploymentRequest {
|
|||||||
config: string;
|
config: string;
|
||||||
meta: string;
|
meta: string;
|
||||||
payment?: string;
|
payment?: string;
|
||||||
dns?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationDeploymentRemovalRequest {
|
export interface ApplicationDeploymentRemovalRequest {
|
||||||
@ -78,6 +77,13 @@ export class Deployment {
|
|||||||
@JoinColumn({ name: 'projectId' })
|
@JoinColumn({ name: 'projectId' })
|
||||||
project!: Project;
|
project!: Project;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
domainId!: string | null;
|
||||||
|
|
||||||
|
@OneToOne(() => Domain)
|
||||||
|
@JoinColumn({ name: 'domainId' })
|
||||||
|
domain!: Domain | null;
|
||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
branch!: string;
|
branch!: string;
|
||||||
|
|
||||||
@ -120,9 +126,6 @@ export class Deployment {
|
|||||||
@Column('simple-json', { nullable: true })
|
@Column('simple-json', { nullable: true })
|
||||||
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
|
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
|
||||||
|
|
||||||
@Column('simple-json', { nullable: true })
|
|
||||||
dnsRecordData!: DNSRecordAttributes | null;
|
|
||||||
|
|
||||||
@ManyToOne(() => Deployer)
|
@ManyToOne(() => Deployer)
|
||||||
@JoinColumn({ name: 'deployerLrn' })
|
@JoinColumn({ name: 'deployerLrn' })
|
||||||
deployer!: Deployer;
|
deployer!: Deployer;
|
||||||
@ -135,9 +138,6 @@ export class Deployment {
|
|||||||
@Column('boolean', { default: false })
|
@Column('boolean', { default: false })
|
||||||
isCurrent!: boolean;
|
isCurrent!: boolean;
|
||||||
|
|
||||||
@Column('boolean', { default: false })
|
|
||||||
isCanonical!: boolean;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
enum: DeploymentStatus
|
enum: DeploymentStatus
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
OneToMany,
|
OneToMany,
|
||||||
DeleteDateColumn,
|
DeleteDateColumn,
|
||||||
JoinTable,
|
JoinTable,
|
||||||
ManyToMany,
|
ManyToMany
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { User } from './User';
|
import { User } from './User';
|
||||||
@ -18,12 +18,6 @@ import { ProjectMember } from './ProjectMember';
|
|||||||
import { Deployment } from './Deployment';
|
import { Deployment } from './Deployment';
|
||||||
import { Deployer } from './Deployer';
|
import { Deployer } from './Deployer';
|
||||||
|
|
||||||
export enum AuctionStatus {
|
|
||||||
Commit = 'commit',
|
|
||||||
Reveal = 'reveal',
|
|
||||||
Completed = 'completed',
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Project {
|
export class Project {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
@ -55,23 +49,16 @@ export class Project {
|
|||||||
@Column('text', { default: '' })
|
@Column('text', { default: '' })
|
||||||
description!: string;
|
description!: string;
|
||||||
|
|
||||||
|
@Column('varchar', { nullable: true })
|
||||||
|
auctionId!: string | null;
|
||||||
|
|
||||||
// Tx hash for sending coins from snowball to deployer
|
// Tx hash for sending coins from snowball to deployer
|
||||||
@Column('varchar', { nullable: true })
|
@Column('varchar', { nullable: true })
|
||||||
txHash!: string | null;
|
txHash!: string | null;
|
||||||
|
|
||||||
@ManyToMany(() => Deployer, (deployer) => deployer.projects)
|
@ManyToMany(() => Deployer, (deployer) => (deployer.projects))
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
deployers!: Deployer[];
|
deployers!: Deployer[]
|
||||||
|
|
||||||
@Column('varchar', { nullable: true })
|
|
||||||
auctionId!: string | null;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
enum: AuctionStatus,
|
|
||||||
// TODO: Remove later after all projects auction status have been set
|
|
||||||
default: AuctionStatus.Completed,
|
|
||||||
})
|
|
||||||
auctionStatus!: AuctionStatus;
|
|
||||||
|
|
||||||
@Column('boolean', { default: false, nullable: true })
|
@Column('boolean', { default: false, nullable: true })
|
||||||
fundsReleased!: boolean;
|
fundsReleased!: boolean;
|
||||||
|
@ -4,15 +4,8 @@ import { DateTime } from 'luxon';
|
|||||||
import { Octokit } from 'octokit';
|
import { Octokit } from 'octokit';
|
||||||
import { inc as semverInc } from 'semver';
|
import { inc as semverInc } from 'semver';
|
||||||
import { DeepPartial } from 'typeorm';
|
import { DeepPartial } from 'typeorm';
|
||||||
import * as openpgp from 'openpgp';
|
|
||||||
|
|
||||||
import {
|
import { Account, DEFAULT_GAS_ESTIMATION_MULTIPLIER, Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk';
|
||||||
Account,
|
|
||||||
DEFAULT_GAS_ESTIMATION_MULTIPLIER,
|
|
||||||
Registry as LaconicRegistry,
|
|
||||||
getGasPrice,
|
|
||||||
parseGasAndFees,
|
|
||||||
} from '@cerc-io/registry-sdk';
|
|
||||||
import { DeliverTxResponse, IndexedTx } from '@cosmjs/stargate';
|
import { DeliverTxResponse, IndexedTx } from '@cosmjs/stargate';
|
||||||
|
|
||||||
import { RegistryConfig } from './config';
|
import { RegistryConfig } from './config';
|
||||||
@ -20,33 +13,20 @@ import {
|
|||||||
ApplicationRecord,
|
ApplicationRecord,
|
||||||
Deployment,
|
Deployment,
|
||||||
ApplicationDeploymentRequest,
|
ApplicationDeploymentRequest,
|
||||||
ApplicationDeploymentRemovalRequest,
|
ApplicationDeploymentRemovalRequest
|
||||||
} from './entity/Deployment';
|
} from './entity/Deployment';
|
||||||
import {
|
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord } from './types';
|
||||||
AppDeploymentRecord,
|
import { getConfig, getRepoDetails, registryTransactionWithRetry, sleep } from './utils';
|
||||||
AppDeploymentRemovalRecord,
|
|
||||||
AuctionParams,
|
|
||||||
DeployerRecord,
|
|
||||||
RegistryRecord,
|
|
||||||
} from './types';
|
|
||||||
import {
|
|
||||||
getConfig,
|
|
||||||
getRepoDetails,
|
|
||||||
registryTransactionWithRetry,
|
|
||||||
sleep,
|
|
||||||
} from './utils';
|
|
||||||
import { MsgCreateAuctionResponse } from '@cerc-io/registry-sdk/dist/proto/cerc/auction/v1/tx';
|
|
||||||
|
|
||||||
const log = debug('snowball:registry');
|
const log = debug('snowball:registry');
|
||||||
|
|
||||||
const APP_RECORD_TYPE = 'ApplicationRecord';
|
const APP_RECORD_TYPE = 'ApplicationRecord';
|
||||||
const APP_DEPLOYMENT_AUCTION_RECORD_TYPE = 'ApplicationDeploymentAuction';
|
const APP_DEPLOYMENT_AUCTION_RECORD_TYPE = 'ApplicationDeploymentAuction';
|
||||||
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
|
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
|
||||||
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE =
|
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
|
||||||
'ApplicationDeploymentRemovalRequest';
|
|
||||||
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
|
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
|
||||||
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
|
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
|
||||||
const WEBAPP_DEPLOYER_RECORD_TYPE = 'WebappDeployer';
|
const WEBAPP_DEPLOYER_RECORD_TYPE = 'WebappDeployer'
|
||||||
const SLEEP_DURATION = 1000;
|
const SLEEP_DURATION = 1000;
|
||||||
|
|
||||||
// TODO: Move registry code to registry-sdk/watcher-ts
|
// TODO: Move registry code to registry-sdk/watcher-ts
|
||||||
@ -62,7 +42,7 @@ export class Registry {
|
|||||||
this.registry = new LaconicRegistry(
|
this.registry = new LaconicRegistry(
|
||||||
registryConfig.gqlEndpoint,
|
registryConfig.gqlEndpoint,
|
||||||
registryConfig.restEndpoint,
|
registryConfig.restEndpoint,
|
||||||
{ chainId: registryConfig.chainId, gasPrice },
|
{ chainId: registryConfig.chainId, gasPrice }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +52,7 @@ export class Registry {
|
|||||||
commitHash,
|
commitHash,
|
||||||
appType,
|
appType,
|
||||||
}: {
|
}: {
|
||||||
octokit: Octokit;
|
octokit: Octokit
|
||||||
repository: string;
|
repository: string;
|
||||||
commitHash: string;
|
commitHash: string;
|
||||||
appType: string;
|
appType: string;
|
||||||
@ -80,33 +60,29 @@ export class Registry {
|
|||||||
applicationRecordId: string;
|
applicationRecordId: string;
|
||||||
applicationRecordData: ApplicationRecord;
|
applicationRecordData: ApplicationRecord;
|
||||||
}> {
|
}> {
|
||||||
const { repo, repoUrl, packageJSON } = await getRepoDetails(
|
const { repo, repoUrl, packageJSON } = await getRepoDetails(octokit, repository, commitHash)
|
||||||
octokit,
|
|
||||||
repository,
|
|
||||||
commitHash,
|
|
||||||
);
|
|
||||||
// Use registry-sdk to publish record
|
// Use registry-sdk to publish record
|
||||||
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
|
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
|
||||||
// Fetch previous records
|
// Fetch previous records
|
||||||
const records = await this.registry.queryRecords(
|
const records = await this.registry.queryRecords(
|
||||||
{
|
{
|
||||||
type: APP_RECORD_TYPE,
|
type: APP_RECORD_TYPE,
|
||||||
name: packageJSON.name,
|
name: packageJSON.name
|
||||||
},
|
},
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get next version of record
|
// Get next version of record
|
||||||
const bondRecords = records.filter(
|
const bondRecords = records.filter(
|
||||||
(record: any) => record.bondId === this.registryConfig.bondId,
|
(record: any) => record.bondId === this.registryConfig.bondId
|
||||||
);
|
);
|
||||||
const [latestBondRecord] = bondRecords.sort(
|
const [latestBondRecord] = bondRecords.sort(
|
||||||
(a: any, b: any) =>
|
(a: any, b: any) =>
|
||||||
new Date(b.createTime).getTime() - new Date(a.createTime).getTime(),
|
new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
|
||||||
);
|
);
|
||||||
const nextVersion = semverInc(
|
const nextVersion = semverInc(
|
||||||
latestBondRecord?.attributes.version ?? '0.0.0',
|
latestBondRecord?.attributes.version ?? '0.0.0',
|
||||||
'patch',
|
'patch'
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(nextVersion, 'Application record version not valid');
|
assert(nextVersion, 'Application record version not valid');
|
||||||
@ -126,12 +102,24 @@ export class Registry {
|
|||||||
author:
|
author:
|
||||||
typeof packageJSON.author === 'object'
|
typeof packageJSON.author === 'object'
|
||||||
? JSON.stringify(packageJSON.author)
|
? JSON.stringify(packageJSON.author)
|
||||||
: packageJSON.author,
|
: packageJSON.author
|
||||||
}),
|
}),
|
||||||
...(packageJSON.version && { app_version: packageJSON.version }),
|
...(packageJSON.version && { app_version: packageJSON.version })
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.publishRecord(applicationRecord);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
|
|
||||||
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
|
{
|
||||||
|
privateKey: this.registryConfig.privateKey,
|
||||||
|
record: applicationRecord,
|
||||||
|
bondId: this.registryConfig.bondId
|
||||||
|
},
|
||||||
|
this.registryConfig.privateKey,
|
||||||
|
fee
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
log(`Published application record ${result.id}`);
|
log(`Published application record ${result.id}`);
|
||||||
log('Application record data:', applicationRecord);
|
log('Application record data:', applicationRecord);
|
||||||
@ -140,9 +128,16 @@ export class Registry {
|
|||||||
const lrn = this.getLrn(repo);
|
const lrn = this.getLrn(repo);
|
||||||
log(`Setting name: ${lrn} for record ID: ${result.id}`);
|
log(`Setting name: ${lrn} for record ID: ${result.id}`);
|
||||||
|
|
||||||
const fee = parseGasAndFees(
|
await sleep(SLEEP_DURATION);
|
||||||
this.registryConfig.fee.gas,
|
await registryTransactionWithRetry(() =>
|
||||||
this.registryConfig.fee.fees,
|
this.registry.setName(
|
||||||
|
{
|
||||||
|
cid: result.id,
|
||||||
|
lrn
|
||||||
|
},
|
||||||
|
this.registryConfig.privateKey,
|
||||||
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
@ -150,11 +145,11 @@ export class Registry {
|
|||||||
this.registry.setName(
|
this.registry.setName(
|
||||||
{
|
{
|
||||||
cid: result.id,
|
cid: result.id,
|
||||||
lrn,
|
lrn: `${lrn}@${applicationRecord.app_version}`
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee,
|
fee
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
@ -162,28 +157,16 @@ export class Registry {
|
|||||||
this.registry.setName(
|
this.registry.setName(
|
||||||
{
|
{
|
||||||
cid: result.id,
|
cid: result.id,
|
||||||
lrn: `${lrn}@${applicationRecord.app_version}`,
|
lrn: `${lrn}@${applicationRecord.repository_ref}`
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee,
|
fee
|
||||||
),
|
)
|
||||||
);
|
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
|
||||||
await registryTransactionWithRetry(() =>
|
|
||||||
this.registry.setName(
|
|
||||||
{
|
|
||||||
cid: result.id,
|
|
||||||
lrn: `${lrn}@${applicationRecord.repository_ref}`,
|
|
||||||
},
|
|
||||||
this.registryConfig.privateKey,
|
|
||||||
fee,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
applicationRecordId: result.id,
|
applicationRecordId: result.id,
|
||||||
applicationRecordData: applicationRecord,
|
applicationRecordData: applicationRecord
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +176,7 @@ export class Registry {
|
|||||||
auctionParams: AuctionParams,
|
auctionParams: AuctionParams,
|
||||||
data: DeepPartial<Deployment>,
|
data: DeepPartial<Deployment>,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
applicationDeploymentAuction: MsgCreateAuctionResponse['auction'];
|
applicationDeploymentAuctionId: string;
|
||||||
}> {
|
}> {
|
||||||
assert(data.project?.repository, 'Project repository not found');
|
assert(data.project?.repository, 'Project repository not found');
|
||||||
|
|
||||||
@ -208,11 +191,8 @@ export class Registry {
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
const auctionConfig = config.auction;
|
const auctionConfig = config.auction;
|
||||||
|
|
||||||
const fee = parseGasAndFees(
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
this.registryConfig.fee.gas,
|
const auctionResult = await registryTransactionWithRetry(() =>
|
||||||
this.registryConfig.fee.fees,
|
|
||||||
);
|
|
||||||
const auctionResult = (await registryTransactionWithRetry(() =>
|
|
||||||
this.registry.createProviderAuction(
|
this.registry.createProviderAuction(
|
||||||
{
|
{
|
||||||
commitFee: auctionConfig.commitFee,
|
commitFee: auctionConfig.commitFee,
|
||||||
@ -224,9 +204,9 @@ export class Registry {
|
|||||||
numProviders: auctionParams.numProviders,
|
numProviders: auctionParams.numProviders,
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee,
|
fee
|
||||||
),
|
)
|
||||||
)) as MsgCreateAuctionResponse;
|
);
|
||||||
|
|
||||||
if (!auctionResult.auction) {
|
if (!auctionResult.auction) {
|
||||||
throw new Error('Error creating auction');
|
throw new Error('Error creating auction');
|
||||||
@ -239,29 +219,36 @@ export class Registry {
|
|||||||
type: APP_DEPLOYMENT_AUCTION_RECORD_TYPE,
|
type: APP_DEPLOYMENT_AUCTION_RECORD_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.publishRecord(applicationDeploymentAuction);
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
|
{
|
||||||
|
privateKey: this.registryConfig.privateKey,
|
||||||
|
record: applicationDeploymentAuction,
|
||||||
|
bondId: this.registryConfig.bondId
|
||||||
|
},
|
||||||
|
this.registryConfig.privateKey,
|
||||||
|
fee
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
log(`Application deployment auction created: ${auctionResult.auction.id}`);
|
log(`Application deployment auction created: ${auctionResult.auction.id}`);
|
||||||
log(`Application deployment auction record published: ${result.id}`);
|
log(`Application deployment auction record published: ${result.id}`);
|
||||||
log('Application deployment auction data:', applicationDeploymentAuction);
|
log('Application deployment auction data:', applicationDeploymentAuction);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
applicationDeploymentAuction: auctionResult.auction!,
|
applicationDeploymentAuctionId: auctionResult.auction.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async createApplicationDeploymentRequest(data: {
|
async createApplicationDeploymentRequest(data: {
|
||||||
deployment: Deployment;
|
deployment: Deployment,
|
||||||
appName: string;
|
appName: string,
|
||||||
repository: string;
|
repository: string,
|
||||||
auctionId?: string | null;
|
auctionId?: string | null,
|
||||||
lrn: string;
|
lrn: string,
|
||||||
apiUrl: string;
|
environmentVariables: { [key: string]: string },
|
||||||
environmentVariables: { [key: string]: string };
|
dns: string,
|
||||||
dns: string;
|
payment?: string | null
|
||||||
requesterAddress: string;
|
|
||||||
publicKey: string;
|
|
||||||
payment?: string | null;
|
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
applicationDeploymentRequestId: string;
|
applicationDeploymentRequestId: string;
|
||||||
applicationDeploymentRequestData: ApplicationDeploymentRequest;
|
applicationDeploymentRequestData: ApplicationDeploymentRequest;
|
||||||
@ -274,16 +261,6 @@ export class Registry {
|
|||||||
throw new Error(`No record found for ${lrn}`);
|
throw new Error(`No record found for ${lrn}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash;
|
|
||||||
if (Object.keys(data.environmentVariables).length !== 0) {
|
|
||||||
hash = await this.generateConfigHash(
|
|
||||||
data.environmentVariables,
|
|
||||||
data.requesterAddress,
|
|
||||||
data.publicKey,
|
|
||||||
data.apiUrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create record of type ApplicationDeploymentRequest and publish
|
// Create record of type ApplicationDeploymentRequest and publish
|
||||||
const applicationDeploymentRequest = {
|
const applicationDeploymentRequest = {
|
||||||
type: APP_DEPLOYMENT_REQUEST_TYPE,
|
type: APP_DEPLOYMENT_REQUEST_TYPE,
|
||||||
@ -293,13 +270,15 @@ export class Registry {
|
|||||||
dns: data.dns,
|
dns: data.dns,
|
||||||
|
|
||||||
// https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b
|
// https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b
|
||||||
config: JSON.stringify(hash ? { ref: hash } : {}),
|
config: JSON.stringify({
|
||||||
|
env: data.environmentVariables
|
||||||
|
}),
|
||||||
meta: JSON.stringify({
|
meta: JSON.stringify({
|
||||||
note: `Added by Snowball @ ${DateTime.utc().toFormat(
|
note: `Added by Snowball @ ${DateTime.utc().toFormat(
|
||||||
"EEE LLL dd HH:mm:ss 'UTC' yyyy",
|
"EEE LLL dd HH:mm:ss 'UTC' yyyy"
|
||||||
)}`,
|
)}`,
|
||||||
repository: data.repository,
|
repository: data.repository,
|
||||||
repository_ref: data.deployment.commitHash,
|
repository_ref: data.deployment.commitHash
|
||||||
}),
|
}),
|
||||||
deployer: data.lrn,
|
deployer: data.lrn,
|
||||||
...(data.auctionId && { auction: data.auctionId }),
|
...(data.auctionId && { auction: data.auctionId }),
|
||||||
@ -308,19 +287,31 @@ export class Registry {
|
|||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
|
|
||||||
const result = await this.publishRecord(applicationDeploymentRequest);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
|
|
||||||
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
|
{
|
||||||
|
privateKey: this.registryConfig.privateKey,
|
||||||
|
record: applicationDeploymentRequest,
|
||||||
|
bondId: this.registryConfig.bondId
|
||||||
|
},
|
||||||
|
this.registryConfig.privateKey,
|
||||||
|
fee
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
log(`Application deployment request record published: ${result.id}`);
|
log(`Application deployment request record published: ${result.id}`);
|
||||||
log('Application deployment request data:', applicationDeploymentRequest);
|
log('Application deployment request data:', applicationDeploymentRequest);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
applicationDeploymentRequestId: result.id,
|
applicationDeploymentRequestId: result.id,
|
||||||
applicationDeploymentRequestData: applicationDeploymentRequest,
|
applicationDeploymentRequestData: applicationDeploymentRequest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAuctionWinningDeployerRecords(
|
async getAuctionWinningDeployerRecords(
|
||||||
auctionId: string,
|
auctionId: string
|
||||||
): Promise<DeployerRecord[]> {
|
): Promise<DeployerRecord[]> {
|
||||||
const records = await this.registry.getAuctionsByIds([auctionId]);
|
const records = await this.registry.getAuctionsByIds([auctionId]);
|
||||||
const auctionResult = records[0];
|
const auctionResult = records[0];
|
||||||
@ -333,7 +324,7 @@ export class Registry {
|
|||||||
paymentAddress: auctionWinner,
|
paymentAddress: auctionWinner,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newRecords = records.filter((record) => {
|
const newRecords = records.filter(record => {
|
||||||
return record.names !== null && record.names.length > 0;
|
return record.names !== null && record.names.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -348,19 +339,18 @@ export class Registry {
|
|||||||
return deployerRecords;
|
return deployerRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseDeployerFunds(auctionId: string): Promise<any> {
|
async releaseDeployerFunds(
|
||||||
const fee = parseGasAndFees(
|
auctionId: string
|
||||||
this.registryConfig.fee.gas,
|
): Promise<any> {
|
||||||
this.registryConfig.fee.fees,
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
);
|
|
||||||
const auction = await registryTransactionWithRetry(() =>
|
const auction = await registryTransactionWithRetry(() =>
|
||||||
this.registry.releaseFunds(
|
this.registry.releaseFunds(
|
||||||
{
|
{
|
||||||
auctionId,
|
auctionId
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee,
|
fee
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return auction;
|
return auction;
|
||||||
@ -370,54 +360,50 @@ export class Registry {
|
|||||||
* Fetch ApplicationDeploymentRecords for deployments
|
* Fetch ApplicationDeploymentRecords for deployments
|
||||||
*/
|
*/
|
||||||
async getDeploymentRecords(
|
async getDeploymentRecords(
|
||||||
deployments: Deployment[],
|
deployments: Deployment[]
|
||||||
): Promise<AppDeploymentRecord[]> {
|
): Promise<AppDeploymentRecord[]> {
|
||||||
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
|
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
|
||||||
// TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute
|
// TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute
|
||||||
const records = await this.registry.queryRecords(
|
const records = await this.registry.queryRecords(
|
||||||
{
|
{
|
||||||
type: APP_DEPLOYMENT_RECORD_TYPE,
|
type: APP_DEPLOYMENT_RECORD_TYPE
|
||||||
},
|
},
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter records with ApplicationDeploymentRequestId ID
|
// Filter records with ApplicationDeploymentRequestId ID and Deployment specific URL
|
||||||
return records.filter((record: AppDeploymentRecord) =>
|
return records.filter((record: AppDeploymentRecord) =>
|
||||||
deployments.some(
|
deployments.some(
|
||||||
(deployment) =>
|
(deployment) =>
|
||||||
deployment.applicationDeploymentRequestId ===
|
deployment.applicationDeploymentRequestId === record.attributes.request &&
|
||||||
record.attributes.request,
|
record.attributes.url.includes(deployment.id)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch WebappDeployer Records by filter
|
* Fetch WebappDeployer Records by filter
|
||||||
*/
|
*/
|
||||||
async getDeployerRecordsByFilter(filter: {
|
async getDeployerRecordsByFilter(filter: { [key: string]: any }): Promise<DeployerRecord[]> {
|
||||||
[key: string]: any;
|
|
||||||
}): Promise<DeployerRecord[]> {
|
|
||||||
return this.registry.queryRecords(
|
return this.registry.queryRecords(
|
||||||
{
|
{
|
||||||
type: WEBAPP_DEPLOYER_RECORD_TYPE,
|
type: WEBAPP_DEPLOYER_RECORD_TYPE,
|
||||||
...filter,
|
...filter
|
||||||
},
|
},
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch ApplicationDeploymentRecords by filter
|
* Fetch ApplicationDeploymentRecords by filter
|
||||||
*/
|
*/
|
||||||
async getDeploymentRecordsByFilter(filter: {
|
async getDeploymentRecordsByFilter(filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
|
||||||
[key: string]: any;
|
|
||||||
}): Promise<AppDeploymentRecord[]> {
|
|
||||||
return this.registry.queryRecords(
|
return this.registry.queryRecords(
|
||||||
{
|
{
|
||||||
type: APP_DEPLOYMENT_RECORD_TYPE,
|
type: APP_DEPLOYMENT_RECORD_TYPE,
|
||||||
...filter,
|
...filter
|
||||||
},
|
},
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,36 +411,26 @@ export class Registry {
|
|||||||
* Fetch ApplicationDeploymentRemovalRecords for deployments
|
* Fetch ApplicationDeploymentRemovalRecords for deployments
|
||||||
*/
|
*/
|
||||||
async getDeploymentRemovalRecords(
|
async getDeploymentRemovalRecords(
|
||||||
deployments: Deployment[],
|
deployments: Deployment[]
|
||||||
): Promise<AppDeploymentRemovalRecord[]> {
|
): Promise<AppDeploymentRemovalRecord[]> {
|
||||||
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
|
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
|
||||||
const records = await this.registry.queryRecords(
|
const records = await this.registry.queryRecords(
|
||||||
{
|
{
|
||||||
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE,
|
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE
|
||||||
},
|
},
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
|
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
|
||||||
return records.filter((record: AppDeploymentRemovalRecord) =>
|
return records.filter((record: AppDeploymentRemovalRecord) =>
|
||||||
deployments.some(
|
deployments.some(
|
||||||
(deployment) =>
|
(deployment) =>
|
||||||
deployment.applicationDeploymentRemovalRequestId ===
|
deployment.applicationDeploymentRemovalRequestId === record.attributes.request &&
|
||||||
record.attributes.request &&
|
deployment.applicationDeploymentRecordId === record.attributes.deployment
|
||||||
deployment.applicationDeploymentRecordId ===
|
)
|
||||||
record.attributes.deployment,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch record by Id
|
|
||||||
*/
|
|
||||||
async getRecordById(id: string): Promise<RegistryRecord | null> {
|
|
||||||
const [record] = await this.registry.getRecordsByIds([id]);
|
|
||||||
return record ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createApplicationDeploymentRemovalRequest(data: {
|
async createApplicationDeploymentRemovalRequest(data: {
|
||||||
deploymentId: string;
|
deploymentId: string;
|
||||||
deployerLrn: string;
|
deployerLrn: string;
|
||||||
@ -473,22 +449,26 @@ export class Registry {
|
|||||||
...(data.payment && { payment: data.payment }),
|
...(data.payment && { payment: data.payment }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.publishRecord(
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
applicationDeploymentRemovalRequest,
|
|
||||||
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
|
{
|
||||||
|
privateKey: this.registryConfig.privateKey,
|
||||||
|
record: applicationDeploymentRemovalRequest,
|
||||||
|
bondId: this.registryConfig.bondId
|
||||||
|
},
|
||||||
|
this.registryConfig.privateKey,
|
||||||
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
log(
|
log(`Application deployment removal request record published: ${result.id}`);
|
||||||
`Application deployment removal request record published: ${result.id}`,
|
log('Application deployment removal request data:', applicationDeploymentRemovalRequest);
|
||||||
);
|
|
||||||
log(
|
|
||||||
'Application deployment removal request data:',
|
|
||||||
applicationDeploymentRemovalRequest,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
applicationDeploymentRemovalRequestId: result.id,
|
applicationDeploymentRemovalRequestId: result.id,
|
||||||
applicationDeploymentRemovalRequestData:
|
applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest
|
||||||
applicationDeploymentRemovalRequest,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,36 +480,12 @@ export class Registry {
|
|||||||
const auctions = await this.registry.getAuctionsByIds(auctionIds);
|
const auctions = await this.registry.getAuctionsByIds(auctionIds);
|
||||||
|
|
||||||
const completedAuctions = auctions
|
const completedAuctions = auctions
|
||||||
.filter(
|
.filter((auction: { id: string, status: string }) => auction.status === 'completed')
|
||||||
(auction: { id: string; status: string }) =>
|
.map((auction: { id: string, status: string }) => auction.id);
|
||||||
auction.status === 'completed',
|
|
||||||
)
|
|
||||||
.map((auction: { id: string; status: string }) => auction.id);
|
|
||||||
|
|
||||||
return completedAuctions;
|
return completedAuctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async publishRecord(recordData: any): Promise<any> {
|
|
||||||
const fee = parseGasAndFees(
|
|
||||||
this.registryConfig.fee.gas,
|
|
||||||
this.registryConfig.fee.fees,
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await registryTransactionWithRetry(() =>
|
|
||||||
this.registry.setRecord(
|
|
||||||
{
|
|
||||||
privateKey: this.registryConfig.privateKey,
|
|
||||||
record: recordData,
|
|
||||||
bondId: this.registryConfig.bondId,
|
|
||||||
},
|
|
||||||
this.registryConfig.privateKey,
|
|
||||||
fee,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRecordsByName(name: string): Promise<any> {
|
async getRecordsByName(name: string): Promise<any> {
|
||||||
return this.registry.resolveNames([name]);
|
return this.registry.resolveNames([name]);
|
||||||
}
|
}
|
||||||
@ -538,38 +494,27 @@ export class Registry {
|
|||||||
return this.registry.getAuctionsByIds([auctionId]);
|
return this.registry.getAuctionsByIds([auctionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendTokensToAccount(
|
async sendTokensToAccount(receiverAddress: string, amount: string): Promise<DeliverTxResponse> {
|
||||||
receiverAddress: string,
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
amount: string,
|
|
||||||
): Promise<DeliverTxResponse> {
|
|
||||||
const fee = parseGasAndFees(
|
|
||||||
this.registryConfig.fee.gas,
|
|
||||||
this.registryConfig.fee.fees,
|
|
||||||
);
|
|
||||||
const account = await this.getAccount();
|
const account = await this.getAccount();
|
||||||
const laconicClient = await this.registry.getLaconicClient(account);
|
const laconicClient = await this.registry.getLaconicClient(account);
|
||||||
const txResponse: DeliverTxResponse = await registryTransactionWithRetry(
|
const txResponse: DeliverTxResponse =
|
||||||
() =>
|
await registryTransactionWithRetry(() =>
|
||||||
laconicClient.sendTokens(
|
laconicClient.sendTokens(account.address, receiverAddress,
|
||||||
account.address,
|
|
||||||
receiverAddress,
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
denom: 'alnt',
|
denom: 'alnt',
|
||||||
amount,
|
amount
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER,
|
fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER)
|
||||||
),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
return txResponse;
|
return txResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccount(): Promise<Account> {
|
async getAccount(): Promise<Account> {
|
||||||
const account = new Account(
|
const account = new Account(Buffer.from(this.registryConfig.privateKey, 'hex'));
|
||||||
Buffer.from(this.registryConfig.privateKey, 'hex'),
|
|
||||||
);
|
|
||||||
await account.init();
|
await account.init();
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
@ -587,43 +532,4 @@ export class Registry {
|
|||||||
assert(this.registryConfig.authority, "Authority doesn't exist");
|
assert(this.registryConfig.authority, "Authority doesn't exist");
|
||||||
return `lrn://${this.registryConfig.authority}/applications/${appName}`;
|
return `lrn://${this.registryConfig.authority}/applications/${appName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateConfigHash(
|
|
||||||
environmentVariables: { [key: string]: string },
|
|
||||||
requesterAddress: string,
|
|
||||||
pubKey: string,
|
|
||||||
url: string,
|
|
||||||
): Promise<string> {
|
|
||||||
// Config to be encrypted
|
|
||||||
const config = {
|
|
||||||
authorized: [requesterAddress],
|
|
||||||
config: { env: environmentVariables },
|
|
||||||
};
|
|
||||||
|
|
||||||
// Serialize the config
|
|
||||||
const serialized = JSON.stringify(config, null, 2);
|
|
||||||
|
|
||||||
const armoredKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n${pubKey}\n\n-----END PGP PUBLIC KEY BLOCK-----`;
|
|
||||||
const publicKey = await openpgp.readKey({ armoredKey });
|
|
||||||
|
|
||||||
// Encrypt the config
|
|
||||||
const encrypted = await openpgp.encrypt({
|
|
||||||
message: await openpgp.createMessage({ text: serialized }),
|
|
||||||
encryptionKeys: publicKey,
|
|
||||||
format: 'binary',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the hash after uploading encrypted config
|
|
||||||
const response = await fetch(`${url}/upload/config`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
},
|
|
||||||
body: encrypted,
|
|
||||||
});
|
|
||||||
|
|
||||||
const configHash = await response.json();
|
|
||||||
|
|
||||||
return configHash.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
||||||
return service.getNonCanonicalDeploymentsByProjectId(projectId);
|
return service.getDeploymentsByProjectId(projectId);
|
||||||
},
|
},
|
||||||
|
|
||||||
environmentVariables: async (
|
environmentVariables: async (
|
||||||
@ -95,13 +95,6 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
|||||||
) => {
|
) => {
|
||||||
return service.verifyTx(txHash, amount, senderAddress);
|
return service.verifyTx(txHash, amount, senderAddress);
|
||||||
},
|
},
|
||||||
|
|
||||||
latestDNSRecord: async (
|
|
||||||
_: any,
|
|
||||||
{ projectId }: { projectId: string },
|
|
||||||
) => {
|
|
||||||
return service.getLatestDNSRecordByProjectId(projectId);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO: Return error in GQL response
|
// TODO: Return error in GQL response
|
||||||
|
@ -94,4 +94,13 @@ router.get('/session', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/logout', (req, res) => {
|
||||||
|
req.session.destroy((err) => {
|
||||||
|
if (err) {
|
||||||
|
return res.send({ success: false });
|
||||||
|
}
|
||||||
|
res.send({ success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -100,6 +100,7 @@ type ProjectMember {
|
|||||||
|
|
||||||
type Deployment {
|
type Deployment {
|
||||||
id: String!
|
id: String!
|
||||||
|
domain: Domain
|
||||||
branch: String!
|
branch: String!
|
||||||
commitHash: String!
|
commitHash: String!
|
||||||
commitMessage: String!
|
commitMessage: String!
|
||||||
@ -107,7 +108,6 @@ type Deployment {
|
|||||||
environment: Environment!
|
environment: Environment!
|
||||||
deployer: Deployer
|
deployer: Deployer
|
||||||
applicationDeploymentRequestId: String
|
applicationDeploymentRequestId: String
|
||||||
applicationDeploymentRecordData: AppDeploymentRecordAttributes
|
|
||||||
isCurrent: Boolean!
|
isCurrent: Boolean!
|
||||||
baseDomain: String
|
baseDomain: String
|
||||||
status: DeploymentStatus!
|
status: DeploymentStatus!
|
||||||
@ -249,27 +249,6 @@ type Auction {
|
|||||||
bids: [Bid!]!
|
bids: [Bid!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRecordAttributes {
|
|
||||||
name: String
|
|
||||||
value: String
|
|
||||||
request: String
|
|
||||||
resourceType: String
|
|
||||||
version: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppDeploymentRecordAttributes {
|
|
||||||
application: String
|
|
||||||
auction: String
|
|
||||||
deployer: String
|
|
||||||
dns: String
|
|
||||||
meta: String
|
|
||||||
name: String
|
|
||||||
request: String
|
|
||||||
type: String
|
|
||||||
url: String
|
|
||||||
version: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input AuctionParams {
|
input AuctionParams {
|
||||||
maxPrice: String,
|
maxPrice: String,
|
||||||
numProviders: Int,
|
numProviders: Int,
|
||||||
@ -286,7 +265,6 @@ type Query {
|
|||||||
projectMembers(projectId: String!): [ProjectMember!]
|
projectMembers(projectId: String!): [ProjectMember!]
|
||||||
searchProjects(searchText: String!): [Project!]
|
searchProjects(searchText: String!): [Project!]
|
||||||
getAuctionData(auctionId: String!): Auction!
|
getAuctionData(auctionId: String!): Auction!
|
||||||
latestDNSRecord(projectId: String!): DNSRecordAttributes
|
|
||||||
domains(projectId: String!, filter: FilterDomainsInput): [Domain]
|
domains(projectId: String!, filter: FilterDomainsInput): [Domain]
|
||||||
deployers: [Deployer]
|
deployers: [Deployer]
|
||||||
address: String!
|
address: String!
|
||||||
|
@ -40,22 +40,6 @@ export interface AppDeploymentRecordAttributes {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DNSRecordAttributes {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
request: string;
|
|
||||||
resourceType: string;
|
|
||||||
version: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RegistryDNSRecordAttributes {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
request: string;
|
|
||||||
resource_type: string;
|
|
||||||
version: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppDeploymentRemovalRecordAttributes {
|
export interface AppDeploymentRemovalRecordAttributes {
|
||||||
deployment: string;
|
deployment: string;
|
||||||
request: string;
|
request: string;
|
||||||
@ -63,7 +47,7 @@ export interface AppDeploymentRemovalRecordAttributes {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegistryRecord {
|
interface RegistryRecord {
|
||||||
id: string;
|
id: string;
|
||||||
names: string[] | null;
|
names: string[] | null;
|
||||||
owners: string[];
|
owners: string[];
|
||||||
@ -80,10 +64,6 @@ export interface AppDeploymentRemovalRecord extends RegistryRecord {
|
|||||||
attributes: AppDeploymentRemovalRecordAttributes;
|
attributes: AppDeploymentRemovalRecordAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DNSRecord extends RegistryRecord {
|
|
||||||
attributes: RegistryDNSRecordAttributes
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddProjectFromTemplateInput {
|
export interface AddProjectFromTemplateInput {
|
||||||
templateOwner: string;
|
templateOwner: string;
|
||||||
templateRepo: string;
|
templateRepo: string;
|
||||||
|
@ -3,5 +3,10 @@
|
|||||||
"id": "2379cf1f-a232-4ad2-ae14-4d881131cc26",
|
"id": "2379cf1f-a232-4ad2-ae14-4d881131cc26",
|
||||||
"name": "Deploy Tools",
|
"name": "Deploy Tools",
|
||||||
"slug": "deploy-tools"
|
"slug": "deploy-tools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb",
|
||||||
|
"name": "Laconic",
|
||||||
|
"slug": "laconic-2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -10,7 +10,7 @@ import { Deployment, DeploymentStatus, Environment } from '../src/entity/Deploym
|
|||||||
const log = debug('snowball:publish-deploy-records');
|
const log = debug('snowball:publish-deploy-records');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const { registryConfig, database } = await getConfig();
|
const { registryConfig, database, misc } = await getConfig();
|
||||||
|
|
||||||
const registry = new Registry(
|
const registry = new Registry(
|
||||||
registryConfig.gqlEndpoint,
|
registryConfig.gqlEndpoint,
|
||||||
|
1
packages/deployer/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
records/*.yml
|
|
@ -1,8 +1,8 @@
|
|||||||
services:
|
services:
|
||||||
registry:
|
registry:
|
||||||
rpcEndpoint: https://laconicd-mainnet-1.laconic.com
|
rpcEndpoint: https://laconicd-sapo.laconic.com
|
||||||
gqlEndpoint: https://laconicd-mainnet-1.laconic.com/api
|
gqlEndpoint: https://laconicd-sapo.laconic.com/api
|
||||||
userKey:
|
userKey:
|
||||||
bondId:
|
bondId:
|
||||||
chainId: laconic-mainnet
|
chainId: laconic_9000-2
|
||||||
gasPrice: 0.001alnt
|
gasPrice: 1alnt
|
||||||
|
@ -8,11 +8,8 @@ echo "Using AUTHORITY: $AUTHORITY"
|
|||||||
# Repository URL
|
# Repository URL
|
||||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
||||||
|
|
||||||
# Get the latest commit hash for a branch
|
# Get the latest commit hash from the repository
|
||||||
BRANCH_NAME="main"
|
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||||
LATEST_HASH=$(git ls-remote $REPO_URL refs/heads/$BRANCH_NAME | awk '{print $1}')
|
|
||||||
|
|
||||||
echo "Latest commit hash for branch $BRANCH_NAME: $LATEST_HASH"
|
|
||||||
|
|
||||||
# Extract version from ../frontend/package.json
|
# Extract version from ../frontend/package.json
|
||||||
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
|
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
|
||||||
@ -102,9 +99,6 @@ fi
|
|||||||
# Get payment address for deployer
|
# Get payment address for deployer
|
||||||
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
|
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
|
||||||
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment' | sed 's/alnt//g')
|
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment' | sed 's/alnt//g')
|
||||||
|
|
||||||
echo "Paying address: $paymentAddress with amount $paymentAmount..."
|
|
||||||
|
|
||||||
# Pay deployer if paymentAmount is not null
|
# Pay deployer if paymentAmount is not null
|
||||||
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
|
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
|
||||||
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
|
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
|
||||||
@ -125,17 +119,17 @@ record:
|
|||||||
name: deploy-frontend@$PACKAGE_VERSION
|
name: deploy-frontend@$PACKAGE_VERSION
|
||||||
application: lrn://$AUTHORITY/applications/deploy-frontend@$PACKAGE_VERSION
|
application: lrn://$AUTHORITY/applications/deploy-frontend@$PACKAGE_VERSION
|
||||||
deployer: $DEPLOYER_LRN
|
deployer: $DEPLOYER_LRN
|
||||||
dns: deploy.laconic.com
|
dns: deploy
|
||||||
config:
|
config:
|
||||||
env:
|
env:
|
||||||
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.laconic.com
|
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.apps.vaasl.io
|
||||||
LACONIC_HOSTED_CONFIG_github_clientid: Ov23li4NtYybQlF6u5Dk
|
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liaet4yc0KX0iM1c
|
||||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
||||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
||||||
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15
|
||||||
LACONIC_HOSTED_CONFIG_wallet_iframe_url: https://wallet.laconic.com
|
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
||||||
meta:
|
meta:
|
||||||
note: Added @ $CURRENT_DATE_TIME
|
note: Added by Snowball @ $CURRENT_DATE_TIME
|
||||||
repository: "$REPO_URL"
|
repository: "$REPO_URL"
|
||||||
repository_ref: $LATEST_HASH
|
repository_ref: $LATEST_HASH
|
||||||
payment: $txHash
|
payment: $txHash
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
source .env
|
|
||||||
echo "Using REGISTRY_BOND_ID: $REGISTRY_BOND_ID"
|
|
||||||
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
|
||||||
echo "Using AUTHORITY: $AUTHORITY"
|
|
||||||
|
|
||||||
# Repository URL
|
# Repository URL
|
||||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
||||||
|
|
||||||
@ -17,33 +12,63 @@ PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
|
|||||||
# Current date and time for note
|
# Current date and time for note
|
||||||
CURRENT_DATE_TIME=$(date -u)
|
CURRENT_DATE_TIME=$(date -u)
|
||||||
|
|
||||||
CONFIG_FILE=config.yml
|
CONFIG_FILE=config.staging.yml
|
||||||
|
REGISTRY_BOND_ID="098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661"
|
||||||
|
|
||||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||||
|
|
||||||
# Get latest version from registry and increment application-record version
|
# Get latest version from registry and increment application-record version
|
||||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "deploy-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "staging-snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||||
|
|
||||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||||
# Set application-record version if no previous records were found
|
# Set application-record version if no previous records were found
|
||||||
NEW_APPLICATION_VERSION=0.0.1
|
NEW_APPLICATION_VERSION=0.0.1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Generate application-deployment-request.yml
|
||||||
|
cat >./staging-records/application-deployment-request.yml <<EOF
|
||||||
|
record:
|
||||||
|
type: ApplicationDeploymentRequest
|
||||||
|
version: '1.0.0'
|
||||||
|
name: staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||||
|
application: lrn://staging-snowballtools/applications/staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||||
|
dns: dashboard.staging.apps.snowballtools.com
|
||||||
|
config:
|
||||||
|
env:
|
||||||
|
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
|
||||||
|
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liOaoahRTYd4nSCV
|
||||||
|
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
|
||||||
|
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/test-progressive-web-app
|
||||||
|
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools/image-upload-pwa-example
|
||||||
|
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||||
|
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
||||||
|
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
|
||||||
|
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
|
||||||
|
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
|
||||||
|
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: dashboard.staging.apps.snowballtools.com
|
||||||
|
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com
|
||||||
|
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
|
||||||
|
meta:
|
||||||
|
note: Added by Snowball @ $CURRENT_DATE_TIME
|
||||||
|
repository: "$REPO_URL"
|
||||||
|
repository_ref: $LATEST_HASH
|
||||||
|
EOF
|
||||||
|
|
||||||
# Generate application-record.yml with incremented version
|
# Generate application-record.yml with incremented version
|
||||||
cat >./records/application-record.yml <<EOF
|
cat >./staging-records/application-record.yml <<EOF
|
||||||
record:
|
record:
|
||||||
type: ApplicationRecord
|
type: ApplicationRecord
|
||||||
version: $NEW_APPLICATION_VERSION
|
version: $NEW_APPLICATION_VERSION
|
||||||
repository_ref: $LATEST_HASH
|
repository_ref: $LATEST_HASH
|
||||||
repository: ["$REPO_URL"]
|
repository: ["$REPO_URL"]
|
||||||
app_type: webapp
|
app_type: webapp
|
||||||
name: deploy-frontend
|
name: staging-snowballtools-base-frontend
|
||||||
app_version: $PACKAGE_VERSION
|
app_version: $PACKAGE_VERSION
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Files generated successfully"
|
echo "Files generated successfully."
|
||||||
|
|
||||||
RECORD_FILE=records/application-record.yml
|
RECORD_FILE=staging-records/application-record.yml
|
||||||
|
|
||||||
# Publish ApplicationRecord
|
# Publish ApplicationRecord
|
||||||
publish_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
publish_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
||||||
@ -57,7 +82,7 @@ echo "ApplicationRecord published"
|
|||||||
echo $RECORD_ID
|
echo $RECORD_ID
|
||||||
|
|
||||||
# Set name to record
|
# Set name to record
|
||||||
REGISTRY_APP_LRN="lrn://$AUTHORITY/applications/deploy-frontend"
|
REGISTRY_APP_LRN="lrn://staging-snowballtools/applications/staging-snowballtools-base-frontend"
|
||||||
|
|
||||||
sleep 2
|
sleep 2
|
||||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||||
@ -96,46 +121,7 @@ if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get payment address for deployer
|
RECORD_FILE=staging-records/application-deployment-request.yml
|
||||||
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
|
|
||||||
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment' | sed 's/alnt//g')
|
|
||||||
# Pay deployer if paymentAmount is not null
|
|
||||||
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
|
|
||||||
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
|
|
||||||
|
|
||||||
# Extract the transaction hash
|
|
||||||
txHash=$(echo "$payment" | jq -r '.tx.hash')
|
|
||||||
echo "Paid deployer with txHash as $txHash"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "Payment amount is null; skipping payment."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate application-deployment-request.yml
|
|
||||||
cat >./records/application-deployment-request.yml <<EOF
|
|
||||||
record:
|
|
||||||
type: ApplicationDeploymentRequest
|
|
||||||
version: '1.0.0'
|
|
||||||
name: deploy-frontend@$PACKAGE_VERSION
|
|
||||||
application: lrn://$AUTHORITY/applications/deploy-frontend@$PACKAGE_VERSION
|
|
||||||
deployer: $DEPLOYER_LRN
|
|
||||||
dns: deploy
|
|
||||||
config:
|
|
||||||
env:
|
|
||||||
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.apps.vaasl.io
|
|
||||||
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liaet4yc0KX0iM1c
|
|
||||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
|
||||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
|
||||||
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
|
|
||||||
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
|
|
||||||
meta:
|
|
||||||
note: Added by Snowball @ $CURRENT_DATE_TIME
|
|
||||||
repository: "$REPO_URL"
|
|
||||||
repository_ref: $LATEST_HASH
|
|
||||||
payment: $txHash
|
|
||||||
EOF
|
|
||||||
|
|
||||||
RECORD_FILE=records/application-deployment-request.yml
|
|
||||||
|
|
||||||
sleep 2
|
sleep 2
|
||||||
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
17
packages/deployer/records/application-deployment-request.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
record:
|
||||||
|
type: ApplicationDeploymentRequest
|
||||||
|
version: '1.0.0'
|
||||||
|
name: deploy-frontend@1.0.0
|
||||||
|
application: lrn://vaasl/applications/deploy-frontend@1.0.0
|
||||||
|
dns: deploy
|
||||||
|
config:
|
||||||
|
env:
|
||||||
|
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.apps.vaasl.io
|
||||||
|
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liaet4yc0KX0iM1c
|
||||||
|
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
|
||||||
|
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
|
||||||
|
LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15
|
||||||
|
meta:
|
||||||
|
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
|
||||||
|
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||||
|
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
8
packages/deployer/records/application-record.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
record:
|
||||||
|
type: ApplicationRecord
|
||||||
|
version: 0.0.2
|
||||||
|
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
||||||
|
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
||||||
|
app_type: webapp
|
||||||
|
name: deploy-frontend
|
||||||
|
app_version: 1.0.0
|
@ -1,56 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source .env
|
|
||||||
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
|
|
||||||
|
|
||||||
# Generate application-deployment-removal-request.yml
|
|
||||||
REMOVAL_REQUEST_RECORD_FILE=records/application-deployment-removal-request.yml
|
|
||||||
# TODO: Pass deployment record ID as arg
|
|
||||||
DEPLOYMENT_RECORD_ID=bafyreidjho77xeczaqpyawhc4wbpm5it5atibtuxk6ost6vnpu2svlp3ka
|
|
||||||
|
|
||||||
cat > $REMOVAL_REQUEST_RECORD_FILE <<EOF
|
|
||||||
record:
|
|
||||||
deployer: $DEPLOYER_LRN
|
|
||||||
deployment: $DEPLOYMENT_RECORD_ID
|
|
||||||
type: ApplicationDeploymentRemovalRequest
|
|
||||||
version: 1.0.0
|
|
||||||
EOF
|
|
||||||
|
|
||||||
CONFIG_FILE=config.yml
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
REMOVAL_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
|
|
||||||
echo "ApplicationDeploymentRemovalRequest published"
|
|
||||||
echo $REMOVAL_REQUEST_ID
|
|
||||||
|
|
||||||
# Deployment checks
|
|
||||||
RETRY_INTERVAL=30
|
|
||||||
MAX_RETRIES=20
|
|
||||||
|
|
||||||
# Check that an ApplicationDeploymentRemovalRecord is published
|
|
||||||
retry_count=0
|
|
||||||
while true; do
|
|
||||||
removal_records_response=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
|
|
||||||
len_removal_records=$(echo $removal_records_response | jq 'length')
|
|
||||||
|
|
||||||
# Check if number of records returned is 0
|
|
||||||
if [ $len_removal_records -eq 0 ]; then
|
|
||||||
# Check if retries are exhausted
|
|
||||||
if [ $retry_count -eq $MAX_RETRIES ]; then
|
|
||||||
echo "Retries exhausted"
|
|
||||||
echo "ApplicationDeploymentRemovalRecord for deployment removal request $REMOVAL_REQUEST_ID not found"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "ApplicationDeploymentRemovalRecord not found, retrying in $RETRY_INTERVAL sec..."
|
|
||||||
sleep $RETRY_INTERVAL
|
|
||||||
retry_count=$((retry_count+1))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "ApplicationDeploymentRemovalRecord found"
|
|
||||||
REMOVAL_RECORD_ID=$(echo $removal_records_response | jq -r '.[0].id')
|
|
||||||
echo $REMOVAL_RECORD_ID
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Deployment removal successful"
|
|
@ -0,0 +1,24 @@
|
|||||||
|
record:
|
||||||
|
type: ApplicationDeploymentRequest
|
||||||
|
version: '1.0.0'
|
||||||
|
name: staging-snowballtools-base-frontend@0.0.0
|
||||||
|
application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@0.0.0
|
||||||
|
dns: dashboard.staging.apps.snowballtools.com
|
||||||
|
config:
|
||||||
|
env:
|
||||||
|
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
|
||||||
|
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liOaoahRTYd4nSCV
|
||||||
|
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
|
||||||
|
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/test-progressive-web-app
|
||||||
|
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools/image-upload-pwa-example
|
||||||
|
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||||
|
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
|
||||||
|
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
|
||||||
|
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
|
||||||
|
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: dashboard.staging.apps.snowballtools.com
|
||||||
|
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com
|
||||||
|
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
|
||||||
|
meta:
|
||||||
|
note: Added by Snowball @ Mon Jun 24 23:51:48 UTC 2024
|
||||||
|
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||||
|
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
|
8
packages/deployer/staging-records/application-record.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
record:
|
||||||
|
type: ApplicationRecord
|
||||||
|
version: 0.0.1
|
||||||
|
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
|
||||||
|
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
||||||
|
app_type: webapp
|
||||||
|
name: staging-snowballtools-base-frontend
|
||||||
|
app_version: 0.0.0
|
@ -3,13 +3,17 @@ VITE_SERVER_URL='http://localhost:8000'
|
|||||||
VITE_GITHUB_CLIENT_ID=
|
VITE_GITHUB_CLIENT_ID=
|
||||||
VITE_GITHUB_PWA_TEMPLATE_REPO="snowball-tools/test-progressive-web-app"
|
VITE_GITHUB_PWA_TEMPLATE_REPO="snowball-tools/test-progressive-web-app"
|
||||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO="snowball-tools/image-upload-pwa-example"
|
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO="snowball-tools/image-upload-pwa-example"
|
||||||
VITE_GITHUB_NEXT_APP_TEMPLATE_REPO="snowball-tools/starter.nextjs-react-tailwind"
|
|
||||||
|
VITE_WALLET_CONNECT_ID=
|
||||||
|
|
||||||
VITE_LIT_RELAY_API_KEY=
|
VITE_LIT_RELAY_API_KEY=
|
||||||
|
|
||||||
|
VITE_ALCHEMY_API_KEY=
|
||||||
|
|
||||||
VITE_BUGSNAG_API_KEY=
|
VITE_BUGSNAG_API_KEY=
|
||||||
|
|
||||||
VITE_PASSKEY_WALLET_RPID=
|
VITE_PASSKEY_WALLET_RPID=
|
||||||
VITE_TURNKEY_API_BASE_URL=
|
VITE_TURNKEY_API_BASE_URL=
|
||||||
|
VITE_TURNKEY_ORGANIZATION_ID=
|
||||||
|
|
||||||
VITE_WALLET_IFRAME_URL=
|
VITE_LACONICD_CHAIN_ID=
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" class="dark dark:bg-background dark:text-foreground">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="description" content="laconic tools dashboard" />
|
<meta name="description" content="snowball tools dashboard" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<meta name="msapplication-TileColor" content="#2d89ef" />
|
<meta name="msapplication-TileColor" content="#2d89ef" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<title>Laconic</title>
|
<title>Snowball</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -41,12 +41,13 @@
|
|||||||
"@turnkey/http": "^2.10.0",
|
"@turnkey/http": "^2.10.0",
|
||||||
"@turnkey/sdk-react": "^0.1.0",
|
"@turnkey/sdk-react": "^0.1.0",
|
||||||
"@turnkey/webauthn-stamper": "^0.5.0",
|
"@turnkey/webauthn-stamper": "^0.5.0",
|
||||||
|
"@walletconnect/ethereum-provider": "^2.12.2",
|
||||||
"@web3modal/siwe": "4.0.5",
|
"@web3modal/siwe": "4.0.5",
|
||||||
|
"@web3modal/wagmi": "4.0.5",
|
||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
"ethers": "^5.6.2",
|
|
||||||
"downshift": "^8.3.2",
|
"downshift": "^8.3.2",
|
||||||
"framer-motion": "^11.0.8",
|
"framer-motion": "^11.0.8",
|
||||||
"gql-client": "^1.0.0",
|
"gql-client": "^1.0.0",
|
||||||
@ -68,6 +69,7 @@
|
|||||||
"usehooks-ts": "^2.15.1",
|
"usehooks-ts": "^2.15.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"viem": "^2.7.11",
|
"viem": "^2.7.11",
|
||||||
|
"wagmi": "2.5.7",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
packages/frontend/public/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packages/frontend/public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
packages/frontend/public/favicon-16x16.png
Normal file
After Width: | Height: | Size: 674 B |
BIN
packages/frontend/public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 7.2 KiB |
@ -1,4 +0,0 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect width="48" height="48" rx="4" fill="#29292E"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.0494 24.6233C18.8425 21.8302 20.5713 17.973 20.5706 13.7142C20.5717 13.1361 20.5396 12.5645 20.4762 12L12 12.0008L12.0003 28.2867C11.9996 30.2608 12.7522 32.2356 14.2578 33.7411C15.7633 35.2466 17.7395 36.0001 19.7139 35.9991L19.7134 35.9996L36 36L35.9995 27.5227C35.4362 27.4605 34.8645 27.4285 34.2852 27.4284C30.0275 27.4289 26.1701 29.1577 23.377 31.9507C21.3446 33.9321 18.0858 33.9325 16.0785 31.9252C14.0722 29.9191 14.0715 26.6593 16.0494 24.6233ZM34.2419 13.7624C31.9012 11.4217 28.0982 11.4208 25.7566 13.7624C23.4151 16.1038 23.4159 19.9067 25.7566 22.2473C28.0986 24.5892 31.9004 24.5889 34.2419 22.2473C36.5835 19.9059 36.5839 16.1042 34.2419 13.7624Z" fill="#FBFBFB"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 892 B |
@ -1,10 +1 @@
|
|||||||
<svg width="115" height="20" viewBox="0 0 115 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="500" height="500" fill="#0F86F5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M191.873 125.126C224.893 126.765 250.458 150.121 274.042 172.995C297.925 196.158 323.089 221.108 324.868 254.114C326.718 288.42 308.902 321.108 283.281 344.355C258.67 366.687 225.288 373.859 191.873 374.788C157.228 375.752 119.038 374.394 95.1648 349.588C71.6207 325.125 74.6696 287.843 75.7341 254.114C76.7518 221.865 79.2961 188.525 101.009 164.41C123.845 139.047 157.543 123.423 191.873 125.126Z" fill="#4BA4F7"/><path fill-rule="evenodd" clip-rule="evenodd" d="M229.373 125.126C262.393 126.765 287.958 150.121 311.542 172.995C335.425 196.158 360.589 221.108 362.368 254.114C364.218 288.42 346.402 321.108 320.781 344.355C296.17 366.687 262.788 373.859 229.373 374.788C194.728 375.752 156.538 374.394 132.665 349.588C109.121 325.125 112.17 287.843 113.234 254.114C114.252 221.865 116.796 188.525 138.509 164.41C161.345 139.047 195.043 123.423 229.373 125.126Z" fill="#8AC4FA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M266.873 125.126C299.893 126.765 325.458 150.121 349.042 172.995C372.925 196.158 398.089 221.108 399.868 254.114C401.718 288.42 383.902 321.108 358.281 344.355C333.67 366.687 300.288 373.859 266.873 374.788C232.228 375.752 194.038 374.394 170.165 349.588C146.621 325.125 149.67 287.843 150.734 254.114C151.752 221.865 154.296 188.525 176.009 164.41C198.845 139.047 232.543 123.423 266.873 125.126Z" fill="#CAE4FD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M304.373 125.126C337.393 126.765 362.958 150.121 386.542 172.995C410.425 196.158 435.589 221.108 437.368 254.114C439.218 288.42 421.402 321.108 395.781 344.355C371.17 366.687 337.788 373.859 304.373 374.788C269.728 375.752 231.538 374.394 207.665 349.588C184.121 325.125 187.17 287.843 188.234 254.114C189.252 221.865 191.796 188.525 213.509 164.41C236.345 139.047 270.043 123.423 304.373 125.126Z" fill="white"/></svg>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z" fill="#FBFBFB"/>
|
|
||||||
<path d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z" fill="#FBFBFB"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
packages/frontend/public/mstile-144x144.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
packages/frontend/public/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
packages/frontend/public/mstile-310x150.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
packages/frontend/public/mstile-310x310.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
packages/frontend/public/mstile-70x70.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"chainId": "laconic-mainnet",
|
|
||||||
"networkName": "laconicd mainnet",
|
|
||||||
"namespace": "cosmos",
|
|
||||||
"rpcUrl": "https://laconicd-mainnet-1.laconic.com",
|
|
||||||
"blockExplorerUrl": "",
|
|
||||||
"nativeDenom": "alnt",
|
|
||||||
"addressPrefix": "laconic",
|
|
||||||
"coinType": 118,
|
|
||||||
"gasPrice": 0.001
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "Laconic Tools Dashboard",
|
"name": "Snowball Tools Dashboard",
|
||||||
"short_name": "snowball tools",
|
"short_name": "snowball tools",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
@ -11,8 +11,8 @@ import ProjectSearchLayout from './layouts/ProjectSearch';
|
|||||||
import Index from './pages';
|
import Index from './pages';
|
||||||
import AuthPage from './pages/AuthPage';
|
import AuthPage from './pages/AuthPage';
|
||||||
import { DashboardLayout } from './pages/org-slug/layout';
|
import { DashboardLayout } from './pages/org-slug/layout';
|
||||||
|
import Web3Provider from 'context/Web3Provider';
|
||||||
import { BASE_URL } from 'utils/constants';
|
import { BASE_URL } from 'utils/constants';
|
||||||
import BuyPrepaidService from './pages/BuyPrepaidService';
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -50,10 +50,6 @@ const router = createBrowserRouter([
|
|||||||
path: '/login',
|
path: '/login',
|
||||||
element: <AuthPage />,
|
element: <AuthPage />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/buy-prepaid-service',
|
|
||||||
element: <BuyPrepaidService />,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@ -79,7 +75,9 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouterProvider router={router} />
|
<Web3Provider>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</Web3Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO,
|
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO,
|
||||||
VITE_GITHUB_PWA_TEMPLATE_REPO,
|
VITE_GITHUB_PWA_TEMPLATE_REPO,
|
||||||
VITE_GITHUB_NEXT_APP_TEMPLATE_REPO,
|
|
||||||
} from 'utils/constants';
|
} from 'utils/constants';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Progressive Web App (PWA)',
|
name: 'Progressive Web App (PWA)',
|
||||||
icon: 'web',
|
icon: 'pwa',
|
||||||
repoFullName: `${VITE_GITHUB_PWA_TEMPLATE_REPO}`,
|
repoFullName: `${VITE_GITHUB_PWA_TEMPLATE_REPO}`,
|
||||||
isComingSoon: false,
|
isComingSoon: false,
|
||||||
},
|
},
|
||||||
@ -21,9 +20,23 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
name: 'Next.js + React + TailwindCSS',
|
name: 'Kotlin',
|
||||||
icon: 'web',
|
icon: 'kotlin',
|
||||||
repoFullName: `${VITE_GITHUB_NEXT_APP_TEMPLATE_REPO}`,
|
repoFullName: '',
|
||||||
isComingSoon: false,
|
isComingSoon: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: 'React Native',
|
||||||
|
icon: 'react-native',
|
||||||
|
repoFullName: '',
|
||||||
|
isComingSoon: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
name: 'Swift',
|
||||||
|
icon: 'swift',
|
||||||
|
repoFullName: '',
|
||||||
|
isComingSoon: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Heading } from './shared/Heading';
|
||||||
|
|
||||||
interface LogoProps {
|
interface LogoProps {
|
||||||
orgSlug?: string;
|
orgSlug?: string;
|
||||||
@ -8,7 +9,14 @@ export const Logo = ({ orgSlug }: LogoProps) => {
|
|||||||
return (
|
return (
|
||||||
<Link to={`/${orgSlug}`}>
|
<Link to={`/${orgSlug}`}>
|
||||||
<div className="flex items-center gap-3 px-0 lg:px-2">
|
<div className="flex items-center gap-3 px-0 lg:px-2">
|
||||||
<img src="/logo.svg" alt="Snowball Logo" />
|
<img
|
||||||
|
src="/logo.svg"
|
||||||
|
alt="Snowball Logo"
|
||||||
|
className="lg:h-10 lg:w-10 h-8 w-8 rounded-lg"
|
||||||
|
/>
|
||||||
|
<Heading className="lg:text-[24px] text-[19px] font-semibold">
|
||||||
|
Snowball
|
||||||
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,7 @@ const SearchBar: React.ForwardRefRenderFunction<
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex w-full">
|
<div className="relative flex w-full">
|
||||||
<Input
|
<Input
|
||||||
leftIcon={<SearchIcon className="text-foreground-secondary" />}
|
leftIcon={<SearchIcon />}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={value}
|
value={value}
|
||||||
type="search"
|
type="search"
|
||||||
|
@ -24,8 +24,8 @@ const Stepper = ({ activeStep, stepperValues }: StepperProps) => {
|
|||||||
<div
|
<div
|
||||||
className={`text-sm ${
|
className={`text-sm ${
|
||||||
activeStep === stepperValue.step
|
activeStep === stepperValue.step
|
||||||
? 'text-black font-semibold dark:text-foreground'
|
? 'text-black font-semibold'
|
||||||
: 'text-gray-600 dark:text-foreground-secondary'
|
: 'text-gray-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{stepperValue.label}
|
{stepperValue.label}
|
||||||
|
@ -47,7 +47,6 @@ export const ChangeStateToProductionDialog = ({
|
|||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
open={open}
|
open={open}
|
||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleConfirm}
|
||||||
confirmButtonTitle={isConfirmButtonLoading ? 'Redeploying' : 'Redeploy'}
|
|
||||||
confirmButtonProps={{
|
confirmButtonProps={{
|
||||||
disabled: isConfirmButtonLoading,
|
disabled: isConfirmButtonLoading,
|
||||||
rightIcon: isConfirmButtonLoading ? (
|
rightIcon: isConfirmButtonLoading ? (
|
||||||
|
@ -4,9 +4,7 @@ export const projectCardTheme = tv({
|
|||||||
slots: {
|
slots: {
|
||||||
wrapper: [
|
wrapper: [
|
||||||
'bg-surface-card',
|
'bg-surface-card',
|
||||||
'dark:bg-overlay2',
|
|
||||||
'shadow-card',
|
'shadow-card',
|
||||||
'dark:shadow-background',
|
|
||||||
'rounded-2xl',
|
'rounded-2xl',
|
||||||
'flex',
|
'flex',
|
||||||
'flex-col',
|
'flex-col',
|
||||||
@ -19,16 +17,10 @@ export const projectCardTheme = tv({
|
|||||||
'text-sm',
|
'text-sm',
|
||||||
'font-medium',
|
'font-medium',
|
||||||
'text-elements-high-em',
|
'text-elements-high-em',
|
||||||
'dark:text-foreground',
|
|
||||||
'tracking-[-0.006em]',
|
'tracking-[-0.006em]',
|
||||||
'truncate',
|
'truncate',
|
||||||
],
|
],
|
||||||
description: [
|
description: ['text-xs', 'text-elements-low-em', 'truncate'],
|
||||||
'text-xs',
|
|
||||||
'text-elements-low-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
'truncate',
|
|
||||||
],
|
|
||||||
icons: ['flex', 'items-center', 'gap-1'],
|
icons: ['flex', 'items-center', 'gap-1'],
|
||||||
lowerContent: [
|
lowerContent: [
|
||||||
'transition-colors',
|
'transition-colors',
|
||||||
@ -40,7 +32,6 @@ export const projectCardTheme = tv({
|
|||||||
'gap-2',
|
'gap-2',
|
||||||
'rounded-b-2xl',
|
'rounded-b-2xl',
|
||||||
'group-hover:bg-surface-card-hovered',
|
'group-hover:bg-surface-card-hovered',
|
||||||
'dark:group-hover:bg-overlay3',
|
|
||||||
],
|
],
|
||||||
latestDeployment: ['flex', 'items-center', 'gap-2'],
|
latestDeployment: ['flex', 'items-center', 'gap-2'],
|
||||||
deploymentStatusContainer: [
|
deploymentStatusContainer: [
|
||||||
@ -51,15 +42,10 @@ export const projectCardTheme = tv({
|
|||||||
'justify-center',
|
'justify-center',
|
||||||
],
|
],
|
||||||
deploymentStatus: ['w-1', 'h-1', 'rounded-full'],
|
deploymentStatus: ['w-1', 'h-1', 'rounded-full'],
|
||||||
deploymentName: [
|
deploymentName: ['text-xs', 'text-elements-low-em'],
|
||||||
'text-xs',
|
|
||||||
'text-elements-low-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
deploymentText: [
|
deploymentText: [
|
||||||
'text-xs',
|
'text-xs',
|
||||||
'text-elements-low-em',
|
'text-elements-low-em',
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
'font-mono',
|
'font-mono',
|
||||||
'flex',
|
'flex',
|
||||||
'items-center',
|
'items-center',
|
||||||
@ -67,11 +53,9 @@ export const projectCardTheme = tv({
|
|||||||
],
|
],
|
||||||
wavyBorder: [
|
wavyBorder: [
|
||||||
'bg-surface-card',
|
'bg-surface-card',
|
||||||
'dark:bg-background',
|
|
||||||
'transition-colors',
|
'transition-colors',
|
||||||
'duration-150',
|
'duration-150',
|
||||||
'group-hover:bg-surface-card-hovered',
|
'group-hover:bg-surface-card-hovered',
|
||||||
'dark:group-hover:bg-overlay2',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
@ -83,7 +67,7 @@ export const projectCardTheme = tv({
|
|||||||
deploymentStatus: ['bg-orange-400'],
|
deploymentStatus: ['bg-orange-400'],
|
||||||
},
|
},
|
||||||
failure: {
|
failure: {
|
||||||
deploymentStatus: ['bg-error'],
|
deploymentStatus: ['bg-rose-500'],
|
||||||
},
|
},
|
||||||
pending: {
|
pending: {
|
||||||
deploymentStatus: ['bg-gray-500'],
|
deploymentStatus: ['bg-gray-500'],
|
||||||
|
@ -4,11 +4,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
} from '@snowballtools/material-tailwind-react-fork';
|
} from '@snowballtools/material-tailwind-react-fork';
|
||||||
import {
|
import { ComponentPropsWithoutRef, MouseEvent, useCallback } from 'react';
|
||||||
ComponentPropsWithoutRef,
|
|
||||||
MouseEvent,
|
|
||||||
useCallback,
|
|
||||||
} from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Project } from 'gql-client';
|
import { Project } from 'gql-client';
|
||||||
import { Avatar } from 'components/shared/Avatar';
|
import { Avatar } from 'components/shared/Avatar';
|
||||||
@ -87,12 +83,12 @@ export const ProjectCard = ({
|
|||||||
<p className={theme.title()}>{project.name}</p>
|
<p className={theme.title()}>{project.name}</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<p className={theme.description()}>
|
<p className={theme.description()}>
|
||||||
{project.deployments[0]?.applicationDeploymentRecordData?.url ?? 'No domain'}
|
{project.deployments[0]?.domain?.name ?? 'No domain'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Icons */}
|
{/* Icons */}
|
||||||
<div className={theme.icons()}>
|
<div className={theme.icons()}>
|
||||||
{hasError && <WarningDiamondIcon className="text-error" />}
|
{hasError && <WarningDiamondIcon className="text-elements-danger" />}
|
||||||
<Menu placement="bottom-end">
|
<Menu placement="bottom-end">
|
||||||
<MenuHandler>
|
<MenuHandler>
|
||||||
<Button
|
<Button
|
||||||
@ -105,15 +101,12 @@ export const ProjectCard = ({
|
|||||||
<HorizontalDotIcon />
|
<HorizontalDotIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</MenuHandler>
|
</MenuHandler>
|
||||||
<MenuList className="dark:bg-overlay3 dark:shadow-background dark:border-none">
|
<MenuList>
|
||||||
<MenuItem
|
<MenuItem onClick={navigateToSettingsOnClick}>
|
||||||
onClick={navigateToSettingsOnClick}
|
|
||||||
className="text-foreground"
|
|
||||||
>
|
|
||||||
Project settings
|
Project settings
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className="text-error"
|
className="text-red-500"
|
||||||
onClick={navigateToSettingsOnClick}
|
onClick={navigateToSettingsOnClick}
|
||||||
>
|
>
|
||||||
Delete project
|
Delete project
|
||||||
|
@ -59,12 +59,12 @@ export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => {
|
|||||||
}, [fetchProjects, debouncedInputValue]);
|
}, [fetchProjects, debouncedInputValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full lg:w-fit dark:bg-overlay">
|
<div className="relative w-full lg:w-fit">
|
||||||
<SearchBar {...getInputProps()} />
|
<SearchBar {...getInputProps()} />
|
||||||
<div
|
<div
|
||||||
{...getMenuProps({}, { suppressRefError: true })}
|
{...getMenuProps({}, { suppressRefError: true })}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col shadow-dropdown rounded-xl dark:bg-overlay2 bg-surface-card absolute w-[459px] max-h-52 overflow-y-auto px-2 py-2 gap-1 z-50',
|
'flex flex-col shadow-dropdown rounded-xl bg-surface-card absolute w-[459px] max-h-52 overflow-y-auto px-2 py-2 gap-1 z-50',
|
||||||
{ hidden: !inputValue || !isOpen },
|
{ hidden: !inputValue || !isOpen },
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -13,10 +13,10 @@ export const ProjectSearchBarEmpty = ({
|
|||||||
{...props}
|
{...props}
|
||||||
className={cn('flex items-center px-2 py-2 gap-3', className)}
|
className={cn('flex items-center px-2 py-2 gap-3', className)}
|
||||||
>
|
>
|
||||||
<div className="w-8 h-8 rounded-lg flex items-center justify-center bg-orange-50 text-elements-warning dark:bg-red-50 text-error">
|
<div className="w-8 h-8 rounded-lg flex items-center justify-center bg-orange-50 text-elements-warning">
|
||||||
<InfoRoundFilledIcon size={16} />
|
<InfoRoundFilledIcon size={16} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-elements-low-em text-sm dark:text-foreground-secondary tracking-[-0.006em]">
|
<p className="text-elements-low-em text-sm tracking-[-0.006em]">
|
||||||
No projects matching this name
|
No projects matching this name
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
|
|
||||||
import { Box, Modal } from '@mui/material';
|
|
||||||
|
|
||||||
import {
|
|
||||||
VITE_WALLET_IFRAME_URL,
|
|
||||||
} from 'utils/constants';
|
|
||||||
import { REQUEST_WALLET_ACCOUNTS, WALLET_ACCOUNTS_DATA } from '../../../constants';
|
|
||||||
import { useAddNetwork } from '../../../hooks/useAddNetwork';
|
|
||||||
|
|
||||||
const ApproveTransactionModal = ({
|
|
||||||
setAccount,
|
|
||||||
setIsDataReceived,
|
|
||||||
isVisible,
|
|
||||||
}: {
|
|
||||||
setAccount: (account: string) => void;
|
|
||||||
setIsDataReceived: (isReceived: boolean) => void;
|
|
||||||
isVisible: boolean;
|
|
||||||
}) => {
|
|
||||||
const { setIframe, isNetworkAvailable, networkData } = useAddNetwork();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
|
||||||
if (event.origin !== VITE_WALLET_IFRAME_URL) return;
|
|
||||||
|
|
||||||
if (event.data.type === WALLET_ACCOUNTS_DATA) {
|
|
||||||
setIsDataReceived(true);
|
|
||||||
|
|
||||||
if (event.data.data.length === 0) {
|
|
||||||
console.error(`Accounts not present for chainId: ${networkData?.chainId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setAccount(event.data.data[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.type === 'ERROR') {
|
|
||||||
console.error('Error from wallet:', event.data.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('message', handleMessage);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('message', handleMessage);
|
|
||||||
};
|
|
||||||
}, [networkData]);
|
|
||||||
|
|
||||||
const getDataFromWallet = useCallback(() => {
|
|
||||||
if (!networkData) {
|
|
||||||
console.error('networkData should not be empty');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const iframe = document.getElementById('walletIframe') as HTMLIFrameElement;
|
|
||||||
|
|
||||||
if (!iframe.contentWindow) {
|
|
||||||
console.error('Iframe not found or not loaded');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe.contentWindow.postMessage(
|
|
||||||
{
|
|
||||||
type: REQUEST_WALLET_ACCOUNTS,
|
|
||||||
chainId: networkData.chainId,
|
|
||||||
},
|
|
||||||
VITE_WALLET_IFRAME_URL,
|
|
||||||
);
|
|
||||||
}, [networkData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isNetworkAvailable) {
|
|
||||||
getDataFromWallet();
|
|
||||||
}
|
|
||||||
}, [isNetworkAvailable, getDataFromWallet])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal open={isVisible} disableEscapeKeyDown keepMounted>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: '90%',
|
|
||||||
maxWidth: '1200px',
|
|
||||||
height: '600px',
|
|
||||||
maxHeight: '80vh',
|
|
||||||
overflow: 'auto',
|
|
||||||
boxShadow: 24,
|
|
||||||
borderRadius: '8px',
|
|
||||||
outline: 'none',
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<iframe
|
|
||||||
onLoad={(event) => setIframe(event.target as HTMLIFrameElement)}
|
|
||||||
id="walletIframe"
|
|
||||||
src={`${VITE_WALLET_IFRAME_URL}/wallet-embed`}
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
sandbox="allow-scripts allow-same-origin"
|
|
||||||
className="border rounded-md shadow-sm"
|
|
||||||
></iframe>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ApproveTransactionModal;
|
|
@ -1,67 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { Modal } from '@mui/material';
|
|
||||||
|
|
||||||
import { VITE_WALLET_IFRAME_URL } from 'utils/constants';
|
|
||||||
import useCheckBalance from '../../../hooks/useCheckBalance';
|
|
||||||
import { useAddNetwork } from '../../../hooks/useAddNetwork';
|
|
||||||
|
|
||||||
const CHECK_BALANCE_INTERVAL = 5000;
|
|
||||||
const IFRAME_ID = 'checkBalanceIframe';
|
|
||||||
|
|
||||||
const CheckBalanceIframe = ({
|
|
||||||
onBalanceChange,
|
|
||||||
isPollingEnabled,
|
|
||||||
amount,
|
|
||||||
}: {
|
|
||||||
onBalanceChange: (value: boolean | undefined) => void;
|
|
||||||
isPollingEnabled: boolean;
|
|
||||||
amount: string;
|
|
||||||
}) => {
|
|
||||||
const { isBalanceSufficient, checkBalance } = useCheckBalance(
|
|
||||||
amount,
|
|
||||||
IFRAME_ID,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isNetworkAvailable, setIframe } = useAddNetwork();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isNetworkAvailable || isBalanceSufficient) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkBalance();
|
|
||||||
|
|
||||||
if (!isPollingEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
checkBalance();
|
|
||||||
}, CHECK_BALANCE_INTERVAL);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, [isBalanceSufficient, isPollingEnabled, checkBalance, isNetworkAvailable]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onBalanceChange(isBalanceSufficient);
|
|
||||||
}, [isBalanceSufficient]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal open={false} disableEscapeKeyDown keepMounted>
|
|
||||||
<iframe
|
|
||||||
onLoad={(event) => setIframe(event.target as HTMLIFrameElement)}
|
|
||||||
id={IFRAME_ID}
|
|
||||||
src={VITE_WALLET_IFRAME_URL}
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
sandbox="allow-scripts allow-same-origin"
|
|
||||||
className="border rounded-md shadow-sm"
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CheckBalanceIframe;
|
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState, useEffect, useMemo } from 'react';
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { FormProvider, FieldValues } from 'react-hook-form';
|
import { FormProvider, FieldValues } from 'react-hook-form';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
AuctionParams,
|
AuctionParams,
|
||||||
Deployer,
|
Deployer,
|
||||||
} from 'gql-client';
|
} from 'gql-client';
|
||||||
import { BigNumber } from 'ethers';
|
|
||||||
|
|
||||||
import { Select, MenuItem, FormControl, FormHelperText } from '@mui/material';
|
import { Select, MenuItem, FormControl, FormHelperText } from '@mui/material';
|
||||||
|
|
||||||
@ -21,19 +20,15 @@ import { Button } from '../../shared/Button';
|
|||||||
import { Input } from 'components/shared/Input';
|
import { Input } from 'components/shared/Input';
|
||||||
import { useToast } from 'components/shared/Toast';
|
import { useToast } from 'components/shared/Toast';
|
||||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||||
import ApproveTransactionModal from './ApproveTransactionModal';
|
|
||||||
import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm';
|
import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm';
|
||||||
import { EnvironmentVariablesFormValues } from 'types/types';
|
import { EnvironmentVariablesFormValues } from 'types/types';
|
||||||
import {
|
import ConnectWallet from './ConnectWallet';
|
||||||
VITE_WALLET_IFRAME_URL,
|
import { useWalletConnectClient } from 'context/WalletConnectContext';
|
||||||
} from 'utils/constants';
|
|
||||||
import CheckBalanceIframe from './CheckBalanceIframe';
|
|
||||||
import { useAddNetwork } from '../../../hooks/useAddNetwork';
|
|
||||||
|
|
||||||
type ConfigureDeploymentFormValues = {
|
type ConfigureDeploymentFormValues = {
|
||||||
option: string;
|
option: string;
|
||||||
lrn?: string;
|
lrn?: string;
|
||||||
numProviders?: string;
|
numProviders?: number;
|
||||||
maxPrice?: string;
|
maxPrice?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,19 +36,16 @@ type ConfigureFormValues = ConfigureDeploymentFormValues &
|
|||||||
EnvironmentVariablesFormValues;
|
EnvironmentVariablesFormValues;
|
||||||
|
|
||||||
const DEFAULT_MAX_PRICE = '10000';
|
const DEFAULT_MAX_PRICE = '10000';
|
||||||
const TX_APPROVAL_TIMEOUT_MS = 60000;
|
|
||||||
|
|
||||||
const Configure = () => {
|
const Configure = () => {
|
||||||
|
const { signClient, session, accounts } = useWalletConnectClient();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [deployers, setDeployers] = useState<Deployer[]>([]);
|
const [deployers, setDeployers] = useState<Deployer[]>([]);
|
||||||
const [selectedAccount, setSelectedAccount] = useState<string>();
|
const [selectedAccount, setSelectedAccount] = useState<string>();
|
||||||
const [selectedDeployer, setSelectedDeployer] = useState<Deployer>();
|
const [selectedDeployer, setSelectedDeployer] = useState<Deployer>();
|
||||||
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
const [isPaymentLoading, setIsPaymentLoading] = useState(false);
|
||||||
const [isPaymentDone, setIsPaymentDone] = useState(false);
|
const [isPaymentDone, setIsPaymentDone] = useState(false);
|
||||||
const [isFrameVisible, setIsFrameVisible] = useState(false);
|
|
||||||
const [isAccountsDataReceived, setIsAccountsDataReceived] = useState(false);
|
|
||||||
const [balanceMessage, setBalanceMessage] = useState<string>();
|
|
||||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>();
|
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const templateId = searchParams.get('templateId');
|
const templateId = searchParams.get('templateId');
|
||||||
@ -71,47 +63,22 @@ const Configure = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast, dismiss } = useToast();
|
const { toast, dismiss } = useToast();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
const { networkData } = useAddNetwork()
|
|
||||||
|
|
||||||
const methods = useForm<ConfigureFormValues>({
|
const methods = useForm<ConfigureFormValues>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
option: 'Auction',
|
option: 'Auction',
|
||||||
maxPrice: DEFAULT_MAX_PRICE,
|
maxPrice: DEFAULT_MAX_PRICE,
|
||||||
lrn: '',
|
lrn: '',
|
||||||
numProviders: '1',
|
numProviders: 1,
|
||||||
variables: [],
|
variables: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedOption = methods.watch('option');
|
const selectedOption = methods.watch('option');
|
||||||
const selectedNumProviders = methods.watch('numProviders') ?? '1';
|
|
||||||
const selectedMaxPrice = methods.watch('maxPrice') ?? DEFAULT_MAX_PRICE;
|
|
||||||
|
|
||||||
const isTabletView = useMediaQuery('(min-width: 720px)'); // md:
|
const isTabletView = useMediaQuery('(min-width: 720px)'); // md:
|
||||||
const buttonSize = isTabletView ? { size: 'lg' as const } : {};
|
const buttonSize = isTabletView ? { size: 'lg' as const } : {};
|
||||||
|
|
||||||
const amountToBePaid = useMemo(() => {
|
|
||||||
let amount: string;
|
|
||||||
|
|
||||||
if (selectedOption === 'LRN') {
|
|
||||||
amount = selectedDeployer?.minimumPayment?.replace(/\D/g, '') ?? '0';
|
|
||||||
} else {
|
|
||||||
if (!selectedNumProviders || !selectedMaxPrice) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const bigMaxPrice = BigNumber.from(selectedMaxPrice);
|
|
||||||
amount = bigMaxPrice.mul(selectedNumProviders).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return amount;
|
|
||||||
}, [
|
|
||||||
selectedOption,
|
|
||||||
selectedDeployer?.minimumPayment,
|
|
||||||
selectedMaxPrice,
|
|
||||||
selectedNumProviders,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const createProject = async (
|
const createProject = async (
|
||||||
data: FieldValues,
|
data: FieldValues,
|
||||||
envVariables: AddEnvironmentVariableInput[],
|
envVariables: AddEnvironmentVariableInput[],
|
||||||
@ -213,8 +180,9 @@ const Configure = () => {
|
|||||||
(deployer) => deployer.deployerLrn === deployerLrn,
|
(deployer) => deployer.deployerLrn === deployerLrn,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let amount: string;
|
||||||
let senderAddress: string;
|
let senderAddress: string;
|
||||||
let txHash: string | null = null;
|
let txHash: string;
|
||||||
if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
|
if (createFormData.option === 'LRN' && !deployer?.minimumPayment) {
|
||||||
toast({
|
toast({
|
||||||
id: 'no-payment-required',
|
id: 'no-payment-required',
|
||||||
@ -228,46 +196,39 @@ const Configure = () => {
|
|||||||
} else {
|
} else {
|
||||||
if (!selectedAccount) return;
|
if (!selectedAccount) return;
|
||||||
|
|
||||||
senderAddress = selectedAccount;
|
senderAddress = selectedAccount.split(':')[2];
|
||||||
|
|
||||||
txHash = await cosmosSendTokensHandler(senderAddress, amountToBePaid);
|
if (createFormData.option === 'LRN') {
|
||||||
|
amount = deployer?.minimumPayment!;
|
||||||
if (!txHash) {
|
} else {
|
||||||
toast({
|
amount = (
|
||||||
id: 'unsuccessful-tx',
|
createFormData.numProviders * createFormData.maxPrice
|
||||||
title: 'Transaction rejected',
|
).toString();
|
||||||
variant: 'error',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
setIsFrameVisible(false);
|
|
||||||
setIsPaymentLoading(false);
|
|
||||||
throw new Error('Transaction rejected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate transaction hash
|
const amountToBePaid = amount.replace(/\D/g, '').toString();
|
||||||
|
|
||||||
|
const txHashResponse = await cosmosSendTokensHandler(
|
||||||
|
selectedAccount,
|
||||||
|
amountToBePaid,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!txHashResponse) {
|
||||||
|
console.error('Tx not successful');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash = txHashResponse;
|
||||||
|
|
||||||
const isTxHashValid = await verifyTx(
|
const isTxHashValid = await verifyTx(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
txHash,
|
txHash,
|
||||||
amountToBePaid,
|
amountToBePaid.toString(),
|
||||||
);
|
);
|
||||||
setIsPaymentLoading(false);
|
|
||||||
|
|
||||||
if (isTxHashValid) {
|
if (isTxHashValid === false) {
|
||||||
toast({
|
console.error('Invalid Tx hash', txHash);
|
||||||
id: 'payment-successful',
|
return;
|
||||||
title: 'Payment successful',
|
|
||||||
variant: 'success',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
setIsPaymentDone(true);
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
id: 'invalid-tx-hash',
|
|
||||||
title: 'Transaction validation failed',
|
|
||||||
variant: 'error',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
throw new Error('Transaction validation failed');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +248,7 @@ const Configure = () => {
|
|||||||
createFormData,
|
createFormData,
|
||||||
environmentVariables,
|
environmentVariables,
|
||||||
senderAddress,
|
senderAddress,
|
||||||
txHash!,
|
txHash,
|
||||||
);
|
);
|
||||||
|
|
||||||
await client.getEnvironmentVariables(projectId);
|
await client.getEnvironmentVariables(projectId);
|
||||||
@ -309,17 +270,17 @@ const Configure = () => {
|
|||||||
`/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
|
`/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
toast({
|
toast({
|
||||||
id: 'error-deploying-app',
|
id: 'error-deploying-app',
|
||||||
title: 'Error deploying app',
|
title: 'Error deploying app',
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
onDismiss: dismiss,
|
onDismiss: dismiss,
|
||||||
});
|
});
|
||||||
throw new Error(error);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[client, createProject, dismiss, toast, amountToBePaid],
|
[client, createProject, dismiss, toast],
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchDeployers = useCallback(async () => {
|
const fetchDeployers = useCallback(async () => {
|
||||||
@ -327,6 +288,10 @@ const Configure = () => {
|
|||||||
setDeployers(res.deployers);
|
setDeployers(res.deployers);
|
||||||
}, [client]);
|
}, [client]);
|
||||||
|
|
||||||
|
const onAccountChange = useCallback((account: string) => {
|
||||||
|
setSelectedAccount(account);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onDeployerChange = useCallback(
|
const onDeployerChange = useCallback(
|
||||||
(selectedLrn: string) => {
|
(selectedLrn: string) => {
|
||||||
const deployer = deployers.find((d) => d.deployerLrn === selectedLrn);
|
const deployer = deployers.find((d) => d.deployerLrn === selectedLrn);
|
||||||
@ -337,112 +302,76 @@ const Configure = () => {
|
|||||||
|
|
||||||
const cosmosSendTokensHandler = useCallback(
|
const cosmosSendTokensHandler = useCallback(
|
||||||
async (selectedAccount: string, amount: string) => {
|
async (selectedAccount: string, amount: string) => {
|
||||||
if (!selectedAccount) {
|
if (!signClient || !session || !selectedAccount) {
|
||||||
throw new Error('Account not selected');
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderAddress = selectedAccount;
|
const chainId = selectedAccount.split(':')[1];
|
||||||
|
const senderAddress = selectedAccount.split(':')[2];
|
||||||
const snowballAddress = await client.getAddress();
|
const snowballAddress = await client.getAddress();
|
||||||
let timeoutId;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsPaymentDone(false);
|
setIsPaymentDone(false);
|
||||||
setIsPaymentLoading(true);
|
setIsPaymentLoading(true);
|
||||||
|
|
||||||
await requestTx(senderAddress, snowballAddress, amount);
|
toast({
|
||||||
|
id: 'sending-payment-request',
|
||||||
const txHash = await new Promise<string | null>((resolve, reject) => {
|
title: 'Check your wallet and approve payment request',
|
||||||
// Call cleanup method only if appropriate event type is recieved
|
variant: 'loading',
|
||||||
const cleanup = () => {
|
onDismiss: dismiss,
|
||||||
setIsFrameVisible(false);
|
|
||||||
window.removeEventListener('message', handleTxStatus);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTxStatus = async (event: MessageEvent) => {
|
|
||||||
if (event.origin !== VITE_WALLET_IFRAME_URL) return;
|
|
||||||
|
|
||||||
if (event.data.type === 'TRANSACTION_RESPONSE') {
|
|
||||||
const txResponse = event.data.data;
|
|
||||||
resolve(txResponse);
|
|
||||||
|
|
||||||
cleanup();
|
|
||||||
} else if (event.data.type === 'ERROR') {
|
|
||||||
console.error('Error from wallet:', event.data.message);
|
|
||||||
reject(new Error('Transaction failed'));
|
|
||||||
toast({
|
|
||||||
id: 'error-transaction',
|
|
||||||
title: 'Error during transaction',
|
|
||||||
variant: 'error',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('message', handleTxStatus);
|
|
||||||
|
|
||||||
// Set a timeout, consider unsuccessful after 1 min
|
|
||||||
timeoutId = setTimeout(() => {
|
|
||||||
reject(new Error('Transaction timeout'));
|
|
||||||
window.removeEventListener('message', handleTxStatus);
|
|
||||||
toast({
|
|
||||||
id: 'transaction-timeout',
|
|
||||||
title: 'The transaction request timed out. Please try again',
|
|
||||||
variant: 'error',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
setIsFrameVisible(false);
|
|
||||||
setIsPaymentLoading(false);
|
|
||||||
}, TX_APPROVAL_TIMEOUT_MS);
|
|
||||||
});
|
});
|
||||||
return txHash;
|
|
||||||
} catch (error) {
|
const result: { signature: string } = await signClient.request({
|
||||||
console.error('Error in transaction:', error);
|
topic: session.topic,
|
||||||
throw new Error('Error in transaction');
|
chainId: `cosmos:${chainId}`,
|
||||||
|
request: {
|
||||||
|
method: 'cosmos_sendTokens',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
from: senderAddress,
|
||||||
|
to: snowballAddress,
|
||||||
|
value: amount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('Error completing transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: 'payment-successful',
|
||||||
|
title: 'Payment successful',
|
||||||
|
variant: 'success',
|
||||||
|
onDismiss: dismiss,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsPaymentDone(true);
|
||||||
|
|
||||||
|
return result.signature;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error sending tokens', error);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
id: 'error-sending-tokens',
|
||||||
|
title: 'Error sending tokens',
|
||||||
|
variant: 'error',
|
||||||
|
onDismiss: dismiss,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsPaymentDone(false);
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId);
|
setIsPaymentLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[client, dismiss, toast],
|
[session, signClient, toast],
|
||||||
);
|
);
|
||||||
|
|
||||||
const requestTx = async (
|
|
||||||
sender: string,
|
|
||||||
recipient: string,
|
|
||||||
amount: string,
|
|
||||||
) => {
|
|
||||||
const iframe = document.getElementById('walletIframe') as HTMLIFrameElement;
|
|
||||||
|
|
||||||
if (!iframe.contentWindow) {
|
|
||||||
console.error('Iframe not found or not loaded');
|
|
||||||
throw new Error('Iframe not found or not loaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe.contentWindow.postMessage(
|
|
||||||
{
|
|
||||||
type: 'REQUEST_TX',
|
|
||||||
chainId: networkData?.chainId,
|
|
||||||
fromAddress: sender,
|
|
||||||
toAddress: recipient,
|
|
||||||
amount,
|
|
||||||
},
|
|
||||||
VITE_WALLET_IFRAME_URL,
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsFrameVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDeployers();
|
fetchDeployers();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isBalanceSufficient) {
|
|
||||||
setBalanceMessage(undefined);
|
|
||||||
}
|
|
||||||
}, [isBalanceSufficient]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-7 px-4 py-6">
|
<div className="space-y-7 px-4 py-6">
|
||||||
<div className="flex justify-between mb-6">
|
<div className="flex justify-between mb-6">
|
||||||
@ -450,10 +379,7 @@ const Configure = () => {
|
|||||||
<Heading as="h4" className="md:text-lg font-medium">
|
<Heading as="h4" className="md:text-lg font-medium">
|
||||||
Configure deployment
|
Configure deployment
|
||||||
</Heading>
|
</Heading>
|
||||||
<Heading
|
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||||
as="h5"
|
|
||||||
className="text-sm font-sans text-elements-low-em dark:text-foreground-secondaryu"
|
|
||||||
>
|
|
||||||
The app can be deployed by setting the deployer LRN for a single
|
The app can be deployed by setting the deployer LRN for a single
|
||||||
deployment or by creating a deployer auction for multiple
|
deployment or by creating a deployer auction for multiple
|
||||||
deployments
|
deployments
|
||||||
@ -474,7 +400,6 @@ const Configure = () => {
|
|||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
size="small"
|
size="small"
|
||||||
displayEmpty
|
displayEmpty
|
||||||
className="dark:bg-overlay2 dark:text-foreground"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
@ -494,7 +419,7 @@ const Configure = () => {
|
|||||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||||
<Heading
|
<Heading
|
||||||
as="h5"
|
as="h5"
|
||||||
className="text-sm font-sans text-elements-low-em dark:text-foreground-secondary"
|
className="text-sm font-sans text-elements-low-em"
|
||||||
>
|
>
|
||||||
The app will be deployed by the configured deployer
|
The app will be deployed by the configured deployer
|
||||||
</Heading>
|
</Heading>
|
||||||
@ -504,7 +429,7 @@ const Configure = () => {
|
|||||||
rules={{ required: true }}
|
rules={{ required: true }}
|
||||||
render={({ field: { value, onChange }, fieldState }) => (
|
render={({ field: { value, onChange }, fieldState }) => (
|
||||||
<FormControl fullWidth error={Boolean(fieldState.error)}>
|
<FormControl fullWidth error={Boolean(fieldState.error)}>
|
||||||
<span className="text-sm dark:text-foreground text-elements-high-em dark:text-foreground mb-4">
|
<span className="text-sm text-elements-high-em mb-4">
|
||||||
Select deployer LRN
|
Select deployer LRN
|
||||||
</span>
|
</span>
|
||||||
<Select
|
<Select
|
||||||
@ -515,7 +440,6 @@ const Configure = () => {
|
|||||||
}}
|
}}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
size="small"
|
size="small"
|
||||||
className="dark:bg-overlay2 dark:text-foreground"
|
|
||||||
>
|
>
|
||||||
{deployers.map((deployer) => (
|
{deployers.map((deployer) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@ -542,12 +466,12 @@ const Configure = () => {
|
|||||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||||
<Heading
|
<Heading
|
||||||
as="h5"
|
as="h5"
|
||||||
className="text-sm font-sans text-elements-low-em dark:text-foreground-secondary"
|
className="text-sm font-sans text-elements-low-em"
|
||||||
>
|
>
|
||||||
Set the number of deployers and maximum price for each
|
Set the number of deployers and maximum price for each
|
||||||
deployment
|
deployment
|
||||||
</Heading>
|
</Heading>
|
||||||
<span className="text-sm text-elements-high-em dark:text-foreground">
|
<span className="text-sm text-elements-high-em">
|
||||||
Number of Deployers
|
Number of Deployers
|
||||||
</span>
|
</span>
|
||||||
<Controller
|
<Controller
|
||||||
@ -559,13 +483,12 @@ const Configure = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e)}
|
onChange={(e) => onChange(e)}
|
||||||
min={1}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-start gap-4 mb-6">
|
<div className="flex flex-col justify-start gap-4 mb-6">
|
||||||
<span className="text-sm text-elements-high-em dark:text-foreground">
|
<span className="text-sm text-elements-high-em">
|
||||||
Maximum Price (alnt)
|
Maximum Price (alnt)
|
||||||
</span>
|
</span>
|
||||||
<Controller
|
<Controller
|
||||||
@ -573,7 +496,7 @@ const Configure = () => {
|
|||||||
control={methods.control}
|
control={methods.control}
|
||||||
rules={{ required: true }}
|
rules={{ required: true }}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<Input type="number" value={value} onChange={onChange} min={1} />
|
<Input type="number" value={value} onChange={onChange} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -583,7 +506,7 @@ const Configure = () => {
|
|||||||
<Heading as="h4" className="md:text-lg font-medium mb-3">
|
<Heading as="h4" className="md:text-lg font-medium mb-3">
|
||||||
Environment Variables
|
Environment Variables
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="p-4 bg-slate-100 dark:bg-overlay3 rounded-lg mb-6">
|
<div className="p-4 bg-slate-100 rounded-lg mb-6">
|
||||||
<EnvironmentVariablesForm />
|
<EnvironmentVariablesForm />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -592,7 +515,7 @@ const Configure = () => {
|
|||||||
<Button
|
<Button
|
||||||
{...buttonSize}
|
{...buttonSize}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading || !selectedDeployer}
|
disabled={isLoading || !selectedDeployer || !selectedAccount}
|
||||||
rightIcon={
|
rightIcon={
|
||||||
isLoading ? (
|
isLoading ? (
|
||||||
<LoadingIcon className="animate-spin" />
|
<LoadingIcon className="animate-spin" />
|
||||||
@ -605,83 +528,41 @@ const Configure = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex gap-4">
|
<>
|
||||||
<Button
|
<Heading as="h4" className="md:text-lg font-medium mb-3">
|
||||||
{...buttonSize}
|
Connect to your wallet
|
||||||
type="submit"
|
</Heading>
|
||||||
shape="default"
|
<ConnectWallet onAccountChange={onAccountChange} />
|
||||||
disabled={
|
{accounts && accounts?.length > 0 && (
|
||||||
isLoading ||
|
<div>
|
||||||
isPaymentLoading ||
|
<Button
|
||||||
!selectedAccount ||
|
{...buttonSize}
|
||||||
!isBalanceSufficient ||
|
type="submit"
|
||||||
amountToBePaid === '' ||
|
disabled={
|
||||||
selectedNumProviders === ''
|
isLoading || isPaymentLoading || !selectedAccount
|
||||||
}
|
}
|
||||||
rightIcon={
|
rightIcon={
|
||||||
isLoading || isPaymentLoading ? (
|
isLoading || isPaymentLoading ? (
|
||||||
<LoadingIcon className="animate-spin" />
|
<LoadingIcon className="animate-spin" />
|
||||||
) : (
|
|
||||||
<ArrowRightCircleFilledIcon />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!isPaymentDone
|
|
||||||
? isPaymentLoading
|
|
||||||
? 'Transaction Requested'
|
|
||||||
: 'Pay and Deploy'
|
|
||||||
: isLoading
|
|
||||||
? 'Deploying'
|
|
||||||
: 'Deploy'}
|
|
||||||
</Button>
|
|
||||||
{isAccountsDataReceived && isBalanceSufficient !== undefined ? (
|
|
||||||
!selectedAccount || !isBalanceSufficient ? (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button
|
|
||||||
{...buttonSize}
|
|
||||||
shape="default"
|
|
||||||
onClick={(e: any) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setBalanceMessage('Waiting for payment');
|
|
||||||
window.open(
|
|
||||||
'https://store.laconic.com',
|
|
||||||
'_blank',
|
|
||||||
'noopener,noreferrer',
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Buy prepaid service
|
|
||||||
</Button>
|
|
||||||
<p className="text-gray-700 dark:text-gray-300">
|
|
||||||
{balanceMessage !== undefined ? (
|
|
||||||
<div className="flex items-center gap-2 text-white">
|
|
||||||
<LoadingIcon className="animate-spin w-5 h-5" />
|
|
||||||
<p>{balanceMessage}</p>
|
|
||||||
</div>
|
|
||||||
) : !selectedAccount ? (
|
|
||||||
'No accounts found. Create a wallet.'
|
|
||||||
) : (
|
) : (
|
||||||
'Insufficient funds.'
|
<ArrowRightCircleFilledIcon />
|
||||||
)}
|
)
|
||||||
</p>
|
}
|
||||||
</div>
|
>
|
||||||
) : null
|
{!isPaymentDone
|
||||||
) : null}
|
? isPaymentLoading
|
||||||
</div>
|
? 'Transaction Requested'
|
||||||
|
: 'Pay and Deploy'
|
||||||
|
: isLoading
|
||||||
|
? 'Deploying'
|
||||||
|
: 'Deploy'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
<ApproveTransactionModal
|
|
||||||
setAccount={setSelectedAccount}
|
|
||||||
setIsDataReceived={setIsAccountsDataReceived}
|
|
||||||
isVisible={isFrameVisible}
|
|
||||||
/>
|
|
||||||
<CheckBalanceIframe
|
|
||||||
onBalanceChange={setIsBalanceSufficient}
|
|
||||||
amount={amountToBePaid}
|
|
||||||
isPollingEnabled={true}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import { Button } from '../../shared/Button';
|
|||||||
import {
|
import {
|
||||||
GitIcon,
|
GitIcon,
|
||||||
EllipsesIcon,
|
EllipsesIcon,
|
||||||
|
SnowballIcon,
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
GitTeaIcon,
|
GitTeaIcon,
|
||||||
} from '../../shared/CustomIcon';
|
} from '../../shared/CustomIcon';
|
||||||
@ -14,9 +15,8 @@ import { IconWithFrame } from '../../shared/IconWithFrame';
|
|||||||
import { Heading } from '../../shared/Heading';
|
import { Heading } from '../../shared/Heading';
|
||||||
import { MockConnectGitCard } from './MockConnectGitCard';
|
import { MockConnectGitCard } from './MockConnectGitCard';
|
||||||
import { VITE_GITHUB_CLIENT_ID } from 'utils/constants';
|
import { VITE_GITHUB_CLIENT_ID } from 'utils/constants';
|
||||||
import { LaconicIcon } from 'components/shared/CustomIcon/LaconicIcon';
|
|
||||||
|
|
||||||
const SCOPES = 'public_repo user';
|
const SCOPES = 'repo user';
|
||||||
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${VITE_GITHUB_CLIENT_ID}&scope=${encodeURIComponent(SCOPES)}`;
|
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${VITE_GITHUB_CLIENT_ID}&scope=${encodeURIComponent(SCOPES)}`;
|
||||||
|
|
||||||
interface ConnectAccountInterface {
|
interface ConnectAccountInterface {
|
||||||
@ -46,24 +46,20 @@ const ConnectAccount: React.FC<ConnectAccountInterface> = ({
|
|||||||
|
|
||||||
// TODO: Use correct height
|
// TODO: Use correct height
|
||||||
return (
|
return (
|
||||||
<div className="dark:bg-overlay bg-gray-100 flex flex-col p-4 gap-7 justify-center items-center text-center text-sm h-full rounded-2xl">
|
<div className="bg-gray-100 flex flex-col p-4 gap-7 justify-center items-center text-center text-sm h-full rounded-2xl">
|
||||||
<div className="flex flex-col items-center max-w-[420px]">
|
<div className="flex flex-col items-center max-w-[420px]">
|
||||||
{/** Icons */}
|
{/** Icons */}
|
||||||
<div className="w-52 h-16 justify-center items-center gap-4 inline-flex mb-7">
|
<div className="w-52 h-16 justify-center items-center gap-4 inline-flex mb-7">
|
||||||
<IconWithFrame icon={<GitIcon />} hasHighlight={false} />
|
<IconWithFrame icon={<GitIcon />} />
|
||||||
<EllipsesIcon className="items-center gap-1.5 flex" />
|
<EllipsesIcon className="items-center gap-1.5 flex" />
|
||||||
<IconWithFrame
|
<IconWithFrame className="bg-blue-400" icon={<SnowballIcon />} />
|
||||||
className="bg-background"
|
|
||||||
icon={<LaconicIcon />}
|
|
||||||
hasHighlight={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/** Text */}
|
{/** Text */}
|
||||||
<div className="flex flex-col gap-1.5 mb-6">
|
<div className="flex flex-col gap-1.5 mb-6">
|
||||||
<Heading className="text-xl font-medium dark:text-foreground">
|
<Heading className="text-xl font-medium">
|
||||||
Connect to your Git account
|
Connect to your Git account
|
||||||
</Heading>
|
</Heading>
|
||||||
<p className="text-center text-elements-mid-em dark:text-foreground-secondary">
|
<p className="text-center text-elements-mid-em">
|
||||||
Once connected, you can import a repository from your account or
|
Once connected, you can import a repository from your account or
|
||||||
start with one of our templates.
|
start with one of our templates.
|
||||||
</p>
|
</p>
|
||||||
@ -74,14 +70,14 @@ const ConnectAccount: React.FC<ConnectAccountInterface> = ({
|
|||||||
url={GITHUB_OAUTH_URL}
|
url={GITHUB_OAUTH_URL}
|
||||||
onCode={handleCode}
|
onCode={handleCode}
|
||||||
onClose={() => {}}
|
onClose={() => {}}
|
||||||
title="Laconic"
|
title="Snowball"
|
||||||
width={1000}
|
width={1000}
|
||||||
height={1000}
|
height={1000}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
leftIcon={<GithubIcon />}
|
leftIcon={<GithubIcon />}
|
||||||
variant="primary"
|
variant="tertiary"
|
||||||
>
|
>
|
||||||
Connect to GitHub
|
Connect to GitHub
|
||||||
</Button>
|
</Button>
|
||||||
@ -89,7 +85,7 @@ const ConnectAccount: React.FC<ConnectAccountInterface> = ({
|
|||||||
<Button
|
<Button
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
leftIcon={<GitTeaIcon />}
|
leftIcon={<GitTeaIcon />}
|
||||||
variant="primary"
|
variant="tertiary"
|
||||||
>
|
>
|
||||||
Connect to GitTea
|
Connect to GitTea
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import { Select, Option } from '@snowballtools/material-tailwind-react-fork';
|
||||||
|
|
||||||
|
import { Button } from '../../shared/Button';
|
||||||
|
import { useWalletConnectClient } from 'context/WalletConnectContext';
|
||||||
|
|
||||||
|
const ConnectWallet = ({
|
||||||
|
onAccountChange,
|
||||||
|
}: {
|
||||||
|
onAccountChange: (selectedAccount: string) => void;
|
||||||
|
}) => {
|
||||||
|
const { onConnect, accounts } = useWalletConnectClient();
|
||||||
|
|
||||||
|
const handleConnect = async () => {
|
||||||
|
await onConnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 bg-slate-100 rounded-lg mb-6">
|
||||||
|
{!accounts ? (
|
||||||
|
<div>
|
||||||
|
<Button type={'button'} onClick={handleConnect}>
|
||||||
|
Connect Wallet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
label="Select Account"
|
||||||
|
defaultValue={accounts[0].address}
|
||||||
|
onChange={(value) => {
|
||||||
|
value && onAccountChange(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{accounts.map((account, index) => (
|
||||||
|
<Option key={index} value={account.address}>
|
||||||
|
{account.address.split(':').slice(1).join(':')}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConnectWallet;
|
@ -36,7 +36,7 @@ const DeployStep = ({ step, status, title, startTime }: DeployStepsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{status === DeployStatus.PROCESSING && (
|
{status === DeployStatus.PROCESSING && (
|
||||||
<LoaderIcon className="animate-spin text-elements-link dark:text-foreground" />
|
<LoaderIcon className="animate-spin text-elements-link" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -44,8 +44,7 @@ const DeployStep = ({ step, status, title, startTime }: DeployStepsProps) => {
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-left text-sm md:text-base',
|
'text-left text-sm md:text-base',
|
||||||
status === DeployStatus.PROCESSING &&
|
status === DeployStatus.PROCESSING && 'text-elements-link',
|
||||||
'text-elements-link dark:text-foreground',
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
@ -55,10 +54,7 @@ const DeployStep = ({ step, status, title, startTime }: DeployStepsProps) => {
|
|||||||
{/* Timer */}
|
{/* Timer */}
|
||||||
{status === DeployStatus.PROCESSING && (
|
{status === DeployStatus.PROCESSING && (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<ClockOutlineIcon
|
<ClockOutlineIcon size={16} className="text-elements-low-em" />
|
||||||
size={16}
|
|
||||||
className="text-elements-low-em dark:text-foreground-secondary"
|
|
||||||
/>
|
|
||||||
<Stopwatch
|
<Stopwatch
|
||||||
offsetTimestamp={setStopWatchOffset(startTime!)}
|
offsetTimestamp={setStopWatchOffset(startTime!)}
|
||||||
isPaused={false}
|
isPaused={false}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { SegmentedControls } from 'components/shared/SegmentedControls';
|
import { SegmentedControls } from 'components/shared/SegmentedControls';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
@ -10,7 +8,7 @@ import {
|
|||||||
TemplateIconType,
|
TemplateIconType,
|
||||||
} from 'components/shared/CustomIcon';
|
} from 'components/shared/CustomIcon';
|
||||||
import { relativeTimeISO } from 'utils/time';
|
import { relativeTimeISO } from 'utils/time';
|
||||||
import templates from 'assets/templates';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
|
||||||
export const MockConnectGitCard = () => {
|
export const MockConnectGitCard = () => {
|
||||||
const [segmentedControlsValue, setSegmentedControlsValue] =
|
const [segmentedControlsValue, setSegmentedControlsValue] =
|
||||||
@ -48,6 +46,29 @@ export const MockConnectGitCard = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TEMPLATE_CONTENT = [
|
||||||
|
{
|
||||||
|
name: 'Web app',
|
||||||
|
icon: 'web',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Progressive Web App (PWA)',
|
||||||
|
icon: 'pwa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'React Native',
|
||||||
|
icon: 'react-native',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kotlin',
|
||||||
|
icon: 'kotlin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Swift',
|
||||||
|
icon: 'swift',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
if (segmentedControlsValue === 'import') {
|
if (segmentedControlsValue === 'import') {
|
||||||
return (
|
return (
|
||||||
@ -65,7 +86,7 @@ export const MockConnectGitCard = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative z-0">
|
<div className="grid grid-cols-1 lg:grid-cols-2 relative z-0">
|
||||||
{templates.map((template, index) => (
|
{TEMPLATE_CONTENT.map((template, index) => (
|
||||||
<MockTemplateCard key={index} {...template} />
|
<MockTemplateCard key={index} {...template} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -73,7 +94,7 @@ export const MockConnectGitCard = () => {
|
|||||||
}, [segmentedControlsValue]);
|
}, [segmentedControlsValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative dark:bg-overlay bg-base-bg shadow-card dark:shadow-background rounded-2xl px-2 py-2 w-full max-w-[560px] flex flex-col gap-2">
|
<div className="relative bg-base-bg shadow-card rounded-2xl px-2 py-2 w-full max-w-[560px] flex flex-col gap-2">
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<SegmentedControls
|
<SegmentedControls
|
||||||
value={segmentedControlsValue}
|
value={segmentedControlsValue}
|
||||||
@ -85,7 +106,7 @@ export const MockConnectGitCard = () => {
|
|||||||
{renderContent}
|
{renderContent}
|
||||||
|
|
||||||
{/* Shade */}
|
{/* Shade */}
|
||||||
<div className="pointer-events-none z-99 absolute inset-0 rounded-2xl bg-gradient-to-t from-white dark:from-overlay to-transparent" />
|
<div className="pointer-events-none z-99 absolute inset-0 rounded-2xl bg-gradient-to-t from-white to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -100,18 +121,18 @@ const MockProjectCard = ({
|
|||||||
visibility?: string;
|
visibility?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="group flex items-start sm:items-center gap-3 pl-3 py-3 cursor-pointer rounded-xl hover:bg-base-bg-emphasized dark:hover:bg-background relative">
|
<div className="group flex items-start sm:items-center gap-3 pl-3 py-3 cursor-pointer rounded-xl hover:bg-base-bg-emphasized relative">
|
||||||
{/* Icon container */}
|
{/* Icon container */}
|
||||||
<div className="w-10 h-10 bg-base-bg dark:bg-background rounded-md justify-center items-center flex">
|
<div className="w-10 h-10 bg-base-bg rounded-md justify-center items-center flex">
|
||||||
<GithubIcon />
|
<GithubIcon />
|
||||||
</div>
|
</div>
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex flex-1 gap-3 flex-wrap">
|
<div className="flex flex-1 gap-3 flex-wrap">
|
||||||
<div className="flex flex-col items-start gap-1">
|
<div className="flex flex-col items-start gap-1">
|
||||||
<p className="text-elements-high-em text-sm dark:text-foreground font-medium tracking-[-0.006em]">
|
<p className="text-elements-high-em text-sm font-medium tracking-[-0.006em]">
|
||||||
{full_name}
|
{full_name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-elements-low-em text-xs dark:text-foreground-secondary">
|
<p className="text-elements-low-em text-xs">
|
||||||
{updated_at && relativeTimeISO(updated_at)}
|
{updated_at && relativeTimeISO(updated_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -128,13 +149,13 @@ const MockProjectCard = ({
|
|||||||
|
|
||||||
const MockTemplateCard = ({ icon, name }: { icon: string; name: string }) => {
|
const MockTemplateCard = ({ icon, name }: { icon: string; name: string }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 px-3 py-3 hover:bg-base-bg-emphasized dark:hover:bg-background relative rounded-2xl group relative cursor-default">
|
<div className="flex items-center gap-3 px-3 py-3 hover:bg-base-bg-emphasized rounded-2xl group relative cursor-default">
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<div className="px-1 py-1 rounded-xl bg-base-bg dark:bg-background border border-border-interactive/10 shadow-card-sm">
|
<div className="px-1 py-1 rounded-xl bg-base-bg border border-border-interactive/10 shadow-card-sm">
|
||||||
<TemplateIcon type={icon as TemplateIconType} />
|
<TemplateIcon type={icon as TemplateIconType} />
|
||||||
</div>
|
</div>
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<p className="flex-1 text-left text-sm tracking-tighter text-elements-high-em dark:text-foreground">
|
<p className="flex-1 text-left text-sm tracking-tighter text-elements-high-em">
|
||||||
{name}
|
{name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,21 +49,21 @@ export const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({
|
|||||||
onClick={createProject}
|
onClick={createProject}
|
||||||
>
|
>
|
||||||
{/* Icon container */}
|
{/* Icon container */}
|
||||||
<div className="w-10 h-10 bg-base-bg rounded-md justify-center items-center flex dark:bg-overlay">
|
<div className="w-10 h-10 bg-base-bg rounded-md justify-center items-center flex">
|
||||||
<GithubIcon />
|
<GithubIcon />
|
||||||
</div>
|
</div>
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex flex-1 gap-3 flex-wrap">
|
<div className="flex flex-1 gap-3 flex-wrap">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<p className="text-elements-high-em dark:text-foreground text-sm font-medium tracking-[-0.006em]">
|
<p className="text-elements-high-em text-sm font-medium tracking-[-0.006em]">
|
||||||
{repository.full_name}
|
{repository.full_name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-elements-low-em dark:text-foreground-secondary text-xs">
|
<p className="text-elements-low-em text-xs">
|
||||||
{repository.updated_at && relativeTimeISO(repository.updated_at)}
|
{repository.updated_at && relativeTimeISO(repository.updated_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{repository.visibility === 'private' && (
|
{repository.visibility === 'private' && (
|
||||||
<div className="bg-orange-50 border border-orange-200 px-2 py-1 flex items-center gap-1 rounded-lg text-xs text-orange-600 dark:text-error h-fit">
|
<div className="bg-orange-50 border border-orange-200 px-2 py-1 flex items-center gap-1 rounded-lg text-xs text-orange-600 h-fit">
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
Private
|
Private
|
||||||
</div>
|
</div>
|
||||||
|
@ -166,9 +166,7 @@ export const RepositoryList = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4 p-6 flex flex-col gap-4 items-center justify-center">
|
<div className="mt-4 p-6 flex flex-col gap-4 items-center justify-center">
|
||||||
<p className="text-elements-high-em dark:text-foreground font-sans">
|
<p className="text-elements-high-em font-sans">No repository found</p>
|
||||||
No repository found
|
|
||||||
</p>
|
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
leftIcon={<RefreshIcon />}
|
leftIcon={<RefreshIcon />}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { CopyBlock, atomOneLight } from 'react-code-blocks';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Modal } from 'components/shared/Modal';
|
||||||
|
import { Button } from 'components/shared/Button';
|
||||||
|
|
||||||
|
interface AssignDomainProps {
|
||||||
|
open: boolean;
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
|
||||||
|
return (
|
||||||
|
<Modal open={open} onOpenChange={handleOpen}>
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Header>Assign Domain</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
In order to assign a domain to your production deployments, configure
|
||||||
|
it in the{' '}
|
||||||
|
{/* TODO: Fix selection of project settings tab on navigation to domains */}
|
||||||
|
<Link to="../settings/domains" className="text-light-blue-800 inline">
|
||||||
|
project settings{' '}
|
||||||
|
</Link>
|
||||||
|
(recommended). If you want to assign to this specific deployment,
|
||||||
|
however, you can do so using our command-line interface:
|
||||||
|
{/* https://github.com/rajinwonderland/react-code-blocks/issues/138 */}
|
||||||
|
<CopyBlock
|
||||||
|
text="snowball alias <deployment> <domain>"
|
||||||
|
language=""
|
||||||
|
showLineNumbers={false}
|
||||||
|
theme={atomOneLight}
|
||||||
|
/>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer className="flex justify-start">
|
||||||
|
<Button onClick={handleOpen}>Okay</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AssignDomainDialog;
|
@ -92,7 +92,7 @@ const DeploymentDetailsCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDeploymentLogs = useCallback(async () => {
|
const fetchDeploymentLogs = async () => {
|
||||||
setDeploymentLogs('Loading logs...');
|
setDeploymentLogs('Loading logs...');
|
||||||
handleOpenDialog();
|
handleOpenDialog();
|
||||||
const statusUrl = `${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`;
|
const statusUrl = `${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`;
|
||||||
@ -108,7 +108,7 @@ const DeploymentDetailsCard = ({
|
|||||||
);
|
);
|
||||||
setDeploymentLogs(logsRes);
|
setDeploymentLogs(logsRes);
|
||||||
}
|
}
|
||||||
}, [deployment.deployer, deployment.applicationDeploymentRequestId]);
|
};
|
||||||
|
|
||||||
const renderDeploymentStatus = useCallback(
|
const renderDeploymentStatus = useCallback(
|
||||||
(className?: string) => {
|
(className?: string) => {
|
||||||
@ -127,7 +127,7 @@ const DeploymentDetailsCard = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[deployment.status, deployment.commitHash, fetchDeploymentLogs],
|
[deployment.status, deployment.commitHash],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { Deployment, Domain, Environment, Project } from 'gql-client';
|
import { Deployment, Domain, Environment, Project } from 'gql-client';
|
||||||
import { Button } from 'components/shared/Button';
|
import { Button } from 'components/shared/Button';
|
||||||
import {
|
import {
|
||||||
|
GlobeIcon,
|
||||||
HorizontalDotIcon,
|
HorizontalDotIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
RefreshIcon,
|
RefreshIcon,
|
||||||
@ -17,6 +18,7 @@ import {
|
|||||||
UndoIcon,
|
UndoIcon,
|
||||||
CrossCircleIcon,
|
CrossCircleIcon,
|
||||||
} from 'components/shared/CustomIcon';
|
} from 'components/shared/CustomIcon';
|
||||||
|
import AssignDomainDialog from './AssignDomainDialog';
|
||||||
import { useGQLClient } from 'context/GQLClientContext';
|
import { useGQLClient } from 'context/GQLClientContext';
|
||||||
import { cn } from 'utils/classnames';
|
import { cn } from 'utils/classnames';
|
||||||
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
|
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
|
||||||
@ -47,8 +49,8 @@ export const DeploymentMenu = ({
|
|||||||
const [redeployToProduction, setRedeployToProduction] = useState(false);
|
const [redeployToProduction, setRedeployToProduction] = useState(false);
|
||||||
const [deleteDeploymentDialog, setDeleteDeploymentDialog] = useState(false);
|
const [deleteDeploymentDialog, setDeleteDeploymentDialog] = useState(false);
|
||||||
const [isConfirmDeleteLoading, setIsConfirmDeleteLoading] = useState(false);
|
const [isConfirmDeleteLoading, setIsConfirmDeleteLoading] = useState(false);
|
||||||
const [isConfirmUpdateLoading, setIsConfirmUpdateLoading] = useState(false);
|
|
||||||
const [rollbackDeployment, setRollbackDeployment] = useState(false);
|
const [rollbackDeployment, setRollbackDeployment] = useState(false);
|
||||||
|
const [assignDomainDialog, setAssignDomainDialog] = useState(false);
|
||||||
const [isConfirmButtonLoading, setConfirmButtonLoadingLoading] =
|
const [isConfirmButtonLoading, setConfirmButtonLoadingLoading] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
@ -56,8 +58,6 @@ export const DeploymentMenu = ({
|
|||||||
const isUpdated = await client.updateDeploymentToProd(deployment.id);
|
const isUpdated = await client.updateDeploymentToProd(deployment.id);
|
||||||
if (isUpdated.updateDeploymentToProd) {
|
if (isUpdated.updateDeploymentToProd) {
|
||||||
await onUpdate();
|
await onUpdate();
|
||||||
setIsConfirmUpdateLoading(false);
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
id: 'deployment_changed_to_production',
|
id: 'deployment_changed_to_production',
|
||||||
title: 'Deployment changed to production',
|
title: 'Deployment changed to production',
|
||||||
@ -102,8 +102,6 @@ export const DeploymentMenu = ({
|
|||||||
);
|
);
|
||||||
if (isRollbacked.rollbackDeployment) {
|
if (isRollbacked.rollbackDeployment) {
|
||||||
await onUpdate();
|
await onUpdate();
|
||||||
setIsConfirmUpdateLoading(false);
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
id: 'deployment_rolled_back',
|
id: 'deployment_rolled_back',
|
||||||
title: 'Deployment rolled back',
|
title: 'Deployment rolled back',
|
||||||
@ -175,6 +173,12 @@ export const DeploymentMenu = ({
|
|||||||
<LinkIcon /> Visit
|
<LinkIcon /> Visit
|
||||||
</a>
|
</a>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
className="hover:bg-base-bg-emphasized flex items-center gap-3"
|
||||||
|
onClick={() => setAssignDomainDialog(!assignDomainDialog)}
|
||||||
|
>
|
||||||
|
<GlobeIcon /> Assign domain
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className="hover:bg-base-bg-emphasized flex items-center gap-3"
|
className="hover:bg-base-bg-emphasized flex items-center gap-3"
|
||||||
onClick={() => setChangeToProduction(!changeToProduction)}
|
onClick={() => setChangeToProduction(!changeToProduction)}
|
||||||
@ -222,11 +226,9 @@ export const DeploymentMenu = ({
|
|||||||
handleCancel={() => setChangeToProduction((preVal) => !preVal)}
|
handleCancel={() => setChangeToProduction((preVal) => !preVal)}
|
||||||
open={changeToProduction}
|
open={changeToProduction}
|
||||||
handleConfirm={async () => {
|
handleConfirm={async () => {
|
||||||
setIsConfirmUpdateLoading(true);
|
|
||||||
await updateDeployment();
|
await updateDeployment();
|
||||||
setChangeToProduction((preVal) => !preVal);
|
setChangeToProduction((preVal) => !preVal);
|
||||||
}}
|
}}
|
||||||
isConfirmButtonLoading={isConfirmUpdateLoading}
|
|
||||||
deployment={deployment}
|
deployment={deployment}
|
||||||
domains={prodBranchDomains}
|
domains={prodBranchDomains}
|
||||||
/>
|
/>
|
||||||
@ -241,7 +243,7 @@ export const DeploymentMenu = ({
|
|||||||
setRedeployToProduction((preVal) => !preVal);
|
setRedeployToProduction((preVal) => !preVal);
|
||||||
}}
|
}}
|
||||||
deployment={deployment}
|
deployment={deployment}
|
||||||
domains={prodBranchDomains}
|
domains={deployment.domain ? [deployment.domain] : []}
|
||||||
isConfirmButtonLoading={isConfirmButtonLoading}
|
isConfirmButtonLoading={isConfirmButtonLoading}
|
||||||
/>
|
/>
|
||||||
{Boolean(currentDeployment) && (
|
{Boolean(currentDeployment) && (
|
||||||
@ -251,16 +253,18 @@ export const DeploymentMenu = ({
|
|||||||
open={rollbackDeployment}
|
open={rollbackDeployment}
|
||||||
confirmButtonTitle="Rollback"
|
confirmButtonTitle="Rollback"
|
||||||
handleConfirm={async () => {
|
handleConfirm={async () => {
|
||||||
setIsConfirmUpdateLoading(true);
|
|
||||||
await rollbackDeploymentHandler();
|
await rollbackDeploymentHandler();
|
||||||
setRollbackDeployment((preVal) => !preVal);
|
setRollbackDeployment((preVal) => !preVal);
|
||||||
}}
|
}}
|
||||||
deployment={currentDeployment}
|
deployment={currentDeployment}
|
||||||
newDeployment={deployment}
|
newDeployment={deployment}
|
||||||
domains={prodBranchDomains}
|
domains={currentDeployment.domain ? [currentDeployment.domain] : []}
|
||||||
isConfirmButtonLoading={isConfirmUpdateLoading}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<AssignDomainDialog
|
||||||
|
open={assignDomainDialog}
|
||||||
|
handleOpen={() => setAssignDomainDialog(!assignDomainDialog)}
|
||||||
|
/>
|
||||||
<DeleteDeploymentDialog
|
<DeleteDeploymentDialog
|
||||||
open={deleteDeploymentDialog}
|
open={deleteDeploymentDialog}
|
||||||
handleConfirm={async () => {
|
handleConfirm={async () => {
|
||||||
|
@ -16,7 +16,7 @@ export const Activity = ({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Heading className="text-lg leading-6 font-medium">Activity</Heading>
|
<Heading className="text-lg leading-6 font-medium">Activity</Heading>
|
||||||
<Button variant="tertiary" size="sm">
|
<Button variant="tertiary" size="sm">
|
||||||
SEE ALL
|
See all
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
|
@ -18,6 +18,7 @@ import { Button, Heading, Tag } from 'components/shared';
|
|||||||
const WAIT_DURATION = 5000;
|
const WAIT_DURATION = 5000;
|
||||||
|
|
||||||
const DIALOG_STYLE = {
|
const DIALOG_STYLE = {
|
||||||
|
backgroundColor: 'rgba(0,0,0, .9)',
|
||||||
padding: '2em',
|
padding: '2em',
|
||||||
borderRadius: '0.5em',
|
borderRadius: '0.5em',
|
||||||
marginLeft: '0.5em',
|
marginLeft: '0.5em',
|
||||||
@ -43,11 +44,6 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
|
|
||||||
const checkAuctionStatus = useCallback(async () => {
|
const checkAuctionStatus = useCallback(async () => {
|
||||||
const result = await client.getAuctionData(project.auctionId);
|
const result = await client.getAuctionData(project.auctionId);
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setAuctionStatus(result.status);
|
setAuctionStatus(result.status);
|
||||||
setAuctionDetails(result);
|
setAuctionDetails(result);
|
||||||
}, [project.auctionId, project.deployers, project.fundsReleased]);
|
}, [project.auctionId, project.deployers, project.fundsReleased]);
|
||||||
@ -90,27 +86,27 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-3 gap-2 rounded-xl border dark:border-overlay3 border-gray-200 transition-colors hover:bg-base-bg-alternate dark:hover:bg-overlay3 flex flex-col mt-8">
|
<div className="p-3 gap-2 rounded-xl border border-gray-200 transition-colors hover:bg-base-bg-alternate flex flex-col mt-8">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Heading className="text-lg leading-6 font-medium">
|
<Heading className="text-lg leading-6 font-medium">
|
||||||
Auction details
|
Auction details
|
||||||
</Heading>
|
</Heading>
|
||||||
<Button onClick={handleOpenDialog} variant="tertiary" size="sm">
|
<Button onClick={handleOpenDialog} variant="tertiary" size="sm">
|
||||||
VIEW DETAILS
|
View details
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-2">
|
<div className="flex justify-between items-center mt-2">
|
||||||
<span className="text-elements-high-em dark:text-foreground-secondary text-sm font-medium tracking-tight">
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
||||||
Auction Id
|
Auction Id
|
||||||
</span>
|
</span>
|
||||||
<span className="text-elements-mid-em dark:text-foreground text-sm text-right">
|
<span className="text-elements-mid-em text-sm text-right">
|
||||||
{project.auctionId}
|
{project.auctionId}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-1">
|
<div className="flex justify-between items-center mt-1">
|
||||||
<span className="text-elements-high-em dark:text-foreground-secondary text-sm font-medium tracking-tight">
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
||||||
Auction Status
|
Auction Status
|
||||||
</span>
|
</span>
|
||||||
<div className="ml-2">{renderAuctionStatus()}</div>
|
<div className="ml-2">{renderAuctionStatus()}</div>
|
||||||
@ -120,20 +116,17 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
<>
|
<>
|
||||||
{deployers?.length > 0 ? (
|
{deployers?.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-elements-high-em dark:text-foreground-secondary text-sm font-medium tracking-tight">
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
||||||
Deployer LRNs
|
Deployer LRNs
|
||||||
</span>
|
</span>
|
||||||
{deployers.map((deployer, index) => (
|
{deployers.map((deployer, index) => (
|
||||||
<p
|
<p key={index} className="text-elements-mid-em text-sm">
|
||||||
key={index}
|
|
||||||
className="text-elements-mid-em dark:text-foreground text-sm"
|
|
||||||
>
|
|
||||||
{'\u2022'} {deployer.deployerLrn}
|
{'\u2022'} {deployer.deployerLrn}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-1">
|
<div className="flex justify-between items-center mt-1">
|
||||||
<span className="text-elements-high-em dark:text-foreground-secondary text-sm font-medium tracking-tight">
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
||||||
Deployer Funds Status
|
Deployer Funds Status
|
||||||
</span>
|
</span>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
@ -148,7 +141,7 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<span className="text-elements-high-em dark:text-foreground-secondary text-sm font-medium tracking-tight">
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
||||||
No winning deployers
|
No winning deployers
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -162,24 +155,15 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
onClose={handleCloseDialog}
|
onClose={handleCloseDialog}
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth="md"
|
maxWidth="md"
|
||||||
PaperProps={{
|
|
||||||
className: 'dark:bg-overlay2',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DialogTitle className="dark:text-foreground">
|
<DialogTitle>Auction Details</DialogTitle>
|
||||||
Auction Details
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent style={DIALOG_STYLE}>
|
<DialogContent style={DIALOG_STYLE}>
|
||||||
{auctionDetails && (
|
{auctionDetails && (
|
||||||
<pre className="dark:text-foreground-secondary">
|
<pre>{JSON.stringify(auctionDetails, null, 2)}</pre>
|
||||||
{JSON.stringify(auctionDetails, null, 2)}
|
|
||||||
</pre>
|
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleCloseDialog} shape="default">
|
<Button onClick={handleCloseDialog}>Close</Button>
|
||||||
CLOSE
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
|
@ -18,7 +18,7 @@ export const OverviewInfo = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between gap-2 py-3 text-sm items-center">
|
<div className="flex justify-between gap-2 py-3 text-sm items-center">
|
||||||
<div className="flex gap-2 items-center text-elements-high-em dark:text-foreground-secondary">
|
<div className="flex gap-2 items-center text-elements-high-em">
|
||||||
{styledIcon}
|
{styledIcon}
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,11 +93,11 @@ const AddMemberDialog = ({
|
|||||||
/>
|
/>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button onClick={handleOpen} variant="danger" shape="default">
|
<Button onClick={handleOpen} variant="secondary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={!isValid} shape="default">
|
<Button type="submit" disabled={!isValid}>
|
||||||
SEND INVITE
|
Send invite
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</form>
|
</form>
|
||||||
|
@ -24,7 +24,7 @@ const DisplayEnvironmentVariables = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="flex gap-4 p-2 dark:text-foreground"
|
className="flex gap-4 p-2"
|
||||||
onClick={() => setOpenCollapse((cur) => !cur)}
|
onClick={() => setOpenCollapse((cur) => !cur)}
|
||||||
>
|
>
|
||||||
{openCollapse ? <ChevronUpSmallIcon /> : <ChevronDownSmallIcon />}
|
{openCollapse ? <ChevronUpSmallIcon /> : <ChevronDownSmallIcon />}
|
||||||
@ -33,7 +33,7 @@ const DisplayEnvironmentVariables = ({
|
|||||||
</div>
|
</div>
|
||||||
<Collapse open={openCollapse}>
|
<Collapse open={openCollapse}>
|
||||||
{variables.length === 0 ? (
|
{variables.length === 0 ? (
|
||||||
<div className="bg-slate-100 dark:bg-overlay2 dark:text-foreground rounded-xl flex-col p-4">
|
<div className="bg-slate-100 rounded-xl flex-col p-4">
|
||||||
No environment variables added yet. Once you add them, they'll show
|
No environment variables added yet. Once you add them, they'll show
|
||||||
up here.
|
up here.
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { DNSRecordAttributes, Domain, DomainStatus, Project } from 'gql-client';
|
import { Domain, DomainStatus, Project } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
@ -14,27 +14,22 @@ import EditDomainDialog from './EditDomainDialog';
|
|||||||
import { useGQLClient } from 'context/GQLClientContext';
|
import { useGQLClient } from 'context/GQLClientContext';
|
||||||
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
|
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
|
||||||
import { useToast } from 'components/shared/Toast';
|
import { useToast } from 'components/shared/Toast';
|
||||||
import { GearIcon } from 'components/shared/CustomIcon';
|
import { Tag } from 'components/shared/Tag';
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
CrossIcon,
|
||||||
|
GearIcon,
|
||||||
|
LoadingIcon,
|
||||||
|
} from 'components/shared/CustomIcon';
|
||||||
import { Heading } from 'components/shared/Heading';
|
import { Heading } from 'components/shared/Heading';
|
||||||
import { Button } from 'components/shared/Button';
|
import { Button } from 'components/shared/Button';
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
// NOTE: Commented code for verify domain functionality
|
enum RefreshStatus {
|
||||||
// import { Tag } from 'components/shared/Tag';
|
IDLE,
|
||||||
// import {
|
CHECKING,
|
||||||
// CheckIcon,
|
CHECK_SUCCESS,
|
||||||
// CrossIcon,
|
CHECK_FAIL,
|
||||||
// LoadingIcon,
|
}
|
||||||
// } from 'components/shared/CustomIcon';
|
|
||||||
|
|
||||||
// enum RefreshStatus {
|
|
||||||
// IDLE,
|
|
||||||
// CHECKING,
|
|
||||||
// CHECK_SUCCESS,
|
|
||||||
// CHECK_FAIL,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const CHECK_FAIL_TIMEOUT = 5000; // In milliseconds
|
|
||||||
|
|
||||||
interface DomainCardProps {
|
interface DomainCardProps {
|
||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
@ -44,6 +39,14 @@ interface DomainCardProps {
|
|||||||
onUpdate: () => Promise<void>;
|
onUpdate: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHECK_FAIL_TIMEOUT = 5000; // In milliseconds
|
||||||
|
|
||||||
|
// TODO: Get domain record
|
||||||
|
const DOMAIN_RECORD = {
|
||||||
|
type: 'A',
|
||||||
|
name: '@',
|
||||||
|
value: '56.49.19.21',
|
||||||
|
};
|
||||||
|
|
||||||
const DomainCard = ({
|
const DomainCard = ({
|
||||||
domains,
|
domains,
|
||||||
@ -53,11 +56,9 @@ const DomainCard = ({
|
|||||||
onUpdate,
|
onUpdate,
|
||||||
}: DomainCardProps) => {
|
}: DomainCardProps) => {
|
||||||
const { toast, dismiss } = useToast();
|
const { toast, dismiss } = useToast();
|
||||||
const { id } = useParams();
|
const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE);
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
const [dnsRecord, setDnsRecord] = useState<DNSRecordAttributes | null>(null);
|
|
||||||
// const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE);
|
|
||||||
|
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
|
||||||
@ -82,33 +83,13 @@ const DomainCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchDNSData = async () => {
|
|
||||||
if (id === undefined) {
|
|
||||||
toast({
|
|
||||||
id: 'domain_cannot_find_project',
|
|
||||||
title: 'Cannot find project',
|
|
||||||
variant: 'error',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dnsRecordResponse = await client.getLatestDNSRecordByProjectId(id);
|
|
||||||
|
|
||||||
setDnsRecord(dnsRecordResponse.latestDNSRecord);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchDNSData();
|
|
||||||
}, [id, client]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<div className="flex justify-start gap-1">
|
<div className="flex justify-start gap-1">
|
||||||
<Heading as="h6" className="flex-col">
|
<Heading as="h6" className="flex-col">
|
||||||
{domain.name}{' '}
|
{domain.name}{' '}
|
||||||
{/* <Tag
|
<Tag
|
||||||
type={
|
type={
|
||||||
domain.status === DomainStatus.Live ? 'positive' : 'negative'
|
domain.status === DomainStatus.Live ? 'positive' : 'negative'
|
||||||
}
|
}
|
||||||
@ -121,12 +102,12 @@ const DomainCard = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{domain.status}
|
{domain.status}
|
||||||
</Tag> */}
|
</Tag>
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-start gap-1">
|
<div className="flex justify-start gap-1">
|
||||||
{/* <i
|
<i
|
||||||
id="refresh"
|
id="refresh"
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -141,7 +122,7 @@ const DomainCard = ({
|
|||||||
) : (
|
) : (
|
||||||
'L'
|
'L'
|
||||||
)}
|
)}
|
||||||
</i> */}
|
</i>
|
||||||
<Menu placement="bottom-end">
|
<Menu placement="bottom-end">
|
||||||
<MenuHandler>
|
<MenuHandler>
|
||||||
<Button iconOnly>
|
<Button iconOnly>
|
||||||
@ -182,11 +163,11 @@ const DomainCard = ({
|
|||||||
<Typography variant="small">Production</Typography>
|
<Typography variant="small">Production</Typography>
|
||||||
{domain.status === DomainStatus.Pending && (
|
{domain.status === DomainStatus.Pending && (
|
||||||
<Card className="bg-slate-100 p-4 text-sm">
|
<Card className="bg-slate-100 p-4 text-sm">
|
||||||
{/* {refreshStatus === RefreshStatus.IDLE ? ( */}
|
{refreshStatus === RefreshStatus.IDLE ? (
|
||||||
<Heading>
|
<Heading>
|
||||||
^ Add these records to your domain {/* and refresh to check */}
|
^ Add these records to your domain and refresh to check
|
||||||
</Heading>
|
</Heading>
|
||||||
{/* ) : refreshStatus === RefreshStatus.CHECKING ? (
|
) : refreshStatus === RefreshStatus.CHECKING ? (
|
||||||
<Heading className="text-blue-500">
|
<Heading className="text-blue-500">
|
||||||
^ Checking records for {domain.name}
|
^ Checking records for {domain.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
@ -197,7 +178,7 @@ const DomainCard = ({
|
|||||||
hours. Please ensure you added the correct records and refresh.
|
hours. Please ensure you added the correct records and refresh.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -208,15 +189,11 @@ const DomainCard = ({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{dnsRecord ? (
|
<tr>
|
||||||
<tr>
|
<td>{DOMAIN_RECORD.type}</td>
|
||||||
<td>{dnsRecord.resourceType}</td>
|
<td>{DOMAIN_RECORD.name}</td>
|
||||||
<td>@</td>
|
<td>{DOMAIN_RECORD.value}</td>
|
||||||
<td>{dnsRecord.value ?? 'Not Configured'}</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
<p className={'text-red-500'}>DNS record data not available</p>
|
|
||||||
)}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import {
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
useCallback,
|
import { Controller, useForm, SubmitHandler } from 'react-hook-form';
|
||||||
useEffect,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
|
||||||
useForm,
|
|
||||||
SubmitHandler,
|
|
||||||
} from 'react-hook-form';
|
|
||||||
import { Domain } from 'gql-client';
|
import { Domain } from 'gql-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
} from '@snowballtools/material-tailwind-react-fork';
|
} from '@snowballtools/material-tailwind-react-fork';
|
||||||
|
|
||||||
import { useGQLClient } from 'context/GQLClientContext';
|
import { useGQLClient } from 'context/GQLClientContext';
|
||||||
@ -18,15 +14,7 @@ import { Button } from 'components/shared/Button';
|
|||||||
import { Input } from 'components/shared/Input';
|
import { Input } from 'components/shared/Input';
|
||||||
import { useToast } from 'components/shared/Toast';
|
import { useToast } from 'components/shared/Toast';
|
||||||
|
|
||||||
// NOTE: Commented code for redirect domain functionality
|
const DEFAULT_REDIRECT_OPTIONS = ['none'];
|
||||||
// import {
|
|
||||||
// Select,
|
|
||||||
// Option,
|
|
||||||
// } from '@snowballtools/material-tailwind-react-fork';
|
|
||||||
// import { Controller } from 'react-hook-form';
|
|
||||||
// import { useMemo } from 'react';
|
|
||||||
|
|
||||||
// const DEFAULT_REDIRECT_OPTIONS = ['none'];
|
|
||||||
|
|
||||||
interface EditDomainDialogProp {
|
interface EditDomainDialogProp {
|
||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
@ -40,7 +28,7 @@ interface EditDomainDialogProp {
|
|||||||
type EditDomainValues = {
|
type EditDomainValues = {
|
||||||
name: string;
|
name: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
// redirectedTo: string;
|
redirectedTo: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditDomainDialog = ({
|
const EditDomainDialog = ({
|
||||||
@ -54,58 +42,58 @@ const EditDomainDialog = ({
|
|||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
const { toast, dismiss } = useToast();
|
const { toast, dismiss } = useToast();
|
||||||
|
|
||||||
// const getRedirectUrl = (domain: Domain) => {
|
const getRedirectUrl = (domain: Domain) => {
|
||||||
// const redirectDomain = domain.redirectTo;
|
const redirectDomain = domain.redirectTo;
|
||||||
|
|
||||||
// if (redirectDomain !== null) {
|
if (redirectDomain !== null) {
|
||||||
// return redirectDomain?.name;
|
return redirectDomain?.name;
|
||||||
// } else {
|
} else {
|
||||||
// return 'none';
|
return 'none';
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// const redirectOptions = useMemo(() => {
|
const redirectOptions = useMemo(() => {
|
||||||
// const domainNames = domains
|
const domainNames = domains
|
||||||
// .filter((domainData) => domainData.id !== domain.id)
|
.filter((domainData) => domainData.id !== domain.id)
|
||||||
// .map((domain) => domain.name);
|
.map((domain) => domain.name);
|
||||||
// return ['none', ...domainNames];
|
return ['none', ...domainNames];
|
||||||
// }, [domain, domains]);
|
}, [domain, domains]);
|
||||||
|
|
||||||
// const domainRedirectedFrom = useMemo(() => {
|
const domainRedirectedFrom = useMemo(() => {
|
||||||
// return domains.find(
|
return domains.find(
|
||||||
// (domainData) => domainData.redirectTo?.id === domain.id,
|
(domainData) => domainData.redirectTo?.id === domain.id,
|
||||||
// );
|
);
|
||||||
// }, [domains, domain]);
|
}, [domains, domain]);
|
||||||
|
|
||||||
// const isDisableDropdown = useMemo(() => {
|
const isDisableDropdown = useMemo(() => {
|
||||||
// return domainRedirectedFrom !== undefined;
|
return domainRedirectedFrom !== undefined;
|
||||||
// }, [domain, domains]);
|
}, [domain, domains]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
register,
|
||||||
// control,
|
control,
|
||||||
// watch,
|
watch,
|
||||||
reset,
|
reset,
|
||||||
formState: { isValid, isDirty },
|
formState: { isValid, isDirty },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: domain.name,
|
name: domain.name,
|
||||||
branch: domain.branch,
|
branch: domain.branch,
|
||||||
// redirectedTo: getRedirectUrl(domain),
|
redirectedTo: getRedirectUrl(domain),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateDomainHandler: SubmitHandler<EditDomainValues> = useCallback(
|
const updateDomainHandler: SubmitHandler<EditDomainValues> = useCallback(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
// const domainRedirectTo = domains.find(
|
const domainRedirectTo = domains.find(
|
||||||
// (domainData) => data.redirectedTo === domainData.name,
|
(domainData) => data.redirectedTo === domainData.name,
|
||||||
// );
|
);
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
name: data.name ? data.name : domain.name,
|
name: data.name ? data.name : domain.name,
|
||||||
branch: data.branch ? data.branch : domain.branch,
|
branch: data.branch ? data.branch : domain.branch,
|
||||||
// redirectToId: domainRedirectTo ? domainRedirectTo.id : null,
|
redirectToId: domainRedirectTo ? domainRedirectTo.id : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { updateDomain } = await client.updateDomain(domain.id, updates);
|
const { updateDomain } = await client.updateDomain(domain.id, updates);
|
||||||
@ -137,7 +125,7 @@ const EditDomainDialog = ({
|
|||||||
reset({
|
reset({
|
||||||
name: domain.name,
|
name: domain.name,
|
||||||
branch: domain.branch,
|
branch: domain.branch,
|
||||||
// redirectedTo: getRedirectUrl(domain),
|
redirectedTo: getRedirectUrl(domain),
|
||||||
});
|
});
|
||||||
}, [domain]);
|
}, [domain]);
|
||||||
|
|
||||||
@ -149,7 +137,7 @@ const EditDomainDialog = ({
|
|||||||
<Modal.Body className="flex flex-col gap-2">
|
<Modal.Body className="flex flex-col gap-2">
|
||||||
<Typography variant="small">Domain name</Typography>
|
<Typography variant="small">Domain name</Typography>
|
||||||
<Input {...register('name')} />
|
<Input {...register('name')} />
|
||||||
{/* <Typography variant="small">Redirect to</Typography>
|
<Typography variant="small">Redirect to</Typography>
|
||||||
<Controller
|
<Controller
|
||||||
name="redirectedTo"
|
name="redirectedTo"
|
||||||
control={control}
|
control={control}
|
||||||
@ -173,7 +161,7 @@ const EditDomainDialog = ({
|
|||||||
further.
|
further.
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)}
|
||||||
<Typography variant="small">Git branch</Typography>
|
<Typography variant="small">Git branch</Typography>
|
||||||
<Input
|
<Input
|
||||||
{...register('branch', {
|
{...register('branch', {
|
||||||
@ -181,8 +169,8 @@ const EditDomainDialog = ({
|
|||||||
Boolean(branches.length) ? branches.includes(value) : true,
|
Boolean(branches.length) ? branches.includes(value) : true,
|
||||||
})}
|
})}
|
||||||
disabled={
|
disabled={
|
||||||
!Boolean(branches.length)
|
!Boolean(branches.length) ||
|
||||||
// || watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]
|
watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{!isValid && (
|
{!isValid && (
|
||||||
|
@ -17,7 +17,6 @@ const GitSelectionSection = ({
|
|||||||
<div className="grow">Github</div>
|
<div className="grow">Github</div>
|
||||||
<div>{'>'}</div>
|
<div>{'>'}</div>
|
||||||
</div>
|
</div>
|
||||||
{/*
|
|
||||||
<div
|
<div
|
||||||
className="flex gap-4 border-b-2 border-gray-200 cursor-pointer p-1"
|
className="flex gap-4 border-b-2 border-gray-200 cursor-pointer p-1"
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
@ -26,7 +25,6 @@ const GitSelectionSection = ({
|
|||||||
<div className="grow">Gitea</div>
|
<div className="grow">Gitea</div>
|
||||||
<div>{'>'}</div>
|
<div>{'>'}</div>
|
||||||
</div>
|
</div>
|
||||||
*/}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -80,7 +80,7 @@ const MemberCard = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex py-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'} dark:text-foreground`}
|
className={`flex py-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
|
||||||
>
|
>
|
||||||
<div className="basis-1/2">
|
<div className="basis-1/2">
|
||||||
{member.name && (
|
{member.name && (
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
import { Heading } from 'components/shared/Heading';
|
import { Heading } from 'components/shared/Heading';
|
||||||
|
import { InlineNotification } from 'components/shared/InlineNotification';
|
||||||
import { Input } from 'components/shared/Input';
|
import { Input } from 'components/shared/Input';
|
||||||
import { Button } from 'components/shared/Button';
|
import { Button } from 'components/shared/Button';
|
||||||
|
import { Radio } from 'components/shared/Radio';
|
||||||
// NOTE: Commented code for redirect domain functionality
|
|
||||||
// import { useEffect, useState } from 'react';
|
|
||||||
// import { InlineNotification } from 'components/shared/InlineNotification';
|
|
||||||
// import { Radio } from 'components/shared/Radio';
|
|
||||||
|
|
||||||
interface SetupDomainFormValues {
|
interface SetupDomainFormValues {
|
||||||
domainName: string;
|
domainName: string;
|
||||||
@ -20,45 +18,47 @@ const SetupDomain = () => {
|
|||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isValid },
|
formState: { isValid },
|
||||||
// watch,
|
watch,
|
||||||
// setValue,
|
setValue,
|
||||||
} = useForm<SetupDomainFormValues>({
|
} = useForm<SetupDomainFormValues>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
domainName: '',
|
domainName: '',
|
||||||
// isWWW: 'false',
|
isWWW: 'false',
|
||||||
},
|
},
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [domainStr, setDomainStr] = useState<string>('');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// const [domainStr, setDomainStr] = useState<string>('');
|
const isWWWRadioOptions = [
|
||||||
// const isWWWRadioOptions = [
|
{ label: domainStr, value: 'false' },
|
||||||
// { label: domainStr, value: 'false' },
|
{ label: `www.${domainStr}`, value: 'true' },
|
||||||
// { label: `www.${domainStr}`, value: 'true' },
|
];
|
||||||
// ];
|
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// const subscription = watch((value, { name }) => {
|
const subscription = watch((value, { name }) => {
|
||||||
// if (name === 'domainName' && value.domainName) {
|
if (name === 'domainName' && value.domainName) {
|
||||||
// const domainArr = value.domainName.split('www.');
|
const domainArr = value.domainName.split('www.');
|
||||||
// const cleanedDomain =
|
const cleanedDomain =
|
||||||
// domainArr.length > 1 ? domainArr[1] : domainArr[0];
|
domainArr.length > 1 ? domainArr[1] : domainArr[0];
|
||||||
// setDomainStr(cleanedDomain);
|
setDomainStr(cleanedDomain);
|
||||||
|
|
||||||
// setValue(
|
setValue(
|
||||||
// 'isWWW',
|
'isWWW',
|
||||||
// value.domainName.startsWith('www.') ? 'true' : 'false',
|
value.domainName.startsWith('www.') ? 'true' : 'false',
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
// return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
// }, [watch, setValue]);
|
}, [watch, setValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit((e) => {
|
onSubmit={handleSubmit(() => {
|
||||||
navigate(`config?name=${e.domainName}`)
|
watch('isWWW') === 'true'
|
||||||
|
? navigate(`config?name=www.${domainStr}`)
|
||||||
|
: navigate(`config?name=${domainStr}`);
|
||||||
})}
|
})}
|
||||||
className="flex flex-col gap-6 w-full"
|
className="flex flex-col gap-6 w-full"
|
||||||
>
|
>
|
||||||
@ -67,7 +67,7 @@ const SetupDomain = () => {
|
|||||||
Setup domain name
|
Setup domain name
|
||||||
</Heading>
|
</Heading>
|
||||||
<p className="text-slate-500 text-sm font-normal leading-tight">
|
<p className="text-slate-500 text-sm font-normal leading-tight">
|
||||||
Add your domain {/* and setup redirects */}
|
Add your domain and setup redirects
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ const SetupDomain = () => {
|
|||||||
label="Domain name"
|
label="Domain name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* {isValid && (
|
{isValid && (
|
||||||
<div className="self-stretch flex flex-col gap-4">
|
<div className="self-stretch flex flex-col gap-4">
|
||||||
<Heading className="text-sky-950 text-lg font-medium leading-normal">
|
<Heading className="text-sky-950 text-lg font-medium leading-normal">
|
||||||
Primary domain
|
Primary domain
|
||||||
@ -99,11 +99,11 @@ const SetupDomain = () => {
|
|||||||
}. Redirect preferences can be changed later`}
|
}. Redirect preferences can be changed later`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
<div className="self-stretch">
|
<div className="self-stretch">
|
||||||
<Button disabled={!isValid} type="submit" shape="default">
|
<Button disabled={!isValid} type="submit">
|
||||||
NEXT
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -30,12 +30,7 @@ export const avatarTheme = tv(
|
|||||||
fallback: ['text-elements-warning', 'bg-base-bg-emphasized-warning'],
|
fallback: ['text-elements-warning', 'bg-base-bg-emphasized-warning'],
|
||||||
},
|
},
|
||||||
blue: {
|
blue: {
|
||||||
fallback: [
|
fallback: ['text-elements-info', 'bg-base-bg-emphasized-info'],
|
||||||
'text-elements-info',
|
|
||||||
'bg-base-bg-emphasized-info',
|
|
||||||
'dark:text-foreground',
|
|
||||||
'dark:bg-primary',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
@ -16,7 +16,6 @@ export const buttonTheme = tv(
|
|||||||
'disabled:cursor-not-allowed',
|
'disabled:cursor-not-allowed',
|
||||||
'transition-colors',
|
'transition-colors',
|
||||||
'duration-150',
|
'duration-150',
|
||||||
'font-mono',
|
|
||||||
],
|
],
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
@ -29,7 +28,7 @@ export const buttonTheme = tv(
|
|||||||
true: 'w-full',
|
true: 'w-full',
|
||||||
},
|
},
|
||||||
shape: {
|
shape: {
|
||||||
default: 'rounded',
|
default: 'rounded-lg',
|
||||||
rounded: 'rounded-full',
|
rounded: 'rounded-full',
|
||||||
},
|
},
|
||||||
iconOnly: {
|
iconOnly: {
|
||||||
@ -40,21 +39,21 @@ export const buttonTheme = tv(
|
|||||||
'text-elements-on-primary',
|
'text-elements-on-primary',
|
||||||
'border',
|
'border',
|
||||||
'border-transparent',
|
'border-transparent',
|
||||||
'bg-primary',
|
'bg-controls-primary',
|
||||||
'shadow-button',
|
'shadow-button',
|
||||||
'hover:bg-primary-hovered',
|
'hover:bg-controls-primary-hovered',
|
||||||
'focus-visible:bg-primary-hovered',
|
'focus-visible:bg-controls-primary-hovered',
|
||||||
'disabled:text-elements-on-disabled',
|
'disabled:text-elements-on-disabled',
|
||||||
'disabled:bg-controls-disabled',
|
'disabled:bg-controls-disabled',
|
||||||
'disabled:border-transparent',
|
'disabled:border-transparent',
|
||||||
'disabled:shadow-none',
|
'disabled:shadow-none',
|
||||||
],
|
],
|
||||||
secondary: [
|
secondary: [
|
||||||
'text-primary',
|
'text-elements-on-secondary',
|
||||||
'border',
|
'border',
|
||||||
'border-transparent',
|
'border-transparent',
|
||||||
'bg-secondary',
|
'bg-controls-secondary',
|
||||||
'hover:bg-overlay2',
|
'hover:bg-controls-secondary-hovered',
|
||||||
'focus-visible:bg-controls-secondary-hovered',
|
'focus-visible:bg-controls-secondary-hovered',
|
||||||
'disabled:text-elements-on-disabled',
|
'disabled:text-elements-on-disabled',
|
||||||
'disabled:bg-controls-disabled',
|
'disabled:bg-controls-disabled',
|
||||||
@ -78,12 +77,10 @@ export const buttonTheme = tv(
|
|||||||
],
|
],
|
||||||
ghost: [
|
ghost: [
|
||||||
'text-elements-on-tertiary',
|
'text-elements-on-tertiary',
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
'border',
|
'border',
|
||||||
'border-transparent',
|
'border-transparent',
|
||||||
'bg-transparent',
|
'bg-transparent',
|
||||||
'hover:bg-controls-tertiary-hovered',
|
'hover:bg-controls-tertiary-hovered',
|
||||||
'dark:hover:bg-overlay2',
|
|
||||||
'hover:border-border-interactive-hovered',
|
'hover:border-border-interactive-hovered',
|
||||||
'focus-visible:bg-controls-tertiary-hovered',
|
'focus-visible:bg-controls-tertiary-hovered',
|
||||||
'focus-visible:border-border-interactive-hovered',
|
'focus-visible:border-border-interactive-hovered',
|
||||||
@ -96,7 +93,7 @@ export const buttonTheme = tv(
|
|||||||
'text-elements-on-danger',
|
'text-elements-on-danger',
|
||||||
'border',
|
'border',
|
||||||
'border-transparent',
|
'border-transparent',
|
||||||
'bg-error',
|
'bg-border-danger',
|
||||||
'hover:bg-controls-danger-hovered',
|
'hover:bg-controls-danger-hovered',
|
||||||
'focus-visible:bg-controls-danger-hovered',
|
'focus-visible:bg-controls-danger-hovered',
|
||||||
'disabled:text-elements-on-disabled',
|
'disabled:text-elements-on-disabled',
|
||||||
|
@ -40,11 +40,11 @@ abbr[title] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile {
|
.react-calendar__tile {
|
||||||
@apply h-12 w-12 text-elements-high-em dark:text-foreground;
|
@apply h-12 w-12 text-elements-high-em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile:hover {
|
.react-calendar__tile:hover {
|
||||||
@apply bg-base-bg-emphasized dark:bg-overlay3 rounded-lg;
|
@apply bg-base-bg-emphasized rounded-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile:focus-visible {
|
.react-calendar__tile:focus-visible {
|
||||||
@ -52,7 +52,7 @@ abbr[title] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--now {
|
.react-calendar__tile--now {
|
||||||
@apply bg-base-bg-emphasized dark:bg-overlay3 text-elements-high-em rounded-lg;
|
@apply bg-base-bg-emphasized text-elements-high-em rounded-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--now:hover {
|
.react-calendar__tile--now:hover {
|
||||||
@ -77,7 +77,7 @@ abbr[title] {
|
|||||||
|
|
||||||
/* Range -- START */
|
/* Range -- START */
|
||||||
.react-calendar__tile--range {
|
.react-calendar__tile--range {
|
||||||
@apply bg-controls-secondary dark:bg-overlay3 text-elements-on-secondary rounded-none;
|
@apply bg-controls-secondary text-elements-on-secondary rounded-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--range:hover {
|
.react-calendar__tile--range:hover {
|
||||||
@ -89,7 +89,7 @@ abbr[title] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--rangeStart {
|
.react-calendar__tile--rangeStart {
|
||||||
@apply bg-controls-primary dark:bg-primary text-elements-on-primary rounded-lg;
|
@apply bg-controls-primary text-elements-on-primary rounded-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--rangeStart:hover {
|
.react-calendar__tile--rangeStart:hover {
|
||||||
@ -101,7 +101,7 @@ abbr[title] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--rangeEnd {
|
.react-calendar__tile--rangeEnd {
|
||||||
@apply bg-controls-primary dark:bg-primary text-elements-on-primary rounded-lg;
|
@apply bg-controls-primary text-elements-on-primary rounded-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-calendar__tile--rangeEnd:hover {
|
.react-calendar__tile--rangeEnd:hover {
|
||||||
|
@ -5,9 +5,7 @@ export const calendarTheme = tv({
|
|||||||
wrapper: [
|
wrapper: [
|
||||||
'max-w-[352px]',
|
'max-w-[352px]',
|
||||||
'bg-surface-floating',
|
'bg-surface-floating',
|
||||||
'dark:bg-overlay2',
|
|
||||||
'shadow-dropdown',
|
'shadow-dropdown',
|
||||||
'dark:shadow-background',
|
|
||||||
'rounded-xl',
|
'rounded-xl',
|
||||||
],
|
],
|
||||||
calendar: ['flex', 'flex-col', 'py-2', 'px-2', 'gap-2'],
|
calendar: ['flex', 'flex-col', 'py-2', 'px-2', 'gap-2'],
|
||||||
@ -30,12 +28,9 @@ export const calendarTheme = tv({
|
|||||||
'border',
|
'border',
|
||||||
'border-border-interactive',
|
'border-border-interactive',
|
||||||
'text-elements-high-em',
|
'text-elements-high-em',
|
||||||
'dark:text-foreground',
|
|
||||||
'shadow-field',
|
'shadow-field',
|
||||||
'bg-white',
|
'bg-white',
|
||||||
'dark:bg-overlay3',
|
|
||||||
'hover:bg-base-bg-alternate',
|
'hover:bg-base-bg-alternate',
|
||||||
'dark:hover:bg-foreground-secondary',
|
|
||||||
'focus-visible:bg-base-bg-alternate',
|
'focus-visible:bg-base-bg-alternate',
|
||||||
],
|
],
|
||||||
footer: [
|
footer: [
|
||||||
|
@ -280,7 +280,6 @@ export const Calendar = ({
|
|||||||
showNavigation={false}
|
showNavigation={false}
|
||||||
selectRange={selectRange}
|
selectRange={selectRange}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
// tileClassName="dark:text-foreground-secondary dark:hover:bg-overlay3"
|
|
||||||
onClickMonth={(date) => handleChangeNavigation('month', date)}
|
onClickMonth={(date) => handleChangeNavigation('month', date)}
|
||||||
onClickYear={(date) => handleChangeNavigation('year', date)}
|
onClickYear={(date) => handleChangeNavigation('year', date)}
|
||||||
/>
|
/>
|
||||||
@ -298,20 +297,19 @@ export const Calendar = ({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{value && (
|
{value && (
|
||||||
<Button variant="danger" onClick={handleReset} shape="default">
|
<Button variant="danger" onClick={handleReset}>
|
||||||
RESET
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<Button variant="tertiary" onClick={onCancel} shape="default">
|
<Button variant="tertiary" onClick={onCancel}>
|
||||||
CANCEL
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={!value}
|
disabled={!value}
|
||||||
onClick={() => (value ? onSelect?.(value) : null)}
|
onClick={() => (value ? onSelect?.(value) : null)}
|
||||||
shape="default"
|
|
||||||
>
|
>
|
||||||
SELECT
|
Select
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -11,9 +11,7 @@ export const getCheckboxVariant = tv({
|
|||||||
'focus-visible:text-controls-disabled',
|
'focus-visible:text-controls-disabled',
|
||||||
'group-focus-visible:text-controls-disabled',
|
'group-focus-visible:text-controls-disabled',
|
||||||
'data-[state=checked]:text-elements-on-primary',
|
'data-[state=checked]:text-elements-on-primary',
|
||||||
'dark:data-[state=checked]:text-foreground',
|
|
||||||
'data-[state=checked]:group-focus-visible:text-elements-on-primary',
|
'data-[state=checked]:group-focus-visible:text-elements-on-primary',
|
||||||
'dark:data-[state=checked]:group-focus-visible:text-foreground',
|
|
||||||
'data-[state=indeterminate]:text-elements-on-primary',
|
'data-[state=indeterminate]:text-elements-on-primary',
|
||||||
'data-[state=checked]:data-[disabled]:text-elements-on-disabled-active',
|
'data-[state=checked]:data-[disabled]:text-elements-on-disabled-active',
|
||||||
],
|
],
|
||||||
@ -25,7 +23,6 @@ export const getCheckboxVariant = tv({
|
|||||||
'border',
|
'border',
|
||||||
'border-border-interactive/10',
|
'border-border-interactive/10',
|
||||||
'bg-controls-tertiary',
|
'bg-controls-tertiary',
|
||||||
'dark:bg-background',
|
|
||||||
'rounded-md',
|
'rounded-md',
|
||||||
'transition-all',
|
'transition-all',
|
||||||
'duration-150',
|
'duration-150',
|
||||||
@ -33,13 +30,9 @@ export const getCheckboxVariant = tv({
|
|||||||
'shadow-button',
|
'shadow-button',
|
||||||
'group-hover:border-border-interactive/[0.14]',
|
'group-hover:border-border-interactive/[0.14]',
|
||||||
'group-hover:bg-controls-tertiary',
|
'group-hover:bg-controls-tertiary',
|
||||||
'dark:group-hover:bg-overlay',
|
|
||||||
'data-[state=checked]:bg-controls-primary',
|
'data-[state=checked]:bg-controls-primary',
|
||||||
'dark:data-[state=checked]:bg-primary',
|
|
||||||
'data-[state=checked]:hover:bg-controls-primary-hovered',
|
'data-[state=checked]:hover:bg-controls-primary-hovered',
|
||||||
'dark:data-[state=checked]:hover:bg-primary-hovered',
|
|
||||||
'data-[state=checked]:focus-visible:bg-controls-primary-hovered',
|
'data-[state=checked]:focus-visible:bg-controls-primary-hovered',
|
||||||
'dark:data-[state=checked]:focus-visible:bg-primary-hovered',
|
|
||||||
'data-[disabled]:bg-controls-disabled',
|
'data-[disabled]:bg-controls-disabled',
|
||||||
'data-[disabled]:shadow-none',
|
'data-[disabled]:shadow-none',
|
||||||
'data-[disabled]:hover:border-border-interactive/10',
|
'data-[disabled]:hover:border-border-interactive/10',
|
||||||
@ -50,17 +43,12 @@ export const getCheckboxVariant = tv({
|
|||||||
'text-sm',
|
'text-sm',
|
||||||
'tracking-[-0.006em]',
|
'tracking-[-0.006em]',
|
||||||
'text-elements-high-em',
|
'text-elements-high-em',
|
||||||
'dark:text-foreground',
|
|
||||||
'flex',
|
'flex',
|
||||||
'flex-col',
|
'flex-col',
|
||||||
'gap-1',
|
'gap-1',
|
||||||
'px-1',
|
'px-1',
|
||||||
],
|
],
|
||||||
description: [
|
description: ['text-xs', 'text-elements-low-em'],
|
||||||
'text-xs',
|
|
||||||
'text-elements-low-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
disabled: {
|
disabled: {
|
||||||
|
@ -11,7 +11,7 @@ export const GitIcon: React.FC<CustomIconProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M35.7782 16.4219L20.0791 0.723956C19.864 0.508762 19.6087 0.338053 19.3276 0.221583C19.0466 0.105114 18.7453 0.045166 18.4411 0.045166C18.1368 0.045166 17.8356 0.105114 17.5545 0.221583C17.2735 0.338053 17.0181 0.508762 16.8031 0.723956L13.5443 3.9843L17.6788 8.11882C18.1649 7.95374 18.6875 7.92797 19.1875 8.04442C19.6874 8.16088 20.1448 8.41491 20.5079 8.77778C20.8731 9.14329 21.128 9.60418 21.2435 10.1077C21.359 10.6113 21.3304 11.1372 21.161 11.6253L25.1473 15.6103C25.6355 15.4408 26.1616 15.4122 26.6653 15.5279C27.169 15.6437 27.6299 15.899 27.9952 16.2646C28.251 16.5204 28.454 16.8241 28.5925 17.1584C28.7309 17.4926 28.8022 17.8509 28.8022 18.2127C28.8022 18.5745 28.7309 18.9328 28.5925 19.267C28.454 19.6013 28.251 19.905 27.9952 20.1608C27.4779 20.6776 26.7766 20.9678 26.0455 20.9678C25.3143 20.9678 24.6131 20.6776 24.0958 20.1608C23.7116 19.7759 23.4497 19.286 23.3434 18.7526C23.237 18.2192 23.2907 17.6663 23.4979 17.1634L19.7805 13.4472V23.2287C20.1729 23.4225 20.5134 23.707 20.7739 24.0586C21.0345 24.4103 21.2075 24.8189 21.2786 25.2507C21.3497 25.6825 21.317 26.1251 21.183 26.5417C21.0491 26.9583 20.8178 27.337 20.5083 27.6465C20.2525 27.9023 19.9488 28.1053 19.6146 28.2438C19.2803 28.3822 18.922 28.4535 18.5602 28.4535C18.1984 28.4535 17.8402 28.3822 17.5059 28.2438C17.1716 28.1053 16.8679 27.9023 16.6121 27.6465C16.3562 27.3907 16.1532 27.0869 16.0147 26.7526C15.8762 26.4183 15.8049 26.06 15.8049 25.6982C15.8049 25.3363 15.8762 24.978 16.0147 24.6437C16.1532 24.3094 16.3562 24.0057 16.6121 23.7499C16.8699 23.4916 17.1763 23.2869 17.5137 23.1477V13.2762C17.1777 13.1378 16.8724 12.9344 16.6153 12.6777C16.3582 12.421 16.1543 12.116 16.0154 11.7802C15.8765 11.4445 15.8053 11.0846 15.8059 10.7212C15.8065 10.3579 15.8789 9.9982 16.0189 9.66291L11.9423 5.58552L1.17673 16.3483C0.742912 16.783 0.499268 17.372 0.499268 17.9862C0.499268 18.6003 0.742912 19.1893 1.17673 19.624L16.8766 35.3239C17.3113 35.7576 17.9002 36.0011 18.5143 36.0011C19.1283 36.0011 19.7172 35.7576 20.1519 35.3239L35.7782 19.6975C36.212 19.2629 36.4557 18.6738 36.4557 18.0597C36.4557 17.4456 36.212 16.8566 35.7782 16.4219Z"
|
d="M35.7782 16.4219L20.0791 0.723956C19.864 0.508762 19.6087 0.338053 19.3276 0.221583C19.0466 0.105114 18.7453 0.045166 18.4411 0.045166C18.1368 0.045166 17.8356 0.105114 17.5545 0.221583C17.2735 0.338053 17.0181 0.508762 16.8031 0.723956L13.5443 3.9843L17.6788 8.11882C18.1649 7.95374 18.6875 7.92797 19.1875 8.04442C19.6874 8.16088 20.1448 8.41491 20.5079 8.77778C20.8731 9.14329 21.128 9.60418 21.2435 10.1077C21.359 10.6113 21.3304 11.1372 21.161 11.6253L25.1473 15.6103C25.6355 15.4408 26.1616 15.4122 26.6653 15.5279C27.169 15.6437 27.6299 15.899 27.9952 16.2646C28.251 16.5204 28.454 16.8241 28.5925 17.1584C28.7309 17.4926 28.8022 17.8509 28.8022 18.2127C28.8022 18.5745 28.7309 18.9328 28.5925 19.267C28.454 19.6013 28.251 19.905 27.9952 20.1608C27.4779 20.6776 26.7766 20.9678 26.0455 20.9678C25.3143 20.9678 24.6131 20.6776 24.0958 20.1608C23.7116 19.7759 23.4497 19.286 23.3434 18.7526C23.237 18.2192 23.2907 17.6663 23.4979 17.1634L19.7805 13.4472V23.2287C20.1729 23.4225 20.5134 23.707 20.7739 24.0586C21.0345 24.4103 21.2075 24.8189 21.2786 25.2507C21.3497 25.6825 21.317 26.1251 21.183 26.5417C21.0491 26.9583 20.8178 27.337 20.5083 27.6465C20.2525 27.9023 19.9488 28.1053 19.6146 28.2438C19.2803 28.3822 18.922 28.4535 18.5602 28.4535C18.1984 28.4535 17.8402 28.3822 17.5059 28.2438C17.1716 28.1053 16.8679 27.9023 16.6121 27.6465C16.3562 27.3907 16.1532 27.0869 16.0147 26.7526C15.8762 26.4183 15.8049 26.06 15.8049 25.6982C15.8049 25.3363 15.8762 24.978 16.0147 24.6437C16.1532 24.3094 16.3562 24.0057 16.6121 23.7499C16.8699 23.4916 17.1763 23.2869 17.5137 23.1477V13.2762C17.1777 13.1378 16.8724 12.9344 16.6153 12.6777C16.3582 12.421 16.1543 12.116 16.0154 11.7802C15.8765 11.4445 15.8053 11.0846 15.8059 10.7212C15.8065 10.3579 15.8789 9.9982 16.0189 9.66291L11.9423 5.58552L1.17673 16.3483C0.742912 16.783 0.499268 17.372 0.499268 17.9862C0.499268 18.6003 0.742912 19.1893 1.17673 19.624L16.8766 35.3239C17.3113 35.7576 17.9002 36.0011 18.5143 36.0011C19.1283 36.0011 19.7172 35.7576 20.1519 35.3239L35.7782 19.6975C36.212 19.2629 36.4557 18.6738 36.4557 18.0597C36.4557 17.4456 36.212 16.8566 35.7782 16.4219Z"
|
||||||
fill="#0000F4"
|
fill="#158FFF"
|
||||||
/>
|
/>
|
||||||
</CustomIcon>
|
</CustomIcon>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ export const GithubIcon: React.FC<CustomIconProps> = (props) => {
|
|||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M9.9702 0.206024C4.45694 0.206024 0 4.69582 0 10.2503C0 14.6903 2.85571 18.4487 6.81735 19.7789C7.31265 19.8789 7.49408 19.5628 7.49408 19.2968C7.49408 19.064 7.47776 18.2658 7.47776 17.4342C4.70429 18.033 4.12674 16.2368 4.12674 16.2368C3.68102 15.0728 3.02061 14.7736 3.02061 14.7736C2.11286 14.1583 3.08673 14.1583 3.08673 14.1583C4.09367 14.2248 4.62204 15.1893 4.62204 15.1893C5.51327 16.7191 6.94939 16.2868 7.52714 16.0207C7.60959 15.3721 7.87388 14.9232 8.15449 14.6738C5.94245 14.4409 3.6151 13.5762 3.6151 9.71807C3.6151 8.62051 4.01102 7.72256 4.63837 7.02419C4.53939 6.7748 4.19265 5.74358 4.73755 4.36337C4.73755 4.36337 5.57939 4.09725 7.47755 5.39439C8.29022 5.17453 9.12832 5.06268 9.9702 5.06174C10.812 5.06174 11.6702 5.17827 12.4627 5.39439C14.361 4.09725 15.2029 4.36337 15.2029 4.36337C15.7478 5.74358 15.4008 6.7748 15.3018 7.02419C15.9457 7.72256 16.3253 8.62051 16.3253 9.71807C16.3253 13.5762 13.998 14.4242 11.7694 14.6738C12.1327 14.9897 12.4461 15.5883 12.4461 16.5362C12.4461 17.8832 12.4298 18.9642 12.4298 19.2966C12.4298 19.5628 12.6114 19.8789 13.1065 19.7791C17.0682 18.4485 19.9239 14.6903 19.9239 10.2503C19.9402 4.69582 15.4669 0.206024 9.9702 0.206024Z"
|
d="M9.9702 0.206024C4.45694 0.206024 0 4.69582 0 10.2503C0 14.6903 2.85571 18.4487 6.81735 19.7789C7.31265 19.8789 7.49408 19.5628 7.49408 19.2968C7.49408 19.064 7.47776 18.2658 7.47776 17.4342C4.70429 18.033 4.12674 16.2368 4.12674 16.2368C3.68102 15.0728 3.02061 14.7736 3.02061 14.7736C2.11286 14.1583 3.08673 14.1583 3.08673 14.1583C4.09367 14.2248 4.62204 15.1893 4.62204 15.1893C5.51327 16.7191 6.94939 16.2868 7.52714 16.0207C7.60959 15.3721 7.87388 14.9232 8.15449 14.6738C5.94245 14.4409 3.6151 13.5762 3.6151 9.71807C3.6151 8.62051 4.01102 7.72256 4.63837 7.02419C4.53939 6.7748 4.19265 5.74358 4.73755 4.36337C4.73755 4.36337 5.57939 4.09725 7.47755 5.39439C8.29022 5.17453 9.12832 5.06268 9.9702 5.06174C10.812 5.06174 11.6702 5.17827 12.4627 5.39439C14.361 4.09725 15.2029 4.36337 15.2029 4.36337C15.7478 5.74358 15.4008 6.7748 15.3018 7.02419C15.9457 7.72256 16.3253 8.62051 16.3253 9.71807C16.3253 13.5762 13.998 14.4242 11.7694 14.6738C12.1327 14.9897 12.4461 15.5883 12.4461 16.5362C12.4461 17.8832 12.4298 18.9642 12.4298 19.2966C12.4298 19.5628 12.6114 19.8789 13.1065 19.7791C17.0682 18.4485 19.9239 14.6903 19.9239 10.2503C19.9402 4.69582 15.4669 0.206024 9.9702 0.206024Z"
|
||||||
fill="#FBFBFB"
|
fill="#0B1D2E"
|
||||||
/>
|
/>
|
||||||
</CustomIcon>
|
</CustomIcon>
|
||||||
);
|
);
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
|
||||||
|
|
||||||
export const LaconicIcon: React.FC<CustomIconProps> = (props) => {
|
|
||||||
return (
|
|
||||||
<CustomIcon
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
fill="none"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<rect width="48" height="48" rx="4" fill="#29292E" />
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M16.0494 24.6233C18.8425 21.8302 20.5713 17.973 20.5706 13.7142C20.5717 13.1361 20.5396 12.5645 20.4762 12L12 12.0008L12.0003 28.2867C11.9996 30.2608 12.7522 32.2356 14.2578 33.7411C15.7633 35.2466 17.7395 36.0001 19.7139 35.9991L19.7134 35.9996L36 36L35.9995 27.5227C35.4362 27.4605 34.8645 27.4285 34.2852 27.4284C30.0275 27.4289 26.1701 29.1577 23.377 31.9507C21.3446 33.9321 18.0858 33.9325 16.0785 31.9252C14.0722 29.9191 14.0715 26.6593 16.0494 24.6233ZM34.2419 13.7624C31.9012 11.4217 28.0982 11.4208 25.7566 13.7624C23.4151 16.1038 23.4159 19.9067 25.7566 22.2473C28.0986 24.5892 31.9004 24.5889 34.2419 22.2473C36.5835 19.9059 36.5839 16.1042 34.2419 13.7624Z"
|
|
||||||
fill="#FBFBFB"
|
|
||||||
/>
|
|
||||||
</CustomIcon>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,10 +1,19 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { CustomIconProps } from '../CustomIcon';
|
import { CustomIconProps } from '../CustomIcon';
|
||||||
|
import { ReactNativeIcon } from './ReactNativeIcon';
|
||||||
import { cloneIcon } from 'utils/cloneIcon';
|
import { cloneIcon } from 'utils/cloneIcon';
|
||||||
import { PWAIcon } from './PWAIcon';
|
import { PWAIcon } from './PWAIcon';
|
||||||
import { WebAppIcon } from './WebAppIcon';
|
import { WebAppIcon } from './WebAppIcon';
|
||||||
|
import { KotlinIcon } from './KotlinIcon';
|
||||||
|
import { SwitfIcon } from './SwiftIcon';
|
||||||
|
|
||||||
const TEMPLATE_ICONS = ['pwa', 'web'] as const;
|
const TEMPLATE_ICONS = [
|
||||||
|
'react-native',
|
||||||
|
'pwa',
|
||||||
|
'web',
|
||||||
|
'kotlin',
|
||||||
|
'swift',
|
||||||
|
] as const;
|
||||||
export type TemplateIconType = (typeof TEMPLATE_ICONS)[number];
|
export type TemplateIconType = (typeof TEMPLATE_ICONS)[number];
|
||||||
|
|
||||||
export interface TemplateIconProps extends CustomIconProps {
|
export interface TemplateIconProps extends CustomIconProps {
|
||||||
@ -14,10 +23,16 @@ export interface TemplateIconProps extends CustomIconProps {
|
|||||||
export const TemplateIcon = ({ type, ...props }: TemplateIconProps) => {
|
export const TemplateIcon = ({ type, ...props }: TemplateIconProps) => {
|
||||||
const renderIcon = useMemo(() => {
|
const renderIcon = useMemo(() => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case 'react-native':
|
||||||
|
return <ReactNativeIcon />;
|
||||||
case 'pwa':
|
case 'pwa':
|
||||||
return <PWAIcon />;
|
return <PWAIcon />;
|
||||||
case 'web':
|
case 'web':
|
||||||
return <WebAppIcon />;
|
return <WebAppIcon />;
|
||||||
|
case 'kotlin':
|
||||||
|
return <KotlinIcon />;
|
||||||
|
case 'swift':
|
||||||
|
return <SwitfIcon />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Invalid template icon type: ${type}`);
|
throw new Error(`Invalid template icon type: ${type}`);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { tv, type VariantProps } from 'tailwind-variants';
|
import { tv, type VariantProps } from 'tailwind-variants';
|
||||||
|
|
||||||
export const headingTheme = tv({
|
export const headingTheme = tv({
|
||||||
base: [
|
base: ['text-elements-high-em', 'font-display', 'font-normal'],
|
||||||
'text-elements-high-em',
|
|
||||||
'dark:text-foreground',
|
|
||||||
'font-display',
|
|
||||||
'font-normal',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type HeadingVariants = VariantProps<typeof headingTheme>;
|
export type HeadingVariants = VariantProps<typeof headingTheme>;
|
||||||
|
@ -18,7 +18,7 @@ export const IconWithFrame = ({
|
|||||||
'relative justify-center items-center gap-2.5 inline-flex',
|
'relative justify-center items-center gap-2.5 inline-flex',
|
||||||
'w-16 h-16 rounded-2xl shadow-inner',
|
'w-16 h-16 rounded-2xl shadow-inner',
|
||||||
'border border-b-[3px] border-border-interactive border-opacity-10',
|
'border border-b-[3px] border-border-interactive border-opacity-10',
|
||||||
'bg-background',
|
'bg-controls-secondary',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -14,29 +14,22 @@ export const inputTheme = tv(
|
|||||||
'disabled:cursor-not-allowed',
|
'disabled:cursor-not-allowed',
|
||||||
'disabled:bg-controls-disabled',
|
'disabled:bg-controls-disabled',
|
||||||
],
|
],
|
||||||
label: [
|
label: ['text-sm', 'text-elements-high-em'],
|
||||||
'text-sm',
|
|
||||||
'text-elements-high-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
description: ['text-xs', 'text-elements-low-em'],
|
description: ['text-xs', 'text-elements-low-em'],
|
||||||
input: [
|
input: [
|
||||||
'focus-ring',
|
'focus-ring',
|
||||||
'dark:focus:ring-0',
|
|
||||||
'block',
|
'block',
|
||||||
'w-full',
|
'w-full',
|
||||||
'h-full',
|
'h-full',
|
||||||
'shadow-sm',
|
'shadow-sm',
|
||||||
'border',
|
'border',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'dark:bg-overlay2',
|
|
||||||
'dark:text-foreground',
|
|
||||||
'text-elements-mid-em',
|
'text-elements-mid-em',
|
||||||
'border-border-interactive',
|
'border-border-interactive',
|
||||||
'disabled:shadow-none',
|
'disabled:shadow-none',
|
||||||
'disabled:border-none',
|
'disabled:border-none',
|
||||||
],
|
],
|
||||||
icon: ['text-elements-low-em dark:text-foreground-secondary'],
|
icon: ['text-elements-low-em'],
|
||||||
iconContainer: [
|
iconContainer: [
|
||||||
'absolute',
|
'absolute',
|
||||||
'inset-y-0',
|
'inset-y-0',
|
||||||
@ -46,13 +39,7 @@ export const inputTheme = tv(
|
|||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
],
|
],
|
||||||
helperIcon: [],
|
helperIcon: [],
|
||||||
helperText: [
|
helperText: ['flex', 'gap-2', 'items-center', 'text-elements-danger'],
|
||||||
'flex',
|
|
||||||
'gap-2',
|
|
||||||
'items-center',
|
|
||||||
'text-elements-danger',
|
|
||||||
'dark:text-primary',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
state: {
|
state: {
|
||||||
@ -67,7 +54,7 @@ export const inputTheme = tv(
|
|||||||
'shadow-none',
|
'shadow-none',
|
||||||
'focus:outline-border-danger',
|
'focus:outline-border-danger',
|
||||||
],
|
],
|
||||||
helperText: 'text-error',
|
helperText: 'text-elements-danger',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
@ -31,7 +31,6 @@ export const modalTheme = tv({
|
|||||||
'sm:px-6',
|
'sm:px-6',
|
||||||
'sm:py-5',
|
'sm:py-5',
|
||||||
'bg-base-bg-alternate',
|
'bg-base-bg-alternate',
|
||||||
'dark:bg-overlay2',
|
|
||||||
],
|
],
|
||||||
headerTitle: [
|
headerTitle: [
|
||||||
'text-base',
|
'text-base',
|
||||||
@ -40,11 +39,7 @@ export const modalTheme = tv({
|
|||||||
'sm:tracking-normal',
|
'sm:tracking-normal',
|
||||||
'text-elements-high-em',
|
'text-elements-high-em',
|
||||||
],
|
],
|
||||||
headerDescription: [
|
headerDescription: ['text-sm', 'text-elements-low-em'],
|
||||||
'text-sm',
|
|
||||||
'text-elements-low-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
footer: ['flex', 'gap-3', 'px-4', 'pb-4', 'pt-7', 'sm:pb-6', 'sm:px-6'],
|
footer: ['flex', 'gap-3', 'px-4', 'pb-4', 'pt-7', 'sm:pb-6', 'sm:px-6'],
|
||||||
content: [
|
content: [
|
||||||
'h-fit',
|
'h-fit',
|
||||||
@ -58,11 +53,8 @@ export const modalTheme = tv({
|
|||||||
'sm:max-w-[562px]',
|
'sm:max-w-[562px]',
|
||||||
'rounded-2xl',
|
'rounded-2xl',
|
||||||
'bg-base-bg',
|
'bg-base-bg',
|
||||||
'dark:bg-overlay',
|
|
||||||
'shadow-card',
|
'shadow-card',
|
||||||
'dark:shadow-background',
|
|
||||||
'text-elements-high-em',
|
'text-elements-high-em',
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
],
|
||||||
body: ['flex-1', 'px-4', 'pt-4', 'sm:pt-6', 'sm:px-6'],
|
body: ['flex-1', 'px-4', 'pt-4', 'sm:pt-6', 'sm:px-6'],
|
||||||
},
|
},
|
||||||
|
@ -4,12 +4,7 @@ export const radioTheme = tv({
|
|||||||
slots: {
|
slots: {
|
||||||
root: ['flex', 'gap-3'],
|
root: ['flex', 'gap-3'],
|
||||||
wrapper: ['flex', 'items-center', 'gap-2', 'group'],
|
wrapper: ['flex', 'items-center', 'gap-2', 'group'],
|
||||||
label: [
|
label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'],
|
||||||
'text-sm',
|
|
||||||
'tracking-[-0.006em]',
|
|
||||||
'text-elements-high-em',
|
|
||||||
'dark:text-foreground',
|
|
||||||
],
|
|
||||||
radio: [
|
radio: [
|
||||||
'w-5',
|
'w-5',
|
||||||
'h-5',
|
'h-5',
|
||||||
@ -22,7 +17,6 @@ export const radioTheme = tv({
|
|||||||
'focus-ring',
|
'focus-ring',
|
||||||
// Checked
|
// Checked
|
||||||
'data-[state=checked]:bg-controls-primary',
|
'data-[state=checked]:bg-controls-primary',
|
||||||
'data-[state=checked]:bg-controls-primary',
|
|
||||||
'data-[state=checked]:group-hover:bg-controls-primary-hovered',
|
'data-[state=checked]:group-hover:bg-controls-primary-hovered',
|
||||||
],
|
],
|
||||||
indicator: [
|
indicator: [
|
||||||
@ -42,7 +36,6 @@ export const radioTheme = tv({
|
|||||||
'after:group-focus-visible:bg-controls-disabled',
|
'after:group-focus-visible:bg-controls-disabled',
|
||||||
// Checked
|
// Checked
|
||||||
'after:data-[state=checked]:bg-elements-on-primary',
|
'after:data-[state=checked]:bg-elements-on-primary',
|
||||||
'dark:after:data-[state=checked]:bg-primary-hovered',
|
|
||||||
'after:data-[state=checked]:group-hover:bg-elements-on-primary',
|
'after:data-[state=checked]:group-hover:bg-elements-on-primary',
|
||||||
'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary',
|
'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary',
|
||||||
],
|
],
|
||||||
|
@ -9,7 +9,6 @@ export const segmentedControlsTheme = tv({
|
|||||||
'flex',
|
'flex',
|
||||||
'items-center',
|
'items-center',
|
||||||
'bg-base-bg-emphasized',
|
'bg-base-bg-emphasized',
|
||||||
'dark:bg-background',
|
|
||||||
'gap-0.5',
|
'gap-0.5',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
],
|
],
|
||||||
@ -19,7 +18,6 @@ export const segmentedControlsTheme = tv({
|
|||||||
'justify-center',
|
'justify-center',
|
||||||
'gap-2',
|
'gap-2',
|
||||||
'text-elements-mid-em',
|
'text-elements-mid-em',
|
||||||
'dark:text-foreground',
|
|
||||||
'bg-transparent',
|
'bg-transparent',
|
||||||
'border',
|
'border',
|
||||||
'border-transparent',
|
'border-transparent',
|
||||||
@ -28,7 +26,6 @@ export const segmentedControlsTheme = tv({
|
|||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'focus-ring',
|
'focus-ring',
|
||||||
'hover:bg-controls-tertiary-hovered',
|
'hover:bg-controls-tertiary-hovered',
|
||||||
'dark:hover:bg-overlay2',
|
|
||||||
'focus-visible:z-20',
|
'focus-visible:z-20',
|
||||||
'focus-visible:bg-controls-tertiary-hovered',
|
'focus-visible:bg-controls-tertiary-hovered',
|
||||||
'disabled:text-controls-disabled',
|
'disabled:text-controls-disabled',
|
||||||
@ -36,7 +33,6 @@ export const segmentedControlsTheme = tv({
|
|||||||
'disabled:cursor-not-allowed',
|
'disabled:cursor-not-allowed',
|
||||||
'disabled:border-transparent',
|
'disabled:border-transparent',
|
||||||
'data-[active=true]:bg-controls-tertiary',
|
'data-[active=true]:bg-controls-tertiary',
|
||||||
'dark:data-[active=true]:bg-overlay2',
|
|
||||||
'data-[active=true]:text-elements-high-em',
|
'data-[active=true]:text-elements-high-em',
|
||||||
'data-[active=true]:border-border-interactive/10',
|
'data-[active=true]:border-border-interactive/10',
|
||||||
'data-[active=true]:shadow-field',
|
'data-[active=true]:shadow-field',
|
||||||
|
@ -3,16 +3,8 @@ import { VariantProps, tv } from 'tailwind-variants';
|
|||||||
export const selectTheme = tv({
|
export const selectTheme = tv({
|
||||||
slots: {
|
slots: {
|
||||||
container: ['flex', 'flex-col', 'relative', 'gap-2', 'w-full'],
|
container: ['flex', 'flex-col', 'relative', 'gap-2', 'w-full'],
|
||||||
label: [
|
label: ['text-sm', 'text-elements-high-em'],
|
||||||
'text-sm',
|
description: ['text-xs', 'text-elements-low-em'],
|
||||||
'text-elements-high-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
description: [
|
|
||||||
'text-xs',
|
|
||||||
'text-elements-low-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
inputWrapper: [
|
inputWrapper: [
|
||||||
'relative',
|
'relative',
|
||||||
'flex',
|
'flex',
|
||||||
@ -22,7 +14,6 @@ export const selectTheme = tv({
|
|||||||
'w-full',
|
'w-full',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'bg-transparent',
|
'bg-transparent',
|
||||||
'dark:bg-overlay2',
|
|
||||||
'text-elements-mid-em',
|
'text-elements-mid-em',
|
||||||
'shadow-sm',
|
'shadow-sm',
|
||||||
'border',
|
'border',
|
||||||
@ -31,7 +22,7 @@ export const selectTheme = tv({
|
|||||||
'disabled:shadow-none',
|
'disabled:shadow-none',
|
||||||
'disabled:border-none',
|
'disabled:border-none',
|
||||||
],
|
],
|
||||||
input: ['outline-none', 'dark:bg-overlay2', 'dark:text-foreground'],
|
input: ['outline-none'],
|
||||||
iconContainer: [
|
iconContainer: [
|
||||||
'absolute',
|
'absolute',
|
||||||
'inset-y-0',
|
'inset-y-0',
|
||||||
@ -41,15 +32,9 @@ export const selectTheme = tv({
|
|||||||
'z-10',
|
'z-10',
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
],
|
],
|
||||||
icon: ['text-elements-mid-em', 'dark:text-foreground-secondary'],
|
icon: ['text-elements-mid-em'],
|
||||||
helperIcon: [],
|
helperIcon: [],
|
||||||
helperText: [
|
helperText: ['flex', 'gap-2', 'items-center', 'text-elements-low-em'],
|
||||||
'flex',
|
|
||||||
'gap-2',
|
|
||||||
'items-center',
|
|
||||||
'text-elements-low-em',
|
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
|
||||||
popover: [
|
popover: [
|
||||||
'z-20',
|
'z-20',
|
||||||
'absolute',
|
'absolute',
|
||||||
@ -59,14 +44,12 @@ export const selectTheme = tv({
|
|||||||
'gap-0.5',
|
'gap-0.5',
|
||||||
'min-w-full',
|
'min-w-full',
|
||||||
'bg-surface-floating',
|
'bg-surface-floating',
|
||||||
'dark:bg-overlay2',
|
|
||||||
'shadow-dropdown',
|
'shadow-dropdown',
|
||||||
'w-auto',
|
'w-auto',
|
||||||
'max-h-60',
|
'max-h-60',
|
||||||
'overflow-auto',
|
'overflow-auto',
|
||||||
'border',
|
'border',
|
||||||
'border-gray-200',
|
'border-gray-200',
|
||||||
'dark:border-overlay',
|
|
||||||
'rounded-xl',
|
'rounded-xl',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -96,7 +79,7 @@ export const selectTheme = tv({
|
|||||||
'shadow-none',
|
'shadow-none',
|
||||||
'focus:outline-border-danger',
|
'focus:outline-border-danger',
|
||||||
],
|
],
|
||||||
helperText: ['text-error'],
|
helperText: ['text-elements-danger'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
@ -12,12 +12,11 @@ export const selectItemTheme = tv({
|
|||||||
'group',
|
'group',
|
||||||
'data-[disabled]:cursor-not-allowed',
|
'data-[disabled]:cursor-not-allowed',
|
||||||
],
|
],
|
||||||
icon: ['h-4.5', 'w-4.5', 'text-elements-high-em', 'dark:text-foreground'],
|
icon: ['h-4.5', 'w-4.5', 'text-elements-high-em'],
|
||||||
content: ['flex', 'flex-1', 'whitespace-nowrap'],
|
content: ['flex', 'flex-1', 'whitespace-nowrap'],
|
||||||
label: [
|
label: [
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'text-elements-high-em',
|
'text-elements-high-em',
|
||||||
'dark:text-foreground',
|
|
||||||
'tracking-[-0.006em]',
|
'tracking-[-0.006em]',
|
||||||
'data-[disabled]:text-elements-disabled',
|
'data-[disabled]:text-elements-disabled',
|
||||||
],
|
],
|
||||||
@ -48,11 +47,7 @@ export const selectItemTheme = tv({
|
|||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
true: {
|
true: {
|
||||||
wrapper: [
|
wrapper: ['bg-base-bg-emphasized', 'data-[disabled]:bg-transparent'],
|
||||||
'bg-base-bg-emphasized',
|
|
||||||
'dark:bg-overlay3',
|
|
||||||
'data-[disabled]:bg-transparent',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { User } from 'gql-client';
|
import { User } from 'gql-client';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useDisconnect } from 'wagmi';
|
||||||
|
|
||||||
import { useGQLClient } from 'context/GQLClientContext';
|
import { useGQLClient } from 'context/GQLClientContext';
|
||||||
import {
|
import {
|
||||||
|
GlobeIcon,
|
||||||
LifeBuoyIcon,
|
LifeBuoyIcon,
|
||||||
|
LogoutIcon,
|
||||||
QuestionMarkRoundIcon,
|
QuestionMarkRoundIcon,
|
||||||
} from 'components/shared/CustomIcon';
|
} from 'components/shared/CustomIcon';
|
||||||
import { Tabs } from 'components/shared/Tabs';
|
import { Tabs } from 'components/shared/Tabs';
|
||||||
@ -13,9 +16,10 @@ import { Logo } from 'components/Logo';
|
|||||||
import { Avatar } from 'components/shared/Avatar';
|
import { Avatar } from 'components/shared/Avatar';
|
||||||
import { formatAddress } from 'utils/format';
|
import { formatAddress } from 'utils/format';
|
||||||
import { getInitials } from 'utils/geInitials';
|
import { getInitials } from 'utils/geInitials';
|
||||||
|
import { Button } from 'components/shared/Button';
|
||||||
import { cn } from 'utils/classnames';
|
import { cn } from 'utils/classnames';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
import { SHOPIFY_APP_URL } from '../../../constants';
|
import { BASE_URL } from 'utils/constants';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
mobileOpen?: boolean;
|
mobileOpen?: boolean;
|
||||||
@ -23,10 +27,12 @@ interface SidebarProps {
|
|||||||
|
|
||||||
export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
||||||
const { orgSlug } = useParams();
|
const { orgSlug } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
const isDesktop = useMediaQuery('(min-width: 960px)');
|
const isDesktop = useMediaQuery('(min-width: 960px)');
|
||||||
|
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
|
const { disconnect } = useDisconnect();
|
||||||
|
|
||||||
const fetchUser = useCallback(async () => {
|
const fetchUser = useCallback(async () => {
|
||||||
const { user } = await client.getUser();
|
const { user } = await client.getUser();
|
||||||
@ -37,6 +43,16 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
|||||||
fetchUser();
|
fetchUser();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleLogOut = useCallback(async () => {
|
||||||
|
await fetch(`${BASE_URL}/auth/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
localStorage.clear();
|
||||||
|
disconnect();
|
||||||
|
navigate('/login');
|
||||||
|
}, [disconnect, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.nav
|
<motion.nav
|
||||||
initial={{ x: -320 }}
|
initial={{ x: -320 }}
|
||||||
@ -66,21 +82,20 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
|||||||
<Tabs defaultValue="Projects" orientation="vertical">
|
<Tabs defaultValue="Projects" orientation="vertical">
|
||||||
{/* // TODO: use proper link buttons */}
|
{/* // TODO: use proper link buttons */}
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Trigger icon={<QuestionMarkRoundIcon />} value="">
|
<Tabs.Trigger
|
||||||
<a
|
icon={<GlobeIcon />}
|
||||||
className="cursor-pointer font-mono"
|
value=""
|
||||||
href={`${SHOPIFY_APP_URL}/pages/instruction-faq`}
|
className="hidden lg:flex"
|
||||||
>
|
>
|
||||||
DOCUMENTATION
|
<a className="cursor-pointer" onClick={handleLogOut}>
|
||||||
|
Log Out
|
||||||
</a>
|
</a>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
|
<Tabs.Trigger icon={<QuestionMarkRoundIcon />} value="">
|
||||||
|
<a className="cursor-pointer">Documentation</a>
|
||||||
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger icon={<LifeBuoyIcon />} value="">
|
<Tabs.Trigger icon={<LifeBuoyIcon />} value="">
|
||||||
<a
|
<a className="cursor-pointer">Support</a>
|
||||||
className="cursor-pointer font-mono"
|
|
||||||
href="https://discord.com/invite/ukhbBemyxY"
|
|
||||||
>
|
|
||||||
SUPPORT
|
|
||||||
</a>
|
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@ -100,6 +115,14 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
iconOnly
|
||||||
|
variant="ghost"
|
||||||
|
className="text-elements-low-em"
|
||||||
|
onClick={handleLogOut}
|
||||||
|
>
|
||||||
|
<LogoutIcon />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</motion.nav>
|
</motion.nav>
|
||||||
);
|
);
|
||||||
|
@ -45,7 +45,6 @@ export const switchTheme = tv({
|
|||||||
true: {
|
true: {
|
||||||
switch: [
|
switch: [
|
||||||
'bg-controls-primary',
|
'bg-controls-primary',
|
||||||
'dark:bg-primary',
|
|
||||||
'hover:bg-controls-primary-hovered',
|
'hover:bg-controls-primary-hovered',
|
||||||
'focus-visible:bg-controls-primary-hovered',
|
'focus-visible:bg-controls-primary-hovered',
|
||||||
],
|
],
|
||||||
|
@ -8,7 +8,6 @@ export const tableTheme = tv({
|
|||||||
'border-b',
|
'border-b',
|
||||||
'border-sky-950/opacity-5',
|
'border-sky-950/opacity-5',
|
||||||
'text-sky-950',
|
'text-sky-950',
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'font-medium',
|
'font-medium',
|
||||||
'leading-tight',
|
'leading-tight',
|
||||||
@ -18,7 +17,6 @@ export const tableTheme = tv({
|
|||||||
columnHeaderCell: [
|
columnHeaderCell: [
|
||||||
'p-4',
|
'p-4',
|
||||||
'text-sky-950',
|
'text-sky-950',
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'font-medium',
|
'font-medium',
|
||||||
'uppercase',
|
'uppercase',
|
||||||
@ -28,7 +26,6 @@ export const tableTheme = tv({
|
|||||||
rowHeaderCell: [
|
rowHeaderCell: [
|
||||||
'p-4',
|
'p-4',
|
||||||
'text-slate-600',
|
'text-slate-600',
|
||||||
'dark:text-foreground',
|
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'font-normal',
|
'font-normal',
|
||||||
'leading-tight',
|
'leading-tight',
|
||||||
@ -39,7 +36,6 @@ export const tableTheme = tv({
|
|||||||
'whitespace-nowrap',
|
'whitespace-nowrap',
|
||||||
'text-sm',
|
'text-sm',
|
||||||
'text-slate-600',
|
'text-slate-600',
|
||||||
'dark:text-foreground',
|
|
||||||
'font-normal',
|
'font-normal',
|
||||||
'text-left',
|
'text-left',
|
||||||
],
|
],
|
||||||
|
@ -11,17 +11,14 @@ export const tabsTheme = tv({
|
|||||||
'cursor-default',
|
'cursor-default',
|
||||||
'select-none',
|
'select-none',
|
||||||
'text-elements-low-em',
|
'text-elements-low-em',
|
||||||
'dark:text-foreground',
|
|
||||||
'border-b-2',
|
'border-b-2',
|
||||||
'border-transparent',
|
'border-transparent',
|
||||||
'hover:border-border-interactive/10',
|
'hover:border-border-interactive/10',
|
||||||
'hover:text-elements-mid-em',
|
'hover:text-elements-mid-em',
|
||||||
'dark:hover:text-foreground-secondary',
|
|
||||||
'focus-within:border-border-interactive/10',
|
'focus-within:border-border-interactive/10',
|
||||||
'data-[state=active]:font-medium',
|
'data-[state=active]:font-medium',
|
||||||
'data-[state=active]:text-elements-high-em',
|
'data-[state=active]:text-elements-high-em',
|
||||||
'data-[state=active]:border-elements-high-em',
|
'data-[state=active]:border-elements-high-em',
|
||||||
'data-[state=active]:border-primary',
|
|
||||||
// Vertical
|
// Vertical
|
||||||
'data-[orientation=vertical]:px-3',
|
'data-[orientation=vertical]:px-3',
|
||||||
'data-[orientation=vertical]:py-3',
|
'data-[orientation=vertical]:py-3',
|
||||||
@ -30,7 +27,6 @@ export const tabsTheme = tv({
|
|||||||
'data-[orientation=vertical]:rounded-xl',
|
'data-[orientation=vertical]:rounded-xl',
|
||||||
'data-[orientation=vertical]:border-transparent',
|
'data-[orientation=vertical]:border-transparent',
|
||||||
'data-[orientation=vertical]:hover:bg-base-bg-emphasized',
|
'data-[orientation=vertical]:hover:bg-base-bg-emphasized',
|
||||||
'data-[orientation=vertical]:dark:hover:bg-overlay2',
|
|
||||||
'data-[orientation=vertical]:hover:text-elements-mid-em',
|
'data-[orientation=vertical]:hover:text-elements-mid-em',
|
||||||
'data-[orientation=vertical]:hover:border-transparent',
|
'data-[orientation=vertical]:hover:border-transparent',
|
||||||
'data-[orientation=vertical]:focus-visible:border-transparent',
|
'data-[orientation=vertical]:focus-visible:border-transparent',
|
||||||
@ -38,17 +34,13 @@ export const tabsTheme = tv({
|
|||||||
'data-[orientation=vertical]:focus-visible:text-elements-mid-em',
|
'data-[orientation=vertical]:focus-visible:text-elements-mid-em',
|
||||||
'data-[orientation=vertical]:data-[state=active]:font-normal',
|
'data-[orientation=vertical]:data-[state=active]:font-normal',
|
||||||
'data-[orientation=vertical]:data-[state=active]:bg-base-bg-emphasized',
|
'data-[orientation=vertical]:data-[state=active]:bg-base-bg-emphasized',
|
||||||
'data-[orientation=vertical]:data-[state=active]:dark:bg-overlay',
|
|
||||||
'data-[orientation=vertical]:data-[state=active]:border-transparent',
|
'data-[orientation=vertical]:data-[state=active]:border-transparent',
|
||||||
'data-[orientation=vertical]:data-[state=active]:hover:text-elements-high-em',
|
'data-[orientation=vertical]:data-[state=active]:hover:text-elements-high-em',
|
||||||
'data-[orientation=vertical]:data-[state=active]:focus-visible:text-elements-high-em',
|
'data-[orientation=vertical]:data-[state=active]:focus-visible:text-elements-high-em',
|
||||||
// TODO: demo additions
|
// TODO: demo additions
|
||||||
'data-[orientation=vertical]:data-[state=active]:bg-snowball-200',
|
'data-[orientation=vertical]:data-[state=active]:bg-snowball-200',
|
||||||
'data-[orientation=vertical]:data-[state=active]:dark:bg-overlay',
|
|
||||||
'data-[orientation=vertical]:data-[state=active]:hover:bg-snowball-200',
|
'data-[orientation=vertical]:data-[state=active]:hover:bg-snowball-200',
|
||||||
'data-[orientation=vertical]:data-[state=active]:dark:hover:bg-overlay2',
|
|
||||||
'data-[orientation=vertical]:data-[state=active]:text-snowball-800',
|
'data-[orientation=vertical]:data-[state=active]:text-snowball-800',
|
||||||
'data-[orientation=vertical]:data-[state=active]:dark:text-foreground',
|
|
||||||
'data-[orientation=vertical]:data-[state=active]:hover:text-snowball-800',
|
'data-[orientation=vertical]:data-[state=active]:hover:text-snowball-800',
|
||||||
'data-[orientation=vertical]:data-[state=active]:shadow-[0px_1px_0px_0px_rgba(8,47,86,0.06)_inset]',
|
'data-[orientation=vertical]:data-[state=active]:shadow-[0px_1px_0px_0px_rgba(8,47,86,0.06)_inset]',
|
||||||
],
|
],
|
||||||
|
@ -6,10 +6,8 @@ export const tooltipTheme = tv({
|
|||||||
'z-tooltip',
|
'z-tooltip',
|
||||||
'rounded-md',
|
'rounded-md',
|
||||||
'bg-surface-high-contrast',
|
'bg-surface-high-contrast',
|
||||||
'dark:bg-overlay3',
|
|
||||||
'p-2',
|
'p-2',
|
||||||
'text-elements-on-high-contrast',
|
'text-elements-on-high-contrast',
|
||||||
'dark:text-foreground-secondary',
|
|
||||||
],
|
],
|
||||||
arrow: ['fill-surface-high-contrast'],
|
arrow: ['fill-surface-high-contrast'],
|
||||||
},
|
},
|
||||||
|