Compare commits

...

492 Commits

Author SHA1 Message Date
17640d3133 Copy updates (#38)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m39s
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m47s
Co-authored-by: zramsay <zach@bluecollarcoding.ca>
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Reviewed-on: #38
Co-authored-by: zramsay <zramsay@noreply.git.vdb.to>
Co-committed-by: zramsay <zramsay@noreply.git.vdb.to>
2024-11-14 09:10:19 +00:00
bba0b57bed Keep records directory for deploy-frontend script (#43)
All checks were successful
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m39s
Lint / lint (20.x) (push) Successful in 4m44s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #43
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-11-13 13:56:15 +00:00
c34e66aa93 Integrate wallet IFrame for payments (#42)
Some checks failed
Deploy Snowball frontend / deploy (20.x) (push) Failing after 4m2s
Lint / lint (20.x) (push) Successful in 4m41s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Replace wallet connect with iframe to display wallet in modal for deployment payments

![image](/attachments/b253d833-0730-45b7-8b65-b0af6d24678a)

Co-authored-by: Isha <ishavenikar7@gmail.com>
Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #42
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-11-13 13:32:27 +00:00
b61682dd20 Fix fetching build logs for redeployments (#39)
All checks were successful
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m35s
Lint / lint (20.x) (push) Successful in 4m41s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #39
2024-11-12 15:18:07 +00:00
bcb6ac241b Fix template tab in GitHub connect page (#41)
All checks were successful
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m33s
Lint / lint (20.x) (push) Successful in 4m39s
Part of #40 and [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #41
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-11-12 14:21:10 +00:00
4a9f517d85 Merge pull request 'fix: colors for project template cards' (#37) from zach/ui-fixes into main
All checks were successful
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m36s
Lint / lint (20.x) (push) Successful in 4m42s
Reviewed-on: #37
2024-11-06 19:27:32 +00:00
6d1135645b fix: colors for project template cards
All checks were successful
Lint / lint (20.x) (pull_request) Successful in 4m35s
2024-11-06 14:16:23 -05:00
e5748e768c Fix check boxes in environment variables form (#36)
All checks were successful
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m36s
Lint / lint (20.x) (push) Successful in 4m43s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

![image](/attachments/0d90877d-86c7-4e3a-94c9-3c0d480f8b8c)

Co-authored-by: Isha <ishavenikar7@gmail.com>
Reviewed-on: #36
2024-11-06 13:11:03 +00:00
9158e3a840 Add a CI workflow to deploy frontend (#35)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m43s
Deploy Snowball frontend / deploy (20.x) (push) Successful in 4m31s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: #35
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-11-06 09:20:01 +00:00
8bd1e17fd2 Add template for NextJS app (#34)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m33s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Add hotfix for updating old DB with new org slug
- Add remove-deployment script (for debugging)
- Part of #28
![image](/attachments/ceb0d4d6-4fa7-4914-ad18-c57d9d0f5b95)

Co-authored-by: Isha <ishavenikar7@gmail.com>
Reviewed-on: #34
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-11-06 07:26:53 +00:00
3998b60888 style: repository cards
All checks were successful
Lint / lint (20.x) (pull_request) Successful in 4m39s
Lint / lint (20.x) (push) Successful in 4m41s
2024-11-04 15:16:11 +05:30
c5bace0660 fix: contrast with new elements 2024-11-04 15:16:10 +05:30
a033a8a7b3 Update .gitea/workflows/lint.yaml 2024-11-04 15:15:23 +05:30
zramsay
420080b1af rm mock project 2024-11-04 15:15:23 +05:30
f95a64546d chore: rename to laconic 2024-11-04 15:15:23 +05:30
3214cfc1d4 style: projects and settings 2024-11-04 15:15:23 +05:30
07b21a835e style: rename color 2024-11-04 15:15:23 +05:30
15ba278bbc style: more colors 2024-11-04 15:15:23 +05:30
364c62783d style: first pass at laconic colors 2024-11-04 15:15:23 +05:30
b9573474a8 Upgrade WalletConnect version to 2.16.1 (#27)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m38s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)
- Upgrade WallectConnect to version `2.16.1`
  - Part of #24
- Change GitHub access to `public_repo` and `user`
  - Part of #22

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: #27
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-11-04 08:59:20 +00:00
ea9a56eb65 Display DNS deployment URLs in overview section (#21)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m40s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Disable `Deploy` button in configure step if account and deployer not selected
- Update organization slug
- Only display project if current user is project owner

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: #21
2024-10-30 13:11:04 +00:00
05bd766133 Display project URLs in Overview tab (#20)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m32s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Fix project create not working after failed tx
- Poll for project details for auction details
- Update wallet connect metadata

![image](/attachments/cd0217c9-8a2f-4bc5-ad4c-2654fa92f958)

Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #20
2024-10-29 14:10:01 +00:00
0f18bc978e Pass payment tx hash in deployment request (#19)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m35s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #19
2024-10-29 09:12:39 +00:00
519e318190 Check if repo with same name already exists when creating project (#18)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m37s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

![image](/attachments/6e0efb39-db83-4140-b840-3eca84c3e0f2)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #18
2024-10-28 11:23:22 +00:00
63969ae25a Implement payments for app deployments (#17)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m30s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)
- Implement funtionality to pay for deployments by connecting wallet using `WalletConnect`

![image](/attachments/842e33e8-7de6-4d91-9008-1c67a259b586)

![image](/attachments/94b2fe39-f753-4e99-a8c2-bda4c0b84897)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: #17
2024-10-28 09:46:18 +00:00
b449c299dc Comment out bugsnag code (#16)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m58s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #16
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-10-25 12:40:34 +00:00
2a35ec1cd5 Check deployment status while creating project with single deployer (#15)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m59s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)
- Use deployer API to get status of the deployments

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: #15
Co-authored-by: Nabarun Gogoi <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun Gogoi <nabarun@deepstacksoft.com>
2024-10-25 10:47:04 +00:00
be90fc76c1 Update script to pay webapp deployer before deployment request (#14)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m57s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: #14
2024-10-25 10:01:22 +00:00
3fa60f3cdf Handle account sequence mismatch error (#13)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m56s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Handle failed txs due to `account sequence mismatch` error by creating a wrapper for all tx methods and retry the tx if `account sequence mismatch` error occurs

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #13
2024-10-24 11:38:17 +00:00
3d9aedeb7e List deployer LRNs in deployment configuration step (#11)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m56s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Fix request Id being set to `null` while fetching build logs
- Populate deployer LRNs dropdown with LRNs fetched from registry in configure delpoyment step

![image](/attachments/ff421bdf-6e0b-443e-9dc8-455bde481b4f)

![image](/attachments/87c9bce3-3743-4f4a-a997-a02a3504e61e)

![image](/attachments/dd442fe6-ad30-4723-a2bb-0723ad3eb3c9)

![image](/attachments/37f0da01-671f-4e3a-92e4-b34e25566a0d)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Reviewed-on: #11
2024-10-23 15:36:19 +00:00
096318cf13 Display build logs only when available (#10)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m53s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Reviewed-on: #10
2024-10-22 12:43:20 +00:00
27ef859075 Remove organization switcher from side bar (#9)
All checks were successful
Lint / lint (20.x) (push) Successful in 5m1s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)
- Display DNS URLs in overview tab

Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #9
2024-10-22 10:16:35 +00:00
5152952a45 Display deployment build logs (#8)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m53s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #8
2024-10-22 09:12:59 +00:00
ef26f9b39e Implement functionality to release funds after deployment (#7)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m57s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Implement functionality to release funds after first successful deployment

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #7
2024-10-21 14:25:49 +00:00
d486f44cfe Update UI to take environment variables from user (#6)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m55s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Take environment variables from the user in the `Configure` deployment step

Co-authored-by: Isha Venikar <ishavenikar@Ishas-MacBook-Air.local>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #6
2024-10-21 11:05:35 +00:00
5c9c7575f2 Set user email with ETH address while authenticating (#5)
All checks were successful
Lint / lint (20.x) (push) Successful in 5m5s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #5
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-10-19 13:06:09 +00:00
59a164f3f8 Update frontend deployment script (#3)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m55s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #3
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-10-18 12:53:24 +00:00
bc52b34462 Implement authentication with SIWE (#4)
All checks were successful
Lint / lint (20.x) (push) Successful in 5m23s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Remove LIT authentication

Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Reviewed-on: #4
2024-10-18 12:47:11 +00:00
5aefda1248 Integrate SP auctions for app deployment (#2)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m5s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)
- Add support for configuring web-app deployers by -
  - Configuring deployer LRN (for targeted deployments)
  - Configuring SP auction params for deployment auction (max price and number of providers)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: #2
2024-10-18 12:37:01 +00:00
42bdd21089 Upgrade from laconic-sdk to registry-sdk (#1)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m12s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: #1
2024-10-16 08:43:51 +00:00
Gilbert
e5a00016c1
Merge pull request #234 from snowball-tools/ng-deployment-test
Some checks failed
Lint / lint (20.x) (push) Failing after 20s
Test webapp deployment / test_app_deployment (20.x) (push) Failing after 19s
Increase retry interval for checking deployment URL in CI test
2024-09-18 23:21:33 -05:00
13b912d318 Increase retry interval for checking deployment URL 2024-08-23 11:09:12 +05:30
059863c4b9
Merge pull request #233 from snowball-tools/dboreham-frontend-deploy-from-gh
Deploy frontend directly from github repo
2024-07-05 09:42:05 -06:00
58906844cc Deploy directly from github repo 2024-07-05 09:36:05 -06:00
Vivian Phung
9f0a2ad548
space 2024-07-03 12:41:39 -04:00
Vivian Phung
bd10e2cb35
fix: version temp 2024-06-25 01:34:21 -04:00
Vivian Phung
eb32385cf3
revert fix 2024-06-25 01:14:08 -04:00
Vivian Phung
aebb20b987
update README.md debugging deployer 2024-06-25 01:08:38 -04:00
Vivian Phung
8a2b51952f
fix: temp frontend (#230)
* v.0.0.9 staging

* fix: temp frontend deployment bug
2024-06-25 01:04:03 -04:00
Vivian Phung
94f46f9621
v.0.0.9 staging (#229) 2024-06-24 19:52:51 -04:00
Vivian Phung
e751addcce
Update local.toml.example in packages/backend (#228)
### TL;DR

Added new keys for Google and Turnkey integration in `local.toml.example`

### What changed?

- Updated server session secret to empty string
- Added placeholders for google clientId and clientSecret
- Added placeholders for Turnkey API integration (apiBaseUrl, apiPrivateKey, apiPublicKey, defaultOrganizationId)

### How to test?

1. Pull the latest changes
2. Update `local.toml.example` file with actual values.
3. Run the application locally and ensure all services are working correctly.

### Why make this change?

To integrate new services (Google and Turnkey) into the project setup.

---
2024-06-24 19:42:33 -04:00
Vivian Phung
c01f8fdabf
Refactor: components/shared (#227)
### TL;DR

This pull request consolidates all shared component exports into a new `index.ts` file within the `components/shared` directory. The purpose is to streamline and centralize the exports of shared components for improved maintainability.

### What changed?

- Created a new `index.ts` file in the `components/shared` directory that exports all shared components.
- Updated import statements to use the new centralized `index.ts` file instead of individual component files.

### How to test?

1. Run the frontend application.
2. Navigate through the application to ensure all shared components render correctly without console errors.

### Why make this change?

The motivation behind this change is to organize exports of shared components in a single file. This enhances code maintainability and simplifies the process of importing shared components across the project.

---
2024-06-24 19:36:08 -04:00
Vivian Phung
2b60114dab
refactor: use onToast remove react-hot-toast dep (#226)
### TL;DR

Removed the dependency on `react-hot-toast` in favor of a custom implementation for toast notifications.

### What changed?

1. Removed `react-hot-toast` from `package.json` and `yarn.lock`. 
2. Updated `EnvironmentVariables` and `Config` to use the custom toast notification system.

### Why make this change?

To reduce bundle size and have more control over the toast notification system.

### How to test?

1. Navigate to the Environment Variables settings for a project. Try adding and removing environment variables to see the new toast notifications in action.
2. Navigate to the Domains settings for a project and try adding a new domain to view the custom toast notifications.

---
2024-06-24 19:25:31 -04:00
Vivian Phung
9a1c0e8338
feat(domains): DomainCard and WebhookCard styling start (#225)
### TL;DR

Refactored the `DomainCard`, `EditDomainDialog`, and `WebhookCard` components to improve code readability and enhance UI using new shared components like `Tag`, `Heading`, `Button`, and `CustomIcon`.

### What changed?

- `DomainCard` component:
  - Replaced `Chip` with `Tag` component.
  - Used `Heading`, `Button`, and `CustomIcon` components.
  - Updated refresh icon to show `LoadingIcon` when checking.
- `EditDomainDialog` component:
  - Used `useToast` hook for toast messages.
- `WebhookCard` component:
  - Used `Input`, `Button`, and `CustomIcon` components for better UI.
- Added Storybook stories for the updated components.

### How to test?

1. Go to the project settings page.
2. Verify the `DomainCard` UI updates.
3. Edit a domain and check the toasts.
4. Verify the `WebhookCard` UI and functionality.
5. Run Storybook and inspect the added stories for the components.

### Why make this change?

To improve the consistency and user experience of the project settings UI, and to make the components more maintainable by using shared components.

---
2024-06-24 19:22:20 -04:00
Vivian Phung
1b038476c7
feat: Settings screen for org (#224)
This update introduces a new header section within the `Settings` page. It incorporates a `Heading` component from the shared components library, ensuring consistent styling and structure across the app. The `Settings` page layout now features appropriate padding and flexbox for better alignment and spacing.

---
2024-06-24 18:57:38 -04:00
Vivian Phung
4a78eb13f6
fix: ProjectSearchBarDialog (search small screen) Suggestions once (#223)
Refactor the rendering logic of the suggestion list in `ProjectSearchBarDialog` component. This change simplifies the conditional rendering by restructuring the JSX to be more readable and maintainable. Now, the 'Suggestions' label is rendered once if there are items, and the items are mapped afterward.

---
2024-06-24 18:54:32 -04:00
Vivian Phung
41bcb2e7d0
fix: ProjectSearchBarDialog suppressRefError (#222)
This PR resolves issues with the Project Search Bar component where `getMenuProps` was causing reference errors. By adding `suppressRefError: true` to `getMenuProps` in both `ProjectSearchBar` and `ProjectSearchBarDialog`, the warnings are suppressed.

---
2024-06-24 18:51:31 -04:00
Vivian Phung
f981f1a3f6
fix(ProjectSearchBarDialog): getMenuProps error (#221)
- Replaced `useDebounce` with `useDebounceValue` for better type inference and simplicity
- Added `getMenuProps` to `useCombobox` to support better accessibility and usability
- Minor style tweak to improve `ProjectSearch` header hover effect
- Created Storybook stories for the `ProjectSearchBar` component

---
2024-06-24 18:48:32 -04:00
Vivian Phung
dee84f18cb
feat: dynamic project success page and update links (#220)
### TL;DR

Integrates project data fetching for dynamic subdomain display on the Project Deployment Success page and the OverviewTabPanel.

### What changed?

- Updated `Id.tsx` to fetch project data and dynamically display project's subdomain after deployment.
- Modified `Overview.tsx` to make project's subdomain a clickable link.

### How to test?

1. Deploy a new project and check the deployment success page for correct subdomain display.
2. Open a project's overview tab and click the subdomain link to ensure it navigates correctly.

### Why make this change?

Improves user experience by displaying the actual subdomain and making it clickable, ensuring users can conveniently verify their deployment and access project domain.

---
2024-06-24 18:44:34 -04:00
Vivian Phung
44015d5451
feat(ui): loading spinner on project creation (#219)
### TL;DR
This pull request adds a loading icon to the 'Deploy' button in the project template creation form.

### What changed?
- Imported `LoadingIcon` from `components/shared/CustomIcon`.
- Modified the `Button` component to conditionally display the `LoadingIcon` when `isLoading` is `true`. The icon will animate by spinning.

### How to test?
1. Navigate to the project template creation form.
2. Fill in the required fields.
3. Click the 'Deploy' button.
4. Ensure the loading icon appears and spins when the button is disabled (when `isLoading` is `true`).

### Why make this change?
This change provides visual feedback to users, indicating that their action is being processed, thereby enhancing user experience.

---
2024-06-24 18:41:31 -04:00
Vivian Phung
a684743bd6
feat(template projects): generate git repo on backend (#218)
### TL;DR

- Still cretaes app if user migrates from page

The PR introduces a new `AddProjectFromTemplate` mutation to facilitate project creation using a repository template. This change centralizes the template project creation logic within the backend, improving code maintainability by removing redundant client-side code.

### What changed?
- Added `AddProjectFromTemplate` input type in `schema.gql` and corresponding TypeScript interfaces.
- Implemented `addProjectFromTemplate` resolver with error handling and Octokit integration for repository creation.
- Updated `service.ts` to include the new `addProjectFromTemplate` method.
- Created new GraphQL `Mutation` for `addProjectFromTemplate` in the GraphQL schema.
- Adjusted the client-side GQLClient to support the new mutation.
- Modified frontend to utilize the new backend mutation for project creation from a template.

### How to test?
1. Ensure your backend server is running.
2. Use a GraphQL client like Postman to call the `addProjectFromTemplate` mutation with appropriate input.
3. Verify that the new project is created using the specified template, and appropriate error messages are returned for failures.
4. Check the frontend flow for creating a project from a template to ensure it is working correctly.

### Why make this change?
This change enhances code maintainability by centralizing template project creation logic within the backend, thereby reducing redundancy and potential inconsistencies in client-side implementations.

---
2024-06-24 18:38:01 -04:00
Vivian Phung
b12c95b2ff
fix(readme) 2024-06-24 15:00:45 -04:00
Vivian Phung
a4d9211ffe
Refactor(README.md) (#217)
This pull request refactors the main `README.md` by removing detailed backend and frontend setup instructions and adding separate `README.md` files for the backend and frontend with their specific setup and deployment instructions. This makes the main `README.md` cleaner and directs users to specific READMEs for backend and frontend setups.

---
2024-06-22 18:07:47 -04:00
Vivian Phung
af31fac3ee
chore(storybook): icons to correct folder (#216)
This pull request refactors several aspects of the frontend codebase focused on Storybook configurations and icon story files. It replaces `args` with `argTypes` to enhance control configurations, adds `staticDirs` for public assets in Storybook, and standardizes the titles of some icon stories.
2024-06-22 17:27:37 -04:00
Vivian Phung
54ae3f429d
fix(staging): github client id on frontend staging deployment script (#214)
### TL;DR
Update references to the new GitHub repository URLs and make minor formatting fixes in deployment scripts.

### What changed?
Updated GitHub repository URLs from `snowball-tools-platform` to `snowball-tools` in various deployment and test scripts. Made minor formatting adjustments including spacing and indentation.

### How to test?
Run the deployment scripts in their respective environments to ensure they reference the correct repository URLs and all functionalities work as expected. Check for successful creation and updation of application records.

### Why make this change?
This change was made to reflect the new repository structure and ensure consistency across all deployment scripts. The minor formatting fixes improve code readability.

---
2024-06-22 17:24:40 -04:00
Vivian Phung
acfe78bf07
chore: ignore .DS_Store 2024-06-22 17:05:34 -04:00
Vivian Phung
ce1833cb51
bump: version (#213)
This pull request updates the backend version endpoint to return version '0.0.8' instead of '0.0.7'.

---
2024-06-21 21:16:34 -04:00
Vivian Phung
f2e59c11fd
Refactor: VerifyCodeInput Component and Modify Access Code Validation (#212)
### TL;DR

Implemented the new `VerifyCodeInput` component and updated the access code validation logic.

### What changed?

1. Added a new reusable `VerifyCodeInput` component for verifying codes in a user-friendly way. This component handles paste events, input changes, and keyboard navigation.
2. Updated the backend route `/accesscode` to accept an arbitrary code for now. 
3. Incorporated the `VerifyCodeInput` component into the `AccessCode` page, replacing the generic `Input` component.
4. Updated the access code validation logic to check for a trimmed length of 5 characters instead of 6.
5. Added a slight pause for UX purposes when validating the access code on the frontend.

### How to test?

1. Go to the Access Code page.
2. Try entering an access code with various inputs (keyboard, paste, etc.) to see if it works seamlessly.
3. Verify that only a 5-digit code is considered valid.
4. Check the backend logs to ensure the validation endpoint is working as expected.

### Why make this change?

This change improves the user experience by providing a custom input component for access code verification and ensures that the access code validation meets the new requirements.

---
2024-06-21 21:13:32 -04:00
Vivian Phung
b261e7e436
Feat: Access Code Authentication Flow (#211)
## What changed?

This Pull Request introduces an access code validation feature to the authentication process. Changes encompass backend route for access code validation, new frontend components for handling access code input, and integration of the access code verification in the signup flow.

### Backend:
- Added POST `/accesscode` route in `auth.ts` for validating access codes.

### Frontend:
- Created `AccessCode` component for access code input and validation.
- Added `AccessSignUp` component that integrates access code verification before signup.
- Updated `SignUp` component to check for valid access code on mount.
- Modified `SnowballAuth` to use new `AccessSignUp` instead of `SignUp`.
- Added `verifyAccessCode` utility function for code verification API call.

## How to test?
1. Run the backend and frontend projects.
2. Navigate to the signup page. You should be prompted to enter an access code.
3. Enter the code `444444` and proceed. Any other code should display an error message.
4. Verify that valid access code routes to the signup component.

## Why make this change?

This change improves authentication by adding an extra layer of security through access code verification.
2024-06-21 21:10:31 -04:00
Vivian Phung
934aa1a26b
Refactor: Env to utils/constants (#210)
This PR centralizes all the environment variable references into a single constants file. The change includes replacing various `import.meta.env` references with imports from the new `utils/constants` module. This improves maintainability by providing a single place to manage environment variables.
2024-06-21 21:07:41 -04:00
Gilbert
d975390a1b Fix url env var 2024-06-20 14:54:17 -05:00
Gilbert
f77323364c Bump 2024-06-20 04:06:16 -05:00
Gilbert
c6a78f2116 Just send back the message, this is a demo after all 2024-06-20 04:05:48 -05:00
Vivian Phung
cff9a5b2ea
[nit] DeploymentMenu dependencies cleanup (#204)
### TL;DR

This PR updates the project settings.

### What changed?

The project settings have been refactored for better organization and readability.

### How to test?

To test this change, navigate to the project settings and ensure all options are functioning as expected.

### Why make this change?

This change was made to improve the user experience when navigating through the project settings.

---
2024-06-20 00:40:32 -04:00
Vivian Phung
003b83ba21
Refactor: DeploymentMenu uses toast component (#203)
### TL;DR

This PR includes updates to the project settings.

### What changed?

The project settings have been refactored for better usability and consistency with other components.

### How to test?

To test this change, navigate to the project settings and ensure all options are working as expected.

### Why make this change?

This change was made to improve the user experience and maintain consistency across the application.

---
2024-06-20 00:36:43 -04:00
Vivian Phung
82a1c151a8
Refactor: AssignDomainDialog uses Modal component (#202)
### TL;DR

This PR includes updates to the project settings.

### What changed?

The project settings have been refactored for better organization and readability.

### How to test?

To test this change, navigate to the project settings and ensure all options are functioning as expected.

### Why make this change?

This change was made to improve the user experience when navigating through the project settings.

---
2024-06-20 00:32:41 -04:00
Gilbert
2ada11f311 Support async express handlers 2024-06-19 22:59:20 -05:00
Gilbert
6e32d0678a Add generic error handling 2024-06-19 22:31:07 -05:00
Gilbert
198478f5fa Add logs for debugging 2024-06-19 14:22:55 -05:00
552dfe783e
Merge pull request #209 from snowball-tools/dboreham/add-rpid-config
Add missing rpid env var
2024-06-18 20:04:19 -06:00
Gilbert
e2bf5d052c Bump server version 2024-06-18 17:46:47 -05:00
33323b9bbf
Merge pull request #208 from snowball-tools/dboreham/error-checked-deployment
Make deployment scripts more robust
2024-06-18 13:51:33 -06:00
e2a3254563 Add missing rpid env var 2024-06-18 13:47:26 -06:00
8589bf4094 Make deployment scripts more robust 2024-06-18 11:15:31 -06:00
f8ebdfe7aa
Merge pull request #207 from snowball-tools/telackey-patch-1
Update config.staging.yml
2024-06-18 06:31:58 -06:00
2166b2f800
Update config.staging.yml 2024-06-17 23:41:01 -05:00
cdd8d15e73
Increase gas for staging deployment chain profile to match prod (#205) 2024-06-17 09:49:52 +05:30
Vivian Phung
dc7b251988
add version backend (temp) (#201)
* add version backend (temp)

* staging router endpoint

* remove frontend
2024-06-05 17:38:19 +01:00
Gilbert
bfb4a3f30b Revert "remove email signup for now"
This reverts commit 0e9c3a07fd.
2024-06-04 16:26:15 -05:00
Gilbert
8f7fc888a9 Log commit hash 2024-06-04 15:56:52 -05:00
Vivian Phung
0e9c3a07fd remove email signup for now 2024-06-04 15:56:52 -05:00
Vivian Phung
61e3e88a6c
Refactor Input and SearchBar (#199)
### TL;DR

This pull request refactors the `SearchBar` and `Input` components, adding a `ref` to the former and removing an incorrect understanding of `react-hook-form` (yes, i prev "fix" the component) in the latter.

### What changed?

A ref is added to the SearchBar component for better control and handling. In the Input component, we have eliminated the usage of 'react-hook-form' and as a result, the 'register' prop is removed. This makes the component less reliant on specific libraries and more reusable.

### How to test?

Ensure that proper testing is done on the updated components. Make sure that the `SearchBar` works as expected with its ref and that Input does not depend on 'react-hook-form' anymore.

### Why make this change?

This change was made to improve the functionality of the `SearchBar` and the flexibility of the Input component, making them more effective and reusable respectively. The changes also align with the current code quality standards and best practices.
2024-05-22 15:06:50 -04:00
Vivian Phung
7b5ba1a5d0
correct suppressRefError (#198)
### TL;DR

This PR refactors the `UserSelect` component, adjusting the call to `getToggleButtonProps`.

### What changed?

The `getToggleButtonProps` method in the `UserSelect` component now takes in two separate objects, one for the `ref` and another for `suppressRefError`, instead of a single one.

### How to test?

Verify the component functionality hasn't changed and there are no reference errors.

### Why make this change?

This code changes improve the readability and maintainability of this component by clearly separating the component reference and error suppression configurations in separate objects.
2024-05-22 15:02:51 -04:00
Vivian Phung
b35f4033c5
Refactor: Collaborator Project Settings (#197)
### TL;DR

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

### What changed?

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

### How to test?

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

### Why make this change?

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

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

### What changed?

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

### How to test?

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

### Why make this change?

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

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

### What changed?

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

### How to test? 

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

### Why make this change? 

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

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

### What changed?

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

### How to test?

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

### Why make this change?

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

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

    Update staging deployment records with new config values

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

    Update build script for new VITE env variables

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

    Update subdomain

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

    Update GitHub client ID and subdomain

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

    Add staging deployment records

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

    Add deployment script for staging
2024-05-22 09:21:00 -05:00
Vivian Phung
9a74205bd5
Merge pull request #154 from snowball-tools/nv-handle-repo-exist-error
Show error in toast for creating repository that already exisits in account
2024-05-22 10:19:09 -04:00
Vivian Phung
7147611842
Merge branch 'main' into nv-handle-repo-exist-error 2024-05-22 10:16:27 -04:00
Vivian Phung
8533c41c58
Merge pull request #193 from snowball-tools/main
storybook and project settings styling
2024-05-21 07:50:36 -04:00
Vivian Phung
2074e08a0c
[3/n][Storybook] Settings - Delete Project Dialog (#76) 2024-05-16 20:45:32 -04:00
Vivian Phung
f01bdf2de7
[2/n][Storybook] Settings - EnvironmentVariables (#75) 2024-05-16 20:42:29 -04:00
Vivian Phung
8cb5eadfb2
EditEnvironmentVariableRow cleanup (#74) 2024-05-16 20:39:42 -04:00
Vivian Phung
41666568f5
AddEnvironmentVariableRow button alignment to bottom (#73) 2024-05-16 20:36:31 -04:00
Vivian Phung
17cf878168
[1/n][Storybook] Settings (#72) 2024-05-16 20:33:33 -04:00
Vivian Phung
c72cbce615
AddEnvironmentVariableRow padding (#71) 2024-05-16 20:29:35 -04:00
Vivian Phung
a69dd71117
DeleteProjectDialog update to Modal (#70) 2024-05-16 20:26:31 -04:00
Vivian Phung
5dc4d28b50
MemberCard remove x padding (#69) 2024-05-16 20:22:33 -04:00
Vivian Phung
7dce1d66ae
remove x padding padding from general tab (#68) 2024-05-16 20:19:31 -04:00
Vivian Phung
46ba6d014d
fix duplicate styling (#67) 2024-05-16 20:16:31 -04:00
Gilbert
8488cfab83 Make Lit Work Again 2024-05-15 17:44:59 -05:00
Vivian Phung
8376aff7bd
Merge pull request #191 from snowball-tools/main
sync forks
2024-05-15 17:52:42 -04:00
Vivian Phung
583c0b9d26
MemberCard for owner disabled (#66)
* AddMemberDialog remove footer styling from old component

* MemberCard for owner disabled (#64)
2024-05-15 17:18:32 -04:00
Vivian Phung
e12c94e087
AddMemberDialog remove footer styling from old component (#63)
### TL;DR

This PR involves a minor change on the AddMemberDialog component style.

### What changed?

The 'justify-start' class was removed from the Modal.Footer in the AddMemberDialog.tsx.

### How to test?

Check the AddMemberDialog on the project settings page to make sure the style changes reflect accurately.

### Why make this change?

It's not specified the specific reason for the change, However, it's aimed towards improving the component's layout and presentation in the project settings page.

---
2024-05-15 17:07:13 -04:00
Vivian Phung
879cfdb2bf
env var register pass (#62) 2024-05-14 20:08:18 -04:00
Vivian Phung
04b6a84440
env var fix 2024-05-14 20:07:02 -04:00
Vivian Phung
41b9ce1096
[22/n][Storybook] AuthPage (#61) 2024-05-14 19:33:29 -04:00
Vivian Phung
ad69ebe4a0 [21/n][Storybook] AuthPage 2024-05-14 23:31:51 +00:00
Vivian Phung
ca8863e1d6
[21/n][Storybook] SearchBar (#60) 2024-05-14 19:29:46 -04:00
Vivian Phung
0fb1127b96 [20/n][Storybook] SearchBar 2024-05-14 23:26:47 +00:00
Vivian Phung
b34e0783c1
[20/n][Storybook] CloudyFlow (#59) 2024-05-14 19:25:36 -04:00
Vivian Phung
050f404776 [19/n][Storybook] CloudyFlow 2024-05-14 23:23:32 +00:00
Vivian Phung
2e30a1aae1
Tabs component export TabsTheme (#58) 2024-05-14 19:22:28 -04:00
Vivian Phung
7c8e9f2448 Tabs component export TabsTheme 2024-05-14 23:20:39 +00:00
Vivian Phung
065b12e3d9
[19/n][Storybook] Tag with typed themes (#57) 2024-05-14 19:19:31 -04:00
Vivian Phung
3668a8edf7 [19/n][Storybook] Tag with typed themes 2024-05-14 23:17:28 +00:00
Vivian Phung
28366ea725
[components] basic Table uses themes (#56) 2024-05-14 19:16:50 -04:00
Vivian Phung
4c3072ed50 [19/n][Storybook] basic Table uses themes 2024-05-14 23:13:33 +00:00
Vivian Phung
6975360d48
[18/n][Storybook] Switch update argTypes and add stories (#55) 2024-05-14 19:12:57 -04:00
Vivian Phung
671321ef4d
[18/n][Storybook] Switch update argTypes and add stories 2024-05-14 18:16:49 -04:00
Vivian Phung
d04ad68f18
[17/n][Storybook] Step (single) story (#54) 2024-05-14 17:52:31 -04:00
Vivian Phung
16df36715a [17/n][Storybook] Step (single) story 2024-05-14 21:50:56 +00:00
Vivian Phung
6fdbcf6f46
[16/n][Storybook] Steps argTypes and typed variants (#53) 2024-05-14 17:48:38 -04:00
Vivian Phung
702cef24b3 [16/n][Storybook] Steps argTypes and typed variants 2024-05-14 21:46:42 +00:00
Vivian Phung
d3cf0453e7
[15/n][Storybook] Select argTypes and typed variants (#52) 2024-05-14 17:44:33 -04:00
Vivian Phung
d76db4fb96 [12/n][Storybook] Select argTypes and typed variants 2024-05-14 21:42:33 +00:00
Vivian Phung
19545c48bd
[14/n][Storybook] SegmentedControls update argTypes and use typed themes (#51) 2024-05-14 17:41:29 -04:00
Vivian Phung
8d96be625e [5/n][Storybook] SegmentedControls update argTypes and use typed themes 2024-05-14 21:40:17 +00:00
Vivian Phung
1c05ba8822
[13/n][Storybook] Radio argTypes update (#50) 2024-05-14 17:37:58 -04:00
Vivian Phung
fd7d06b9e2 [13/n][Storybook] Radio argTypes update 2024-05-14 21:36:07 +00:00
Vivian Phung
550ef91968
[12/n][Storybook] OverflownText update argTypes (#49) 2024-05-14 17:33:41 -04:00
Vivian Phung
c710fb5a53 [12/n][Storybook] OverflownText update argTypes 2024-05-14 21:31:40 +00:00
Vivian Phung
a95f18a66b
[11/n][Storybook] Modal argTypes update (#48) 2024-05-14 17:29:38 -04:00
Vivian Phung
ccc36fd175 [4/n][Storybook] Modal argTypes update 2024-05-14 21:27:40 +00:00
Vivian Phung
ce7e9174e4
[10/n][Storybook] Input update argTypes (#47) 2024-05-14 17:25:37 -04:00
Vivian Phung
43d7c94cb0 [4/n][Storybook] Input update argTypes 2024-05-14 21:24:00 +00:00
Vivian Phung
294675c276
[9/n][Storybook] InlineNotification update argTypes and use theme variants (#46) 2024-05-14 17:22:38 -04:00
Vivian Phung
2e0b228aa5 [9/n][Storybook] InlineNotification update argTypes and use theme variants 2024-05-14 21:20:45 +00:00
Vivian Phung
907b5ee02d
[8/n][Storybook] IconWithFrame update argTypes (#45) 2024-05-14 17:18:35 -04:00
Vivian Phung
8f456c04f5 [8/n][Storybook] IconWithFrame update argTypes 2024-05-14 21:16:34 +00:00
Vivian Phung
29caf510ea
[7/n][Storybook] DatePicker update argTypes (#44) 2024-05-14 17:15:32 -04:00
Vivian Phung
a63d0f69ed [7/n][Storybook] DatePicker update argTypes 2024-05-14 21:13:29 +00:00
Vivian Phung
1aa57aaecc
[6/n][Storybook] Checkbox update argTypes (#43) 2024-05-14 17:12:29 -04:00
Vivian Phung
a27331f54f [6/n][Storybook] Checkbox update argTypes 2024-05-14 21:10:37 +00:00
Vivian Phung
ebe6d35b54
[5/n][Storybook] Calendar update argTypes (#42) 2024-05-14 17:08:33 -04:00
Vivian Phung
29557d7597 [6/n][Storybook] Calendar update argTypes 2024-05-14 21:06:34 +00:00
Vivian Phung
df540e06eb
[4/n][Storybook] Button using theme variants (#41) 2024-05-14 17:05:00 -04:00
Vivian Phung
3fe0718532
[4/n][Storybook] Button using theme variants 2024-05-14 16:24:25 -04:00
Vivian Phung
039753df56
[7/n][project settings ui] member list cleanup (#40) 2024-05-14 16:23:33 -04:00
Vivian Phung
386f40952e member list cleanup 2024-05-14 20:22:02 +00:00
Vivian Phung
e1c4f77ec1
[7/n][project settings ui] env var cleanup start (#35) 2024-05-14 16:19:32 -04:00
Vivian Phung
57601e6b4b env var needs fixing 2024-05-14 20:17:54 +00:00
Vivian Phung
e9a367db42
[7/n][project settings ui] cleanup dialogs (#34) 2024-05-14 16:16:29 -04:00
Vivian Phung
6ae04251d2 cleanup dialogs 2024-05-14 20:14:50 +00:00
Vivian Phung
690fc8d2cb
[7/n][project settings ui] ProjectSettingContainer (#33) 2024-05-14 16:13:28 -04:00
Vivian Phung
2719b3e385 ProjectSettingContainer 2024-05-14 20:11:57 +00:00
Vivian Phung
de6d5c302b
[8/n][project settings ui] config domains and react dom context (#32) 2024-05-14 16:09:33 -04:00
Vivian Phung
dc91fa0d7f storybook config domains and react dom context 2024-05-14 20:07:46 +00:00
Vivian Phung
571d58d57d
[component lib] table basic component (#31) 2024-05-14 16:06:31 -04:00
Vivian Phung
947337acb1 table basic component 2024-05-14 20:04:40 +00:00
Vivian Phung
f8908c1c06
[component lib] input forward ref react-hook-form (#30) 2024-05-14 16:03:29 -04:00
Vivian Phung
6e7385b118 input forward ref react-hook-form 2024-05-14 20:01:35 +00:00
Vivian Phung
abbda18fc7
[7/n][project settings ui] AddEnvironmentVariableRowProps Input (#29) 2024-05-14 16:00:30 -04:00
Vivian Phung
2d0de785f9 ui cleanup 2024-05-14 19:58:32 +00:00
Vivian Phung
68f1a265f4
[6/n][project settings ui] collaborators ui (#28) 2024-05-14 15:57:47 -04:00
Vivian Phung
9b7a021e8b [6/n][project settings ui] collaborators ui 2024-05-14 19:56:14 +00:00
Vivian Phung
cdfc72a5a0
[5/n][project setting ui] MembersTabPanel cleanip (#27) 2024-05-14 15:53:52 -04:00
Vivian Phung
47b322c212 [5/n][project setting ui] MembersTabPanel cleanip 2024-05-14 19:51:41 +00:00
Vivian Phung
e4c39e0955
[4/n][project settings ui] GitTabPanel cleanup (#26) 2024-05-14 15:50:44 -04:00
Vivian Phung
9f46290ecc [4/n][project settings ui] GitTabPanel cleanup 2024-05-14 19:49:02 +00:00
Vivian Phung
b71b7579a6
[3/n][project settings ui] Domains cleanup (#25) 2024-05-14 15:46:48 -04:00
Vivian Phung
8aa1362be6 [3/n][project settings ui] Domains cleanup 2024-05-14 19:45:08 +00:00
Vivian Phung
e1d2b789a3
[2/n][project settings ui] AddEnvironmentVariableRowProps (#24) 2024-05-14 15:42:51 -04:00
Vivian Phung
cf161bcba0 AddEnvironmentVariableRowProps UI Cleanip 2024-05-14 19:40:59 +00:00
Vivian Phung
57956ec269
[1/n][project settings ui] GeneralTabPanel (#23) 2024-05-14 15:38:44 -04:00
Vivian Phung
fb77433bea GeneralTabPanel cleanup 2024-05-14 19:36:50 +00:00
Vivian Phung
146b904556
[4/n][Storybook] components & icons (#22) 2024-05-14 15:34:37 -04:00
Vivian Phung
756cc52e1c [1/n] storybook components start 2024-05-14 19:32:47 +00:00
Vivian Phung
ba5f281671
[3/n][Storybook] Radio (#39) 2024-05-14 15:31:33 -04:00
Vivian Phung
60a66e94bd [3/n][Storybook] Radio 2024-05-14 19:29:48 +00:00
Vivian Phung
5f5b0a4d4f
[2/n][Storybook] Move AuthHeader Stories (#21)
### TL;DR

This pull request refactors the directory structure of the `AuthHeader` component, moving it to the `Pages/Auth` directory. The meta property's attributes were also refined.

### What changed?

The `AuthHeader` component and its story were relocated from the `packages/frontend/src/stories/` directory to the `packages/frontend/src/stories/Pages/Auth/` directory. The title in the meta property has been updated to `Pages/Auth/Header`. The previously explicit assertion that meta satisfies `Meta<typeof Header>` has been removed, making the type assertion implicit.

### How to test?

To test these changes, navigate to the new directory and any references associated with `AuthHeader` component, verify that the software builds successfully, and confirm that Storybook correctly displays the AuthHeader story.

### Why make this change?

This change improves the organization of the codebase by categorizing components into functionally relevant directories. This allows for better management and scalability of the code. Additionally, the adjustments made to the component's metadata in Storybook makes the documentation easier to understand.
2024-05-14 15:28:35 -04:00
Vivian Phung
5ebdd3003a Badges 2024-05-14 19:26:48 +00:00
Vivian Phung
987643b153
[1/n][Storybook] Badges (#36)
### TL;DR

This PR updates formatting in `Button.stories.tsx`

### What changed?

Pulled the Button heading inside `<h1>` tags onto a new line for readability and consistency.

### How to test?

Check the Button story in Storybook and ensure that the Button title displays correctly.

### Why make this change?

This small change improves the readability and consistency of code in the `Button` story.
2024-05-14 15:25:30 -04:00
Vivian Phung
090a9054e4 Button strories lint 2024-05-14 19:23:29 +00:00
Vivian Phung
7dcd6581a7
[Storybook] Refactor Component Directories and Imports (#38) 2024-05-14 15:22:43 -04:00
Vivian Phung
928958131c component story directory 2024-05-14 19:19:35 +00:00
Vivian Phung
327ac62186
Configure aliases pages and types in tsconfig and vite (#37)
### TL;DR

Updated paths in `tsconfig.json` and `vite.config.ts`

### What changed?

The paths for `pages` and `types` have been added to both `tsconfig.json` and `vite.config.ts` to support easier importing.

### How to test?

Check if imports using the new paths are working correctly in the code.

### Why make this change?

These changes seem to be for better organization and accessibility of different parts of the code by providing clear and quick paths for `pages` and `types`.
2024-05-14 15:18:19 -04:00
Vivian Phung
37c250873c
resolve aliases 2024-05-14 13:27:23 -04:00
Vivian Phung
f7fa47dbc8
format prettier (#20)
format prettier

storybook
2024-05-09 16:23:41 -04:00
Vivian Phung
1bba82ee86
storybook 2024-05-09 16:22:03 -04:00
Vivian Phung
99cc35459b
format prettier 2024-05-09 16:22:03 -04:00
Vivian Phung
4b54f2fc2e
format prettier (#18) 2024-05-09 16:19:32 -04:00
Vivian Phung
4906740876
format prettier 2024-05-09 16:18:52 -04:00
Vivian Phung
73ff7896f8
env example fix (#16) 2024-05-09 14:39:58 -04:00
Vivian Phung
67d28910d4
env example fix 2024-05-09 14:04:28 -04:00
Gilbert
939b1c40e8 Fix default branch bug 2024-05-08 22:27:58 -05:00
Vivian Phung
c3b048d273
Revert "sign in with passkeys coming soon"
This reverts commit ed0de90118.
2024-05-08 11:45:24 -04:00
Gilbert
48552310e0 Turnkey auth 2024-05-06 14:36:58 -05:00
Vivian Phung
c82e1110d3
remove unneccessary , (#12)
### TL;DR

This pull request includes a minor refactoring in the `index.tsx` file of the frontend package.

### What changed?

A trailing comma and a new line after this comma were removed from the `index.tsx` file.

### How to test?

You can test this change by running the application and checking if everything works as expected. As this is a minor change, it's not expected to break anything.

### Why make this change?

This change is part of the project's cleanup activity to maintain a clean and readable codebase.

Before | After |
| -- | --|
![Screenshot 2024-05-06 at 2.55.10 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/YN8FgzXirh28JBEdfrb9/7876d655-dddf-4140-8bf8-09f5170c2169.png) | ![Screenshot 2024-05-06 at 2.54.59 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/YN8FgzXirh28JBEdfrb9/29fdce38-b674-4e51-854e-77d51755a266.png) |
2024-05-06 14:59:14 -04:00
Vivian Phung
b7f29781f2
remove unneccessary , 2024-05-06 14:55:34 -04:00
Vivian Phung
cf531127c4
sign in with passkeys coming soon (#11)
### TL;DR

Added a toast notification for 'Sign-in with Passkeys' feature which is still under development.

### What changed?

'The Sign-in with Passkey' button now triggers a toast notification informing the user that this feature is coming soon.

### How to test?

Click on the 'Sign-in with Passkey' button on the login page. You should see a notification with the message 'Sign-in with Passkeys is coming soon!'.

### Why make this change?

This change has been made to inform users about upcoming features in an interactive manner and ensure a smooth user experience by preventing confusion about non-functional buttons.
2024-05-06 13:32:12 -04:00
Vivian Phung
44de7bb0f1
font path fix (#9)
### TL;DR

Update font URLs in `index.css`

### What changed?

Updated font URLs from `../public/fonts/InterDisplay/` to `/fonts/InterDisplay/` for better path consistency

### How to test?

Test loading fonts and check for any font display issues

### Why make this change?

To ensure correct font loading and path consistency in the project
2024-05-06 13:31:53 -04:00
Vivian Phung
ed0de90118
sign in with passkeys coming soon 2024-05-06 13:31:11 -04:00
Vivian Phung
11c8f7b09b
Revert "Build source maps for bugsnag"
This reverts commit df8bc3784a.
2024-05-06 13:31:10 -04:00
Vivian Phung
3dd62114b4
font path fix 2024-05-06 13:31:09 -04:00
Vivian Phung
4f3ea09b79
Revert "Build source maps for bugsnag"
This reverts commit df8bc3784a.
2024-05-06 13:25:26 -04:00
Vivian Phung
f4f80903f8
update .gitignore for dev output (#10)
### TL;DR

Added `packages/backend/dev/` to `.gitignore` file.

### What changed?

`packages/backend/dev/` is now ignored from the git repository for better development practices.

### How to test?

Try adding files to `packages/backend/dev/`, they should not be tracked by git now.

### Why make this change?

To ensure unnecessary development files don’t accidentally get checked into the repository.
2024-05-06 13:08:09 -04:00
Vivian Phung
5307eab443
update gitignore for build output 2024-05-06 13:02:37 -04:00
Gilbert
00199424f7 Improve exception handling 2024-05-02 16:36:31 -05:00
Gilbert
aaadfeab72 Don't need this 2024-05-02 16:30:49 -05:00
Gilbert
3ffac1a019 ngmi 2024-05-02 15:51:51 -05:00
Gilbert
56a264199b Fix dev script 2024-05-02 15:47:23 -05:00
Gilbert
df8bc3784a Build source maps for bugsnag 2024-05-02 10:08:16 -05:00
Vivian Phung
464b0463af
remove all icons for now (#8) 2024-05-01 22:01:51 -04:00
Vivian Phung
c29134bfd9
remove all icons for now 2024-05-01 21:55:13 -04:00
Vivian Phung
9b0ad34831
ad g around gear icon (#7) 2024-05-01 21:38:28 -04:00
Vivian Phung
89b3cb8c31
ad g around gear icon 2024-05-01 21:38:14 -04:00
Vivian Phung
c52d695ca7
gear icon on settings page (#6) 2024-05-01 21:31:48 -04:00
Vivian Phung
187f75d561
gear icon on settings page 2024-05-01 21:31:22 -04:00
Vivian Phung
14deecfad6
add space btw linked learn more (#5)
### TL;DR

Update description text in project transfer section to include a 'Learn more' link.

### What changed?

The description text under the 'Transfer project' section of the project settings page is updated. A 'Learn more' link is added to provide additional information about transferring projects.

### How to test?

Navigate to a project's settings page and look for the 'Transfer project' section. Verify whether the description text contains a clickable 'Learn more' link.

### Why make this change?

To provide users with more information and to make the transfer process clearer by providing relevant resources.
2024-05-01 21:21:55 -04:00
Vivian Phung
808b2ad61b
add space btw linked learn more 2024-05-01 21:20:48 -04:00
Vivian Phung
775e731f4d
project avatar (#4)
### TL;DR

This pull request replaces the Avatar showing user's initial with a formatted text of user's name in Project Search layout.

### What changed?

In the `ProjectSearch` file, the `Avatar` component that displayed the user's initial is replaced with a paragraph tag that now shows the user's formatted name.

### How to test?

To test the change, navigate to the Project Search page to verify that the user's formatted name is displayed instead of an `Avatar`.
2024-05-01 20:35:57 -04:00
Vivian Phung
813cea055b
project avatar 2024-05-01 20:33:32 -04:00
Gilbert
072e2c20e3 Add missing file 2024-05-01 18:32:25 -05:00
Vivian Phung
83d5e3d769
address should be in top view formatted. not just first letter 0 (#3) 2024-05-01 19:29:09 -04:00
Vivian Phung
9ed52c67c9
address should be in top view formatted. not just first letter 0 2024-05-01 19:28:09 -04:00
Gilbert
f7d6d02b27 Quick error logging (will clean this up later) 2024-05-01 18:24:06 -05:00
Vivian Phung
d7dc9a07f9
checkbox should always be disabled (#2)
### TL;DR

This change removes unused properties from a checkbox component in the `org-slug/projects/create/template` file.

### What changed?

The `value` and `onChange` properties were deleted from the checkbox that determines whether a repo is private or not. It's important to note that this checkbox is currently disabled.

### How to test?

Try to create a new project and check that there aren't any errors or behavior changes when dealing with the 'Make this repo private' checkbox.

### Why make this change?

This change enhances code readability and ensures we're not keeping unnecessary code. It does not affect functionality as the checkbox is currently disabled.
2024-05-01 18:33:58 -04:00
Vivian Phung
6cbc87a7d1
should always be disabled 2024-05-01 18:23:54 -04:00
Vivian Phung
ddb54de49b
make private templates disabled (#1) 2024-05-01 18:06:59 -04:00
Vivian Phung
cf9fd04272
make private templates disabled 2024-05-01 17:50:54 -04:00
Gilbert
535c37d0b4 Support not having bugsnag available 2024-05-01 15:03:59 -05:00
Gilbert
46476bef28 Add bugsnag 2024-05-01 15:00:52 -05:00
8b39f664ad Replace REACT_APP env with VITE 2024-04-25 16:59:02 +05:30
5f4be30799 Remove current deployment and publish ApplicationDeploymentRemovalRequest for project DNS deployment 2024-04-25 16:59:02 +05:30
b53e12b94b Add script for publishing ApplicationDeploymentRemovalRecord record 2024-04-25 16:59:02 +05:30
f290b5c0b5 Implement checking for deployment removal records in intervals 2024-04-25 16:59:02 +05:30
4fa6f418ba Lint fix 2024-04-25 16:59:02 +05:30
Eric Lewis
328da7fdc8 feat: submit delete deployment request
doesn't appear to work when the deployment is current?
2024-04-25 16:57:14 +05:30
Eric Lewis
8210512eea fix: missing key 2024-04-25 16:57:14 +05:30
Eric Lewis
cd2dce2404 fix: don't use two refs 2024-04-25 16:57:14 +05:30
Eric Lewis
c2158510d9 feat: clean up stuck builds 2024-04-25 16:57:14 +05:30
Eric Lewis
096fd04a22 fix: project card settings links 2024-04-25 16:57:14 +05:30
Eric Lewis
a8d93732ce feat(gql-client): deployment removal request 2024-04-25 16:57:14 +05:30
Eric Lewis
953c3fc10b feat(gql): deployment removal request 2024-04-25 16:57:14 +05:30
Eric Lewis
3cbea57294 feat(registry): deployment removal request 2024-04-25 16:57:14 +05:30
Gilbert
27f5c57c37 Attempt to fix prod 2024-04-24 19:02:52 -05:00
Gilbert
d1c3249b98 Copy tweak 2024-04-24 12:32:09 -05:00
Gilbert
d34a5f29cd Prettier format 2024-04-24 12:18:59 -05:00
Gilbert
4a14681753 Support process.env in frontend 2024-04-24 12:18:18 -05:00
Eric Lewis
aea6bfde54 feat: support cf workers
Note: we don't really want to be committing the gql-client. it is a stopgap.
2024-04-24 09:59:47 -04:00
Gilbert
4aac93b504
Sign in with google, using snowball's sdk (#188) 2024-04-23 22:02:54 -05:00
Gilbert
e6b68dcce4 Bump snowball version 2024-04-23 21:54:46 -05:00
Gilbert
d2daed4cac Sign in with google 2024-04-23 21:22:11 -05:00
Gilbert
c395be82b5 Signup with sdk 2024-04-23 21:21:58 -05:00
Eric Lewis
748ca507da fix: use correct output directory for build 2024-04-23 09:43:52 -04:00
Gilbert
bc210fdb0f Revert "Signup with sdk" until we get everything working (will submit pr)
This reverts commit bace4a6ce6.
2024-04-21 21:47:25 -05:00
Gilbert
bace4a6ce6 Signup with sdk 2024-04-21 18:03:01 -05:00
Eric Lewis
0d36cc1b6d
Merge pull request #182 from snowball-tools/ericlewis-patch-1
fix: default deployment filter status
2024-04-17 17:29:36 -04:00
Eric Lewis
1ed4ee979c
fix: default deployment filter status 2024-04-17 17:11:43 -04:00
72f1abcdf6
Add CI to test web app deployment and undeployment (#174)
* Add a script to deploy test records

* Add checks for ApplicationDeploymentRecord and the deployment URL

* Add a CI workflow to run the app deployment test

* Update test deployment request record config

* Add test for deployment removal

* Update test deployment request record

* Increase max retries

* Add retries when checking if URL is up or down

* Rename test script

* Run webapp deployment test CI on PR

* Remove unnecessary jq installation step from CI

* Revert "Run webapp deployment test CI on PR"

This reverts commit 01f373501a95b95cf0abc1dee8fed639ec62daf5.

* Add a step for Slack alerts on a CI failure

* Add a workflow dispatch for manual trigger

* Update workflow title and remove trigger on main

* Document deployment test setup

---------

Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-04-15 14:10:59 +05:30
Vivian Phung
57add027ab
Merge pull request #180 from snowball-tools/fix-lint
Add build step to lint action
2024-04-12 16:55:23 -05:00
Gilbert
7ad02d27bf Add build step to lint action 2024-04-12 16:32:09 -05:00
Eric Lewis
b17e9df1e4
Merge pull request #169 from snowball-tools/eric/snow-314-dashboarduicreate-template-app-page-auto-select-first-git
fix: auto select first git account
2024-04-12 12:20:41 -04:00
Gilbert
baadc507e7 Not yet 2024-04-11 21:59:33 -05:00
Gilbert
6316aa852b Add back .env.example 2024-04-11 21:55:25 -05:00
Gilbert
2274e8d145 Lint using tsc; fix type errors 2024-04-11 21:49:14 -05:00
Gilbert
f8d706233e Switch from cra to vite 2024-04-11 21:48:58 -05:00
cc8f9527da
Check for GitHub authentication in template create page and handle GitHub unauthorized errors (#173)
* Fix GitHub auth check on reloading template create page

* Fix error handling for unauthenticated GitHub token
2024-04-11 17:19:15 +05:30
Eric Lewis
47231a6eab
Merge pull request #171 from snowball-tools/eric/airf-42-update-org-to-team
fix: use 'team' instead of 'organization'
2024-04-09 17:20:51 -04:00
Eric Lewis
4774074b67
Merge pull request #170 from snowball-tools/eric/airf-28-dashboard-hide-database-from-project-tab
fix: remove database tabs & overviews
2024-04-09 17:20:40 -04:00
Eric Lewis
0da7c3541e fix: use 'team' instead of 'organization' 2024-04-09 14:22:58 -04:00
Eric Lewis
b7b4ab1f14 fix: remove database tabs & overviews 2024-04-09 14:19:09 -04:00
Eric Lewis
ea6ad52a73 fix: show placeholder select while waiting for load 2024-04-09 14:11:26 -04:00
Eric Lewis
f38dfb5604 chore: bump deploy 2024-04-04 10:50:39 -04:00
351db16336
Upgrade laconic-sdk package for showing error with reason in console (#165)
* Upgrade laconic sdk version

* Update toast message for add project

* Log error messages

* Update bond id in readme

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
2024-04-03 20:26:34 +05:30
Wahyu Kurniawan
78f04c3669
Merge pull request #162 from snowball-tools/ayungavis/T-4943-product-reskin-confirmdialog-styling 2024-03-25 23:04:22 +07:00
Vivian Phung
44a6a8902f
Merge pull request #140 from snowball-tools/readme-cleanup
readme
2024-03-15 15:05:36 -04:00
Wahyu Kurniawan
4fcf8e92b7
♻️ refactor: create another example page for modal only 2024-03-14 22:07:36 +07:00
Wahyu Kurniawan
193dbb058a
🎨 style: adjust overflow text width on mobile 2024-03-14 22:07:05 +07:00
Wahyu Kurniawan
8834de893e
📝 docs: put all the new dialog into component page 2024-03-14 21:58:04 +07:00
Wahyu Kurniawan
9b3ac4654f
♻️ refactor: create transfer project dialog component 2024-03-14 21:57:26 +07:00
Wahyu Kurniawan
e4fdae3329
♻️ refactor: create disconnect repository dialog component 2024-03-14 21:57:00 +07:00
Wahyu Kurniawan
4c936b1eb7
♻️ refactor: create remove member dialog component 2024-03-14 21:56:29 +07:00
Wahyu Kurniawan
f5807c1126
♻️ refactor: create delete webhook dialog component 2024-03-14 21:56:00 +07:00
Wahyu Kurniawan
c1696fbf48
♻️ refactor: create delete variable dialog component 2024-03-14 21:55:30 +07:00
Wahyu Kurniawan
45e8e9a7f4
♻️ refactor: create delete domain dialog component 2024-03-14 21:55:02 +07:00
Wahyu Kurniawan
28740ffbee
♻️ refactor: create change state to production dialog reusable component 2024-03-14 21:54:30 +07:00
Wahyu Kurniawan
97289d85a3
♻️ refactor: create cancel deployment dialog component 2024-03-14 21:53:48 +07:00
Wahyu Kurniawan
f3ce0d0621
🎨 style: adjust deployment body card design to match with the figma 2024-03-14 21:52:55 +07:00
Wahyu Kurniawan
c2417a9daa
️ feat: export the tag props 2024-03-14 21:52:07 +07:00
Wahyu Kurniawan
d50a318e16
️ feat: create chevron doublw down icon component 2024-03-14 21:50:11 +07:00
Wahyu Kurniawan
3674750011
️ feat: update confirm dialog to use new modal component 2024-03-14 21:49:45 +07:00
Wahyu Kurniawan
102c861617
🎨 style: adjust modal theme and add wavy border on the header 2024-03-14 21:49:13 +07:00
Wahyu Kurniawan
56e9be59ad
🐛 fix: missing props 2024-03-08 15:32:11 +07:00
Wahyu Kurniawan
300b8e4b5e
📝 docs: add modal to the example page 2024-03-08 15:30:53 +07:00
Wahyu Kurniawan
296e149391
🎨 style: update confirm dialog to use custom modal component 2024-03-08 15:29:06 +07:00
Wahyu Kurniawan
cbda1cc652
🎨 style: add new keyframe animation for modal 2024-03-08 15:28:23 +07:00
Wahyu Kurniawan
16e7b22507
️ feat: create modal comopnent 2024-03-08 15:27:53 +07:00
Wahyu Kurniawan
93fb696d5c
Merge pull request #161 from snowball-tools/ayungavis/T-4936-make-deployed-lines-responsive
[T-4936: style] Make deployed lines responsive
2024-03-08 14:54:27 +07:00
Wahyu Kurniawan
32305cedbc
🎨 style: add overflow for the content 2024-03-08 12:44:48 +07:00
Wahyu Kurniawan
519618b456
♻️ refactor: remove duplicate component and use absolute for the mobile version 2024-03-08 12:42:42 +07:00
Andre Hadianto
4057480df2
Merge pull request #160 from snowball-tools/andrehadianto/T-4939-create-project-connect-github-assets
[T-4939] create project connect GitHub assets
2024-03-08 12:57:02 +08:00
Wahyu Kurniawan
de197759de
🎨 style: remove width and height icon style from tag size 2024-03-08 10:36:14 +07:00
Wahyu Kurniawan
65b66fe383
♻️ refactor: remove empty space 2024-03-08 10:35:22 +07:00
Wahyu Kurniawan
c6ea3a8f53
🎨 style: adjust responsive for deployment details card 2024-03-08 09:58:26 +07:00
Wahyu Kurniawan
6d861c71cc
🎨 style: adjust filter form mobile 2024-03-08 09:57:50 +07:00
Andre Hadianto
1d96f6430f
Merge pull request #159 from snowball-tools/andrehadianto/T-4968-qol-and-chores
[T-4968] qol and chores
2024-03-07 18:44:04 +08:00
Andre H
b922e632bf 🔧 chore: add mock card to template 2024-03-07 18:23:43 +08:00
Andre H
b1f318bbbd ️ feat: implement mock connect git card 2024-03-07 18:23:22 +08:00
Andre H
667deb78fb 🎨 style: tabs to use overflow-auto instead of overflow-scroll 2024-03-07 14:23:59 +08:00
Andre H
095008867f 🔧 chore: show loading state on fetching repo activities 2024-03-07 14:23:34 +08:00
Andre H
41033c5241 ️ feat: reskin "no repository found" component 2024-03-07 14:23:11 +08:00
Andre H
cdb995205a 🎨 style: truncate overflow text on projectCard 2024-03-07 14:22:32 +08:00
Andre H
3a84832da5 🔧 chore: add tooltip on hover projectcard name 2024-03-07 14:22:10 +08:00
Wahyu Kurniawan
3fdc0b2dff
Merge pull request #156 from snowball-tools/ayungavis/T-4931-layout-mobile-sidebar-layout
[T-4931: style] Mobile sidebar layout and search bar
2024-03-07 12:52:12 +07:00
Andre Hadianto
89da03cec6
Merge pull request #158 from snowball-tools/andrehadianto/T-4961-create-project-success-page
[T-4961] create project success page
2024-03-07 13:51:14 +08:00
Andre Hadianto
9b6cd9baae
Merge pull request #157 from snowball-tools/andrehadianto/T-4944-layout-template-steps-layout
[T-4944] layout template steps layout
2024-03-07 12:24:29 +08:00
Andre H
658cc0b6b2 🔧 chore: change to isDesktopView for >960px 2024-03-07 12:18:41 +08:00
Andre H
be8f0bc4ad 🔧 chore: use `` for text 2024-03-07 12:17:06 +08:00
Andre H
2a8fc30979 🔧 chore: add TODO 2024-03-07 12:16:23 +08:00
Andre H
4074dd4001 🔧 chore: use link-emphasized button 2024-03-07 12:16:03 +08:00
Andre H
028831f806 🔧 chore: change isDesktopView to isTabletView 2024-03-07 11:45:51 +08:00
Andre H
646099707c 🔧 chore: use lottie for success logo 2024-03-07 11:42:12 +08:00
Andre H
6b0548ec47 ⚒️ build: add lottie-react 2024-03-07 11:41:41 +08:00
Andre H
2ed07a6987 ️ feat: reskin success create project 2024-03-07 10:18:04 +08:00
Andre H
e9ab034625 🔧 chore: add new icons 2024-03-07 10:17:30 +08:00
Wahyu Kurniawan
02500b3cba
🎨 style: make the sidebar scrollable 2024-03-06 21:03:01 +07:00
Wahyu Kurniawan
4c8d0e2436
🎨 style: change the background color of the avatar 2024-03-06 19:14:32 +07:00
Wahyu Kurniawan
efa74898af
🎨 style: adjust padding of the content layout 2024-03-06 19:07:41 +07:00
Wahyu Kurniawan
532732943e
♻️ refactor: add empty space 2024-03-06 16:38:45 +07:00
Wahyu Kurniawan
be4900e63f
Merge branch 'main' of https://github.com/snowball-tools/snowballtools-base into ayungavis/T-4931-layout-mobile-sidebar-layout 2024-03-06 16:28:50 +07:00
Wahyu Kurniawan
9e4e203f5f
🎨 style: remove bottom border radius for mobile 2024-03-06 15:44:35 +07:00
Wahyu Kurniawan
d812cdf05f
🎨 style: adjust logo and mobile header padding on mobile 2024-03-06 15:39:46 +07:00
Wahyu Kurniawan
b605c1bc5b
🎨 style: adjust media query minimum width for desktop to match with tailwind large responsive min width 2024-03-06 15:27:29 +07:00
Wahyu Kurniawan
8d3ef369bb
♻️ refactor: change project bar item component to button 2024-03-06 15:14:09 +07:00
Wahyu Kurniawan
7aac8cdcef
🚀 perf: change the debounce to 300 ms 2024-03-06 15:11:16 +07:00
Wahyu Kurniawan
6c31113bca
🎨 style: adjust grid width of the project list 2024-03-06 15:08:45 +07:00
Andre Hadianto
30ecd41975
Merge pull request #141 from snowball-tools/andrehadianto/T-4904-home-org-switcher
[T-4904] home org switcher
2024-03-06 15:01:39 +08:00
Andre Hadianto
410375f8c7
Merge branch 'main' into andrehadianto/T-4904-home-org-switcher 2024-03-06 15:01:29 +08:00
Andre H
226d02ea59 🔧 chore: change media query to lg 2024-03-06 14:29:28 +08:00
Wahyu Kurniawan
ca64d00355
🎨 style: adjust grid for the project list 2024-03-06 13:22:39 +07:00
Wahyu Kurniawan
287fe360d3
♻️ refactor: use useMemo to render organization and sidebar menu 2024-03-06 13:22:17 +07:00
Andre H
f23c757de9 🔧 chore: create project create repo buttonSize to be responsive 2024-03-06 14:15:19 +08:00
Andre H
3737550f7f 🔧 chore: useMediaQuery change md to >720px 2024-03-06 14:14:49 +08:00
Andre H
274762cbfe ️ feat: create project mobile layout 2024-03-06 14:14:10 +08:00
Wahyu Kurniawan
90295fd620
♻️ refactor: use dialog overlay instead of div 2024-03-06 13:09:23 +07:00
Wahyu Kurniawan
b75957929a
🎨 style: fixed content moves down issue when opening the sidebar 2024-03-06 12:46:49 +07:00
Wahyu Kurniawan
daffcf1203
🎨 style: adjust mobile sidebar animation 2024-03-06 11:53:44 +07:00
Wahyu Kurniawan
eccf78afdb
🎨 style: add background tot the project search header 2024-03-06 11:40:07 +07:00
Wahyu Kurniawan
0901036f71
Merge branch 'main' of https://github.com/snowball-tools/snowballtools-base into ayungavis/T-4931-layout-mobile-sidebar-layout 2024-03-06 11:31:37 +07:00
Wahyu Kurniawan
8d3ef3bafc
Merge pull request #142 from snowball-tools/ayungavis/T-4917-project-deployments-layout-and-empty-state
[T-4917: style] Re-styling and refactor project page deployments layout and empty state
2024-03-06 11:29:21 +07:00
Wahyu Kurniawan
b6e02fb19d
🐛 fix: on reset error when value is undefined 2024-03-06 11:23:25 +07:00
Wahyu Kurniawan
462d247a86
Merge branch 'main' of https://github.com/snowball-tools/snowballtools-base into ayungavis/T-4917-project-deployments-layout-and-empty-state 2024-03-06 11:18:38 +07:00
Wahyu Kurniawan
80097a32ac
️ feat: make the default status to undefined 2024-03-06 11:17:08 +07:00
Wahyu Kurniawan
a80d40156f
🎨 style: remove bouncing animation 2024-03-06 11:11:16 +07:00
Wahyu Kurniawan
ced50bf7f2
🎨 style: add animation to the layout 2024-03-06 10:55:37 +07:00
Wahyu Kurniawan
90b583aa19
Merge branch 'main' of https://github.com/snowball-tools/snowballtools-base into ayungavis/T-4931-layout-mobile-sidebar-layout 2024-03-06 10:54:24 +07:00
Wahyu Kurniawan
48e3581322
️ feat: add handler when click the project item on mobile 2024-03-06 10:42:06 +07:00
Wahyu Kurniawan
1ddc3b81c7
️ feat: add search dialog on mobile 2024-03-06 10:31:11 +07:00
Wahyu Kurniawan
5a86abd133
🎨 style: adjust input and search bar input style 2024-03-06 10:30:50 +07:00
Wahyu Kurniawan
6ddf44cddd
♻️ refactor: project search bar 2024-03-06 10:30:26 +07:00
Wahyu Kurniawan
b03200c256
🎨 style: add animation when sidebar open on mobile 2024-03-06 09:34:41 +07:00
Wahyu Kurniawan
1648deb64f
🔧 chore: install framer-motion 2024-03-06 09:34:06 +07:00
Andre Hadianto
621ca8926e
Merge pull request #150 from snowball-tools/andrehadianto/T-4912-create-project-create-repository-page
[T-4912] create project create repository page
2024-03-06 09:39:33 +08:00
Andre H
8c0162f9f3 🔧 chore: replace Avatar with TemplateIcon on Template 2024-03-06 09:35:18 +08:00
Wahyu Kurniawan
e05e2c63c2
🎨 style: adjust sidebar for mobile 2024-03-06 08:25:29 +07:00
Wahyu Kurniawan
3b1f03bcb6
🎨 style: replacee grid of the layout to use flex and fixed width of the sidebar 2024-03-06 08:24:11 +07:00
Wahyu Kurniawan
80fee1f585
️ feat: create logout icon 2024-03-06 08:23:09 +07:00
Andre Hadianto
d50f559a07
Merge branch 'main' into andrehadianto/T-4912-create-project-create-repository-page 2024-03-06 09:04:28 +08:00
Andre Hadianto
46d44378c5
Merge pull request #155 from snowball-tools/andrehadianto/T-4940-project-overview-mobile-layout
[T-4940] project overview mobile layout
2024-03-06 09:02:02 +08:00
Wahyu Kurniawan
3e42899f2e
Merge pull request #138 from snowball-tools/ayungavis/T-4911-create-project-import-a-repository-section
[T-4911: feat] Re-styling and refactor create project import a repository section
2024-03-05 22:41:43 +07:00
Andre Hadianto
75a048c7a9
Merge pull request #153 from snowball-tools/andrehadianto/T-4935-timeline-component
[T-4935] timeline component
2024-03-05 18:32:08 +08:00
Wahyu Kurniawan
b5f50fe16f
🎨 style: adjust layout for mobile 2024-03-05 16:16:45 +07:00
Wahyu Kurniawan
aa4094669d
️ feat: create logo component 2024-03-05 16:16:20 +07:00
Wahyu Kurniawan
33fbdf60ae
️ feat: create some icons needed 2024-03-05 16:15:37 +07:00
Wahyu Kurniawan
abe1401a6d
🎨 style: adjust the style of project search bar 2024-03-05 16:15:11 +07:00
Andre H
2182beca22 🔧 chore: remove database tab 2024-03-05 14:22:59 +08:00
Andre H
96f519d5e3 ️ feat: mobile layout for project overview 2024-03-05 14:19:47 +08:00
Andre H
f6db1e119b 🔧 chore: add overflow-scroll to Tabs component 2024-03-05 14:18:51 +08:00
Andre H
c079a9c336 🔧 chore: prevent Avatar from shrinking 2024-03-05 14:18:31 +08:00
Andre H
f9ac778e47 ⚒️ build: add usehooks-ts 2024-03-05 14:16:54 +08:00
Andre H
e8ae417772 🔧 chore: whitespace nowrap to prevent text warping on steps 2024-03-05 09:39:00 +08:00
Wahyu Kurniawan
18dde52c9a
️ feat: update select input default icon and options icon 2024-03-05 05:07:21 +07:00
Wahyu Kurniawan
d9bae720d7
️ feat: update date picker input icon 2024-03-05 05:06:53 +07:00
Wahyu Kurniawan
e9f32ff668
♻️ refactor: make the conditional logic to boolean 2024-03-05 05:06:28 +07:00
Wahyu Kurniawan
8a1e84386a
️ feat: create some icons needed 2024-03-05 05:05:54 +07:00
Wahyu Kurniawan
3335c26f82
Merge branch 'main' of https://github.com/snowball-tools/snowballtools-base into ayungavis/T-4917-project-deployments-layout-and-empty-state 2024-03-05 04:37:49 +07:00
Andre H
0a2686d5ec 🔧 chore: add divider to render page 2024-03-04 17:50:23 +08:00
Andre H
c1da92bc99 🔧 chore: adjust design to figma 2024-03-04 17:50:04 +08:00
Andre H
add2559860 🔧 chore: adjust other CheckRoundFilledIcon user 2024-03-04 17:41:33 +08:00
Andre H
460f8a80b8 🔧 chore: replace Stepper with Steps component 2024-03-04 11:52:55 +08:00
Andre H
f5296e9b8d 🔧 chore: first index to be '1' for Steps 2024-03-04 11:52:31 +08:00
Andre H
0aeea36dbd Merge remote-tracking branch 'origin/main' into andrehadianto/T-4935-timeline-component 2024-03-04 11:46:27 +08:00
Andre H
8f0c39022d 🔧 chore: add render component 2024-03-04 10:40:06 +08:00
Andre H
6c42a22972 ️ feat: implement steps/ timeline 2024-03-04 10:38:12 +08:00
Andre H
adb64f2db1 🔧 chore: remove outer padding on the icon 2024-03-04 10:14:21 +08:00
Zachery
64e3aa5b25
[T-4913] Create Project - Deploy page (#152) 2024-03-03 13:41:25 +08:00
neeraj
dc6f436409 Handle review changes 2024-03-01 14:04:43 +05:30
neeraj
691edf3508 Update toast message 2024-03-01 13:26:38 +05:30
Zachery
4fc654f763
[T-4933] Create Project - Mobile modal layout (#151)
* feat: create project mobile modal layout

* style: make dialog content stretch full height
2024-03-01 15:22:16 +08:00
neeraj
fe599f97a3 Handle error if repo already exists 2024-03-01 12:20:19 +05:30
Andre H
be0ea904ee 🔧 chore: REMOVE framework for now 2024-03-01 14:07:12 +08:00
Andre H
8114b10932 ♻️ refactor: use shared Avatar 2024-03-01 14:05:17 +08:00
Andre H
0ee27df25e 🔧 chore: font hierarchy 2024-03-01 14:00:41 +08:00
Andre H
893a499be5 🔧 chore: dont render when props not provided 2024-03-01 14:00:08 +08:00
Andre H
596d8eb326 🔧 chore: add flex to Input and Select 2024-03-01 13:49:30 +08:00
Andre H
e9d38e0d3b 🔧 chore: add htmlFor to label in radioItem 2024-03-01 13:48:54 +08:00
Andre H
ab77ce681a 🔧 chore: radioItem leftIcon use span 2024-03-01 13:48:33 +08:00
Vivian Phung
5be6e14db9
Update README.md 2024-02-29 21:59:41 -07:00
Sushan Yadav
409b654f9b
Project Deployments - Deployed line items (#147)
* feat: add deployment lines

* fix: typo DeploymentMenu
2024-03-01 10:05:50 +05:45
Andre H
4a86952810 🔧 chore: prevent 'gap' due to empty div on Select 2024-03-01 11:26:38 +08:00
Andre H
207b32ce65 ️ feat: integrate useform 2024-03-01 11:23:38 +08:00
Andre H
6e653774be 🔧 chore: reskin with shared components 2024-03-01 11:13:17 +08:00
Andre H
0b64ccc364 🔧 chore: replace space-y-2 with gap-y-2 for input 2024-03-01 11:12:47 +08:00
Andre H
a8ad6c6eec ️ feat: implement card style for radio 2024-03-01 10:41:12 +08:00
65f64a3dcd
Fix frontend deployment config (#148) 2024-02-29 22:45:33 +05:30
d64cf46d89
Fetch latest registry record version and use in deploy script (#146)
* Fetch latest registry record version and increment

* Move reference up

* Remove steps to manually update record values

* Update registry config with new key and bond
2024-02-29 22:29:23 +05:30
1ff5ab3dfd
Assign project domain to latest production deployment (#145)
* Create ApplicationDeploymentRequest with project DNS

* Add comment for filtering AppDeploymentRecord

* Fix lint

* Handle delete gitHub event

* Add sleep before registry write methods

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
2024-02-29 18:33:34 +05:30
Andre H
d9392c095d ️ feat: reskin template header 2024-02-29 15:31:10 +07:00
Andre H
2cd1865d19 🔧 chore: add linkchain icon 2024-02-29 15:30:34 +07:00
Eric Lewis
a7810a34c9
feat: one click deployer (#139)
* feat: one click deployer

This adds a script for deploying whatever is the latest on main, essentially automating the changes specified in the readme.

* fix

* repurpose existing script

* clean up

* fix

* fix

* Update latest record version

---------

Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
2024-02-29 13:11:01 +05:30
Vivian Phung
ff9b08e91d
Merge pull request #143 from snowball-tools/template-card-redirect-bug
Template card redirect bug
2024-02-29 00:38:12 -07:00
94a9bf88a9
Remove check for .env in build script (#144) 2024-02-29 12:51:32 +05:30
Vivian Phung
dd5f93050d
fix navigate 2024-02-29 00:01:56 -07:00
Andre H
11aeda9453 🔧 chore: remove squiggly line 2024-02-29 13:59:53 +07:00
Andre H
dfa38a4cba 🔧 chore: remove useMemo 2024-02-29 13:58:45 +07:00
Andre H
638e31f7fe 🔧 chore: use wavyBorder 2024-02-29 13:58:22 +07:00
Vivian Phung
5946755749
fix navigate 2024-02-28 23:52:37 -07:00
Vivian Phung
4532a962d8
fix navigate 2024-02-28 23:44:10 -07:00
Wahyu Kurniawan
83fec1d273
🎨 style: refactor and re-styling deployment tab page 2024-02-29 11:17:41 +07:00
Wahyu Kurniawan
6db5871868
🎨 style: make the input width to full 2024-02-29 11:17:41 +07:00
Wahyu Kurniawan
e0c5895e9c
️ feat: add onReset prop 2024-02-29 11:17:41 +07:00
Wahyu Kurniawan
630af612a2
️ feat: add some components 2024-02-29 11:17:41 +07:00
Wahyu Kurniawan
87307381cb
🎨 style: refactor and re-styling filter form component 2024-02-29 11:17:40 +07:00
Sushan Yadav
6a108c1a1b
Merge pull request #137 from snowball-tools/sushan/T-4914-project-overview-layout
Project overview layout
2024-02-29 09:57:12 +05:45
Andre Hadianto
aa5507f309
Merge branch 'main' into andrehadianto/T-4904-home-org-switcher 2024-02-29 10:34:36 +08:00
Andre H
91a80cac8a ️ feat: add squiggly line and personal TODO 2024-02-29 09:30:53 +07:00
Andre H
ee397d0e6e 🔧 chore: add squigglyline icon 2024-02-29 09:30:29 +07:00
Wahyu Kurniawan
98ec1915ca
♻️ refactor: remove unused comment 2024-02-29 09:21:49 +07:00
Wahyu Kurniawan
f08081932c
♻️ refactor: use custom toast component and handle error 2024-02-29 09:21:28 +07:00
Andre H
fe427a9fb8 🐛 fix: onChange not triggered. thanks @ayungavis 2024-02-29 09:16:01 +07:00
Andre H
a5b4103515 🔧 chore: bigger img on userselectitem 2024-02-29 09:15:18 +07:00
Andre H
f77f7c120a 🔧 chore: follow select changes 2024-02-29 08:52:26 +07:00
Vivian Phung
22f3cbafb7
readme 2024-02-28 17:27:27 -07:00
Andre H
30c50a3eec 🔧 chore: use UserSelect in sidebar 2024-02-28 23:08:21 +07:00
Andre H
0381fdbbed ️ feat: impelment user select 2024-02-28 23:07:54 +07:00
Andre H
2f466d4fbb 🔧 chore: remove dot when description dont exist 2024-02-28 23:07:31 +07:00
Andre H
ee35e64f69 🔧 chore: add icons 2024-02-28 23:06:54 +07:00
Sushan Yadav
0fb378386a chore: update comment 2024-02-28 20:51:58 +05:45
Sushan Yadav
563059e887 chore: comment fixes - tab issues and hardcoded strings 2024-02-28 20:46:08 +05:45
Wahyu Kurniawan
075cec58e5
🎨 style: add github icon 2024-02-28 21:27:04 +07:00
Wahyu Kurniawan
d52a34da35
🐛 fix: controlled and uncontroller console error 2024-02-28 21:18:29 +07:00
Wahyu Kurniawan
eb4ccfbc9c
🎨 style: make the input width full 2024-02-28 21:17:59 +07:00
Wahyu Kurniawan
769593913e
🐛 fix: button inside button console browser error 2024-02-28 21:17:30 +07:00
Wahyu Kurniawan
eda2cc76ed
♻️ refactor: restructured and restyling project repo card component 2024-02-28 21:16:59 +07:00
Wahyu Kurniawan
99eb514306
♻️ refactor: restructured and restryling repository list component 2024-02-28 21:16:26 +07:00
Sushan Yadav
f34eb48d1e feat: add layout 2024-02-28 17:35:02 +05:45
Sushan Yadav
acf5aab26f Merge branch 'main' into sushan/T-4914-project-overview-layout 2024-02-28 16:57:32 +05:45
Sushan Yadav
6b6582c287 feat: top section 2024-02-28 16:56:28 +05:45
Sushan Yadav
b43ee3b7bb feat: add deplyment infos 2024-02-28 16:39:26 +05:45
Wahyu Kurniawan
b1bf47d104
[T-4921: feat] Update project card component (#136)
* 🎨 style: add hover interaction to the card

* ️ feat: make the whole card clickable

* 🎨 style: adjust hovered background for wavy border

* ♻️ refactor: move wavy border class to project card theme

* 🎨 style: add transition when hover

* 📝 docs: add todo to experiment using `Link` componnt
2024-02-28 16:50:55 +07:00
Wahyu Kurniawan
8ee61c0c85
[T-4910: feat] Re-styling dashboard, create project layout, project template card (#135)
* 🎨 style: adjust wavy border and add layout wavy border

* ♻️ refactor: change sidebar to use `nav`

* ♻️ refactor: org slug dashboard layout

* ♻️ refactor: create project layout and restyling it

* ♻️ refactor: remove unused style

* ️ feat: restyling template card

* ️ feat: create template icon component

* ️ feat: use `h2` for layout title

* ️ feat: Add isComingSoon property to templates and handle click event in TemplateCard component

* ♻️ refactor: WavyBorder component and update CreateProjectLayout

* 🎨 style: update button medium size padding

* 🎨 style: update layout shadow and add new shadow for the template card

* ️ feat: add wavy border gradient and line svg assets

* refactor: update wavy border svg

* 🎨 style: adjust template card name and arrow also responsive of the list of template

---------

Co-authored-by: Zachery Ng <zachery.ng@gmail.com>
2024-02-28 16:22:54 +07:00
Andre Hadianto
33aa75e341
Merge pull request #133 from snowball-tools/andrehadianto/T-4906-create-project-connect-github
[T-4906] create project connect GitHub
2024-02-28 15:55:59 +08:00
Sushan Yadav
a238610522
Reskin Project Overview - Activity section (#134)
* feat: reskin w/ new design system

* feat: add sepeator calc in comment

* chore: limit commit message to 4 line

* comment fixes

* chore: use Heading component
2024-02-28 13:31:41 +05:45
Zachery Ng
bd4a51c830 style: update tertiary button 2024-02-28 15:45:57 +08:00
Andre H
4a87c100d6 🔧 chore: use proper type 2024-02-28 14:20:12 +07:00
Andre H
3493d735b9 🔧 chore: replace bgClass with className 2024-02-28 14:19:02 +07:00
Andre H
a324d32ebf 🔧 chore: use Heading 2024-02-28 13:51:35 +07:00
Andre H
4519494be8 ♻️ refactor: cherry pick fomr main 2024-02-28 13:49:11 +07:00
Andre H
319822c8e6 🔧 chore: add shadow to button 2024-02-28 13:46:09 +07:00
Andre H
c5273ff530 ♻️ refactor: use IconWithFrame 2024-02-28 13:39:50 +07:00
Andre H
2585418ed6 🔧 chore: implement IconWithFrame common component 2024-02-28 13:39:30 +07:00
Wahyu Kurniawan
62734308fc
[T-4907: style] Re-styling home page & sidebar component (#132)
* ️ feat: create heading component

* ♻️ refactor: move sidebar inside shared components

* ️ feat: add polymorphic prop type for heading component

* 🎨 style: re-styling project list page

* ♻️ refactor: remove `.env`

* 🎨 style: set default font weight to normal for heading component
2024-02-28 12:50:30 +07:00
Andre H
89306ddcc7 🔧 chore: add toast 2024-02-28 11:19:09 +07:00
Andre H
3b67396c43 ️ feat: reskin connect github 2024-02-28 10:55:10 +07:00
Andre H
3535bd3a58 🔧 chore: add GitTeaIcon 2024-02-28 10:53:05 +07:00
563 changed files with 27709 additions and 12200 deletions

View File

@ -0,0 +1,61 @@
name: Deploy Snowball frontend
on:
push:
branches:
- main
env:
REGISTRY_USER_KEY: ${{ secrets.REGISTRY_USER_KEY }}
REGISTRY_BOND_ID: ${{ secrets.REGISTRY_BOND_ID }}
DEPLOYER_LRN: lrn://vaasl-provider/deployers/webapp-deployer-api.apps.vaasl.io
AUTHORITY: laconic-deploy
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- name: Check out repository
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Download yarn
run: |
curl -fsSL -o /usr/local/bin/yarn https://github.com/yarnpkg/yarn/releases/download/v1.22.21/yarn-1.22.21.js
chmod +x /usr/local/bin/yarn
- name: Install dependencies
run: |
yarn install
- name: Set up environment
run: |
# Create a .env file with the necessary variables
echo "REGISTRY_BOND_ID=$REGISTRY_BOND_ID" > packages/deployer/.env
echo "DEPLOYER_LRN=$DEPLOYER_LRN" >> packages/deployer/.env
echo "AUTHORITY=$AUTHORITY" >> packages/deployer/.env
# Create a config file with necessary endpoints and secrets
cat > packages/deployer/config.yml <<EOF
services:
registry:
rpcEndpoint: https://laconicd-sapo.laconic.com
gqlEndpoint: https://laconicd-sapo.laconic.com/api
userKey: $REGISTRY_USER_KEY
bondId: $REGISTRY_BOND_ID
chainId: laconic-testnet-2
gasPrice: 0.001alnt
EOF
- name: Run deploy script
run: |
cd packages/deployer
./deploy-frontend.sh

View File

@ -0,0 +1,30 @@
name: Lint
on:
pull_request:
push:
branches:
- main
- staging
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

View File

@ -19,5 +19,7 @@ jobs:
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: yarn - run: yarn
- name: Build libs
run: yarn workspace gql-client run build
- name: Linter check - name: Linter check
run: yarn lint run: yarn lint

View File

@ -0,0 +1,39 @@
name: Test webapp deployment
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
jobs:
test_app_deployment:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: yarn
- name: Test webapp deployment
run: ./packages/deployer/test/test-webapp-deployment-undeployment.sh
- name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.VULCANIZE_SLACK_CI_ALERTS_WEBHOOK }}
- name: Notify DeepStack Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.DEEPSTACK_SLACK_CI_ALERTS_WEBHOOK }}

5
.gitignore vendored
View File

@ -5,3 +5,8 @@ yarn-error.log
.yarnrc .yarnrc
packages/backend/environments/local.toml packages/backend/environments/local.toml
packages/backend/dev/
packages/frontend/dist/
# ignore all .DS_Store files
**/.DS_Store

1
.node-version Normal file
View File

@ -0,0 +1 @@
v20.12.1

View File

@ -1,6 +1,7 @@
{ {
// IntelliSense for taiwind variants // IntelliSense for taiwind variants
"tailwindCSS.experimental.classRegex": [ "tailwindCSS.experimental.classRegex": [
["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] "tv\\('([^)]*)\\')",
"(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"
] ]
} }

272
README.md
View File

@ -1,269 +1,23 @@
# snowballtools-base # snowballtools-base
## Setup This is a [yarn workspace](https://yarnpkg.com/features/workspaces) monorepo for the dashboard.
- Clone the `snowballtools-base` repo ## Getting Started
```bash ### Install dependencies
git clone git@github.com:snowball-tools/snowballtools-base.git
```
- In root of the repo, install depedencies In the root of the project, run:
```bash ```zsh
yarn yarn
``` ```
- Build packages ### Build backend
```bash ```zsh
yarn build --ignore frontend yarn build --ignore frontend
``` ```
## Backend ### Environment variables, running the development server, and deployment
- Change directory to `packages/backend` Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and [frontend](packages/frontend/README.md) packages.
```bash
cd packages/backend
```
- Rename backend config file from [environments/local.toml.example](packages/backend/environments/local.toml.example) to `local.toml`
```bash
mv environments/local.toml.example environments/local.toml
```
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
- In "Homepage URL", type `http://localhost:3000`
- In "Authorization callback URL", type `http://localhost:3000/organization/projects/create`
- Generate a new client secret after app is created
### Backend Production
- Let us assume the following domains for backend and frontend
- Backend server: `api.snowballtools.com`
- Frontend app: `dashboard.snowballtools.com`
- 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)
- Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
- In "Homepage URL", type `https://dashboard.snowballtools.com`
- In "Authorization callback URL", type `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"
...
```
- Start the server in `packages/backend`
```bash
yarn start
```
### Backend Development
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
- In "Homepage URL", type `http://localhost:3000`
- In "Authorization callback URL", type `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 --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
```
- Get the REST and GQL endpoint ports of Laconicd and replace the ports for `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
```bash
# For registryConfig.restEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
# 0.0.0.0:32777
# For registryConfig.gqlEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
# 0.0.0.0:32771
```
- Set authority in `registryConfig.authority` in backend [config file](packages/backend/environments/local.toml)
- Run the script to create bond, reserve the authority and set authority bond
```bash
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"
...
```
- Start the server in `packages/backend`
```bash
yarn start:dev
```
## Frontend
- Change directory to `packages/frontend` in a new terminal
```bash
cd packages/frontend
```
- Rename [.env.example](packages/frontend/.env.example) to `.env`
```bash
mv .env.example .env
```
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
```
- Set `REACT_APP_GITHUB_PWA_TEMPLATE_REPO` and `REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
```env
# Set actual owner/name of the template repo that will be used for creating new repo
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = cerc-io/test-progressive-web-app
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = cerc-io/image-upload-pwa-example
```
### Frontend Production
- Let us assume the following domains for backend and frontend
- Backend server: `api.snowballtools.com`
- Frontend app: `dashboard.snowballtools.com`
- Set the following values in [.env](packages/frontend/.env) file
```env
# Backend server endpoint
REACT_APP_SERVER_URL = 'https://api.snowballtools.com'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Set project home page URL to `https://dashboard.snowballtools.com`
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_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
```
### Frontend Development
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
```env
REACT_APP_SERVER_URL = 'http://localhost:8000'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID.
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Project home page URL is not required to be set
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <Project_ID>
```
- Start the React application
```bash
yarn start
```
- The React application will be running in `http://localhost:3000/`

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
PKG_DIR="./packages/frontend" PKG_DIR="./packages/frontend"
OUTPUT_DIR="${PKG_DIR}/build" OUTPUT_DIR="${PKG_DIR}/dist"
DEST_DIR=${1:-/data} DEST_DIR=${1:-/data}
if [[ -d "$DEST_DIR" ]]; then if [[ -d "$DEST_DIR" ]]; then
@ -9,24 +9,23 @@ if [[ -d "$DEST_DIR" ]]; then
exit 1 exit 1
fi fi
if [[ -f "$PKG_DIR/.env" ]]; then
echo "Using existing .env file"
else
mv "$PKG_DIR/.env.example" "$PKG_DIR/.env"
echo "Created .env file. Please populate with the correct values."
exit 1
fi
cat > $PKG_DIR/.env <<EOF cat > $PKG_DIR/.env <<EOF
REACT_APP_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_server_url' VITE_SERVER_URL = 'LACONIC_HOSTED_CONFIG_server_url'
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid' VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo' VITE_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_pwa_templaterepo'
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo' VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo'
REACT_APP_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_app_wallet_connect_id' VITE_GITHUB_NEXT_APP_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_next_app_templaterepo'
VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
VITE_LACONICD_CHAIN_ID = 'LACONIC_HOSTED_CONFIG_laconicd_chain_id'
VITE_WALLET_IFRAME_URL = 'LACONIC_HOSTED_CONFIG_wallet_iframe_url'
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
VITE_TURNKEY_API_BASE_URL = 'LACONIC_HOSTED_CONFIG_turnkey_api_base_url'
EOF EOF
yarn || exit 1 yarn || exit 1
yarn build || exit 1 yarn build --ignore backend || exit 1
if [[ ! -d "$OUTPUT_DIR" ]]; then if [[ ! -d "$OUTPUT_DIR" ]]; then
echo "Missing output directory: $OUTPUT_DIR" 1>&2 echo "Missing output directory: $OUTPUT_DIR" 1>&2

View File

@ -4,15 +4,15 @@
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
], ],
"dependencies": {},
"devDependencies": { "devDependencies": {
"depcheck": "^1.4.2",
"husky": "^8.0.3", "husky": "^8.0.3",
"lerna": "^8.0.0", "lerna": "^8.0.0",
"depcheck": "^1.4.2" "patch-package": "^8.0.0"
}, },
"scripts": { "scripts": {
"prepare": "husky install", "prepare": "husky install",
"build": "lerna run build --stream", "build": "lerna run build --stream",
"lint": "lerna run lint --stream -- --max-warnings=0" "lint": "lerna run lint --stream"
} }
} }

View File

@ -0,0 +1 @@
v20.12.1

View File

@ -1 +1,76 @@
# Backend for Snowball Tools # backend
This backend is a [node.js](https://nodejs.org/) [express.js](https://expressjs.com/) [apollo server](https://www.apollographql.com/docs/apollo-server/) project in a [yarn workspace](https://yarnpkg.com/features/workspaces).
## Getting Started
### Install dependencies
In the root of the project, run:
```zsh
yarn
```
### Build backend
```zsh
yarn build --ignore frontend
```
### Environment variables
#### Local
Copy the `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)

View File

@ -3,9 +3,12 @@
port = 8000 port = 8000
gqlPath = "/graphql" gqlPath = "/graphql"
[server.session] [server.session]
secret = "p4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi" secret = ""
# Frontend webapp URL origin
appOriginUrl = "http://localhost:3000" appOriginUrl = "http://localhost:3000"
# Set to true if server running behind proxy
trustProxy = false trustProxy = false
# Backend URL hostname
domain = "localhost" domain = "localhost"
[database] [database]
@ -19,6 +22,7 @@
[registryConfig] [registryConfig]
fetchDeploymentRecordDelay = 5000 fetchDeploymentRecordDelay = 5000
checkAuctionStatusDelay = 5000
restEndpoint = "http://localhost:1317" restEndpoint = "http://localhost:1317"
gqlEndpoint = "http://localhost:9473/api" gqlEndpoint = "http://localhost:9473/api"
chainId = "laconic_9000-1" chainId = "laconic_9000-1"
@ -26,9 +30,14 @@
bondId = "" bondId = ""
authority = "" authority = ""
[registryConfig.fee] [registryConfig.fee]
amount = "200000" gas = ""
denom = "aphoton" fees = ""
gas = "750000" gasPrice = "1alnt"
[misc] # Durations are set to 2 mins as deployers may take time with ongoing deployments and auctions
projectDomain = "apps.snowballtools.com" [auction]
commitFee = "100000"
commitsDuration = "120s"
revealFee = "100000"
revealsDuration = "120s"
denom = "alnt"

View File

@ -1,20 +1,25 @@
{ {
"name": "backend", "name": "backend",
"license": "UNLICENSED",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@cerc-io/laconic-sdk": "^0.1.14", "@cerc-io/registry-sdk": "^0.2.11",
"@graphql-tools/schema": "^10.0.2", "@graphql-tools/schema": "^10.0.2",
"@graphql-tools/utils": "^10.0.12", "@graphql-tools/utils": "^10.0.12",
"@octokit/oauth-app": "^6.1.0", "@octokit/oauth-app": "^6.1.0",
"@turnkey/sdk-server": "^0.1.0",
"@types/debug": "^4.1.5", "@types/debug": "^4.1.5",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/node": "^20.11.0", "@types/node": "^20.11.0",
"@types/semver": "^7.5.8",
"apollo-server-core": "^3.13.0", "apollo-server-core": "^3.13.0",
"apollo-server-express": "^3.13.0", "apollo-server-express": "^3.13.0",
"cookie-session": "^2.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"debug": "^4.3.1", "debug": "^4.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-async-errors": "^3.1.1",
"express-session": "^1.18.0", "express-session": "^1.18.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"graphql": "^16.8.1", "graphql": "^16.8.1",
@ -35,30 +40,21 @@
"copy-assets": "copyfiles -u 1 src/**/*.gql dist/", "copy-assets": "copyfiles -u 1 src/**/*.gql dist/",
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"build": "yarn clean && tsc && yarn copy-assets", "build": "yarn clean && tsc && yarn copy-assets",
"lint": "eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"lint": "tsc --noEmit",
"test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts", "test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts",
"test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts", "test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts",
"test:registry:publish-deployment-removal-records": "DEBUG=snowball:* ts-node ./test/publish-deployment-removal-records.ts",
"test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts", "test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts",
"test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts" "test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts"
}, },
"devDependencies": { "devDependencies": {
"@types/cookie-session": "^2.0.49",
"@types/express-session": "^1.17.10", "@types/express-session": "^1.17.10",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"better-sqlite3": "^9.2.2", "better-sqlite3": "^9.2.2",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-semistandard": "^15.0.1",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"workspace": "^0.0.1-preview.1" "workspace": "^0.0.1-preview.1"
} }

View File

@ -34,16 +34,21 @@ export interface RegistryConfig {
privateKey: string; privateKey: string;
bondId: string; bondId: string;
fetchDeploymentRecordDelay: number; fetchDeploymentRecordDelay: number;
checkAuctionStatusDelay: number;
authority: string; authority: string;
fee: { fee: {
amount: string;
denom: string;
gas: string; gas: string;
fees: string;
gasPrice: string;
}; };
} }
export interface MiscConfig { export interface AuctionConfig {
projectDomain: string; commitFee: string;
commitsDuration: string;
revealFee: string;
revealsDuration: string;
denom: string;
} }
export interface Config { export interface Config {
@ -51,5 +56,11 @@ export interface Config {
database: DatabaseConfig; database: DatabaseConfig;
gitHub: GitHubConfig; gitHub: GitHubConfig;
registryConfig: RegistryConfig; registryConfig: RegistryConfig;
misc: MiscConfig; auction: AuctionConfig;
turnkey: {
apiBaseUrl: string;
apiPublicKey: string;
apiPrivateKey: string;
defaultOrganizationId: string;
};
} }

View File

@ -3,7 +3,9 @@ import {
DeepPartial, DeepPartial,
FindManyOptions, FindManyOptions,
FindOneOptions, FindOneOptions,
FindOptionsWhere FindOptionsWhere,
IsNull,
Not
} from 'typeorm'; } from 'typeorm';
import path from 'path'; import path from 'path';
import debug from 'debug'; import debug from 'debug';
@ -11,7 +13,7 @@ import assert from 'assert';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import { lowercase, numbers } from 'nanoid-dictionary'; import { lowercase, numbers } from 'nanoid-dictionary';
import { DatabaseConfig, MiscConfig } from './config'; import { DatabaseConfig } from './config';
import { User } from './entity/User'; import { User } from './entity/User';
import { Organization } from './entity/Organization'; import { Organization } from './entity/Organization';
import { Project } from './entity/Project'; import { Project } from './entity/Project';
@ -21,6 +23,7 @@ import { EnvironmentVariable } from './entity/EnvironmentVariable';
import { Domain } from './entity/Domain'; import { Domain } from './entity/Domain';
import { getEntities, loadAndSaveData } from './utils'; import { getEntities, loadAndSaveData } from './utils';
import { UserOrganization } from './entity/UserOrganization'; import { UserOrganization } from './entity/UserOrganization';
import { Deployer } from './entity/Deployer';
const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json'; const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json';
@ -31,9 +34,8 @@ const nanoid = customAlphabet(lowercase + numbers, 8);
// TODO: Fix order of methods // TODO: Fix order of methods
export class Database { export class Database {
private dataSource: DataSource; private dataSource: DataSource;
private projectDomain: string;
constructor ({ dbPath } : DatabaseConfig, { projectDomain } : MiscConfig) { constructor({ dbPath }: DatabaseConfig) {
this.dataSource = new DataSource({ this.dataSource = new DataSource({
type: 'better-sqlite3', type: 'better-sqlite3',
database: dbPath, database: dbPath,
@ -41,38 +43,49 @@ export class Database {
synchronize: true, synchronize: true,
logging: false logging: false
}); });
this.projectDomain = projectDomain;
} }
async init (): Promise<void> { async init(): Promise<void> {
await this.dataSource.initialize(); await this.dataSource.initialize();
log('database initialized'); log('database initialized');
const organizations = await this.getOrganizations({}); let organizations = await this.getOrganizations({});
// Load an organization if none exist // Load an organization if none exist
if (!organizations.length) { if (!organizations.length) {
const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH)); const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH));
await loadAndSaveData(Organization, this.dataSource, [orgEntities[0]]); organizations = await loadAndSaveData(Organization, this.dataSource, [orgEntities[0]]);
}
// Hotfix for updating old DB data
if (organizations[0].slug === 'snowball-tools-1') {
const [orgEntity] = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH));
await this.updateOrganization(
organizations[0].id,
{
slug: orgEntity.slug as string,
name: orgEntity.name as string
}
)
} }
} }
async getUser (options: FindOneOptions<User>): Promise<User | null> { async getUser(options: FindOneOptions<User>): Promise<User | null> {
const userRepository = this.dataSource.getRepository(User); const userRepository = this.dataSource.getRepository(User);
const user = await userRepository.findOne(options); const user = await userRepository.findOne(options);
return user; return user;
} }
async addUser (data: DeepPartial<User>): Promise<User> { async addUser(data: DeepPartial<User>): Promise<User> {
const userRepository = this.dataSource.getRepository(User); const userRepository = this.dataSource.getRepository(User);
const user = await userRepository.save(data); const user = await userRepository.save(data);
return user; return user;
} }
async updateUser (user: User, data: DeepPartial<User>): Promise<boolean> { async updateUser(user: User, data: DeepPartial<User>): Promise<boolean> {
const userRepository = this.dataSource.getRepository(User); const userRepository = this.dataSource.getRepository(User);
const updateResult = await userRepository.update({ id: user.id }, data); const updateResult = await userRepository.update({ id: user.id }, data);
assert(updateResult.affected); assert(updateResult.affected);
@ -80,7 +93,7 @@ export class Database {
return updateResult.affected > 0; return updateResult.affected > 0;
} }
async getOrganizations ( async getOrganizations(
options: FindManyOptions<Organization> options: FindManyOptions<Organization>
): Promise<Organization[]> { ): Promise<Organization[]> {
const organizationRepository = this.dataSource.getRepository(Organization); const organizationRepository = this.dataSource.getRepository(Organization);
@ -89,7 +102,7 @@ export class Database {
return organizations; return organizations;
} }
async getOrganization ( async getOrganization(
options: FindOneOptions<Organization> options: FindOneOptions<Organization>
): Promise<Organization | null> { ): Promise<Organization | null> {
const organizationRepository = this.dataSource.getRepository(Organization); const organizationRepository = this.dataSource.getRepository(Organization);
@ -98,7 +111,7 @@ export class Database {
return organization; return organization;
} }
async getOrganizationsByUserId (userId: string): Promise<Organization[]> { async getOrganizationsByUserId(userId: string): Promise<Organization[]> {
const organizationRepository = this.dataSource.getRepository(Organization); const organizationRepository = this.dataSource.getRepository(Organization);
const userOrgs = await organizationRepository.find({ const userOrgs = await organizationRepository.find({
@ -114,21 +127,29 @@ export class Database {
return userOrgs; return userOrgs;
} }
async addUserOrganization (data: DeepPartial<UserOrganization>): Promise<UserOrganization> { async addUserOrganization(data: DeepPartial<UserOrganization>): Promise<UserOrganization> {
const userOrganizationRepository = this.dataSource.getRepository(UserOrganization); const userOrganizationRepository = this.dataSource.getRepository(UserOrganization);
const newUserOrganization = await userOrganizationRepository.save(data); const newUserOrganization = await userOrganizationRepository.save(data);
return newUserOrganization; return newUserOrganization;
} }
async getProjects (options: FindManyOptions<Project>): Promise<Project[]> { async updateOrganization(organizationId: string, data: DeepPartial<Organization>): Promise<boolean> {
const organizationRepository = this.dataSource.getRepository(Organization);
const updateResult = await organizationRepository.update({ id: organizationId }, data);
assert(updateResult.affected);
return updateResult.affected > 0;
}
async getProjects(options: FindManyOptions<Project>): Promise<Project[]> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);
const projects = await projectRepository.find(options); const projects = await projectRepository.find(options);
return projects; return projects;
} }
async getProjectById (projectId: string): Promise<Project | null> { async getProjectById(projectId: string): Promise<Project | null> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);
const project = await projectRepository const project = await projectRepository
@ -140,7 +161,9 @@ export class Database {
) )
.leftJoinAndSelect('deployments.createdBy', 'user') .leftJoinAndSelect('deployments.createdBy', 'user')
.leftJoinAndSelect('deployments.domain', 'domain') .leftJoinAndSelect('deployments.domain', 'domain')
.leftJoinAndSelect('deployments.deployer', 'deployer')
.leftJoinAndSelect('project.owner', 'owner') .leftJoinAndSelect('project.owner', 'owner')
.leftJoinAndSelect('project.deployers', 'deployers')
.leftJoinAndSelect('project.organization', 'organization') .leftJoinAndSelect('project.organization', 'organization')
.where('project.id = :projectId', { .where('project.id = :projectId', {
projectId projectId
@ -150,7 +173,25 @@ export class Database {
return project; return project;
} }
async getProjectsInOrganization ( async allProjectsWithoutDeployments(): Promise<Project[]> {
const allProjects = await this.getProjects({
where: {
auctionId: Not(IsNull()),
},
relations: ['deployments'],
withDeleted: true,
});
const projects = allProjects.filter(project => {
if (project.deletedAt !== null) return false;
return project.deployments.length === 0;
});
return projects;
}
async getProjectsInOrganization(
userId: string, userId: string,
organizationSlug: string organizationSlug: string
): Promise<Project[]> { ): Promise<Project[]> {
@ -181,7 +222,7 @@ export class Database {
/** /**
* Get deployments with specified filter * Get deployments with specified filter
*/ */
async getDeployments ( async getDeployments(
options: FindManyOptions<Deployment> options: FindManyOptions<Deployment>
): Promise<Deployment[]> { ): Promise<Deployment[]> {
const deploymentRepository = this.dataSource.getRepository(Deployment); const deploymentRepository = this.dataSource.getRepository(Deployment);
@ -190,12 +231,13 @@ export class Database {
return deployments; return deployments;
} }
async getDeploymentsByProjectId (projectId: string): Promise<Deployment[]> { async getDeploymentsByProjectId(projectId: string): Promise<Deployment[]> {
return this.getDeployments({ return this.getDeployments({
relations: { relations: {
project: true, project: true,
domain: true, domain: true,
createdBy: true createdBy: true,
deployer: true,
}, },
where: { where: {
project: { project: {
@ -208,7 +250,7 @@ export class Database {
}); });
} }
async getDeployment ( async getDeployment(
options: FindOneOptions<Deployment> options: FindOneOptions<Deployment>
): Promise<Deployment | null> { ): Promise<Deployment | null> {
const deploymentRepository = this.dataSource.getRepository(Deployment); const deploymentRepository = this.dataSource.getRepository(Deployment);
@ -217,14 +259,14 @@ export class Database {
return deployment; return deployment;
} }
async getDomains (options: FindManyOptions<Domain>): Promise<Domain[]> { async getDomains(options: FindManyOptions<Domain>): Promise<Domain[]> {
const domainRepository = this.dataSource.getRepository(Domain); const domainRepository = this.dataSource.getRepository(Domain);
const domains = await domainRepository.find(options); const domains = await domainRepository.find(options);
return domains; return domains;
} }
async addDeployment (data: DeepPartial<Deployment>): Promise<Deployment> { async addDeployment(data: DeepPartial<Deployment>): Promise<Deployment> {
const deploymentRepository = this.dataSource.getRepository(Deployment); const deploymentRepository = this.dataSource.getRepository(Deployment);
const id = nanoid(); const id = nanoid();
@ -238,7 +280,7 @@ export class Database {
return deployment; return deployment;
} }
async getProjectMembersByProjectId ( async getProjectMembersByProjectId(
projectId: string projectId: string
): Promise<ProjectMember[]> { ): Promise<ProjectMember[]> {
const projectMemberRepository = const projectMemberRepository =
@ -259,7 +301,7 @@ export class Database {
return projectMembers; return projectMembers;
} }
async getEnvironmentVariablesByProjectId ( async getEnvironmentVariablesByProjectId(
projectId: string, projectId: string,
filter?: FindOptionsWhere<EnvironmentVariable> filter?: FindOptionsWhere<EnvironmentVariable>
): Promise<EnvironmentVariable[]> { ): Promise<EnvironmentVariable[]> {
@ -278,7 +320,7 @@ export class Database {
return environmentVariables; return environmentVariables;
} }
async removeProjectMemberById (projectMemberId: string): Promise<boolean> { async removeProjectMemberById(projectMemberId: string): Promise<boolean> {
const projectMemberRepository = const projectMemberRepository =
this.dataSource.getRepository(ProjectMember); this.dataSource.getRepository(ProjectMember);
@ -293,7 +335,7 @@ export class Database {
} }
} }
async updateProjectMemberById ( async updateProjectMemberById(
projectMemberId: string, projectMemberId: string,
data: DeepPartial<ProjectMember> data: DeepPartial<ProjectMember>
): Promise<boolean> { ): Promise<boolean> {
@ -307,7 +349,7 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async addProjectMember ( async addProjectMember(
data: DeepPartial<ProjectMember> data: DeepPartial<ProjectMember>
): Promise<ProjectMember> { ): Promise<ProjectMember> {
const projectMemberRepository = const projectMemberRepository =
@ -317,7 +359,7 @@ export class Database {
return newProjectMember; return newProjectMember;
} }
async addEnvironmentVariables ( async addEnvironmentVariables(
data: DeepPartial<EnvironmentVariable>[] data: DeepPartial<EnvironmentVariable>[]
): Promise<EnvironmentVariable[]> { ): Promise<EnvironmentVariable[]> {
const environmentVariableRepository = const environmentVariableRepository =
@ -328,7 +370,7 @@ export class Database {
return savedEnvironmentVariables; return savedEnvironmentVariables;
} }
async updateEnvironmentVariable ( async updateEnvironmentVariable(
environmentVariableId: string, environmentVariableId: string,
data: DeepPartial<EnvironmentVariable> data: DeepPartial<EnvironmentVariable>
): Promise<boolean> { ): Promise<boolean> {
@ -342,7 +384,7 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async deleteEnvironmentVariable ( async deleteEnvironmentVariable(
environmentVariableId: string environmentVariableId: string
): Promise<boolean> { ): Promise<boolean> {
const environmentVariableRepository = const environmentVariableRepository =
@ -358,7 +400,7 @@ export class Database {
} }
} }
async getProjectMemberById (projectMemberId: string): Promise<ProjectMember> { async getProjectMemberById(projectMemberId: string): Promise<ProjectMember> {
const projectMemberRepository = const projectMemberRepository =
this.dataSource.getRepository(ProjectMember); this.dataSource.getRepository(ProjectMember);
@ -381,7 +423,7 @@ export class Database {
return projectMemberWithProject[0]; return projectMemberWithProject[0];
} }
async getProjectsBySearchText ( async getProjectsBySearchText(
userId: string, userId: string,
searchText: string searchText: string
): Promise<Project[]> { ): Promise<Project[]> {
@ -403,14 +445,14 @@ export class Database {
return projects; return projects;
} }
async updateDeploymentById ( async updateDeploymentById(
deploymentId: string, deploymentId: string,
data: DeepPartial<Deployment> data: DeepPartial<Deployment>
): Promise<boolean> { ): Promise<boolean> {
return this.updateDeployment({ id: deploymentId }, data); return this.updateDeployment({ id: deploymentId }, data);
} }
async updateDeployment ( async updateDeployment(
criteria: FindOptionsWhere<Deployment>, criteria: FindOptionsWhere<Deployment>,
data: DeepPartial<Deployment> data: DeepPartial<Deployment>
): Promise<boolean> { ): Promise<boolean> {
@ -420,7 +462,7 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async updateDeploymentsByProjectIds ( async updateDeploymentsByProjectIds(
projectIds: string[], projectIds: string[],
data: DeepPartial<Deployment> data: DeepPartial<Deployment>
): Promise<boolean> { ): Promise<boolean> {
@ -436,7 +478,20 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async addProject (user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> { async deleteDeploymentById(deploymentId: string): Promise<boolean> {
const deploymentRepository = this.dataSource.getRepository(Deployment);
const deployment = await deploymentRepository.findOneOrFail({
where: {
id: deploymentId
}
});
const deleteResult = await deploymentRepository.softRemove(deployment);
return Boolean(deleteResult);
}
async addProject(user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);
// TODO: Check if organization exists // TODO: Check if organization exists
@ -452,12 +507,16 @@ export class Database {
id: organizationId id: organizationId
}); });
newProject.subDomain = `${newProject.name}.${this.projectDomain}`;
return projectRepository.save(newProject); return projectRepository.save(newProject);
} }
async updateProjectById ( async saveProject(project: Project): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project);
return projectRepository.save(project);
}
async updateProjectById(
projectId: string, projectId: string,
data: DeepPartial<Project> data: DeepPartial<Project>
): Promise<boolean> { ): Promise<boolean> {
@ -470,7 +529,7 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async deleteProjectById (projectId: string): Promise<boolean> { async deleteProjectById(projectId: string): Promise<boolean> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);
const project = await projectRepository.findOneOrFail({ const project = await projectRepository.findOneOrFail({
where: { where: {
@ -486,7 +545,7 @@ export class Database {
return Boolean(deleteResult); return Boolean(deleteResult);
} }
async deleteDomainById (domainId: string): Promise<boolean> { async deleteDomainById(domainId: string): Promise<boolean> {
const domainRepository = this.dataSource.getRepository(Domain); const domainRepository = this.dataSource.getRepository(Domain);
const deleteResult = await domainRepository.softDelete({ id: domainId }); const deleteResult = await domainRepository.softDelete({ id: domainId });
@ -498,21 +557,21 @@ export class Database {
} }
} }
async addDomain (data: DeepPartial<Domain>): Promise<Domain> { async addDomain(data: DeepPartial<Domain>): Promise<Domain> {
const domainRepository = this.dataSource.getRepository(Domain); const domainRepository = this.dataSource.getRepository(Domain);
const newDomain = await domainRepository.save(data); const newDomain = await domainRepository.save(data);
return newDomain; return newDomain;
} }
async getDomain (options: FindOneOptions<Domain>): Promise<Domain | null> { async getDomain(options: FindOneOptions<Domain>): Promise<Domain | null> {
const domainRepository = this.dataSource.getRepository(Domain); const domainRepository = this.dataSource.getRepository(Domain);
const domain = await domainRepository.findOne(options); const domain = await domainRepository.findOne(options);
return domain; return domain;
} }
async updateDomainById ( async updateDomainById(
domainId: string, domainId: string,
data: DeepPartial<Domain> data: DeepPartial<Domain>
): Promise<boolean> { ): Promise<boolean> {
@ -522,7 +581,7 @@ export class Database {
return Boolean(updateResult.affected); return Boolean(updateResult.affected);
} }
async getDomainsByProjectId ( async getDomainsByProjectId(
projectId: string, projectId: string,
filter?: FindOptionsWhere<Domain> filter?: FindOptionsWhere<Domain>
): Promise<Domain[]> { ): Promise<Domain[]> {
@ -542,4 +601,24 @@ export class Database {
return domains; return domains;
} }
async addDeployer(data: DeepPartial<Deployer>): Promise<Deployer> {
const deployerRepository = this.dataSource.getRepository(Deployer);
const newDomain = await deployerRepository.save(data);
return newDomain;
}
async getDeployers(): Promise<Deployer[]> {
const deployerRepository = this.dataSource.getRepository(Deployer);
const deployers = await deployerRepository.find();
return deployers;
}
async getDeployerByLRN(deployerLrn: string): Promise<Deployer | null> {
const deployerRepository = this.dataSource.getRepository(Deployer);
const deployer = await deployerRepository.findOne({ where: { deployerLrn } });
return deployer;
}
} }

View File

@ -0,0 +1,26 @@
import { Entity, PrimaryColumn, Column, ManyToMany } from 'typeorm';
import { Project } from './Project';
@Entity()
export class Deployer {
@PrimaryColumn('varchar')
deployerLrn!: string;
@Column('varchar')
deployerId!: string;
@Column('varchar')
deployerApiUrl!: string;
@Column('varchar')
baseDomain!: string;
@Column('varchar', { nullable: true })
minimumPayment!: string | null;
@Column('varchar', { nullable: true })
paymentAddress!: string | null;
@ManyToMany(() => Project, (project) => project.deployers)
projects!: Project[];
}

View File

@ -6,13 +6,15 @@ import {
UpdateDateColumn, UpdateDateColumn,
ManyToOne, ManyToOne,
OneToOne, OneToOne,
JoinColumn JoinColumn,
DeleteDateColumn
} from 'typeorm'; } from 'typeorm';
import { Project } from './Project'; import { Project } from './Project';
import { Domain } from './Domain'; import { Domain } from './Domain';
import { User } from './User'; import { User } from './User';
import { AppDeploymentRecordAttributes } from '../types'; import { Deployer } from './Deployer';
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
export enum Environment { export enum Environment {
Production = 'Production', Production = 'Production',
@ -24,6 +26,7 @@ export enum DeploymentStatus {
Building = 'Building', Building = 'Building',
Ready = 'Ready', Ready = 'Ready',
Error = 'Error', Error = 'Error',
Deleting = 'Deleting',
} }
export interface ApplicationDeploymentRequest { export interface ApplicationDeploymentRequest {
@ -31,10 +34,22 @@ export interface ApplicationDeploymentRequest {
version: string; version: string;
name: string; name: string;
application: string; application: string;
lrn?: string;
auction?: string;
config: string; config: string;
meta: string; meta: string;
payment?: string;
} }
export interface ApplicationDeploymentRemovalRequest {
type: string;
version: string;
deployment: string;
auction?: string;
payment?: string;
}
export interface ApplicationRecord { export interface ApplicationRecord {
type: string; type: string;
version: string; version: string;
@ -99,6 +114,22 @@ export class Deployment {
@Column('simple-json', { nullable: true }) @Column('simple-json', { nullable: true })
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null; 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;
@ManyToOne(() => Deployer)
@JoinColumn({ name: 'deployerLrn' })
deployer!: Deployer;
@Column({ @Column({
enum: Environment enum: Environment
}) })
@ -121,4 +152,7 @@ export class Deployment {
@UpdateDateColumn() @UpdateDateColumn()
updatedAt!: Date; updatedAt!: Date;
@DeleteDateColumn()
deletedAt!: Date | null;
} }

View File

@ -7,13 +7,16 @@ import {
ManyToOne, ManyToOne,
JoinColumn, JoinColumn,
OneToMany, OneToMany,
DeleteDateColumn DeleteDateColumn,
JoinTable,
ManyToMany
} from 'typeorm'; } from 'typeorm';
import { User } from './User'; import { User } from './User';
import { Organization } from './Organization'; import { Organization } from './Organization';
import { ProjectMember } from './ProjectMember'; import { ProjectMember } from './ProjectMember';
import { Deployment } from './Deployment'; import { Deployment } from './Deployment';
import { Deployer } from './Deployer';
@Entity() @Entity()
export class Project { export class Project {
@ -46,6 +49,20 @@ export class Project {
@Column('text', { default: '' }) @Column('text', { default: '' })
description!: string; description!: string;
@Column('varchar', { nullable: true })
auctionId!: string | null;
// Tx hash for sending coins from snowball to deployer
@Column('varchar', { nullable: true })
txHash!: string | null;
@ManyToMany(() => Deployer, (deployer) => (deployer.projects))
@JoinTable()
deployers!: Deployer[]
@Column('boolean', { default: false, nullable: true })
fundsReleased!: boolean;
// TODO: Compute template & framework in import repository // TODO: Compute template & framework in import repository
@Column('varchar', { nullable: true }) @Column('varchar', { nullable: true })
template!: string | null; template!: string | null;
@ -53,6 +70,10 @@ export class Project {
@Column('varchar', { nullable: true }) @Column('varchar', { nullable: true })
framework!: string | null; framework!: string | null;
// Address of the user who created the project i.e. requested deployments
@Column('varchar')
paymentAddress!: string;
@Column({ @Column({
type: 'simple-array' type: 'simple-array'
}) })
@ -61,9 +82,6 @@ export class Project {
@Column('varchar') @Column('varchar')
icon!: string; icon!: string;
@Column('varchar')
subDomain!: string;
@CreateDateColumn() @CreateDateColumn()
createdAt!: Date; createdAt!: Date;

View File

@ -39,6 +39,12 @@ export class User {
@CreateDateColumn() @CreateDateColumn()
updatedAt!: Date; updatedAt!: Date;
@Column()
subOrgId!: string;
@Column()
turnkeyWalletId!: string;
@OneToMany(() => ProjectMember, (projectMember) => projectMember.project, { @OneToMany(() => ProjectMember, (projectMember) => projectMember.project, {
cascade: ['soft-remove'] cascade: ['soft-remove']
}) })

View File

@ -1,3 +1,4 @@
import 'express-async-errors';
import 'reflect-metadata'; import 'reflect-metadata';
import debug from 'debug'; import debug from 'debug';
import fs from 'fs'; import fs from 'fs';
@ -9,8 +10,6 @@ import { Database } from './database';
import { createAndStartServer } from './server'; import { createAndStartServer } from './server';
import { createResolvers } from './resolvers'; import { createResolvers } from './resolvers';
import { getConfig } from './utils'; import { getConfig } from './utils';
import { Config } from './config';
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
import { Service } from './service'; import { Service } from './service';
import { Registry } from './registry'; import { Registry } from './registry';
@ -18,16 +17,15 @@ const log = debug('snowball:server');
const OAUTH_CLIENT_TYPE = 'oauth-app'; const OAUTH_CLIENT_TYPE = 'oauth-app';
export const main = async (): Promise<void> => { export const main = async (): Promise<void> => {
// TODO: get config path using cli const { server, database, gitHub, registryConfig } = await getConfig();
const { server, database, gitHub, registryConfig, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
const app = new OAuthApp({ const app = new OAuthApp({
clientType: OAUTH_CLIENT_TYPE, clientType: OAUTH_CLIENT_TYPE,
clientId: gitHub.oAuth.clientId, clientId: gitHub.oAuth.clientId,
clientSecret: gitHub.oAuth.clientSecret clientSecret: gitHub.oAuth.clientSecret,
}); });
const db = new Database(database, misc); const db = new Database(database);
await db.init(); await db.init();
const registry = new Registry(registryConfig); const registry = new Registry(registryConfig);
@ -35,7 +33,7 @@ export const main = async (): Promise<void> => {
{ gitHubConfig: gitHub, registryConfig }, { gitHubConfig: gitHub, registryConfig },
db, db,
app, app,
registry registry,
); );
const typeDefs = fs const typeDefs = fs

View File

@ -1,55 +1,67 @@
import debug from 'debug';
import assert from 'assert'; import assert from 'assert';
import { inc as semverInc } from 'semver'; import debug from 'debug';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { Octokit } from 'octokit';
import { inc as semverInc } from 'semver';
import { DeepPartial } from 'typeorm';
import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk'; import { Account, DEFAULT_GAS_ESTIMATION_MULTIPLIER, Registry as LaconicRegistry, getGasPrice, parseGasAndFees } from '@cerc-io/registry-sdk';
import { DeliverTxResponse, IndexedTx } from '@cosmjs/stargate';
import { RegistryConfig } from './config'; import { RegistryConfig } from './config';
import { import {
ApplicationRecord, ApplicationRecord,
Deployment, Deployment,
ApplicationDeploymentRequest ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest
} from './entity/Deployment'; } from './entity/Deployment';
import { AppDeploymentRecord, PackageJSON } from './types'; import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord } from './types';
import { getConfig, getRepoDetails, registryTransactionWithRetry, sleep } from './utils';
const log = debug('snowball:registry'); const log = debug('snowball:registry');
const APP_RECORD_TYPE = 'ApplicationRecord'; const APP_RECORD_TYPE = 'ApplicationRecord';
const APP_DEPLOYMENT_AUCTION_RECORD_TYPE = 'ApplicationDeploymentAuction';
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest'; const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord'; const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
const WEBAPP_DEPLOYER_RECORD_TYPE = 'WebappDeployer'
const SLEEP_DURATION = 1000;
// TODO: Move registry code to laconic-sdk/watcher-ts // TODO: Move registry code to registry-sdk/watcher-ts
export class Registry { export class Registry {
private registry: LaconicRegistry; private registry: LaconicRegistry;
private registryConfig: RegistryConfig; private registryConfig: RegistryConfig;
constructor (registryConfig: RegistryConfig) { constructor(registryConfig: RegistryConfig) {
this.registryConfig = registryConfig; this.registryConfig = registryConfig;
const gasPrice = getGasPrice(registryConfig.fee.gasPrice);
this.registry = new LaconicRegistry( this.registry = new LaconicRegistry(
registryConfig.gqlEndpoint, registryConfig.gqlEndpoint,
registryConfig.restEndpoint, registryConfig.restEndpoint,
registryConfig.chainId { chainId: registryConfig.chainId, gasPrice }
); );
} }
async createApplicationRecord ({ async createApplicationRecord({
appName, octokit,
packageJSON, repository,
commitHash, commitHash,
appType, appType,
repoUrl
}: { }: {
appName: string; octokit: Octokit
packageJSON: PackageJSON; repository: string;
commitHash: string; commitHash: string;
appType: string; appType: string;
repoUrl: string;
}): Promise<{ }): Promise<{
applicationRecordId: string; applicationRecordId: string;
applicationRecordData: ApplicationRecord; applicationRecordData: ApplicationRecord;
}> { }> {
// Use laconic-sdk to publish record const { repo, repoUrl, packageJSON } = await getRepoDetails(octokit, repository, commitHash)
// Use registry-sdk to publish record
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh // Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
// Fetch previous records // Fetch previous records
const records = await this.registry.queryRecords( const records = await this.registry.queryRecords(
@ -82,7 +94,7 @@ export class Registry {
repository_ref: commitHash, repository_ref: commitHash,
repository: [repoUrl], repository: [repoUrl],
app_type: appType, app_type: appType,
name: appName, name: repo,
...(packageJSON.description && { description: packageJSON.description }), ...(packageJSON.description && { description: packageJSON.description }),
...(packageJSON.homepage && { homepage: packageJSON.homepage }), ...(packageJSON.homepage && { homepage: packageJSON.homepage }),
...(packageJSON.license && { license: packageJSON.license }), ...(packageJSON.license && { license: packageJSON.license }),
@ -95,63 +107,158 @@ export class Registry {
...(packageJSON.version && { app_version: packageJSON.version }) ...(packageJSON.version && { app_version: packageJSON.version })
}; };
const result = await this.registry.setRecord( const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
{
privateKey: this.registryConfig.privateKey, const result = await registryTransactionWithRetry(() =>
record: applicationRecord, this.registry.setRecord(
bondId: this.registryConfig.bondId {
}, privateKey: this.registryConfig.privateKey,
'', record: applicationRecord,
this.registryConfig.fee bondId: this.registryConfig.bondId
},
this.registryConfig.privateKey,
fee
)
); );
log(`Published application record ${result.id}`);
log('Application record data:', applicationRecord); log('Application record data:', applicationRecord);
// TODO: Discuss computation of CRN // TODO: Discuss computation of LRN
const crn = this.getCrn(appName); const lrn = this.getLrn(repo);
log(`Setting name: ${crn} for record ID: ${result.data.id}`); log(`Setting name: ${lrn} for record ID: ${result.id}`);
await this.registry.setName( await sleep(SLEEP_DURATION);
{ cid: result.data.id, crn }, await registryTransactionWithRetry(() =>
this.registryConfig.privateKey, this.registry.setName(
this.registryConfig.fee {
cid: result.id,
lrn
},
this.registryConfig.privateKey,
fee
)
); );
await this.registry.setName(
{ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, await sleep(SLEEP_DURATION);
this.registryConfig.privateKey, await registryTransactionWithRetry(() =>
this.registryConfig.fee this.registry.setName(
{
cid: result.id,
lrn: `${lrn}@${applicationRecord.app_version}`
},
this.registryConfig.privateKey,
fee
)
); );
await this.registry.setName(
{ await sleep(SLEEP_DURATION);
cid: result.data.id, await registryTransactionWithRetry(() =>
crn: `${crn}@${applicationRecord.repository_ref}` this.registry.setName(
}, {
this.registryConfig.privateKey, cid: result.id,
this.registryConfig.fee lrn: `${lrn}@${applicationRecord.repository_ref}`
},
this.registryConfig.privateKey,
fee
)
); );
return { return {
applicationRecordId: result.data.id, applicationRecordId: result.id,
applicationRecordData: applicationRecord applicationRecordData: applicationRecord
}; };
} }
async createApplicationDeploymentRequest (data: { async createApplicationDeploymentAuction(
appName: string,
octokit: Octokit,
auctionParams: AuctionParams,
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 registryTransactionWithRetry(() =>
this.registry.createProviderAuction(
{
commitFee: auctionConfig.commitFee,
commitsDuration: auctionConfig.commitsDuration,
revealFee: auctionConfig.revealFee,
revealsDuration: auctionConfig.revealsDuration,
denom: auctionConfig.denom,
maxPrice: auctionParams.maxPrice,
numProviders: auctionParams.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 registryTransactionWithRetry(() =>
this.registry.setRecord(
{
privateKey: this.registryConfig.privateKey,
record: applicationDeploymentAuction,
bondId: this.registryConfig.bondId
},
this.registryConfig.privateKey,
fee
)
);
log(`Application deployment auction created: ${auctionResult.auction.id}`);
log(`Application deployment auction record published: ${result.id}`);
log('Application deployment auction data:', applicationDeploymentAuction);
return {
applicationDeploymentAuctionId: auctionResult.auction.id,
};
}
async createApplicationDeploymentRequest(data: {
deployment: Deployment, deployment: Deployment,
appName: string, appName: string,
packageJsonName: string,
repository: string, repository: string,
environmentVariables: { [key: string]: string } auctionId?: string | null,
lrn: string,
environmentVariables: { [key: string]: string },
dns: string,
payment?: string | null
}): Promise<{ }): Promise<{
applicationDeploymentRequestId: string; applicationDeploymentRequestId: string;
applicationDeploymentRequestData: ApplicationDeploymentRequest; applicationDeploymentRequestData: ApplicationDeploymentRequest;
}> { }> {
const crn = this.getCrn(data.appName); const lrn = this.getLrn(data.appName);
const records = await this.registry.resolveNames([crn]); const records = await this.registry.resolveNames([lrn]);
const applicationRecord = records[0]; const applicationRecord = records[0];
if (!applicationRecord) { if (!applicationRecord) {
throw new Error(`No record found for ${crn}`); throw new Error(`No record found for ${lrn}`);
} }
// Create record of type ApplicationDeploymentRequest and publish // Create record of type ApplicationDeploymentRequest and publish
@ -159,11 +266,8 @@ export class Registry {
type: APP_DEPLOYMENT_REQUEST_TYPE, type: APP_DEPLOYMENT_REQUEST_TYPE,
version: '1.0.0', version: '1.0.0',
name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`, name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`,
application: `${crn}@${applicationRecord.attributes.app_version}`, application: `${lrn}@${applicationRecord.attributes.app_version}`,
dns: `${data.deployment.project.name}-${data.deployment.id}`, 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 // https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b
config: JSON.stringify({ config: JSON.stringify({
@ -175,31 +279,87 @@ export class Registry {
)}`, )}`,
repository: data.repository, repository: data.repository,
repository_ref: data.deployment.commitHash repository_ref: data.deployment.commitHash
}) }),
deployer: data.lrn,
...(data.auctionId && { auction: data.auctionId }),
...(data.payment && { payment: data.payment }),
}; };
const result = await this.registry.setRecord( await sleep(SLEEP_DURATION);
{
privateKey: this.registryConfig.privateKey, const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
record: applicationDeploymentRequest,
bondId: this.registryConfig.bondId const result = await registryTransactionWithRetry(() =>
}, this.registry.setRecord(
'', {
this.registryConfig.fee privateKey: this.registryConfig.privateKey,
record: applicationDeploymentRequest,
bondId: this.registryConfig.bondId
},
this.registryConfig.privateKey,
fee
)
); );
log(`Application deployment request record published: ${result.data.id}`);
log(`Application deployment request record published: ${result.id}`);
log('Application deployment request data:', applicationDeploymentRequest); log('Application deployment request data:', applicationDeploymentRequest);
return { return {
applicationDeploymentRequestId: result.data.id, applicationDeploymentRequestId: result.id,
applicationDeploymentRequestData: applicationDeploymentRequest applicationDeploymentRequestData: applicationDeploymentRequest
}; };
} }
async getAuctionWinningDeployerRecords(
auctionId: string
): Promise<DeployerRecord[]> {
const records = await this.registry.getAuctionsByIds([auctionId]);
const auctionResult = records[0];
let deployerRecords = [];
const { winnerAddresses } = auctionResult;
for (const auctionWinner of winnerAddresses) {
const records = await this.getDeployerRecordsByFilter({
paymentAddress: auctionWinner,
});
const newRecords = records.filter(record => {
return record.names !== null && record.names.length > 0;
});
for (const record of newRecords) {
if (record.id) {
deployerRecords.push(record);
break;
}
}
}
return deployerRecords;
}
async releaseDeployerFunds(
auctionId: string
): Promise<any> {
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
const auction = await registryTransactionWithRetry(() =>
this.registry.releaseFunds(
{
auctionId
},
this.registryConfig.privateKey,
fee
)
);
return auction;
}
/** /**
* Fetch ApplicationDeploymentRecords for deployments * Fetch ApplicationDeploymentRecords for deployments
*/ */
async getDeploymentRecords ( async getDeploymentRecords(
deployments: Deployment[] deployments: Deployment[]
): Promise<AppDeploymentRecord[]> { ): Promise<AppDeploymentRecord[]> {
// Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments // Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments
@ -211,17 +371,165 @@ export class Registry {
true true
); );
// Filter records with ApplicationRecord ids // Filter records with ApplicationDeploymentRequestId ID and Deployment specific URL
return records.filter((record: AppDeploymentRecord) => return records.filter((record: AppDeploymentRecord) =>
deployments.some( deployments.some(
(deployment) => (deployment) =>
deployment.applicationRecordId === record.attributes.application deployment.applicationDeploymentRequestId === record.attributes.request &&
record.attributes.url.includes(deployment.id)
) )
); );
} }
getCrn (appName: string): string { /**
* Fetch WebappDeployer Records by filter
*/
async getDeployerRecordsByFilter(filter: { [key: string]: any }): Promise<DeployerRecord[]> {
return this.registry.queryRecords(
{
type: WEBAPP_DEPLOYER_RECORD_TYPE,
...filter
},
true
);
}
/**
* Fetch ApplicationDeploymentRecords by filter
*/
async getDeploymentRecordsByFilter(filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
return this.registry.queryRecords(
{
type: APP_DEPLOYMENT_RECORD_TYPE,
...filter
},
true
);
}
/**
* Fetch ApplicationDeploymentRemovalRecords for deployments
*/
async getDeploymentRemovalRecords(
deployments: Deployment[]
): Promise<AppDeploymentRemovalRecord[]> {
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
const records = await this.registry.queryRecords(
{
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE
},
true
);
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
return records.filter((record: AppDeploymentRemovalRecord) =>
deployments.some(
(deployment) =>
deployment.applicationDeploymentRemovalRequestId === record.attributes.request &&
deployment.applicationDeploymentRecordId === record.attributes.deployment
)
);
}
async createApplicationDeploymentRemovalRequest(data: {
deploymentId: string;
deployerLrn: string;
auctionId?: string | null;
payment?: string | null;
}): Promise<{
applicationDeploymentRemovalRequestId: string;
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
}> {
const applicationDeploymentRemovalRequest = {
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
version: '1.0.0',
deployment: data.deploymentId,
deployer: data.deployerLrn,
...(data.auctionId && { auction: data.auctionId }),
...(data.payment && { payment: data.payment }),
};
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
const result = await registryTransactionWithRetry(() =>
this.registry.setRecord(
{
privateKey: this.registryConfig.privateKey,
record: applicationDeploymentRemovalRequest,
bondId: this.registryConfig.bondId
},
this.registryConfig.privateKey,
fee
)
);
log(`Application deployment removal request record published: ${result.id}`);
log('Application deployment removal request data:', applicationDeploymentRemovalRequest);
return {
applicationDeploymentRemovalRequestId: result.id,
applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest
};
}
async getCompletedAuctionIds(auctionIds: string[]): Promise<string[]> {
if (auctionIds.length === 0) {
return [];
}
const auctions = await this.registry.getAuctionsByIds(auctionIds);
const completedAuctions = auctions
.filter((auction: { id: string, status: string }) => auction.status === 'completed')
.map((auction: { id: string, status: string }) => 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]);
}
async sendTokensToAccount(receiverAddress: string, amount: string): Promise<DeliverTxResponse> {
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
const account = await this.getAccount();
const laconicClient = await this.registry.getLaconicClient(account);
const txResponse: DeliverTxResponse =
await registryTransactionWithRetry(() =>
laconicClient.sendTokens(account.address, receiverAddress,
[
{
denom: 'alnt',
amount
}
],
fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER)
);
return txResponse;
}
async getAccount(): Promise<Account> {
const account = new Account(Buffer.from(this.registryConfig.privateKey, 'hex'));
await account.init();
return account;
}
async getTxResponse(txHash: string): Promise<IndexedTx | null> {
const account = await this.getAccount();
const laconicClient = await this.registry.getLaconicClient(account);
const txResponse: IndexedTx | null = await laconicClient.getTx(txHash);
return txResponse;
}
getLrn(appName: string): string {
assert(this.registryConfig.authority, "Authority doesn't exist"); assert(this.registryConfig.authority, "Authority doesn't exist");
return `crn://${this.registryConfig.authority}/applications/${appName}`; return `lrn://${this.registryConfig.authority}/applications/${appName}`;
} }
} }

View File

@ -6,6 +6,7 @@ import { Permission } from './entity/ProjectMember';
import { Domain } from './entity/Domain'; import { Domain } from './entity/Domain';
import { Project } from './entity/Project'; import { Project } from './entity/Project';
import { EnvironmentVariable } from './entity/EnvironmentVariable'; import { EnvironmentVariable } from './entity/EnvironmentVariable';
import { AddProjectFromTemplateInput, AuctionParams, EnvironmentVariables } from './types';
const log = debug('snowball:resolver'); const log = debug('snowball:resolver');
@ -17,16 +18,23 @@ export const createResolvers = async (service: Service): Promise<any> => {
return context.user; return context.user;
}, },
organizations: async (_:any, __: any, context: any) => { organizations: async (_: any, __: any, context: any) => {
return service.getOrganizationsByUserId(context.user); return service.getOrganizationsByUserId(context.user);
}, },
project: async (_: any, { projectId }: { projectId: string }) => { project: async (_: any, { projectId }: { projectId: string }, context: any) => {
return service.getProjectById(projectId); return service.getProjectById(context.user, projectId);
}, },
projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => { projectsInOrganization: async (
return service.getProjectsInOrganization(context.user, organizationSlug); _: any,
{ organizationSlug }: { organizationSlug: string },
context: any,
) => {
return service.getProjectsInOrganization(
context.user,
organizationSlug,
);
}, },
deployments: async (_: any, { projectId }: { projectId: string }) => { deployments: async (_: any, { projectId }: { projectId: string }) => {
@ -35,7 +43,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
environmentVariables: async ( environmentVariables: async (
_: any, _: any,
{ projectId }: { projectId: string } { projectId }: { projectId: string },
) => { ) => {
return service.getEnvironmentVariablesByProjectId(projectId); return service.getEnvironmentVariablesByProjectId(projectId);
}, },
@ -44,7 +52,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
return service.getProjectMembersByProjectId(projectId); 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); return service.searchProjects(context.user, searchText);
}, },
@ -52,11 +64,37 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any, _: any,
{ {
projectId, projectId,
filter filter,
}: { projectId: string; filter?: FindOptionsWhere<Domain> } }: { projectId: string; filter?: FindOptionsWhere<Domain> },
) => { ) => {
return service.getDomainsByProjectId(projectId, filter); return service.getDomainsByProjectId(projectId, filter);
} },
getAuctionData: async (
_: any,
{ auctionId }: { auctionId: string },
) => {
return service.getAuctionData(auctionId);
},
deployers: async (_: any, __: any, context: any) => {
return service.getDeployers();
},
address: async (_: any, __: any, context: any) => {
return service.getAddress();
},
verifyTx: async (
_: any,
{
txHash,
amount,
senderAddress,
}: { txHash: string; amount: string; senderAddress: string },
) => {
return service.verifyTx(txHash, amount, senderAddress);
},
}, },
// TODO: Return error in GQL response // TODO: Return error in GQL response
@ -64,10 +102,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
removeProjectMember: async ( removeProjectMember: async (
_: any, _: any,
{ projectMemberId }: { projectMemberId: string }, { projectMemberId }: { projectMemberId: string },
context: any context: any,
) => { ) => {
try { try {
return await service.removeProjectMember(context.user, projectMemberId); return await service.removeProjectMember(
context.user,
projectMemberId,
);
} catch (err) { } catch (err) {
log(err); log(err);
return false; return false;
@ -78,13 +119,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any, _: any,
{ {
projectMemberId, projectMemberId,
data data,
}: { }: {
projectMemberId: string; projectMemberId: string;
data: { data: {
permissions: Permission[]; permissions: Permission[];
}; };
} },
) => { ) => {
try { try {
return await service.updateProjectMember(projectMemberId, data); return await service.updateProjectMember(projectMemberId, data);
@ -98,14 +139,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any, _: any,
{ {
projectId, projectId,
data data,
}: { }: {
projectId: string; projectId: string;
data: { data: {
email: string; email: string;
permissions: Permission[]; permissions: Permission[];
}; };
} },
) => { ) => {
try { try {
return Boolean(await service.addProjectMember(projectId, data)); return Boolean(await service.addProjectMember(projectId, data));
@ -119,15 +160,15 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any, _: any,
{ {
projectId, projectId,
data data,
}: { }: {
projectId: string; projectId: string;
data: { environments: string[]; key: string; value: string }[]; data: { environments: string[]; key: string; value: string }[];
} },
) => { ) => {
try { try {
return Boolean( return Boolean(
await service.addEnvironmentVariables(projectId, data) await service.addEnvironmentVariables(projectId, data),
); );
} catch (err) { } catch (err) {
log(err); log(err);
@ -139,16 +180,16 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any, _: any,
{ {
environmentVariableId, environmentVariableId,
data data,
}: { }: {
environmentVariableId: string; environmentVariableId: string;
data: DeepPartial<EnvironmentVariable>; data: DeepPartial<EnvironmentVariable>;
} },
) => { ) => {
try { try {
return await service.updateEnvironmentVariable( return await service.updateEnvironmentVariable(
environmentVariableId, environmentVariableId,
data data,
); );
} catch (err) { } catch (err) {
log(err); log(err);
@ -158,7 +199,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
removeEnvironmentVariable: async ( removeEnvironmentVariable: async (
_: any, _: any,
{ environmentVariableId }: { environmentVariableId: string } { environmentVariableId }: { environmentVariableId: string },
) => { ) => {
try { try {
return await service.removeEnvironmentVariable(environmentVariableId); return await service.removeEnvironmentVariable(environmentVariableId);
@ -171,26 +212,76 @@ export const createResolvers = async (service: Service): Promise<any> => {
updateDeploymentToProd: async ( updateDeploymentToProd: async (
_: any, _: any,
{ deploymentId }: { deploymentId: string }, { deploymentId }: { deploymentId: string },
context: any context: any,
) => { ) => {
try { try {
return Boolean(await service.updateDeploymentToProd(context.user, deploymentId)); return Boolean(
await service.updateDeploymentToProd(context.user, deploymentId),
);
} catch (err) { } catch (err) {
log(err); log(err);
return false; return false;
} }
}, },
addProjectFromTemplate: async (
_: any,
{
organizationSlug,
data,
lrn,
auctionParams,
environmentVariables
}: {
organizationSlug: string;
data: AddProjectFromTemplateInput;
lrn: string;
auctionParams: AuctionParams;
environmentVariables: EnvironmentVariables[];
},
context: any,
) => {
try {
return await service.addProjectFromTemplate(
context.user,
organizationSlug,
data,
lrn,
auctionParams,
environmentVariables
);
} catch (err) {
log(err);
throw err;
}
},
addProject: async ( addProject: async (
_: any, _: any,
{ {
organizationSlug, organizationSlug,
data data,
}: { organizationSlug: string; data: DeepPartial<Project> }, lrn,
context: any auctionParams,
environmentVariables
}: {
organizationSlug: string;
data: DeepPartial<Project>;
lrn: string;
auctionParams: AuctionParams,
environmentVariables: EnvironmentVariables[];
},
context: any,
) => { ) => {
try { try {
return await service.addProject(context.user, organizationSlug, data); return await service.addProject(
context.user,
organizationSlug,
data,
lrn,
auctionParams,
environmentVariables
);
} catch (err) { } catch (err) {
log(err); log(err);
throw err; throw err;
@ -199,7 +290,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
updateProject: async ( updateProject: async (
_: any, _: any,
{ projectId, data }: { projectId: string; data: DeepPartial<Project> } { projectId, data }: { projectId: string; data: DeepPartial<Project> },
) => { ) => {
try { try {
return await service.updateProject(projectId, data); return await service.updateProject(projectId, data);
@ -212,10 +303,12 @@ export const createResolvers = async (service: Service): Promise<any> => {
redeployToProd: async ( redeployToProd: async (
_: any, _: any,
{ deploymentId }: { deploymentId: string }, { deploymentId }: { deploymentId: string },
context: any context: any,
) => { ) => {
try { try {
return Boolean(await service.redeployToProd(context.user, deploymentId)); return Boolean(
await service.redeployToProd(context.user, deploymentId),
);
} catch (err) { } catch (err) {
log(err); log(err);
return false; return false;
@ -244,8 +337,8 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any, _: any,
{ {
projectId, projectId,
deploymentId deploymentId,
}: { deploymentId: string; projectId: string } }: { deploymentId: string; projectId: string },
) => { ) => {
try { try {
return await service.rollbackDeployment(projectId, deploymentId); return await service.rollbackDeployment(projectId, deploymentId);
@ -255,9 +348,21 @@ export const createResolvers = async (service: Service): Promise<any> => {
} }
}, },
deleteDeployment: async (
_: any,
{ deploymentId }: { deploymentId: string },
) => {
try {
return await service.deleteDeployment(deploymentId);
} catch (err) {
log(err);
return false;
}
},
addDomain: async ( addDomain: async (
_: any, _: any,
{ projectId, data }: { projectId: string; data: { name: string } } { projectId, data }: { projectId: string; data: { name: string } },
) => { ) => {
try { try {
return Boolean(await service.addDomain(projectId, data)); return Boolean(await service.addDomain(projectId, data));
@ -269,7 +374,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
updateDomain: async ( updateDomain: async (
_: any, _: any,
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> } { domainId, data }: { domainId: string; data: DeepPartial<Domain> },
) => { ) => {
try { try {
return await service.updateDomain(domainId, data); return await service.updateDomain(domainId, data);
@ -282,7 +387,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
authenticateGitHub: async ( authenticateGitHub: async (
_: any, _: any,
{ code }: { code: string }, { code }: { code: string },
context: any context: any,
) => { ) => {
try { try {
return await service.authenticateGitHub(code, context.user); return await service.authenticateGitHub(code, context.user);
@ -294,12 +399,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
unauthenticateGitHub: async (_: any, __: object, context: any) => { unauthenticateGitHub: async (_: any, __: object, context: any) => {
try { try {
return service.unauthenticateGitHub(context.user, { gitHubToken: null }); return service.unauthenticateGitHub(context.user, {
gitHubToken: null,
});
} catch (err) { } catch (err) {
log(err); log(err);
return false; return false;
} }
} },
} },
}; };
}; };

View File

@ -1,29 +1,94 @@
import { Router } from 'express'; import { Router } from 'express';
import { SiweMessage, generateNonce } from 'siwe'; import { SiweMessage } from 'siwe';
import { Service } from '../service';
import { authenticateUser, createUser } from '../turnkey-backend';
const router = Router(); const router = Router();
router.get('/nonce', async (_, res) => { //
res.send(generateNonce()); // Turnkey
//
router.get('/registration/:email', async (req, res) => {
const service: Service = req.app.get('service');
const user = await service.getUserByEmail(req.params.email);
if (user) {
return res.send({ subOrganizationId: user?.subOrgId });
} else {
return res.sendStatus(204);
}
}); });
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,
attestation,
userEmail: email,
userName: email.split('@')[0],
});
req.session.address = user.id;
res.sendStatus(200);
});
router.post('/authenticate', async (req, res) => {
console.log('Authenticate', req.body);
const { signedWhoamiRequest } = req.body;
const user = await authenticateUser(
req.app.get('service'),
signedWhoamiRequest,
);
if (user) {
req.session.address = user.id;
res.sendStatus(200);
} else {
res.sendStatus(401);
}
});
//
// SIWE Auth
//
router.post('/validate', async (req, res) => { router.post('/validate', async (req, res) => {
const { message, signature } = req.body; const { message, signature } = req.body;
const { success, data } = await new SiweMessage(message).verify({ const { success, data } = await new SiweMessage(message).verify({
signature signature,
}); });
if (success) { if (!success) {
req.session.address = data.address; return res.send({ success });
}
const service: Service = req.app.get('service');
const user = await service.getUserByEthAddress(data.address);
if (!user) {
const newUser = await service.createUser({
ethAddress: data.address,
email: `${data.address}@example.com`,
subOrgId: '',
turnkeyWalletId: '',
});
// SIWESession from the web3modal library requires both address and chain ID
req.session.address = newUser.id;
req.session.chainId = data.chainId;
} else {
req.session.address = user.id;
req.session.chainId = data.chainId; req.session.chainId = data.chainId;
} }
res.send({ success }); res.send({ success });
}); });
//
// General
//
router.get('/session', (req, res) => { router.get('/session', (req, res) => {
if (req.session.address && req.session.chainId) { if (req.session.address && req.session.chainId) {
res.send({ address: req.session.address, chainId: req.session.chainId }); res.send({
address: req.session.address,
chainId: req.session.chainId
});
} else { } else {
res.status(401).send({ error: 'Unauthorized: No active session' }); res.status(401).send({ error: 'Unauthorized: No active session' });
} }

View File

@ -0,0 +1,9 @@
import { Router } from 'express';
const router = Router();
router.get('/version', async (req, res) => {
return res.send({ version: '0.0.9' });
});
export default router;

View File

@ -19,6 +19,14 @@ enum DeploymentStatus {
Building Building
Ready Ready
Error Error
Deleting
}
enum AuctionStatus {
completed
reveal
commit
expired
} }
enum DomainStatus { enum DomainStatus {
@ -64,8 +72,13 @@ type Project {
repository: String! repository: String!
prodBranch: String! prodBranch: String!
description: String description: String
deployers: [Deployer!]
auctionId: String
fundsReleased: Boolean
template: String template: String
framework: String framework: String
paymentAddress: String!
txHash: String!
webhooks: [String!] webhooks: [String!]
members: [ProjectMember!] members: [ProjectMember!]
environmentVariables: [EnvironmentVariable!] environmentVariables: [EnvironmentVariable!]
@ -73,7 +86,7 @@ type Project {
updatedAt: String! updatedAt: String!
organization: Organization! organization: Organization!
icon: String icon: String
subDomain: String baseDomains: [String!]
} }
type ProjectMember { type ProjectMember {
@ -93,7 +106,10 @@ type Deployment {
commitMessage: String! commitMessage: String!
url: String url: String
environment: Environment! environment: Environment!
deployer: Deployer
applicationDeploymentRequestId: String
isCurrent: Boolean! isCurrent: Boolean!
baseDomain: String
status: DeploymentStatus! status: DeploymentStatus!
createdAt: String! createdAt: String!
updatedAt: String! updatedAt: String!
@ -119,6 +135,17 @@ type EnvironmentVariable {
updatedAt: String! updatedAt: String!
} }
type Deployer {
deployerLrn: String!
deployerId: String!
deployerApiUrl: String!
minimumPayment: String
paymentAddress: String
createdAt: String!
updatedAt: String!
baseDomain: String
}
type AuthResult { type AuthResult {
token: String! token: String!
} }
@ -129,11 +156,23 @@ input AddEnvironmentVariableInput {
value: String! value: String!
} }
input AddProjectFromTemplateInput {
templateOwner: String!
templateRepo: String!
owner: String!
name: String!
isPrivate: Boolean!
paymentAddress: String!
txHash: String!
}
input AddProjectInput { input AddProjectInput {
name: String! name: String!
repository: String! repository: String!
prodBranch: String! prodBranch: String!
template: String template: String
paymentAddress: String!
txHash: String!
} }
input UpdateProjectInput { input UpdateProjectInput {
@ -173,6 +212,48 @@ input FilterDomainsInput {
status: DomainStatus 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 AuctionParams {
maxPrice: String,
numProviders: Int,
}
type Query { type Query {
user: User! user: User!
organizations: [Organization!] organizations: [Organization!]
@ -183,7 +264,11 @@ type Query {
environmentVariables(projectId: String!): [EnvironmentVariable!] environmentVariables(projectId: String!): [EnvironmentVariable!]
projectMembers(projectId: String!): [ProjectMember!] projectMembers(projectId: String!): [ProjectMember!]
searchProjects(searchText: String!): [Project!] searchProjects(searchText: String!): [Project!]
getAuctionData(auctionId: String!): Auction!
domains(projectId: String!, filter: FilterDomainsInput): [Domain] domains(projectId: String!, filter: FilterDomainsInput): [Domain]
deployers: [Deployer]
address: String!
verifyTx(txHash: String!, amount: String!, senderAddress: String!): Boolean!
} }
type Mutation { type Mutation {
@ -203,12 +288,26 @@ type Mutation {
): Boolean! ): Boolean!
removeEnvironmentVariable(environmentVariableId: String!): Boolean! removeEnvironmentVariable(environmentVariableId: String!): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean!
addProject(organizationSlug: String!, data: AddProjectInput): Project! addProjectFromTemplate(
organizationSlug: String!
data: AddProjectFromTemplateInput
lrn: String
auctionParams: AuctionParams
environmentVariables: [AddEnvironmentVariableInput!]
): Project!
addProject(
organizationSlug: String!
data: AddProjectInput!
lrn: String
auctionParams: AuctionParams
environmentVariables: [AddEnvironmentVariableInput!]
): Project!
updateProject(projectId: String!, data: UpdateProjectInput): Boolean! updateProject(projectId: String!, data: UpdateProjectInput): Boolean!
redeployToProd(deploymentId: String!): Boolean! redeployToProd(deploymentId: String!): Boolean!
deleteProject(projectId: String!): Boolean! deleteProject(projectId: String!): Boolean!
deleteDomain(domainId: String!): Boolean! deleteDomain(domainId: String!): Boolean!
rollbackDeployment(projectId: String!, deploymentId: String!): Boolean! rollbackDeployment(projectId: String!, deploymentId: String!): Boolean!
deleteDeployment(deploymentId: String!): Boolean!
addDomain(projectId: String!, data: AddDomainInput!): Boolean! addDomain(projectId: String!, data: AddDomainInput!): Boolean!
updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean! updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean!
authenticateGitHub(code: String!): AuthResult! authenticateGitHub(code: String!): AuthResult!

View File

@ -6,7 +6,7 @@ import { createServer } from 'http';
import { import {
ApolloServerPluginDrainHttpServer, ApolloServerPluginDrainHttpServer,
ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageLocalDefault,
AuthenticationError AuthenticationError,
} from 'apollo-server-core'; } from 'apollo-server-core';
import session from 'express-session'; import session from 'express-session';
@ -17,10 +17,14 @@ import { ServerConfig } from './config';
import { DEFAULT_GQL_PATH } from './constants'; import { DEFAULT_GQL_PATH } from './constants';
import githubRouter from './routes/github'; import githubRouter from './routes/github';
import authRouter from './routes/auth'; import authRouter from './routes/auth';
import stagingRouter from './routes/staging';
import { Service } from './service'; import { Service } from './service';
const log = debug('snowball:server'); const log = debug('snowball:server');
// Set cookie expiration to 1 month in milliseconds
const COOKIE_MAX_AGE = 30 * 24 * 60 * 60 * 1000;
declare module 'express-session' { declare module 'express-session' {
interface SessionData { interface SessionData {
address: string; address: string;
@ -32,7 +36,7 @@ export const createAndStartServer = async (
serverConfig: ServerConfig, serverConfig: ServerConfig,
typeDefs: TypeSource, typeDefs: TypeSource,
resolvers: any, resolvers: any,
service: Service service: Service,
): Promise<ApolloServer> => { ): Promise<ApolloServer> => {
const { host, port, gqlPath = DEFAULT_GQL_PATH } = serverConfig; const { host, port, gqlPath = DEFAULT_GQL_PATH } = serverConfig;
const { appOriginUrl, secret, domain, trustProxy } = serverConfig.session; const { appOriginUrl, secret, domain, trustProxy } = serverConfig.session;
@ -45,7 +49,7 @@ export const createAndStartServer = async (
// Create the schema // Create the schema
const schema = makeExecutableSchema({ const schema = makeExecutableSchema({
typeDefs, typeDefs,
resolvers resolvers,
}); });
const server = new ApolloServer({ const server = new ApolloServer({
@ -60,24 +64,24 @@ export const createAndStartServer = async (
throw new AuthenticationError('Unauthorized: No active session'); throw new AuthenticationError('Unauthorized: No active session');
} }
// Find/create user from ETH address in request session const user = await service.getUser(address);
const user = await service.loadOrCreateUser(address);
return { user }; return { user };
}, },
plugins: [ plugins: [
// Proper shutdown for the HTTP server // Proper shutdown for the HTTP server
ApolloServerPluginDrainHttpServer({ httpServer }), ApolloServerPluginDrainHttpServer({ httpServer }),
ApolloServerPluginLandingPageLocalDefault({ embed: true }) ApolloServerPluginLandingPageLocalDefault({ embed: true }),
] ],
}); });
await server.start(); await server.start();
app.use(cors({ app.use(
origin: appOriginUrl, cors({
credentials: true origin: appOriginUrl,
})); credentials: true,
}),
);
const sessionOptions: session.SessionOptions = { const sessionOptions: session.SessionOptions = {
secret: secret, secret: secret,
@ -85,16 +89,12 @@ export const createAndStartServer = async (
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
secure: new URL(appOriginUrl).protocol === 'https:', secure: new URL(appOriginUrl).protocol === 'https:',
// TODO: Set cookie maxAge and handle cookie expiry in frontend maxAge: COOKIE_MAX_AGE,
// maxAge: SESSION_COOKIE_MAX_AGE, domain: domain || undefined,
sameSite: new URL(appOriginUrl).protocol === 'https:' ? 'none' : 'lax' sameSite: new URL(appOriginUrl).protocol === 'https:' ? 'none' : 'lax',
} }
}; };
if (domain) {
sessionOptions.cookie!.domain = domain;
}
if (trustProxy) { if (trustProxy) {
// trust first proxy // trust first proxy
app.set('trust proxy', 1); app.set('trust proxy', 1);
@ -109,8 +109,8 @@ export const createAndStartServer = async (
path: gqlPath, path: gqlPath,
cors: { cors: {
origin: [appOriginUrl], origin: [appOriginUrl],
credentials: true credentials: true,
} },
}); });
app.use(express.json()); app.use(express.json());
@ -118,6 +118,12 @@ export const createAndStartServer = async (
app.set('service', service); app.set('service', service);
app.use('/auth', authRouter); app.use('/auth', authRouter);
app.use('/api/github', githubRouter); 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, () => { httpServer.listen(port, host, () => {
log(`Server is listening on ${host}:${port}${server.graphqlPath}`); log(`Server is listening on ${host}:${port}${server.graphqlPath}`);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,130 @@
import { Turnkey, TurnkeyApiTypes } from '@turnkey/sdk-server';
// Default path for the first Ethereum address in a new HD wallet.
// See https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki, paths are in the form:
// m / purpose' / coin_type' / account' / change / address_index
// - Purpose is a constant set to 44' following the BIP43 recommendation.
// - Coin type is set to 60 (ETH) -- see https://github.com/satoshilabs/slips/blob/master/slip-0044.md
// - Account, Change, and Address Index are set to 0
import { DEFAULT_ETHEREUM_ACCOUNTS } from '@turnkey/sdk-server';
import { getConfig } from './utils';
import { Service } from './service';
type TAttestation = TurnkeyApiTypes['v1Attestation'];
type CreateUserParams = {
userName: string;
userEmail: string;
challenge: string;
attestation: TAttestation;
};
export async function createUser(
service: Service,
{ userName, userEmail, challenge, attestation }: CreateUserParams,
) {
try {
if (await service.getUserByEmail(userEmail)) {
throw new Error(`User already exists: ${userEmail}`);
}
const config = await getConfig();
const turnkey = new Turnkey(config.turnkey);
const apiClient = turnkey.api();
const walletName = `Default ETH Wallet`;
const createSubOrgResponse = await apiClient.createSubOrganization({
subOrganizationName: `Default SubOrg for ${userEmail}`,
rootQuorumThreshold: 1,
rootUsers: [
{
userName,
userEmail,
apiKeys: [],
authenticators: [
{
authenticatorName: 'Passkey',
challenge,
attestation,
},
],
},
],
wallet: {
walletName: walletName,
accounts: DEFAULT_ETHEREUM_ACCOUNTS,
},
});
const subOrgId = refineNonNull(createSubOrgResponse.subOrganizationId);
const wallet = refineNonNull(createSubOrgResponse.wallet);
const result = {
id: wallet.walletId,
address: wallet.addresses[0],
subOrgId: subOrgId,
};
console.log('Turnkey success', result);
const user = await service.createUser({
name: userName,
email: userEmail,
subOrgId,
ethAddress: wallet.addresses[0],
turnkeyWalletId: wallet.walletId,
});
console.log('New user', user);
return user;
} catch (e) {
console.error('Failed to create user:', e);
throw e;
}
}
export async function authenticateUser(
service: Service,
signedWhoamiRequest: {
url: string;
body: any;
stamp: {
stampHeaderName: string;
stampHeaderValue: string;
};
},
) {
try {
const tkRes = await fetch(signedWhoamiRequest.url, {
method: 'POST',
body: signedWhoamiRequest.body,
headers: {
[signedWhoamiRequest.stamp.stampHeaderName]:
signedWhoamiRequest.stamp.stampHeaderValue,
},
});
console.log('AUTH RESULT', tkRes.status);
if (tkRes.status !== 200) {
console.log(await tkRes.text());
return null;
}
const orgId = (await tkRes.json()).organizationId;
const user = await service.getUserBySubOrgId(orgId);
return user;
} catch (e) {
console.error('Failed to authenticate:', e);
throw e;
}
}
function refineNonNull<T>(
input: T | null | undefined,
errorMessage?: string,
): T {
if (input == null) {
throw new Error(errorMessage ?? `Unexpected ${JSON.stringify(input)}`);
}
return input;
}

View File

@ -24,10 +24,13 @@ export interface GitPushEventPayload {
id: string; id: string;
message: string; message: string;
}; };
deleted: boolean;
} }
export interface AppDeploymentRecordAttributes { export interface AppDeploymentRecordAttributes {
application: string; application: string;
auction: string;
deployer: string;
dns: string; dns: string;
meta: string; meta: string;
name: string; name: string;
@ -37,6 +40,13 @@ export interface AppDeploymentRecordAttributes {
version: string; version: string;
} }
export interface AppDeploymentRemovalRecordAttributes {
deployment: string;
request: string;
type: 'ApplicationDeploymentRemovalRecord';
version: string;
}
interface RegistryRecord { interface RegistryRecord {
id: string; id: string;
names: string[] | null; names: string[] | null;
@ -49,3 +59,46 @@ interface RegistryRecord {
export interface AppDeploymentRecord extends RegistryRecord { export interface AppDeploymentRecord extends RegistryRecord {
attributes: AppDeploymentRecordAttributes; attributes: AppDeploymentRecordAttributes;
} }
export interface AppDeploymentRemovalRecord extends RegistryRecord {
attributes: AppDeploymentRemovalRecordAttributes;
}
export interface AddProjectFromTemplateInput {
templateOwner: string;
templateRepo: string;
owner: string;
name: string;
isPrivate: boolean;
paymentAddress: string;
txHash: string;
}
export interface AuctionParams {
maxPrice: string,
numProviders: number,
}
export interface EnvironmentVariables {
environments: string[],
key: string,
value: string,
}
export interface DeployerRecord {
id: string;
names: string[];
owners: string[];
bondId: string;
createTime: string;
expiryTime: string;
attributes: {
apiUrl: string;
minimumPayment: string | null;
name: string;
paymentAddress: string;
publicKey: string;
type: string;
version: string;
};
}

View File

@ -1,13 +1,24 @@
import assert from 'assert';
import debug from 'debug';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { Octokit } from 'octokit';
import path from 'path'; import path from 'path';
import toml from 'toml'; import toml from 'toml';
import debug from 'debug';
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm'; 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'); const log = debug('snowball:utils');
export const getConfig = async <ConfigType>( export async function getConfig() {
configFile: string // TODO: get config path using cli
return await _getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
}
const _getConfig = async <ConfigType>(
configFile: string,
): Promise<ConfigType> => { ): Promise<ConfigType> => {
const configFilePath = path.resolve(configFile); const configFilePath = path.resolve(configFile);
const fileExists = await fs.pathExists(configFilePath); const fileExists = await fs.pathExists(configFilePath);
@ -41,7 +52,7 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
entityType: EntityTarget<Entity>, entityType: EntityTarget<Entity>,
dataSource: DataSource, dataSource: DataSource,
entities: any, entities: any,
relations?: any | undefined relations?: any | undefined,
): Promise<Entity[]> => { ): Promise<Entity[]> => {
const entityRepository = dataSource.getRepository(entityType); const entityRepository = dataSource.getRepository(entityType);
@ -56,7 +67,7 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
entity = { entity = {
...entity, ...entity,
[field]: relations[field][entityData[valueIndex]] [field]: relations[field][entityData[valueIndex]],
}; };
} }
} }
@ -66,3 +77,67 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
return savedEntity; return savedEntity;
}; };
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
};
}
// Wrapper method for registry txs to retry once if 'account sequence mismatch' occurs
export const registryTransactionWithRetry = async (
txMethod: () => Promise<any>
): Promise<any> => {
try {
return await txMethod();
} catch (error: any) {
if (!error.message.includes('account sequence mismatch')) {
throw error;
}
console.error(`Transaction failed due to account sequence mismatch. Retrying...`);
try {
return await txMethod();
} catch (retryError: any) {
throw new Error(`Transaction failed again after retry: ${retryError.message}`);
}
}
}

View File

@ -2,8 +2,6 @@ import * as fs from 'fs/promises';
import debug from 'debug'; import debug from 'debug';
import { getConfig } from '../src/utils'; import { getConfig } from '../src/utils';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
const log = debug('snowball:delete-database'); const log = debug('snowball:delete-database');
@ -13,7 +11,7 @@ const deleteFile = async (filePath: string) => {
}; };
const main = async () => { const main = async () => {
const config = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH); const config = await getConfig();
deleteFile(config.database.dbPath); deleteFile(config.database.dbPath);
}; };

View File

@ -1,12 +1,7 @@
[ [
{ {
"id": "2379cf1f-a232-4ad2-ae14-4d881131cc26", "id": "2379cf1f-a232-4ad2-ae14-4d881131cc26",
"name": "Snowball Tools", "name": "Deploy Tools",
"slug": "snowball-tools-1" "slug": "deploy-tools"
},
{
"id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb",
"name": "Laconic",
"slug": "laconic-2"
} }
] ]

View File

@ -16,8 +16,6 @@ import {
getEntities, getEntities,
loadAndSaveData loadAndSaveData
} from '../src/utils'; } from '../src/utils';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
const log = debug('snowball:initialize-database'); const log = debug('snowball:initialize-database');
@ -156,7 +154,7 @@ const generateTestData = async (dataSource: DataSource) => {
}; };
const main = async () => { const main = async () => {
const config = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH); const config = await getConfig();
const isDbPresent = await checkFileExists(config.database.dbPath); const isDbPresent = await checkFileExists(config.database.dbPath);
if (!isDbPresent) { if (!isDbPresent) {

View File

@ -1,39 +1,40 @@
import debug from 'debug'; import debug from 'debug';
import { Registry } from '@cerc-io/laconic-sdk'; import { parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { Config } from '../src/config';
import { getConfig } from '../src/utils'; import { getConfig } from '../src/utils';
const log = debug('snowball:initialize-registry'); const log = debug('snowball:initialize-registry');
const DENOM = 'aphoton'; const DENOM = 'alnt';
const BOND_AMOUNT = '1000000000'; const BOND_AMOUNT = '1000000000';
async function main () { async function main () {
const { registryConfig } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH); const { registryConfig } = await getConfig();
// TODO: Get authority names from args // TODO: Get authority names from args
const authorityNames = ['snowballtools', registryConfig.authority]; const authorityNames = ['snowballtools', registryConfig.authority];
const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, {chainId: registryConfig.chainId});
const bondId = await registry.getNextBondId(registryConfig.privateKey); const bondId = await registry.getNextBondId(registryConfig.privateKey);
log('bondId:', bondId); log('bondId:', bondId);
const fee = parseGasAndFees(registryConfig.fee.gas, registryConfig.fee.fees);
await registry.createBond( await registry.createBond(
{ denom: DENOM, amount: BOND_AMOUNT }, { denom: DENOM, amount: BOND_AMOUNT },
registryConfig.privateKey, registryConfig.privateKey,
registryConfig.fee fee
); );
for await (const name of authorityNames) { for await (const name of authorityNames) {
await registry.reserveAuthority({ name }, registryConfig.privateKey, registryConfig.fee); await registry.reserveAuthority({ name }, registryConfig.privateKey, fee);
log('Reserved authority name:', name); log('Reserved authority name:', name);
await registry.setAuthorityBond( await registry.setAuthorityBond(
{ name, bondId }, { name, bondId },
registryConfig.privateKey, registryConfig.privateKey,
registryConfig.fee fee
); );
log(`Bond ${bondId} set for authority ${name}`); log(`Bond ${bondId} set for authority ${name}`);
} }

View File

@ -2,22 +2,20 @@ import debug from 'debug';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import path from 'path'; import path from 'path';
import { Registry } from '@cerc-io/laconic-sdk'; import { parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { getConfig } from '../src/utils'; import { getConfig } from '../src/utils';
import { Deployment, DeploymentStatus } from '../src/entity/Deployment'; import { Deployment, DeploymentStatus, Environment } from '../src/entity/Deployment';
const log = debug('snowball:publish-deploy-records'); const log = debug('snowball:publish-deploy-records');
async function main () { async function main() {
const { registryConfig, database, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH); const { registryConfig, database, misc } = await getConfig();
const registry = new Registry( const registry = new Registry(
registryConfig.gqlEndpoint, registryConfig.gqlEndpoint,
registryConfig.restEndpoint, registryConfig.restEndpoint,
registryConfig.chainId { chainId: registryConfig.chainId }
); );
const dataSource = new DataSource({ const dataSource = new DataSource({
@ -40,7 +38,7 @@ async function main () {
}); });
for await (const deployment of deployments) { for await (const deployment of deployments) {
const url = `${deployment.project.name}-${deployment.id}.${misc.projectDomain}`; const url = `https://${(deployment.project.name).toLowerCase()}-${deployment.id}.${deployment.deployer.baseDomain}`;
const applicationDeploymentRecord = { const applicationDeploymentRecord = {
type: 'ApplicationDeploymentRecord', type: 'ApplicationDeploymentRecord',
@ -61,6 +59,8 @@ async function main () {
url url
}; };
const fee = parseGasAndFees(registryConfig.fee.gas, registryConfig.fee.fees);
const result = await registry.setRecord( const result = await registry.setRecord(
{ {
privateKey: registryConfig.privateKey, privateKey: registryConfig.privateKey,
@ -68,11 +68,26 @@ async function main () {
bondId: registryConfig.bondId bondId: registryConfig.bondId
}, },
'', '',
registryConfig.fee fee
); );
// Remove deployment for project subdomain if deployment is for production environment
if (deployment.environment === Environment.Production) {
applicationDeploymentRecord.url = `https://${deployment.project.name}.${deployment.deployer.baseDomain}`;
await registry.setRecord(
{
privateKey: registryConfig.privateKey,
record: applicationDeploymentRecord,
bondId: registryConfig.bondId
},
'',
fee
);
}
log('Application deployment record data:', applicationDeploymentRecord); log('Application deployment record data:', applicationDeploymentRecord);
log(`Application deployment record published: ${result.data.id}`); log(`Application deployment record published: ${result.id}`);
} }
} }

View File

@ -0,0 +1,67 @@
import debug from 'debug';
import { DataSource } from 'typeorm';
import path from 'path';
import { parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
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 registry = new Registry(
registryConfig.gqlEndpoint,
registryConfig.restEndpoint,
{ chainId: registryConfig.chainId }
);
const dataSource = new DataSource({
type: 'better-sqlite3',
database: database.dbPath,
synchronize: true,
entities: [path.join(__dirname, '../src/entity/*')]
});
await dataSource.initialize();
const deploymentRepository = dataSource.getRepository(Deployment);
const deployments = await deploymentRepository.find({
relations: {
project: true
},
where: {
status: DeploymentStatus.Deleting
}
});
for await (const deployment of deployments) {
const applicationDeploymentRemovalRecord = {
type: "ApplicationDeploymentRemovalRecord",
version: "1.0.0",
deployment: deployment.applicationDeploymentRecordId,
request: deployment.applicationDeploymentRemovalRequestId,
}
const fee = parseGasAndFees(registryConfig.fee.gas, registryConfig.fee.fees);
const result = await registry.setRecord(
{
privateKey: registryConfig.privateKey,
record: applicationDeploymentRemovalRecord,
bondId: registryConfig.bondId
},
'',
fee
);
log('Application deployment removal record data:', applicationDeploymentRemovalRecord);
log(`Application deployment removal record published: ${result.id}`);
}
}
main().catch((err) => {
log(err);
});

View File

@ -0,0 +1,3 @@
REGISTRY_BOND_ID=
DEPLOYER_LRN=
AUTHORITY=

1
packages/deployer/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
records/*.yml

View File

@ -1,74 +1,35 @@
# deployer # deployer
- Install dependencies - Install dependencies
```bash ```bash
yarn yarn
``` ```
```bash ```bash
brew install jq # if you do not have jq installed already brew install jq # if you do not have jq installed already
``` ```
Example of how to make the necessary deploy edits [here](https://github.com/snowball-tools/snowballtools-base/pull/131/files).
- Replace variables in the following files
- [records/application-deployment-request.yml](records/application-deployment-request.yml)
- update the name & application version numbers
- `<CURRENT_DATE_TIME>`: Replace with current time which can be generated by command `date -u`
```yml
# Example
record:
...
meta:
note: Added by Snowball @ Friday 23 February 2024 06:35:50 AM UTC
...
```
- Update record version in [records/application-record.yml](records/application-record.yml)
```yml
record:
type: ApplicationRecord
version: <NEW_VERSION>
...
```
- Update commit hash in the following places:
- [records/application-record.yml](records/application-record.yml)
```yml
record:
...
repository_ref: <COMMIT_HASH>
...
```
- [records/application-deployment-request.yml](records/application-deployment-request.yml)
```yml
record:
...
meta:
...
repository_ref: <COMMIT_HASH>
```
- [deploy-frontend.sh](deploy-frontend.sh)
Also be sure to update the app version
```bash
...
RCD_APP_VERSION="<NEW_VERSION>"
REPO_REF="<COMMIT_HASH>"
...
```
- Run script to deploy app - Run script to deploy app
```
./deploy-frontend.sh - 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
```
- Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository - Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository
## Notes ## Notes
- Any config env can be updated in [records/application-deployment-request.yml](records/application-deployment-request.yml) - Any config env can be updated in [records/application-deployment-request.yml](records/application-deployment-request.yml)
```yml ```yml
record: record:
... ...
@ -77,23 +38,27 @@ Example of how to make the necessary deploy edits [here](https://github.com/snow
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com 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 - On changing `LACONIC_HOSTED_CONFIG_app_github_clientid`, the GitHub client ID and secret need to be changed in backend config too
## Troubleshoot ## Troubleshoot
- Check deployment status [here](https://console.laconic.com/deployer). - Check deployment status in [web-app deployer](https://console.laconic.com/deployer).
- Check records [here](https://console.laconic.com/#/registry). - Check records in [registry console app](https://console.laconic.com/#/registry).
- If deployment fails due to low bond balance - If deployment fails due to low bond balance
- Check balances - Check balances
```bash ```bash
# Account balance # Account balance
yarn laconic cns account get yarn laconic registry account get
# Bond balance # Bond balance
yarn laconic cns bond get --id 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac yarn laconic registry bond get --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
``` ```
- Command to refill bond - Command to refill bond
```bash ```bash
yarn laconic cns bond refill --id 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac --type aphoton --quantity 10000000 yarn laconic registry bond refill --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32 --type alnt --quantity 10000000
``` ```

View File

@ -0,0 +1,10 @@
services:
registry:
restEndpoint: 'http://console.laconic.com:1317'
gqlEndpoint: 'http://console.laconic.com:9473/api'
userKey: 87d00f66a73e2ca428adeb49ba9164d0ad9a87edc60e33d46ad3031b9c5701fe
bondId: 89c75c7bc5759861d10285aff6f9e7227d6855e446b77ad5d8324822dfec7deb
chainId: laconic_9000-1
gas:
fees:
gasPrice: 1

View File

@ -1,9 +1,8 @@
services: services:
cns: registry:
restEndpoint: http://console.laconic.com:1317 rpcEndpoint: https://laconicd-sapo.laconic.com
gqlEndpoint: http://console.laconic.com:9473/api gqlEndpoint: https://laconicd-sapo.laconic.com/api
chainId: laconic_9000-1 userKey:
gas: 1000000 bondId:
fees: 200000aphoton chainId: laconic-testnet-2
userKey: 0524fc22ea0a12e6c5cc4cfe08e73c95dffd0ab5ed72a59f459ed33134fa3b16 gasPrice: 0.001alnt
bondId: 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac

View File

@ -1,35 +1,156 @@
#!/bin/bash #!/bin/bash
source .env
echo "Using REGISTRY_BOND_ID: $REGISTRY_BOND_ID"
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
echo "Using AUTHORITY: $AUTHORITY"
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
# Get the latest commit hash for a branch
BRANCH_NAME="main"
LATEST_HASH=$(git ls-remote $REPO_URL refs/heads/$BRANCH_NAME | awk '{print $1}')
echo "Latest commit hash for branch $BRANCH_NAME: $LATEST_HASH"
# Extract version from ../frontend/package.json
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
CONFIG_FILE=config.yml
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts # Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
# Get latest version from registry and increment application-record version
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationRecord --all --name "deploy-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
# Set application-record version if no previous records were found
NEW_APPLICATION_VERSION=0.0.1
fi
# Generate application-record.yml with incremented version
cat >./records/application-record.yml <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: deploy-frontend
app_version: $PACKAGE_VERSION
EOF
echo "Files generated successfully"
RECORD_FILE=records/application-record.yml RECORD_FILE=records/application-record.yml
CONFIG_FILE=config.yml
RCD_APP_VERSION="0.1.3"
REPO_REF="513ca69d01bee857cf207a0605483205b384e218"
# Publish ApplicationRecord # Publish ApplicationRecord
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id') publish_response=$(yarn --silent laconic -c $CONFIG_FILE 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')
echo "ApplicationRecord published" echo "ApplicationRecord published"
echo $RECORD_ID echo $RECORD_ID
# Set name to record # Set name to record
REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend" REGISTRY_APP_LRN="lrn://$AUTHORITY/applications/deploy-frontend"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${RCD_APP_VERSION}" "$RECORD_ID" sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${REPO_REF}" "$RECORD_ID" 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
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
sleep 2
# Set name if latest release # Set name if latest release
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID" yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN" "$RECORD_ID"
echo "$REGISTRY_APP_CRN set for ApplicationRecord" rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set release"
exit $rc
fi
echo "$REGISTRY_APP_LRN set for ApplicationRecord"
# Check if record found for REGISTRY_APP_CRN # Check if record found for REGISTRY_APP_LRN
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]') 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]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_CRN." echo "No record found for $REGISTRY_APP_LRN."
exit 1 exit 1
fi fi
# Get payment address for deployer
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment' | sed 's/alnt//g')
echo "Paying address: $paymentAddress with amount $paymentAmount..."
# Pay deployer if paymentAmount is not null
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
# Extract the transaction hash
txHash=$(echo "$payment" | jq -r '.tx.hash')
echo "Paid deployer with txHash as $txHash"
else
echo "Payment amount is null; skipping payment."
fi
# Generate application-deployment-request.yml
cat >./records/application-deployment-request.yml <<EOF
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: deploy-frontend@$PACKAGE_VERSION
application: lrn://$AUTHORITY/applications/deploy-frontend@$PACKAGE_VERSION
deployer: $DEPLOYER_LRN
dns: deploy.laconic.com
config:
env:
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.laconic.com
LACONIC_HOSTED_CONFIG_github_clientid: Ov23li4NtYybQlF6u5Dk
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
LACONIC_HOSTED_CONFIG_wallet_iframe_url: https://wallet.laconic.com
meta:
note: Added @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
repository_ref: $LATEST_HASH
payment: $txHash
EOF
RECORD_FILE=records/application-deployment-request.yml RECORD_FILE=records/application-deployment-request.yml
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id') 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')
echo "ApplicationDeploymentRequest published" echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID echo $DEPLOYMENT_REQUEST_ID

View File

@ -0,0 +1,149 @@
#!/bin/bash
source .env
echo "Using REGISTRY_BOND_ID: $REGISTRY_BOND_ID"
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
echo "Using AUTHORITY: $AUTHORITY"
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# Extract version from ../frontend/package.json
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
CONFIG_FILE=config.yml
# 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 "deploy-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
# Set application-record version if no previous records were found
NEW_APPLICATION_VERSION=0.0.1
fi
# Generate application-record.yml with incremented version
cat >./records/application-record.yml <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: deploy-frontend
app_version: $PACKAGE_VERSION
EOF
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')
echo "ApplicationRecord published"
echo $RECORD_ID
# Set name to record
REGISTRY_APP_LRN="lrn://$AUTHORITY/applications/deploy-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
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
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"
# 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]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_LRN."
exit 1
fi
# Get payment address for deployer
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment' | sed 's/alnt//g')
# Pay deployer if paymentAmount is not null
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
# Extract the transaction hash
txHash=$(echo "$payment" | jq -r '.tx.hash')
echo "Paid deployer with txHash as $txHash"
else
echo "Payment amount is null; skipping payment."
fi
# Generate application-deployment-request.yml
cat >./records/application-deployment-request.yml <<EOF
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: deploy-frontend@$PACKAGE_VERSION
application: lrn://$AUTHORITY/applications/deploy-frontend@$PACKAGE_VERSION
deployer: $DEPLOYER_LRN
dns: deploy
config:
env:
LACONIC_HOSTED_CONFIG_server_url: https://deploy-backend.apps.vaasl.io
LACONIC_HOSTED_CONFIG_github_clientid: Ov23liaet4yc0KX0iM1c
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: laconic-templates/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: laconic-templates/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_next_app_templaterepo: laconic-templates/starter.nextjs-react-tailwind
LACONIC_HOSTED_CONFIG_wallet_connect_id: 63cad7ba97391f63652161f484670e15
LACONIC_HOSTED_CONFIG_laconicd_chain_id: laconic-testnet-2
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
repository_ref: $LATEST_HASH
payment: $txHash
EOF
RECORD_FILE=records/application-deployment-request.yml
sleep 2
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')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID

View File

@ -4,6 +4,6 @@
"main": "index.js", "main": "index.js",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@cerc-io/laconic-registry-cli": "^0.1.10" "@cerc-io/laconic-registry-cli": "^0.2.9"
} }
} }

View File

View File

@ -1,21 +0,0 @@
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: snowballtools-base-frontend@0.1.3
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.3
dns: dashboard
config:
env:
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
# If GitHub client ID is changed, same ID and corresponding secret has to be set in backend config
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
# New config env after changes for image upload PWA
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:
# Set CURRENT_DATE_TIME; Use command date -u
note: Added by Snowball @ Tue Feb 27 17:24:06 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 513ca69d01bee857cf207a0605483205b384e218

View File

@ -1,10 +0,0 @@
record:
type: ApplicationRecord
version: 0.0.11
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
app_type: webapp
# name is set to repo name
name: snowballtools-base-frontend
# app_version is set from package.json
app_version: 0.1.3

View File

@ -0,0 +1,56 @@
#!/bin/bash
source .env
echo "Using DEPLOYER_LRN: $DEPLOYER_LRN"
# Generate application-deployment-removal-request.yml
REMOVAL_REQUEST_RECORD_FILE=records/application-deployment-removal-request.yml
# TODO: Pass deployment record ID as arg
DEPLOYMENT_RECORD_ID=bafyreidjho77xeczaqpyawhc4wbpm5it5atibtuxk6ost6vnpu2svlp3ka
cat > $REMOVAL_REQUEST_RECORD_FILE <<EOF
record:
deployer: $DEPLOYER_LRN
deployment: $DEPLOYMENT_RECORD_ID
type: ApplicationDeploymentRemovalRequest
version: 1.0.0
EOF
CONFIG_FILE=config.yml
sleep 2
REMOVAL_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRemovalRequest published"
echo $REMOVAL_REQUEST_ID
# Deployment checks
RETRY_INTERVAL=30
MAX_RETRIES=20
# Check that an ApplicationDeploymentRemovalRecord is published
retry_count=0
while true; do
removal_records_response=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
len_removal_records=$(echo $removal_records_response | jq 'length')
# Check if number of records returned is 0
if [ $len_removal_records -eq 0 ]; then
# Check if retries are exhausted
if [ $retry_count -eq $MAX_RETRIES ]; then
echo "Retries exhausted"
echo "ApplicationDeploymentRemovalRecord for deployment removal request $REMOVAL_REQUEST_ID not found"
exit 1
else
echo "ApplicationDeploymentRemovalRecord not found, retrying in $RETRY_INTERVAL sec..."
sleep $RETRY_INTERVAL
retry_count=$((retry_count+1))
fi
else
echo "ApplicationDeploymentRemovalRecord found"
REMOVAL_RECORD_ID=$(echo $removal_records_response | jq -r '.[0].id')
echo $REMOVAL_RECORD_ID
break
fi
done
echo "Deployment removal successful"

View File

@ -0,0 +1,23 @@
# deployer test
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)
- 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
- Fetch the latest version of `deployment-test-app` from registry and increment `ApplicationRecord` version
- Publish the resulting `ApplicationRecord` record
- Set names to the record and check name resolution
- Publish the `ApplicationDeploymentRequest` record
- Check that the deployment occurs
- Check that a `ApplicationDeploymentRecord` is created
- Check that the deployment record has correct `ApplicationRecord` id
- Check that the URL present in deployment record is active
- Create and publish a [`ApplicationDeploymentRemovalRequest`](./records/application-deployment-removal-request.yml) record
- Check that the deployment is removed
- Check that a `ApplicationDeploymentRemovalRecord` is created
- Check that the deployment URL goes down
- The test script is run in a GitHub CI [workflow](../../../.github/workflows/test-app-deployment.yaml) that:
- Is scheduled to run everyday on the default (`main`) branch or can be triggered manually
- Sends Slack alerts to configured channels on failure

View File

@ -0,0 +1,4 @@
record:
deployment: <APPLICATION_DEPLOYMENT_RECORD_ID>
type: ApplicationDeploymentRemovalRequest
version: 1.0.0

View File

@ -0,0 +1,15 @@
record:
type: ApplicationDeploymentRequest
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
config:
env:
CERC_TEST_WEBAPP_CONFIG1: "deployment test config 1"
CERC_TEST_WEBAPP_CONFIG2: "deployment test config 2"
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_ref: 05819619487a0d2dbc5453b6d1ccff3044c0dd26

View File

@ -0,0 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.1
repository_ref: 05819619487a0d2dbc5453b6d1ccff3044c0dd26
repository: ["https://github.com/snowball-tools/test-progressive-web-app"]
app_type: webapp
name: deployment-test-app
app_version: 0.1.24

View File

@ -0,0 +1,225 @@
#!/bin/bash
# Repository URL
REPO_URL="https://github.com/snowball-tools/test-progressive-web-app"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# Fetch the package.json file content
# Extract version from package.json content
package_json=$(wget -qO- "$REPO_URL/raw/$LATEST_HASH/package.json")
PACKAGE_VERSION=$(echo "$package_json" | jq -r '.version')
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
CONFIG_FILE=packages/deployer/config.yml
REGISTRY_BOND_ID="99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32"
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
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}')
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
# Set application-record version if no previous records were found
NEW_APPLICATION_VERSION=0.0.1
fi
# Generate application-record.yml with incremented version
RECORD_FILE=packages/deployer/test/records/application-record.yml
cat >$RECORD_FILE <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: $APP_NAME
app_version: $PACKAGE_VERSION
EOF
# Generate application-deployment-request.yml
REQUEST_RECORD_FILE=packages/deployer/test/records/application-deployment-request.yml
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
dns: deployment-ci-test
config:
env:
CERC_TEST_WEBAPP_CONFIG1: "deployment test config 1"
CERC_TEST_WEBAPP_CONFIG2: "deployment test config 2"
CERC_WEBAPP_DEBUG: 0
meta:
note: Deployment test @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
repository_ref: $LATEST_HASH
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')
echo "ApplicationRecord published"
echo $RECORD_ID
# Set name to record
REGISTRY_APP_LRN="lrn://snowballtools/applications/$APP_NAME"
sleep 2
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${PACKAGE_VERSION}" "$RECORD_ID"
sleep 2
yarn --silent laconic -c $CONFIG_FILE registry name set "$REGISTRY_APP_LRN@${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"
# Check if record exists for REGISTRY_APP_LRN
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE registry name resolve "$REGISTRY_APP_LRN" | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_LRN."
exit 1
fi
sleep 2
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $REQUEST_RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID
# Deployment checks
RETRY_INTERVAL=30
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)
len_deployment_records=$(echo $deployment_records_response | jq 'length')
# Check if number of records returned is 0
if [ $len_deployment_records -eq 0 ]; then
# Check if retries are exhausted
if [ $retry_count -eq $MAX_RETRIES ]; then
echo "Retries exhausted"
echo "ApplicationDeploymentRecord for deployment request $DEPLOYMENT_REQUEST_ID not found, exiting"
exit 1
else
echo "ApplicationDeploymentRecord not found, retrying in $RETRY_INTERVAL sec..."
sleep $RETRY_INTERVAL
retry_count=$((retry_count + 1))
fi
else
echo "ApplicationDeploymentRecord found"
break
fi
done
DEPLOYMENT_RECORD_ID=$(echo $deployment_records_response | jq -r '.[0].id')
echo $DEPLOYMENT_RECORD_ID
# Check if ApplicationDeploymentRecord has the correct record id
fetched_application_record_id=$(echo $deployment_records_response | jq -r '.[0].attributes.application')
if [ "$fetched_application_record_id" = "$RECORD_ID" ]; then
echo "ApplicationRecord id matched"
else
echo "ApplicationRecord id does not match, expected: $RECORD_ID, received: $fetched_application_record_id"
exit 1
fi
# Check if the url present in ApplicationDeploymentRecord is active
fetched_url=$(echo $deployment_records_response | jq -r '.[0].attributes.url')
retry_count=0
max_retries=10
retry_interval=10
while true; do
url_response=$(curl -s -o /dev/null -I -w "%{http_code}" $fetched_url)
if [ "$url_response" = "200" ]; then
echo "Deployment URL $fetched_url is active"
break
else
if [ $retry_count -eq $max_retries ]; then
echo "Retries exhausted"
echo "Deployment URL $fetched_url is not active, exiting"
exit 1
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))
fi
fi
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
record:
deployment: $DEPLOYMENT_RECORD_ID
type: ApplicationDeploymentRemovalRequest
version: 1.0.0
EOF
sleep 2
REMOVAL_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE registry record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRemovalRequest published"
echo $REMOVAL_REQUEST_ID
# Check that an ApplicationDeploymentRemovalRecord is published
retry_count=0
while true; do
removal_records_response=$(yarn --silent laconic -c $CONFIG_FILE registry record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
len_removal_records=$(echo $removal_records_response | jq 'length')
# Check if number of records returned is 0
if [ $len_removal_records -eq 0 ]; then
# Check if retries are exhausted
if [ $retry_count -eq $MAX_RETRIES ]; then
echo "Retries exhausted"
echo "ApplicationDeploymentRemovalRecord for deployment removal request $REMOVAL_REQUEST_ID not found"
exit 1
else
echo "ApplicationDeploymentRemovalRecord not found, retrying in $RETRY_INTERVAL sec..."
sleep $RETRY_INTERVAL
retry_count=$((retry_count + 1))
fi
else
echo "ApplicationDeploymentRemovalRecord found"
REMOVAL_RECORD_ID=$(echo $removal_records_response | jq -r '.[0].id')
echo $REMOVAL_RECORD_ID
break
fi
done
# Check if the application url is down after deployment removal
retry_count=0
max_retries=10
retry_interval=5
while true; do
url_response=$(curl -s -o /dev/null -I -w "%{http_code}" $fetched_url)
if [ "$url_response" = "404" ]; then
echo "Deployment URL $fetched_url is down"
break
else
if [ $retry_count -eq $max_retries ]; then
echo "Retries exhausted"
echo "Deployment URL $fetched_url is still active, exiting"
exit 1
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))
fi
fi
done
echo "Test successful"

View File

@ -1,7 +0,0 @@
REACT_APP_SERVER_URL = 'http://localhost:8000'
REACT_APP_GITHUB_CLIENT_ID =
REACT_APP_GITHUB_PWA_TEMPLATE_REPO =
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO =
REACT_APP_WALLET_CONNECT_ID =

View File

@ -1,7 +1,18 @@
REACT_APP_SERVER_URL = 'http://localhost:8000' VITE_SERVER_URL='http://localhost:8000'
REACT_APP_GITHUB_CLIENT_ID = VITE_GITHUB_CLIENT_ID=
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = VITE_GITHUB_PWA_TEMPLATE_REPO="snowball-tools/test-progressive-web-app"
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO="snowball-tools/image-upload-pwa-example"
VITE_GITHUB_NEXT_APP_TEMPLATE_REPO="snowball-tools/starter.nextjs-react-tailwind"
REACT_APP_WALLET_CONNECT_ID = VITE_WALLET_CONNECT_ID=
VITE_LIT_RELAY_API_KEY=
VITE_BUGSNAG_API_KEY=
VITE_PASSKEY_WALLET_RPID=
VITE_TURNKEY_API_BASE_URL=
VITE_LACONICD_CHAIN_ID=
VITE_WALLET_IFRAME_URL=

View File

@ -1 +0,0 @@
build

View File

@ -0,0 +1,19 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
};

View File

@ -1,25 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 13,
"sourceType": "module"
},
"env": {
"browser": true,
"es2021": true
},
"plugins": ["react", "@typescript-eslint"],
"extends": [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@ -22,3 +22,4 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
*storybook.log

View File

@ -0,0 +1 @@
v20.12.1

View File

@ -1,3 +1 @@
# artifacts dist/
build
coverage

View File

@ -0,0 +1,33 @@
import type { StorybookConfig } from '@storybook/react-vite';
import { join, dirname } from 'path';
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
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: [
getAbsolutePath('@storybook/addon-onboarding'),
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('storybook-addon-remix-react-router'),
],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
options: {},
},
docs: {
autodocs: 'tag',
},
staticDirs: ['../public'],
};
export default config;

View File

@ -0,0 +1,16 @@
import type { Preview } from '@storybook/react';
import '../src/index.css';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@ -1,46 +1,63 @@
# Getting Started with Create React App # frontend
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using [typescript-tailwindcss-eslint-prettier](https://github.com/cufarvid/cra-templates) template. 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).
## Available Scripts ## Getting Started
In the project directory, you can run: ### Install dependencies
### `yarn start` In the root of the project, run:
Runs the app in the development mode.\ ```zsh
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. yarn
```
The page will reload if you make edits.\ ### Build backend
You will also see any lint errors in the console.
### `yarn test` ```zsh
yarn build --ignore frontend
```
Launches the test runner in the interactive watch mode.\ ### Environment variables
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build` #### Local
Builds the app for production to the `build` folder.\ Copy the `.env.example` file to `.env`:
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\ ```zsh
Your app is ready to be deployed! cp .env.example .env
```
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. #### Staging environment variables
### `yarn eject` Change in [deployer/deploy-frontend.staging.sh](/packages/deployer/deploy-frontend.staging.sh)
**Note: this is a one-way operation. Once you `eject`, you cant go back!** #### Production environment variables
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. Change in [deployer/deploy-frontend.sh](/packages/deployer/deploy-frontend.sh)
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own. ### Run development server
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it. ```zsh
yarn dev
```
## Learn More ## Deployment
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). From the root of the project,
To learn React, check out the [React documentation](https://reactjs.org/). ### 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)

View File

@ -0,0 +1,4 @@
{
"projectId": "Project:663d04870db27ed66a48e466",
"zip": true
}

View File

@ -0,0 +1,24 @@
<!doctype html>
<html lang="en" class="dark dark:bg-background dark:text-foreground">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="laconic tools dashboard" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#2d89ef" />
<meta name="theme-color" content="#ffffff" />
<link rel="manifest" href="/manifest.json" />
<title>Laconic</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@ -1,7 +0,0 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="500" height="500" fill="#0F86F5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M191.873 125.126C224.893 126.765 250.458 150.121 274.042 172.995C297.925 196.158 323.089 221.108 324.868 254.114C326.718 288.42 308.902 321.108 283.281 344.355C258.67 366.687 225.288 373.859 191.873 374.788C157.228 375.752 119.038 374.394 95.1648 349.588C71.6207 325.125 74.6696 287.843 75.7341 254.114C76.7518 221.865 79.2961 188.525 101.009 164.41C123.845 139.047 157.543 123.423 191.873 125.126Z" fill="#4BA4F7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M229.373 125.126C262.393 126.765 287.958 150.121 311.542 172.995C335.425 196.158 360.589 221.108 362.368 254.114C364.218 288.42 346.402 321.108 320.781 344.355C296.17 366.687 262.788 373.859 229.373 374.788C194.728 375.752 156.538 374.394 132.665 349.588C109.121 325.125 112.17 287.843 113.234 254.114C114.252 221.865 116.796 188.525 138.509 164.41C161.345 139.047 195.043 123.423 229.373 125.126Z" fill="#8AC4FA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M266.873 125.126C299.893 126.765 325.458 150.121 349.042 172.995C372.925 196.158 398.089 221.108 399.868 254.114C401.718 288.42 383.902 321.108 358.281 344.355C333.67 366.687 300.288 373.859 266.873 374.788C232.228 375.752 194.038 374.394 170.165 349.588C146.621 325.125 149.67 287.843 150.734 254.114C151.752 221.865 154.296 188.525 176.009 164.41C198.845 139.047 232.543 123.423 266.873 125.126Z" fill="#CAE4FD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M304.373 125.126C337.393 126.765 362.958 150.121 386.542 172.995C410.425 196.158 435.589 221.108 437.368 254.114C439.218 288.42 421.402 321.108 395.781 344.355C371.17 366.687 337.788 373.859 304.373 374.788C269.728 375.752 231.538 374.394 207.665 349.588C184.121 325.125 187.17 287.843 188.234 254.114C189.252 221.865 191.796 188.525 213.509 164.41C236.345 139.047 270.043 123.423 304.373 125.126Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,36 +1,57 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.1.3",
"private": true, "private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"build": "vite build",
"lint": "tsc --noEmit",
"preview": "vite preview",
"format": "prettier --write .",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": { "dependencies": {
"@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-variable/jetbrains-mono": "^5.0.19",
"@fontsource/inter": "^5.0.16", "@fontsource/inter": "^5.0.16",
"@material-tailwind/react": "^2.1.7", "@mui/material": "^6.1.3",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@snowballtools/material-tailwind-react-fork": "^2.1.10",
"@snowballtools/smartwallet-alchemy-light": "^0.2.0",
"@snowballtools/types": "^0.2.0",
"@snowballtools/utils": "^0.1.1",
"@tanstack/react-query": "^5.22.2", "@tanstack/react-query": "^5.22.2",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2", "@turnkey/http": "^2.10.0",
"@types/node": "^16.18.68", "@turnkey/sdk-react": "^0.1.0",
"@types/react": "^18.2.42", "@turnkey/webauthn-stamper": "^0.5.0",
"@types/react-dom": "^18.2.17", "@walletconnect/ethereum-provider": "^2.16.1",
"@web3modal/siwe": "^4.0.5", "@web3modal/siwe": "4.0.5",
"@web3modal/wagmi": "^4.0.5", "@web3modal/wagmi": "4.0.5",
"assert": "^2.1.0", "assert": "^2.1.0",
"axios": "^1.6.7", "axios": "^1.6.7",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"date-fns": "^3.3.1", "date-fns": "^3.3.1",
"downshift": "^8.3.2", "downshift": "^8.3.2",
"eslint-config-react-app": "^7.0.1", "framer-motion": "^11.0.8",
"gql-client": "^1.0.0", "gql-client": "^1.0.0",
"lottie-react": "^2.4.0",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"octokit": "^3.1.2", "octokit": "^3.1.2",
"react": "^18.2.0", "react": "^18.2.0",
@ -40,57 +61,44 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropdown": "^1.11.0", "react-dropdown": "^1.11.0",
"react-hook-form": "^7.49.0", "react-hook-form": "^7.49.0",
"react-hot-toast": "^2.4.1",
"react-oauth-popup": "^1.0.5", "react-oauth-popup": "^1.0.5",
"react-router-dom": "^6.20.1", "react-router-dom": "^6.20.1",
"react-scripts": "5.0.1",
"react-timer-hook": "^3.0.7", "react-timer-hook": "^3.0.7",
"siwe": "^2.1.4", "siwe": "2.1.4",
"tailwind-variants": "^0.2.0", "tailwind-variants": "^0.2.0",
"typescript": "^4.9.5", "usehooks-ts": "^2.15.1",
"usehooks-ts": "^2.10.0", "uuid": "^9.0.1",
"vertical-stepper-nav": "^1.0.2",
"viem": "^2.7.11", "viem": "^2.7.11",
"wagmi": "^2.5.7", "wagmi": "2.5.7",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint ."
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@chromatic-com/storybook": "^1.3.3",
"@storybook/addon-essentials": "^8.0.10",
"@storybook/addon-interactions": "^8.0.10",
"@storybook/addon-links": "^8.0.10",
"@storybook/addon-onboarding": "^8.0.10",
"@storybook/blocks": "^8.0.10",
"@storybook/react": "^8.0.10",
"@storybook/react-vite": "^8.0.10",
"@storybook/test": "^8.0.10",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.17.0",
"@types/luxon": "^3.3.7", "@types/luxon": "^3.3.7",
"@typescript-eslint/eslint-plugin": "^6.13.2", "@types/node": "^16.18.68",
"@typescript-eslint/parser": "^6.13.2", "@types/react": "^18.2.66",
"eslint": "^8.55.0", "@types/react-dom": "^18.2.22",
"eslint-config-prettier": "^9.1.0", "@types/uuid": "^9.0.8",
"eslint-plugin-prettier": "^5.0.1", "@vitejs/plugin-react": "^4.2.1",
"eslint-plugin-react": "^7.33.2", "autoprefixer": "^10.4.19",
"chromatic": "^11.3.2",
"eslint-plugin-storybook": "^0.8.0",
"postcss": "^8.4.38",
"prettier": "^3.1.0", "prettier": "^3.1.0",
"tailwindcss": "^3.4.1" "storybook": "^8.0.10",
"storybook-addon-remix-react-router": "^3.0.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.3.3",
"vite": "^5.2.0"
} }
} }

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -0,0 +1 @@
350e9ac2-8b27-4a79-9a82-78cfdb68ef71=0eacb7ae462f82c8b0199d28193b0bfa5265973dbb1fe991eec2cab737dfc1ec

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="197" height="2" viewBox="0 0 197 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0.5" y1="1.19141" x2="197" y2="1.19141" stroke="#94A7B8" stroke-dasharray="1 12"/>
</svg>

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="4" fill="#29292E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.0494 24.6233C18.8425 21.8302 20.5713 17.973 20.5706 13.7142C20.5717 13.1361 20.5396 12.5645 20.4762 12L12 12.0008L12.0003 28.2867C11.9996 30.2608 12.7522 32.2356 14.2578 33.7411C15.7633 35.2466 17.7395 36.0001 19.7139 35.9991L19.7134 35.9996L36 36L35.9995 27.5227C35.4362 27.4605 34.8645 27.4285 34.2852 27.4284C30.0275 27.4289 26.1701 29.1577 23.377 31.9507C21.3446 33.9321 18.0858 33.9325 16.0785 31.9252C14.0722 29.9191 14.0715 26.6593 16.0494 24.6233ZM34.2419 13.7624C31.9012 11.4217 28.0982 11.4208 25.7566 13.7624C23.4151 16.1038 23.4159 19.9067 25.7566 22.2473C28.0986 24.5892 31.9004 24.5889 34.2419 22.2473C36.5835 19.9059 36.5839 16.1042 34.2419 13.7624Z" fill="#FBFBFB"/>
</svg>

After

Width:  |  Height:  |  Size: 892 B

View File

@ -1 +1,10 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="500" height="500" fill="#0F86F5"/><path fill-rule="evenodd" clip-rule="evenodd" d="M191.873 125.126C224.893 126.765 250.458 150.121 274.042 172.995C297.925 196.158 323.089 221.108 324.868 254.114C326.718 288.42 308.902 321.108 283.281 344.355C258.67 366.687 225.288 373.859 191.873 374.788C157.228 375.752 119.038 374.394 95.1648 349.588C71.6207 325.125 74.6696 287.843 75.7341 254.114C76.7518 221.865 79.2961 188.525 101.009 164.41C123.845 139.047 157.543 123.423 191.873 125.126Z" fill="#4BA4F7"/><path fill-rule="evenodd" clip-rule="evenodd" d="M229.373 125.126C262.393 126.765 287.958 150.121 311.542 172.995C335.425 196.158 360.589 221.108 362.368 254.114C364.218 288.42 346.402 321.108 320.781 344.355C296.17 366.687 262.788 373.859 229.373 374.788C194.728 375.752 156.538 374.394 132.665 349.588C109.121 325.125 112.17 287.843 113.234 254.114C114.252 221.865 116.796 188.525 138.509 164.41C161.345 139.047 195.043 123.423 229.373 125.126Z" fill="#8AC4FA"/><path fill-rule="evenodd" clip-rule="evenodd" d="M266.873 125.126C299.893 126.765 325.458 150.121 349.042 172.995C372.925 196.158 398.089 221.108 399.868 254.114C401.718 288.42 383.902 321.108 358.281 344.355C333.67 366.687 300.288 373.859 266.873 374.788C232.228 375.752 194.038 374.394 170.165 349.588C146.621 325.125 149.67 287.843 150.734 254.114C151.752 221.865 154.296 188.525 176.009 164.41C198.845 139.047 232.543 123.423 266.873 125.126Z" fill="#CAE4FD"/><path fill-rule="evenodd" clip-rule="evenodd" d="M304.373 125.126C337.393 126.765 362.958 150.121 386.542 172.995C410.425 196.158 435.589 221.108 437.368 254.114C439.218 288.42 421.402 321.108 395.781 344.355C371.17 366.687 337.788 373.859 304.373 374.788C269.728 375.752 231.538 374.394 207.665 349.588C184.121 325.125 187.17 287.843 188.234 254.114C189.252 221.865 191.796 188.525 213.509 164.41C236.345 139.047 270.043 123.423 304.373 125.126Z" fill="white"/></svg> <svg width="115" height="20" viewBox="0 0 115 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z" fill="#FBFBFB"/>
<path d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z" fill="#FBFBFB"/>
<path d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z" fill="#FBFBFB"/>
<path d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z" fill="#FBFBFB"/>
<path d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z" fill="#FBFBFB"/>
<path d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z" fill="#FBFBFB"/>
<path d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z" fill="#FBFBFB"/>
<path d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z" fill="#FBFBFB"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,5 +1,5 @@
{ {
"name": "Snowball Tools Dashboard", "name": "Laconic Tools Dashboard",
"short_name": "snowball tools", "short_name": "snowball tools",
"icons": [ "icons": [
{ {

View File

@ -1,10 +0,0 @@
<svg width="333" height="5" viewBox="0 0 333 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 4L6.39555 1.30714C8.38078 0.47125 10.6192 0.47125 12.6045 1.30714L15.8955 2.69286C17.8808 3.52875 20.1192 3.52875 22.1045 2.69286L25.3955 1.30714C27.3808 0.47125 29.6192 0.47125 31.6045 1.30714L34.8955 2.69286C36.8808 3.52875 39.1192 3.52875 41.1045 2.69286L44.3955 1.30714C46.3808 0.47125 48.6192 0.47125 50.6045 1.30714L53.8955 2.69286C55.8808 3.52875 58.1192 3.52875 60.1045 2.69286L63.3955 1.30714C65.3808 0.47125 67.6192 0.47125 69.6045 1.30714L72.8955 2.69286C74.8808 3.52875 77.1192 3.52875 79.1045 2.69286L82.3955 1.30714C84.3808 0.47125 86.6192 0.47125 88.6045 1.30714L91.8955 2.69286C93.8808 3.52875 96.1192 3.52875 98.1045 2.69286L101.396 1.30714C103.381 0.47125 105.619 0.47125 107.604 1.30714L110.896 2.69286C112.881 3.52875 115.119 3.52875 117.104 2.69286L120.396 1.30714C122.381 0.47125 124.619 0.47125 126.604 1.30714L129.896 2.69286C131.881 3.52875 134.119 3.52875 136.104 2.69286L139.396 1.30714C141.381 0.47125 143.619 0.47125 145.604 1.30714L148.896 2.69286C150.881 3.52875 153.119 3.52875 155.104 2.69286L158.396 1.30714C160.381 0.47125 162.619 0.47125 164.604 1.30714L167.896 2.69286C169.881 3.52875 172.119 3.52875 174.104 2.69286L177.396 1.30714C179.381 0.47125 181.619 0.47125 183.604 1.30714L186.896 2.69286C188.881 3.52875 191.119 3.52875 193.104 2.69286L196.396 1.30714C198.381 0.47125 200.619 0.47125 202.604 1.30714L205.896 2.69286C207.881 3.52875 210.119 3.52875 212.104 2.69286L215.396 1.30714C217.381 0.47125 219.619 0.47125 221.604 1.30714L224.896 2.69286C226.881 3.52875 229.119 3.52875 231.104 2.69286L234.396 1.30714C236.381 0.47125 238.619 0.47125 240.604 1.30714L243.896 2.69286C245.881 3.52875 248.119 3.52875 250.104 2.69286L253.396 1.30714C255.381 0.47125 257.619 0.47125 259.604 1.30714L262.896 2.69286C264.881 3.52875 267.119 3.52875 269.104 2.69286L272.396 1.30714C274.381 0.47125 276.619 0.47125 278.604 1.30714L281.896 2.69286C283.881 3.52875 286.119 3.52875 288.104 2.69286L291.396 1.30714C293.381 0.47125 295.619 0.47125 297.604 1.30714L300.896 2.69286C302.881 3.52875 305.119 3.52875 307.104 2.69286L310.396 1.30714C312.381 0.47125 314.619 0.47125 316.604 1.30714L319.973 2.72566C321.913 3.54216 324.095 3.56183 326.049 2.78039L330.029 1.18845C331.936 0.425535 334.064 0.425535 335.971 1.18845L343 4" stroke="#DBEBF9"/>
<path d="M6.39555 1.30714L0 4H342.5L336.027 1.27434C334.087 0.457837 331.905 0.438174 329.951 1.21961L326.049 2.78039C324.095 3.56183 321.913 3.54216 319.973 2.72566L316.604 1.30714C314.619 0.47125 312.381 0.47125 310.396 1.30714L307.104 2.69286C305.119 3.52875 302.881 3.52875 300.896 2.69286L297.604 1.30714C295.619 0.47125 293.381 0.47125 291.396 1.30714L288.104 2.69286C286.119 3.52875 283.881 3.52875 281.896 2.69286L278.604 1.30714C276.619 0.47125 274.381 0.47125 272.396 1.30714L269.104 2.69286C267.119 3.52875 264.881 3.52875 262.896 2.69286L259.604 1.30714C257.619 0.47125 255.381 0.47125 253.396 1.30714L250.104 2.69286C248.119 3.52875 245.881 3.52875 243.896 2.69286L240.604 1.30714C238.619 0.47125 236.381 0.47125 234.396 1.30714L231.104 2.69286C229.119 3.52875 226.881 3.52875 224.896 2.69286L221.604 1.30714C219.619 0.47125 217.381 0.47125 215.396 1.30714L212.104 2.69286C210.119 3.52875 207.881 3.52875 205.896 2.69286L202.604 1.30714C200.619 0.47125 198.381 0.47125 196.396 1.30714L193.104 2.69286C191.119 3.52875 188.881 3.52875 186.896 2.69286L183.604 1.30714C181.619 0.47125 179.381 0.47125 177.396 1.30714L174.104 2.69286C172.119 3.52875 169.881 3.52875 167.896 2.69286L164.604 1.30714C162.619 0.47125 160.381 0.47125 158.396 1.30714L155.104 2.69286C153.119 3.52875 150.881 3.52875 148.896 2.69286L145.604 1.30714C143.619 0.47125 141.381 0.47125 139.396 1.30714L136.104 2.69286C134.119 3.52875 131.881 3.52875 129.896 2.69286L126.604 1.30714C124.619 0.47125 122.381 0.47125 120.396 1.30714L117.104 2.69286C115.119 3.52875 112.881 3.52875 110.896 2.69286L107.604 1.30714C105.619 0.47125 103.381 0.47125 101.396 1.30714L98.1045 2.69286C96.1192 3.52875 93.8808 3.52875 91.8955 2.69286L88.6045 1.30714C86.6192 0.47125 84.3808 0.47125 82.3955 1.30714L79.1045 2.69286C77.1192 3.52875 74.8808 3.52875 72.8955 2.69286L69.6045 1.30714C67.6192 0.47125 65.3808 0.47125 63.3955 1.30714L60.1045 2.69286C58.1192 3.52875 55.8808 3.52875 53.8955 2.69286L50.6045 1.30714C48.6192 0.47125 46.3808 0.47125 44.3955 1.30714L41.1045 2.69286C39.1192 3.52875 36.8808 3.52875 34.8955 2.69286L31.6045 1.30714C29.6192 0.47125 27.3808 0.47125 25.3955 1.30714L22.1045 2.69286C20.1192 3.52875 17.8808 3.52875 15.8955 2.69286L12.6045 1.30714C10.6192 0.47125 8.38078 0.47125 6.39555 1.30714Z" fill="url(#paint0_linear_1729_11298)"/>
<defs>
<linearGradient id="paint0_linear_1729_11298" x1="171.25" y1="0" x2="171.25" y2="4" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6F4FF"/>
<stop offset="1" stop-color="#F9FCFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,4 @@
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M-12.6045 1.27694L-19 3.9698H37.5L34.8955 2.66266L31.6045 1.27694C29.6192 0.441052 27.3808 0.441052 25.3955 1.27694L22.1045 2.66266C20.1192 3.49855 17.8808 3.49855 15.8955 2.66266L12.6045 1.27694C10.6192 0.441052 8.38078 0.441052 6.39555 1.27694L3.10446 2.66266C1.11922 3.49855 -1.11922 3.49855 -3.10445 2.66266L-6.39554 1.27694C-8.38078 0.441052 -10.6192 0.441052 -12.6045 1.27694Z" fill="currentColor"/>
<path d="M25.7836 1.58773L22.4925 2.97346C20.3832 3.86159 18.0049 3.86159 15.8955 2.97346L12.6045 1.58774C10.7433 0.804089 8.64476 0.804088 6.7836 1.58773L3.49251 2.97346C1.3832 3.86159 -0.99514 3.86159 -3.10445 2.97346L-6.39554 1.58774C-8.2567 0.804089 -10.3552 0.804088 -12.2164 1.58773L-18.6119 4.2806L-19 3.35896L-12.6045 0.666099C-10.4951 -0.222033 -8.1168 -0.222033 -6.00749 0.6661L-2.7164 2.05182C-0.855238 2.83547 1.2433 2.83547 3.10446 2.05182L6.39555 0.666099C8.50486 -0.222033 10.8832 -0.222033 12.9925 0.6661L16.2836 2.05182C18.1448 2.83547 20.2433 2.83547 22.1045 2.05182L25.3955 0.666099L25.7836 1.58773Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,15 @@
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1177_19315)">
<path d="M-12.6045 1.27694L-19 3.9698H37.5L34.8955 2.66266L31.6045 1.27694C29.6192 0.441052 27.3808 0.441052 25.3955 1.27694L22.1045 2.66266C20.1192 3.49855 17.8808 3.49855 15.8955 2.66266L12.6045 1.27694C10.6192 0.441052 8.38078 0.441052 6.39555 1.27694L3.10446 2.66266C1.11922 3.49855 -1.11922 3.49855 -3.10445 2.66266L-6.39554 1.27694C-8.38078 0.441052 -10.6192 0.441052 -12.6045 1.27694Z" fill="url(#paint0_linear_1177_19315)"/>
<path d="M25.7836 1.58773L22.4925 2.97346C20.3832 3.86159 18.0049 3.86159 15.8955 2.97346L12.6045 1.58774C10.7433 0.804089 8.64476 0.804088 6.7836 1.58773L3.49251 2.97346C1.3832 3.86159 -0.99514 3.86159 -3.10445 2.97346L-6.39554 1.58774C-8.2567 0.804089 -10.3552 0.804088 -12.2164 1.58773L-18.6119 4.2806L-19 3.35896L-12.6045 0.666099C-10.4951 -0.222033 -8.1168 -0.222033 -6.00749 0.6661L-2.7164 2.05182C-0.855238 2.83547 1.2433 2.83547 3.10446 2.05182L6.39555 0.666099C8.50486 -0.222033 10.8832 -0.222033 12.9925 0.6661L16.2836 2.05182C18.1448 2.83547 20.2433 2.83547 22.1045 2.05182L25.3955 0.666099L25.7836 1.58773Z" fill="#DBEBF9"/>
</g>
<defs>
<linearGradient id="paint0_linear_1177_19315" x1="9.25" y1="0.650024" x2="9.25" y2="3.9698" gradientUnits="userSpaceOnUse">
<stop stop-color="#EBF6FF"/>
<stop offset="1" stop-color="#F7FCFF" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_1177_19315">
<rect width="19" height="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,10 @@
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1175_19292)">
<path d="M-9.5 0L-3.10445 2.69286C-1.11922 3.52875 1.11922 3.52875 3.10446 2.69286L6.39555 1.30714C8.38078 0.47125 10.6192 0.47125 12.6045 1.30714L15.8955 2.69286C17.8808 3.52875 20.1192 3.52875 22.1045 2.69286L28.5 0" stroke="#082F56" stroke-opacity="0.1"/>
</g>
<defs>
<clipPath id="clip0_1175_19292">
<rect width="19" height="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 507 B

View File

@ -0,0 +1,9 @@
#!/bin/bash
(cd /Users/rabbit-m2/p/snowball/js-sdk && NO_CLEAN=1 turbo build)
(cd ../.. && ./scripts/yarn-file-for-local-dev.sh)
rm -rf node_modules/.vite
yarn dev

View File

@ -1,4 +1,3 @@
import React from 'react';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import App from './App'; import App from './App';

View File

@ -1,7 +1,6 @@
import React from 'react'; import { useEffect } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import OrgSlug from './pages/OrgSlug';
import Projects from './pages/org-slug'; import Projects from './pages/org-slug';
import Settings from './pages/org-slug/Settings'; import Settings from './pages/org-slug/Settings';
import { import {
@ -10,12 +9,15 @@ import {
} from './pages/org-slug/projects/routes'; } from './pages/org-slug/projects/routes';
import ProjectSearchLayout from './layouts/ProjectSearch'; import ProjectSearchLayout from './layouts/ProjectSearch';
import Index from './pages'; import Index from './pages';
import Login from './pages/Login'; import AuthPage from './pages/AuthPage';
import { DashboardLayout } from './pages/org-slug/layout';
import Web3Provider from 'context/Web3Provider';
import { BASE_URL } from 'utils/constants';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: ':orgSlug', path: ':orgSlug',
element: <OrgSlug />, element: <DashboardLayout />,
children: [ children: [
{ {
element: <ProjectSearchLayout />, element: <ProjectSearchLayout />,
@ -46,12 +48,37 @@ const router = createBrowserRouter([
}, },
{ {
path: '/login', path: '/login',
element: <Login />, element: <AuthPage />,
}, },
]); ]);
function App() { function App() {
return <RouterProvider router={router} />; // Hacky way of checking session
// TODO: Handle redirect backs
useEffect(() => {
fetch(`${BASE_URL}/auth/session`, {
credentials: 'include',
}).then((res) => {
const path = window.location.pathname;
if (res.status !== 200) {
localStorage.clear();
if (path !== '/login') {
window.location.pathname = '/login';
}
} else {
if (path === '/login') {
window.location.pathname = '/';
}
}
});
}, []);
return (
<Web3Provider>
<RouterProvider router={router} />
</Web3Provider>
);
} }
export default App; export default App;

View File

@ -1,32 +1,29 @@
import {
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO,
VITE_GITHUB_PWA_TEMPLATE_REPO,
VITE_GITHUB_NEXT_APP_TEMPLATE_REPO,
} from 'utils/constants';
export default [ export default [
{ {
id: '1', id: '1',
name: 'Progressive Web App (PWA)', name: 'Progressive Web App (PWA)',
icon: '^', icon: 'web',
repoFullName: `${process.env.REACT_APP_GITHUB_PWA_TEMPLATE_REPO}`, repoFullName: `${VITE_GITHUB_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
}, },
{ {
id: '2', id: '2',
name: 'Image Upload PWA', name: 'Image Upload PWA',
icon: '^', icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`, repoFullName: `${VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
}, },
{ {
id: '3', id: '3',
name: 'Kotlin', name: 'Next.js + React + TailwindCSS',
icon: '^', icon: 'web',
repoFullName: '', repoFullName: `${VITE_GITHUB_NEXT_APP_TEMPLATE_REPO}`,
}, isComingSoon: false,
{
id: '4',
name: 'React Native',
icon: '^',
repoFullName: '',
},
{
id: '5',
name: 'Swift',
icon: '^',
repoFullName: '',
}, },
]; ];

View File

@ -0,0 +1,219 @@
import React from 'react';
type Props = React.PropsWithChildren<{
className?: string;
snowZIndex?: number;
}>;
export const CloudyFlow = ({ className, children, snowZIndex }: Props) => {
return (
<div className={`bg-sky-100 relative ${className || ''}`}>
{children}
<div
className="absolute inset-0 overflow-hidden"
style={{ zIndex: snowZIndex || 0 }}
>
<div className="w-[3.72px] h-[3.72px] left-[587px] top-[147px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.72px] h-[4.72px] left-[742px] top-[336px] absolute bg-white rounded-full" />
<div className="w-[3.49px] h-[3.49px] left-[36px] top-[68px] absolute bg-white rounded-full" />
<div className="w-[3.25px] h-[3.25px] left-[55px] top-[114px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.60px] h-[5.60px] left-[1334px] top-[63px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.53px] h-[3.53px] left-[988px] top-[108px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.65px] h-[2.65px] left-[1380px] top-[16px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.60px] h-[3.60px] left-[1284px] top-[95px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-0.5 h-0.5 left-[1191px] top-[376px] absolute bg-white rounded-full" />
<div className="w-[2.83px] h-[2.83px] left-[1182px] top-[257px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.41px] h-[2.41px] left-[627px] top-[26px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.71px] h-[5.71px] left-[30px] top-[33px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.09px] h-[4.09px] left-[425px] top-[386px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.38px] h-[3.38px] left-[394px] top-[29px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.70px] h-[4.70px] left-[817px] top-[113px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-1.5 h-1.5 left-[1194px] top-[332px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.89px] h-[4.89px] left-[811px] top-[76px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.25px] h-[4.25px] left-[458px] top-[366px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.82px] h-[4.82px] left-[936px] top-[46px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.74px] h-[3.74px] left-[64px] top-[132px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-1 h-1 left-[763px] top-[10px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.67px] h-[3.67px] left-[861px] top-[106px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.62px] h-[3.62px] left-[710px] top-[278px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.45px] h-[3.45px] left-[1069px] top-[329px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.92px] h-[2.92px] left-[1286px] top-[299px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.84px] h-[4.84px] left-[219px] top-[269px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.39px] h-[2.39px] left-[817px] top-[121px] absolute bg-white rounded-full" />
<div className="w-[5.83px] h-[5.83px] left-[168px] top-[320px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.94px] h-[5.94px] left-[419px] top-[244px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.67px] h-[4.67px] left-[604px] top-[309px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.87px] h-[5.87px] left-[1098px] top-[379px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.85px] h-[5.85px] left-[644px] top-[352px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.19px] h-[4.19px] left-[1361px] top-[349px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[2.84px] h-[2.84px] left-[1299px] top-[194px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.51px] h-[4.51px] left-[468px] top-[319px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.73px] h-[2.73px] left-[1084px] top-[86px] absolute bg-white rounded-full" />
<div className="w-[3.43px] h-[3.43px] left-[1271px] top-[28px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.25px] h-[2.25px] left-[106px] top-[197px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.82px] h-[2.82px] left-[122px] top-[173px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.89px] h-[2.89px] left-[343px] top-[345px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.82px] h-[2.82px] left-[433px] top-[40px] absolute bg-white rounded-full" />
<div className="w-[4.11px] h-[4.11px] left-[904px] top-[350px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.42px] h-[4.42px] left-[1066px] top-[349px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.67px] h-[4.67px] left-[904px] top-[317px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.54px] h-[5.54px] left-[501px] top-[336px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.11px] h-[4.11px] left-[1149px] top-[206px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.55px] h-[3.55px] left-[235px] top-[362px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[2.60px] h-[2.60px] left-[1246px] top-[1px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.94px] h-[2.94px] left-[788px] top-[6px] absolute bg-white rounded-full" />
<div className="w-[4.19px] h-[4.19px] left-[527px] top-[365px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.13px] h-[4.13px] left-[201px] top-[53px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.94px] h-[2.94px] left-[765px] top-[13px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.11px] h-[4.11px] left-[1254px] top-[30px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.85px] h-[3.85px] left-[107px] top-[316px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.72px] h-[5.72px] left-[1305px] top-[8px] absolute bg-white rounded-full" />
<div className="w-[5.46px] h-[5.46px] left-[102px] top-[316px] absolute bg-white rounded-full" />
<div className="w-[3.77px] h-[3.77px] left-[1322px] top-[334px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.84px] h-[4.84px] left-[1370px] top-[317px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.55px] h-[5.55px] left-[945px] top-[258px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.24px] h-[2.24px] left-[266px] top-[362px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.89px] h-[2.89px] left-[987px] top-[156px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.46px] h-[3.46px] left-[10px] top-[168px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.67px] h-[5.67px] left-[441px] top-[291px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.07px] h-[4.07px] left-[962px] top-[364px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.57px] h-[5.57px] left-[599px] top-[293px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.41px] h-[4.41px] left-[358px] top-[163px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.31px] h-[2.31px] left-[670px] top-[182px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.60px] h-[2.60px] left-[621px] top-[257px] absolute bg-white rounded-full" />
<div className="w-[2.16px] h-[2.16px] left-[48px] top-[322px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.91px] h-[5.91px] left-[491px] top-[5px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.50px] h-[5.50px] left-[1139px] top-[274px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.74px] h-[3.74px] left-[24px] top-[177px] absolute bg-white rounded-full" />
<div className="w-[5.57px] h-[5.57px] left-[1166px] top-[316px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5px] h-[5px] left-[445px] top-[326px] absolute bg-white rounded-full" />
<div className="w-[3.01px] h-[3.01px] left-[438px] top-[252px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.14px] h-[4.14px] left-[554px] top-[131px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.30px] h-[5.30px] left-[1010px] top-[116px] absolute bg-white rounded-full" />
<div className="w-[5.53px] h-[5.53px] left-[437px] top-[367px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.87px] h-[5.87px] left-[948px] top-[27px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[2.87px] h-[2.87px] left-[826px] top-[20px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.89px] h-[3.89px] left-[1222px] top-[112px] absolute bg-white rounded-full" />
<div className="w-[3.77px] h-[3.77px] left-[796px] top-[395px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.09px] h-[2.09px] left-[272px] top-[103px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.12px] h-[4.12px] left-[76px] top-[2px] absolute bg-white rounded-full" />
<div className="w-[3.51px] h-[3.51px] left-[226px] top-[276px] absolute bg-white rounded-full" />
<div className="w-[3.03px] h-[3.03px] left-[723px] top-[197px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.14px] h-[2.14px] left-[1259px] top-[17px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.28px] h-[3.28px] left-[1244px] top-[293px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.45px] h-[4.45px] left-[118px] top-[128px] absolute bg-white rounded-full" />
<div className="w-[4.15px] h-[4.15px] left-[490px] top-[204px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[4.93px] h-[4.93px] left-[552px] top-[38px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.56px] h-[5.56px] left-[115px] top-[303px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.35px] h-[2.35px] left-[509px] top-[278px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.24px] h-[5.24px] left-[804px] top-[389px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.44px] h-[2.44px] left-[1013px] top-[50px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.69px] h-[3.69px] left-[1183px] top-[95px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.83px] h-[2.83px] left-[278px] top-[181px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.22px] h-[3.22px] left-[1316px] top-[282px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.55px] h-[3.55px] left-[736px] top-[119px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.29px] h-[2.29px] left-[483px] top-[319px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.14px] h-[2.14px] left-[1135px] top-[19px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.64px] h-[3.64px] left-[39px] top-[126px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.30px] h-[5.30px] left-[237px] top-[369px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.57px] h-[5.57px] left-[1156px] top-[126px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.78px] h-[2.78px] left-[1295px] top-[74px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-0.5 h-0.5 left-[76px] top-[227px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.61px] h-[3.61px] left-[108px] top-[89px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.37px] h-[5.37px] left-[191px] top-[167px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.18px] h-[4.18px] left-[164px] top-[117px] absolute bg-white rounded-full" />
<div className="w-[5.15px] h-[5.15px] left-[533px] top-[261px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-1.5 h-1.5 left-[327px] top-[157px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.74px] h-[5.74px] left-[1242px] top-[122px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.22px] h-[4.22px] left-[129px] top-[265px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.30px] h-[2.30px] left-[1305px] top-[86px] absolute bg-white rounded-full" />
<div className="w-[2.70px] h-[2.70px] left-[1235px] top-[120px] absolute bg-white rounded-full" />
<div className="w-[2.15px] h-[2.15px] left-[596px] top-[103px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.17px] h-[2.17px] left-[483px] top-[233px] absolute bg-white rounded-full" />
<div className="w-[5.09px] h-[5.09px] left-[706px] top-[188px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.15px] h-[4.15px] left-[141px] top-[2px] absolute bg-white rounded-full" />
<div className="w-[4.20px] h-[4.20px] left-[48px] top-[124px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.51px] h-[3.51px] left-[1095px] top-[201px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.21px] h-[3.21px] left-[730px] top-[185px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.61px] h-[2.61px] left-[722px] top-[319px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.28px] h-[2.28px] left-[444px] top-[26px] absolute bg-white rounded-full" />
<div className="w-[4.49px] h-[4.49px] left-[355px] top-[212px] absolute bg-white rounded-full" />
<div className="w-[3.69px] h-[3.69px] left-[1280px] top-[312px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.23px] h-[4.23px] left-[1114px] top-[113px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.48px] h-[3.48px] left-[729px] top-[117px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.11px] h-[4.11px] left-[647px] top-[276px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.16px] h-[4.16px] left-[365px] top-[116px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.35px] h-[5.35px] left-[94px] top-[194px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.84px] h-[5.84px] left-[2px] top-[84px] absolute bg-white rounded-full" />
<div className="w-[4.43px] h-[4.43px] left-[1382px] top-[23px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.38px] h-[5.38px] left-[857px] top-[284px] absolute bg-white rounded-full" />
<div className="w-[2.77px] h-[2.77px] left-[1228px] top-[385px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.65px] h-[4.65px] left-[165px] top-[184px] absolute bg-white rounded-full" />
<div className="w-[5.53px] h-[5.53px] left-[568px] top-[354px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.59px] h-[3.59px] left-[1303px] top-[371px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.84px] h-[5.84px] left-[235px] top-[188px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.84px] h-[3.84px] left-[902px] top-[211px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.45px] h-[3.45px] left-[367px] top-[161px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.08px] h-[4.08px] left-[855px] top-[394px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.25px] h-[3.25px] left-[383px] top-[47px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.39px] h-[4.39px] left-[1313px] top-[165px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.60px] h-[5.60px] left-[697px] top-[327px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.09px] h-[2.09px] left-[646px] top-[370px] absolute bg-white rounded-full" />
<div className="w-[3.13px] h-[3.13px] left-[728px] top-[122px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.53px] h-[5.53px] left-[203px] top-[293px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.83px] h-[5.83px] left-[424px] top-[121px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.82px] h-[4.82px] left-[1358px] top-[176px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.18px] h-[3.18px] left-[1212px] top-[24px] absolute bg-white rounded-full" />
<div className="w-[5.23px] h-[5.23px] left-[260px] top-[217px] absolute bg-white rounded-full" />
<div className="w-[5.29px] h-[5.29px] left-[1204px] top-[367px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.47px] h-[3.47px] left-[1163px] top-[159px] absolute bg-white rounded-full" />
<div className="w-[5.77px] h-[5.77px] left-[1257px] top-[115px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.31px] h-[5.31px] left-[222px] top-[356px] absolute bg-white rounded-full" />
<div className="w-[5.43px] h-[5.43px] left-[1141px] top-[349px] absolute bg-white rounded-full" />
<div className="w-[5.62px] h-[5.62px] left-[683px] top-[81px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.91px] h-[3.91px] left-[269px] top-[3px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.51px] h-[3.51px] left-[305px] top-[310px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.41px] h-[5.41px] left-[530px] top-[94px] absolute bg-white rounded-full" />
<div className="w-[4.64px] h-[4.64px] left-[730px] top-[301px] absolute bg-white rounded-full" />
<div className="w-[3.59px] h-[3.59px] left-[716px] top-[14px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.77px] h-[4.77px] left-[544px] top-[13px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[2.29px] h-[2.29px] left-[357px] top-[281px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.42px] h-[2.42px] left-[1346px] top-[112px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.42px] h-[3.42px] left-[671px] top-[150px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.40px] h-[4.40px] left-[1324px] top-[268px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.21px] h-[5.21px] left-[1028px] top-[376px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.27px] h-[4.27px] left-[499px] top-[50px] absolute bg-white rounded-full" />
<div className="w-[4.35px] h-[4.35px] left-[543px] top-[359px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.25px] h-[5.25px] left-[1245px] top-[296px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.52px] h-[5.52px] left-[360px] top-[98px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.46px] h-[4.46px] left-[741px] top-[358px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[3.90px] h-[3.90px] left-[1262px] top-[184px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[5.75px] h-[5.75px] left-[552px] top-[335px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[4.95px] h-[4.95px] left-[120px] top-[178px] absolute bg-white rounded-full" />
<div className="w-[3.28px] h-[3.28px] left-[1337px] top-[293px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[2.43px] h-[2.43px] left-[233px] top-[310px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-1 h-1 left-[218px] top-[322px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.68px] h-[3.68px] left-[984px] top-[8px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[2.44px] h-[2.44px] left-[832px] top-[55px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.93px] h-[3.93px] left-[1105px] top-[209px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.08px] h-[4.08px] left-[957px] top-[23px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[2.33px] h-[2.33px] left-[1066px] top-[390px] absolute bg-white bg-opacity-80 rounded-full" />
<div className="w-[3.25px] h-[3.25px] left-[737px] top-[118px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.18px] h-[5.18px] left-[202px] top-[19px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.05px] h-[5.05px] left-[466px] top-[17px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.85px] h-[3.85px] left-[144px] top-[153px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.35px] h-[5.35px] left-[233px] top-[330px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-1 h-1 left-[730px] top-[179px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[4.46px] h-[4.46px] left-[1156px] top-[342px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.22px] h-[5.22px] left-[1275px] top-[204px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.50px] h-[5.50px] left-[38px] top-[343px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.14px] h-[5.14px] left-[867px] top-[113px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[2.19px] h-[2.19px] left-[1277px] top-[314px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[3.74px] h-[3.74px] left-[1136px] top-[197px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.37px] h-[5.37px] left-[34px] top-[226px] absolute bg-white bg-opacity-60 rounded-full" />
<div className="w-[5.93px] h-[5.93px] left-[727px] top-[272px] absolute bg-white bg-opacity-50 rounded-full" />
<div className="w-[5.29px] h-[5.29px] left-[277px] top-[43px] absolute bg-white bg-opacity-80 rounded-full" />
</div>
</div>
);
};

View File

@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { import {
DayPicker, DayPicker,
@ -12,7 +12,7 @@ import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverHandler, PopoverHandler,
} from '@material-tailwind/react'; } from '@snowballtools/material-tailwind-react-fork';
import HorizontalLine from './HorizontalLine'; import HorizontalLine from './HorizontalLine';
@ -120,14 +120,11 @@ const DatePicker = ({
handler={(value) => setIsOpen(value)} handler={(value) => setIsOpen(value)}
> >
<PopoverHandler> <PopoverHandler>
<Input <Input onChange={() => null} value={inputValue} />
onChange={() => null}
value={inputValue}
crossOrigin={undefined}
/>
</PopoverHandler> </PopoverHandler>
{/* TODO: Figure out what placeholder is for */} {/* TODO: Figure out what placeholder is for */}
<PopoverContent placeholder={''}> {/* @ts-ignore */}
<PopoverContent>
{mode === 'single' && ( {mode === 'single' && (
<DayPicker <DayPicker
mode="single" mode="single"
@ -152,7 +149,6 @@ const DatePicker = ({
className="rounded-full mr-2" className="rounded-full mr-2"
variant="outlined" variant="outlined"
onClick={() => setIsOpen(false)} onClick={() => setIsOpen(false)}
placeholder={''}
> >
Cancel Cancel
</Button> </Button>
@ -162,7 +158,6 @@ const DatePicker = ({
className="rounded-full" className="rounded-full"
color="gray" color="gray"
onClick={() => handleRangeSelect()} onClick={() => handleRangeSelect()}
placeholder={''}
> >
Select Select
</Button> </Button>

View File

@ -1,4 +1,3 @@
import React from 'react';
import { import {
default as ReactDropdown, default as ReactDropdown,
Option as ReactDropdownOption, Option as ReactDropdownOption,

View File

@ -1,13 +1,22 @@
import { Duration } from 'luxon'; import { Duration } from 'luxon';
import React from 'react'; import { ComponentPropsWithoutRef } from 'react';
import { cn } from 'utils/classnames';
const FormatMillisecond = ({ time }: { time: number }) => { export interface FormatMilliSecondProps
extends ComponentPropsWithoutRef<'div'> {
time: number;
}
const FormatMillisecond = ({ time, ...props }: FormatMilliSecondProps) => {
const formatTime = Duration.fromMillis(time) const formatTime = Duration.fromMillis(time)
.shiftTo('days', 'hours', 'minutes', 'seconds') .shiftTo('days', 'hours', 'minutes', 'seconds')
.toObject(); .toObject();
return ( return (
<div> <div
{...props}
className={cn('text-sm text-elements-mid-em', props?.className)}
>
{formatTime.days !== 0 && <span>{formatTime.days}d&nbsp;</span>} {formatTime.days !== 0 && <span>{formatTime.days}d&nbsp;</span>}
{formatTime.hours !== 0 && <span>{formatTime.hours}h&nbsp;</span>} {formatTime.hours !== 0 && <span>{formatTime.hours}h&nbsp;</span>}
{formatTime.minutes !== 0 && <span>{formatTime.minutes}m&nbsp;</span>} {formatTime.minutes !== 0 && <span>{formatTime.minutes}m&nbsp;</span>}

View File

@ -1,5 +1,3 @@
import React from 'react';
const HorizontalLine = () => { const HorizontalLine = () => {
return <hr className="h-px bg-gray-100 border-0" />; return <hr className="h-px bg-gray-100 border-0" />;
}; };

Some files were not shown because too many files have changed in this diff Show More