Compare commits

..
Author SHA1 Message Date
dboreham 175af0f9e0 Deploy frontend app directly from github repo 2024-07-05 07:01:26 -06:00
70 changed files with 853 additions and 1582 deletions
+1 -4
View File
@@ -6,7 +6,4 @@ yarn-error.log
packages/backend/environments/local.toml
packages/backend/dev/
packages/frontend/dist/
# ignore all .DS_Store files
**/.DS_Store
packages/frontend/dist/
+210 -13
View File
@@ -1,23 +1,220 @@
# snowballtools-base
This is a [yarn workspace](https://yarnpkg.com/features/workspaces) monorepo for the dashboard.
## Setup
## Getting Started
- Clone the `snowballtools-base` repo
### Install dependencies
```bash
git clone git@github.com:snowball-tools/snowballtools-base.git
cd snowballtools-base
```
In the root of the project, run:
- Install dependencies in root
```zsh
yarn
```
```bash
yarn
```
### Build backend
- Build packages
```zsh
yarn build --ignore frontend
```
```bash
yarn build --ignore frontend
```
### Environment variables, running the development server, and deployment
Let us assume the following domains for backend and frontend
- Backend server: `api.snowballtools.com`
- Frontend app: `dashboard.snowballtools.com`
Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and [frontend](packages/frontend/README.md) packages.
- 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
```
+1 -76
View File
@@ -1,76 +1 @@
# 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)
# Backend for Snowball Tools
-21
View File
@@ -6,7 +6,6 @@ import { Permission } from './entity/ProjectMember';
import { Domain } from './entity/Domain';
import { Project } from './entity/Project';
import { EnvironmentVariable } from './entity/EnvironmentVariable';
import { AddProjectFromTemplateInput } from './types';
const log = debug('snowball:resolver');
@@ -198,26 +197,6 @@ export const createResolvers = async (service: Service): Promise<any> => {
}
},
addProjectFromTemplate: async (
_: any,
{
organizationSlug,
data,
}: { organizationSlug: string; data: AddProjectFromTemplateInput },
context: any,
) => {
try {
return await service.addProjectFromTemplate(
context.user,
organizationSlug,
data,
);
} catch (err) {
log(err);
throw err;
}
},
addProject: async (
_: any,
{
-13
View File
@@ -5,19 +5,6 @@ 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
//
+1 -1
View File
@@ -3,7 +3,7 @@ import { Router } from 'express';
const router = Router();
router.get('/version', async (req, res) => {
return res.send({ version: '0.0.8' });
return res.send({ version: '0.0.7' });
});
export default router;
-12
View File
@@ -130,14 +130,6 @@ input AddEnvironmentVariableInput {
value: String!
}
input AddProjectFromTemplateInput {
templateOwner: String!
templateRepo: String!
owner: String!
name: String!
isPrivate: Boolean!
}
input AddProjectInput {
name: String!
repository: String!
@@ -212,10 +204,6 @@ type Mutation {
): Boolean!
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean!
addProjectFromTemplate(
organizationSlug: String!
data: AddProjectFromTemplateInput
): Project!
addProject(organizationSlug: String!, data: AddProjectInput): Project!
updateProject(projectId: String!, data: UpdateProjectInput): Boolean!
redeployToProd(deploymentId: String!): Boolean!
+1 -51
View File
@@ -16,7 +16,6 @@ import { User } from './entity/User';
import { Registry } from './registry';
import { GitHubConfig, RegistryConfig } from './config';
import {
AddProjectFromTemplateInput,
AppDeploymentRecord,
AppDeploymentRemovalRecord,
GitPushEventPayload,
@@ -644,53 +643,6 @@ export class Service {
return newDeployment;
}
async addProjectFromTemplate(
user: User,
organizationSlug: string,
data: AddProjectFromTemplateInput,
): Promise<Project | undefined> {
try {
const octokit = await this.getOctokit(user.id);
const gitRepo = await octokit?.rest.repos.createUsingTemplate({
template_owner: data.templateOwner,
template_repo: data.templateRepo,
owner: data.owner,
name: data.name,
include_all_branches: false,
private: data.isPrivate,
});
if (!gitRepo) {
throw new Error('Failed to create repository from template');
}
const createdTemplateRepo = await octokit.rest.repos.get({
owner: data.owner,
repo: data.name,
});
const prodBranch = createdTemplateRepo.data.default_branch ?? 'main';
const project = await this.addProject(user, organizationSlug, {
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
prodBranch,
repository: gitRepo.data.full_name,
// TODO: Set selected template
template: 'webapp',
});
if (!project || !project.id) {
throw new Error('Failed to create project from template');
}
return project;
} catch (error) {
console.error('Error creating project from template:', error);
throw error;
}
}
async addProject(
user: User,
organizationSlug: string,
@@ -720,7 +672,7 @@ export class Service {
});
// Create deployment with prod branch and latest commit
const deployment = await this.createDeployment(user.id, octokit, {
await this.createDeployment(user.id, octokit, {
project,
branch: project.prodBranch,
environment: Environment.Production,
@@ -731,8 +683,6 @@ export class Service {
await this.createRepoHook(octokit, project);
console.log('projectid is', project.id);
return project;
}
+1 -9
View File
@@ -41,7 +41,7 @@ export interface AppDeploymentRecordAttributes {
export interface AppDeploymentRemovalRecordAttributes {
deployment: string;
request: string;
type: 'ApplicationDeploymentRemovalRecord';
type: "ApplicationDeploymentRemovalRecord";
version: string;
}
@@ -61,11 +61,3 @@ export interface AppDeploymentRecord extends RegistryRecord {
export interface AppDeploymentRemovalRecord extends RegistryRecord {
attributes: AppDeploymentRemovalRecordAttributes;
}
export interface AddProjectFromTemplateInput {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
}
+2 -9
View File
@@ -1,11 +1,9 @@
# deployer
- Install dependencies
```bash
yarn
```
```bash
brew install jq # if you do not have jq installed already
```
@@ -14,13 +12,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
```
@@ -29,7 +27,6 @@
## Notes
- Any config env can be updated in [records/application-deployment-request.yml](records/application-deployment-request.yml)
```yml
record:
...
@@ -38,7 +35,6 @@
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
@@ -48,7 +44,6 @@
- If deployment fails due to low bond balance
- Check balances
```bash
# Account balance
yarn laconic cns account get
@@ -56,9 +51,7 @@
# 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
```
+6 -6
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
@@ -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/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_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
@@ -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
+6 -6
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
@@ -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,10 +36,10 @@ record:
config:
env:
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liOaoahRTYd4nSCV
LACONIC_HOSTED_CONFIG_github_clientid: 905c09553f527d2cdff5
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_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
@@ -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
@@ -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,11 +8,11 @@ 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/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_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository: "https://github.com/snowball-tools/snowballtools-base"
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
@@ -2,7 +2,7 @@ record:
type: ApplicationRecord
version: 0.0.2
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
repository: ["https://github.com/snowball-tools/snowballtools-base"]
app_type: webapp
name: snowballtools-base-frontend
app_version: 0.1.8
@@ -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: Ov23liOaoahRTYd4nSCV
LACONIC_HOSTED_CONFIG_github_clientid: 905c09553f527d2cdff5
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: dashboard.staging.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: localhost
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
meta:
note: Added by Snowball @ Sat Jun 22 02:49:45 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
note: Added by Snowball @ Tuesday 21 May 2024 06:17:23 AM UTC
repository: "https://github.com/snowball-tools/snowballtools-base"
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
@@ -1,8 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.1
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
version: 0.0.2
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
repository: ["https://github.com/snowball-tools/snowballtools-base"]
app_type: webapp
name: staging-snowballtools-base-frontend
app_version: 0.0.0
+1 -1
View File
@@ -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/test-progressive-web-app> (main branch)
- Web app repo used: https://github.com/snowball-tools-platform/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/test-progressive-web-app"
repository: "https://github.com/snowball-tools-platform/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/test-progressive-web-app"]
repository: ["https://github.com/snowball-tools-platform/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/test-progressive-web-app"
REPO_URL="https://github.com/snowball-tools-platform/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
+1 -1
View File
@@ -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=
-2
View File
@@ -9,7 +9,6 @@ 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: [
@@ -27,7 +26,6 @@ const config: StorybookConfig = {
docs: {
autodocs: 'tag',
},
staticDirs: ['../public'],
};
export default config;
+23 -56
View File
@@ -1,63 +1,30 @@
# frontend
# React + TypeScript + Vite
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).
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
## Getting Started
Currently, two official plugins are available:
### Install dependencies
- [@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
In the root of the project, run:
## Expanding the ESLint configuration
```zsh
yarn
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,
},
};
```
### 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)
- 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
+2 -3
View File
@@ -1,4 +1,3 @@
import { useEffect } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Projects from './pages/org-slug';
@@ -11,8 +10,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([
{
@@ -60,7 +59,7 @@ function App() {
// Hacky way of checking session
// TODO: Handle redirect backs
useEffect(() => {
fetch(`${baseUrl}/auth/session`, {
fetch(`${import.meta.env.VITE_SERVER_URL}/auth/session`, {
credentials: 'include',
}).then((res) => {
if (res.status !== 200) {
+2 -7
View File
@@ -1,21 +1,16 @@
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: `${VITE_GITHUB_PWA_TEMPLATE_REPO}`,
repoFullName: `${import.meta.env.VITE_GITHUB_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
id: '2',
name: 'Image Upload PWA',
icon: 'pwa',
repoFullName: `${VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
repoFullName: `${import.meta.env.VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useCombobox } from 'downshift';
import { Project } from 'gql-client';
import { useDebounceValue } from 'usehooks-ts';
import { useDebounce } from 'usehooks-ts';
import SearchBar from 'components/SearchBar';
import { useGQLClient } from 'context/GQLClientContext';
@@ -42,7 +42,7 @@ export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => {
},
});
const [debouncedInputValue, _] = useDebounceValue<string>(inputValue, 300);
const debouncedInputValue = useDebounce<string>(inputValue, 300);
const fetchProjects = useCallback(
async (inputValue: string) => {
@@ -62,7 +62,7 @@ export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => {
<div className="relative w-full lg:w-fit">
<SearchBar {...getInputProps()} />
<div
{...getMenuProps({}, { suppressRefError: true })}
{...getMenuProps()}
className={cn(
'flex flex-col shadow-dropdown rounded-xl bg-surface-card absolute w-[459px] max-h-52 overflow-y-auto px-2 py-2 gap-1 z-50',
{ hidden: !inputValue || !isOpen },
@@ -5,7 +5,7 @@ import { CrossIcon, SearchIcon } from 'components/shared/CustomIcon';
import { Input } from 'components/shared/Input';
import { useGQLClient } from 'context/GQLClientContext';
import { Project } from 'gql-client';
import { useDebounceValue } from 'usehooks-ts';
import { useDebounce } from 'usehooks-ts';
import { ProjectSearchBarItem } from './ProjectSearchBarItem';
import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty';
import { useNavigate } from 'react-router-dom';
@@ -27,30 +27,25 @@ export const ProjectSearchBarDialog = ({
const client = useGQLClient();
const navigate = useNavigate();
const {
getInputProps,
getItemProps,
getMenuProps,
inputValue,
setInputValue,
} = useCombobox({
items,
itemToString(item) {
return item ? item.name : '';
},
selectedItem,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
if (newSelectedItem) {
setSelectedItem(newSelectedItem);
onClickItem?.(newSelectedItem);
navigate(
`/${newSelectedItem.organization.slug}/projects/${newSelectedItem.id}`,
);
}
},
});
const { getInputProps, getItemProps, inputValue, setInputValue } =
useCombobox({
items,
itemToString(item) {
return item ? item.name : '';
},
selectedItem,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
if (newSelectedItem) {
setSelectedItem(newSelectedItem);
onClickItem?.(newSelectedItem);
navigate(
`/${newSelectedItem.organization.slug}/projects/${newSelectedItem.id}`,
);
}
},
});
const [debouncedInputValue, _] = useDebounceValue<string>(inputValue, 300);
const debouncedInputValue = useDebounce<string>(inputValue, 300);
const fetchProjects = useCallback(
async (inputValue: string) => {
@@ -80,7 +75,7 @@ export const ProjectSearchBarDialog = ({
<div className="h-full flex flex-col fixed top-0 inset-0">
<div className="py-2.5 px-4 flex items-center justify-between border-b border-border-separator/[0.06]">
<Input
{...getInputProps({}, { suppressRefError: true })}
{...getInputProps()}
leftIcon={<SearchIcon />}
placeholder="Search"
appearance="borderless"
@@ -91,33 +86,23 @@ export const ProjectSearchBarDialog = ({
</Button>
</div>
{/* Content */}
<div
className="flex flex-col gap-1 px-2 py-2"
{...getMenuProps(
{},
{
suppressRefError: true,
},
)}
>
{items.length > 0 ? (
<>
<div className="px-2 py-2">
<p className="text-elements-mid-em text-xs font-medium">
Suggestions
</p>
</div>
{items.map((item, index) => (
<ProjectSearchBarItem
key={item.id}
item={item}
{...getItemProps({ item, index })}
/>
))}
</>
) : (
inputValue && <ProjectSearchBarEmpty />
)}
<div className="flex flex-col gap-1 px-2 py-2">
{items.length > 0
? items.map((item, index) => (
<>
<div className="px-2 py-2">
<p className="text-elements-mid-em text-xs font-medium">
Suggestions
</p>
</div>
<ProjectSearchBarItem
key={item.id}
item={item}
{...getItemProps({ item, index })}
/>
</>
))
: inputValue && <ProjectSearchBarEmpty />}
</div>
</div>
</Dialog.Content>
@@ -14,10 +14,11 @@ 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=${VITE_GITHUB_CLIENT_ID}&scope=${encodeURIComponent(SCOPES)}`;
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
import.meta.env.VITE_GITHUB_CLIENT_ID
}&scope=${encodeURIComponent(SCOPES)}`;
interface ConnectAccountInterface {
onAuth: (token: string) => void;
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { Domain, DomainStatus, Project } from 'gql-client';
import {
Chip,
Typography,
Menu,
MenuHandler,
@@ -14,15 +15,6 @@ import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
import { useToast } from 'components/shared/Toast';
import { Tag } from 'components/shared/Tag';
import {
CheckIcon,
CrossIcon,
GearIcon,
LoadingIcon,
} from 'components/shared/CustomIcon';
import { Heading } from 'components/shared/Heading';
import { Button } from 'components/shared/Button';
enum RefreshStatus {
IDLE,
@@ -87,29 +79,22 @@ const DomainCard = ({
<>
<div className="flex justify-between py-3">
<div className="flex justify-start gap-1">
<Heading as="h6" className="flex-col">
{domain.name}{' '}
<Tag
type={
domain.status === DomainStatus.Live ? 'positive' : 'negative'
}
leftIcon={
domain.status === DomainStatus.Live ? (
<CheckIcon />
) : (
<CrossIcon />
)
}
>
{domain.status}
</Tag>
</Heading>
<Typography variant="h6">
<i>^</i> {domain.name}
</Typography>
<Chip
className="w-fit capitalize"
value={domain.status}
color={domain.status === DomainStatus.Live ? 'green' : 'orange'}
variant="ghost"
icon={<i>^</i>}
/>
</div>
<div className="flex justify-start gap-1">
<i
id="refresh"
className="cursor-pointer"
className="cursor-pointer w-8 h-8"
onClick={() => {
SetRefreshStatus(RefreshStatus.CHECKING);
setTimeout(() => {
@@ -117,17 +102,11 @@ const DomainCard = ({
}, CHECK_FAIL_TIMEOUT);
}}
>
{refreshStatus === RefreshStatus.CHECKING ? (
<LoadingIcon className="animate-spin" />
) : (
'L'
)}
{refreshStatus === RefreshStatus.CHECKING ? 'L' : 'R'}
</i>
<Menu placement="bottom-end">
<MenuHandler>
<Button iconOnly>
<GearIcon />
</Button>
<button className="border-2 rounded-full w-8 h-8">...</button>
</MenuHandler>
<MenuList>
<MenuItem
@@ -164,13 +143,13 @@ const DomainCard = ({
{domain.status === DomainStatus.Pending && (
<Card className="bg-slate-100 p-4 text-sm">
{refreshStatus === RefreshStatus.IDLE ? (
<Heading>
<Typography variant="small">
^ Add these records to your domain and refresh to check
</Heading>
</Typography>
) : refreshStatus === RefreshStatus.CHECKING ? (
<Heading className="text-blue-500">
<Typography variant="small" className="text-blue-500">
^ Checking records for {domain.name}
</Heading>
</Typography>
) : (
<div className="flex gap-2 text-red-500 mb-2">
<div className="grow">
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo } from 'react';
import { Controller, useForm, SubmitHandler } from 'react-hook-form';
import toast from 'react-hot-toast';
import { Domain } from 'gql-client';
import {
@@ -8,11 +9,10 @@ import {
Option,
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from 'context/GQLClientContext';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { Modal } from 'components/shared/Modal';
import { Button } from 'components/shared/Button';
import { Input } from 'components/shared/Input';
import { useToast } from 'components/shared/Toast';
const DEFAULT_REDIRECT_OPTIONS = ['none'];
@@ -40,7 +40,6 @@ const EditDomainDialog = ({
onUpdate,
}: EditDomainDialogProp) => {
const client = useGQLClient();
const { toast, dismiss } = useToast();
const getRedirectUrl = (domain: Domain) => {
const redirectDomain = domain.redirectTo;
@@ -100,20 +99,10 @@ const EditDomainDialog = ({
if (updateDomain) {
await onUpdate();
toast({
id: 'domain_id_updated',
title: `Domain ${domain.name} has been updated`,
variant: 'success',
onDismiss: dismiss,
});
toast.success(`Domain ${domain.name} has been updated`);
} else {
reset();
toast({
id: 'domain_id_error_update',
title: `Error updating domain ${domain.name}`,
variant: 'error',
onDismiss: dismiss,
});
toast.error(`Error updating domain ${domain.name}`);
}
handleOpen();
@@ -3,8 +3,6 @@ import { useState } from 'react';
import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog';
import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast';
import { Input } from 'components/shared/Input';
import { CopyIcon, TrashIcon } from 'components/shared/CustomIcon';
interface WebhookCardProps {
webhookUrl: string;
@@ -16,12 +14,11 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
return (
<div className="flex justify-between w-full mb-3 gap-3">
<Input value={webhookUrl} disabled />
<div className="flex justify-between w-full mb-3">
{webhookUrl}
<div className="flex gap-3">
<Button
iconOnly
size="md"
size="sm"
onClick={() => {
navigator.clipboard.writeText(webhookUrl);
toast({
@@ -32,17 +29,16 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
});
}}
>
<CopyIcon />
Copy
</Button>
<Button
iconOnly
size="md"
size="sm"
variant="danger"
onClick={() => {
setDeleteDialogOpen(true);
}}
>
<TrashIcon />
X
</Button>
</div>
<DeleteWebhookDialog
@@ -20,7 +20,6 @@ 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;
@@ -84,7 +83,7 @@ export const Sidebar = ({ mobileOpen }: SidebarProps) => {
}, [orgSlug]);
const handleLogOut = useCallback(async () => {
await fetch(`${baseUrl}/auth/logout`, {
await fetch(`${import.meta.env.VITE_SERVER_URL}/auth/logout`, {
method: 'POST',
credentials: 'include',
});
@@ -1,117 +0,0 @@
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>
);
};
@@ -1 +0,0 @@
export { VerifyCodeInput } from './VerifyCodeInput';
@@ -1,11 +1,9 @@
import { ReactNode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { VITE_WALLET_CONNECT_ID } from 'utils/constants';
const queryClient = new QueryClient();
if (!VITE_WALLET_CONNECT_ID) {
if (!import.meta.env.VITE_WALLET_CONNECT_ID) {
throw new Error('Error: REACT_APP_WALLET_CONNECT_ID env config is not set');
}
+2 -3
View File
@@ -14,7 +14,6 @@ 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__}`);
@@ -23,8 +22,8 @@ const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
assert(baseUrl, 'VITE_SERVER_URL is not set in env');
const gqlEndpoint = `${baseUrl}/${SERVER_GQL_PATH}`;
assert(import.meta.env.VITE_SERVER_URL, 'VITE_SERVER_URL is not set in env');
const gqlEndpoint = `${import.meta.env.VITE_SERVER_URL}/${SERVER_GQL_PATH}`;
const gqlClient = new GQLClient({ gqlEndpoint });
@@ -32,7 +32,7 @@ const ProjectSearch = () => {
return (
<section className="h-full flex flex-col">
{/* Header */}
<div className="sticky hidden lg:block top-0 border-b bg-base-bg border-border-separator/[0.06] hover:z-30">
<div className="sticky hidden lg:block top-0 border-b bg-base-bg border-border-separator/[0.06] z-30">
<div className="flex pr-6 pl-2 py-2 items-center">
<div className="flex-1">
<ProjectSearchBar
@@ -1,99 +0,0 @@
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>
);
};
@@ -1,20 +0,0 @@
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)} />
);
};
@@ -21,7 +21,6 @@ import {
turnkeySignin,
turnkeySignup,
} from 'utils/turnkey-frontend';
import { verifyAccessCode } from 'utils/accessCode';
type Provider = 'google' | 'github' | 'apple' | 'email';
@@ -112,29 +111,6 @@ 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,15 +1,14 @@
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: React.FC = () => {
export const SnowballAuth = () => {
const path = window.location.pathname;
const [screen, setScreen] = useState<Screen>(
path === '/login' ? 'login' : 'signup',
@@ -31,7 +30,7 @@ export const SnowballAuth: React.FC = () => {
if (screen === 'signup') {
return (
<AccessSignUp
<SignUp
onDone={() => {
setScreen('success');
}}
@@ -1,19 +1,5 @@
import { Heading } from 'components/shared/Heading';
const Settings = () => {
return (
<section className="px-4 md:px-6 py-6 flex flex-col gap-6">
{/* Header */}
<div className="flex items-center">
<div className="grow">
<div className="flex gap-4 items-center">
<Heading as="h2" className="text-[24px]">
Settings
</Heading>
</div>
</div>
</div>
</section>
);
return <div className="p-5">Settings page</div>;
};
export default Settings;
@@ -11,128 +11,98 @@ import {
import { Heading } from 'components/shared/Heading';
import logoAnimation from 'components/../../public/lottie/logo.json';
import { useGQLClient } from 'context/GQLClientContext';
import { Project } from 'gql-client';
import { useEffect, useState } from 'react';
const Id = () => {
const { id, orgSlug } = useParams();
const client = useGQLClient();
const [project, setProject] = useState<Project | null>(null);
const handleSetupDomain = async () => {
if (id) {
// console.log('id', id);
// console.log('getting project for id', id);
const project = await client.getProject(id);
// console.log('project found:', project);
if (project && project.project) {
// console.log('project:', project.project);
setProject(project.project);
}
} else {
window.location.href = '/';
}
const handleSetupDomain = () => {
//TODO: Implement this
};
useEffect(() => {
handleSetupDomain();
});
return (
<>
{project ? (
<div className="flex flex-col gap-8 lg:gap-11 max-w-[522px] mx-auto py-6 lg:py-12">
{/* Icon */}
<div className="flex justify-center">
<Lottie animationData={logoAnimation} loop={false} size={40} />
</div>
<div className="flex flex-col gap-8 lg:gap-11 max-w-[522px] mx-auto py-6 lg:py-12">
{/* Icon */}
<div className="flex justify-center">
<Lottie animationData={logoAnimation} loop={false} size={40} />
</div>
{/* Heading */}
<div className="flex flex-col items-center gap-1.5">
<Heading as="h3" className="font-medium text-xl">
Project deployed successfully.
</Heading>
<p className="flex flex-col items-center lg:flex-row font-sans gap-0.5 lg:gap-2 text-sm text-elements-high-em">
Your project has been deployed at{' '}
<Button
className="no-underline text-elements-link"
// TODO: use dynamic value
href={project ? `https://${project.subDomain}` : ''}
as="a"
variant="link-emphasized"
external
leftIcon={<LinkChainIcon />}
>
{project.subDomain}
{/* Heading */}
<div className="flex flex-col items-center gap-1.5">
<Heading as="h3" className="font-medium text-xl">
Project deployed successfully.
</Heading>
<p className="flex flex-col items-center lg:flex-row font-sans gap-0.5 lg:gap-2 text-sm text-elements-high-em">
Your project has been deployed at{' '}
<Button
className="no-underline text-elements-link"
// TODO: use dynamic value
href="https://www.iglootools.snowballtools.xyz"
as="a"
variant="link-emphasized"
external
leftIcon={<LinkChainIcon />}
>
{/* // TODO: use dynamic value */}
www.iglootools.snowballtools.xyz
</Button>
</p>
</div>
{/* Card */}
<div className="bg-base-bg-alternate rounded-xl shadow-inset w-full px-1 py-1">
{/* Trigger question */}
<div className="flex gap-2 justify-center items-center py-3">
<div className="h-5 w-5">
<QuestionMarkRoundFilledIcon size={18} />
</div>
<Heading as="h5" className="font-sans font-medium text-sm">
{`Wondering what's next?`}
</Heading>
</div>
{/* CTA card */}
<div className="bg-surface-card rounded-xl shadow-card-sm px-4 py-4">
<div className="flex gap-2">
<Badge variant="secondary">1</Badge>
<div className="space-y-3">
<div className="flex flex-col gap-1">
<Heading as="h6" className="text-sm font-sans">
Add a custom domain
</Heading>
<p className="text-xs text-elements-low-em font-sans">
Make it easy for your visitors to remember your URL with a
custom domain.
</p>
</div>
<Button onClick={handleSetupDomain} variant="tertiary" size="sm">
Setup domain
</Button>
</p>
</div>
{/* Card */}
<div className="bg-base-bg-alternate rounded-xl shadow-inset w-full px-1 py-1">
{/* Trigger question */}
<div className="flex gap-2 justify-center items-center py-3">
<div className="h-5 w-5">
<QuestionMarkRoundFilledIcon size={18} />
</div>
<Heading as="h5" className="font-sans font-medium text-sm">
{`Wondering what's next?`}
</Heading>
</div>
{/* CTA card */}
<div className="bg-surface-card rounded-xl shadow-card-sm px-4 py-4">
<div className="flex gap-2">
<Badge variant="secondary">1</Badge>
<div className="space-y-3">
<div className="flex flex-col gap-1">
<Heading as="h6" className="text-sm font-sans">
Add a custom domain
</Heading>
<p className="text-xs text-elements-low-em font-sans">
Make it easy for your visitors to remember your URL with a
custom domain.
</p>
</div>
<Button
onClick={handleSetupDomain}
variant="tertiary"
size="sm"
>
Setup domain
</Button>
</div>
</div>
</div>
</div>
{/* CTA Buttons */}
<div className="flex flex-col lg:flex-row justify-center gap-3">
<div className="w-full lg:w-fit">
<Link to="/">
<Button
leftIcon={<ArrowLeftCircleFilledIcon />}
fullWidth
variant="tertiary"
>
Back to projects
</Button>
</Link>
</div>
<div className="w-full lg:w-fit">
<Link to={`/${orgSlug}/projects/${id}`}>
<Button fullWidth variant="primary">
View project
</Button>
</Link>
</div>
</div>
</div>
) : (
<></>
)}
</>
</div>
{/* CTA Buttons */}
<div className="flex flex-col lg:flex-row justify-center gap-3">
<div className="w-full lg:w-fit">
<Link to="/">
<Button
leftIcon={<ArrowLeftCircleFilledIcon />}
fullWidth
variant="tertiary"
>
Back to projects
</Button>
</Link>
</div>
<div className="w-full lg:w-fit">
<Link to={`/${orgSlug}/projects/${id}`}>
<Button fullWidth variant="primary">
View project
</Button>
</Link>
</div>
</div>
</div>
);
};
@@ -11,10 +11,7 @@ import { Template } from '../../../../../types/types';
import { Heading } from 'components/shared/Heading';
import { Input } from 'components/shared/Input';
import { Select, SelectOption } from 'components/shared/Select';
import {
ArrowRightCircleFilledIcon,
LoadingIcon,
} from 'components/shared/CustomIcon';
import { ArrowRightCircleFilledIcon } from 'components/shared/CustomIcon';
import { Checkbox } from 'components/shared/Checkbox';
import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast';
@@ -53,22 +50,36 @@ const CreateRepo = () => {
assert(template.repoFullName, 'Template URL not provided');
const [owner, repo] = template.repoFullName.split('/');
setIsLoading(true);
// TODO: Handle this functionality in backend
const gitRepo = await octokit?.rest.repos.createUsingTemplate({
template_owner: owner,
template_repo: repo,
owner: data.account,
name: data.repoName,
include_all_branches: false,
private: data.isPrivate,
});
const { addProjectFromTemplate } = await client.addProjectFromTemplate(
orgSlug!,
{
templateOwner: owner,
templateRepo: repo,
owner: data.account,
name: data.repoName,
isPrivate: false,
},
);
if (!gitRepo) {
return;
}
navigate(
`deploy?projectId=${addProjectFromTemplate.id}&templateId=${template.id}`,
);
// Refetch to always get correct default branch
const templateRepo = await octokit.rest.repos.get({
owner: template.repoFullName.split('/')[0],
repo: template.repoFullName.split('/')[1],
});
const prodBranch = templateRepo.data.default_branch ?? 'main';
const { addProject } = await client.addProject(orgSlug!, {
name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`,
prodBranch,
repository: gitRepo.data.full_name,
// TODO: Set selected template
template: 'webapp',
});
navigate(`deploy?projectId=${addProject.id}&templateId=${template.id}`);
} catch (err) {
setIsLoading(false);
@@ -195,13 +206,7 @@ const CreateRepo = () => {
{...buttonSize}
type="submit"
disabled={!Boolean(template.repoFullName) || isLoading}
rightIcon={
isLoading ? (
<LoadingIcon className="animate-spin" />
) : (
<ArrowRightCircleFilledIcon />
)
}
rightIcon={<ArrowRightCircleFilledIcon />}
>
Deploy
</Button>
@@ -131,12 +131,9 @@ const OverviewTabPanel = () => {
<Heading className="text-lg leading-6 font-medium truncate">
{project.name}
</Heading>
<a
href={`https://${project.subDomain}`}
className="text-sm text-elements-low-em tracking-tight truncate"
>
<p className="text-sm text-elements-low-em tracking-tight truncate">
{project.subDomain}
</a>
</p>
</div>
</div>
<OverviewInfo label="Domain" icon={<GlobeIcon />}>
@@ -6,13 +6,9 @@ const meta: Meta<typeof ChevronDownSmallIcon> = {
title: 'Icons/ChevronDownSmallIcon',
component: ChevronDownSmallIcon,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
args: {
size: 'string | number' as unknown as any,
name: 'string',
},
};
@@ -6,13 +6,9 @@ const meta: Meta<typeof ChevronRight> = {
title: 'Icons/ChevronRight',
component: ChevronRight,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
args: {
size: 'string | number' as unknown as any,
name: 'string',
},
};
@@ -6,13 +6,9 @@ const meta: Meta<typeof ChevronUpDown> = {
title: 'Icons/ChevronUpDown',
component: ChevronUpDown,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
args: {
size: 'string | number' as unknown as any,
name: 'string',
},
};
@@ -3,16 +3,12 @@ import { Meta, StoryObj } from '@storybook/react';
import { ChevronUpSmallIcon } from 'components/shared/CustomIcon';
const meta: Meta<typeof ChevronUpSmallIcon> = {
title: 'Icons/ChevronUpSmallIcon',
title: 'Components/ChevronUpSmallIcon',
component: ChevronUpSmallIcon,
tags: ['autodocs'],
argTypes: {
size: {
control: 'text',
},
name: {
control: 'text',
},
args: {
size: 'string | number' as unknown as any,
name: 'string',
},
};
@@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { CollaboratorsIcon } from 'components/shared/CustomIcon';
const meta: Meta<typeof CollaboratorsIcon> = {
title: 'Icons/CollaboratorsIcon',
title: 'Components/CollaboratorsIcon',
component: CollaboratorsIcon,
tags: ['autodocs'],
argTypes: {
@@ -1,20 +0,0 @@
import { StoryObj, Meta } from '@storybook/react';
import { ProjectSearchBar } from 'components/projects/ProjectSearchBar';
const meta: Meta<typeof ProjectSearchBar> = {
title: 'Components/ProjectSearchBar',
component: ProjectSearchBar,
tags: ['autodocs'],
argTypes: {
onChange: {
action: 'change',
},
},
};
export default meta;
type Story = StoryObj<typeof ProjectSearchBar>;
export const Default: Story = {};
@@ -11,7 +11,6 @@ import {
DomainStatus,
Domain,
Environment,
Permission,
} from 'gql-client';
export const user: User = {
@@ -45,7 +44,7 @@ export const organization: Organization = {
export const member: ProjectMember = {
id: '1',
member: user,
permissions: [Permission.Edit],
permissions: [],
isPending: false,
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
@@ -71,7 +70,7 @@ export const environmentVariable1: EnvironmentVariable = {
export const domain0: Domain = {
id: '1',
name: 'domain.com',
name: 'Domain',
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
branch: 'Branch',
@@ -79,16 +78,6 @@ export const domain0: Domain = {
redirectTo: null,
};
export const domain1: Domain = {
id: '2',
name: 'www.domain.com',
createdAt: '2021-08-01T00:00:00.000Z',
updatedAt: '2021-08-01T00:00:00.000Z',
branch: 'Branch',
status: DomainStatus.Live,
redirectTo: domain0,
};
export const deployment0: Deployment = {
id: '1',
url: 'https://deployment.com',
@@ -1,48 +0,0 @@
import { StoryObj, Meta } from '@storybook/react';
import DomainCard from 'components/projects/project/settings/DomainCard';
import { domain0, domain1, project } from '../../MockStoriesData';
const meta: Meta<typeof DomainCard> = {
title: 'Project/Settings/DomainCard',
component: DomainCard,
tags: ['autodocs'],
argTypes: {
domains: {
control: {
type: 'object',
},
},
domain: {
control: {
type: 'object',
},
},
branches: {
control: {
type: 'object',
},
},
project: {
control: {
type: 'object',
},
},
onUpdate: {
action: 'update',
},
},
};
export default meta;
type Story = StoryObj<typeof DomainCard>;
export const Default: Story = {
args: {
domains: [domain0, domain1],
domain: domain0,
branches: ['main'],
project: project,
},
};
@@ -1,43 +0,0 @@
import { StoryObj, Meta } from '@storybook/react';
import EditDomainDialog from 'components/projects/project/settings/EditDomainDialog';
const meta: Meta<typeof EditDomainDialog> = {
title: 'Components/EditDomainDialog',
component: EditDomainDialog,
tags: ['autodocs'],
argTypes: {
domains: {
control: {
type: 'object',
},
},
open: {
control: {
type: 'boolean',
},
},
handleOpen: {
action: 'open',
},
domain: {
control: {
type: 'object',
},
},
branches: {
control: {
type: 'object',
},
},
onUpdate: {
action: 'update',
},
},
};
export default meta;
type Story = StoryObj<typeof EditDomainDialog>;
export const Default: Story = {};
@@ -1,29 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import WebhookCard from 'components/projects/project/settings/WebhookCard';
const meta: Meta<typeof WebhookCard> = {
title: 'Project/Settings/WebhookCard',
component: WebhookCard,
tags: ['autodocs'],
argTypes: {
webhookUrl: {
control: {
type: 'text',
},
},
onDelete: {
action: 'delete',
},
},
};
export default meta;
type Story = StoryObj<typeof WebhookCard>;
export const Default: Story = {
args: {
webhookUrl: 'https://api.retool.com',
},
};
-28
View File
@@ -1,28 +0,0 @@
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()}`,
);
}
}
-11
View File
@@ -1,11 +0,0 @@
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;
+8 -8
View File
@@ -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';
import { VITE_BUGSNAG_API_KEY } from './constants';
const bugsnagApiKey = import.meta.env.VITE_BUGSNAG_API_KEY;
if (VITE_BUGSNAG_API_KEY) {
if (bugsnagApiKey) {
Bugsnag.start({
apiKey: VITE_BUGSNAG_API_KEY,
apiKey: bugsnagApiKey,
plugins: [new BugsnagPluginReact()],
});
BugsnagPerformance.start({ apiKey: VITE_BUGSNAG_API_KEY });
BugsnagPerformance.start({ apiKey: bugsnagApiKey });
}
export const errorLoggingEnabled = !!VITE_BUGSNAG_API_KEY;
export const errorLoggingEnabled = !!bugsnagApiKey;
export const LogErrorBoundary = VITE_BUGSNAG_API_KEY
export const LogErrorBoundary = bugsnagApiKey
? Bugsnag.getPlugin('react')!.createErrorBoundary(React)
: ({ children }: any) => children;
@@ -28,7 +28,7 @@ export function logError(error: Error) {
}
console.error(...errors);
if (VITE_BUGSNAG_API_KEY) {
if (import.meta.env.VITE_BUGSNAG_API_KEY) {
Bugsnag.notify(error);
}
}
+1 -3
View File
@@ -2,8 +2,6 @@ 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;
@@ -19,7 +17,7 @@ export async function signInWithEthereum(
);
const signature = await wallet.signMessage(message);
const res = await fetch(`${baseUrl}/auth/validate`, {
const res = await fetch(`${import.meta.env.VITE_SERVER_URL}/auth/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -1,7 +1,10 @@
import { TurnkeyClient, getWebAuthnAttestation } from '@turnkey/http';
import { WebauthnStamper } from '@turnkey/webauthn-stamper';
import { baseUrl, PASSKEY_WALLET_RPID, TURNKEY_BASE_URL } from './constants';
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!;
// All algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
// We only support ES256, which is listed here
+3 -4
View File
@@ -5,17 +5,16 @@ import {
LitGoogleAuth,
LitPasskeyAuth,
} from '@snowballtools/auth-lit';
import { VITE_LIT_RELAY_API_KEY } from './constants';
export const snowball = Snowball.withAuth({
google: LitGoogleAuth.configure({
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
}),
// apple: LitAppleAuth.configure({
// litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
// litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
// }),
passkey: LitPasskeyAuth.configure({
litRelayApiKey: VITE_LIT_RELAY_API_KEY!,
litRelayApiKey: import.meta.env.VITE_LIT_RELAY_API_KEY!,
}),
}).create({
initialChain: SnowballChain.sepolia,
+1 -12
View File
@@ -185,9 +185,6 @@ type RemoveEnvironmentVariableResponse = {
type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean;
};
type AddProjectFromTemplateResponse = {
addProjectFromTemplate: Project;
};
type AddProjectResponse = {
addProject: Project;
};
@@ -203,13 +200,6 @@ type DeleteProjectResponse = {
type DeleteDomainResponse = {
deleteDomain: boolean;
};
type AddProjectFromTemplateInput = {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
};
type AddProjectInput = {
name: string;
repository: string;
@@ -277,7 +267,6 @@ declare class GQLClient {
updateEnvironmentVariable(environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse>;
removeEnvironmentVariable(environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse>;
updateDeploymentToProd(deploymentId: string): Promise<UpdateDeploymentToProdResponse>;
addProjectFromTemplate(organizationSlug: string, data: AddProjectFromTemplateInput): Promise<AddProjectFromTemplateResponse>;
addProject(organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse>;
updateProject(projectId: string, data: UpdateProjectInput): Promise<UpdateProjectResponse>;
updateDomain(domainId: string, data: UpdateDomainInput): Promise<UpdateDomainResponse>;
@@ -292,4 +281,4 @@ declare class GQLClient {
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
}
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectFromTemplateInput, type AddProjectFromTemplateResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
+1 -12
View File
@@ -185,9 +185,6 @@ type RemoveEnvironmentVariableResponse = {
type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean;
};
type AddProjectFromTemplateResponse = {
addProjectFromTemplate: Project;
};
type AddProjectResponse = {
addProject: Project;
};
@@ -203,13 +200,6 @@ type DeleteProjectResponse = {
type DeleteDomainResponse = {
deleteDomain: boolean;
};
type AddProjectFromTemplateInput = {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
};
type AddProjectInput = {
name: string;
repository: string;
@@ -277,7 +267,6 @@ declare class GQLClient {
updateEnvironmentVariable(environmentVariableId: string, data: UpdateEnvironmentVariableInput): Promise<UpdateEnvironmentVariableResponse>;
removeEnvironmentVariable(environmentVariableId: string): Promise<RemoveEnvironmentVariableResponse>;
updateDeploymentToProd(deploymentId: string): Promise<UpdateDeploymentToProdResponse>;
addProjectFromTemplate(organizationSlug: string, data: AddProjectFromTemplateInput): Promise<AddProjectFromTemplateResponse>;
addProject(organizationSlug: string, data: AddProjectInput): Promise<AddProjectResponse>;
updateProject(projectId: string, data: UpdateProjectInput): Promise<UpdateProjectResponse>;
updateDomain(domainId: string, data: UpdateDomainInput): Promise<UpdateDomainResponse>;
@@ -292,4 +281,4 @@ declare class GQLClient {
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
}
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectFromTemplateInput, type AddProjectFromTemplateResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
+56 -87
View File
@@ -267,112 +267,93 @@ query ($projectId: String!, $filter: FilterDomainsInput) {
// src/mutations.ts
var import_client2 = require("@apollo/client");
var removeProjectMember = import_client2.gql`
mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId)
}
mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId)
}
`;
var updateProjectMember = import_client2.gql`
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data)
}
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data)
}
`;
var addProjectMember = import_client2.gql`
mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data)
}
`;
var addEnvironmentVariables = import_client2.gql`
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data)
}
`;
var updateEnvironmentVariable = import_client2.gql`
mutation (
$environmentVariableId: String!
$data: UpdateEnvironmentVariableInput!
) {
updateEnvironmentVariable(
environmentVariableId: $environmentVariableId
data: $data
)
}
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) {
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data)
}
`;
var removeEnvironmentVariable = import_client2.gql`
mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
}
mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
}
`;
var updateDeploymentToProd = import_client2.gql`
mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId)
}
`;
var addProjectFromTemplate = import_client2.gql`
mutation ($organizationSlug: String!, $data: AddProjectFromTemplateInput) {
addProjectFromTemplate(organizationSlug: $organizationSlug, data: $data) {
id
}
}
mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId)
}
`;
var addProject = import_client2.gql`
mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) {
id
}
mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) {
id
}
`;
}`;
var updateProjectMutation = import_client2.gql`
mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data)
}
`;
mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data)
}`;
var updateDomainMutation = import_client2.gql`
mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data)
}
`;
mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data)
}`;
var redeployToProd = import_client2.gql`
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
}
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
}
`;
var deleteProject = import_client2.gql`
mutation ($projectId: String!) {
deleteProject(projectId: $projectId)
}
mutation ($projectId: String!) {
deleteProject(projectId: $projectId)
}
`;
var deleteDomain = import_client2.gql`
mutation ($domainId: String!) {
deleteDomain(domainId: $domainId)
}
`;
mutation ($domainId: String!) {
deleteDomain(domainId: $domainId)
}`;
var rollbackDeployment = import_client2.gql`
mutation ($projectId: String!, $deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
mutation ($projectId: String! ,$deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
`;
var deleteDeployment = import_client2.gql`
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
`;
var addDomain = import_client2.gql`
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
}
`;
var authenticateGitHub = import_client2.gql`
mutation ($code: String!) {
authenticateGitHub(code: $code) {
token
}
mutation ($code: String!) {
authenticateGitHub(code: $code) {
token
}
`;
}`;
var unauthenticateGitHub = import_client2.gql`
mutation {
unauthenticateGitHub
}
`;
mutation {
unauthenticateGitHub
}`;
// src/client.ts
var defaultOptions = {
@@ -557,18 +538,6 @@ var GQLClient = class {
return data;
});
}
addProjectFromTemplate(organizationSlug, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
mutation: addProjectFromTemplate,
variables: {
organizationSlug,
data
}
});
return result.data;
});
}
addProject(organizationSlug, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
File diff suppressed because one or more lines are too long
+56 -87
View File
@@ -240,112 +240,93 @@ query ($projectId: String!, $filter: FilterDomainsInput) {
// src/mutations.ts
import { gql as gql2 } from "@apollo/client";
var removeProjectMember = gql2`
mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId)
}
mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId)
}
`;
var updateProjectMember = gql2`
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data)
}
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data)
}
`;
var addProjectMember = gql2`
mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data)
}
`;
var addEnvironmentVariables = gql2`
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data)
}
`;
var updateEnvironmentVariable = gql2`
mutation (
$environmentVariableId: String!
$data: UpdateEnvironmentVariableInput!
) {
updateEnvironmentVariable(
environmentVariableId: $environmentVariableId
data: $data
)
}
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) {
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data)
}
`;
var removeEnvironmentVariable = gql2`
mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
}
mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
}
`;
var updateDeploymentToProd = gql2`
mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId)
}
`;
var addProjectFromTemplate = gql2`
mutation ($organizationSlug: String!, $data: AddProjectFromTemplateInput) {
addProjectFromTemplate(organizationSlug: $organizationSlug, data: $data) {
id
}
}
mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId)
}
`;
var addProject = gql2`
mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) {
id
}
mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) {
id
}
`;
}`;
var updateProjectMutation = gql2`
mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data)
}
`;
mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data)
}`;
var updateDomainMutation = gql2`
mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data)
}
`;
mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data)
}`;
var redeployToProd = gql2`
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
}
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
}
`;
var deleteProject = gql2`
mutation ($projectId: String!) {
deleteProject(projectId: $projectId)
}
mutation ($projectId: String!) {
deleteProject(projectId: $projectId)
}
`;
var deleteDomain = gql2`
mutation ($domainId: String!) {
deleteDomain(domainId: $domainId)
}
`;
mutation ($domainId: String!) {
deleteDomain(domainId: $domainId)
}`;
var rollbackDeployment = gql2`
mutation ($projectId: String!, $deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
mutation ($projectId: String! ,$deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
`;
var deleteDeployment = gql2`
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
`;
var addDomain = gql2`
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
}
`;
var authenticateGitHub = gql2`
mutation ($code: String!) {
authenticateGitHub(code: $code) {
token
}
mutation ($code: String!) {
authenticateGitHub(code: $code) {
token
}
`;
}`;
var unauthenticateGitHub = gql2`
mutation {
unauthenticateGitHub
}
`;
mutation {
unauthenticateGitHub
}`;
// src/client.ts
var defaultOptions = {
@@ -530,18 +511,6 @@ var GQLClient = class {
return data;
});
}
addProjectFromTemplate(organizationSlug, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
mutation: addProjectFromTemplate,
variables: {
organizationSlug,
data
}
});
return result.data;
});
}
addProject(organizationSlug, data) {
return __async(this, null, function* () {
const result = yield this.client.mutate({
File diff suppressed because one or more lines are too long
-15
View File
@@ -228,21 +228,6 @@ export class GQLClient {
return data;
}
async addProjectFromTemplate(
organizationSlug: string,
data: types.AddProjectFromTemplateInput
): Promise<types.AddProjectFromTemplateResponse> {
const result = await this.client.mutate({
mutation: mutations.addProjectFromTemplate,
variables: {
organizationSlug,
data,
},
});
return result.data;
}
async addProject(
organizationSlug: string,
data: types.AddProjectInput
+57 -77
View File
@@ -1,127 +1,107 @@
import { gql } from "@apollo/client";
import { gql } from '@apollo/client';
export const removeProjectMember = gql`
mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId)
}
mutation ($projectMemberId: String!) {
removeProjectMember(projectMemberId: $projectMemberId)
}
`;
export const updateProjectMember = gql`
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data)
}
mutation ($projectMemberId: String!, $data: UpdateProjectMemberInput) {
updateProjectMember(projectMemberId: $projectMemberId, data: $data)
}
`;
export const addProjectMember = gql`
mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: AddProjectMemberInput) {
addProjectMember(projectId: $projectId, data: $data)
}
`;
export const addEnvironmentVariables = gql`
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: [AddEnvironmentVariableInput!]) {
addEnvironmentVariables(projectId: $projectId, data: $data)
}
`;
export const updateEnvironmentVariable = gql`
mutation (
$environmentVariableId: String!
$data: UpdateEnvironmentVariableInput!
) {
updateEnvironmentVariable(
environmentVariableId: $environmentVariableId
data: $data
)
}
mutation ($environmentVariableId: String!, $data: UpdateEnvironmentVariableInput!) {
updateEnvironmentVariable(environmentVariableId: $environmentVariableId, data: $data)
}
`;
export const removeEnvironmentVariable = gql`
mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
}
mutation ($environmentVariableId: String!) {
removeEnvironmentVariable(environmentVariableId: $environmentVariableId)
}
`;
export const updateDeploymentToProd = gql`
mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId)
}
`;
export const addProjectFromTemplate = gql`
mutation ($organizationSlug: String!, $data: AddProjectFromTemplateInput) {
addProjectFromTemplate(organizationSlug: $organizationSlug, data: $data) {
id
}
}
mutation ($deploymentId: String!) {
updateDeploymentToProd(deploymentId: $deploymentId)
}
`;
export const addProject = gql`
mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) {
id
}
mutation ($organizationSlug: String!, $data: AddProjectInput) {
addProject(organizationSlug: $organizationSlug, data: $data) {
id
}
`;
}`;
export const updateProjectMutation = gql`
mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data)
}
`;
mutation ($projectId: String!, $data: UpdateProjectInput) {
updateProject(projectId: $projectId, data: $data)
}`;
export const updateDomainMutation = gql`
mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data)
}
`;
mutation ($domainId: String!, $data: UpdateDomainInput!) {
updateDomain(domainId: $domainId, data: $data)
}`;
export const redeployToProd = gql`
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
}
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
}
`;
export const deleteProject = gql`
mutation ($projectId: String!) {
deleteProject(projectId: $projectId)
}
mutation ($projectId: String!) {
deleteProject(projectId: $projectId)
}
`;
export const deleteDomain = gql`
mutation ($domainId: String!) {
deleteDomain(domainId: $domainId)
}
`;
mutation ($domainId: String!) {
deleteDomain(domainId: $domainId)
}`;
export const rollbackDeployment = gql`
mutation ($projectId: String!, $deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
mutation ($projectId: String! ,$deploymentId: String!) {
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
}
`;
export const deleteDeployment = gql`
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
mutation ($deploymentId: String!) {
deleteDeployment(deploymentId: $deploymentId)
}
`;
export const addDomain = gql`
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
}
mutation ($projectId: String!, $data: AddDomainInput!) {
addDomain(projectId: $projectId, data: $data)
}
`;
export const authenticateGitHub = gql`
mutation ($code: String!) {
authenticateGitHub(code: $code) {
token
}
mutation ($code: String!) {
authenticateGitHub(code: $code) {
token
}
`;
}`;
export const unauthenticateGitHub = gql`
mutation {
unauthenticateGitHub
}
`;
mutation {
unauthenticateGitHub
}`;
+167 -180
View File
@@ -1,311 +1,298 @@
import { addProjectFromTemplate } from "./mutations";
// Note: equivalent to types present in GQL schema
export enum Role {
Owner = "Owner",
Maintainer = "Maintainer",
Reader = "Reader",
Owner = 'Owner',
Maintainer = 'Maintainer',
Reader = 'Reader',
}
export enum Permission {
View = "View",
Edit = "Edit",
View = 'View',
Edit = 'Edit',
}
export enum Environment {
Production = "Production",
Preview = "Preview",
Development = "Development",
Production = 'Production',
Preview = 'Preview',
Development = 'Development',
}
export enum DeploymentStatus {
Building = "Building",
Ready = "Ready",
Error = "Error",
Deleting = "Deleting",
Building = 'Building',
Ready = 'Ready',
Error = 'Error',
Deleting = 'Deleting'
}
export enum DomainStatus {
Live = "Live",
Pending = "Pending",
Live = 'Live',
Pending = 'Pending',
}
export type EnvironmentVariable = {
id: string;
environment: Environment;
key: string;
value: string;
createdAt: string;
updatedAt: string;
};
id: string
environment: Environment
key: string
value: string
createdAt: string
updatedAt: string
}
export type Domain = {
id: string;
branch: string;
name: string;
status: DomainStatus;
redirectTo: Domain | null;
createdAt: string;
updatedAt: string;
};
id: string
branch: string
name: string
status: DomainStatus
redirectTo: Domain | null
createdAt: string
updatedAt: string
}
export type User = {
id: string;
name: string | null;
email: string;
isVerified: boolean;
createdAt: string;
updatedAt: string;
gitHubToken: string | null;
};
id: string
name: string | null
email: string
isVerified: boolean
createdAt: string
updatedAt: string
gitHubToken: string | null
}
export type Deployment = {
id: string;
domain: Domain;
branch: string;
commitHash: string;
commitMessage: string;
url?: string;
environment: Environment;
isCurrent: boolean;
status: DeploymentStatus;
createdBy: User;
createdAt: string;
updatedAt: string;
};
id: string
domain: Domain
branch: string
commitHash: string
commitMessage: string
url?: string
environment: Environment
isCurrent: boolean
status: DeploymentStatus
createdBy: User
createdAt: string
updatedAt: string
}
export type OrganizationMember = {
id: string;
member: User;
role: Role;
createdAt: string;
updatedAt: string;
};
id: string
member: User
role: Role
createdAt: string
updatedAt: string
}
export type ProjectMember = {
id: string;
member: User;
permissions: Permission[];
isPending: boolean;
createdAt: string;
updatedAt: string;
};
id: string
member: User
permissions: Permission[]
isPending: boolean
createdAt: string
updatedAt: string
}
export type OrganizationProject = {
id: string;
owner: User;
deployments: Deployment[];
name: string;
repository: string;
prodBranch: string;
description: string;
template: string;
framework: string;
webhooks: string[];
members: ProjectMember[];
environmentVariables: EnvironmentVariable[];
createdAt: string;
updatedAt: string;
};
id: string
owner: User
deployments: Deployment[]
name: string
repository: string
prodBranch: string
description: string
template: string
framework: string
webhooks: string[]
members: ProjectMember[]
environmentVariables: EnvironmentVariable[]
createdAt: string
updatedAt: string
}
export type Organization = {
id: string;
name: string;
slug: string;
projects: OrganizationProject[];
createdAt: string;
updatedAt: string;
members: OrganizationMember[];
};
id: string
name: string
slug: string
projects: OrganizationProject[]
createdAt: string
updatedAt: string
members: OrganizationMember[]
}
export type Project = {
id: string;
owner: User;
deployments: Deployment[];
name: string;
repository: string;
prodBranch: string;
description: string;
template: string;
framework: string;
webhooks: string[];
members: ProjectMember[];
environmentVariables: EnvironmentVariable[];
createdAt: string;
updatedAt: string;
organization: Organization;
icon: string;
subDomain: string;
};
id: string
owner: User
deployments: Deployment[]
name: string
repository: string
prodBranch: string
description: string
template: string
framework: string
webhooks: string[]
members: ProjectMember[]
environmentVariables: EnvironmentVariable[]
createdAt: string
updatedAt: string
organization: Organization
icon: string
subDomain: string
}
export type GetProjectMembersResponse = {
projectMembers: ProjectMember[];
};
projectMembers: ProjectMember[]
}
export type AddProjectMemberResponse = {
addProjectMember: boolean;
};
addProjectMember: boolean
}
export type RemoveProjectMemberResponse = {
removeProjectMember: boolean;
};
}
export type UpdateProjectMemberResponse = {
updateProjectMember: boolean;
};
}
export type GetDeploymentsResponse = {
deployments: Deployment[];
};
deployments: Deployment[]
}
export type GetEnvironmentVariablesResponse = {
environmentVariables: EnvironmentVariable[];
};
environmentVariables: EnvironmentVariable[]
}
export type GetOrganizationsResponse = {
organizations: Organization[];
};
organizations: Organization[]
}
export type GetUserResponse = {
user: User;
};
user: User
}
export type GetProjectResponse = {
project: Project | null;
};
project: Project | null
}
export type GetProjectsInOrganizationResponse = {
projectsInOrganization: Project[];
};
projectsInOrganization: Project[]
}
export type GetDomainsResponse = {
domains: Domain[];
};
domains: Domain[]
}
export type SearchProjectsResponse = {
searchProjects: Project[];
};
searchProjects: Project[]
}
export type AddEnvironmentVariablesResponse = {
addEnvironmentVariables: boolean;
};
}
export type AddEnvironmentVariableInput = {
environments: string[];
key: string;
value: string;
};
}
export type UpdateEnvironmentVariableInput = {
key: string;
value: string;
};
}
export type UpdateProjectMemberInput = {
permissions: Permission[];
};
}
export type AddProjectMemberInput = {
email: string;
permissions: Permission[];
};
permissions: Permission[]
}
export type UpdateEnvironmentVariableResponse = {
updateEnvironmentVariable: boolean;
};
}
export type RemoveEnvironmentVariableResponse = {
removeEnvironmentVariable: boolean;
};
}
export type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean;
};
export type AddProjectFromTemplateResponse = {
addProjectFromTemplate: Project;
};
}
export type AddProjectResponse = {
addProject: Project;
};
addProject: Project
}
export type UpdateProjectResponse = {
updateProject: boolean;
};
}
export type UpdateDomainResponse = {
updateDomain: boolean;
};
}
export type DeleteProjectResponse = {
deleteProject: boolean;
};
}
export type DeleteDomainResponse = {
deleteDomain: boolean;
};
export type AddProjectFromTemplateInput = {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
};
}
export type AddProjectInput = {
name: string;
repository: string;
prodBranch: string;
template?: string;
};
}
export type UpdateProjectInput = {
name?: string;
description?: string;
prodBranch?: string;
webhooks?: string[];
organizationId?: string;
};
name?: string
description?: string
prodBranch?: string
webhooks?: string[]
organizationId?: string
}
export type UpdateDomainInput = {
name?: string;
branch?: string;
redirectToId?: string | null;
};
}
export type RedeployToProdResponse = {
redeployToProd: boolean;
};
redeployToProd: boolean
}
export type RollbackDeploymentResponse = {
rollbackDeployment: boolean;
};
rollbackDeployment: boolean
}
export type DeleteDeploymentResponse = {
deleteDeployment: boolean;
};
deleteDeployment: boolean
}
export type AddDomainInput = {
name: string;
};
name: string
}
export type FilterDomainInput = {
branch?: string;
status?: DomainStatus;
};
branch?: string
status?: DomainStatus
}
export type AddDomainResponse = {
addDomain: true;
};
addDomain: true
}
export type AuthenticateGitHubResponse = {
authenticateGitHub: {
token: string;
};
};
token: string
}
}
export type UnauthenticateGitHubResponse = {
unauthenticateGitHub: boolean;
};
unauthenticateGitHub: boolean
}