Compare commits

..
Author SHA1 Message Date
Vivian Phung f5c02f7230 correct suppressRefError 2024-05-22 19:01:49 +00:00
Vivian Phung b35f4033c5 Refactor: Collaborator Project Settings (#197)
### TL;DR

AddMemberDialog component now uses a Select dropdown for permissions instead of Checkboxes. CollaboratorsTabPanel now includes dismiss functionality for toasts.

### What changed?

- Updated AddMemberDialog to use a Select dropdown for permissions
- Added dismiss functionality for toasts in CollaboratorsTabPanel

### How to test?

Test the functionality of selecting permissions using the dropdown and toast dismissal in CollaboratorsTabPanel.

### Why make this change?

To improve user experience and UI consistency in permissions selection and toast management.
2024-05-22 14:58:47 -04:00
Vivian Phung 306d3235b3 Project Search Bar Dialog Update (#196)
### TL;DR

Reordered the properties in the `ProjectSearchBar` component to follow better coding standards.

### What changed?

In `ProjectSearchBarDialog.tsx`, the 'getItemProps' object was moved to the end of the properties list within `ProjectSearchBarItem`.

### How to test?

Verify that the `ProjectSearchBar` component functions as intended and that no properties are unduly affected by this change.

### Why make this change?

This change enhances code readability and consistency, aligning the ordering of the properties more accurately with our standards.
2024-05-22 14:54:51 -04:00
Vivian Phung e148fd8d6b Add dist/ to .prettierignore (#195)
### TL;DR

This small change adds 'dist/' directory to `.prettierignore` in the frontend package.

### What changed?

An entry for 'dist/' was added to `.prettierignore` file in the frontend package. Since we don't want to format the distribution files, we have added it to our list of ignored paths for prettier. The change just includes the addition of single line `dist/` to `.prettierignore` file.

### How to test? 

There is no specific testing needed other than PR build success, as it is a development focused change.

### Why make this change? 

The reason for this change is to prevent Prettier from installing unnecessary dependencies in the dist directory which is generated and can cause linter warnings and errors.
2024-05-22 14:50:37 -04:00
Vivian Phung f84e2c0d9d Refactor: Rename SVG Properties for React Standard (#194)
### TL;DR

A refactor of the Icon components in the front-end package has been carried out. This includes `CollaboratorsIcon.tsx`, `CopyUnfilledIcon.tsx`, and `TrashIcon.tsx`.

### What changed?

Several attributes previously written in kebab-case were changed to camelCase to adhere to JSX syntax standards. These include `stroke-linecap`, `stroke-linejoin`, `fill-rule`, and `clip-rule`.

### How to test?

Ensure that the rendering and functionality of the icons in the application remain unchanged after this update. 

### Why make this change?

The update ensures that our code complies with the preferred casing convention in JSX and avoids all potential related issues.
2024-05-22 14:46:53 -04:00
Vivian Phung 216a5670e6 [nit] remove package.json: No license field warning 2024-05-22 10:59:24 -04:00
Vivian Phung 92016c8837 format 2024-05-22 10:57:20 -04:00
Vivian Phung c6ebcafaac gpl-client/dist and delete unused Web3ModalProvider 2024-05-22 10:44:22 -04:00
Vivian Phung 6dfe85cb1a Merge branch 'ng-check-deployment-removal-record' 2024-05-22 10:41:37 -04:00
Gilbert 2bb5feeb88 Laconic frontend deployment
Squashed commit of the following:

commit 86e6600749
Author: Nabarun <nabarun@deepstacksoft.com>
Date:   Tue May 21 11:59:32 2024 +0530

    Update staging deployment records with new config values

commit 3f11ff3da1
Author: Nabarun <nabarun@deepstacksoft.com>
Date:   Tue May 21 11:02:43 2024 +0530

    Update build script for new VITE env variables

commit f614199bb8
Author: Nabarun <nabarun@deepstacksoft.com>
Date:   Fri May 17 12:34:36 2024 +0530

    Update subdomain

commit 0787d2008b
Author: Nabarun <nabarun@deepstacksoft.com>
Date:   Fri May 17 12:03:29 2024 +0530

    Update GitHub client ID and subdomain

commit b9ffb691dc
Author: Nabarun <nabarun@deepstacksoft.com>
Date:   Fri May 17 11:28:22 2024 +0530

    Add staging deployment records

commit 25e47aa5ec
Author: Nabarun <nabarun@deepstacksoft.com>
Date:   Fri May 17 10:56:26 2024 +0530

    Add deployment script for staging
2024-05-22 09:21:00 -05:00
Vivian Phung 9a74205bd5 Merge pull request #154 from snowball-tools/nv-handle-repo-exist-error
Show error in toast for creating repository that already exisits in account
2024-05-22 10:19:09 -04:00
Vivian Phung 7147611842 Merge branch 'main' into nv-handle-repo-exist-error 2024-05-22 10:16:27 -04:00
Vivian Phung 8533c41c58 Merge pull request #193 from snowball-tools/main
storybook and project settings styling
2024-05-21 07:50:36 -04:00
Vivian Phung 2074e08a0c [3/n][Storybook] Settings - Delete Project Dialog (#76) 2024-05-16 20:45:32 -04:00
Vivian Phung f01bdf2de7 [2/n][Storybook] Settings - EnvironmentVariables (#75) 2024-05-16 20:42:29 -04:00
Vivian Phung 8cb5eadfb2 EditEnvironmentVariableRow cleanup (#74) 2024-05-16 20:39:42 -04:00
Vivian Phung 41666568f5 AddEnvironmentVariableRow button alignment to bottom (#73) 2024-05-16 20:36:31 -04:00
Vivian Phung 17cf878168 [1/n][Storybook] Settings (#72) 2024-05-16 20:33:33 -04:00
Vivian Phung c72cbce615 AddEnvironmentVariableRow padding (#71) 2024-05-16 20:29:35 -04:00
Vivian Phung a69dd71117 DeleteProjectDialog update to Modal (#70) 2024-05-16 20:26:31 -04:00
Vivian Phung 5dc4d28b50 MemberCard remove x padding (#69) 2024-05-16 20:22:33 -04:00
Vivian Phung 7dce1d66ae remove x padding padding from general tab (#68) 2024-05-16 20:19:31 -04:00
Vivian Phung 46ba6d014d fix duplicate styling (#67) 2024-05-16 20:16:31 -04:00
nabarun 8b39f664ad Replace REACT_APP env with VITE 2024-04-25 16:59:02 +05:30
nabarun 5f4be30799 Remove current deployment and publish ApplicationDeploymentRemovalRequest for project DNS deployment 2024-04-25 16:59:02 +05:30
nabarun b53e12b94b Add script for publishing ApplicationDeploymentRemovalRecord record 2024-04-25 16:59:02 +05:30
nabarun f290b5c0b5 Implement checking for deployment removal records in intervals 2024-04-25 16:59:02 +05:30
nabarun 4fa6f418ba Lint fix 2024-04-25 16:59:02 +05:30
Eric Lewis 328da7fdc8 feat: submit delete deployment request
doesn't appear to work when the deployment is current?
2024-04-25 16:57:14 +05:30
Eric Lewis 8210512eea fix: missing key 2024-04-25 16:57:14 +05:30
Eric Lewis cd2dce2404 fix: don't use two refs 2024-04-25 16:57:14 +05:30
Eric Lewis c2158510d9 feat: clean up stuck builds 2024-04-25 16:57:14 +05:30
Eric Lewis 096fd04a22 fix: project card settings links 2024-04-25 16:57:14 +05:30
Eric Lewis a8d93732ce feat(gql-client): deployment removal request 2024-04-25 16:57:14 +05:30
Eric Lewis 953c3fc10b feat(gql): deployment removal request 2024-04-25 16:57:14 +05:30
Eric Lewis 3cbea57294 feat(registry): deployment removal request 2024-04-25 16:57:14 +05:30
neeraj dc6f436409 Handle review changes 2024-03-01 14:04:43 +05:30
neeraj 691edf3508 Update toast message 2024-03-01 13:26:38 +05:30
neeraj fe599f97a3 Handle error if repo already exists 2024-03-01 12:20:19 +05:30
65 changed files with 1334 additions and 281 deletions
+13 -25
View File
@@ -99,25 +99,13 @@ Let us assume the following domains for backend and frontend
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
laconic-so deployment --dir laconic-loaded-deployment exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
```
- Get the REST and GQL endpoint ports of Laconicd and replace the ports for `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
```bash
# For registryConfig.restEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
# 0.0.0.0:32777
# For registryConfig.gqlEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
# 0.0.0.0:32771
```
- Set authority in `registryConfig.authority` in backend [config file](packages/backend/environments/local.toml)
- Run the script to create bond, reserve the authority and set authority bond
```bash
@@ -157,14 +145,14 @@ Let us assume the following domains for backend and frontend
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
VITE_GITHUB_CLIENT_ID = <CLIENT_ID>
```
- Set `REACT_APP_GITHUB_PWA_TEMPLATE_REPO` and `REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
- Set `VITE_GITHUB_PWA_TEMPLATE_REPO` and `VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
VITE_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
```
- Production
@@ -172,17 +160,17 @@ Let us assume the following domains for backend and frontend
- Set the following values in [.env](packages/frontend/.env) file
```env
REACT_APP_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
VITE_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Set project home page URL to `https://dashboard.snowballtools.com`
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
- On creation of project, use the `Project ID` and set it in `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <PROJECT_ID>
VITE_WALLET_CONNECT_ID = <PROJECT_ID>
```
- Build the React application
@@ -202,17 +190,17 @@ Let us assume the following domains for backend and frontend
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
```env
REACT_APP_SERVER_URL = 'http://localhost:8000'
VITE_SERVER_URL = 'http://localhost:8000'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID.
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Project home page URL is not required to be set
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
- On creation of project, use the `Project ID` and set it in `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <Project_ID>
VITE_WALLET_CONNECT_ID = <Project_ID>
```
- The React application will be running in `http://localhost:3000/`
+11 -5
View File
@@ -10,11 +10,17 @@ if [[ -d "$DEST_DIR" ]]; then
fi
cat > $PKG_DIR/.env <<EOF
REACT_APP_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_server_url'
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid'
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo'
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo'
REACT_APP_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_app_wallet_connect_id'
VITE_SERVER_URL = 'LACONIC_HOSTED_CONFIG_server_url'
VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
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_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
VITE_ALCHEMY_API_KEY = 'LACONIC_HOSTED_CONFIG_aplchemy_api_key'
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
VITE_TURNKEY_API_BASE_URL = 'LACONIC_HOSTED_CONFIG_turnkey_api_base_url'
VITE_TURNKEY_ORGANIZATION_ID = 'LACONIC_HOSTED_CONFIG_turnkey_organization_id'
EOF
yarn || exit 1
+2
View File
@@ -1,5 +1,6 @@
{
"name": "backend",
"license": "UNLICENSED",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
@@ -43,6 +44,7 @@
"lint": "tsc --noEmit",
"test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts",
"test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts",
"test:registry:publish-deployment-removal-records": "DEBUG=snowball:* ts-node ./test/publish-deployment-removal-records.ts",
"test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts",
"test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts"
},
+13
View File
@@ -436,6 +436,19 @@ export class Database {
return Boolean(updateResult.affected);
}
async deleteDeploymentById (deploymentId: string): Promise<boolean> {
const deploymentRepository = this.dataSource.getRepository(Deployment);
const deployment = await deploymentRepository.findOneOrFail({
where: {
id: deploymentId
}
});
const deleteResult = await deploymentRepository.softRemove(deployment);
return Boolean(deleteResult);
}
async addProject (user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project);
+31 -2
View File
@@ -6,13 +6,14 @@ import {
UpdateDateColumn,
ManyToOne,
OneToOne,
JoinColumn
JoinColumn,
DeleteDateColumn
} from 'typeorm';
import { Project } from './Project';
import { Domain } from './Domain';
import { User } from './User';
import { AppDeploymentRecordAttributes } from '../types';
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
export enum Environment {
Production = 'Production',
@@ -24,6 +25,7 @@ export enum DeploymentStatus {
Building = 'Building',
Ready = 'Ready',
Error = 'Error',
Deleting = 'Deleting',
}
export interface ApplicationDeploymentRequest {
@@ -35,6 +37,18 @@ export interface ApplicationDeploymentRequest {
meta: string;
}
export interface ApplicationDeploymentRemovalRequest {
type: string;
version: string;
deployment: string;
}
export interface ApplicationDeploymentRemovalRequest {
type: string;
version: string;
deployment: string;
}
export interface ApplicationRecord {
type: string;
version: string;
@@ -98,6 +112,18 @@ export class Deployment {
@Column('simple-json', { nullable: true })
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
@Column('varchar', { nullable: true })
applicationDeploymentRemovalRequestId!: string | null;
@Column('simple-json', { nullable: true })
applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null;
@Column('varchar', { nullable: true })
applicationDeploymentRemovalRecordId!: string | null;
@Column('simple-json', { nullable: true })
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
@Column({
enum: Environment
@@ -121,4 +147,7 @@ export class Deployment {
@UpdateDateColumn()
updatedAt!: Date;
@DeleteDateColumn()
deletedAt!: Date | null;
}
+73 -2
View File
@@ -9,16 +9,19 @@ import { RegistryConfig } from './config';
import {
ApplicationRecord,
Deployment,
ApplicationDeploymentRequest
ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest
} from './entity/Deployment';
import { AppDeploymentRecord, PackageJSON } from './types';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, PackageJSON } from './types';
import { sleep } from './utils';
const log = debug('snowball:registry');
const APP_RECORD_TYPE = 'ApplicationRecord';
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
const SLEEP_DURATION = 1000;
// TODO: Move registry code to laconic-sdk/watcher-ts
@@ -229,6 +232,74 @@ export class Registry {
);
}
/**
* Fetch ApplicationDeploymentRecords by filter
*/
async getDeploymentRecordsByFilter (filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
return this.registry.queryRecords(
{
type: APP_DEPLOYMENT_RECORD_TYPE,
...filter
},
true
);
}
/**
* Fetch ApplicationDeploymentRemovalRecords for deployments
*/
async getDeploymentRemovalRecords (
deployments: Deployment[]
): Promise<AppDeploymentRemovalRecord[]> {
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
const records = await this.registry.queryRecords(
{
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE
},
true
);
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
return records.filter((record: AppDeploymentRemovalRecord) =>
deployments.some(
(deployment) =>
deployment.applicationDeploymentRemovalRequestId === record.attributes.request &&
deployment.applicationDeploymentRecordId === record.attributes.deployment
)
);
}
async createApplicationDeploymentRemovalRequest (data: {
deploymentId: string;
}): Promise<{
applicationDeploymentRemovalRequestId: string;
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
}> {
const applicationDeploymentRemovalRequest = {
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
version: '1.0.0',
deployment: data.deploymentId
};
const result = await this.registry.setRecord(
{
privateKey: this.registryConfig.privateKey,
record: applicationDeploymentRemovalRequest,
bondId: this.registryConfig.bondId
},
'',
this.registryConfig.fee
);
log(`Application deployment removal request record published: ${result.data.id}`);
log('Application deployment removal request data:', applicationDeploymentRemovalRequest);
return {
applicationDeploymentRemovalRequestId: result.data.id,
applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest
};
}
getCrn (appName: string): string {
assert(this.registryConfig.authority, "Authority doesn't exist");
return `crn://${this.registryConfig.authority}/applications/${appName}`;
+14
View File
@@ -255,6 +255,20 @@ export const createResolvers = async (service: Service): Promise<any> => {
}
},
deleteDeployment: async (
_: any,
{
deploymentId
}: { deploymentId: string; }
) => {
try {
return await service.deleteDeployment(deploymentId);
} catch (err) {
log(err);
return false;
}
},
addDomain: async (
_: any,
{ projectId, data }: { projectId: string; data: { name: string } }
+2
View File
@@ -19,6 +19,7 @@ enum DeploymentStatus {
Building
Ready
Error
Deleting
}
enum DomainStatus {
@@ -209,6 +210,7 @@ type Mutation {
deleteProject(projectId: String!): Boolean!
deleteDomain(domainId: String!): Boolean!
rollbackDeployment(projectId: String!, deploymentId: String!): Boolean!
deleteDeployment(deploymentId: String!): Boolean!
addDomain(projectId: String!, data: AddDomainInput!): Boolean!
updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean!
authenticateGitHub(code: String!): AuthResult!
+156 -12
View File
@@ -15,13 +15,16 @@ import { Permission, ProjectMember } from './entity/ProjectMember';
import { User } from './entity/User';
import { Registry } from './registry';
import { GitHubConfig, RegistryConfig } from './config';
import { AppDeploymentRecord, GitPushEventPayload, PackageJSON } from './types';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, GitPushEventPayload, PackageJSON } from './types';
import { Role } from './entity/UserOrganization';
const log = debug('snowball:service');
const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository';
// Define a constant for an hour in milliseconds
const HOUR = 1000 * 60 * 60;
interface Config {
gitHubConfig: GitHubConfig;
registryConfig: RegistryConfig;
@@ -49,6 +52,8 @@ export class Service {
init (): void {
// Start check for ApplicationDeploymentRecords asynchronously
this.checkDeployRecordsAndUpdate();
// Start check for ApplicationDeploymentRemovalRecords asynchronously
this.checkDeploymentRemovalRecordsAndUpdate();
}
/**
@@ -60,14 +65,13 @@ export class Service {
/**
* Checks for ApplicationDeploymentRecord and update corresponding deployments
* Continues check in loop after a delay of DEPLOY_RECORD_CHECK_DELAY_MS
* Continues check in loop after a delay of registryConfig.fetchDeploymentRecordDelay
*/
async checkDeployRecordsAndUpdate (): Promise<void> {
// Fetch deployments in building state
const deployments = await this.db.getDeployments({
where: {
status: DeploymentStatus.Building
// TODO: Fetch and check records for recent deployments
}
});
@@ -76,6 +80,28 @@ export class Service {
`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`
);
// Calculate a timestamp for one hour ago
const anHourAgo = Date.now() - HOUR;
// Filter out deployments started more than an hour ago and mark them as Error
const oldDeploymentsToUpdate = deployments.filter(
deployment => (Number(deployment.updatedAt) < anHourAgo)
)
.map((deployment) => {
return this.db.updateDeploymentById(deployment.id, {
status: DeploymentStatus.Error,
isCurrent: false
});
});
// If there are old deployments to update, log and perform the updates
if (oldDeploymentsToUpdate.length > 0) {
log(
`Cleaning up ${oldDeploymentsToUpdate.length} deployments stuck in ${DeploymentStatus.Building} state for over an hour`
);
await Promise.all(oldDeploymentsToUpdate);
}
// Fetch ApplicationDeploymentRecord for deployments
const records = await this.registry.getDeploymentRecords(deployments);
log(`Found ${records.length} ApplicationDeploymentRecords`);
@@ -91,6 +117,38 @@ export class Service {
}, this.config.registryConfig.fetchDeploymentRecordDelay);
}
/**
* Checks for ApplicationDeploymentRemovalRecord and remove corresponding deployments
* Continues check in loop after a delay of registryConfig.fetchDeploymentRecordDelay
*/
async checkDeploymentRemovalRecordsAndUpdate (): Promise<void> {
// Fetch deployments in deleting state
const deployments = await this.db.getDeployments({
where: {
status: DeploymentStatus.Deleting
}
});
if (deployments.length) {
log(
`Found ${deployments.length} deployments in ${DeploymentStatus.Deleting} state`
);
// Fetch ApplicationDeploymentRemovalRecords for deployments
const records = await this.registry.getDeploymentRemovalRecords(deployments);
log(`Found ${records.length} ApplicationDeploymentRemovalRecords`);
// Update deployments for which ApplicationDeploymentRemovalRecords were returned
if (records.length) {
await this.deleteDeploymentsWithRecordData(records, deployments);
}
}
this.deployRecordCheckTimeout = setTimeout(() => {
this.checkDeploymentRemovalRecordsAndUpdate();
}, this.config.registryConfig.fetchDeploymentRecordDelay);
}
/**
* Update deployments with ApplicationDeploymentRecord data
*/
@@ -153,6 +211,45 @@ export class Service {
await Promise.all(deploymentUpdatePromises);
}
/**
* Delete deployments with ApplicationDeploymentRemovalRecord data
*/
async deleteDeploymentsWithRecordData (
records: AppDeploymentRemovalRecord[],
deployments: Deployment[],
): Promise<void> {
const removedApplicationDeploymentRecordIds = records.map(record => record.attributes.deployment);
// Get removed deployments for ApplicationDeploymentRecords
const removedDeployments = deployments.filter(deployment => removedApplicationDeploymentRecordIds.includes(deployment.applicationDeploymentRecordId!))
const recordToDeploymentsMap = removedDeployments.reduce(
(acc: { [key: string]: Deployment }, deployment) => {
acc[deployment.applicationDeploymentRecordId!] = deployment;
return acc;
},
{}
);
// Update deployment data for ApplicationDeploymentRecords and delete
const deploymentUpdatePromises = records.map(async (record) => {
const deployment = recordToDeploymentsMap[record.attributes.deployment];
await this.db.updateDeploymentById(deployment.id, {
applicationDeploymentRemovalRecordId: record.id,
applicationDeploymentRemovalRecordData: record.attributes,
});
log(
`Updated deployment ${deployment.id} with ApplicationDeploymentRemovalRecord ${record.id}`
);
await this.db.deleteDeploymentById(deployment.id)
});
await Promise.all(deploymentUpdatePromises);
}
async getUser (userId: string): Promise<User | null> {
return this.db.getUser({
where: {
@@ -479,17 +576,10 @@ export class Service {
return acc;
}, {} as { [key: string]: string });
const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest(
{
deployment: newDeployment,
appName: repo,
repository: repoUrl,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`
});
// To set project DNS
if (data.environment === Environment.Production) {
// On deleting deployment later, project DNS deployment is also deleted
// So publish project DNS deployment first so that ApplicationDeploymentRecord for the same is available when deleting deployment later
await this.registry.createApplicationDeploymentRequest(
{
deployment: newDeployment,
@@ -500,6 +590,15 @@ export class Service {
});
}
const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest(
{
deployment: newDeployment,
appName: repo,
repository: repoUrl,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`
});
await this.db.updateDeploymentById(newDeployment.id, { applicationDeploymentRequestId, applicationDeploymentRequestData });
return newDeployment;
@@ -717,6 +816,51 @@ export class Service {
return newCurrentDeploymentUpdate && oldCurrentDeploymentUpdate;
}
async deleteDeployment (deploymentId: string): Promise<boolean> {
const deployment = await this.db.getDeployment({
where: {
id: deploymentId
},
relations: {
project: true
}
});
if (deployment && deployment.applicationDeploymentRecordId) {
// If deployment is current, remove deployment for project subdomain as well
if (deployment.isCurrent) {
const currentDeploymentURL = `https://${deployment.project.subDomain}`;
const deploymentRecords = await this.registry.getDeploymentRecordsByFilter({
application: deployment.applicationRecordId,
url: currentDeploymentURL
})
if (!deploymentRecords.length) {
log(`No ApplicationDeploymentRecord found for URL ${currentDeploymentURL} and ApplicationDeploymentRecord id ${deployment.applicationDeploymentRecordId}`);
return false;
}
await this.registry.createApplicationDeploymentRemovalRequest({ deploymentId: deploymentRecords[0].id });
}
const result = await this.registry.createApplicationDeploymentRemovalRequest({ deploymentId: deployment.applicationDeploymentRecordId });
await this.db.updateDeploymentById(
deployment.id,
{
status: DeploymentStatus.Deleting,
applicationDeploymentRemovalRequestId: result.applicationDeploymentRemovalRequestId,
applicationDeploymentRemovalRequestData: result.applicationDeploymentRemovalRequestData
}
);
return (result !== undefined || result !== null);
}
return false;
}
async addDomain (
projectId: string,
data: { name: string }
+11
View File
@@ -38,6 +38,13 @@ export interface AppDeploymentRecordAttributes {
version: string;
}
export interface AppDeploymentRemovalRecordAttributes {
deployment: string;
request: string;
type: "ApplicationDeploymentRemovalRecord";
version: string;
}
interface RegistryRecord {
id: string;
names: string[] | null;
@@ -50,3 +57,7 @@ interface RegistryRecord {
export interface AppDeploymentRecord extends RegistryRecord {
attributes: AppDeploymentRecordAttributes;
}
export interface AppDeploymentRemovalRecord extends RegistryRecord {
attributes: AppDeploymentRemovalRecordAttributes;
}
@@ -7,7 +7,7 @@ import { Registry } from '@snowballtools/laconic-sdk';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { getConfig } from '../src/utils';
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
import { Deployment, DeploymentStatus, Environment } from '../src/entity/Deployment';
const log = debug('snowball:publish-deploy-records');
@@ -40,7 +40,7 @@ async function main () {
});
for await (const deployment of deployments) {
const url = `${deployment.project.name}-${deployment.id}.${misc.projectDomain}`;
const url = `https://${deployment.project.name}-${deployment.id}.${misc.projectDomain}`;
const applicationDeploymentRecord = {
type: 'ApplicationDeploymentRecord',
@@ -71,6 +71,21 @@ async function main () {
registryConfig.fee
);
// Remove deployment for project subdomain if deployment is for production environment
if (deployment.environment === Environment.Production) {
applicationDeploymentRecord.url = `https://${deployment.project.subDomain}`
await registry.setRecord(
{
privateKey: registryConfig.privateKey,
record: applicationDeploymentRecord,
bondId: registryConfig.bondId
},
'',
registryConfig.fee
);
}
log('Application deployment record data:', applicationDeploymentRecord);
log(`Application deployment record published: ${result.data.id}`);
}
@@ -0,0 +1,67 @@
import debug from 'debug';
import { DataSource } from 'typeorm';
import path from 'path';
import { Registry } from '@cerc-io/laconic-sdk';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { getConfig } from '../src/utils';
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
const log = debug('snowball:publish-deployment-removal-records');
async function main () {
const { registryConfig, database, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
const registry = new Registry(
registryConfig.gqlEndpoint,
registryConfig.restEndpoint,
registryConfig.chainId
);
const dataSource = new DataSource({
type: 'better-sqlite3',
database: database.dbPath,
synchronize: true,
entities: [path.join(__dirname, '../src/entity/*')]
});
await dataSource.initialize();
const deploymentRepository = dataSource.getRepository(Deployment);
const deployments = await deploymentRepository.find({
relations: {
project: true
},
where: {
status: DeploymentStatus.Deleting
}
});
for await (const deployment of deployments) {
const applicationDeploymentRemovalRecord = {
type: "ApplicationDeploymentRemovalRecord",
version: "1.0.0",
deployment: deployment.applicationDeploymentRecordId,
request: deployment.applicationDeploymentRemovalRequestId,
}
const result = await registry.setRecord(
{
privateKey: registryConfig.privateKey,
record: applicationDeploymentRemovalRecord,
bondId: registryConfig.bondId
},
'',
registryConfig.fee
);
log('Application deployment removal record data:', applicationDeploymentRemovalRecord);
log(`Application deployment removal record published: ${result.data.id}`);
}
}
main().catch((err) => {
log(err);
});
+12 -3
View File
@@ -9,9 +9,18 @@
```
- Run script to deploy app
```
./deploy-frontend.sh
```
- To deploy frontend app to `dashboard.staging.apps.snowballtools.com`
```
./deploy-frontend.staging.sh
```
- To deploy frontend app to `dashboard.apps.snowballtools.com`
```
./deploy-frontend.sh
```
- Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository
+9
View File
@@ -0,0 +1,9 @@
services:
cns:
restEndpoint: 'http://console.laconic.com:1317'
gqlEndpoint: 'http://console.laconic.com:9473/api'
userKey: 87d00f66a73e2ca428adeb49ba9164d0ad9a87edc60e33d46ad3031b9c5701fe
bondId: 098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661
chainId: laconic_9000-1
gas: 550000
fees: 200000aphoton
+6 -6
View File
@@ -35,12 +35,12 @@ record:
dns: dashboard
config:
env:
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# Extract version from ../frontend/package.json
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
CONFIG_FILE=config.staging.yml
REGISTRY_BOND_ID="098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661"
# 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
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE cns 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
# Set application-record version if no previous records were found
NEW_APPLICATION_VERSION=0.0.1
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: crn://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: 905c09553f527d2cdff5
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/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: localhost
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
cat > ./staging-records/application-record.yml <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: staging-snowballtools-base-frontend
app_version: $PACKAGE_VERSION
EOF
echo "Files generated successfully."
RECORD_FILE=staging-records/application-record.yml
# Publish ApplicationRecord
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
echo "ApplicationRecord published"
echo $RECORD_ID
# Set name to record
REGISTRY_APP_CRN="crn://staging-snowballtools/applications/staging-snowballtools-base-frontend"
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
sleep 2
# Set name if latest release
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
# Check if record found for REGISTRY_APP_CRN
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_CRN."
exit 1
fi
RECORD_FILE=staging-records/application-deployment-request.yml
sleep 2
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID
@@ -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: 905c09553f527d2cdff5
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/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: localhost
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 @ Tuesday 21 May 2024 06:17:23 AM UTC
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
@@ -0,0 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.2
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
app_type: webapp
name: staging-snowballtools-base-frontend
app_version: 0.0.0
+1
View File
@@ -0,0 +1 @@
dist/
+1 -1
View File
@@ -102,4 +102,4 @@
"typescript": "^5.3.3",
"vite": "^5.2.0"
}
}
}
@@ -53,6 +53,16 @@ export const ProjectCard = ({
navigate(`projects/${project.id}`);
}, [project.id, navigate]);
const navigateToSettingsOnClick = useCallback(
(
e: React.MouseEvent<HTMLLIElement> | React.MouseEvent<HTMLButtonElement>,
) => {
e.stopPropagation();
navigate(`projects/${project.id}/settings`);
},
[project.id, navigate],
);
return (
<div
{...props}
@@ -92,8 +102,15 @@ export const ProjectCard = ({
</Button>
</MenuHandler>
<MenuList>
<MenuItem>Project settings</MenuItem>
<MenuItem className="text-red-500">Delete project</MenuItem>
<MenuItem onClick={navigateToSettingsOnClick}>
Project settings
</MenuItem>
<MenuItem
className="text-red-500"
onClick={navigateToSettingsOnClick}
>
Delete project
</MenuItem>
</MenuList>
</Menu>
</div>
@@ -96,9 +96,9 @@ export const ProjectSearchBarDialog = ({
</p>
</div>
<ProjectSearchBarItem
{...getItemProps({ item, index })}
key={item.id}
item={item}
{...getItemProps({ item, index })}
/>
</>
))
@@ -155,13 +155,13 @@ export const RepositoryList = () => {
{Boolean(repositoryDetails.length) ? (
<div className="flex flex-col gap-2">
{repositoryDetails.map((repo, index) => (
<>
<ProjectRepoCard repository={repo} key={index} />
<div key={index}>
<ProjectRepoCard repository={repo} />
{/* Horizontal line */}
{index !== repositoryDetails.length - 1 && (
<div className="border-b border-border-separator/[0.06] w-full" />
)}
</>
</div>
))}
</div>
) : (
@@ -38,6 +38,7 @@ const STATUS_COLORS: {
[DeploymentStatus.Building]: 'emphasized',
[DeploymentStatus.Ready]: 'positive',
[DeploymentStatus.Error]: 'negative',
[DeploymentStatus.Deleting]: 'neutral',
};
const DeploymentDetailsCard = ({
@@ -48,7 +49,10 @@ const DeploymentDetailsCard = ({
prodBranchDomains,
}: DeployDetailsCardProps) => {
const getIconByDeploymentStatus = (status: DeploymentStatus) => {
if (status === DeploymentStatus.Building) {
if (
status === DeploymentStatus.Building ||
status === DeploymentStatus.Deleting
) {
return <LoadingIcon className="animate-spin" />;
}
if (status === DeploymentStatus.Ready) {
@@ -9,6 +9,7 @@ import {
RefreshIcon,
RocketIcon,
UndoIcon,
CrossCircleIcon,
} from 'components/shared/CustomIcon';
import {
Menu,
@@ -79,6 +80,16 @@ export const DeploymentMenu = ({
}
};
const deleteDeployment = async () => {
const isDeleted = await client.deleteDeployment(deployment.id);
if (isDeleted) {
await onUpdate();
toast.success('Deleted deployment');
} else {
toast.error('Unable to delete deployment');
}
};
return (
<>
<div className={cn('max-w-[32px]', className)} {...props}>
@@ -147,6 +158,12 @@ export const DeploymentMenu = ({
>
<UndoIcon /> Rollback to this version
</MenuItem>
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => deleteDeployment()}
>
<CrossCircleIcon /> Delete deployment
</MenuItem>
</MenuList>
</Menu>
</div>
@@ -19,7 +19,7 @@ const AddEnvironmentVariableRow = ({
isDeleteDisabled,
}: AddEnvironmentVariableRowProps) => {
return (
<div className="flex py-4 self-stretch">
<div className="flex gap-2 py-1 self-stretch items-end">
<Input
size="md"
{...register(`variables.${index}.key`, {
@@ -34,16 +34,9 @@ const AddEnvironmentVariableRow = ({
required: 'Value field cannot be empty',
})}
/>
<div className="self-end">
<Button
size="md"
iconOnly
onClick={onDelete}
disabled={isDeleteDisabled}
>
<TrashIcon />
</Button>
</div>
<Button size="md" iconOnly onClick={onDelete} disabled={isDeleteDisabled}>
<TrashIcon />
</Button>
</div>
);
};
@@ -1,13 +1,13 @@
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { AddProjectMemberInput, Permission } from 'gql-client';
import { Typography } from '@snowballtools/material-tailwind-react-fork';
import { Button } from 'components/shared/Button';
import { Modal } from 'components/shared/Modal';
import { Input } from 'components/shared/Input';
import { Checkbox } from 'components/shared/Checkbox';
import { Select, SelectOption } from 'components/shared/Select';
import { AddProjectMemberInput, Permission } from 'gql-client';
interface AddMemberDialogProp {
open: boolean;
@@ -17,18 +17,30 @@ interface AddMemberDialogProp {
interface formData {
emailAddress: string;
permissions: {
view: boolean;
edit: boolean;
};
canEdit: boolean;
}
const permissionViewOptions: SelectOption = {
value: Permission.View,
label: Permission.View,
};
const permissionEditOptions: SelectOption = {
value: Permission.Edit,
label: Permission.Edit,
};
const permissionsDropdownOptions: SelectOption[] = [
permissionViewOptions,
permissionEditOptions,
];
const AddMemberDialog = ({
open,
handleOpen,
handleAddMember,
}: AddMemberDialogProp) => {
const {
watch,
setValue,
handleSubmit,
register,
reset,
@@ -36,10 +48,7 @@ const AddMemberDialog = ({
} = useForm({
defaultValues: {
emailAddress: '',
permissions: {
view: true,
edit: false,
},
canEdit: false,
},
});
@@ -47,11 +56,7 @@ const AddMemberDialog = ({
reset();
handleOpen();
const permissions = Object.entries(data.permissions)
.filter(([, value]) => value)
.map(
([key]) => key.charAt(0).toUpperCase() + key.slice(1),
) as Permission[];
const permissions = [data.canEdit ? Permission.Edit : Permission.View];
await handleAddMember({ email: data.emailAddress, permissions });
}, []);
@@ -72,19 +77,19 @@ const AddMemberDialog = ({
required: 'email field cannot be empty',
})}
/>
<Typography variant="small">Permissions</Typography>
<Typography variant="small">
You can change this later if required.
</Typography>
<Checkbox
label={Permission.View}
{...register(`permissions.view`)}
color="blue"
/>
<Checkbox
label={Permission.Edit}
{...register(`permissions.edit`)}
color="blue"
<Select
label="Permissions"
description="You can change this later if required."
options={permissionsDropdownOptions}
value={
watch('canEdit') ? permissionEditOptions : permissionViewOptions
}
onChange={(value) =>
setValue(
'canEdit',
(value as SelectOption)!.value === Permission.Edit,
)
}
/>
</Modal.Body>
<Modal.Footer>
@@ -1,20 +1,13 @@
import { useCallback } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import { Project } from 'gql-client';
import {
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
Input,
Typography,
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useGQLClient } from 'context/GQLClientContext';
import { useToast } from 'components/shared/Toast';
import { Modal } from 'components/shared/Modal';
import { Button } from 'components/shared/Button';
import { Input } from 'components/shared/Input';
import { Project } from 'gql-client';
interface DeleteProjectDialogProp {
open: boolean;
@@ -60,51 +53,36 @@ const DeleteProjectDialog = ({
}, [client, project, handleOpen]);
return (
<Dialog open={open} handler={handleOpen}>
<DialogHeader className="flex justify-between">
<div>Delete project?</div>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1 rounded-3xl"
>
X
</Button>
</DialogHeader>
<form onSubmit={handleSubmit(deleteProjectHandler)}>
<DialogBody className="flex flex-col gap-2">
<Typography variant="paragraph">
Deleting your project is irreversible. Enter your projects
name&nbsp;
<span className="bg-blue-100 text-blue-700">({project.name})</span>
&nbsp;below to confirm you want to permanently delete it:
</Typography>
<Input
id="input"
{...register('projectName', {
required: 'Project name is required',
validate: (value) => value === project.name,
})}
/>
<Typography variant="small" color="red">
^ Deleting your project is irreversible.
</Typography>
</DialogBody>
<DialogFooter className="flex justify-start">
<Button variant="outlined" onClick={handleOpen} className="mr-1">
Cancel
</Button>
<Button
variant="gradient"
color="red"
type="submit"
disabled={!isValid}
>
Yes, Delete project
</Button>
</DialogFooter>
</form>
</Dialog>
<Modal open={open} onOpenChange={handleOpen}>
<Modal.Content>
<Modal.Header>Delete project?</Modal.Header>
<form onSubmit={handleSubmit(deleteProjectHandler)}>
<Modal.Body>
<Input
label={
"Deleting your project is irreversible. Enter your project's name " +
project.name +
' below to confirm you want to permanently delete it:'
}
id="input"
{...register('projectName', {
required: 'Project name is required',
validate: (value) => value === project.name,
})}
helperText="Deleting your project is irreversible."
/>
</Modal.Body>
<Modal.Footer className="flex justify-start">
<Button onClick={handleOpen} variant="tertiary">
Cancel
</Button>
<Button variant="danger" type="submit" disabled={!isValid}>
Yes, delete project
</Button>
</Modal.Footer>
</form>
</Modal.Content>
</Modal>
);
};
@@ -1,16 +1,20 @@
import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { EnvironmentVariable } from 'gql-client';
import {
IconButton,
Typography,
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteVariableDialog } from 'components/projects/Dialog/DeleteVariableDialog';
import { Input } from 'components/shared/Input';
import { Button } from 'components/shared/Button';
import {
CheckRoundFilledIcon,
CrossIcon,
EditBigIcon,
HideEyeOffIcon,
ShowEyeIcon,
TrashIcon,
} from 'components/shared/CustomIcon';
import { EnvironmentVariable } from 'gql-client';
import { useToast } from 'components/shared/Toast';
const ShowPasswordIcon = ({
handler,
@@ -26,19 +30,24 @@ const ShowPasswordIcon = ({
}}
className="cursor-pointer"
>
{isVisible ? '-' : '+'}
{isVisible ? <ShowEyeIcon size={16} /> : <HideEyeOffIcon size={16} />}
</span>
);
};
export interface EditEnvironmentVariableRowProps {
variable: EnvironmentVariable;
onUpdate: () => Promise<void>;
isFirstVariable?: boolean;
}
const EditEnvironmentVariableRow = ({
variable,
onUpdate,
}: {
variable: EnvironmentVariable;
onUpdate: () => Promise<void>;
}) => {
isFirstVariable = false,
}: EditEnvironmentVariableRowProps) => {
const client = useGQLClient();
const { toast, dismiss } = useToast();
const { handleSubmit, register, reset } = useForm({
defaultValues: {
@@ -58,9 +67,19 @@ const EditEnvironmentVariableRow = ({
if (isEnvironmentVariableRemoved) {
await onUpdate();
setDeleteDialogOpen((preVal) => !preVal);
toast.success('Variable deleted');
toast({
id: 'variable_deleted',
title: 'Variable deleted',
variant: 'success',
onDismiss: dismiss,
});
} else {
toast.error('Variable not deleted');
toast({
id: 'variable_not_deleted',
title: 'Variable not deleted',
variant: 'error',
onDismiss: dismiss,
});
}
}, [variable, onUpdate]);
@@ -71,10 +90,21 @@ const EditEnvironmentVariableRow = ({
if (isEnvironmentVariableUpdated) {
await onUpdate();
toast.success('Variable edited');
toast({
id: 'variable_updated',
title: 'Variable edited',
variant: 'success',
onDismiss: dismiss,
});
setEdit((preVal) => !preVal);
} else {
toast.error('Variable not edited');
toast({
id: 'variable_not_updated',
title: 'Variable not edited',
variant: 'error',
onDismiss: dismiss,
});
}
},
[variable, onUpdate],
@@ -86,71 +116,55 @@ const EditEnvironmentVariableRow = ({
return (
<>
<div className="flex gap-1 p-2">
<div>
<Typography variant="small">Key</Typography>
<Input disabled={!edit} {...register(`key`)} />
</div>
<div>
<Typography variant="small">Value</Typography>
<Input
disabled={!edit}
type={showPassword ? 'text' : 'password'}
leftIcon={
<ShowPasswordIcon
handler={() => {
setShowPassword((prevShowPassword) => !prevShowPassword);
}}
isVisible={showPassword}
/>
<div className="flex gap-1 items-end">
<Input
disabled={!edit}
{...register(`key`)}
label={isFirstVariable ? 'Key' : undefined}
/>
<Input
disabled={!edit}
type={showPassword || edit ? 'text' : 'password'}
label={isFirstVariable ? 'Value' : undefined}
rightIcon={
<ShowPasswordIcon
handler={() => {
setShowPassword((prevShowPassword) => !prevShowPassword);
}}
isVisible={showPassword || edit}
/>
}
{...register(`value`)}
/>
<Button
iconOnly
size="md"
onClick={() => {
edit
? handleSubmit(updateEnvironmentVariableHandler)()
: setEdit((preVal) => !preVal);
}}
>
{edit ? (
<CheckRoundFilledIcon size={16} />
) : (
<EditBigIcon size={16} />
)}
</Button>
<Button
iconOnly
size="md"
onClick={() => {
if (edit) {
reset();
setEdit((preVal) => !preVal);
} else {
setDeleteDialogOpen((preVal) => !preVal);
}
{...register(`value`)}
/>
</div>
{edit ? (
<>
<div className="self-end">
<IconButton
onClick={handleSubmit(updateEnvironmentVariableHandler)}
size="sm"
>
{'S'}
</IconButton>
</div>
<div className="self-end">
<IconButton
size="sm"
onClick={() => {
reset();
setEdit((preVal) => !preVal);
}}
>
{'C'}
</IconButton>
</div>
</>
) : (
<>
<div className="self-end">
<IconButton
size="sm"
onClick={() => {
setEdit((preVal) => !preVal);
}}
>
{'E'}
</IconButton>
</div>
<div className="self-end">
<IconButton
size="sm"
onClick={() => setDeleteDialogOpen((preVal) => !preVal)}
>
{'D'}
</IconButton>
</div>
</>
)}
}}
>
{edit ? <CrossIcon size={16} /> : <TrashIcon size={16} />}
</Button>
</div>
<DeleteVariableDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
@@ -80,7 +80,7 @@ const MemberCard = ({
return (
<div
className={`flex p-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
className={`flex py-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
>
<div className="basis-1/2">
{member.name && (
@@ -30,7 +30,7 @@ abbr[title] {
@apply !text-elements-disabled !bg-transparent;
}
.react-calendar__month-view__days__day--neighboringMonth {
.react-calendar__month-view__days__day--neighboringMonth:focus-visible {
@apply !text-elements-disabled !bg-transparent;
}
@@ -12,7 +12,7 @@ export const CollaboratorsIcon = (props: CustomIconProps) => {
<path
d="M12.5002 2.08301C14.3411 2.08301 15.8335 3.57539 15.8335 5.41634C15.8335 7.25729 14.3411 8.74968 12.5002 8.74968M14.5835 11.1659C17.2451 12.0374 19.1668 14.498 19.1668 17.083H16.6668M7.50016 8.74968C5.65921 8.74968 4.16683 7.25729 4.16683 5.41634C4.16683 3.57539 5.65921 2.08301 7.50016 2.08301C9.34111 2.08301 10.8335 3.57539 10.8335 5.41634C10.8335 7.25729 9.34111 8.74968 7.50016 8.74968ZM0.833496 17.083C0.833496 13.8613 3.81826 10.833 7.50016 10.833C11.1821 10.833 14.1668 13.8613 14.1668 17.083H0.833496Z"
stroke="currentColor"
stroke-linecap="square"
strokeLinecap="square"
/>
</CustomIcon>
);
@@ -12,8 +12,8 @@ export const CopyUnfilledIcon = (props: CustomIconProps) => {
<path
d="M6 5.625V5.025C6 4.18492 6 3.76488 6.16349 3.44401C6.3073 3.16177 6.53677 2.9323 6.81901 2.78849C7.13988 2.625 7.55992 2.625 8.4 2.625H12.975C13.8151 2.625 14.2351 2.625 14.556 2.78849C14.8382 2.9323 15.0677 3.16177 15.2115 3.44401C15.375 3.76488 15.375 4.18492 15.375 5.025V9.975C15.375 10.8151 15.375 11.2351 15.2115 11.556C15.0677 11.8382 14.8382 12.0677 14.556 12.2115C14.2351 12.375 13.8151 12.375 12.975 12.375H12.375M12.375 8.025V12.975C12.375 13.8151 12.375 14.2351 12.2115 14.556C12.0677 14.8382 11.8382 15.0677 11.556 15.2115C11.2351 15.375 10.8151 15.375 9.975 15.375H5.025C4.18492 15.375 3.76488 15.375 3.44401 15.2115C3.16177 15.0677 2.9323 14.8382 2.78849 14.556C2.625 14.2351 2.625 13.8151 2.625 12.975V8.025C2.625 7.18492 2.625 6.76488 2.78849 6.44401C2.9323 6.16177 3.16177 5.9323 3.44401 5.78849C3.76488 5.625 4.18492 5.625 5.025 5.625H9.975C10.8151 5.625 11.2351 5.625 11.556 5.78849C11.8382 5.9323 12.0677 6.16177 12.2115 6.44401C12.375 6.76488 12.375 7.18492 12.375 8.025Z"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
@@ -0,0 +1,26 @@
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const EditBigIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<g>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.61304 1.99935L7.5 1.99935C7.77614 1.99935 8 2.22321 8 2.49935C8 2.77549 7.77614 2.99935 7.5 2.99935H4.63333C4.25171 2.99935 3.99557 2.99974 3.79832 3.01585C3.6069 3.03149 3.51538 3.05941 3.45501 3.09018C3.29821 3.17007 3.17072 3.29755 3.09083 3.45436C3.06007 3.51473 3.03214 3.60625 3.01651 3.79767C3.00039 3.99492 3 4.25106 3 4.63268V11.366C3 11.7476 3.00039 12.0038 3.01651 12.201C3.03214 12.3924 3.06007 12.484 3.09083 12.5443C3.17072 12.7011 3.29821 12.8286 3.45501 12.9085C3.51538 12.9393 3.6069 12.9672 3.79832 12.9828C3.99557 12.999 4.25171 12.9993 4.63333 12.9993H11.3667C11.7483 12.9993 12.0044 12.999 12.2017 12.9828C12.3931 12.9672 12.4846 12.9393 12.545 12.9085C12.7018 12.8286 12.8293 12.7011 12.9092 12.5443C12.9399 12.484 12.9679 12.3924 12.9835 12.201C12.9996 12.0038 13 11.7476 13 11.366V8.49935C13 8.22321 13.2239 7.99935 13.5 7.99935C13.7761 7.99935 14 8.22321 14 8.49935V11.3863C14 11.7424 14 12.0396 13.9802 12.2825C13.9595 12.5357 13.9147 12.7735 13.8002 12.9983C13.6244 13.3433 13.3439 13.6238 12.999 13.7995C12.7741 13.9141 12.5364 13.9588 12.2831 13.9795C12.0403 13.9994 11.7431 13.9994 11.387 13.9993H4.61303C4.25693 13.9994 3.95971 13.9994 3.71689 13.9795C3.46363 13.9588 3.22586 13.9141 3.00102 13.7995C2.65605 13.6238 2.37559 13.3433 2.19982 12.9983C2.08526 12.7735 2.04052 12.5357 2.01983 12.2825C1.99999 12.0396 1.99999 11.7424 2 11.3863V4.61239C1.99999 4.25629 1.99999 3.95906 2.01983 3.71624C2.04052 3.46298 2.08526 3.22521 2.19982 3.00037C2.37559 2.6554 2.65605 2.37494 3.00102 2.19917C3.22586 2.08461 3.46363 2.03987 3.71689 2.01918C3.95971 1.99934 4.25694 1.99934 4.61304 1.99935Z"
fill="currentColor"
/>
<path
d="M13.963 1.92174C13.2471 1.20578 12.0863 1.20578 11.3703 1.92174L5.67504 7.617C5.45625 7.83579 5.33333 8.13254 5.33333 8.44196V10.1658C5.33333 10.442 5.55719 10.6658 5.83333 10.6658H7.55719C7.86661 10.6658 8.16336 10.5429 8.38215 10.3241L14.0774 4.62884C14.7934 3.91288 14.7934 2.75208 14.0774 2.03612L13.963 1.92174Z"
fill="currentColor"
/>
</g>
</CustomIcon>
);
};
@@ -0,0 +1,18 @@
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const HideEyeOffIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M2.02012 1.31246C1.82486 1.1172 1.50828 1.1172 1.31301 1.31246C1.11775 1.50772 1.11775 1.82431 1.31301 2.01957L2.02012 1.31246ZM1.62744 7.42685L1.1887 7.18704L1.62744 7.42685ZM11.7196 11.719L12.0731 11.3655L11.7196 11.719ZM13.9797 14.6862C14.1749 14.8815 14.4915 14.8815 14.6868 14.6862C14.8821 14.491 14.8821 14.1744 14.6868 13.9791L13.9797 14.6862ZM1.62804 8.57302L1.18932 8.81287H1.18932L1.62804 8.57302ZM5.2498 3.12216C4.99958 3.23898 4.89145 3.53652 5.00828 3.78674C5.1251 4.03695 5.42264 4.14508 5.67286 4.02826L5.2498 3.12216ZM14.3718 7.42567L14.8105 7.18582L14.3718 7.42567ZM12.3668 10.4904C12.1743 10.6884 12.1786 11.0049 12.3766 11.1975C12.5745 11.39 12.891 11.3857 13.0836 11.1877L12.3668 10.4904ZM14.3724 8.57183L13.9336 8.33203V8.33203L14.3724 8.57183ZM6.58569 6.58512L6.93924 6.23157C6.74398 6.03631 6.4274 6.03631 6.23213 6.23157L6.58569 6.58512ZM9.41411 9.41355L9.76767 9.7671C9.86144 9.67334 9.91411 9.54616 9.91411 9.41355C9.91411 9.28094 9.86144 9.15377 9.76767 9.06L9.41411 9.41355ZM1.31301 2.01957L3.92667 4.63323L4.63378 3.92612L2.02012 1.31246L1.31301 2.01957ZM2.06618 7.66665C2.78183 6.35732 3.64551 5.3672 4.57648 4.68246L3.98397 3.87689C2.91986 4.65956 1.96456 5.76756 1.1887 7.18704L2.06618 7.66665ZM3.92667 4.63323L11.366 12.0726L12.0731 11.3655L4.63378 3.92612L3.92667 4.63323ZM11.366 12.0726L13.9797 14.6862L14.6868 13.9791L12.0731 11.3655L11.366 12.0726ZM1.18932 8.81287C2.47954 11.1728 4.26886 12.674 6.21987 13.243C8.17733 13.8138 10.2399 13.428 12.0158 12.1218L11.4233 11.3162C9.88108 12.4506 8.13835 12.7608 6.49983 12.283C4.85485 11.8033 3.25748 10.5112 2.06675 8.33317L1.18932 8.81287ZM1.1887 7.18704C0.911724 7.69378 0.912792 8.30707 1.18932 8.81287L2.06675 8.33317C1.95313 8.12535 1.95308 7.87358 2.06618 7.66665L1.1887 7.18704ZM5.67286 4.02826C7.09829 3.36272 8.63166 3.32591 10.0669 3.91426C11.5069 4.50457 12.8795 5.7385 13.933 7.66553L14.8105 7.18582C13.6688 5.09759 12.1371 3.68216 10.4462 2.98899C8.75047 2.29385 6.92622 2.33943 5.2498 3.12216L5.67286 4.02826ZM13.0836 11.1877C13.7227 10.5308 14.305 9.7376 14.8111 8.81164L13.9336 8.33203C13.4669 9.18586 12.9371 9.90419 12.3668 10.4904L13.0836 11.1877ZM13.933 7.66553C14.0467 7.87336 14.0467 8.12511 13.9336 8.33203L14.8111 8.81164C15.0881 8.30489 15.087 7.69161 14.8105 7.18582L13.933 7.66553ZM7.9999 9.49934C7.17147 9.49934 6.4999 8.82776 6.4999 7.99934H5.4999C5.4999 9.38005 6.61919 10.4993 7.9999 10.4993V9.49934ZM6.4999 7.99934C6.4999 7.58499 6.66725 7.21067 6.93924 6.93868L6.23213 6.23157C5.78027 6.68343 5.4999 7.30912 5.4999 7.99934H6.4999ZM6.23213 6.93868L9.06056 9.7671L9.76767 9.06L6.93924 6.23157L6.23213 6.93868ZM9.06056 9.06C8.78857 9.33199 8.41425 9.49934 7.9999 9.49934V10.4993C8.69012 10.4993 9.3158 10.219 9.76767 9.7671L9.06056 9.06Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,18 @@
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ShowEyeIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M1.62774 7.42694L2.06646 7.66677L1.62774 7.42694ZM14.3721 7.42686L13.9333 7.66669V7.66669L14.3721 7.42686ZM1.62774 8.5731L2.06646 8.33327H2.06646L1.62774 8.5731ZM14.3721 8.57302L14.8108 8.81285L14.8108 8.81285L14.3721 8.57302ZM2.06646 7.66677C3.61474 4.83447 5.84609 3.50002 7.99991 3.5C10.1537 3.49998 12.3851 4.8344 13.9333 7.66669L14.8108 7.18703C13.1325 4.11679 10.6049 2.49998 7.9999 2.5C5.39486 2.50002 2.86735 4.11687 1.18901 7.18711L2.06646 7.66677ZM1.18901 8.81293C2.86735 11.8832 5.39486 13.5 7.99991 13.5C10.6049 13.4999 13.1325 11.8831 14.8108 8.81285L13.9333 8.33319C12.3851 11.1655 10.1537 12.4999 7.9999 12.5C5.84609 12.5 3.61473 11.1656 2.06646 8.33327L1.18901 8.81293ZM1.18901 7.18711C0.912257 7.69338 0.912257 8.30665 1.18901 8.81293L2.06646 8.33327C1.95311 8.1259 1.95311 7.87414 2.06646 7.66677L1.18901 7.18711ZM13.9333 7.66669C14.0467 7.87406 14.0467 8.12582 13.9333 8.33319L14.8108 8.81285C15.0875 8.30658 15.0875 7.69331 14.8108 7.18703L13.9333 7.66669ZM9.4999 8C9.4999 8.82843 8.82833 9.5 7.9999 9.5V10.5C9.38062 10.5 10.4999 9.38071 10.4999 8H9.4999ZM7.9999 9.5C7.17148 9.5 6.4999 8.82843 6.4999 8H5.4999C5.4999 9.38071 6.61919 10.5 7.9999 10.5V9.5ZM6.4999 8C6.4999 7.17157 7.17148 6.5 7.9999 6.5V5.5C6.61919 5.5 5.4999 6.61929 5.4999 8H6.4999ZM7.9999 6.5C8.82833 6.5 9.4999 7.17157 9.4999 8H10.4999C10.4999 6.61929 9.38062 5.5 7.9999 5.5V6.5Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -10,8 +10,8 @@ export const TrashIcon: React.FC<CustomIconProps> = (props) => {
{...props}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M5.05612 3.33398C5.52062 2.16265 6.66341 1.33398 8.00079 1.33398C9.33816 1.33398 10.481 2.16265 10.9455 3.33398H14.1668C14.443 3.33398 14.6668 3.55784 14.6668 3.83398C14.6668 4.11013 14.443 4.33398 14.1668 4.33398H13.3023L12.7463 12.952C12.684 13.9167 11.8834 14.6673 10.9167 14.6673H5.08358C4.11688 14.6673 3.31629 13.9167 3.25405 12.952L2.69805 4.33398H1.8335C1.55735 4.33398 1.3335 4.11013 1.3335 3.83398C1.3335 3.55784 1.55735 3.33398 1.8335 3.33398H5.05612ZM6.17457 3.33398C6.55973 2.73248 7.23408 2.33398 8.00079 2.33398C8.76749 2.33398 9.44184 2.73248 9.827 3.33398H6.17457ZM7.00016 7.16732C7.00016 6.89118 6.77631 6.66732 6.50016 6.66732C6.22402 6.66732 6.00016 6.89118 6.00016 7.16732V10.834C6.00016 11.1101 6.22402 11.334 6.50016 11.334C6.77631 11.334 7.00016 11.1101 7.00016 10.834V7.16732ZM9.50016 6.66732C9.77631 6.66732 10.0002 6.89118 10.0002 7.16732V10.834C10.0002 11.1101 9.77631 11.334 9.50016 11.334C9.22402 11.334 9.00016 11.1101 9.00016 10.834V7.16732C9.00016 6.89118 9.22402 6.66732 9.50016 6.66732Z"
fill="currentColor"
/>
@@ -73,6 +73,9 @@ export * from './SwitchIcon';
export * from './CollaboratorsIcon';
export * from './ChevronUpSmallIcon';
export * from './ChevronDownSmallIcon';
export * from './EditBigIcon';
export * from './ShowEyeIcon';
export * from './HideEyeOffIcon';
// Templates
export * from './templates';
@@ -103,11 +103,12 @@ export const UserSelect = ({ options, value }: UserSelectProps) => {
<div className={theme.container()}>
{/* Input */}
<div
{...getToggleButtonProps({
ref: inputWrapperRef,
suppressRefError: true,
})}
ref={inputWrapperRef}
{...getToggleButtonProps(
{
ref: inputWrapperRef,
},
{ suppressRefError: true },
)}
onClick={() => !dropdownOpen && openMenu()}
className="cursor-pointer relative py-2 pl-2 pr-4 flex min-w-[200px] w-full items-center justify-between rounded-xl bg-surface-card shadow-sm"
>
+1 -4
View File
@@ -19,10 +19,7 @@ const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
assert(
import.meta.env.VITE_SERVER_URL,
'REACT_APP_SERVER_URL is not set in env',
);
assert(import.meta.env.VITE_SERVER_URL, 'VITE_SERVER_URL is not set in env');
const gqlEndpoint = `${import.meta.env.VITE_SERVER_URL}/${SERVER_GQL_PATH}`;
const gqlClient = new GQLClient({ gqlEndpoint });
@@ -99,8 +99,8 @@ const CreateRepo = () => {
console.error((err as Error).message);
toast({
id: 'failed-to-create-project',
title: 'Failed to create project',
id: 'error-deploying-project',
title: 'Error deploying project',
variant: 'error',
onDismiss: dismiss,
});
@@ -16,7 +16,7 @@ const FIRST_MEMBER_CARD = 0;
const CollaboratorsTabPanel = () => {
const client = useGQLClient();
const { toast } = useToast();
const { toast, dismiss } = useToast();
const { project } = useOutletContext<OutletContextType>();
const [addmemberDialogOpen, setAddMemberDialogOpen] = useState(false);
@@ -39,14 +39,14 @@ const CollaboratorsTabPanel = () => {
id: 'member_added',
title: 'Member added to project',
variant: 'success',
onDismiss() {},
onDismiss: dismiss,
});
} else {
toast({
id: 'member_not_added',
title: 'Invitation not sent',
variant: 'error',
onDismiss() {},
onDismiss: dismiss,
});
}
},
@@ -63,14 +63,14 @@ const CollaboratorsTabPanel = () => {
id: 'member_removed',
title: 'Member removed from project',
variant: 'success',
onDismiss() {},
onDismiss: dismiss,
});
} else {
toast({
id: 'member_not_removed',
title: 'Not able to remove member',
variant: 'error',
onDismiss() {},
onDismiss: dismiss,
});
}
};
@@ -86,14 +86,14 @@ const CollaboratorsTabPanel = () => {
id: 'member_permission_updated',
title: 'Project member permission updated',
variant: 'success',
onDismiss() {},
onDismiss: dismiss,
});
} else {
toast({
id: 'member_permission_not_updated',
title: 'Project member permission not updated',
variant: 'error',
onDismiss() {},
onDismiss: dismiss,
});
}
},
@@ -166,7 +166,7 @@ const GeneralTabPanel = () => {
setSelectedTransferOrganization(org.org);
setOpenTransferDialog(!openTransferDialog);
})}
className="self-stretch space-y-3 px-2"
className="self-stretch space-y-3"
>
<Heading className="text-sky-950 text-lg font-medium leading-normal">
Transfer project
@@ -197,7 +197,7 @@ const GeneralTabPanel = () => {
from={project.organization.name}
to={selectedUserOrgName}
/>
<div className="self-stretch space-y-3 px-2">
<div className="self-stretch space-y-3">
<Heading className="text-sky-950 text-lg font-medium leading-normal">
Delete project
</Heading>
@@ -0,0 +1,29 @@
import { Meta, StoryObj } from '@storybook/react';
import { EditBigIcon } from 'components/shared/CustomIcon';
const meta: Meta<typeof EditBigIcon> = {
title: 'Icons/EditBigIcon',
component: EditBigIcon,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
},
};
export default meta;
type Story = StoryObj<typeof EditBigIcon>;
export const Default: Story = {
render: ({ size, name }) => <EditBigIcon size={size} name={name} />,
args: {
size: '24px',
name: 'plus',
},
};
@@ -0,0 +1,29 @@
import { Meta, StoryObj } from '@storybook/react';
import { HideEyeOffIcon } from 'components/shared/CustomIcon';
const meta: Meta<typeof HideEyeOffIcon> = {
title: 'Icons/HideEyeOffIcon',
component: HideEyeOffIcon,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
},
};
export default meta;
type Story = StoryObj<typeof HideEyeOffIcon>;
export const Default: Story = {
render: ({ size, name }) => <HideEyeOffIcon size={size} name={name} />,
args: {
size: '24px',
name: 'plus',
},
};
@@ -0,0 +1,29 @@
import { Meta, StoryObj } from '@storybook/react';
import { ShowEyeIcon } from 'components/shared/CustomIcon';
const meta: Meta<typeof ShowEyeIcon> = {
title: 'Icons/ShowEyeIcon',
component: ShowEyeIcon,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
},
};
export default meta;
type Story = StoryObj<typeof ShowEyeIcon>;
export const Default: Story = {
render: ({ size, name }) => <ShowEyeIcon size={size} name={name} />,
args: {
size: '24px',
name: 'plus',
},
};
@@ -0,0 +1,114 @@
import {
User,
Project,
Organization,
Role,
OrganizationMember,
ProjectMember,
EnvironmentVariable,
Deployment,
DeploymentStatus,
DomainStatus,
Domain,
Environment,
} from 'gql-client';
export const user: User = {
id: '1',
email: 'helloworld@helloworld.com',
isVerified: true,
createdAt: '2021-08-01T00:00:00.000Z',
name: 'Hello World',
updatedAt: '2021-08-01T00:00:00.000Z',
gitHubToken: 'GitHub Token',
};
export const organizationMember: OrganizationMember = {
id: '1',
member: user,
role: Role.Owner,
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
};
export const organization: Organization = {
id: '1',
name: 'Organization',
slug: 'organization',
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
members: [organizationMember],
projects: [],
};
export const member: ProjectMember = {
id: '1',
member: user,
permissions: [],
isPending: false,
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
};
export const environmentVariable0: EnvironmentVariable = {
id: '1',
key: 'API_KEY',
value: '123456',
environment: Environment.Development,
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
};
export const environmentVariable1: EnvironmentVariable = {
id: '2',
key: 'API_KEY_2',
value: '123456',
environment: Environment.Development,
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
};
export const domain0: Domain = {
id: '1',
name: 'Domain',
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
branch: 'Branch',
status: DomainStatus.Live,
redirectTo: null,
};
export const deployment0: Deployment = {
id: '1',
url: 'https://deployment.com',
status: DeploymentStatus.Ready,
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
branch: 'Branch',
environment: Environment.Development,
isCurrent: true,
commitHash: 'Commit Hash',
domain: domain0,
commitMessage: 'Commit Message',
createdBy: user,
};
export const project: Project = {
id: '1',
name: 'GithubUsername-ProjectName',
owner: user,
deployments: [deployment0],
repository: 'Repository',
prodBranch: 'Prod Branch',
description: 'Description',
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
framework: 'NextJS',
environmentVariables: [environmentVariable0, environmentVariable1],
organization: organization,
template: 'Template',
members: [member],
webhooks: ['beepboop'],
icon: 'Icon',
subDomain: 'SubDomain',
};
@@ -21,10 +21,45 @@ const meta: Meta<typeof AddEnvironmentVariableRow> = {
},
}),
},
} as Meta<typeof AddEnvironmentVariableRow>;
argTypes: {
onDelete: {
action: 'delete',
},
register: {
action: 'register',
},
index: {
type: 'number',
},
isDeleteDisabled: {
type: 'boolean',
},
},
args: {
isDeleteDisabled: false,
},
};
export default meta;
type Story = StoryObj<typeof AddEnvironmentVariableRow>;
export const Default: Story = {};
export const DisabledDelete: Story = {
args: {
isDeleteDisabled: true,
},
};
export const First: Story = {
args: {
index: 0,
},
};
export const Second: Story = {
args: {
index: 1,
},
};
@@ -0,0 +1,31 @@
import { Meta, StoryObj } from '@storybook/react';
import AddMemberDialog from 'components/projects/project/settings/AddMemberDialog';
const meta: Meta<typeof AddMemberDialog> = {
title: 'Project/Settings/AddMemberDialog',
component: AddMemberDialog,
tags: ['autodocs'],
argTypes: {
open: {
control: {
type: 'boolean',
},
},
handleOpen: {
action: 'open',
},
handleAddMember: {
action: 'addMember',
},
},
args: {
open: true,
},
};
export default meta;
type Story = StoryObj<typeof AddMemberDialog>;
export const Default: Story = {};
@@ -0,0 +1,50 @@
import { Meta, StoryObj } from '@storybook/react';
import {
reactRouterParameters,
withRouter,
} from 'storybook-addon-remix-react-router';
import DeleteProjectDialog from 'components/projects/project/settings/DeleteProjectDialog';
import { project } from '../../MockStoriesData';
const meta: Meta<typeof DeleteProjectDialog> = {
title: 'Project/Settings/DeleteProjectDialog',
component: DeleteProjectDialog,
tags: ['autodocs'],
decorators: [withRouter],
parameters: {
reactRouter: reactRouterParameters({
location: {
pathParams: { userId: 'me' },
},
routing: {
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings',
},
}),
},
argTypes: {
open: {
control: {
type: 'boolean',
},
},
handleOpen: {
action: 'open',
},
project: {
control: {
type: 'object',
},
},
},
args: {
open: true,
project: project,
},
};
export default meta;
type Story = StoryObj<typeof DeleteProjectDialog>;
export const Default: Story = {};
@@ -0,0 +1,38 @@
import { Meta, StoryObj } from '@storybook/react';
import DisplayEnvironmentVariables from 'components/projects/project/settings/DisplayEnvironmentVariables';
import {
environmentVariable0,
environmentVariable1,
} from '../../MockStoriesData';
import { Environment } from 'gql-client';
const meta: Meta<typeof DisplayEnvironmentVariables> = {
title: 'Project/Settings/DisplayEnvironmentVariables',
component: DisplayEnvironmentVariables,
argTypes: {
environment: {
control: {
type: 'object',
},
},
variables: {
control: {
type: 'object',
},
},
onUpdate: {
action: 'update',
},
},
args: {
environment: Environment.Development,
variables: [environmentVariable0, environmentVariable1],
},
};
export default meta;
type Story = StoryObj<typeof DisplayEnvironmentVariables>;
export const Default: Story = {};
@@ -0,0 +1,28 @@
import { Meta, StoryObj } from '@storybook/react';
import EditEnvironmentVariableRow from 'components/projects/project/settings/EditEnvironmentVariableRow';
import { environmentVariable0 } from '../../MockStoriesData';
const meta: Meta<typeof EditEnvironmentVariableRow> = {
title: 'Project/Settings/EditEnvironmentVariableRow',
component: EditEnvironmentVariableRow,
argTypes: {
variable: {
control: {
type: 'object',
},
},
onUpdate: {
action: 'update',
},
},
args: {
variable: environmentVariable0,
},
};
export default meta;
type Story = StoryObj<typeof EditEnvironmentVariableRow>;
export const Default: Story = {};
@@ -17,7 +17,7 @@ const meta: Meta<typeof SetupDomain> = {
pathParams: { userId: 'me' },
},
routing: {
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings/domains/add',
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings/domains',
},
}),
},
+1 -3
View File
@@ -14,9 +14,6 @@ export default withMT({
xxs: '400px',
xs: '480px',
},
zIndex: {
tooltip: '52',
},
letterSpacing: {
tight: '-0.084px',
},
@@ -178,6 +175,7 @@ export default withMT({
4.5: '1.125rem',
},
zIndex: {
tooltip: '52',
toast: '9999',
},
animation: {
+2 -2
View File
@@ -1,9 +1,9 @@
import { defineConfig } from 'vite';
import { defineConfig, PluginOption } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react()] as PluginOption[],
resolve: {
alias: {
utils: '/src/utils',
+7 -2
View File
@@ -15,7 +15,8 @@ declare enum Environment {
declare enum DeploymentStatus {
Building = "Building",
Ready = "Ready",
Error = "Error"
Error = "Error",
Deleting = "Deleting"
}
declare enum DomainStatus {
Live = "Live",
@@ -223,6 +224,9 @@ type RedeployToProdResponse = {
type RollbackDeploymentResponse = {
rollbackDeployment: boolean;
};
type DeleteDeploymentResponse = {
deleteDeployment: boolean;
};
type AddDomainInput = {
name: string;
};
@@ -270,10 +274,11 @@ declare class GQLClient {
deleteProject(projectId: string): Promise<DeleteProjectResponse>;
deleteDomain(domainId: string): Promise<DeleteDomainResponse>;
rollbackDeployment(projectId: string, deploymentId: string): Promise<RollbackDeploymentResponse>;
deleteDeployment(deploymentId: string): Promise<DeleteDeploymentResponse>;
addDomain(projectId: string, data: AddDomainInput): Promise<AddDomainResponse>;
getDomains(projectId: string, filter?: FilterDomainInput): Promise<GetDomainsResponse>;
authenticateGitHub(code: string): Promise<AuthenticateGitHubResponse>;
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
}
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
+7 -2
View File
@@ -15,7 +15,8 @@ declare enum Environment {
declare enum DeploymentStatus {
Building = "Building",
Ready = "Ready",
Error = "Error"
Error = "Error",
Deleting = "Deleting"
}
declare enum DomainStatus {
Live = "Live",
@@ -223,6 +224,9 @@ type RedeployToProdResponse = {
type RollbackDeploymentResponse = {
rollbackDeployment: boolean;
};
type DeleteDeploymentResponse = {
deleteDeployment: boolean;
};
type AddDomainInput = {
name: string;
};
@@ -270,10 +274,11 @@ declare class GQLClient {
deleteProject(projectId: string): Promise<DeleteProjectResponse>;
deleteDomain(domainId: string): Promise<DeleteDomainResponse>;
rollbackDeployment(projectId: string, deploymentId: string): Promise<RollbackDeploymentResponse>;
deleteDeployment(deploymentId: string): Promise<DeleteDeploymentResponse>;
addDomain(projectId: string, data: AddDomainInput): Promise<AddDomainResponse>;
getDomains(projectId: string, filter?: FilterDomainInput): Promise<GetDomainsResponse>;
authenticateGitHub(code: string): Promise<AuthenticateGitHubResponse>;
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
}
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
+17
View File
@@ -334,6 +334,11 @@ mutation ($projectId: String! ,$deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
`;
var deleteDeployment = import_client2.gql`
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
`;
var addDomain = import_client2.gql`
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
@@ -614,6 +619,17 @@ var GQLClient = class {
return data;
});
}
deleteDeployment(deploymentId) {
return __async(this, null, function* () {
const { data } = yield this.client.mutate({
mutation: deleteDeployment,
variables: {
deploymentId
}
});
return data;
});
}
addDomain(projectId, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
@@ -681,6 +697,7 @@ var DeploymentStatus = /* @__PURE__ */ ((DeploymentStatus2) => {
DeploymentStatus2["Building"] = "Building";
DeploymentStatus2["Ready"] = "Ready";
DeploymentStatus2["Error"] = "Error";
DeploymentStatus2["Deleting"] = "Deleting";
return DeploymentStatus2;
})(DeploymentStatus || {});
var DomainStatus = /* @__PURE__ */ ((DomainStatus2) => {
File diff suppressed because one or more lines are too long
+17
View File
@@ -304,6 +304,11 @@ mutation ($projectId: String! ,$deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
`;
var deleteDeployment = gql2`
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
`;
var addDomain = gql2`
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
@@ -584,6 +589,17 @@ var GQLClient = class {
return data;
});
}
deleteDeployment(deploymentId) {
return __async(this, null, function* () {
const { data } = yield this.client.mutate({
mutation: deleteDeployment,
variables: {
deploymentId
}
});
return data;
});
}
addDomain(projectId, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
@@ -651,6 +667,7 @@ var DeploymentStatus = /* @__PURE__ */ ((DeploymentStatus2) => {
DeploymentStatus2["Building"] = "Building";
DeploymentStatus2["Ready"] = "Ready";
DeploymentStatus2["Error"] = "Error";
DeploymentStatus2["Deleting"] = "Deleting";
return DeploymentStatus2;
})(DeploymentStatus || {});
var DomainStatus = /* @__PURE__ */ ((DomainStatus2) => {
File diff suppressed because one or more lines are too long
+1
View File
@@ -1,5 +1,6 @@
{
"name": "gql-client",
"license": "UNLICENSED",
"version": "1.0.0",
"main": "dist/index.js",
"module": "./dist/index.mjs",
+11
View File
@@ -276,6 +276,17 @@ export class GQLClient {
return data;
}
async deleteDeployment (deploymentId: string): Promise<types.DeleteDeploymentResponse> {
const { data } = await this.client.mutate({
mutation: mutations.deleteDeployment,
variables: {
deploymentId
}
});
return data;
}
async addDomain (projectId: string, data: types.AddDomainInput): Promise<types.AddDomainResponse> {
const result = await this.client.mutate({
mutation: mutations.addDomain,
+6
View File
@@ -82,6 +82,12 @@ mutation ($projectId: String! ,$deploymentId: String!) {
}
`;
export const deleteDeployment = gql`
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
`;
export const addDomain = gql`
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
+5
View File
@@ -21,6 +21,7 @@ export enum DeploymentStatus {
Building = 'Building',
Ready = 'Ready',
Error = 'Error',
Deleting = 'Deleting'
}
export enum DomainStatus {
@@ -269,6 +270,10 @@ export type RollbackDeploymentResponse = {
rollbackDeployment: boolean
}
export type DeleteDeploymentResponse = {
deleteDeployment: boolean
}
export type AddDomainInput = {
name: string
}