forked from cerc-io/snowballtools-base
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a37f81dd6 | ||
|
|
af31fac3ee | ||
|
|
54ae3f429d | ||
|
|
acfe78bf07 | ||
|
|
ce1833cb51 | ||
|
|
f2e59c11fd | ||
|
|
b261e7e436 | ||
|
|
934aa1a26b | ||
|
|
d975390a1b | ||
|
|
f77323364c | ||
|
|
c6a78f2116 | ||
|
|
cff9a5b2ea | ||
|
|
003b83ba21 | ||
|
|
82a1c151a8 | ||
|
|
2ada11f311 | ||
|
|
6e32d0678a | ||
|
|
198478f5fa | ||
|
|
552dfe783e | ||
|
|
e2bf5d052c | ||
|
|
33323b9bbf | ||
|
|
e2a3254563 | ||
|
|
8589bf4094 | ||
|
|
f8ebdfe7aa | ||
|
|
2166b2f800 | ||
|
|
cdd8d15e73 | ||
|
|
dc7b251988 | ||
|
|
bfb4a3f30b | ||
|
|
8f7fc888a9 | ||
|
|
0e9c3a07fd | ||
|
|
61e3e88a6c | ||
|
|
7b5ba1a5d0 | ||
|
|
b35f4033c5 | ||
|
|
306d3235b3 | ||
|
|
e148fd8d6b | ||
|
|
f84e2c0d9d |
+4
-1
@@ -6,4 +6,7 @@ yarn-error.log
|
||||
|
||||
packages/backend/environments/local.toml
|
||||
packages/backend/dev/
|
||||
packages/frontend/dist/
|
||||
packages/frontend/dist/
|
||||
|
||||
# ignore all .DS_Store files
|
||||
**/.DS_Store
|
||||
|
||||
@@ -1,220 +1,23 @@
|
||||
# snowballtools-base
|
||||
|
||||
## Setup
|
||||
This is a [yarn workspace](https://yarnpkg.com/features/workspaces) monorepo for the dashboard.
|
||||
|
||||
- Clone the `snowballtools-base` repo
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
git clone git@github.com:snowball-tools/snowballtools-base.git
|
||||
cd snowballtools-base
|
||||
```
|
||||
### Install dependencies
|
||||
|
||||
- Install dependencies in root
|
||||
In the root of the project, run:
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
```zsh
|
||||
yarn
|
||||
```
|
||||
|
||||
- Build packages
|
||||
### Build backend
|
||||
|
||||
```bash
|
||||
yarn build --ignore frontend
|
||||
```
|
||||
```zsh
|
||||
yarn build --ignore-frontend
|
||||
```
|
||||
|
||||
Let us assume the following domains for backend and frontend
|
||||
- Backend server: `api.snowballtools.com`
|
||||
- Frontend app: `dashboard.snowballtools.com`
|
||||
### Environment variables, running the development server, and deployment
|
||||
|
||||
- Configuration Files
|
||||
|
||||
- For Backend:
|
||||
|
||||
```bash
|
||||
cp packages/backend/environments/local.toml.example packages/backend/environments/local.toml
|
||||
```
|
||||
|
||||
- Production
|
||||
|
||||
- Update the following in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```toml
|
||||
[server]
|
||||
...
|
||||
[server.session]
|
||||
# Secret should be changed to a different random string
|
||||
secret = "p4yfpkqnddkui2iw7t6hbhwq74lbqs7sidnc382"
|
||||
# Set URL of the frontend app
|
||||
appOriginUrl = "https://dashboard.snowballtools.com"
|
||||
# Set to true for session cookies to work behind proxy
|
||||
trustProxy = true
|
||||
# Set empty domain when using secure connection
|
||||
domain = ""
|
||||
```
|
||||
|
||||
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
|
||||
- [OAuth App Creation](https://github.com/organizations/<org>/settings/applications/new)
|
||||
- Homepage URL: `https://dashboard.snowballtools.com`
|
||||
- Authorization callback URL: `https://dashboard.snowballtools.com/organization/projects/create`
|
||||
- Generate a new client secret after app is created
|
||||
|
||||
- Set `gitHub.webhookUrl` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```toml
|
||||
[gitHub]
|
||||
webhookUrl = "https://api.snowballtools.com"
|
||||
```
|
||||
|
||||
- Let us assume domain for Laconicd to be `api.laconic.com` and set the following in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```toml
|
||||
[registryConfig]
|
||||
fetchDeploymentRecordDelay = 5000
|
||||
# Use actual port for REST endpoint
|
||||
restEndpoint = "http://api.laconic.com:1317"
|
||||
# Use actual port for GQL endpoint
|
||||
gqlEndpoint = "http://api.laconic.com:9473/api"
|
||||
# Set private key of account to be used in Laconicd
|
||||
privateKey = "0wtu92cd4f1y791ezpjwgzzazni4dmd3q3mzqc3t6i6r9v06ji784tey6hwmnn69"
|
||||
# Set Bond ID to be used for publishing records
|
||||
bondId = "8xk8c2pb61kajwixpm223zvptr2x2ncajq0vd998p6aqhvqqep2reu6pik245epf"
|
||||
chainId = "laconic_9000-1"
|
||||
# Set authority that is existing in the chain
|
||||
authority = "laconic"
|
||||
[registryConfig.fee]
|
||||
amount = "200000"
|
||||
denom = "aphoton"
|
||||
gas = "750000"
|
||||
```
|
||||
|
||||
- Development
|
||||
|
||||
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
|
||||
- [OAuth App Creation](https://github.com/organizations/<org>/settings/applications/new)
|
||||
- Homepage URL: `http://localhost:3000`
|
||||
- Authorization callback URL: `http://localhost:3000/organization/projects/create`
|
||||
- Generate a new client secret after app is created
|
||||
|
||||
- Setup Laconicd
|
||||
- Run the laconicd stack following this [doc](https://git.vdb.to/cerc-io/stack-orchestrator/src/branch/main/docs/laconicd-with-console.md)
|
||||
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
- 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
|
||||
yarn test:registry:init
|
||||
# snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms
|
||||
```
|
||||
|
||||
- Get the bond id and set `registryConfig.bondId` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
- Setup ngrok for GitHub webhooks
|
||||
- [ngrok getting started](https://ngrok.com/docs/getting-started/)
|
||||
- Start ngrok and point to backend server endpoint
|
||||
|
||||
```bash
|
||||
ngrok http http://localhost:8000
|
||||
```
|
||||
|
||||
- Look for the forwarding URL in ngrok
|
||||
|
||||
```bash
|
||||
Forwarding https://19c1-61-95-158-116.ngrok-free.app -> http://localhost:8000
|
||||
```
|
||||
|
||||
- Set `gitHub.webhookUrl` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```toml
|
||||
[gitHub]
|
||||
webhookUrl = "https://19c1-61-95-158-116.ngrok-free.app"
|
||||
```
|
||||
|
||||
- For Frontend:
|
||||
|
||||
```bash
|
||||
cp packages/frontend/.env.example packages/frontend/.env
|
||||
```
|
||||
|
||||
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
VITE_GITHUB_CLIENT_ID = <CLIENT_ID>
|
||||
```
|
||||
|
||||
- Set `VITE_GITHUB_PWA_TEMPLATE_REPO` and `VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
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
|
||||
|
||||
- Set the following values in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
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 `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
VITE_WALLET_CONNECT_ID = <PROJECT_ID>
|
||||
```
|
||||
|
||||
- Build the React application
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
- Use a web server for hosting static built files
|
||||
|
||||
```bash
|
||||
python3 -m http.server -d build 3000
|
||||
```
|
||||
|
||||
- Development
|
||||
|
||||
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
|
||||
|
||||
```env
|
||||
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 `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
VITE_WALLET_CONNECT_ID = <Project_ID>
|
||||
```
|
||||
|
||||
- The React application will be running in `http://localhost:3000/`
|
||||
|
||||
## Development
|
||||
|
||||
- Start the backend server
|
||||
|
||||
```bash
|
||||
yarn start:backend
|
||||
```
|
||||
|
||||
- Start the frontend
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and [frontend](packages/frontend/README.md) packages.
|
||||
|
||||
@@ -1 +1,76 @@
|
||||
# Backend for Snowball Tools
|
||||
# backend
|
||||
|
||||
This backend is a [node.js](https://nodejs.org/) [express.js](https://expressjs.com/) [apollo server](https://www.apollographql.com/docs/apollo-server/) project in a [yarn workspace](https://yarnpkg.com/features/workspaces).
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Install dependencies
|
||||
|
||||
In the root of the project, run:
|
||||
|
||||
```zsh
|
||||
yarn
|
||||
```
|
||||
|
||||
### Build backend
|
||||
|
||||
```zsh
|
||||
yarn build --ignore-frontend
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
#### Local
|
||||
|
||||
Copy the `envionments/local.toml.example` file to `envionments/local.toml`:
|
||||
|
||||
```zsh
|
||||
cp envionments/local.toml.example envionments/local.toml
|
||||
```
|
||||
|
||||
#### Staging environment variables
|
||||
|
||||
In the deployment repository, update staging [staging/configmaps/config/prod.toml](https://git.vdb.to/cerc-io/snowballtools-base-api-deployments/src/commit/318c2bc09f334dca79c3501838512749f9431bf1/deployments/staging/configmaps/config/prod.toml)
|
||||
|
||||
#### Production environment variables
|
||||
|
||||
In the deployment repository, update production [production/configmaps/config/prod.toml](https://git.vdb.to/cerc-io/snowballtools-base-api-deployments/src/commit/318c2bc09f334dca79c3501838512749f9431bf1/deployments/production/configmaps/config/prod.toml)
|
||||
|
||||
### Run development server
|
||||
|
||||
```zsh
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Clone the deployer repository:
|
||||
|
||||
```zsh
|
||||
git clone git@git.vdb.to:cerc-io/snowballtools-base-api-deployments.git
|
||||
```
|
||||
|
||||
### Staging
|
||||
|
||||
```zsh
|
||||
echo trigger >> .gitea/workflows/triggers/staging-deploy
|
||||
git commit -a -m "Deploy v0.0.8" # replace with version number
|
||||
git push
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```zsh
|
||||
echo trigger >> .gitea/workflows/triggers/production-deploy
|
||||
git commit -a -m "Deploy v0.0.8" # replace with version number
|
||||
git push
|
||||
```
|
||||
|
||||
### Deployment status
|
||||
|
||||
Dumb for now
|
||||
|
||||
- [Staging](https://snowballtools-base-api.staging.apps.snowballtools.com/staging/version)
|
||||
- [Production](https://snowballtools-base-api.apps.snowballtools.com/staging/version)
|
||||
|
||||
Update version number manually in [routes/staging.ts](/packages/backend/src/routes/staging.ts)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"debug": "^4.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"express-session": "^1.18.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"graphql": "^16.8.1",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'express-async-errors';
|
||||
import 'reflect-metadata';
|
||||
import debug from 'debug';
|
||||
import fs from 'fs';
|
||||
|
||||
@@ -17,7 +17,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
return context.user;
|
||||
},
|
||||
|
||||
organizations: async (_:any, __: any, context: any) => {
|
||||
organizations: async (_: any, __: any, context: any) => {
|
||||
return service.getOrganizationsByUserId(context.user);
|
||||
},
|
||||
|
||||
@@ -25,8 +25,15 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
return service.getProjectById(projectId);
|
||||
},
|
||||
|
||||
projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => {
|
||||
return service.getProjectsInOrganization(context.user, organizationSlug);
|
||||
projectsInOrganization: async (
|
||||
_: any,
|
||||
{ organizationSlug }: { organizationSlug: string },
|
||||
context: any,
|
||||
) => {
|
||||
return service.getProjectsInOrganization(
|
||||
context.user,
|
||||
organizationSlug,
|
||||
);
|
||||
},
|
||||
|
||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
||||
@@ -35,7 +42,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
environmentVariables: async (
|
||||
_: any,
|
||||
{ projectId }: { projectId: string }
|
||||
{ projectId }: { projectId: string },
|
||||
) => {
|
||||
return service.getEnvironmentVariablesByProjectId(projectId);
|
||||
},
|
||||
@@ -44,7 +51,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
return service.getProjectMembersByProjectId(projectId);
|
||||
},
|
||||
|
||||
searchProjects: async (_: any, { searchText }: { searchText: string }, context: any) => {
|
||||
searchProjects: async (
|
||||
_: any,
|
||||
{ searchText }: { searchText: string },
|
||||
context: any,
|
||||
) => {
|
||||
return service.searchProjects(context.user, searchText);
|
||||
},
|
||||
|
||||
@@ -52,11 +63,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
filter
|
||||
}: { projectId: string; filter?: FindOptionsWhere<Domain> }
|
||||
filter,
|
||||
}: { projectId: string; filter?: FindOptionsWhere<Domain> },
|
||||
) => {
|
||||
return service.getDomainsByProjectId(projectId, filter);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: Return error in GQL response
|
||||
@@ -64,10 +75,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
removeProjectMember: async (
|
||||
_: any,
|
||||
{ projectMemberId }: { projectMemberId: string },
|
||||
context: any
|
||||
context: any,
|
||||
) => {
|
||||
try {
|
||||
return await service.removeProjectMember(context.user, projectMemberId);
|
||||
return await service.removeProjectMember(
|
||||
context.user,
|
||||
projectMemberId,
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
@@ -78,13 +92,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectMemberId,
|
||||
data
|
||||
data,
|
||||
}: {
|
||||
projectMemberId: string;
|
||||
data: {
|
||||
permissions: Permission[];
|
||||
};
|
||||
}
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
return await service.updateProjectMember(projectMemberId, data);
|
||||
@@ -98,14 +112,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
data
|
||||
data,
|
||||
}: {
|
||||
projectId: string;
|
||||
data: {
|
||||
email: string;
|
||||
permissions: Permission[];
|
||||
};
|
||||
}
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
return Boolean(await service.addProjectMember(projectId, data));
|
||||
@@ -119,15 +133,15 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
data
|
||||
data,
|
||||
}: {
|
||||
projectId: string;
|
||||
data: { environments: string[]; key: string; value: string }[];
|
||||
}
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
return Boolean(
|
||||
await service.addEnvironmentVariables(projectId, data)
|
||||
await service.addEnvironmentVariables(projectId, data),
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
@@ -139,16 +153,16 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
environmentVariableId,
|
||||
data
|
||||
data,
|
||||
}: {
|
||||
environmentVariableId: string;
|
||||
data: DeepPartial<EnvironmentVariable>;
|
||||
}
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
return await service.updateEnvironmentVariable(
|
||||
environmentVariableId,
|
||||
data
|
||||
data,
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
@@ -158,7 +172,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
removeEnvironmentVariable: async (
|
||||
_: any,
|
||||
{ environmentVariableId }: { environmentVariableId: string }
|
||||
{ environmentVariableId }: { environmentVariableId: string },
|
||||
) => {
|
||||
try {
|
||||
return await service.removeEnvironmentVariable(environmentVariableId);
|
||||
@@ -171,10 +185,12 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
updateDeploymentToProd: async (
|
||||
_: any,
|
||||
{ deploymentId }: { deploymentId: string },
|
||||
context: any
|
||||
context: any,
|
||||
) => {
|
||||
try {
|
||||
return Boolean(await service.updateDeploymentToProd(context.user, deploymentId));
|
||||
return Boolean(
|
||||
await service.updateDeploymentToProd(context.user, deploymentId),
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
@@ -185,9 +201,9 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
organizationSlug,
|
||||
data
|
||||
data,
|
||||
}: { organizationSlug: string; data: DeepPartial<Project> },
|
||||
context: any
|
||||
context: any,
|
||||
) => {
|
||||
try {
|
||||
return await service.addProject(context.user, organizationSlug, data);
|
||||
@@ -199,7 +215,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
updateProject: async (
|
||||
_: any,
|
||||
{ projectId, data }: { projectId: string; data: DeepPartial<Project> }
|
||||
{ projectId, data }: { projectId: string; data: DeepPartial<Project> },
|
||||
) => {
|
||||
try {
|
||||
return await service.updateProject(projectId, data);
|
||||
@@ -212,10 +228,12 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
redeployToProd: async (
|
||||
_: any,
|
||||
{ deploymentId }: { deploymentId: string },
|
||||
context: any
|
||||
context: any,
|
||||
) => {
|
||||
try {
|
||||
return Boolean(await service.redeployToProd(context.user, deploymentId));
|
||||
return Boolean(
|
||||
await service.redeployToProd(context.user, deploymentId),
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
@@ -244,8 +262,8 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
deploymentId
|
||||
}: { deploymentId: string; projectId: string }
|
||||
deploymentId,
|
||||
}: { deploymentId: string; projectId: string },
|
||||
) => {
|
||||
try {
|
||||
return await service.rollbackDeployment(projectId, deploymentId);
|
||||
@@ -257,9 +275,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
deleteDeployment: async (
|
||||
_: any,
|
||||
{
|
||||
deploymentId
|
||||
}: { deploymentId: string; }
|
||||
{ deploymentId }: { deploymentId: string },
|
||||
) => {
|
||||
try {
|
||||
return await service.deleteDeployment(deploymentId);
|
||||
@@ -271,7 +287,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
addDomain: async (
|
||||
_: any,
|
||||
{ projectId, data }: { projectId: string; data: { name: string } }
|
||||
{ projectId, data }: { projectId: string; data: { name: string } },
|
||||
) => {
|
||||
try {
|
||||
return Boolean(await service.addDomain(projectId, data));
|
||||
@@ -283,7 +299,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
updateDomain: async (
|
||||
_: any,
|
||||
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> }
|
||||
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> },
|
||||
) => {
|
||||
try {
|
||||
return await service.updateDomain(domainId, data);
|
||||
@@ -296,7 +312,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
authenticateGitHub: async (
|
||||
_: any,
|
||||
{ code }: { code: string },
|
||||
context: any
|
||||
context: any,
|
||||
) => {
|
||||
try {
|
||||
return await service.authenticateGitHub(code, context.user);
|
||||
@@ -308,12 +324,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
||||
try {
|
||||
return service.unauthenticateGitHub(context.user, { gitHubToken: null });
|
||||
return service.unauthenticateGitHub(context.user, {
|
||||
gitHubToken: null,
|
||||
});
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,6 +5,19 @@ import { authenticateUser, createUser } from '../turnkey-backend';
|
||||
|
||||
const router = Router();
|
||||
|
||||
//
|
||||
// Access Code
|
||||
//
|
||||
router.post('/accesscode', async (req, res) => {
|
||||
console.log('Access Code', req.body);
|
||||
const { accesscode } = req.body;
|
||||
if (accesscode === '44444') {
|
||||
return res.send({ isValid: true });
|
||||
} else {
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Turnkey
|
||||
//
|
||||
@@ -19,6 +32,7 @@ router.get('/registration/:email', async (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/register', async (req, res) => {
|
||||
console.log('Register', req.body);
|
||||
const { email, challenge, attestation } = req.body;
|
||||
const user = await createUser(req.app.get('service'), {
|
||||
challenge,
|
||||
@@ -31,6 +45,7 @@ router.post('/register', async (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/authenticate', async (req, res) => {
|
||||
console.log('Authenticate', req.body);
|
||||
const { signedWhoamiRequest } = req.body;
|
||||
const user = await authenticateUser(
|
||||
req.app.get('service'),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/version', async (req, res) => {
|
||||
return res.send({ version: '0.0.8' });
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -17,6 +17,7 @@ import { ServerConfig } from './config';
|
||||
import { DEFAULT_GQL_PATH } from './constants';
|
||||
import githubRouter from './routes/github';
|
||||
import authRouter from './routes/auth';
|
||||
import stagingRouter from './routes/staging';
|
||||
import { Service } from './service';
|
||||
|
||||
const log = debug('snowball:server');
|
||||
@@ -109,6 +110,12 @@ export const createAndStartServer = async (
|
||||
app.set('service', service);
|
||||
app.use('/auth', authRouter);
|
||||
app.use('/api/github', githubRouter);
|
||||
app.use('/staging', stagingRouter);
|
||||
|
||||
app.use((err: any, req: any, res: any, next: any) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: err.message });
|
||||
});
|
||||
|
||||
httpServer.listen(port, host, () => {
|
||||
log(`Server is listening on ${host}:${port}${server.graphqlPath}`);
|
||||
|
||||
+291
-235
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,11 @@
|
||||
# deployer
|
||||
|
||||
- Install dependencies
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
```bash
|
||||
brew install jq # if you do not have jq installed already
|
||||
```
|
||||
@@ -12,13 +14,13 @@
|
||||
|
||||
- To deploy frontend app to `dashboard.staging.apps.snowballtools.com`
|
||||
|
||||
```
|
||||
```bash
|
||||
./deploy-frontend.staging.sh
|
||||
```
|
||||
|
||||
- To deploy frontend app to `dashboard.apps.snowballtools.com`
|
||||
|
||||
```
|
||||
```bash
|
||||
./deploy-frontend.sh
|
||||
```
|
||||
|
||||
@@ -27,6 +29,7 @@
|
||||
## Notes
|
||||
|
||||
- Any config env can be updated in [records/application-deployment-request.yml](records/application-deployment-request.yml)
|
||||
|
||||
```yml
|
||||
record:
|
||||
...
|
||||
@@ -35,6 +38,7 @@
|
||||
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
|
||||
...
|
||||
```
|
||||
|
||||
- On changing `LACONIC_HOSTED_CONFIG_app_github_clientid`, the GitHub client ID and secret need to be changed in backend config too
|
||||
|
||||
## Troubleshoot
|
||||
@@ -44,6 +48,7 @@
|
||||
|
||||
- If deployment fails due to low bond balance
|
||||
- Check balances
|
||||
|
||||
```bash
|
||||
# Account balance
|
||||
yarn laconic cns account get
|
||||
@@ -51,7 +56,9 @@
|
||||
# Bond balance
|
||||
yarn laconic cns bond get --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
|
||||
```
|
||||
|
||||
- Command to refill bond
|
||||
|
||||
```bash
|
||||
yarn laconic cns bond refill --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32 --type aphoton --quantity 10000000
|
||||
```
|
||||
|
||||
@@ -3,7 +3,7 @@ services:
|
||||
restEndpoint: 'http://console.laconic.com:1317'
|
||||
gqlEndpoint: 'http://console.laconic.com:9473/api'
|
||||
userKey: 87d00f66a73e2ca428adeb49ba9164d0ad9a87edc60e33d46ad3031b9c5701fe
|
||||
bondId: 098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661
|
||||
bondId: 89c75c7bc5759861d10285aff6f9e7227d6855e446b77ad5d8324822dfec7deb
|
||||
chainId: laconic_9000-1
|
||||
gas: 550000
|
||||
gas: 1200000
|
||||
fees: 200000aphoton
|
||||
|
||||
@@ -26,7 +26,7 @@ if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; t
|
||||
fi
|
||||
|
||||
# Generate application-deployment-request.yml
|
||||
cat > ./records/application-deployment-request.yml <<EOF
|
||||
cat >./records/application-deployment-request.yml <<EOF
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
@@ -37,9 +37,9 @@ record:
|
||||
env:
|
||||
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_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
|
||||
meta:
|
||||
note: Added by Snowball @ $CURRENT_DATE_TIME
|
||||
@@ -48,7 +48,7 @@ record:
|
||||
EOF
|
||||
|
||||
# Generate application-record.yml with incremented version
|
||||
cat > ./records/application-record.yml <<EOF
|
||||
cat >./records/application-record.yml <<EOF
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
@@ -64,7 +64,13 @@ echo "Files generated successfully."
|
||||
RECORD_FILE=records/application-record.yml
|
||||
|
||||
# Publish ApplicationRecord
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
publish_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to publish record"
|
||||
exit $rc
|
||||
fi
|
||||
RECORD_ID=$(echo $publish_response | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
@@ -73,15 +79,36 @@ REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend"
|
||||
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set name: $REGISTRY_APP_CRN@${PACKAGE_VERSION}"
|
||||
exit $rc
|
||||
fi
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set hash"
|
||||
exit $rc
|
||||
fi
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set release"
|
||||
exit $rc
|
||||
fi
|
||||
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]')
|
||||
query_response=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN")
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query name"
|
||||
exit $rc
|
||||
fi
|
||||
APP_RECORD=$(echo $query_response | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
@@ -90,6 +117,11 @@ fi
|
||||
RECORD_FILE=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')
|
||||
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query deployment request"
|
||||
exit $rc
|
||||
fi
|
||||
DEPLOYMENT_REQUEST_ID=$(echo $deployment_response | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
|
||||
@@ -26,7 +26,7 @@ if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; t
|
||||
fi
|
||||
|
||||
# Generate application-deployment-request.yml
|
||||
cat > ./staging-records/application-deployment-request.yml <<EOF
|
||||
cat >./staging-records/application-deployment-request.yml <<EOF
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
@@ -36,16 +36,16 @@ record:
|
||||
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_clientid: Ov23liOaoahRTYd4nSCV
|
||||
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_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: localhost
|
||||
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
|
||||
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
|
||||
@@ -54,7 +54,7 @@ record:
|
||||
EOF
|
||||
|
||||
# Generate application-record.yml with incremented version
|
||||
cat > ./staging-records/application-record.yml <<EOF
|
||||
cat >./staging-records/application-record.yml <<EOF
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
@@ -70,7 +70,13 @@ 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')
|
||||
publish_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to publish record"
|
||||
exit $rc
|
||||
fi
|
||||
RECORD_ID=$(echo $publish_response | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
@@ -79,15 +85,36 @@ REGISTRY_APP_CRN="crn://staging-snowballtools/applications/staging-snowballtools
|
||||
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set name: $REGISTRY_APP_CRN@${PACKAGE_VERSION}"
|
||||
exit $rc
|
||||
fi
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set hash"
|
||||
exit $rc
|
||||
fi
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set release"
|
||||
exit $rc
|
||||
fi
|
||||
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]')
|
||||
query_response=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN")
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query name"
|
||||
exit $rc
|
||||
fi
|
||||
APP_RECORD=$(echo $query_response | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
@@ -96,6 +123,11 @@ 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')
|
||||
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query deployment request"
|
||||
exit $rc
|
||||
fi
|
||||
DEPLOYMENT_REQUEST_ID=$(echo $deployment_response | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
version: "1.0.0"
|
||||
name: snowballtools-base-frontend@0.1.8
|
||||
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.8
|
||||
dns: dashboard
|
||||
@@ -8,9 +8,9 @@ record:
|
||||
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_github_templaterepo: snowball-tools/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||
meta:
|
||||
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
|
||||
|
||||
@@ -7,18 +7,18 @@ record:
|
||||
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_clientid: Ov23liOaoahRTYd4nSCV
|
||||
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_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: localhost
|
||||
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
|
||||
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 @ Tuesday 21 May 2024 06:17:23 AM UTC
|
||||
note: Added by Snowball @ Sat Jun 22 02:49:45 UTC 2024
|
||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: 0.0.2
|
||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||
version: 0.0.1
|
||||
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
|
||||
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
||||
app_type: webapp
|
||||
name: staging-snowballtools-base-frontend
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Check if the live web app deployer is in a working state
|
||||
|
||||
- Web app repo used: https://github.com/snowball-tools-platform/test-progressive-web-app (main branch)
|
||||
- Web app repo used: <https://github.com/snowball-tools/test-progressive-web-app> (main branch)
|
||||
- Config used: [../config.yml](../config.yml)
|
||||
- The script [test-webapp-deployment-undeployment.sh](./test-webapp-deployment-undeployment.sh) performs the following:
|
||||
- Create / update [`ApplicationRecord`](./records/application-record.yml) and [`ApplicationDeploymentRequest`](./records/application-deployment-request.yml) records with latest meta data from the repo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
version: "1.0.0"
|
||||
name: deployment-test-app@0.1.24
|
||||
application: crn://snowballtools/applications/deployment-test-app@0.1.24
|
||||
dns: deployment-ci-test
|
||||
@@ -11,5 +11,5 @@ record:
|
||||
CERC_WEBAPP_DEBUG: 0
|
||||
meta:
|
||||
note: Deployment test @ Thu 11 Apr 2024 07:29:19 AM UTC
|
||||
repository: "https://github.com/snowball-tools-platform/test-progressive-web-app"
|
||||
repository: "https://github.com/snowball-tools/test-progressive-web-app"
|
||||
repository_ref: 05819619487a0d2dbc5453b6d1ccff3044c0dd26
|
||||
|
||||
@@ -2,7 +2,7 @@ record:
|
||||
type: ApplicationRecord
|
||||
version: 0.0.1
|
||||
repository_ref: 05819619487a0d2dbc5453b6d1ccff3044c0dd26
|
||||
repository: ["https://github.com/snowball-tools-platform/test-progressive-web-app"]
|
||||
repository: ["https://github.com/snowball-tools/test-progressive-web-app"]
|
||||
app_type: webapp
|
||||
name: deployment-test-app
|
||||
app_version: 0.1.24
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Repository URL
|
||||
REPO_URL="https://github.com/snowball-tools-platform/test-progressive-web-app"
|
||||
REPO_URL="https://github.com/snowball-tools/test-progressive-web-app"
|
||||
|
||||
# Get the latest commit hash from the repository
|
||||
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
# Generate application-record.yml with incremented version
|
||||
RECORD_FILE=packages/deployer/test/records/application-record.yml
|
||||
|
||||
cat > $RECORD_FILE <<EOF
|
||||
cat >$RECORD_FILE <<EOF
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
@@ -46,7 +46,7 @@ EOF
|
||||
# Generate application-deployment-request.yml
|
||||
REQUEST_RECORD_FILE=packages/deployer/test/records/application-deployment-request.yml
|
||||
|
||||
cat > $REQUEST_RECORD_FILE <<EOF
|
||||
cat >$REQUEST_RECORD_FILE <<EOF
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
@@ -115,7 +115,7 @@ while true; do
|
||||
else
|
||||
echo "ApplicationDeploymentRecord not found, retrying in $RETRY_INTERVAL sec..."
|
||||
sleep $RETRY_INTERVAL
|
||||
retry_count=$((retry_count+1))
|
||||
retry_count=$((retry_count + 1))
|
||||
fi
|
||||
else
|
||||
echo "ApplicationDeploymentRecord found"
|
||||
@@ -154,7 +154,7 @@ while true; do
|
||||
else
|
||||
echo "Deployment URL $fetched_url is not active, received code $url_response, retrying in $retry_interval sec..."
|
||||
sleep $retry_interval
|
||||
retry_count=$((retry_count+1))
|
||||
retry_count=$((retry_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -162,7 +162,7 @@ done
|
||||
# Generate application-deployment-removal-request.yml
|
||||
REMOVAL_REQUEST_RECORD_FILE=packages/deployer/test/records/application-deployment-removal-request.yml
|
||||
|
||||
cat > $REMOVAL_REQUEST_RECORD_FILE <<EOF
|
||||
cat >$REMOVAL_REQUEST_RECORD_FILE <<EOF
|
||||
record:
|
||||
deployment: $DEPLOYMENT_RECORD_ID
|
||||
type: ApplicationDeploymentRemovalRequest
|
||||
@@ -190,7 +190,7 @@ while true; do
|
||||
else
|
||||
echo "ApplicationDeploymentRemovalRecord not found, retrying in $RETRY_INTERVAL sec..."
|
||||
sleep $RETRY_INTERVAL
|
||||
retry_count=$((retry_count+1))
|
||||
retry_count=$((retry_count + 1))
|
||||
fi
|
||||
else
|
||||
echo "ApplicationDeploymentRemovalRecord found"
|
||||
@@ -217,7 +217,7 @@ while true; do
|
||||
else
|
||||
echo "Deployment URL $fetched_url is still active, received code $url_response, retrying in $retry_interval sec..."
|
||||
sleep $retry_interval
|
||||
retry_count=$((retry_count+1))
|
||||
retry_count=$((retry_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -14,4 +14,4 @@ VITE_BUGSNAG_API_KEY=
|
||||
|
||||
VITE_PASSKEY_WALLET_RPID=
|
||||
VITE_TURNKEY_API_BASE_URL=
|
||||
VITE_TURNKEY_ORGANIZATION_ID=
|
||||
VITE_TURNKEY_ORGANIZATION_ID=
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist/
|
||||
@@ -9,6 +9,7 @@ import { join, dirname } from 'path';
|
||||
function getAbsolutePath(value: string): any {
|
||||
return dirname(require.resolve(join(value, 'package.json')));
|
||||
}
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
@@ -26,6 +27,7 @@ const config: StorybookConfig = {
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
staticDirs: ['../public'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
+56
-23
@@ -1,30 +1,63 @@
|
||||
# React + TypeScript + Vite
|
||||
# frontend
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
This is a [vite](https://vitejs.dev/) [react](https://reactjs.org/) [nextjs](https://nextjs.org/) project in a [yarn workspace](https://yarnpkg.com/features/workspaces).
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## Getting Started
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
### Install dependencies
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
In the root of the project, run:
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default {
|
||||
// other rules...
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
};
|
||||
```zsh
|
||||
yarn
|
||||
```
|
||||
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
||||
### Build backend
|
||||
|
||||
```zsh
|
||||
yarn build --ignore-frontend
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
#### Local
|
||||
|
||||
Copy the `.env.example` file to `.env`:
|
||||
|
||||
```zsh
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
#### Staging environment variables
|
||||
|
||||
Change in [deployer/deploy-frontend.staging.sh](/packages/deployer/deploy-frontend.staging.sh)
|
||||
|
||||
#### Production environment variables
|
||||
|
||||
Change in [deployer/deploy-frontend.sh](/packages/deployer/deploy-frontend.sh)
|
||||
|
||||
### Run development server
|
||||
|
||||
```zsh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
From the root of the project, run:
|
||||
|
||||
### Staging
|
||||
|
||||
```zsh
|
||||
cd packages/deployer && ./deploy-frontend.staging.sh
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```zsh
|
||||
cd packages/deployer && ./deploy-frontend.sh
|
||||
```
|
||||
|
||||
### Deployment status
|
||||
|
||||
Check the status of the deployment [here](https://webapp-deployer.apps.snowballtools.com)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
|
||||
import Projects from './pages/org-slug';
|
||||
@@ -10,8 +11,8 @@ import ProjectSearchLayout from './layouts/ProjectSearch';
|
||||
import Index from './pages';
|
||||
import AuthPage from './pages/AuthPage';
|
||||
import { DashboardLayout } from './pages/org-slug/layout';
|
||||
import { useEffect } from 'react';
|
||||
import Web3Provider from 'context/Web3Provider';
|
||||
import { baseUrl } from 'utils/constants';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -59,7 +60,7 @@ function App() {
|
||||
// Hacky way of checking session
|
||||
// TODO: Handle redirect backs
|
||||
useEffect(() => {
|
||||
fetch(`${import.meta.env.VITE_SERVER_URL}/auth/session`, {
|
||||
fetch(`${baseUrl}/auth/session`, {
|
||||
credentials: 'include',
|
||||
}).then((res) => {
|
||||
if (res.status !== 200) {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import {
|
||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO,
|
||||
VITE_GITHUB_PWA_TEMPLATE_REPO,
|
||||
} from 'utils/constants';
|
||||
|
||||
export default [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Progressive Web App (PWA)',
|
||||
icon: 'pwa',
|
||||
repoFullName: `${import.meta.env.VITE_GITHUB_PWA_TEMPLATE_REPO}`,
|
||||
repoFullName: `${VITE_GITHUB_PWA_TEMPLATE_REPO}`,
|
||||
isComingSoon: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Image Upload PWA',
|
||||
icon: 'pwa',
|
||||
repoFullName: `${import.meta.env.VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
|
||||
repoFullName: `${VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
|
||||
isComingSoon: false,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Input, InputProps } from './shared/Input';
|
||||
const SearchBar: React.ForwardRefRenderFunction<
|
||||
HTMLInputElement,
|
||||
InputProps & RefAttributes<HTMLInputElement>
|
||||
> = ({ value, onChange, placeholder = 'Search', ...props }) => {
|
||||
> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => {
|
||||
return (
|
||||
<div className="relative flex w-full">
|
||||
<Input
|
||||
@@ -18,6 +18,7 @@ const SearchBar: React.ForwardRefRenderFunction<
|
||||
appearance="borderless"
|
||||
className="w-full lg:w-[459px]"
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+1
-1
@@ -96,9 +96,9 @@ export const ProjectSearchBarDialog = ({
|
||||
</p>
|
||||
</div>
|
||||
<ProjectSearchBarItem
|
||||
{...getItemProps({ item, index })}
|
||||
key={item.id}
|
||||
item={item}
|
||||
{...getItemProps({ item, index })}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
|
||||
@@ -14,11 +14,10 @@ import { useToast } from '../../shared/Toast';
|
||||
import { IconWithFrame } from '../../shared/IconWithFrame';
|
||||
import { Heading } from '../../shared/Heading';
|
||||
import { MockConnectGitCard } from './MockConnectGitCard';
|
||||
import { VITE_GITHUB_CLIENT_ID } from 'utils/constants';
|
||||
|
||||
const SCOPES = 'repo user';
|
||||
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
|
||||
import.meta.env.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 {
|
||||
onAuth: (token: string) => void;
|
||||
|
||||
+27
-37
@@ -1,13 +1,8 @@
|
||||
import { CopyBlock, atomOneLight } from 'react-code-blocks';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
import { Modal } from 'components/shared/Modal';
|
||||
import { Button } from 'components/shared/Button';
|
||||
|
||||
interface AssignDomainProps {
|
||||
open: boolean;
|
||||
@@ -16,36 +11,31 @@ interface AssignDomainProps {
|
||||
|
||||
const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
|
||||
return (
|
||||
<Dialog open={open} handler={handleOpen}>
|
||||
<DialogHeader>Assign Domain</DialogHeader>
|
||||
<DialogBody>
|
||||
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}
|
||||
/>
|
||||
</DialogBody>
|
||||
<DialogFooter className="flex justify-start">
|
||||
<Button
|
||||
className="rounded-3xl"
|
||||
variant="gradient"
|
||||
color="blue"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<span>Okay</span>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useState, ComponentPropsWithRef } from 'react';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuHandler,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
import { Deployment, Domain, Environment, Project } from 'gql-client';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
@@ -11,17 +18,11 @@ import {
|
||||
UndoIcon,
|
||||
CrossCircleIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import {
|
||||
Menu,
|
||||
MenuHandler,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
import { ComponentPropsWithRef } from 'react';
|
||||
import AssignDomainDialog from './AssignDomainDialog';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { cn } from 'utils/classnames';
|
||||
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
|
||||
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
|
||||
deployment: Deployment;
|
||||
@@ -41,6 +42,7 @@ export const DeploymentMenu = ({
|
||||
...props
|
||||
}: DeploymentMenuProps) => {
|
||||
const client = useGQLClient();
|
||||
const { toast, dismiss } = useToast();
|
||||
|
||||
const [changeToProduction, setChangeToProduction] = useState(false);
|
||||
const [redeployToProduction, setRedeployToProduction] = useState(false);
|
||||
@@ -51,9 +53,19 @@ export const DeploymentMenu = ({
|
||||
const isUpdated = await client.updateDeploymentToProd(deployment.id);
|
||||
if (isUpdated) {
|
||||
await onUpdate();
|
||||
toast.success('Deployment changed to production');
|
||||
toast({
|
||||
id: 'deployment_changed_to_production',
|
||||
title: 'Deployment changed to production',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} else {
|
||||
toast.error('Unable to change deployment to production');
|
||||
toast({
|
||||
id: 'deployment_not_changed_to_production',
|
||||
title: 'Error changing deployment to production',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,9 +73,19 @@ export const DeploymentMenu = ({
|
||||
const isRedeployed = await client.redeployToProd(deployment.id);
|
||||
if (isRedeployed) {
|
||||
await onUpdate();
|
||||
toast.success('Redeployed to production');
|
||||
toast({
|
||||
id: 'redeployed_to_production',
|
||||
title: 'Redeployed to production',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} else {
|
||||
toast.error('Unable to redeploy to production');
|
||||
toast({
|
||||
id: 'redeployed_to_production_failed',
|
||||
title: 'Error redeploying to production',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,9 +96,19 @@ export const DeploymentMenu = ({
|
||||
);
|
||||
if (isRollbacked) {
|
||||
await onUpdate();
|
||||
toast.success('Deployment rolled back');
|
||||
toast({
|
||||
id: 'deployment_rolled_back',
|
||||
title: 'Deployment rolled back',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} else {
|
||||
toast.error('Unable to rollback deployment');
|
||||
toast({
|
||||
id: 'deployment_rollback_failed',
|
||||
title: 'Error rolling back deployment',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,9 +116,19 @@ export const DeploymentMenu = ({
|
||||
const isDeleted = await client.deleteDeployment(deployment.id);
|
||||
if (isDeleted) {
|
||||
await onUpdate();
|
||||
toast.success('Deleted deployment');
|
||||
toast({
|
||||
id: 'deployment_deleted',
|
||||
title: 'Deployment deleted',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} else {
|
||||
toast.error('Unable to delete deployment');
|
||||
toast({
|
||||
id: 'deployment_not_deleted',
|
||||
title: 'Error deleting deployment',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
useMemo,
|
||||
ComponentPropsWithoutRef,
|
||||
} from 'react';
|
||||
import { FieldValues, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { WarningIcon } from 'components/shared/CustomIcon';
|
||||
import { cloneIcon } from 'utils/cloneIcon';
|
||||
@@ -12,7 +11,7 @@ import { cn } from 'utils/classnames';
|
||||
|
||||
import { InputTheme, inputTheme } from './Input.theme';
|
||||
|
||||
export interface InputProps<T extends FieldValues = FieldValues>
|
||||
export interface InputProps
|
||||
extends InputTheme,
|
||||
Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
|
||||
label?: string;
|
||||
@@ -20,9 +19,6 @@ export interface InputProps<T extends FieldValues = FieldValues>
|
||||
leftIcon?: ReactNode;
|
||||
rightIcon?: ReactNode;
|
||||
helperText?: string;
|
||||
|
||||
// react-hook-form optional register
|
||||
register?: ReturnType<UseFormRegister<T>>;
|
||||
}
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
@@ -34,7 +30,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
helperText,
|
||||
register,
|
||||
size,
|
||||
state,
|
||||
appearance,
|
||||
@@ -107,12 +102,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
<div className={containerCls({ class: className })}>
|
||||
{leftIcon && renderLeftIcon}
|
||||
<input
|
||||
{...(register ? register : {})}
|
||||
className={cn(inputCls(), {
|
||||
'pl-10': leftIcon,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
{rightIcon && renderRightIcon}
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { cn } from 'utils/classnames';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
import { SIDEBAR_MENU } from './constants';
|
||||
import { UserSelect } from 'components/shared/UserSelect';
|
||||
import { baseUrl } from 'utils/constants';
|
||||
|
||||
interface SidebarProps {
|
||||
mobileOpen?: boolean;
|
||||
@@ -83,7 +84,7 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
||||
}, [orgSlug]);
|
||||
|
||||
const handleLogOut = useCallback(async () => {
|
||||
await fetch(`${import.meta.env.VITE_SERVER_URL}/auth/logout`, {
|
||||
await fetch(`${baseUrl}/auth/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
@@ -103,10 +103,12 @@ export const UserSelect = ({ options, value }: UserSelectProps) => {
|
||||
<div className={theme.container()}>
|
||||
{/* Input */}
|
||||
<div
|
||||
{...getToggleButtonProps({
|
||||
ref: inputWrapperRef,
|
||||
suppressRefError: true,
|
||||
})}
|
||||
{...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"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Input } from '../Input';
|
||||
|
||||
export interface VerifyCodeInputProps {
|
||||
code: string;
|
||||
setCode: (code: string) => void;
|
||||
submitCode: () => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const VerifyCodeInput = ({
|
||||
code,
|
||||
setCode,
|
||||
submitCode,
|
||||
loading,
|
||||
}: VerifyCodeInputProps) => {
|
||||
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
|
||||
|
||||
const handlePaste = (
|
||||
e: React.ClipboardEvent<HTMLInputElement>,
|
||||
i: number,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
const pasteData = e.clipboardData.getData('text').replace(/\D/g, ''); // Only digits
|
||||
if (pasteData.length > 0) {
|
||||
let newCodeArray = code.split('');
|
||||
for (let j = 0; j < pasteData.length && i + j < 6; j++) {
|
||||
newCodeArray[i + j] = pasteData[j];
|
||||
}
|
||||
const newCode = newCodeArray.join('');
|
||||
setCode(newCode);
|
||||
const nextIndex = Math.min(i + pasteData.length, 5);
|
||||
const nextInput = inputRefs.current[nextIndex];
|
||||
if (nextInput) nextInput.focus();
|
||||
if (!newCode.includes(' ')) {
|
||||
submitCode();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (
|
||||
e: React.KeyboardEvent<HTMLInputElement>,
|
||||
i: number,
|
||||
) => {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
||||
|
||||
if (e.key === 'Backspace') {
|
||||
e.preventDefault();
|
||||
const isEmpty = code[i] === ' ';
|
||||
const newCode = !isEmpty
|
||||
? code.slice(0, i) + ' ' + code.slice(i + 1, 6)
|
||||
: code.slice(0, i - 1) + ' ' + code.slice(i, 6);
|
||||
|
||||
setCode(newCode.slice(0, 6));
|
||||
|
||||
if (i === 0 || !isEmpty) return;
|
||||
const prev = inputRefs.current[i - 1];
|
||||
if (prev) prev.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.key.match(/[0-9]/)) return;
|
||||
|
||||
e.preventDefault(); // Prevent the default event to avoid duplicate input
|
||||
|
||||
const newCode = code.slice(0, i) + e.key + code.slice(i + 1, 6);
|
||||
setCode(newCode);
|
||||
|
||||
if (i === 5) {
|
||||
submitCode();
|
||||
return;
|
||||
}
|
||||
|
||||
const next = inputRefs.current[i + 1];
|
||||
if (next) next.focus();
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, i: number) => {
|
||||
const value = e.target.value.slice(-1);
|
||||
if (!value.match(/[0-9]/)) return;
|
||||
|
||||
const newCode = code.slice(0, i) + value + code.slice(i + 1, 6);
|
||||
setCode(newCode);
|
||||
|
||||
if (i < 5) {
|
||||
const next = inputRefs.current[i + 1];
|
||||
if (next) next.focus();
|
||||
}
|
||||
|
||||
if (!newCode.includes(' ')) {
|
||||
submitCode();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRefs.current[0]) {
|
||||
inputRefs.current[0].focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{code.split('').map((char, i) => (
|
||||
<Input
|
||||
key={i}
|
||||
value={char === ' ' ? '' : char}
|
||||
ref={(el) => (inputRefs.current[i] = el)}
|
||||
onChange={(e) => handleChange(e, i)}
|
||||
onPaste={(e) => handlePaste(e, i)}
|
||||
onKeyDown={(e) => handleKeyDown(e, i)}
|
||||
disabled={!!loading}
|
||||
style={{ textAlign: 'center' }} // Add this line to center text
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { VerifyCodeInput } from './VerifyCodeInput';
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { VITE_WALLET_CONNECT_ID } from 'utils/constants';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
if (!import.meta.env.VITE_WALLET_CONNECT_ID) {
|
||||
if (!VITE_WALLET_CONNECT_ID) {
|
||||
throw new Error('Error: REACT_APP_WALLET_CONNECT_ID env config is not set');
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,17 @@ import { GQLClientProvider } from './context/GQLClientContext';
|
||||
import { SERVER_GQL_PATH } from './constants';
|
||||
import { Toaster } from 'components/shared/Toast';
|
||||
import { LogErrorBoundary } from 'utils/log-error';
|
||||
import { baseUrl } from 'utils/constants';
|
||||
|
||||
// @ts-ignore
|
||||
console.log(`v-${__VERSION__}`);
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement,
|
||||
);
|
||||
|
||||
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}`;
|
||||
assert(baseUrl, 'VITE_SERVER_URL is not set in env');
|
||||
const gqlEndpoint = `${baseUrl}/${SERVER_GQL_PATH}`;
|
||||
|
||||
const gqlClient = new GQLClient({ gqlEndpoint });
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
ArrowRightCircleFilledIcon,
|
||||
LoaderIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { WavyBorder } from 'components/shared/WavyBorder';
|
||||
import { VerifyCodeInput } from 'components/shared/VerifyCodeInput';
|
||||
import { verifyAccessCode } from 'utils/accessCode';
|
||||
|
||||
type AccessMethod = 'accesscode' | 'passkey';
|
||||
|
||||
type Err = { type: AccessMethod; message: string };
|
||||
|
||||
type AccessCodeProps = {
|
||||
onCorrectAccessCode: () => void;
|
||||
};
|
||||
|
||||
export const AccessCode: React.FC<AccessCodeProps> = ({
|
||||
onCorrectAccessCode,
|
||||
}) => {
|
||||
const [accessCode, setAccessCode] = useState(' ');
|
||||
const [error, setError] = useState<Err | null>();
|
||||
const [accessMethod, setAccessMethod] = useState<AccessMethod | false>(false);
|
||||
|
||||
async function validateAccessCode() {
|
||||
setAccessMethod('accesscode');
|
||||
try {
|
||||
const isValidAccessCode = await verifyAccessCode(accessCode);
|
||||
|
||||
// add a pause for ux
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
if (isValidAccessCode) {
|
||||
localStorage.setItem('accessCode', accessCode);
|
||||
onCorrectAccessCode();
|
||||
} else {
|
||||
setError({
|
||||
type: 'accesscode',
|
||||
message: 'Invalid access code',
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError({ type: 'accesscode', message: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
const loading = accessMethod;
|
||||
const isValidAccessCodeLength = accessCode.trim().length === 5;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="self-stretch p-3 xs:p-6 flex-col justify-center items-center gap-5 flex">
|
||||
<div className="self-stretch text-center text-sky-950 text-2xl font-medium font-display leading-tight">
|
||||
Access Code
|
||||
</div>
|
||||
</div>
|
||||
<WavyBorder className="self-stretch" variant="stroke" />
|
||||
<div className="self-stretch p-4 xs:p-6 flex-col justify-center items-center gap-8 flex">
|
||||
<div className="self-stretch flex-col gap-8 flex">
|
||||
<div className="flex-col justify-start items-start gap-2 inline-flex">
|
||||
<VerifyCodeInput
|
||||
loading={!!loading}
|
||||
code={accessCode}
|
||||
setCode={setAccessCode}
|
||||
submitCode={validateAccessCode}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
rightIcon={
|
||||
loading && loading === 'accesscode' ? (
|
||||
<LoaderIcon className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircleFilledIcon height="16" />
|
||||
)
|
||||
}
|
||||
onClick={validateAccessCode}
|
||||
variant={'secondary'}
|
||||
disabled={!accessCode || !isValidAccessCodeLength || !!loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
{error && error.type === 'accesscode' && (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="justify-center items-center gap-2 inline-flex">
|
||||
<div className="text-red-500 text-sm">
|
||||
Error: {error.message}.{' '}
|
||||
<a href="/signup" className="underline">
|
||||
Try again?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { AccessCode } from './AccessCode';
|
||||
import { SignUp } from './SignUp';
|
||||
|
||||
type AccessSignUpProps = {
|
||||
onDone: () => void;
|
||||
};
|
||||
|
||||
export const AccessSignUp: React.FC<AccessSignUpProps> = ({ onDone }) => {
|
||||
const [isValidAccessCode, setIsValidAccessCode] = useState<boolean>(
|
||||
!!localStorage.getItem('accessCode'),
|
||||
);
|
||||
|
||||
return isValidAccessCode ? (
|
||||
<SignUp onDone={onDone} />
|
||||
) : (
|
||||
<AccessCode onCorrectAccessCode={() => setIsValidAccessCode(true)} />
|
||||
);
|
||||
};
|
||||
@@ -47,7 +47,9 @@ export const Login = ({ onDone }: Props) => {
|
||||
console.log('Handling google redirect back');
|
||||
try {
|
||||
await google.handleOAuthRedirectBack();
|
||||
// @ts-ignore
|
||||
wallet = await google.getEthersWallet();
|
||||
// @ts-ignore
|
||||
const result = await signInWithEthereum(1, 'login', wallet);
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
turnkeySignin,
|
||||
turnkeySignup,
|
||||
} from 'utils/turnkey-frontend';
|
||||
import { verifyAccessCode } from 'utils/accessCode';
|
||||
|
||||
type Provider = 'google' | 'github' | 'apple' | 'email';
|
||||
|
||||
@@ -45,7 +46,9 @@ export const SignUp = ({ onDone }: Props) => {
|
||||
setProvider('google');
|
||||
try {
|
||||
await google.handleOAuthRedirectBack();
|
||||
// @ts-ignore
|
||||
wallet = await google.getEthersWallet();
|
||||
// @ts-ignore
|
||||
const result = await signInWithEthereum(1, 'signup', wallet);
|
||||
if (result.error) {
|
||||
setError({ type: 'provider', message: result.error });
|
||||
@@ -109,6 +112,29 @@ export const SignUp = ({ onDone }: Props) => {
|
||||
const loading = provider;
|
||||
const emailValid = /.@./.test(email);
|
||||
|
||||
useEffect(() => {
|
||||
const validateAccessCode = async () => {
|
||||
const accessCode = localStorage.getItem('accessCode');
|
||||
if (!accessCode) {
|
||||
redirectToSignup();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyAccessCode(accessCode);
|
||||
} catch (err: any) {
|
||||
redirectToSignup();
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToSignup = () => {
|
||||
localStorage.removeItem('accessCode');
|
||||
window.location.href = '/signup';
|
||||
};
|
||||
|
||||
validateAccessCode();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="self-stretch p-3 xs:p-6 flex-col justify-center items-center gap-5 flex">
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { snowball } from 'utils/use-snowball';
|
||||
import { Login } from './Login';
|
||||
import { SignUp } from './SignUp';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Done } from './Done';
|
||||
import { AccessSignUp } from './AccessSignUp';
|
||||
|
||||
type Screen = 'login' | 'signup' | 'success';
|
||||
|
||||
const DASHBOARD_URL = '/';
|
||||
|
||||
export const SnowballAuth = () => {
|
||||
export const SnowballAuth: React.FC = () => {
|
||||
const path = window.location.pathname;
|
||||
const [screen, setScreen] = useState<Screen>(
|
||||
path === '/login' ? 'login' : 'signup',
|
||||
@@ -30,7 +31,7 @@ export const SnowballAuth = () => {
|
||||
|
||||
if (screen === 'signup') {
|
||||
return (
|
||||
<SignUp
|
||||
<AccessSignUp
|
||||
onDone={() => {
|
||||
setScreen('success');
|
||||
}}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,9 +6,13 @@ const meta: Meta<typeof ChevronDownSmallIcon> = {
|
||||
title: 'Icons/ChevronDownSmallIcon',
|
||||
component: ChevronDownSmallIcon,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
size: 'string | number' as unknown as any,
|
||||
name: 'string',
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,13 @@ const meta: Meta<typeof ChevronRight> = {
|
||||
title: 'Icons/ChevronRight',
|
||||
component: ChevronRight,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
size: 'string | number' as unknown as any,
|
||||
name: 'string',
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,13 @@ const meta: Meta<typeof ChevronUpDown> = {
|
||||
title: 'Icons/ChevronUpDown',
|
||||
component: ChevronUpDown,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
size: 'string | number' as unknown as any,
|
||||
name: 'string',
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,16 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ChevronUpSmallIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
const meta: Meta<typeof ChevronUpSmallIcon> = {
|
||||
title: 'Components/ChevronUpSmallIcon',
|
||||
title: 'Icons/ChevronUpSmallIcon',
|
||||
component: ChevronUpSmallIcon,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
size: 'string | number' as unknown as any,
|
||||
name: 'string',
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
import { CollaboratorsIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
const meta: Meta<typeof CollaboratorsIcon> = {
|
||||
title: 'Components/CollaboratorsIcon',
|
||||
title: 'Icons/CollaboratorsIcon',
|
||||
component: CollaboratorsIcon,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { baseUrl } from './constants';
|
||||
|
||||
export async function verifyAccessCode(
|
||||
accesscode: string,
|
||||
): Promise<boolean | null> {
|
||||
const res = await fetch(`${baseUrl}/auth/accesscode`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
accesscode,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
// If API returns a non-empty 200, this is a valid access code.
|
||||
if (res.status == 200) {
|
||||
const isValid = (await res.json()).isValid as boolean;
|
||||
console.log('isValid', isValid);
|
||||
return isValid;
|
||||
} else if (res.status === 204) {
|
||||
return null;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected response from access code endpoint: ${res.status}: ${await res.text()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export const baseUrl = import.meta.env.VITE_SERVER_URL;
|
||||
export const PASSKEY_WALLET_RPID = import.meta.env.VITE_PASSKEY_WALLET_RPID!;
|
||||
export const TURNKEY_BASE_URL = import.meta.env.VITE_TURNKEY_API_BASE_URL!;
|
||||
export const VITE_GITHUB_PWA_TEMPLATE_REPO = import.meta.env
|
||||
.VITE_GITHUB_PWA_TEMPLATE_REPO;
|
||||
export const VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = import.meta.env
|
||||
.VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO;
|
||||
export const VITE_GITHUB_CLIENT_ID = import.meta.env.VITE_GITHUB_CLIENT_ID;
|
||||
export const VITE_WALLET_CONNECT_ID = import.meta.env.VITE_WALLET_CONNECT_ID;
|
||||
export const VITE_BUGSNAG_API_KEY = import.meta.env.VITE_BUGSNAG_API_KEY;
|
||||
export const VITE_LIT_RELAY_API_KEY = import.meta.env.VITE_LIT_RELAY_API_KEY;
|
||||
@@ -1,21 +1,21 @@
|
||||
import React from 'react';
|
||||
import Bugsnag from '@bugsnag/js';
|
||||
import BugsnagPluginReact from '@bugsnag/plugin-react';
|
||||
import BugsnagPerformance from '@bugsnag/browser-performance';
|
||||
import React from 'react';
|
||||
|
||||
const bugsnagApiKey = import.meta.env.VITE_BUGSNAG_API_KEY;
|
||||
import { VITE_BUGSNAG_API_KEY } from './constants';
|
||||
|
||||
if (bugsnagApiKey) {
|
||||
if (VITE_BUGSNAG_API_KEY) {
|
||||
Bugsnag.start({
|
||||
apiKey: bugsnagApiKey,
|
||||
apiKey: VITE_BUGSNAG_API_KEY,
|
||||
plugins: [new BugsnagPluginReact()],
|
||||
});
|
||||
BugsnagPerformance.start({ apiKey: bugsnagApiKey });
|
||||
BugsnagPerformance.start({ apiKey: VITE_BUGSNAG_API_KEY });
|
||||
}
|
||||
|
||||
export const errorLoggingEnabled = !!bugsnagApiKey;
|
||||
export const errorLoggingEnabled = !!VITE_BUGSNAG_API_KEY;
|
||||
|
||||
export const LogErrorBoundary = bugsnagApiKey
|
||||
export const LogErrorBoundary = VITE_BUGSNAG_API_KEY
|
||||
? Bugsnag.getPlugin('react')!.createErrorBoundary(React)
|
||||
: ({ children }: any) => children;
|
||||
|
||||
@@ -28,7 +28,7 @@ export function logError(error: Error) {
|
||||
}
|
||||
console.error(...errors);
|
||||
|
||||
if (import.meta.env.VITE_BUGSNAG_API_KEY) {
|
||||
if (VITE_BUGSNAG_API_KEY) {
|
||||
Bugsnag.notify(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { SiweMessage } from 'siwe';
|
||||
import { PKPEthersWallet } from '@lit-protocol/pkp-ethers';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { baseUrl } from './constants';
|
||||
|
||||
const domain = window.location.host;
|
||||
const origin = window.location.origin;
|
||||
|
||||
@@ -17,7 +19,7 @@ export async function signInWithEthereum(
|
||||
);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
const res = await fetch(`${import.meta.env.VITE_SERVER_URL}/auth/validate`, {
|
||||
const res = await fetch(`${baseUrl}/auth/validate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { TurnkeyClient, getWebAuthnAttestation } from '@turnkey/http';
|
||||
import { WebauthnStamper } from '@turnkey/webauthn-stamper';
|
||||
|
||||
const baseUrl = import.meta.env.VITE_SERVER_URL;
|
||||
|
||||
const PASSKEY_WALLET_RPID = import.meta.env.VITE_PASSKEY_WALLET_RPID!;
|
||||
const TURNKEY_BASE_URL = import.meta.env.VITE_TURNKEY_API_BASE_URL!;
|
||||
import { baseUrl, PASSKEY_WALLET_RPID, TURNKEY_BASE_URL } from './constants';
|
||||
|
||||
// All algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
|
||||
// We only support ES256, which is listed here
|
||||
|
||||
@@ -5,16 +5,17 @@ import {
|
||||
LitGoogleAuth,
|
||||
LitPasskeyAuth,
|
||||
} from '@snowballtools/auth-lit';
|
||||
import { VITE_LIT_RELAY_API_KEY } from './constants';
|
||||
|
||||
export const snowball = Snowball.withAuth({
|
||||
google: LitGoogleAuth.configure({
|
||||
litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
|
||||
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
|
||||
}),
|
||||
// apple: LitAppleAuth.configure({
|
||||
// litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
|
||||
// litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
|
||||
// }),
|
||||
passkey: LitPasskeyAuth.configure({
|
||||
litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
|
||||
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
|
||||
}),
|
||||
}).create({
|
||||
initialChain: SnowballChain.sepolia,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { defineConfig, PluginOption } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -16,5 +18,8 @@ export default defineConfig({
|
||||
},
|
||||
define: {
|
||||
'process.env': {},
|
||||
__VERSION__: JSON.stringify(
|
||||
(await promisify(exec)('git rev-parse --short HEAD')).stdout.trim(),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-1
@@ -20,7 +20,10 @@ var __async = (__this, __arguments, generator) => {
|
||||
};
|
||||
|
||||
// src/client.ts
|
||||
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
||||
import {
|
||||
ApolloClient,
|
||||
InMemoryCache
|
||||
} from "@apollo/client";
|
||||
|
||||
// src/queries.ts
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,8 +1,13 @@
|
||||
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||
import {
|
||||
ApolloClient,
|
||||
DefaultOptions,
|
||||
InMemoryCache,
|
||||
NormalizedCacheObject,
|
||||
} from "@apollo/client";
|
||||
|
||||
import * as queries from './queries';
|
||||
import * as types from './types';
|
||||
import * as mutations from './mutations';
|
||||
import * as queries from "./queries";
|
||||
import * as types from "./types";
|
||||
import * as mutations from "./mutations";
|
||||
|
||||
export interface GraphQLConfig {
|
||||
gqlEndpoint: string;
|
||||
@@ -11,320 +16,372 @@ export interface GraphQLConfig {
|
||||
// TODO: check options
|
||||
const defaultOptions: DefaultOptions = {
|
||||
watchQuery: {
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'ignore'
|
||||
fetchPolicy: "no-cache",
|
||||
errorPolicy: "ignore",
|
||||
},
|
||||
query: {
|
||||
fetchPolicy: 'no-cache',
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
fetchPolicy: "no-cache",
|
||||
errorPolicy: "all",
|
||||
},
|
||||
};
|
||||
|
||||
export class GQLClient {
|
||||
private client: ApolloClient<NormalizedCacheObject>;
|
||||
|
||||
constructor (config: GraphQLConfig) {
|
||||
constructor(config: GraphQLConfig) {
|
||||
this.client = new ApolloClient({
|
||||
uri: config.gqlEndpoint,
|
||||
cache: new InMemoryCache(),
|
||||
defaultOptions,
|
||||
credentials: 'include'
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
async getUser () : Promise<types.GetUserResponse> {
|
||||
async getUser(): Promise<types.GetUserResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getUser
|
||||
query: queries.getUser,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getProject (projectId: string) : Promise<types.GetProjectResponse> {
|
||||
async getProject(projectId: string): Promise<types.GetProjectResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getProject,
|
||||
variables: {
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getProjectsInOrganization (organizationSlug: string) : Promise<types.GetProjectsInOrganizationResponse> {
|
||||
async getProjectsInOrganization(
|
||||
organizationSlug: string
|
||||
): Promise<types.GetProjectsInOrganizationResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getProjectsInOrganization,
|
||||
variables: {
|
||||
organizationSlug
|
||||
}
|
||||
organizationSlug,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getOrganizations () : Promise<types.GetOrganizationsResponse> {
|
||||
async getOrganizations(): Promise<types.GetOrganizationsResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getOrganizations
|
||||
query: queries.getOrganizations,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getDeployments (projectId: string) : Promise<types.GetDeploymentsResponse> {
|
||||
async getDeployments(
|
||||
projectId: string
|
||||
): Promise<types.GetDeploymentsResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getDeployments,
|
||||
variables: {
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getEnvironmentVariables (projectId: string) : Promise<types.GetEnvironmentVariablesResponse> {
|
||||
async getEnvironmentVariables(
|
||||
projectId: string
|
||||
): Promise<types.GetEnvironmentVariablesResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getEnvironmentVariables,
|
||||
variables: {
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getProjectMembers (projectId: string) : Promise<types.GetProjectMembersResponse> {
|
||||
async getProjectMembers(
|
||||
projectId: string
|
||||
): Promise<types.GetProjectMembersResponse> {
|
||||
const result = await this.client.query({
|
||||
query: queries.getProjectMembers,
|
||||
variables: {
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async addProjectMember (projectId: string, data: types.AddProjectMemberInput) : Promise<types.AddProjectMemberResponse> {
|
||||
async addProjectMember(
|
||||
projectId: string,
|
||||
data: types.AddProjectMemberInput
|
||||
): Promise<types.AddProjectMemberResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.addProjectMember,
|
||||
variables: {
|
||||
projectId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async updateProjectMember (projectMemberId: string, data: types.UpdateProjectMemberInput): Promise<types.UpdateProjectMemberResponse> {
|
||||
async updateProjectMember(
|
||||
projectMemberId: string,
|
||||
data: types.UpdateProjectMemberInput
|
||||
): Promise<types.UpdateProjectMemberResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.updateProjectMember,
|
||||
variables: {
|
||||
projectMemberId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async removeProjectMember (projectMemberId: string): Promise<types.RemoveProjectMemberResponse> {
|
||||
async removeProjectMember(
|
||||
projectMemberId: string
|
||||
): Promise<types.RemoveProjectMemberResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.removeProjectMember,
|
||||
variables: {
|
||||
projectMemberId
|
||||
}
|
||||
projectMemberId,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async searchProjects (searchText: string) : Promise<types.SearchProjectsResponse> {
|
||||
async searchProjects(
|
||||
searchText: string
|
||||
): Promise<types.SearchProjectsResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.searchProjects,
|
||||
variables: {
|
||||
searchText
|
||||
}
|
||||
searchText,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async addEnvironmentVariables (projectId: string, data: types.AddEnvironmentVariableInput[]): Promise<types.AddEnvironmentVariablesResponse> {
|
||||
async addEnvironmentVariables(
|
||||
projectId: string,
|
||||
data: types.AddEnvironmentVariableInput[]
|
||||
): Promise<types.AddEnvironmentVariablesResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.addEnvironmentVariables,
|
||||
variables: {
|
||||
projectId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async updateEnvironmentVariable (environmentVariableId: string, data: types.UpdateEnvironmentVariableInput): Promise<types.UpdateEnvironmentVariableResponse> {
|
||||
async updateEnvironmentVariable(
|
||||
environmentVariableId: string,
|
||||
data: types.UpdateEnvironmentVariableInput
|
||||
): Promise<types.UpdateEnvironmentVariableResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.updateEnvironmentVariable,
|
||||
variables: {
|
||||
environmentVariableId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async removeEnvironmentVariable (environmentVariableId: string): Promise<types.RemoveEnvironmentVariableResponse> {
|
||||
async removeEnvironmentVariable(
|
||||
environmentVariableId: string
|
||||
): Promise<types.RemoveEnvironmentVariableResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.removeEnvironmentVariable,
|
||||
variables: {
|
||||
environmentVariableId
|
||||
}
|
||||
environmentVariableId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async updateDeploymentToProd (deploymentId: string): Promise<types.UpdateDeploymentToProdResponse> {
|
||||
async updateDeploymentToProd(
|
||||
deploymentId: string
|
||||
): Promise<types.UpdateDeploymentToProdResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.updateDeploymentToProd,
|
||||
variables: {
|
||||
deploymentId
|
||||
}
|
||||
deploymentId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async addProject (organizationSlug: string, data: types.AddProjectInput): Promise<types.AddProjectResponse> {
|
||||
async addProject(
|
||||
organizationSlug: string,
|
||||
data: types.AddProjectInput
|
||||
): Promise<types.AddProjectResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.addProject,
|
||||
variables: {
|
||||
organizationSlug,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async updateProject (projectId: string, data: types.UpdateProjectInput): Promise<types.UpdateProjectResponse> {
|
||||
async updateProject(
|
||||
projectId: string,
|
||||
data: types.UpdateProjectInput
|
||||
): Promise<types.UpdateProjectResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.updateProjectMutation,
|
||||
variables: {
|
||||
projectId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async updateDomain (domainId: string, data: types.UpdateDomainInput): Promise<types.UpdateDomainResponse> {
|
||||
async updateDomain(
|
||||
domainId: string,
|
||||
data: types.UpdateDomainInput
|
||||
): Promise<types.UpdateDomainResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.updateDomainMutation,
|
||||
variables: {
|
||||
domainId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async redeployToProd (deploymentId: string): Promise<types.RedeployToProdResponse> {
|
||||
async redeployToProd(
|
||||
deploymentId: string
|
||||
): Promise<types.RedeployToProdResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.redeployToProd,
|
||||
variables: {
|
||||
deploymentId
|
||||
}
|
||||
deploymentId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteProject (projectId: string): Promise<types.DeleteProjectResponse> {
|
||||
async deleteProject(projectId: string): Promise<types.DeleteProjectResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.deleteProject,
|
||||
variables: {
|
||||
projectId
|
||||
}
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteDomain (domainId: string): Promise<types.DeleteDomainResponse> {
|
||||
async deleteDomain(domainId: string): Promise<types.DeleteDomainResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.deleteDomain,
|
||||
variables: {
|
||||
domainId
|
||||
}
|
||||
domainId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async rollbackDeployment (projectId: string, deploymentId: string): Promise<types.RollbackDeploymentResponse> {
|
||||
async rollbackDeployment(
|
||||
projectId: string,
|
||||
deploymentId: string
|
||||
): Promise<types.RollbackDeploymentResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.rollbackDeployment,
|
||||
variables: {
|
||||
projectId,
|
||||
deploymentId
|
||||
}
|
||||
deploymentId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteDeployment (deploymentId: string): Promise<types.DeleteDeploymentResponse> {
|
||||
async deleteDeployment(
|
||||
deploymentId: string
|
||||
): Promise<types.DeleteDeploymentResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.deleteDeployment,
|
||||
variables: {
|
||||
deploymentId
|
||||
}
|
||||
deploymentId,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async addDomain (projectId: string, data: types.AddDomainInput): Promise<types.AddDomainResponse> {
|
||||
async addDomain(
|
||||
projectId: string,
|
||||
data: types.AddDomainInput
|
||||
): Promise<types.AddDomainResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.addDomain,
|
||||
variables: {
|
||||
projectId,
|
||||
data
|
||||
}
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async getDomains (projectId: string, filter?: types.FilterDomainInput): Promise<types.GetDomainsResponse> {
|
||||
async getDomains(
|
||||
projectId: string,
|
||||
filter?: types.FilterDomainInput
|
||||
): Promise<types.GetDomainsResponse> {
|
||||
const { data } = await this.client.query({
|
||||
query: queries.getDomains,
|
||||
variables: {
|
||||
projectId,
|
||||
filter
|
||||
}
|
||||
filter,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async authenticateGitHub (code: string): Promise<types.AuthenticateGitHubResponse> {
|
||||
async authenticateGitHub(
|
||||
code: string
|
||||
): Promise<types.AuthenticateGitHubResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.authenticateGitHub,
|
||||
variables: {
|
||||
code
|
||||
}
|
||||
code,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async unauthenticateGithub (): Promise<types.UnauthenticateGitHubResponse> {
|
||||
async unauthenticateGithub(): Promise<types.UnauthenticateGitHubResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.unauthenticateGitHub
|
||||
mutation: mutations.unauthenticateGitHub,
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -10575,6 +10575,11 @@ exponential-backoff@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
|
||||
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
|
||||
|
||||
express-async-errors@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/express-async-errors/-/express-async-errors-3.1.1.tgz#6053236d61d21ddef4892d6bd1d736889fc9da41"
|
||||
integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==
|
||||
|
||||
express-session@^1.18.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.18.0.tgz#a6ae39d9091f2efba5f20fc5c65a3ce7c9ce16a3"
|
||||
|
||||
Reference in New Issue
Block a user