forked from cerc-io/snowballtools-base
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0294b3729d |
@@ -1,29 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download yarn
|
||||
run: |
|
||||
curl -fsSL -o /usr/local/bin/yarn https://github.com/yarnpkg/yarn/releases/download/v1.22.21/yarn-1.22.21.js
|
||||
chmod +x /usr/local/bin/yarn
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: yarn
|
||||
- name: Build libs
|
||||
run: yarn workspace gql-client run build
|
||||
- name: Linter check
|
||||
run: yarn lint
|
||||
+1
-4
@@ -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/
|
||||
@@ -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 +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 `environments/local.toml.example` file to `environments/local.toml`:
|
||||
|
||||
```zsh
|
||||
cp environments/local.toml.example environments/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](https://git.vdb.to/cerc-io/snowballtools-base-api-deployments):
|
||||
|
||||
```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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
port = 8000
|
||||
gqlPath = "/graphql"
|
||||
[server.session]
|
||||
secret = ""
|
||||
secret = "p4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi"
|
||||
appOriginUrl = "http://localhost:3000"
|
||||
trustProxy = false
|
||||
domain = "localhost"
|
||||
@@ -17,19 +17,8 @@
|
||||
clientId = ""
|
||||
clientSecret = ""
|
||||
|
||||
[google]
|
||||
clientId = ""
|
||||
clientSecret = ""
|
||||
|
||||
[turnkey]
|
||||
apiBaseUrl = "https://api.turnkey.com"
|
||||
apiPrivateKey = ""
|
||||
apiPublicKey = ""
|
||||
defaultOrganizationId = ""
|
||||
|
||||
[registryConfig]
|
||||
fetchDeploymentRecordDelay = 5000
|
||||
checkAuctionStatusDelay = 5000
|
||||
restEndpoint = "http://localhost:1317"
|
||||
gqlEndpoint = "http://localhost:9473/api"
|
||||
chainId = "laconic_9000-1"
|
||||
@@ -37,16 +26,9 @@
|
||||
bondId = ""
|
||||
authority = ""
|
||||
[registryConfig.fee]
|
||||
gas = ""
|
||||
fees = ""
|
||||
gasPrice = "1alnt"
|
||||
|
||||
[auction]
|
||||
commitFee = "1000"
|
||||
commitsDuration = "60s"
|
||||
revealFee = "1000"
|
||||
revealsDuration = "60s"
|
||||
denom = "alnt"
|
||||
amount = "200000"
|
||||
denom = "aphoton"
|
||||
gas = "750000"
|
||||
|
||||
[misc]
|
||||
projectDomain = "apps.snowballtools.com"
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@cerc-io/registry-sdk": "^0.2.11",
|
||||
"@graphql-tools/schema": "^10.0.2",
|
||||
"@graphql-tools/utils": "^10.0.12",
|
||||
"@octokit/oauth-app": "^6.1.0",
|
||||
"@snowballtools/laconic-sdk": "^0.1.17",
|
||||
"@turnkey/sdk-server": "^0.1.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/express": "^4.17.21",
|
||||
@@ -19,7 +19,6 @@
|
||||
"cors": "^2.8.5",
|
||||
"debug": "^4.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"express-session": "^1.18.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"graphql": "^16.8.1",
|
||||
|
||||
@@ -34,23 +34,14 @@ export interface RegistryConfig {
|
||||
privateKey: string;
|
||||
bondId: string;
|
||||
fetchDeploymentRecordDelay: number;
|
||||
checkAuctionStatusDelay: number;
|
||||
authority: string;
|
||||
fee: {
|
||||
amount: string;
|
||||
denom: string;
|
||||
gas: string;
|
||||
fees: string;
|
||||
gasPrice: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuctionConfig {
|
||||
commitFee: string;
|
||||
commitsDuration: string;
|
||||
revealFee: string;
|
||||
revealsDuration: string;
|
||||
denom: string;
|
||||
}
|
||||
|
||||
export interface MiscConfig {
|
||||
projectDomain: string;
|
||||
}
|
||||
@@ -60,7 +51,6 @@ export interface Config {
|
||||
database: DatabaseConfig;
|
||||
gitHub: GitHubConfig;
|
||||
registryConfig: RegistryConfig;
|
||||
auction: AuctionConfig;
|
||||
misc: MiscConfig;
|
||||
turnkey: {
|
||||
apiBaseUrl: string;
|
||||
|
||||
@@ -465,6 +465,8 @@ export class Database {
|
||||
id: organizationId
|
||||
});
|
||||
|
||||
newProject.subDomain = `${newProject.name}.${this.projectDomain}`;
|
||||
|
||||
return projectRepository.save(newProject);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,6 @@ export interface ApplicationDeploymentRequest {
|
||||
version: string;
|
||||
name: string;
|
||||
application: string;
|
||||
lrn?: string;
|
||||
auction?: string;
|
||||
config: string;
|
||||
meta: string;
|
||||
}
|
||||
@@ -114,22 +112,19 @@ export class Deployment {
|
||||
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
|
||||
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
applicationDeploymentRemovalRequestId!: string | null;
|
||||
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null;
|
||||
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
applicationDeploymentRemovalRecordId!: string | null;
|
||||
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
|
||||
|
||||
@Column('varchar')
|
||||
deployerLrn!: string;
|
||||
|
||||
@Column({
|
||||
enum: Environment
|
||||
})
|
||||
@@ -138,9 +133,6 @@ export class Deployment {
|
||||
@Column('boolean', { default: false })
|
||||
isCurrent!: boolean;
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
baseDomain!: string | null;
|
||||
|
||||
@Column({
|
||||
enum: DeploymentStatus
|
||||
})
|
||||
@@ -155,7 +147,7 @@ export class Deployment {
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt!: Date | null;
|
||||
}
|
||||
|
||||
@@ -46,12 +46,6 @@ export class Project {
|
||||
@Column('text', { default: '' })
|
||||
description!: string;
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
auctionId!: string | null;
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
deployerLrns!: string[] | null;
|
||||
|
||||
// TODO: Compute template & framework in import repository
|
||||
@Column('varchar', { nullable: true })
|
||||
template!: string | null;
|
||||
@@ -67,8 +61,8 @@ export class Project {
|
||||
@Column('varchar')
|
||||
icon!: string;
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
baseDomains!: string[] | null;
|
||||
@Column('varchar')
|
||||
subDomain!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'express-async-errors';
|
||||
import 'reflect-metadata';
|
||||
import debug from 'debug';
|
||||
import fs from 'fs';
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import assert from 'assert';
|
||||
import debug from 'debug';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Octokit } from 'octokit';
|
||||
import assert from 'assert';
|
||||
import { inc as semverInc } from 'semver';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk';
|
||||
import { Registry as LaconicRegistry } from '@snowballtools/laconic-sdk';
|
||||
|
||||
import { RegistryConfig } from './config';
|
||||
import {
|
||||
@@ -14,53 +12,49 @@ import {
|
||||
ApplicationDeploymentRequest,
|
||||
ApplicationDeploymentRemovalRequest
|
||||
} from './entity/Deployment';
|
||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionData } from './types';
|
||||
import { getConfig, getRepoDetails, sleep } from './utils';
|
||||
import { Auction } from '@cerc-io/registry-sdk/dist/proto/cerc/auction/v1/auction';
|
||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, PackageJSON } from './types';
|
||||
import { sleep } from './utils';
|
||||
|
||||
const log = debug('snowball:registry');
|
||||
|
||||
const APP_RECORD_TYPE = 'ApplicationRecord';
|
||||
const APP_DEPLOYMENT_AUCTION_RECORD_TYPE = 'ApplicationDeploymentAuction';
|
||||
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
|
||||
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
|
||||
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
|
||||
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
|
||||
const SLEEP_DURATION = 1000;
|
||||
|
||||
// TODO: Move registry code to registry-sdk/watcher-ts
|
||||
// TODO: Move registry code to laconic-sdk/watcher-ts
|
||||
export class Registry {
|
||||
private registry: LaconicRegistry;
|
||||
private registryConfig: RegistryConfig;
|
||||
|
||||
constructor(registryConfig: RegistryConfig) {
|
||||
constructor (registryConfig: RegistryConfig) {
|
||||
this.registryConfig = registryConfig;
|
||||
|
||||
const gasPrice = getGasPrice(registryConfig.fee.gasPrice);
|
||||
|
||||
this.registry = new LaconicRegistry(
|
||||
registryConfig.gqlEndpoint,
|
||||
registryConfig.restEndpoint,
|
||||
{ chainId: registryConfig.chainId, gasPrice }
|
||||
registryConfig.chainId
|
||||
);
|
||||
}
|
||||
|
||||
async createApplicationRecord({
|
||||
octokit,
|
||||
repository,
|
||||
async createApplicationRecord ({
|
||||
appName,
|
||||
packageJSON,
|
||||
commitHash,
|
||||
appType,
|
||||
repoUrl
|
||||
}: {
|
||||
octokit: Octokit
|
||||
repository: string;
|
||||
appName: string;
|
||||
packageJSON: PackageJSON;
|
||||
commitHash: string;
|
||||
appType: string;
|
||||
repoUrl: string;
|
||||
}): Promise<{
|
||||
applicationRecordId: string;
|
||||
applicationRecordData: ApplicationRecord;
|
||||
}> {
|
||||
const { repo, repoUrl, packageJSON } = await getRepoDetails(octokit, repository, commitHash)
|
||||
// Use registry-sdk to publish record
|
||||
// Use laconic-sdk to publish record
|
||||
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
|
||||
// Fetch previous records
|
||||
const records = await this.registry.queryRecords(
|
||||
@@ -93,7 +87,7 @@ export class Registry {
|
||||
repository_ref: commitHash,
|
||||
repository: [repoUrl],
|
||||
app_type: appType,
|
||||
name: repo,
|
||||
name: appName,
|
||||
...(packageJSON.description && { description: packageJSON.description }),
|
||||
...(packageJSON.homepage && { homepage: packageJSON.homepage }),
|
||||
...(packageJSON.license && { license: packageJSON.license }),
|
||||
@@ -106,145 +100,68 @@ export class Registry {
|
||||
...(packageJSON.version && { app_version: packageJSON.version })
|
||||
};
|
||||
|
||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||
|
||||
const result = await this.registry.setRecord(
|
||||
{
|
||||
privateKey: this.registryConfig.privateKey,
|
||||
record: applicationRecord,
|
||||
bondId: this.registryConfig.bondId
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
'',
|
||||
this.registryConfig.fee
|
||||
);
|
||||
|
||||
log(`Published application record ${result.id}`);
|
||||
log('Application record data:', applicationRecord);
|
||||
|
||||
// TODO: Discuss computation of LRN
|
||||
const lrn = this.getLrn(repo);
|
||||
log(`Setting name: ${lrn} for record ID: ${result.id}`);
|
||||
// TODO: Discuss computation of CRN
|
||||
const crn = this.getCrn(appName);
|
||||
log(`Setting name: ${crn} for record ID: ${result.data.id}`);
|
||||
|
||||
await sleep(SLEEP_DURATION);
|
||||
await this.registry.setName(
|
||||
{
|
||||
cid: result.id,
|
||||
lrn
|
||||
},
|
||||
{ cid: result.data.id, crn },
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
this.registryConfig.fee
|
||||
);
|
||||
|
||||
await sleep(SLEEP_DURATION);
|
||||
await this.registry.setName(
|
||||
{ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` },
|
||||
this.registryConfig.privateKey,
|
||||
this.registryConfig.fee
|
||||
);
|
||||
|
||||
await sleep(SLEEP_DURATION);
|
||||
await this.registry.setName(
|
||||
{
|
||||
cid: result.id,
|
||||
lrn: `${lrn}@${applicationRecord.app_version}`
|
||||
cid: result.data.id,
|
||||
crn: `${crn}@${applicationRecord.repository_ref}`
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
);
|
||||
|
||||
await sleep(SLEEP_DURATION);
|
||||
await this.registry.setName(
|
||||
{
|
||||
cid: result.id,
|
||||
lrn: `${lrn}@${applicationRecord.repository_ref}`
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
this.registryConfig.fee
|
||||
);
|
||||
|
||||
return {
|
||||
applicationRecordId: result.id,
|
||||
applicationRecordId: result.data.id,
|
||||
applicationRecordData: applicationRecord
|
||||
};
|
||||
}
|
||||
|
||||
async createApplicationDeploymentAuction(
|
||||
appName: string,
|
||||
octokit: Octokit,
|
||||
auctionData: AuctionData,
|
||||
data: DeepPartial<Deployment>,
|
||||
): Promise<{
|
||||
applicationDeploymentAuctionId: string;
|
||||
}> {
|
||||
assert(data.project?.repository, 'Project repository not found');
|
||||
|
||||
await this.createApplicationRecord({
|
||||
octokit,
|
||||
repository: data.project.repository,
|
||||
appType: data.project!.template!,
|
||||
commitHash: data.commitHash!,
|
||||
});
|
||||
|
||||
const lrn = this.getLrn(appName);
|
||||
const config = await getConfig();
|
||||
const auctionConfig = config.auction;
|
||||
|
||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||
const auctionResult = await this.registry.createProviderAuction(
|
||||
{
|
||||
commitFee: auctionConfig.commitFee,
|
||||
commitsDuration: auctionConfig.commitsDuration,
|
||||
revealFee: auctionConfig.revealFee,
|
||||
revealsDuration: auctionConfig.revealsDuration,
|
||||
denom: auctionConfig.denom,
|
||||
maxPrice: auctionData.maxPrice,
|
||||
numProviders: auctionData.numProviders,
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
)
|
||||
|
||||
if (!auctionResult.auction) {
|
||||
throw new Error('Error creating auction');
|
||||
}
|
||||
|
||||
// Create record of type applicationDeploymentAuction and publish
|
||||
const applicationDeploymentAuction = {
|
||||
application: lrn,
|
||||
auction: auctionResult.auction.id,
|
||||
type: APP_DEPLOYMENT_AUCTION_RECORD_TYPE,
|
||||
};
|
||||
|
||||
const result = await this.registry.setRecord(
|
||||
{
|
||||
privateKey: this.registryConfig.privateKey,
|
||||
record: applicationDeploymentAuction,
|
||||
bondId: this.registryConfig.bondId
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
);
|
||||
|
||||
log(`Application deployment auction created: ${auctionResult.auction.id}`);
|
||||
log(`Application deployment auction record published: ${result.id}`);
|
||||
log('Application deployment auction data:', applicationDeploymentAuction);
|
||||
|
||||
return {
|
||||
applicationDeploymentAuctionId: auctionResult.auction.id,
|
||||
};
|
||||
}
|
||||
|
||||
async createApplicationDeploymentRequest(data: {
|
||||
async createApplicationDeploymentRequest (data: {
|
||||
deployment: Deployment,
|
||||
appName: string,
|
||||
repository: string,
|
||||
auctionId?: string,
|
||||
lrn: string,
|
||||
environmentVariables: { [key: string]: string },
|
||||
dns: string,
|
||||
}): Promise<{
|
||||
applicationDeploymentRequestId: string;
|
||||
applicationDeploymentRequestData: ApplicationDeploymentRequest;
|
||||
}> {
|
||||
const lrn = this.getLrn(data.appName);
|
||||
const records = await this.registry.resolveNames([lrn]);
|
||||
const crn = this.getCrn(data.appName);
|
||||
const records = await this.registry.resolveNames([crn]);
|
||||
const applicationRecord = records[0];
|
||||
|
||||
if (!applicationRecord) {
|
||||
throw new Error(`No record found for ${lrn}`);
|
||||
throw new Error(`No record found for ${crn}`);
|
||||
}
|
||||
|
||||
// Create record of type ApplicationDeploymentRequest and publish
|
||||
@@ -252,10 +169,12 @@ export class Registry {
|
||||
type: APP_DEPLOYMENT_REQUEST_TYPE,
|
||||
version: '1.0.0',
|
||||
name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`,
|
||||
application: `${lrn}@${applicationRecord.attributes.app_version}`,
|
||||
application: `${crn}@${applicationRecord.attributes.app_version}`,
|
||||
dns: data.dns,
|
||||
|
||||
// TODO: Not set in test-progressive-web-app CI
|
||||
// deployment: '$CERC_REGISTRY_DEPLOYMENT_CRN',
|
||||
|
||||
// https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b
|
||||
config: JSON.stringify({
|
||||
env: data.environmentVariables
|
||||
@@ -266,65 +185,32 @@ export class Registry {
|
||||
)}`,
|
||||
repository: data.repository,
|
||||
repository_ref: data.deployment.commitHash
|
||||
}),
|
||||
deployer: data.lrn,
|
||||
...(data.auctionId && { auction: data.auctionId }),
|
||||
})
|
||||
};
|
||||
|
||||
await sleep(SLEEP_DURATION);
|
||||
|
||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||
|
||||
const result = await this.registry.setRecord(
|
||||
{
|
||||
privateKey: this.registryConfig.privateKey,
|
||||
record: applicationDeploymentRequest,
|
||||
bondId: this.registryConfig.bondId
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
'',
|
||||
this.registryConfig.fee
|
||||
);
|
||||
log(`Application deployment request record published: ${result.id}`);
|
||||
log(`Application deployment request record published: ${result.data.id}`);
|
||||
log('Application deployment request data:', applicationDeploymentRequest);
|
||||
|
||||
return {
|
||||
applicationDeploymentRequestId: result.id,
|
||||
applicationDeploymentRequestId: result.data.id,
|
||||
applicationDeploymentRequestData: applicationDeploymentRequest
|
||||
};
|
||||
}
|
||||
|
||||
async getAuctionWinningDeployers(
|
||||
auctionId: string
|
||||
): Promise<string[]> {
|
||||
const records = await this.registry.getAuctionsByIds([auctionId]);
|
||||
const auctionResult = records[0];
|
||||
|
||||
let deployerLrns = [];
|
||||
const { winnerAddresses } = auctionResult;
|
||||
|
||||
for (const auctionWinner of winnerAddresses) {
|
||||
const deployerRecords = await this.registry.queryRecords(
|
||||
{
|
||||
paymentAddress: auctionWinner,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
for (const record of deployerRecords) {
|
||||
if (record.names && record.names.length > 0) {
|
||||
deployerLrns.push(record.names[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deployerLrns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch ApplicationDeploymentRecords for deployments
|
||||
*/
|
||||
async getDeploymentRecords(
|
||||
async getDeploymentRecords (
|
||||
deployments: Deployment[]
|
||||
): Promise<AppDeploymentRecord[]> {
|
||||
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
|
||||
@@ -336,11 +222,11 @@ export class Registry {
|
||||
true
|
||||
);
|
||||
|
||||
// Filter records with ApplicationDeploymentRequestId ID and Deployment specific URL
|
||||
// Filter records with ApplicationRecord ID and Deployment specific URL
|
||||
return records.filter((record: AppDeploymentRecord) =>
|
||||
deployments.some(
|
||||
(deployment) =>
|
||||
deployment.applicationDeploymentRequestId === record.attributes.request &&
|
||||
deployment.applicationRecordId === record.attributes.application &&
|
||||
record.attributes.url.includes(deployment.id)
|
||||
)
|
||||
);
|
||||
@@ -349,7 +235,7 @@ export class Registry {
|
||||
/**
|
||||
* Fetch ApplicationDeploymentRecords by filter
|
||||
*/
|
||||
async getDeploymentRecordsByFilter(filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
|
||||
async getDeploymentRecordsByFilter (filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
|
||||
return this.registry.queryRecords(
|
||||
{
|
||||
type: APP_DEPLOYMENT_RECORD_TYPE,
|
||||
@@ -362,7 +248,7 @@ export class Registry {
|
||||
/**
|
||||
* Fetch ApplicationDeploymentRemovalRecords for deployments
|
||||
*/
|
||||
async getDeploymentRemovalRecords(
|
||||
async getDeploymentRemovalRecords (
|
||||
deployments: Deployment[]
|
||||
): Promise<AppDeploymentRemovalRecord[]> {
|
||||
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
|
||||
@@ -383,9 +269,8 @@ export class Registry {
|
||||
);
|
||||
}
|
||||
|
||||
async createApplicationDeploymentRemovalRequest(data: {
|
||||
async createApplicationDeploymentRemovalRequest (data: {
|
||||
deploymentId: string;
|
||||
deployerLrn: string;
|
||||
}): Promise<{
|
||||
applicationDeploymentRemovalRequestId: string;
|
||||
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
|
||||
@@ -393,57 +278,30 @@ export class Registry {
|
||||
const applicationDeploymentRemovalRequest = {
|
||||
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
|
||||
version: '1.0.0',
|
||||
deployment: data.deploymentId,
|
||||
deployer: data.deployerLrn
|
||||
deployment: data.deploymentId
|
||||
};
|
||||
|
||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||
|
||||
const result = await this.registry.setRecord(
|
||||
{
|
||||
privateKey: this.registryConfig.privateKey,
|
||||
record: applicationDeploymentRemovalRequest,
|
||||
bondId: this.registryConfig.bondId
|
||||
},
|
||||
this.registryConfig.privateKey,
|
||||
fee
|
||||
'',
|
||||
this.registryConfig.fee
|
||||
);
|
||||
|
||||
log(`Application deployment removal request record published: ${result.id}`);
|
||||
log(`Application deployment removal request record published: ${result.data.id}`);
|
||||
log('Application deployment removal request data:', applicationDeploymentRemovalRequest);
|
||||
|
||||
return {
|
||||
applicationDeploymentRemovalRequestId: result.id,
|
||||
applicationDeploymentRemovalRequestId: result.data.id,
|
||||
applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest
|
||||
};
|
||||
}
|
||||
|
||||
async getCompletedAuctionIds(auctionIds: (string | null | undefined)[]): Promise<string[] | null> {
|
||||
const validAuctionIds = auctionIds.filter((id): id is string => id !== null && id !== undefined);
|
||||
|
||||
if (!validAuctionIds.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const auctions = await this.registry.getAuctionsByIds(validAuctionIds);
|
||||
|
||||
const completedAuctions = auctions
|
||||
.filter((auction: Auction) => auction.status === 'completed')
|
||||
.map((auction: Auction) => auction.id);
|
||||
|
||||
return completedAuctions;
|
||||
}
|
||||
|
||||
async getRecordsByName(name: string): Promise<any> {
|
||||
return this.registry.resolveNames([name]);
|
||||
}
|
||||
|
||||
async getAuctionData(auctionId: string): Promise<any> {
|
||||
return this.registry.getAuctionsByIds([auctionId]);
|
||||
}
|
||||
|
||||
getLrn(appName: string): string {
|
||||
getCrn (appName: string): string {
|
||||
assert(this.registryConfig.authority, "Authority doesn't exist");
|
||||
return `lrn://${this.registryConfig.authority}/applications/${appName}`;
|
||||
return `crn://${this.registryConfig.authority}/applications/${appName}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, AuctionData } from './types';
|
||||
|
||||
const log = debug('snowball:resolver');
|
||||
|
||||
@@ -18,7 +17,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
return context.user;
|
||||
},
|
||||
|
||||
organizations: async (_: any, __: any, context: any) => {
|
||||
organizations: async (_:any, __: any, context: any) => {
|
||||
return service.getOrganizationsByUserId(context.user);
|
||||
},
|
||||
|
||||
@@ -26,15 +25,8 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
return service.getProjectById(projectId);
|
||||
},
|
||||
|
||||
projectsInOrganization: async (
|
||||
_: any,
|
||||
{ organizationSlug }: { organizationSlug: string },
|
||||
context: any,
|
||||
) => {
|
||||
return service.getProjectsInOrganization(
|
||||
context.user,
|
||||
organizationSlug,
|
||||
);
|
||||
projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => {
|
||||
return service.getProjectsInOrganization(context.user, organizationSlug);
|
||||
},
|
||||
|
||||
deployments: async (_: any, { projectId }: { projectId: string }) => {
|
||||
@@ -43,7 +35,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
environmentVariables: async (
|
||||
_: any,
|
||||
{ projectId }: { projectId: string },
|
||||
{ projectId }: { projectId: string }
|
||||
) => {
|
||||
return service.getEnvironmentVariablesByProjectId(projectId);
|
||||
},
|
||||
@@ -52,11 +44,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
return service.getProjectMembersByProjectId(projectId);
|
||||
},
|
||||
|
||||
searchProjects: async (
|
||||
_: any,
|
||||
{ searchText }: { searchText: string },
|
||||
context: any,
|
||||
) => {
|
||||
searchProjects: async (_: any, { searchText }: { searchText: string }, context: any) => {
|
||||
return service.searchProjects(context.user, searchText);
|
||||
},
|
||||
|
||||
@@ -64,18 +52,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
filter,
|
||||
}: { projectId: string; filter?: FindOptionsWhere<Domain> },
|
||||
filter
|
||||
}: { projectId: string; filter?: FindOptionsWhere<Domain> }
|
||||
) => {
|
||||
return service.getDomainsByProjectId(projectId, filter);
|
||||
},
|
||||
|
||||
getAuctionData: async (
|
||||
_: any,
|
||||
{ auctionId }: { auctionId: string },
|
||||
) => {
|
||||
return service.getAuctionData(auctionId);
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: Return error in GQL response
|
||||
@@ -83,13 +64,10 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
removeProjectMember: async (
|
||||
_: any,
|
||||
{ projectMemberId }: { projectMemberId: string },
|
||||
context: any,
|
||||
context: any
|
||||
) => {
|
||||
try {
|
||||
return await service.removeProjectMember(
|
||||
context.user,
|
||||
projectMemberId,
|
||||
);
|
||||
return await service.removeProjectMember(context.user, projectMemberId);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
@@ -100,13 +78,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectMemberId,
|
||||
data,
|
||||
data
|
||||
}: {
|
||||
projectMemberId: string;
|
||||
data: {
|
||||
permissions: Permission[];
|
||||
};
|
||||
},
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
return await service.updateProjectMember(projectMemberId, data);
|
||||
@@ -120,14 +98,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
data,
|
||||
data
|
||||
}: {
|
||||
projectId: string;
|
||||
data: {
|
||||
email: string;
|
||||
permissions: Permission[];
|
||||
};
|
||||
},
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
return Boolean(await service.addProjectMember(projectId, data));
|
||||
@@ -141,15 +119,15 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
data,
|
||||
data
|
||||
}: {
|
||||
projectId: string;
|
||||
data: { environments: string[]; key: string; value: string }[];
|
||||
},
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
return Boolean(
|
||||
await service.addEnvironmentVariables(projectId, data),
|
||||
await service.addEnvironmentVariables(projectId, data)
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
@@ -161,16 +139,16 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
environmentVariableId,
|
||||
data,
|
||||
data
|
||||
}: {
|
||||
environmentVariableId: string;
|
||||
data: DeepPartial<EnvironmentVariable>;
|
||||
},
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
return await service.updateEnvironmentVariable(
|
||||
environmentVariableId,
|
||||
data,
|
||||
data
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
@@ -180,7 +158,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
removeEnvironmentVariable: async (
|
||||
_: any,
|
||||
{ environmentVariableId }: { environmentVariableId: string },
|
||||
{ environmentVariableId }: { environmentVariableId: string }
|
||||
) => {
|
||||
try {
|
||||
return await service.removeEnvironmentVariable(environmentVariableId);
|
||||
@@ -193,54 +171,26 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
updateDeploymentToProd: async (
|
||||
_: any,
|
||||
{ deploymentId }: { deploymentId: string },
|
||||
context: any,
|
||||
context: any
|
||||
) => {
|
||||
try {
|
||||
return Boolean(
|
||||
await service.updateDeploymentToProd(context.user, deploymentId),
|
||||
);
|
||||
return Boolean(await service.updateDeploymentToProd(context.user, deploymentId));
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
addProjectFromTemplate: async (
|
||||
_: any,
|
||||
{
|
||||
organizationSlug,
|
||||
data,
|
||||
lrn,
|
||||
auctionData
|
||||
}: { organizationSlug: string; data: AddProjectFromTemplateInput; lrn: string; auctionData: AuctionData },
|
||||
context: any,
|
||||
) => {
|
||||
try {
|
||||
return await service.addProjectFromTemplate(
|
||||
context.user,
|
||||
organizationSlug,
|
||||
data,
|
||||
lrn,
|
||||
auctionData
|
||||
);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
addProject: async (
|
||||
_: any,
|
||||
{
|
||||
organizationSlug,
|
||||
data,
|
||||
lrn,
|
||||
auctionData
|
||||
}: { organizationSlug: string; data: DeepPartial<Project>; lrn: string; auctionData: AuctionData },
|
||||
context: any,
|
||||
data
|
||||
}: { organizationSlug: string; data: DeepPartial<Project> },
|
||||
context: any
|
||||
) => {
|
||||
try {
|
||||
return await service.addProject(context.user, organizationSlug, data, lrn, auctionData);
|
||||
return await service.addProject(context.user, organizationSlug, data);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
throw err;
|
||||
@@ -249,7 +199,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
updateProject: async (
|
||||
_: any,
|
||||
{ projectId, data }: { projectId: string; data: DeepPartial<Project> },
|
||||
{ projectId, data }: { projectId: string; data: DeepPartial<Project> }
|
||||
) => {
|
||||
try {
|
||||
return await service.updateProject(projectId, data);
|
||||
@@ -262,12 +212,10 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
redeployToProd: async (
|
||||
_: any,
|
||||
{ deploymentId }: { deploymentId: string },
|
||||
context: any,
|
||||
context: any
|
||||
) => {
|
||||
try {
|
||||
return Boolean(
|
||||
await service.redeployToProd(context.user, deploymentId),
|
||||
);
|
||||
return Boolean(await service.redeployToProd(context.user, deploymentId));
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
@@ -296,8 +244,8 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
_: any,
|
||||
{
|
||||
projectId,
|
||||
deploymentId,
|
||||
}: { deploymentId: string; projectId: string },
|
||||
deploymentId
|
||||
}: { deploymentId: string; projectId: string }
|
||||
) => {
|
||||
try {
|
||||
return await service.rollbackDeployment(projectId, deploymentId);
|
||||
@@ -309,7 +257,9 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
deleteDeployment: async (
|
||||
_: any,
|
||||
{ deploymentId }: { deploymentId: string },
|
||||
{
|
||||
deploymentId
|
||||
}: { deploymentId: string; }
|
||||
) => {
|
||||
try {
|
||||
return await service.deleteDeployment(deploymentId);
|
||||
@@ -321,7 +271,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
addDomain: async (
|
||||
_: any,
|
||||
{ projectId, data }: { projectId: string; data: { name: string } },
|
||||
{ projectId, data }: { projectId: string; data: { name: string } }
|
||||
) => {
|
||||
try {
|
||||
return Boolean(await service.addDomain(projectId, data));
|
||||
@@ -333,7 +283,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
updateDomain: async (
|
||||
_: any,
|
||||
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> },
|
||||
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> }
|
||||
) => {
|
||||
try {
|
||||
return await service.updateDomain(domainId, data);
|
||||
@@ -346,7 +296,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
authenticateGitHub: async (
|
||||
_: any,
|
||||
{ code }: { code: string },
|
||||
context: any,
|
||||
context: any
|
||||
) => {
|
||||
try {
|
||||
return await service.authenticateGitHub(code, context.user);
|
||||
@@ -358,14 +308,12 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
|
||||
unauthenticateGitHub: async (_: any, __: object, context: any) => {
|
||||
try {
|
||||
return service.unauthenticateGitHub(context.user, {
|
||||
gitHubToken: null,
|
||||
});
|
||||
return service.unauthenticateGitHub(context.user, { gitHubToken: null });
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,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
|
||||
//
|
||||
@@ -32,7 +19,6 @@ router.get('/registration/:email', async (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/register', async (req, res) => {
|
||||
console.log('Register', req.body);
|
||||
const { email, challenge, attestation } = req.body;
|
||||
const user = await createUser(req.app.get('service'), {
|
||||
challenge,
|
||||
@@ -45,7 +31,6 @@ router.post('/register', async (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/authenticate', async (req, res) => {
|
||||
console.log('Authenticate', req.body);
|
||||
const { signedWhoamiRequest } = req.body;
|
||||
const user = await authenticateUser(
|
||||
req.app.get('service'),
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/version', async (req, res) => {
|
||||
return res.send({ version: '0.0.9' });
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -22,13 +22,6 @@ enum DeploymentStatus {
|
||||
Deleting
|
||||
}
|
||||
|
||||
enum AuctionStatus {
|
||||
completed
|
||||
reveal
|
||||
commit
|
||||
expired
|
||||
}
|
||||
|
||||
enum DomainStatus {
|
||||
Live
|
||||
Pending
|
||||
@@ -72,8 +65,6 @@ type Project {
|
||||
repository: String!
|
||||
prodBranch: String!
|
||||
description: String
|
||||
deployerLrns: [String]
|
||||
auctionId: String
|
||||
template: String
|
||||
framework: String
|
||||
webhooks: [String!]
|
||||
@@ -83,7 +74,7 @@ type Project {
|
||||
updatedAt: String!
|
||||
organization: Organization!
|
||||
icon: String
|
||||
baseDomains: [String!]
|
||||
subDomain: String
|
||||
}
|
||||
|
||||
type ProjectMember {
|
||||
@@ -103,9 +94,7 @@ type Deployment {
|
||||
commitMessage: String!
|
||||
url: String
|
||||
environment: Environment!
|
||||
deployerLrn: String
|
||||
isCurrent: Boolean!
|
||||
baseDomain: String
|
||||
status: DeploymentStatus!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
@@ -141,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!
|
||||
@@ -193,48 +174,6 @@ input FilterDomainsInput {
|
||||
status: DomainStatus
|
||||
}
|
||||
|
||||
type Fee {
|
||||
type: String!
|
||||
quantity: String!
|
||||
}
|
||||
|
||||
type Bid {
|
||||
auctionId: String!
|
||||
bidderAddress: String!
|
||||
status: String!
|
||||
commitHash: String!
|
||||
commitTime: String
|
||||
commitFee: Fee
|
||||
revealTime: String
|
||||
revealFee: Fee
|
||||
bidAmount: Fee
|
||||
}
|
||||
|
||||
type Auction {
|
||||
id: String!
|
||||
kind: String!
|
||||
status: String!
|
||||
ownerAddress: String!
|
||||
createTime: String!
|
||||
commitsEndTime: String!
|
||||
revealsEndTime: String!
|
||||
commitFee: Fee!
|
||||
revealFee: Fee!
|
||||
minimumBid: Fee
|
||||
winnerAddresses: [String!]!
|
||||
winnerBids: [Fee!]
|
||||
winnerPrice: Fee
|
||||
maxPrice: Fee
|
||||
numProviders: Int!
|
||||
fundsReleased: Boolean!
|
||||
bids: [Bid!]!
|
||||
}
|
||||
|
||||
input AuctionData {
|
||||
maxPrice: String,
|
||||
numProviders: Int,
|
||||
}
|
||||
|
||||
type Query {
|
||||
user: User!
|
||||
organizations: [Organization!]
|
||||
@@ -245,7 +184,6 @@ type Query {
|
||||
environmentVariables(projectId: String!): [EnvironmentVariable!]
|
||||
projectMembers(projectId: String!): [ProjectMember!]
|
||||
searchProjects(searchText: String!): [Project!]
|
||||
getAuctionData(auctionId: String!): Auction!
|
||||
domains(projectId: String!, filter: FilterDomainsInput): [Domain]
|
||||
}
|
||||
|
||||
@@ -266,18 +204,7 @@ type Mutation {
|
||||
): Boolean!
|
||||
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
|
||||
updateDeploymentToProd(deploymentId: String!): Boolean!
|
||||
addProjectFromTemplate(
|
||||
organizationSlug: String!
|
||||
data: AddProjectFromTemplateInput
|
||||
lrn: String
|
||||
auctionData: AuctionData
|
||||
): Project!
|
||||
addProject(
|
||||
organizationSlug: String!
|
||||
data: AddProjectInput!
|
||||
lrn: String
|
||||
auctionData: AuctionData
|
||||
): Project!
|
||||
addProject(organizationSlug: String!, data: AddProjectInput): Project!
|
||||
updateProject(projectId: String!, data: UpdateProjectInput): Boolean!
|
||||
redeployToProd(deploymentId: String!): Boolean!
|
||||
deleteProject(projectId: String!): Boolean!
|
||||
|
||||
@@ -17,7 +17,6 @@ import { ServerConfig } from './config';
|
||||
import { DEFAULT_GQL_PATH } from './constants';
|
||||
import githubRouter from './routes/github';
|
||||
import authRouter from './routes/auth';
|
||||
import stagingRouter from './routes/staging';
|
||||
import { Service } from './service';
|
||||
|
||||
const log = debug('snowball:server');
|
||||
@@ -110,12 +109,6 @@ export const createAndStartServer = async (
|
||||
app.set('service', service);
|
||||
app.use('/auth', authRouter);
|
||||
app.use('/api/github', githubRouter);
|
||||
app.use('/staging', stagingRouter);
|
||||
|
||||
app.use((err: any, req: any, res: any, next: any) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: err.message });
|
||||
});
|
||||
|
||||
httpServer.listen(port, host, () => {
|
||||
log(`Server is listening on ${host}:${port}${server.graphqlPath}`);
|
||||
|
||||
+300
-607
File diff suppressed because it is too large
Load Diff
@@ -29,8 +29,6 @@ export interface GitPushEventPayload {
|
||||
|
||||
export interface AppDeploymentRecordAttributes {
|
||||
application: string;
|
||||
auction: string;
|
||||
deployer: string;
|
||||
dns: string;
|
||||
meta: string;
|
||||
name: string;
|
||||
@@ -43,7 +41,7 @@ export interface AppDeploymentRecordAttributes {
|
||||
export interface AppDeploymentRemovalRecordAttributes {
|
||||
deployment: string;
|
||||
request: string;
|
||||
type: 'ApplicationDeploymentRemovalRecord';
|
||||
type: "ApplicationDeploymentRemovalRecord";
|
||||
version: string;
|
||||
}
|
||||
|
||||
@@ -63,36 +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;
|
||||
}
|
||||
|
||||
export interface Auction {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
ownerAddress: string;
|
||||
createTime: Date;
|
||||
commitsEndTime: Date;
|
||||
revealsEndTime: Date;
|
||||
commitFee: string;
|
||||
revealFee: string;
|
||||
minimumBid?: string;
|
||||
winnerAddresses: string[];
|
||||
winnerBids?: string[];
|
||||
winnerPrice?: string;
|
||||
maxPrice?: string;
|
||||
numProviders: number;
|
||||
fundsReleased: boolean;
|
||||
bids: string[];
|
||||
}
|
||||
|
||||
export interface AuctionData {
|
||||
maxPrice: string,
|
||||
numProviders: number,
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import assert from 'assert';
|
||||
import debug from 'debug';
|
||||
import fs from 'fs-extra';
|
||||
import { Octokit } from 'octokit';
|
||||
import path from 'path';
|
||||
import toml from 'toml';
|
||||
import debug from 'debug';
|
||||
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { Config } from './config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
|
||||
import { PackageJSON } from './types';
|
||||
|
||||
const log = debug('snowball:utils');
|
||||
|
||||
@@ -80,43 +76,3 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
|
||||
|
||||
export const sleep = async (ms: number): Promise<void> =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export const getRepoDetails = async (
|
||||
octokit: Octokit,
|
||||
repository: string,
|
||||
commitHash: string | undefined,
|
||||
): Promise<{
|
||||
repo: string;
|
||||
packageJSON: PackageJSON;
|
||||
repoUrl: string;
|
||||
}> => {
|
||||
const [owner, repo] = repository.split('/');
|
||||
const { data: packageJSONData } = await octokit.rest.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
path: 'package.json',
|
||||
ref: commitHash,
|
||||
});
|
||||
|
||||
if (!packageJSONData) {
|
||||
throw new Error('Package.json file not found');
|
||||
}
|
||||
|
||||
assert(!Array.isArray(packageJSONData) && packageJSONData.type === 'file');
|
||||
const packageJSON: PackageJSON = JSON.parse(atob(packageJSONData.content));
|
||||
|
||||
assert(packageJSON.name, "name field doesn't exist in package.json");
|
||||
|
||||
const repoUrl = (
|
||||
await octokit.rest.repos.get({
|
||||
owner,
|
||||
repo,
|
||||
})
|
||||
).data.html_url;
|
||||
|
||||
return {
|
||||
repo,
|
||||
packageJSON,
|
||||
repoUrl
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import * as fs from 'fs/promises';
|
||||
import debug from 'debug';
|
||||
|
||||
import { getConfig } from '../src/utils';
|
||||
import { Config } from '../src/config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
|
||||
const log = debug('snowball:delete-database');
|
||||
|
||||
@@ -11,7 +13,7 @@ const deleteFile = async (filePath: string) => {
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const config = await getConfig();
|
||||
const config = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||
|
||||
deleteFile(config.database.dbPath);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
getEntities,
|
||||
loadAndSaveData
|
||||
} from '../src/utils';
|
||||
import { Config } from '../src/config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
|
||||
const log = debug('snowball:initialize-database');
|
||||
|
||||
@@ -154,7 +156,7 @@ const generateTestData = async (dataSource: DataSource) => {
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const config = await getConfig();
|
||||
const config = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||
const isDbPresent = await checkFileExists(config.database.dbPath);
|
||||
|
||||
if (!isDbPresent) {
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
import debug from 'debug';
|
||||
|
||||
import { parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
|
||||
import { Registry } from '@snowballtools/laconic-sdk';
|
||||
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
import { Config } from '../src/config';
|
||||
import { getConfig } from '../src/utils';
|
||||
|
||||
const log = debug('snowball:initialize-registry');
|
||||
|
||||
const DENOM = 'alnt';
|
||||
const DENOM = 'aphoton';
|
||||
const BOND_AMOUNT = '1000000000';
|
||||
|
||||
async function main () {
|
||||
const { registryConfig } = await getConfig();
|
||||
const { registryConfig } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||
|
||||
// TODO: Get authority names from args
|
||||
const authorityNames = ['snowballtools', registryConfig.authority];
|
||||
|
||||
const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, {chainId: registryConfig.chainId});
|
||||
const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
|
||||
|
||||
const bondId = await registry.getNextBondId(registryConfig.privateKey);
|
||||
log('bondId:', bondId);
|
||||
|
||||
const fee = parseGasAndFees(registryConfig.fee.gas, registryConfig.fee.fees);
|
||||
|
||||
await registry.createBond(
|
||||
{ denom: DENOM, amount: BOND_AMOUNT },
|
||||
registryConfig.privateKey,
|
||||
fee
|
||||
registryConfig.fee
|
||||
);
|
||||
|
||||
for await (const name of authorityNames) {
|
||||
await registry.reserveAuthority({ name }, registryConfig.privateKey, fee);
|
||||
await registry.reserveAuthority({ name }, registryConfig.privateKey, registryConfig.fee);
|
||||
log('Reserved authority name:', name);
|
||||
await registry.setAuthorityBond(
|
||||
{ name, bondId },
|
||||
registryConfig.privateKey,
|
||||
fee
|
||||
registryConfig.fee
|
||||
);
|
||||
log(`Bond ${bondId} set for authority ${name}`);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,22 @@ import debug from 'debug';
|
||||
import { DataSource } from 'typeorm';
|
||||
import path from 'path';
|
||||
|
||||
import { parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
|
||||
import { Registry } from '@snowballtools/laconic-sdk';
|
||||
|
||||
import { Config } from '../src/config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
import { getConfig } from '../src/utils';
|
||||
import { Deployment, DeploymentStatus, Environment } from '../src/entity/Deployment';
|
||||
|
||||
const log = debug('snowball:publish-deploy-records');
|
||||
|
||||
async function main() {
|
||||
const { registryConfig, database, misc } = await getConfig();
|
||||
async function main () {
|
||||
const { registryConfig, database, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||
|
||||
const registry = new Registry(
|
||||
registryConfig.gqlEndpoint,
|
||||
registryConfig.restEndpoint,
|
||||
{ chainId: registryConfig.chainId }
|
||||
registryConfig.chainId
|
||||
);
|
||||
|
||||
const dataSource = new DataSource({
|
||||
@@ -59,8 +61,6 @@ async function main() {
|
||||
url
|
||||
};
|
||||
|
||||
const fee = parseGasAndFees(registryConfig.fee.gas, registryConfig.fee.fees);
|
||||
|
||||
const result = await registry.setRecord(
|
||||
{
|
||||
privateKey: registryConfig.privateKey,
|
||||
@@ -68,12 +68,12 @@ async function main() {
|
||||
bondId: registryConfig.bondId
|
||||
},
|
||||
'',
|
||||
fee
|
||||
registryConfig.fee
|
||||
);
|
||||
|
||||
// Remove deployment for project subdomain if deployment is for production environment
|
||||
if (deployment.environment === Environment.Production) {
|
||||
applicationDeploymentRecord.url = `https://${deployment.project.name}.${deployment.baseDomain}`;
|
||||
applicationDeploymentRecord.url = `https://${deployment.project.subDomain}`
|
||||
|
||||
await registry.setRecord(
|
||||
{
|
||||
@@ -82,12 +82,12 @@ async function main() {
|
||||
bondId: registryConfig.bondId
|
||||
},
|
||||
'',
|
||||
fee
|
||||
registryConfig.fee
|
||||
);
|
||||
}
|
||||
|
||||
log('Application deployment record data:', applicationDeploymentRecord);
|
||||
log(`Application deployment record published: ${result.id}`);
|
||||
log(`Application deployment record published: ${result.data.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,22 @@ import debug from 'debug';
|
||||
import { DataSource } from 'typeorm';
|
||||
import path from 'path';
|
||||
|
||||
import { parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
|
||||
import { Registry } from '@cerc-io/laconic-sdk';
|
||||
|
||||
import { Config } from '../src/config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
import { getConfig } from '../src/utils';
|
||||
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
|
||||
|
||||
const log = debug('snowball:publish-deployment-removal-records');
|
||||
|
||||
async function main () {
|
||||
const { registryConfig, database } = await getConfig();
|
||||
const { registryConfig, database, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||
|
||||
const registry = new Registry(
|
||||
registryConfig.gqlEndpoint,
|
||||
registryConfig.restEndpoint,
|
||||
{ chainId: registryConfig.chainId }
|
||||
registryConfig.chainId
|
||||
);
|
||||
|
||||
const dataSource = new DataSource({
|
||||
@@ -45,8 +47,6 @@ async function main () {
|
||||
request: deployment.applicationDeploymentRemovalRequestId,
|
||||
}
|
||||
|
||||
const fee = parseGasAndFees(registryConfig.fee.gas, registryConfig.fee.fees);
|
||||
|
||||
const result = await registry.setRecord(
|
||||
{
|
||||
privateKey: registryConfig.privateKey,
|
||||
@@ -54,11 +54,11 @@ async function main () {
|
||||
bondId: registryConfig.bondId
|
||||
},
|
||||
'',
|
||||
fee
|
||||
registryConfig.fee
|
||||
);
|
||||
|
||||
log('Application deployment removal record data:', applicationDeploymentRemovalRecord);
|
||||
log(`Application deployment removal record published: ${result.id}`);
|
||||
log(`Application deployment removal record published: ${result.data.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,17 +44,14 @@
|
||||
|
||||
- If deployment fails due to low bond balance
|
||||
- Check balances
|
||||
|
||||
```bash
|
||||
# Account balance
|
||||
yarn laconic registry account get
|
||||
yarn laconic cns account get
|
||||
|
||||
# Bond balance
|
||||
yarn laconic registry bond get --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
|
||||
yarn laconic cns bond get --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
|
||||
```
|
||||
|
||||
- Command to refill bond
|
||||
|
||||
```bash
|
||||
yarn laconic registry bond refill --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32 --type alnt --quantity 10000000
|
||||
yarn laconic cns bond refill --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32 --type aphoton --quantity 10000000
|
||||
```
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
services:
|
||||
registry:
|
||||
cns:
|
||||
restEndpoint: 'http://console.laconic.com:1317'
|
||||
gqlEndpoint: 'http://console.laconic.com:9473/api'
|
||||
userKey: 87d00f66a73e2ca428adeb49ba9164d0ad9a87edc60e33d46ad3031b9c5701fe
|
||||
bondId: 89c75c7bc5759861d10285aff6f9e7227d6855e446b77ad5d8324822dfec7deb
|
||||
bondId: 098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661
|
||||
chainId: laconic_9000-1
|
||||
gas:
|
||||
fees:
|
||||
gasPrice: 1
|
||||
gas: 550000
|
||||
fees: 200000aphoton
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
services:
|
||||
registry:
|
||||
cns:
|
||||
restEndpoint: http://console.laconic.com:1317
|
||||
gqlEndpoint: http://console.laconic.com:9473/api
|
||||
userKey: 489c9dd3931c2a2d4dd77973302dc5eb01e2a49552f9d932c58d9da823512311
|
||||
bondId: 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
|
||||
chainId: laconic_9000-1
|
||||
gas:
|
||||
fees:
|
||||
gasPrice: 1
|
||||
gas: 1200000
|
||||
fees: 200000aphoton
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Repository URL
|
||||
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
|
||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
|
||||
# Get the latest commit hash from the repository
|
||||
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||
@@ -18,7 +18,7 @@ REGISTRY_BOND_ID="99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be
|
||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||
|
||||
# Get latest version from registry and increment application-record version
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationRecord --all --name "snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
|
||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||
# Set application-record version if no previous records were found
|
||||
@@ -26,20 +26,20 @@ 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'
|
||||
name: snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
application: lrn://snowballtools/applications/snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
application: crn://snowballtools/applications/snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
dns: dashboard
|
||||
config:
|
||||
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
|
||||
@@ -64,64 +64,32 @@ echo "Files generated successfully."
|
||||
RECORD_FILE=records/application-record.yml
|
||||
|
||||
# Publish ApplicationRecord
|
||||
publish_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to publish record"
|
||||
exit $rc
|
||||
fi
|
||||
RECORD_ID=$(echo $publish_response | jq -r '.id')
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_LRN="lrn://snowballtools/applications/snowballtools-base-frontend"
|
||||
REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend"
|
||||
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set name: $REGISTRY_APP_LRN@${PACKAGE_VERSION}"
|
||||
exit $rc
|
||||
fi
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set hash"
|
||||
exit $rc
|
||||
fi
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set release"
|
||||
exit $rc
|
||||
fi
|
||||
echo "$REGISTRY_APP_LRN set for ApplicationRecord"
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
|
||||
|
||||
# Check if record found for REGISTRY_APP_LRN
|
||||
query_response=$(yarn --silent laconic -c $CONFIG_FILE registry name resolve "$REGISTRY_APP_LRN")
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query name"
|
||||
exit $rc
|
||||
fi
|
||||
APP_RECORD=$(echo $query_response | jq '.[0]')
|
||||
# Check if record found for REGISTRY_APP_CRN
|
||||
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_LRN."
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RECORD_FILE=records/application-deployment-request.yml
|
||||
|
||||
sleep 2
|
||||
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query deployment request"
|
||||
exit $rc
|
||||
fi
|
||||
DEPLOYMENT_REQUEST_ID=$(echo $deployment_response | jq -r '.id')
|
||||
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Repository URL
|
||||
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
|
||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
|
||||
# Get the latest commit hash from the repository
|
||||
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||
@@ -18,7 +18,7 @@ REGISTRY_BOND_ID="098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb466
|
||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||
|
||||
# Get latest version from registry and increment application-record version
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "staging-snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationRecord --all --name "staging-snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
|
||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||
# Set application-record version if no previous records were found
|
||||
@@ -26,26 +26,26 @@ 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'
|
||||
name: staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
application: lrn://staging-snowballtools/applications/staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
dns: dashboard.staging.apps.snowballtools.com
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
|
||||
LACONIC_HOSTED_CONFIG_github_clientid: 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 @ $CURRENT_DATE_TIME
|
||||
@@ -54,7 +54,7 @@ record:
|
||||
EOF
|
||||
|
||||
# Generate application-record.yml with incremented version
|
||||
cat >./staging-records/application-record.yml <<EOF
|
||||
cat > ./staging-records/application-record.yml <<EOF
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
@@ -70,64 +70,32 @@ echo "Files generated successfully."
|
||||
RECORD_FILE=staging-records/application-record.yml
|
||||
|
||||
# Publish ApplicationRecord
|
||||
publish_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to publish record"
|
||||
exit $rc
|
||||
fi
|
||||
RECORD_ID=$(echo $publish_response | jq -r '.id')
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_LRN="lrn://staging-snowballtools/applications/staging-snowballtools-base-frontend"
|
||||
REGISTRY_APP_CRN="crn://staging-snowballtools/applications/staging-snowballtools-base-frontend"
|
||||
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set name: $REGISTRY_APP_LRN@${PACKAGE_VERSION}"
|
||||
exit $rc
|
||||
fi
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set hash"
|
||||
exit $rc
|
||||
fi
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN" "$RECORD_ID"
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to set release"
|
||||
exit $rc
|
||||
fi
|
||||
echo "$REGISTRY_APP_LRN set for ApplicationRecord"
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
|
||||
|
||||
# Check if record found for REGISTRY_APP_LRN
|
||||
query_response=$(yarn --silent laconic -c $CONFIG_FILE registry name resolve "$REGISTRY_APP_LRN")
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query name"
|
||||
exit $rc
|
||||
fi
|
||||
APP_RECORD=$(echo $query_response | jq '.[0]')
|
||||
# Check if record found for REGISTRY_APP_CRN
|
||||
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_LRN."
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RECORD_FILE=staging-records/application-deployment-request.yml
|
||||
|
||||
sleep 2
|
||||
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE)
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FATAL: Failed to query deployment request"
|
||||
exit $rc
|
||||
fi
|
||||
DEPLOYMENT_REQUEST_ID=$(echo $deployment_response | jq -r '.id')
|
||||
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
|
||||
@@ -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://github.com/snowball-tools/snowballtools-base"
|
||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
||||
|
||||
@@ -2,7 +2,7 @@ record:
|
||||
type: ApplicationRecord
|
||||
version: 0.0.2
|
||||
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
|
||||
repository: ["https://github.com/snowball-tools/snowballtools-base"]
|
||||
repository: ["https://git.vdb.to/cerc-io/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 @ Mon Jun 24 23:51:48 UTC 2024
|
||||
repository: "https://github.com/snowball-tools/snowballtools-base"
|
||||
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
|
||||
note: Added by Snowball @ Tuesday 21 May 2024 06:17:23 AM UTC
|
||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: 0.0.1
|
||||
repository_ref: 61e3e88a6c9d57e95441059369ee5a46f5c07601
|
||||
repository: ["https://github.com/snowball-tools/snowballtools-base"]
|
||||
version: 0.0.2
|
||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
||||
app_type: webapp
|
||||
name: staging-snowballtools-base-frontend
|
||||
app_version: 0.0.0
|
||||
|
||||
@@ -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}')
|
||||
@@ -22,7 +22,7 @@ REGISTRY_BOND_ID="99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be
|
||||
APP_NAME=deployment-test-app
|
||||
|
||||
# Get latest version from registry and increment application-record version
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "$APP_NAME" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationRecord --all --name "$APP_NAME" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
|
||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||
# Set application-record version if no previous records were found
|
||||
@@ -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,12 +46,12 @@ 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'
|
||||
name: $APP_NAME@$PACKAGE_VERSION
|
||||
application: lrn://snowballtools/applications/$APP_NAME@$PACKAGE_VERSION
|
||||
application: crn://snowballtools/applications/$APP_NAME@$PACKAGE_VERSION
|
||||
dns: deployment-ci-test
|
||||
config:
|
||||
env:
|
||||
@@ -67,31 +67,31 @@ EOF
|
||||
echo "Record files generated successfully."
|
||||
|
||||
# Publish ApplicationRecord
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_LRN="lrn://snowballtools/applications/$APP_NAME"
|
||||
REGISTRY_APP_CRN="crn://snowballtools/applications/$APP_NAME"
|
||||
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN" "$RECORD_ID"
|
||||
echo "$REGISTRY_APP_LRN set for ApplicationRecord"
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
|
||||
|
||||
# Check if record exists for REGISTRY_APP_LRN
|
||||
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE registry name resolve "$REGISTRY_APP_LRN" | jq '.[0]')
|
||||
# Check if record exists for REGISTRY_APP_CRN
|
||||
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_LRN."
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $REQUEST_RECORD_FILE | jq -r '.id')
|
||||
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $REQUEST_RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
|
||||
@@ -102,7 +102,7 @@ MAX_RETRIES=20
|
||||
# Check that a ApplicationDeploymentRecord is published
|
||||
retry_count=0
|
||||
while true; do
|
||||
deployment_records_response=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationDeploymentRecord --all --name "$APP_NAME" request $DEPLOYMENT_REQUEST_ID)
|
||||
deployment_records_response=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationDeploymentRecord --all --name "$APP_NAME" request $DEPLOYMENT_REQUEST_ID)
|
||||
len_deployment_records=$(echo $deployment_records_response | jq 'length')
|
||||
|
||||
# Check if number of records returned is 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"
|
||||
@@ -140,7 +140,7 @@ fetched_url=$(echo $deployment_records_response | jq -r '.[0].attributes.url')
|
||||
|
||||
retry_count=0
|
||||
max_retries=10
|
||||
retry_interval=10
|
||||
retry_interval=5
|
||||
while true; do
|
||||
url_response=$(curl -s -o /dev/null -I -w "%{http_code}" $fetched_url)
|
||||
if [ "$url_response" = "200" ]; then
|
||||
@@ -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
|
||||
@@ -170,14 +170,14 @@ record:
|
||||
EOF
|
||||
|
||||
sleep 2
|
||||
REMOVAL_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
|
||||
REMOVAL_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRemovalRequest published"
|
||||
echo $REMOVAL_REQUEST_ID
|
||||
|
||||
# Check that an ApplicationDeploymentRemovalRecord is published
|
||||
retry_count=0
|
||||
while true; do
|
||||
removal_records_response=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
|
||||
removal_records_response=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
|
||||
len_removal_records=$(echo $removal_records_response | jq 'length')
|
||||
|
||||
# Check if number of records returned is 0
|
||||
@@ -190,7 +190,7 @@ while true; do
|
||||
else
|
||||
echo "ApplicationDeploymentRemovalRecord not found, retrying in $RETRY_INTERVAL sec..."
|
||||
sleep $RETRY_INTERVAL
|
||||
retry_count=$((retry_count + 1))
|
||||
retry_count=$((retry_count+1))
|
||||
fi
|
||||
else
|
||||
echo "ApplicationDeploymentRemovalRecord found"
|
||||
@@ -217,7 +217,7 @@ while true; do
|
||||
else
|
||||
echo "Deployment URL $fetched_url is still active, received code $url_response, retrying in $retry_interval sec..."
|
||||
sleep $retry_interval
|
||||
retry_count=$((retry_count + 1))
|
||||
retry_count=$((retry_count+1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -14,4 +14,4 @@ VITE_BUGSNAG_API_KEY=
|
||||
|
||||
VITE_PASSKEY_WALLET_RPID=
|
||||
VITE_TURNKEY_API_BASE_URL=
|
||||
VITE_TURNKEY_ORGANIZATION_ID=
|
||||
VITE_TURNKEY_ORGANIZATION_ID=
|
||||
@@ -1 +0,0 @@
|
||||
dist/
|
||||
@@ -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
@@ -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,
|
||||
|
||||
### 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
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
"@bugsnag/browser-performance": "^2.4.1",
|
||||
"@bugsnag/js": "^7.22.7",
|
||||
"@bugsnag/plugin-react": "^7.22.7",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.0.19",
|
||||
"@fontsource/inter": "^5.0.16",
|
||||
"@mui/material": "^6.1.3",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
@@ -65,6 +62,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropdown": "^1.11.0",
|
||||
"react-hook-form": "^7.49.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-oauth-popup": "^1.0.5",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-timer-hook": "^3.0.7",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Input, InputProps } from './shared/Input';
|
||||
const SearchBar: React.ForwardRefRenderFunction<
|
||||
HTMLInputElement,
|
||||
InputProps & RefAttributes<HTMLInputElement>
|
||||
> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => {
|
||||
> = ({ value, onChange, placeholder = 'Search', ...props }) => {
|
||||
return (
|
||||
<div className="relative flex w-full">
|
||||
<Input
|
||||
@@ -18,7 +18,6 @@ const SearchBar: React.ForwardRefRenderFunction<
|
||||
appearance="borderless"
|
||||
className="w-full lg:w-[459px]"
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 },
|
||||
|
||||
+37
-52
@@ -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
|
||||
{...getItemProps({ item, index })}
|
||||
key={item.id}
|
||||
item={item}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
: inputValue && <ProjectSearchBarEmpty />}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
import { AuctionData } from 'gql-client';
|
||||
|
||||
import {
|
||||
ArrowRightCircleFilledIcon,
|
||||
LoadingIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { Heading } from '../../shared/Heading';
|
||||
import { Button } from '../../shared/Button';
|
||||
import { Select, SelectOption } from 'components/shared/Select';
|
||||
import { Input } from 'components/shared/Input';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||
|
||||
type ConfigureFormValues = {
|
||||
option: string;
|
||||
lrn?: string;
|
||||
numProviders?: number;
|
||||
maxPrice?: string;
|
||||
};
|
||||
|
||||
const Configure = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const templateId = searchParams.get('templateId');
|
||||
const location = useLocation();
|
||||
const { templateOwner, templateRepo, owner, name, isPrivate, orgSlug, repository } = location.state || {};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { toast, dismiss } = useToast();
|
||||
const client = useGQLClient();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { handleSubmit, control, watch } = useForm<ConfigureFormValues>({
|
||||
defaultValues: { option: 'LRN' },
|
||||
});
|
||||
|
||||
const selectedOption = watch('option');
|
||||
|
||||
const isTabletView = useMediaQuery('(min-width: 720px)'); // md:
|
||||
const buttonSize = isTabletView ? { size: 'lg' as const } : {};
|
||||
|
||||
const onSubmit: SubmitHandler<ConfigureFormValues> = useCallback(
|
||||
async (data) => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
let lrn: string | undefined;
|
||||
let auctionData: AuctionData | undefined;
|
||||
|
||||
if (data.option === 'LRN') {
|
||||
lrn = data.lrn;
|
||||
} else if (data.option === 'Auction') {
|
||||
auctionData = {
|
||||
numProviders: Number(data.numProviders!),
|
||||
maxPrice: (data.maxPrice!).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
if (templateId) {
|
||||
// Template-based project creation
|
||||
const projectData: any = {
|
||||
templateOwner,
|
||||
templateRepo,
|
||||
owner,
|
||||
name,
|
||||
isPrivate,
|
||||
};
|
||||
|
||||
const { addProjectFromTemplate } = await client.addProjectFromTemplate(
|
||||
orgSlug,
|
||||
projectData,
|
||||
lrn,
|
||||
auctionData
|
||||
);
|
||||
|
||||
data.option === 'Auction'
|
||||
? navigate(
|
||||
`/${orgSlug}/projects/create/success/${addProjectFromTemplate.id}`,
|
||||
{ state: { isAuction: true } }
|
||||
)
|
||||
: navigate(
|
||||
`/${orgSlug}/projects/create/template/deploy?projectId=${addProjectFromTemplate.id}&templateId=${templateId}`
|
||||
);
|
||||
} else {
|
||||
const { addProject } = await client.addProject(
|
||||
orgSlug,
|
||||
{
|
||||
name: repository.fullName,
|
||||
prodBranch: repository.defaultBranch,
|
||||
repository: repository.fullName,
|
||||
template: 'webapp',
|
||||
},
|
||||
lrn,
|
||||
auctionData
|
||||
);
|
||||
|
||||
data.option === 'Auction'
|
||||
? navigate(
|
||||
`/${orgSlug}/projects/create/success/${addProject.id}`,
|
||||
{ state: { isAuction: true } }
|
||||
)
|
||||
: navigate(
|
||||
`/${orgSlug}/projects/create/deploy?projectId=${addProject.id}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating project:', error);
|
||||
toast({
|
||||
id: 'error-creating-project',
|
||||
title: 'Error creating project',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[client, isPrivate, templateId, navigate, dismiss, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-7">
|
||||
<div className="flex justify-between">
|
||||
<div className="space-y-1.5">
|
||||
<Heading as="h4" className="md:text-lg font-medium">
|
||||
Configure deployment
|
||||
</Heading>
|
||||
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||
The app can be deployed by setting the deployer LRN for a single
|
||||
deployment or by creating a deployer auction for multiple
|
||||
deployments
|
||||
</Heading>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col gap-4 lg:gap-7 w-full">
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<Controller
|
||||
name="option"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Select
|
||||
label="Configuration Options"
|
||||
value={
|
||||
{
|
||||
value: value || 'LRN',
|
||||
label: value === 'Auction' ? 'Create Auction' : 'Deployer LRN',
|
||||
} as SelectOption
|
||||
}
|
||||
onChange={(value) => onChange((value as SelectOption).value)}
|
||||
options={[
|
||||
{ value: 'LRN', label: 'Deployer LRN' },
|
||||
{ value: 'Auction', label: 'Create Auction' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedOption === 'LRN' && (
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||
The app will be deployed by the configured deployer
|
||||
</Heading>
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Enter LRN for deployer
|
||||
</span>
|
||||
<Controller
|
||||
name="lrn"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedOption === 'Auction' && (
|
||||
<>
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<Heading as="h5" className="text-sm font-sans text-elements-low-em">
|
||||
Set the number of deployers and maximum price for each deployment
|
||||
</Heading>
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Number of Deployers
|
||||
</span>
|
||||
<Controller
|
||||
name="numProviders"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input type="number" value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-start gap-3">
|
||||
<span className="text-sm text-elements-high-em">
|
||||
Maximum Price (alnt)
|
||||
</span>
|
||||
<Controller
|
||||
name="maxPrice"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input type="number" value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Button
|
||||
{...buttonSize}
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
rightIcon={
|
||||
isLoading ? (
|
||||
<LoadingIcon className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircleFilledIcon />
|
||||
)
|
||||
}
|
||||
>
|
||||
{isLoading ? 'Deploying repo' : 'Deploy repo'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Configure;
|
||||
@@ -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;
|
||||
|
||||
+29
-12
@@ -38,19 +38,36 @@ export const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
navigate(`configure`,
|
||||
{
|
||||
state: {
|
||||
repository: {
|
||||
owner: repository.owner?.login,
|
||||
name: repository.name,
|
||||
defaultBranch: repository.default_branch,
|
||||
fullName: repository.full_name,
|
||||
},
|
||||
orgSlug,
|
||||
},
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const { addProject } = await client.addProject(orgSlug, {
|
||||
name: `${repository.owner?.login}-${repository.name}`,
|
||||
prodBranch: repository.default_branch as string,
|
||||
repository: repository.full_name,
|
||||
// TODO: Compute template from repo
|
||||
template: 'webapp',
|
||||
});
|
||||
if (addProject) {
|
||||
navigate(`import?projectId=${addProject.id}`);
|
||||
} else {
|
||||
toast({
|
||||
id: 'failed-to-create-project',
|
||||
title: 'Failed to create project',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error((error as Error).message);
|
||||
toast({
|
||||
id: 'failed-to-create-project',
|
||||
title: 'Failed to create project',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [client, repository, orgSlug, setIsLoading, navigate, toast]);
|
||||
|
||||
return (
|
||||
|
||||
+37
-27
@@ -1,8 +1,13 @@
|
||||
import { CopyBlock, atomOneLight } from 'react-code-blocks';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Modal } from 'components/shared/Modal';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
interface AssignDomainProps {
|
||||
open: boolean;
|
||||
@@ -11,31 +16,36 @@ interface AssignDomainProps {
|
||||
|
||||
const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
|
||||
return (
|
||||
<Modal open={open} onOpenChange={handleOpen}>
|
||||
<Modal.Content>
|
||||
<Modal.Header>Assign Domain</Modal.Header>
|
||||
<Modal.Body>
|
||||
In order to assign a domain to your production deployments, configure
|
||||
it in the{' '}
|
||||
{/* TODO: Fix selection of project settings tab on navigation to domains */}
|
||||
<Link to="../settings/domains" className="text-light-blue-800 inline">
|
||||
project settings{' '}
|
||||
</Link>
|
||||
(recommended). If you want to assign to this specific deployment,
|
||||
however, you can do so using our command-line interface:
|
||||
{/* https://github.com/rajinwonderland/react-code-blocks/issues/138 */}
|
||||
<CopyBlock
|
||||
text="snowball alias <deployment> <domain>"
|
||||
language=""
|
||||
showLineNumbers={false}
|
||||
theme={atomOneLight}
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer className="flex justify-start">
|
||||
<Button onClick={handleOpen}>Okay</Button>
|
||||
</Modal.Footer>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
<Dialog open={open} handler={handleOpen}>
|
||||
<DialogHeader>Assign Domain</DialogHeader>
|
||||
<DialogBody>
|
||||
In order to assign a domain to your production deployments, configure it
|
||||
in the{' '}
|
||||
{/* TODO: Fix selection of project settings tab on navigation to domains */}
|
||||
<Link to="../settings/domains" className="text-light-blue-800 inline">
|
||||
project settings{' '}
|
||||
</Link>
|
||||
(recommended). If you want to assign to this specific deployment,
|
||||
however, you can do so using our command-line interface:
|
||||
{/* https://github.com/rajinwonderland/react-code-blocks/issues/138 */}
|
||||
<CopyBlock
|
||||
text="snowball alias <deployment> <domain>"
|
||||
language=""
|
||||
showLineNumbers={false}
|
||||
theme={atomOneLight}
|
||||
/>
|
||||
</DialogBody>
|
||||
<DialogFooter className="flex justify-start">
|
||||
<Button
|
||||
className="rounded-3xl"
|
||||
variant="gradient"
|
||||
color="blue"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<span>Okay</span>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+2
-7
@@ -83,7 +83,7 @@ const DeploymentDetailsCard = ({
|
||||
|
||||
return (
|
||||
<div className="flex md:flex-row flex-col gap-6 py-4 px-3 pb-6 mb-2 last:mb-0 last:pb-4 border-b border-border-separator last:border-b-transparent relative">
|
||||
<div className="flex-1 flex justify-between w-full md:max-w-[30%] lg:max-w-[33%]">
|
||||
<div className="flex-1 flex justify-between w-full md:max-w-[25%] lg:max-w-[28%]">
|
||||
<div className="flex-1 w-full space-y-2 max-w-[90%] sm:max-w-full">
|
||||
{/* DEPLOYMENT URL */}
|
||||
{deployment.url && (
|
||||
@@ -96,12 +96,7 @@ const DeploymentDetailsCard = ({
|
||||
</OverflownText>
|
||||
</Heading>
|
||||
)}
|
||||
{deployment.deployerLrn && (
|
||||
<span className="text-sm text-elements-low-em tracking-tight block mt-2">
|
||||
Deployer LRN: {deployment.deployerLrn}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm text-elements-low-em tracking-tight block">
|
||||
<span className="text-sm text-elements-low-em tracking-tight">
|
||||
{deployment.environment === Environment.Production
|
||||
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
|
||||
: 'Preview'}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import { useState, ComponentPropsWithRef } from 'react';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuHandler,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { Deployment, Domain, Environment, Project } from 'gql-client';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
@@ -18,11 +11,17 @@ import {
|
||||
UndoIcon,
|
||||
CrossCircleIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import {
|
||||
Menu,
|
||||
MenuHandler,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
import { ComponentPropsWithRef } from 'react';
|
||||
import AssignDomainDialog from './AssignDomainDialog';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { cn } from 'utils/classnames';
|
||||
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
|
||||
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
|
||||
deployment: Deployment;
|
||||
@@ -42,7 +41,6 @@ export const DeploymentMenu = ({
|
||||
...props
|
||||
}: DeploymentMenuProps) => {
|
||||
const client = useGQLClient();
|
||||
const { toast, dismiss } = useToast();
|
||||
|
||||
const [changeToProduction, setChangeToProduction] = useState(false);
|
||||
const [redeployToProduction, setRedeployToProduction] = useState(false);
|
||||
@@ -53,19 +51,9 @@ export const DeploymentMenu = ({
|
||||
const isUpdated = await client.updateDeploymentToProd(deployment.id);
|
||||
if (isUpdated) {
|
||||
await onUpdate();
|
||||
toast({
|
||||
id: 'deployment_changed_to_production',
|
||||
title: 'Deployment changed to production',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.success('Deployment changed to production');
|
||||
} else {
|
||||
toast({
|
||||
id: 'deployment_not_changed_to_production',
|
||||
title: 'Error changing deployment to production',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Unable to change deployment to production');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,19 +61,9 @@ export const DeploymentMenu = ({
|
||||
const isRedeployed = await client.redeployToProd(deployment.id);
|
||||
if (isRedeployed) {
|
||||
await onUpdate();
|
||||
toast({
|
||||
id: 'redeployed_to_production',
|
||||
title: 'Redeployed to production',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.success('Redeployed to production');
|
||||
} else {
|
||||
toast({
|
||||
id: 'redeployed_to_production_failed',
|
||||
title: 'Error redeploying to production',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Unable to redeploy to production');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,19 +74,9 @@ export const DeploymentMenu = ({
|
||||
);
|
||||
if (isRollbacked) {
|
||||
await onUpdate();
|
||||
toast({
|
||||
id: 'deployment_rolled_back',
|
||||
title: 'Deployment rolled back',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.success('Deployment rolled back');
|
||||
} else {
|
||||
toast({
|
||||
id: 'deployment_rollback_failed',
|
||||
title: 'Error rolling back deployment',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Unable to rollback deployment');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,19 +84,9 @@ export const DeploymentMenu = ({
|
||||
const isDeleted = await client.deleteDeployment(deployment.id);
|
||||
if (isDeleted) {
|
||||
await onUpdate();
|
||||
toast({
|
||||
id: 'deployment_deleted',
|
||||
title: 'Deployment deleted',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.success('Deleted deployment');
|
||||
} else {
|
||||
toast({
|
||||
id: 'deployment_not_deleted',
|
||||
title: 'Error deleting deployment',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Unable to delete deployment');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Auction, Project } from 'gql-client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
|
||||
import { CheckRoundFilledIcon, LoadingIcon } from 'components/shared/CustomIcon';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { Button, Heading, Tag } from 'components/shared';
|
||||
|
||||
const WAIT_DURATION = 5000;
|
||||
|
||||
export const AuctionCard = ({ project }: { project: Project }) => {
|
||||
const [auctionStatus, setAuctionStatus] = useState<string>('');
|
||||
const [deployerLrns, setDeployerLrns] = useState<string[]>([]);
|
||||
const [auctionDetails, setAuctionDetails] = useState<Auction | null>(null);
|
||||
const [openDialog, setOpenDialog] = useState<boolean>(false);
|
||||
const client = useGQLClient();
|
||||
|
||||
const getIconByAuctionStatus = (status: string) =>
|
||||
status === 'completed' ? <CheckRoundFilledIcon /> : <LoadingIcon className="animate-spin" />;
|
||||
|
||||
const checkAuctionStatus = useCallback(async () => {
|
||||
const result = await client.getAuctionData(project.auctionId);
|
||||
setAuctionStatus(result.status);
|
||||
setAuctionDetails(result);
|
||||
setDeployerLrns(project.deployerLrns);
|
||||
}, [client, project.auctionId, project.deployerLrns]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUpdatedProject = async () => {
|
||||
if (auctionStatus === 'completed') {
|
||||
// Wait for 5 secs since the project is not immediately updated with deployer LRNs
|
||||
await new Promise((resolve) => setTimeout(resolve, WAIT_DURATION));
|
||||
|
||||
const updatedProject = await client.getProject(project.id);
|
||||
setDeployerLrns(updatedProject.project!.deployerLrns || []);
|
||||
}
|
||||
};
|
||||
|
||||
if (auctionStatus !== 'completed') {
|
||||
const intervalId = setInterval(checkAuctionStatus, WAIT_DURATION);
|
||||
checkAuctionStatus();
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
} else {
|
||||
fetchUpdatedProject();
|
||||
}
|
||||
}, [auctionStatus, checkAuctionStatus, client]);
|
||||
|
||||
const renderAuctionStatus = useCallback(
|
||||
() => (
|
||||
<Tag
|
||||
leftIcon={getIconByAuctionStatus(auctionStatus)}
|
||||
size="xs"
|
||||
type={auctionStatus === 'completed' ? 'positive' : 'emphasized'}
|
||||
>
|
||||
{auctionStatus.toUpperCase()}
|
||||
</Tag>
|
||||
),
|
||||
[auctionStatus]
|
||||
);
|
||||
|
||||
const handleOpenDialog = () => setOpenDialog(true);
|
||||
const handleCloseDialog = () => setOpenDialog(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-3 gap-2 rounded-xl border border-gray-200 transition-colors hover:bg-base-bg-alternate flex flex-col mt-8">
|
||||
<div className="flex justify-between items-center">
|
||||
<Heading className="text-lg leading-6 font-medium">Auction details</Heading>
|
||||
<Button onClick={handleOpenDialog} variant="tertiary" size="sm">
|
||||
View details
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center mt-1">
|
||||
<span className="text-elements-high-em text-sm font-medium tracking-tight">Auction Status</span>
|
||||
<div className="ml-2">{renderAuctionStatus()}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
<span className="text-elements-high-em text-sm font-medium tracking-tight">Auction Id</span>
|
||||
<span className="text-elements-mid-em text-sm text-right">
|
||||
{project.auctionId}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{deployerLrns?.length > 0 && (
|
||||
<div className="mt-3">
|
||||
<span className="text-elements-high-em text-sm font-medium tracking-tight">Deployer LRNs</span>
|
||||
{deployerLrns.map((lrn, index) => (
|
||||
<p key={index} className="text-elements-mid-em text-sm">
|
||||
{'\u2022'} {lrn}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Dialog open={openDialog} onClose={handleCloseDialog} fullWidth maxWidth="md">
|
||||
<DialogTitle>Auction Details</DialogTitle>
|
||||
<DialogContent>
|
||||
{auctionDetails && <pre>{JSON.stringify(auctionDetails, null, 2)}</pre>}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCloseDialog}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AddProjectMemberInput, Permission } from 'gql-client';
|
||||
|
||||
import { Typography } from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { Modal } from 'components/shared/Modal';
|
||||
import { Input } from 'components/shared/Input';
|
||||
import { Select, SelectOption } from 'components/shared/Select';
|
||||
import { AddProjectMemberInput, Permission } from 'gql-client';
|
||||
import { Checkbox } from 'components/shared/Checkbox';
|
||||
|
||||
interface AddMemberDialogProp {
|
||||
open: boolean;
|
||||
@@ -17,30 +17,18 @@ interface AddMemberDialogProp {
|
||||
|
||||
interface formData {
|
||||
emailAddress: string;
|
||||
canEdit: boolean;
|
||||
permissions: {
|
||||
view: boolean;
|
||||
edit: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const permissionViewOptions: SelectOption = {
|
||||
value: Permission.View,
|
||||
label: Permission.View,
|
||||
};
|
||||
const permissionEditOptions: SelectOption = {
|
||||
value: Permission.Edit,
|
||||
label: Permission.Edit,
|
||||
};
|
||||
const permissionsDropdownOptions: SelectOption[] = [
|
||||
permissionViewOptions,
|
||||
permissionEditOptions,
|
||||
];
|
||||
|
||||
const AddMemberDialog = ({
|
||||
open,
|
||||
handleOpen,
|
||||
handleAddMember,
|
||||
}: AddMemberDialogProp) => {
|
||||
const {
|
||||
watch,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
@@ -48,7 +36,10 @@ const AddMemberDialog = ({
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
emailAddress: '',
|
||||
canEdit: false,
|
||||
permissions: {
|
||||
view: true,
|
||||
edit: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -56,7 +47,11 @@ const AddMemberDialog = ({
|
||||
reset();
|
||||
handleOpen();
|
||||
|
||||
const permissions = [data.canEdit ? Permission.Edit : Permission.View];
|
||||
const permissions = Object.entries(data.permissions)
|
||||
.filter(([, value]) => value)
|
||||
.map(
|
||||
([key]) => key.charAt(0).toUpperCase() + key.slice(1),
|
||||
) as Permission[];
|
||||
|
||||
await handleAddMember({ email: data.emailAddress, permissions });
|
||||
}, []);
|
||||
@@ -77,19 +72,19 @@ const AddMemberDialog = ({
|
||||
required: 'email field cannot be empty',
|
||||
})}
|
||||
/>
|
||||
<Select
|
||||
label="Permissions"
|
||||
description="You can change this later if required."
|
||||
options={permissionsDropdownOptions}
|
||||
value={
|
||||
watch('canEdit') ? permissionEditOptions : permissionViewOptions
|
||||
}
|
||||
onChange={(value) =>
|
||||
setValue(
|
||||
'canEdit',
|
||||
(value as SelectOption)!.value === Permission.Edit,
|
||||
)
|
||||
}
|
||||
<Typography variant="small">Permissions</Typography>
|
||||
<Typography variant="small">
|
||||
You can change this later if required.
|
||||
</Typography>
|
||||
<Checkbox
|
||||
label={Permission.View}
|
||||
{...register(`permissions.view`)}
|
||||
color="blue"
|
||||
/>
|
||||
<Checkbox
|
||||
label={Permission.Edit}
|
||||
{...register(`permissions.edit`)}
|
||||
color="blue"
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
|
||||
@@ -60,8 +60,8 @@ const DeleteProjectDialog = ({
|
||||
<Modal.Body>
|
||||
<Input
|
||||
label={
|
||||
"Deleting your project is irreversible. Enter your project's name " + '"' +
|
||||
project.name + '"' +
|
||||
"Deleting your project is irreversible. Enter your project's name " +
|
||||
project.name +
|
||||
' below to confirm you want to permanently delete it:'
|
||||
}
|
||||
id="input"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
useMemo,
|
||||
ComponentPropsWithoutRef,
|
||||
} from 'react';
|
||||
import { FieldValues, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { WarningIcon } from 'components/shared/CustomIcon';
|
||||
import { cloneIcon } from 'utils/cloneIcon';
|
||||
@@ -11,7 +12,7 @@ import { cn } from 'utils/classnames';
|
||||
|
||||
import { InputTheme, inputTheme } from './Input.theme';
|
||||
|
||||
export interface InputProps
|
||||
export interface InputProps<T extends FieldValues = FieldValues>
|
||||
extends InputTheme,
|
||||
Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
|
||||
label?: string;
|
||||
@@ -19,6 +20,9 @@ export interface InputProps
|
||||
leftIcon?: ReactNode;
|
||||
rightIcon?: ReactNode;
|
||||
helperText?: string;
|
||||
|
||||
// react-hook-form optional register
|
||||
register?: ReturnType<UseFormRegister<T>>;
|
||||
}
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
@@ -30,6 +34,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
helperText,
|
||||
register,
|
||||
size,
|
||||
state,
|
||||
appearance,
|
||||
@@ -102,11 +107,12 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
<div className={containerCls({ class: className })}>
|
||||
{leftIcon && renderLeftIcon}
|
||||
<input
|
||||
{...(register ? register : {})}
|
||||
className={cn(inputCls(), {
|
||||
'pl-10': leftIcon,
|
||||
})}
|
||||
ref={ref}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
{rightIcon && renderRightIcon}
|
||||
</div>
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -103,12 +103,10 @@ export const UserSelect = ({ options, value }: UserSelectProps) => {
|
||||
<div className={theme.container()}>
|
||||
{/* Input */}
|
||||
<div
|
||||
{...getToggleButtonProps(
|
||||
{
|
||||
ref: inputWrapperRef,
|
||||
},
|
||||
{ suppressRefError: true },
|
||||
)}
|
||||
{...getToggleButtonProps({
|
||||
ref: inputWrapperRef,
|
||||
suppressRefError: true,
|
||||
})}
|
||||
onClick={() => !dropdownOpen && openMenu()}
|
||||
className="cursor-pointer relative py-2 pl-2 pr-4 flex min-w-[200px] w-full items-center justify-between rounded-xl bg-surface-card shadow-sm"
|
||||
>
|
||||
|
||||
@@ -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,27 +0,0 @@
|
||||
export { Avatar } from './Avatar';
|
||||
export { Badge } from './Badge';
|
||||
export { Button } from './Button';
|
||||
export { Calendar } from './Calendar';
|
||||
export { Checkbox } from './Checkbox';
|
||||
export { DatePicker } from './DatePicker';
|
||||
export { DotBorder } from './DotBorder';
|
||||
export { Heading } from './Heading';
|
||||
export { IconWithFrame } from './IconWithFrame';
|
||||
export { InlineNotification } from './InlineNotification';
|
||||
export { Input } from './Input';
|
||||
export { Modal } from './Modal';
|
||||
export { OverflownText } from './OverflownText';
|
||||
export { Radio } from './Radio';
|
||||
export { SegmentedControls } from './SegmentedControls';
|
||||
export { Select } from './Select';
|
||||
export { Sidebar } from './Sidebar';
|
||||
export { Step } from './Steps';
|
||||
export { Switch } from './Switch';
|
||||
export { Table } from './Table';
|
||||
export { Tabs } from './Tabs';
|
||||
export { Tag } from './Tag';
|
||||
export { useToast } from './Toast';
|
||||
export { Tooltip } from './Tooltip';
|
||||
export { UserSelect } from './UserSelect';
|
||||
export { VerifyCodeInput } from './VerifyCodeInput';
|
||||
export { WavyBorder } from './WavyBorder';
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,13 @@ 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';
|
||||
|
||||
console.log(`v-0.0.9`);
|
||||
|
||||
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)} />
|
||||
);
|
||||
};
|
||||
@@ -47,9 +47,7 @@ export const Login = ({ onDone }: Props) => {
|
||||
console.log('Handling google redirect back');
|
||||
try {
|
||||
await google.handleOAuthRedirectBack();
|
||||
// @ts-ignore
|
||||
wallet = await google.getEthersWallet();
|
||||
// @ts-ignore
|
||||
const result = await signInWithEthereum(1, 'login', wallet);
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
turnkeySignin,
|
||||
turnkeySignup,
|
||||
} from 'utils/turnkey-frontend';
|
||||
import { verifyAccessCode } from 'utils/accessCode';
|
||||
|
||||
type Provider = 'google' | 'github' | 'apple' | 'email';
|
||||
|
||||
@@ -46,9 +45,7 @@ export const SignUp = ({ onDone }: Props) => {
|
||||
setProvider('google');
|
||||
try {
|
||||
await google.handleOAuthRedirectBack();
|
||||
// @ts-ignore
|
||||
wallet = await google.getEthersWallet();
|
||||
// @ts-ignore
|
||||
const result = await signInWithEthereum(1, 'signup', wallet);
|
||||
if (result.error) {
|
||||
setError({ type: 'provider', message: result.error });
|
||||
@@ -112,29 +109,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');
|
||||
}}
|
||||
|
||||
@@ -36,7 +36,6 @@ const deployment: Deployment = {
|
||||
url: 'https://deploy1.example.com',
|
||||
environment: Environment.Production,
|
||||
isCurrent: true,
|
||||
deployerLrn: 'lrn://deepstack-test4/deployers/webapp-deployer-api.test4.wireitin.com',
|
||||
status: DeploymentStatus.Ready,
|
||||
createdBy: {
|
||||
id: 'user1',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
|
||||
import { ProjectCard } from 'components/projects/ProjectCard';
|
||||
import { Heading, Badge, Button } from 'components/shared';
|
||||
import { PlusIcon } from 'components/shared/CustomIcon';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { Project } from 'gql-client';
|
||||
import { Button } from 'components/shared/Button';
|
||||
|
||||
import { PlusIcon } from 'components/shared/CustomIcon';
|
||||
import { ProjectCard } from 'components/projects/ProjectCard';
|
||||
import { Heading } from 'components/shared/Heading';
|
||||
import { Badge } from 'components/shared/Badge';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
|
||||
const Projects = () => {
|
||||
const client = useGQLClient();
|
||||
@@ -26,7 +28,6 @@ const Projects = () => {
|
||||
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">
|
||||
|
||||
@@ -31,11 +31,6 @@ const CreateWithTemplate = () => {
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
route: `/${orgSlug}/projects/create/template/configure`,
|
||||
label: 'Configure',
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
route: `/${orgSlug}/projects/create/template/deploy`,
|
||||
label: 'Deploy',
|
||||
},
|
||||
|
||||
@@ -2,8 +2,7 @@ import NewProject from './index';
|
||||
import CreateWithTemplate from './Template';
|
||||
import { templateRoutes } from './template/routes';
|
||||
import Id from './success/Id';
|
||||
import Configure from 'components/projects/create/Configure';
|
||||
import Deploy from 'components/projects/create/Deploy';
|
||||
import Import from './Import';
|
||||
|
||||
export const createProjectRoutes = [
|
||||
{
|
||||
@@ -20,11 +19,7 @@ export const createProjectRoutes = [
|
||||
element: <Id />,
|
||||
},
|
||||
{
|
||||
path: 'configure',
|
||||
element: <Configure />,
|
||||
},
|
||||
{
|
||||
path: 'deploy',
|
||||
element: <Deploy />,
|
||||
path: 'import',
|
||||
element: <Import />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,125 +1,108 @@
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import Lottie from 'lottie-react';
|
||||
|
||||
import { Badge } from 'components/shared/Badge';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
ArrowLeftCircleFilledIcon,
|
||||
LinkChainIcon,
|
||||
QuestionMarkRoundFilledIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
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 location = useLocation();
|
||||
const { isAuction } = location.state || {};
|
||||
|
||||
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">
|
||||
{isAuction? 'Auction created successfully.' : 'Project deployment created successfully.'}
|
||||
</Heading>
|
||||
</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="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} />
|
||||
{/* 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>
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import ConfigureComponent from '../../../../../components/projects/create/Configure';
|
||||
|
||||
const Configure = () => {
|
||||
return <ConfigureComponent />;
|
||||
};
|
||||
|
||||
export default Configure;
|
||||
@@ -6,14 +6,12 @@ import { useMediaQuery } from 'usehooks-ts';
|
||||
import { RequestError } from 'octokit';
|
||||
|
||||
import { useOctokit } from '../../../../../context/OctokitContext';
|
||||
import { useGQLClient } from '../../../../../context/GQLClientContext';
|
||||
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';
|
||||
@@ -30,6 +28,7 @@ type SubmitRepoValues = {
|
||||
const CreateRepo = () => {
|
||||
const { octokit, isAuth } = useOctokit();
|
||||
const { template } = useOutletContext<{ template: Template }>();
|
||||
const client = useGQLClient();
|
||||
|
||||
const { orgSlug } = useParams();
|
||||
const { toast, dismiss } = useToast();
|
||||
@@ -51,21 +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,
|
||||
});
|
||||
|
||||
navigate(
|
||||
`configure?templateId=${template.id}`,
|
||||
{
|
||||
state: {
|
||||
templateOwner: owner,
|
||||
templateRepo: repo,
|
||||
owner: data.account,
|
||||
name: data.repoName,
|
||||
isPrivate: false,
|
||||
orgSlug
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!gitRepo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -192,15 +206,9 @@ const CreateRepo = () => {
|
||||
{...buttonSize}
|
||||
type="submit"
|
||||
disabled={!Boolean(template.repoFullName) || isLoading}
|
||||
rightIcon={
|
||||
isLoading ? (
|
||||
<LoadingIcon className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircleFilledIcon />
|
||||
)
|
||||
}
|
||||
rightIcon={<ArrowRightCircleFilledIcon />}
|
||||
>
|
||||
Next
|
||||
Deploy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import CreateRepo from './index';
|
||||
import Configure from './Configure';
|
||||
import Deploy from './Deploy';
|
||||
|
||||
export const templateRoutes = [
|
||||
@@ -7,10 +6,6 @@ export const templateRoutes = [
|
||||
index: true,
|
||||
element: <CreateRepo />,
|
||||
},
|
||||
{
|
||||
path: 'configure',
|
||||
element: <Configure />,
|
||||
},
|
||||
{
|
||||
path: 'deploy',
|
||||
element: <Deploy />,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Domain, DomainStatus } from 'gql-client';
|
||||
import { Link, useNavigate, useOutletContext } from 'react-router-dom';
|
||||
import { RequestError } from 'octokit';
|
||||
|
||||
import { useOctokit } from 'context/OctokitContext';
|
||||
import { useOctokit } from '../../../../context/OctokitContext';
|
||||
import { GitCommitWithBranch, OutletContextType } from '../../../../types';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { Button, Heading, Avatar, Tag } from 'components/shared';
|
||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { Heading } from 'components/shared/Heading';
|
||||
import { Avatar } from 'components/shared/Avatar';
|
||||
import { getInitials } from 'utils/geInitials';
|
||||
import {
|
||||
BranchStrokeIcon,
|
||||
@@ -15,13 +18,12 @@ import {
|
||||
GithubStrokeIcon,
|
||||
GlobeIcon,
|
||||
LinkIcon,
|
||||
CalendarDaysIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { Tag } from 'components/shared/Tag';
|
||||
import { Activity } from 'components/projects/project/overview/Activity';
|
||||
import { OverviewInfo } from 'components/projects/project/overview/OverviewInfo';
|
||||
import { CalendarDaysIcon } from 'components/shared/CustomIcon/CalendarDaysIcon';
|
||||
import { relativeTimeMs } from 'utils/time';
|
||||
import { Domain, DomainStatus } from 'gql-client';
|
||||
import { AuctionCard } from 'components/projects/project/overview/Activity/AuctionCard';
|
||||
|
||||
const COMMITS_PER_PAGE = 4;
|
||||
|
||||
@@ -129,15 +131,9 @@ const OverviewTabPanel = () => {
|
||||
<Heading className="text-lg leading-6 font-medium truncate">
|
||||
{project.name}
|
||||
</Heading>
|
||||
{project.baseDomains && project.baseDomains.length > 0 && project.baseDomains.map((baseDomain, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={`https://${project.name}.${baseDomain}`}
|
||||
className="text-sm text-elements-low-em tracking-tight truncate"
|
||||
>
|
||||
{baseDomain}
|
||||
</a>
|
||||
))}
|
||||
<p className="text-sm text-elements-low-em tracking-tight truncate">
|
||||
{project.subDomain}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<OverviewInfo label="Domain" icon={<GlobeIcon />}>
|
||||
@@ -209,7 +205,6 @@ const OverviewTabPanel = () => {
|
||||
No current deployment found.
|
||||
</p>
|
||||
)}
|
||||
{project.auctionId && <AuctionCard project={project}/>}
|
||||
</div>
|
||||
<Activity activities={activities} isLoading={fetchingActivities} />
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ const FIRST_MEMBER_CARD = 0;
|
||||
|
||||
const CollaboratorsTabPanel = () => {
|
||||
const client = useGQLClient();
|
||||
const { toast, dismiss } = useToast();
|
||||
const { toast } = useToast();
|
||||
const { project } = useOutletContext<OutletContextType>();
|
||||
|
||||
const [addmemberDialogOpen, setAddMemberDialogOpen] = useState(false);
|
||||
@@ -39,14 +39,14 @@ const CollaboratorsTabPanel = () => {
|
||||
id: 'member_added',
|
||||
title: 'Member added to project',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
onDismiss() {},
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
id: 'member_not_added',
|
||||
title: 'Invitation not sent',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
onDismiss() {},
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -63,14 +63,14 @@ const CollaboratorsTabPanel = () => {
|
||||
id: 'member_removed',
|
||||
title: 'Member removed from project',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
onDismiss() {},
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
id: 'member_not_removed',
|
||||
title: 'Not able to remove member',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
onDismiss() {},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -86,14 +86,14 @@ const CollaboratorsTabPanel = () => {
|
||||
id: 'member_permission_updated',
|
||||
title: 'Project member permission updated',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
onDismiss() {},
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
id: 'member_permission_not_updated',
|
||||
title: 'Project member permission not updated',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
onDismiss() {},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||
|
||||
import { Collapse } from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
import AddEnvironmentVariableRow from 'components/projects/project/settings/AddEnvironmentVariableRow';
|
||||
import DisplayEnvironmentVariables from 'components/projects/project/settings/DisplayEnvironmentVariables';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { useGQLClient } from '../../../../../context/GQLClientContext';
|
||||
import { EnvironmentVariablesFormValues } from '../../../../../types';
|
||||
import HorizontalLine from 'components/HorizontalLine';
|
||||
import { Heading } from 'components/shared/Heading';
|
||||
@@ -15,13 +17,10 @@ import { Checkbox } from 'components/shared/Checkbox';
|
||||
import { PlusIcon } from 'components/shared/CustomIcon';
|
||||
import { InlineNotification } from 'components/shared/InlineNotification';
|
||||
import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
import { Environment, EnvironmentVariable } from 'gql-client';
|
||||
|
||||
export const EnvironmentVariablesTabPanel = () => {
|
||||
const { id } = useParams();
|
||||
const client = useGQLClient();
|
||||
const { toast, dismiss } = useToast();
|
||||
|
||||
const [environmentVariables, setEnvironmentVariables] = useState<
|
||||
EnvironmentVariable[]
|
||||
@@ -119,25 +118,13 @@ export const EnvironmentVariablesTabPanel = () => {
|
||||
|
||||
fetchEnvironmentVariables(id);
|
||||
|
||||
toast({
|
||||
id:
|
||||
createFormData.variables.length > 1
|
||||
? 'env_variable_added'
|
||||
: 'env_variables_added',
|
||||
title:
|
||||
createFormData.variables.length > 1
|
||||
? `${createFormData.variables.length} variables added`
|
||||
: `Variable added`,
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.success(
|
||||
createFormData.variables.length > 1
|
||||
? `${createFormData.variables.length} variables added`
|
||||
: `Variable added`,
|
||||
);
|
||||
} else {
|
||||
toast({
|
||||
id: 'env_variables_not_added',
|
||||
title: 'Environment variables not added',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Environment variables not added');
|
||||
}
|
||||
},
|
||||
[id, client],
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import toast from 'react-hot-toast';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { useGQLClient } from '../../../../../../../context/GQLClientContext';
|
||||
import { Table } from 'components/shared/Table';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { InlineNotification } from 'components/shared/InlineNotification';
|
||||
import { ArrowRightCircleIcon } from 'components/shared/CustomIcon';
|
||||
import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
|
||||
const Config = () => {
|
||||
const { id, orgSlug } = useParams();
|
||||
@@ -14,26 +14,15 @@ const Config = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const primaryDomainName = searchParams.get('name');
|
||||
const { toast, dismiss } = useToast();
|
||||
|
||||
const handleSubmitDomain = async () => {
|
||||
if (primaryDomainName === null) {
|
||||
toast({
|
||||
id: 'unresolvable_domain_name',
|
||||
title: 'Cannot resolve domain name',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Cannot resolve domain name');
|
||||
return;
|
||||
}
|
||||
|
||||
if (id === undefined) {
|
||||
toast({
|
||||
id: 'domain_cannot_find_project',
|
||||
title: 'Cannot find project',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Cannot find project');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,20 +31,10 @@ const Config = () => {
|
||||
});
|
||||
|
||||
if (isAdded) {
|
||||
toast({
|
||||
id: 'domain_added_successfully',
|
||||
title: 'Domain added successfully',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.success('Domain added successfully');
|
||||
navigate(`/${orgSlug}/projects/${id}/settings/domains`);
|
||||
} else {
|
||||
toast({
|
||||
id: 'generic_error_adding_domain',
|
||||
title: 'Error adding domaint',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
toast.error('Error adding domain');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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',
|
||||
@@ -102,7 +91,6 @@ export const deployment0: Deployment = {
|
||||
domain: domain0,
|
||||
commitMessage: 'Commit Message',
|
||||
createdBy: user,
|
||||
deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
|
||||
};
|
||||
|
||||
export const project: Project = {
|
||||
@@ -120,9 +108,7 @@ export const project: Project = {
|
||||
organization: organization,
|
||||
template: 'Template',
|
||||
members: [member],
|
||||
auctionId: '7553538436710373822151221341b43f577e07b0525d083cc9b2de98890138a1',
|
||||
deployerLrns: ['lrn://deployer.apps.snowballtools.com '],
|
||||
webhooks: ['beepboop'],
|
||||
icon: 'Icon',
|
||||
baseDomains: ['baseDomain'],
|
||||
subDomain: 'SubDomain',
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
@@ -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()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user