Compare commits

..
Author SHA1 Message Date
dboreham 175af0f9e0 Deploy frontend app directly from github repo 2024-07-05 07:01:26 -06: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
dboreham 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
dboreham 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
dboreham e2a3254563 Add missing rpid env var 2024-06-18 13:47:26 -06:00
dboreham 8589bf4094 Make deployment scripts more robust 2024-06-18 11:15:31 -06:00
dboreham f8ebdfe7aa Merge pull request #207 from snowball-tools/telackey-patch-1
Update config.staging.yml
2024-06-18 06:31:58 -06:00
telackey 2166b2f800 Update config.staging.yml 2024-06-17 23:41:01 -05:00
dboreham 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
nabarun 8b39f664ad Replace REACT_APP env with VITE 2024-04-25 16:59:02 +05:30
nabarun 5f4be30799 Remove current deployment and publish ApplicationDeploymentRemovalRequest for project DNS deployment 2024-04-25 16:59:02 +05:30
nabarun b53e12b94b Add script for publishing ApplicationDeploymentRemovalRecord record 2024-04-25 16:59:02 +05:30
nabarun f290b5c0b5 Implement checking for deployment removal records in intervals 2024-04-25 16:59:02 +05:30
nabarun 4fa6f418ba Lint fix 2024-04-25 16:59:02 +05:30
Eric Lewis 328da7fdc8 feat: submit delete deployment request
doesn't appear to work when the deployment is current?
2024-04-25 16:57:14 +05:30
Eric Lewis 8210512eea fix: missing key 2024-04-25 16:57:14 +05:30
Eric Lewis cd2dce2404 fix: don't use two refs 2024-04-25 16:57:14 +05:30
Eric Lewis c2158510d9 feat: clean up stuck builds 2024-04-25 16:57:14 +05:30
Eric Lewis 096fd04a22 fix: project card settings links 2024-04-25 16:57:14 +05:30
Eric Lewis a8d93732ce feat(gql-client): deployment removal request 2024-04-25 16:57:14 +05:30
Eric Lewis 953c3fc10b feat(gql): deployment removal request 2024-04-25 16:57:14 +05:30
Eric Lewis 3cbea57294 feat(registry): deployment removal request 2024-04-25 16:57:14 +05:30
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
nabarunandprathamesh 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
nabarun 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
neeraj dc6f436409 Handle review changes 2024-03-01 14:04:43 +05:30
neeraj 691edf3508 Update toast message 2024-03-01 13:26:38 +05:30
neeraj fe599f97a3 Handle error if repo already exists 2024-03-01 12:20:19 +05:30
475 changed files with 20561 additions and 10201 deletions
+2
View File
@@ -19,5 +19,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: yarn
- name: Build libs
run: yarn workspace gql-client run build
- name: Linter check
run: yarn lint
@@ -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 }}
+2
View File
@@ -5,3 +5,5 @@ yarn-error.log
.yarnrc
packages/backend/environments/local.toml
packages/backend/dev/
packages/frontend/dist/
+1
View File
@@ -0,0 +1 @@
v20.12.1
+13 -25
View File
@@ -99,25 +99,13 @@ Let us assume the following domains for backend and frontend
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
laconic-so deployment --dir laconic-loaded-deployment exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
```
- Get the REST and GQL endpoint ports of Laconicd and replace the ports for `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
```bash
# For registryConfig.restEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
# 0.0.0.0:32777
# For registryConfig.gqlEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
# 0.0.0.0:32771
```
- Set authority in `registryConfig.authority` in backend [config file](packages/backend/environments/local.toml)
- Run the script to create bond, reserve the authority and set authority bond
```bash
@@ -157,14 +145,14 @@ Let us assume the following domains for backend and frontend
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
VITE_GITHUB_CLIENT_ID = <CLIENT_ID>
```
- Set `REACT_APP_GITHUB_PWA_TEMPLATE_REPO` and `REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
- Set `VITE_GITHUB_PWA_TEMPLATE_REPO` and `VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
VITE_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
```
- Production
@@ -172,17 +160,17 @@ Let us assume the following domains for backend and frontend
- Set the following values in [.env](packages/frontend/.env) file
```env
REACT_APP_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
VITE_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Set project home page URL to `https://dashboard.snowballtools.com`
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
- On creation of project, use the `Project ID` and set it in `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <PROJECT_ID>
VITE_WALLET_CONNECT_ID = <PROJECT_ID>
```
- Build the React application
@@ -202,17 +190,17 @@ Let us assume the following domains for backend and frontend
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
```env
REACT_APP_SERVER_URL = 'http://localhost:8000'
VITE_SERVER_URL = 'http://localhost:8000'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID.
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Project home page URL is not required to be set
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
- On creation of project, use the `Project ID` and set it in `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <Project_ID>
VITE_WALLET_CONNECT_ID = <Project_ID>
```
- The React application will be running in `http://localhost:3000/`
+12 -6
View File
@@ -1,7 +1,7 @@
#!/bin/bash
PKG_DIR="./packages/frontend"
OUTPUT_DIR="${PKG_DIR}/build"
OUTPUT_DIR="${PKG_DIR}/dist"
DEST_DIR=${1:-/data}
if [[ -d "$DEST_DIR" ]]; then
@@ -10,11 +10,17 @@ if [[ -d "$DEST_DIR" ]]; then
fi
cat > $PKG_DIR/.env <<EOF
REACT_APP_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_server_url'
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid'
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo'
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo'
REACT_APP_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_app_wallet_connect_id'
VITE_SERVER_URL = 'LACONIC_HOSTED_CONFIG_server_url'
VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
VITE_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_pwa_templaterepo'
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo'
VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
VITE_ALCHEMY_API_KEY = 'LACONIC_HOSTED_CONFIG_aplchemy_api_key'
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
VITE_TURNKEY_API_BASE_URL = 'LACONIC_HOSTED_CONFIG_turnkey_api_base_url'
VITE_TURNKEY_ORGANIZATION_ID = 'LACONIC_HOSTED_CONFIG_turnkey_organization_id'
EOF
yarn || exit 1
+4 -4
View File
@@ -4,15 +4,15 @@
"workspaces": [
"packages/*"
],
"dependencies": {},
"devDependencies": {
"depcheck": "^1.4.2",
"husky": "^8.0.3",
"lerna": "^8.0.0",
"depcheck": "^1.4.2"
"patch-package": "^8.0.0"
},
"scripts": {
"prepare": "husky install",
"build": "lerna run build --stream",
"lint": "lerna run lint --stream -- --max-warnings=0"
"lint": "lerna run lint --stream"
}
}
}
+1
View File
@@ -0,0 +1 @@
v20.12.1
+9 -13
View File
@@ -1,20 +1,25 @@
{
"name": "backend",
"license": "UNLICENSED",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@cerc-io/laconic-sdk": "^0.1.16",
"@graphql-tools/schema": "^10.0.2",
"@graphql-tools/utils": "^10.0.12",
"@octokit/oauth-app": "^6.1.0",
"@snowballtools/laconic-sdk": "^0.1.17",
"@turnkey/sdk-server": "^0.1.0",
"@types/debug": "^4.1.5",
"@types/express": "^4.17.21",
"@types/node": "^20.11.0",
"@types/semver": "^7.5.8",
"apollo-server-core": "^3.13.0",
"apollo-server-express": "^3.13.0",
"cookie-session": "^2.1.0",
"cors": "^2.8.5",
"debug": "^4.3.1",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"express-session": "^1.18.0",
"fs-extra": "^11.2.0",
"graphql": "^16.8.1",
@@ -35,30 +40,21 @@
"copy-assets": "copyfiles -u 1 src/**/*.gql dist/",
"clean": "rm -rf ./dist",
"build": "yarn clean && tsc && yarn copy-assets",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "tsc --noEmit",
"test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts",
"test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts",
"test:registry:publish-deployment-removal-records": "DEBUG=snowball:* ts-node ./test/publish-deployment-removal-records.ts",
"test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts",
"test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts"
},
"devDependencies": {
"@types/cookie-session": "^2.0.49",
"@types/express-session": "^1.17.10",
"@types/fs-extra": "^11.0.4",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"better-sqlite3": "^9.2.2",
"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",
"workspace": "^0.0.1-preview.1"
}
+6
View File
@@ -52,4 +52,10 @@ export interface Config {
gitHub: GitHubConfig;
registryConfig: RegistryConfig;
misc: MiscConfig;
turnkey: {
apiBaseUrl: string;
apiPublicKey: string;
apiPrivateKey: string;
defaultOrganizationId: string;
};
}
+13
View File
@@ -436,6 +436,19 @@ export class Database {
return Boolean(updateResult.affected);
}
async deleteDeploymentById (deploymentId: string): Promise<boolean> {
const deploymentRepository = this.dataSource.getRepository(Deployment);
const deployment = await deploymentRepository.findOneOrFail({
where: {
id: deploymentId
}
});
const deleteResult = await deploymentRepository.softRemove(deployment);
return Boolean(deleteResult);
}
async addProject (user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project);
+31 -2
View File
@@ -6,13 +6,14 @@ import {
UpdateDateColumn,
ManyToOne,
OneToOne,
JoinColumn
JoinColumn,
DeleteDateColumn
} from 'typeorm';
import { Project } from './Project';
import { Domain } from './Domain';
import { User } from './User';
import { AppDeploymentRecordAttributes } from '../types';
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
export enum Environment {
Production = 'Production',
@@ -24,6 +25,7 @@ export enum DeploymentStatus {
Building = 'Building',
Ready = 'Ready',
Error = 'Error',
Deleting = 'Deleting',
}
export interface ApplicationDeploymentRequest {
@@ -35,6 +37,18 @@ export interface ApplicationDeploymentRequest {
meta: string;
}
export interface ApplicationDeploymentRemovalRequest {
type: string;
version: string;
deployment: string;
}
export interface ApplicationDeploymentRemovalRequest {
type: string;
version: string;
deployment: string;
}
export interface ApplicationRecord {
type: string;
version: string;
@@ -98,6 +112,18 @@ export class Deployment {
@Column('simple-json', { nullable: true })
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
@Column('varchar', { nullable: true })
applicationDeploymentRemovalRequestId!: string | null;
@Column('simple-json', { nullable: true })
applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null;
@Column('varchar', { nullable: true })
applicationDeploymentRemovalRecordId!: string | null;
@Column('simple-json', { nullable: true })
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
@Column({
enum: Environment
@@ -121,4 +147,7 @@ export class Deployment {
@UpdateDateColumn()
updatedAt!: Date;
@DeleteDateColumn()
deletedAt!: Date | null;
}
+6
View File
@@ -39,6 +39,12 @@ export class User {
@CreateDateColumn()
updatedAt!: Date;
@Column()
subOrgId!: string;
@Column()
turnkeyWalletId!: string;
@OneToMany(() => ProjectMember, (projectMember) => projectMember.project, {
cascade: ['soft-remove']
})
+4 -6
View File
@@ -1,3 +1,4 @@
import 'express-async-errors';
import 'reflect-metadata';
import debug from 'debug';
import fs from 'fs';
@@ -9,8 +10,6 @@ import { Database } from './database';
import { createAndStartServer } from './server';
import { createResolvers } from './resolvers';
import { getConfig } from './utils';
import { Config } from './config';
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
import { Service } from './service';
import { Registry } from './registry';
@@ -18,13 +17,12 @@ const log = debug('snowball:server');
const OAUTH_CLIENT_TYPE = 'oauth-app';
export const main = async (): Promise<void> => {
// TODO: get config path using cli
const { server, database, gitHub, registryConfig, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
const { server, database, gitHub, registryConfig, misc } = await getConfig();
const app = new OAuthApp({
clientType: OAUTH_CLIENT_TYPE,
clientId: gitHub.oAuth.clientId,
clientSecret: gitHub.oAuth.clientSecret
clientSecret: gitHub.oAuth.clientSecret,
});
const db = new Database(database, misc);
@@ -35,7 +33,7 @@ export const main = async (): Promise<void> => {
{ gitHubConfig: gitHub, registryConfig },
db,
app,
registry
registry,
);
const typeDefs = fs
+74 -3
View File
@@ -3,22 +3,25 @@ import assert from 'assert';
import { inc as semverInc } from 'semver';
import { DateTime } from 'luxon';
import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk';
import { Registry as LaconicRegistry } from '@snowballtools/laconic-sdk';
import { RegistryConfig } from './config';
import {
ApplicationRecord,
Deployment,
ApplicationDeploymentRequest
ApplicationDeploymentRequest,
ApplicationDeploymentRemovalRequest
} from './entity/Deployment';
import { AppDeploymentRecord, PackageJSON } from './types';
import { AppDeploymentRecord, AppDeploymentRemovalRecord, PackageJSON } from './types';
import { sleep } from './utils';
const log = debug('snowball:registry');
const APP_RECORD_TYPE = 'ApplicationRecord';
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
const SLEEP_DURATION = 1000;
// TODO: Move registry code to laconic-sdk/watcher-ts
@@ -229,6 +232,74 @@ export class Registry {
);
}
/**
* Fetch ApplicationDeploymentRecords by filter
*/
async getDeploymentRecordsByFilter (filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
return this.registry.queryRecords(
{
type: APP_DEPLOYMENT_RECORD_TYPE,
...filter
},
true
);
}
/**
* Fetch ApplicationDeploymentRemovalRecords for deployments
*/
async getDeploymentRemovalRecords (
deployments: Deployment[]
): Promise<AppDeploymentRemovalRecord[]> {
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
const records = await this.registry.queryRecords(
{
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE
},
true
);
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
return records.filter((record: AppDeploymentRemovalRecord) =>
deployments.some(
(deployment) =>
deployment.applicationDeploymentRemovalRequestId === record.attributes.request &&
deployment.applicationDeploymentRecordId === record.attributes.deployment
)
);
}
async createApplicationDeploymentRemovalRequest (data: {
deploymentId: string;
}): Promise<{
applicationDeploymentRemovalRequestId: string;
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
}> {
const applicationDeploymentRemovalRequest = {
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
version: '1.0.0',
deployment: data.deploymentId
};
const result = await this.registry.setRecord(
{
privateKey: this.registryConfig.privateKey,
record: applicationDeploymentRemovalRequest,
bondId: this.registryConfig.bondId
},
'',
this.registryConfig.fee
);
log(`Application deployment removal request record published: ${result.data.id}`);
log('Application deployment removal request data:', applicationDeploymentRemovalRequest);
return {
applicationDeploymentRemovalRequestId: result.data.id,
applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest
};
}
getCrn (appName: string): string {
assert(this.registryConfig.authority, "Authority doesn't exist");
return `crn://${this.registryConfig.authority}/applications/${appName}`;
+68 -36
View File
@@ -17,7 +17,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
return context.user;
},
organizations: async (_:any, __: any, context: any) => {
organizations: async (_: any, __: any, context: any) => {
return service.getOrganizationsByUserId(context.user);
},
@@ -25,8 +25,15 @@ export const createResolvers = async (service: Service): Promise<any> => {
return service.getProjectById(projectId);
},
projectsInOrganization: async (_: any, { organizationSlug }: {organizationSlug: string }, context: any) => {
return service.getProjectsInOrganization(context.user, organizationSlug);
projectsInOrganization: async (
_: any,
{ organizationSlug }: { organizationSlug: string },
context: any,
) => {
return service.getProjectsInOrganization(
context.user,
organizationSlug,
);
},
deployments: async (_: any, { projectId }: { projectId: string }) => {
@@ -35,7 +42,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
environmentVariables: async (
_: any,
{ projectId }: { projectId: string }
{ projectId }: { projectId: string },
) => {
return service.getEnvironmentVariablesByProjectId(projectId);
},
@@ -44,7 +51,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
return service.getProjectMembersByProjectId(projectId);
},
searchProjects: async (_: any, { searchText }: { searchText: string }, context: any) => {
searchProjects: async (
_: any,
{ searchText }: { searchText: string },
context: any,
) => {
return service.searchProjects(context.user, searchText);
},
@@ -52,11 +63,11 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
projectId,
filter
}: { projectId: string; filter?: FindOptionsWhere<Domain> }
filter,
}: { projectId: string; filter?: FindOptionsWhere<Domain> },
) => {
return service.getDomainsByProjectId(projectId, filter);
}
},
},
// TODO: Return error in GQL response
@@ -64,10 +75,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
removeProjectMember: async (
_: any,
{ projectMemberId }: { projectMemberId: string },
context: any
context: any,
) => {
try {
return await service.removeProjectMember(context.user, projectMemberId);
return await service.removeProjectMember(
context.user,
projectMemberId,
);
} catch (err) {
log(err);
return false;
@@ -78,13 +92,13 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
projectMemberId,
data
data,
}: {
projectMemberId: string;
data: {
permissions: Permission[];
};
}
},
) => {
try {
return await service.updateProjectMember(projectMemberId, data);
@@ -98,14 +112,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
projectId,
data
data,
}: {
projectId: string;
data: {
email: string;
permissions: Permission[];
};
}
},
) => {
try {
return Boolean(await service.addProjectMember(projectId, data));
@@ -119,15 +133,15 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
projectId,
data
data,
}: {
projectId: string;
data: { environments: string[]; key: string; value: string }[];
}
},
) => {
try {
return Boolean(
await service.addEnvironmentVariables(projectId, data)
await service.addEnvironmentVariables(projectId, data),
);
} catch (err) {
log(err);
@@ -139,16 +153,16 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
environmentVariableId,
data
data,
}: {
environmentVariableId: string;
data: DeepPartial<EnvironmentVariable>;
}
},
) => {
try {
return await service.updateEnvironmentVariable(
environmentVariableId,
data
data,
);
} catch (err) {
log(err);
@@ -158,7 +172,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
removeEnvironmentVariable: async (
_: any,
{ environmentVariableId }: { environmentVariableId: string }
{ environmentVariableId }: { environmentVariableId: string },
) => {
try {
return await service.removeEnvironmentVariable(environmentVariableId);
@@ -171,10 +185,12 @@ export const createResolvers = async (service: Service): Promise<any> => {
updateDeploymentToProd: async (
_: any,
{ deploymentId }: { deploymentId: string },
context: any
context: any,
) => {
try {
return Boolean(await service.updateDeploymentToProd(context.user, deploymentId));
return Boolean(
await service.updateDeploymentToProd(context.user, deploymentId),
);
} catch (err) {
log(err);
return false;
@@ -185,9 +201,9 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
organizationSlug,
data
data,
}: { organizationSlug: string; data: DeepPartial<Project> },
context: any
context: any,
) => {
try {
return await service.addProject(context.user, organizationSlug, data);
@@ -199,7 +215,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
updateProject: async (
_: any,
{ projectId, data }: { projectId: string; data: DeepPartial<Project> }
{ projectId, data }: { projectId: string; data: DeepPartial<Project> },
) => {
try {
return await service.updateProject(projectId, data);
@@ -212,10 +228,12 @@ export const createResolvers = async (service: Service): Promise<any> => {
redeployToProd: async (
_: any,
{ deploymentId }: { deploymentId: string },
context: any
context: any,
) => {
try {
return Boolean(await service.redeployToProd(context.user, deploymentId));
return Boolean(
await service.redeployToProd(context.user, deploymentId),
);
} catch (err) {
log(err);
return false;
@@ -244,8 +262,8 @@ export const createResolvers = async (service: Service): Promise<any> => {
_: any,
{
projectId,
deploymentId
}: { deploymentId: string; projectId: string }
deploymentId,
}: { deploymentId: string; projectId: string },
) => {
try {
return await service.rollbackDeployment(projectId, deploymentId);
@@ -255,9 +273,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 (
_: any,
{ projectId, data }: { projectId: string; data: { name: string } }
{ projectId, data }: { projectId: string; data: { name: string } },
) => {
try {
return Boolean(await service.addDomain(projectId, data));
@@ -269,7 +299,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
updateDomain: async (
_: any,
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> }
{ domainId, data }: { domainId: string; data: DeepPartial<Domain> },
) => {
try {
return await service.updateDomain(domainId, data);
@@ -282,7 +312,7 @@ export const createResolvers = async (service: Service): Promise<any> => {
authenticateGitHub: async (
_: any,
{ code }: { code: string },
context: any
context: any,
) => {
try {
return await service.authenticateGitHub(code, context.user);
@@ -294,12 +324,14 @@ export const createResolvers = async (service: Service): Promise<any> => {
unauthenticateGitHub: async (_: any, __: object, context: any) => {
try {
return service.unauthenticateGitHub(context.user, { gitHubToken: null });
return service.unauthenticateGitHub(context.user, {
gitHubToken: null,
});
} catch (err) {
log(err);
return false;
}
}
}
},
},
};
};
+81 -16
View File
@@ -1,41 +1,106 @@
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();
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.userId = 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.userId = user.id;
res.sendStatus(200);
} else {
res.sendStatus(401);
}
});
//
// Lit
//
router.post('/validate', async (req, res) => {
const { message, signature } = req.body;
const { message, signature, action } = req.body;
const { success, data } = await new SiweMessage(message).verify({
signature
signature,
});
if (success) {
req.session.address = data.address;
req.session.chainId = data.chainId;
if (!success) {
return res.send({ success });
}
const service: Service = req.app.get('service');
const user = await service.getUserByEthAddress(data.address);
if (action === 'signup') {
if (user) {
return res.send({ success: false, error: 'user_already_exists' });
}
const newUser = await service.createUser({
ethAddress: data.address,
email: '',
name: '',
subOrgId: '',
turnkeyWalletId: '',
});
req.session.userId = newUser.id;
} else if (action === 'login') {
if (!user) {
return res.send({ success: false, error: 'user_not_found' });
}
req.session.userId = user.id;
}
res.send({ success });
});
//
// General
//
router.get('/session', (req, res) => {
if (req.session.address && req.session.chainId) {
res.send({ address: req.session.address, chainId: req.session.chainId });
if (req.session.userId) {
res.send({
userId: req.session.userId,
});
} else {
res.status(401).send({ error: 'Unauthorized: No active session' });
}
});
router.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.send({ success: false });
}
res.send({ success: true });
});
// This is how you clear cookie-session
(req as any).session = null;
res.send({ success: true });
});
export default router;
+9
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.7' });
});
export default router;
+2
View File
@@ -19,6 +19,7 @@ enum DeploymentStatus {
Building
Ready
Error
Deleting
}
enum DomainStatus {
@@ -209,6 +210,7 @@ type Mutation {
deleteProject(projectId: String!): Boolean!
deleteDomain(domainId: String!): Boolean!
rollbackDeployment(projectId: String!, deploymentId: String!): Boolean!
deleteDeployment(deploymentId: String!): Boolean!
addDomain(projectId: String!, data: AddDomainInput!): Boolean!
updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean!
authenticateGitHub(code: String!): AuthResult!
+33 -35
View File
@@ -6,9 +6,9 @@ import { createServer } from 'http';
import {
ApolloServerPluginDrainHttpServer,
ApolloServerPluginLandingPageLocalDefault,
AuthenticationError
AuthenticationError,
} from 'apollo-server-core';
import session from 'express-session';
import cookieSession from 'cookie-session';
import { TypeSource } from '@graphql-tools/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
@@ -17,14 +17,14 @@ import { ServerConfig } from './config';
import { DEFAULT_GQL_PATH } from './constants';
import githubRouter from './routes/github';
import authRouter from './routes/auth';
import stagingRouter from './routes/staging';
import { Service } from './service';
const log = debug('snowball:server');
declare module 'express-session' {
interface SessionData {
address: string;
chainId: number;
userId: string;
}
}
@@ -32,7 +32,7 @@ export const createAndStartServer = async (
serverConfig: ServerConfig,
typeDefs: TypeSource,
resolvers: any,
service: Service
service: Service,
): Promise<ApolloServer> => {
const { host, port, gqlPath = DEFAULT_GQL_PATH } = serverConfig;
const { appOriginUrl, secret, domain, trustProxy } = serverConfig.session;
@@ -45,7 +45,7 @@ export const createAndStartServer = async (
// Create the schema
const schema = makeExecutableSchema({
typeDefs,
resolvers
resolvers,
});
const server = new ApolloServer({
@@ -54,46 +54,31 @@ export const createAndStartServer = async (
context: async ({ req }) => {
// https://www.apollographql.com/docs/apollo-server/v3/security/authentication#api-wide-authorization
const { address } = req.session;
const { userId } = req.session;
if (!address) {
if (!userId) {
throw new AuthenticationError('Unauthorized: No active session');
}
// Find/create user from ETH address in request session
const user = await service.loadOrCreateUser(address);
const user = await service.getUser(userId);
return { user };
},
plugins: [
// Proper shutdown for the HTTP server
ApolloServerPluginDrainHttpServer({ httpServer }),
ApolloServerPluginLandingPageLocalDefault({ embed: true })
]
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
],
});
await server.start();
app.use(cors({
origin: appOriginUrl,
credentials: true
}));
const sessionOptions: session.SessionOptions = {
secret: secret,
resave: false,
saveUninitialized: true,
cookie: {
secure: new URL(appOriginUrl).protocol === 'https:',
// TODO: Set cookie maxAge and handle cookie expiry in frontend
// maxAge: SESSION_COOKIE_MAX_AGE,
sameSite: new URL(appOriginUrl).protocol === 'https:' ? 'none' : 'lax'
}
};
if (domain) {
sessionOptions.cookie!.domain = domain;
}
app.use(
cors({
origin: appOriginUrl,
credentials: true,
}),
);
if (trustProxy) {
// trust first proxy
@@ -101,7 +86,14 @@ export const createAndStartServer = async (
}
app.use(
session(sessionOptions)
cookieSession({
secret: secret,
secure: new URL(appOriginUrl).protocol === 'https:',
// 23 hours (less than 24 hours to avoid sessionSigs expiration issues)
maxAge: 23 * 60 * 60 * 1000,
sameSite: new URL(appOriginUrl).protocol === 'https:' ? 'none' : 'lax',
domain: domain || undefined,
}),
);
server.applyMiddleware({
@@ -109,8 +101,8 @@ export const createAndStartServer = async (
path: gqlPath,
cors: {
origin: [appOriginUrl],
credentials: true
}
credentials: true,
},
});
app.use(express.json());
@@ -118,6 +110,12 @@ export const createAndStartServer = async (
app.set('service', service);
app.use('/auth', authRouter);
app.use('/api/github', githubRouter);
app.use('/staging', stagingRouter);
app.use((err: any, req: any, res: any, next: any) => {
console.error(err);
res.status(500).json({ error: err.message });
});
httpServer.listen(port, host, () => {
log(`Server is listening on ${host}:${port}${server.graphqlPath}`);
File diff suppressed because it is too large Load Diff
+130
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;
}
+11
View File
@@ -38,6 +38,13 @@ export interface AppDeploymentRecordAttributes {
version: string;
}
export interface AppDeploymentRemovalRecordAttributes {
deployment: string;
request: string;
type: "ApplicationDeploymentRemovalRecord";
version: string;
}
interface RegistryRecord {
id: string;
names: string[] | null;
@@ -50,3 +57,7 @@ interface RegistryRecord {
export interface AppDeploymentRecord extends RegistryRecord {
attributes: AppDeploymentRecordAttributes;
}
export interface AppDeploymentRemovalRecord extends RegistryRecord {
attributes: AppDeploymentRemovalRecordAttributes;
}
+13 -5
View File
@@ -3,11 +3,18 @@ import path from 'path';
import toml from 'toml';
import debug from 'debug';
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
import { Config } from './config';
import { DEFAULT_CONFIG_FILE_PATH } from './constants';
const log = debug('snowball:utils');
export const getConfig = async <ConfigType>(
configFile: string
export async function getConfig() {
// TODO: get config path using cli
return await _getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
}
const _getConfig = async <ConfigType>(
configFile: string,
): Promise<ConfigType> => {
const configFilePath = path.resolve(configFile);
const fileExists = await fs.pathExists(configFilePath);
@@ -41,7 +48,7 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
entityType: EntityTarget<Entity>,
dataSource: DataSource,
entities: any,
relations?: any | undefined
relations?: any | undefined,
): Promise<Entity[]> => {
const entityRepository = dataSource.getRepository(entityType);
@@ -56,7 +63,7 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
entity = {
...entity,
[field]: relations[field][entityData[valueIndex]]
[field]: relations[field][entityData[valueIndex]],
};
}
}
@@ -67,4 +74,5 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
return savedEntity;
};
export const sleep = async (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
export const sleep = async (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));
+1 -1
View File
@@ -1,6 +1,6 @@
import debug from 'debug';
import { Registry } from '@cerc-io/laconic-sdk';
import { Registry } from '@snowballtools/laconic-sdk';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { Config } from '../src/config';
@@ -2,12 +2,12 @@ import debug from 'debug';
import { DataSource } from 'typeorm';
import path from 'path';
import { Registry } from '@cerc-io/laconic-sdk';
import { Registry } from '@snowballtools/laconic-sdk';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { getConfig } from '../src/utils';
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
import { Deployment, DeploymentStatus, Environment } from '../src/entity/Deployment';
const log = debug('snowball:publish-deploy-records');
@@ -40,7 +40,7 @@ async function main () {
});
for await (const deployment of deployments) {
const url = `${deployment.project.name}-${deployment.id}.${misc.projectDomain}`;
const url = `https://${deployment.project.name}-${deployment.id}.${misc.projectDomain}`;
const applicationDeploymentRecord = {
type: 'ApplicationDeploymentRecord',
@@ -71,6 +71,21 @@ async function main () {
registryConfig.fee
);
// Remove deployment for project subdomain if deployment is for production environment
if (deployment.environment === Environment.Production) {
applicationDeploymentRecord.url = `https://${deployment.project.subDomain}`
await registry.setRecord(
{
privateKey: registryConfig.privateKey,
record: applicationDeploymentRecord,
bondId: registryConfig.bondId
},
'',
registryConfig.fee
);
}
log('Application deployment record data:', applicationDeploymentRecord);
log(`Application deployment record published: ${result.data.id}`);
}
@@ -0,0 +1,67 @@
import debug from 'debug';
import { DataSource } from 'typeorm';
import path from 'path';
import { Registry } from '@cerc-io/laconic-sdk';
import { Config } from '../src/config';
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
import { getConfig } from '../src/utils';
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
const log = debug('snowball:publish-deployment-removal-records');
async function main () {
const { registryConfig, database, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
const registry = new Registry(
registryConfig.gqlEndpoint,
registryConfig.restEndpoint,
registryConfig.chainId
);
const dataSource = new DataSource({
type: 'better-sqlite3',
database: database.dbPath,
synchronize: true,
entities: [path.join(__dirname, '../src/entity/*')]
});
await dataSource.initialize();
const deploymentRepository = dataSource.getRepository(Deployment);
const deployments = await deploymentRepository.find({
relations: {
project: true
},
where: {
status: DeploymentStatus.Deleting
}
});
for await (const deployment of deployments) {
const applicationDeploymentRemovalRecord = {
type: "ApplicationDeploymentRemovalRecord",
version: "1.0.0",
deployment: deployment.applicationDeploymentRecordId,
request: deployment.applicationDeploymentRemovalRequestId,
}
const result = await registry.setRecord(
{
privateKey: registryConfig.privateKey,
record: applicationDeploymentRemovalRecord,
bondId: registryConfig.bondId
},
'',
registryConfig.fee
);
log('Application deployment removal record data:', applicationDeploymentRemovalRecord);
log(`Application deployment removal record published: ${result.data.id}`);
}
}
main().catch((err) => {
log(err);
});
+12 -3
View File
@@ -9,9 +9,18 @@
```
- Run script to deploy app
```
./deploy-frontend.sh
```
- To deploy frontend app to `dashboard.staging.apps.snowballtools.com`
```
./deploy-frontend.staging.sh
```
- To deploy frontend app to `dashboard.apps.snowballtools.com`
```
./deploy-frontend.sh
```
- Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository
+9
View File
@@ -0,0 +1,9 @@
services:
cns:
restEndpoint: 'http://console.laconic.com:1317'
gqlEndpoint: 'http://console.laconic.com:9473/api'
userKey: 87d00f66a73e2ca428adeb49ba9164d0ad9a87edc60e33d46ad3031b9c5701fe
bondId: 89c75c7bc5759861d10285aff6f9e7227d6855e446b77ad5d8324822dfec7deb
chainId: laconic_9000-1
gas: 1200000
fees: 200000aphoton
+42 -10
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
@@ -35,12 +35,12 @@ record:
dns: dashboard
config:
env:
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
@@ -64,7 +64,13 @@ echo "Files generated successfully."
RECORD_FILE=records/application-record.yml
# Publish ApplicationRecord
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
publish_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to publish record"
exit $rc
fi
RECORD_ID=$(echo $publish_response | jq -r '.id')
echo "ApplicationRecord published"
echo $RECORD_ID
@@ -73,15 +79,36 @@ REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend"
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set name: $REGISTRY_APP_CRN@${PACKAGE_VERSION}"
exit $rc
fi
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set hash"
exit $rc
fi
sleep 2
# Set name if latest release
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set release"
exit $rc
fi
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
# Check if record found for REGISTRY_APP_CRN
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
query_response=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN")
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to query name"
exit $rc
fi
APP_RECORD=$(echo $query_response | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_CRN."
exit 1
@@ -90,6 +117,11 @@ fi
RECORD_FILE=records/application-deployment-request.yml
sleep 2
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to query deployment request"
exit $rc
fi
DEPLOYMENT_REQUEST_ID=$(echo $deployment_response | jq -r '.id')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# Repository URL
REPO_URL="https://github.com/snowball-tools/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# Extract version from ../frontend/package.json
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
CONFIG_FILE=config.staging.yml
REGISTRY_BOND_ID="098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661"
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
# Get latest version from registry and increment application-record version
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationRecord --all --name "staging-snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
# Set application-record version if no previous records were found
NEW_APPLICATION_VERSION=0.0.1
fi
# Generate application-deployment-request.yml
cat > ./staging-records/application-deployment-request.yml <<EOF
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: staging-snowballtools-base-frontend@$PACKAGE_VERSION
application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@$PACKAGE_VERSION
dns: dashboard.staging.apps.snowballtools.com
config:
env:
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_github_clientid: 905c09553f527d2cdff5
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: dashboard.staging.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
repository_ref: $LATEST_HASH
EOF
# Generate application-record.yml with incremented version
cat > ./staging-records/application-record.yml <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: staging-snowballtools-base-frontend
app_version: $PACKAGE_VERSION
EOF
echo "Files generated successfully."
RECORD_FILE=staging-records/application-record.yml
# Publish ApplicationRecord
publish_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to publish record"
exit $rc
fi
RECORD_ID=$(echo $publish_response | jq -r '.id')
echo "ApplicationRecord published"
echo $RECORD_ID
# Set name to record
REGISTRY_APP_CRN="crn://staging-snowballtools/applications/staging-snowballtools-base-frontend"
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set name: $REGISTRY_APP_CRN@${PACKAGE_VERSION}"
exit $rc
fi
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set hash"
exit $rc
fi
sleep 2
# Set name if latest release
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to set release"
exit $rc
fi
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
# Check if record found for REGISTRY_APP_CRN
query_response=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN")
rc=$?
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to query name"
exit $rc
fi
APP_RECORD=$(echo $query_response | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_CRN."
exit 1
fi
RECORD_FILE=staging-records/application-deployment-request.yml
sleep 2
deployment_response=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE)
if [ $rc -ne 0 ]; then
echo "FATAL: Failed to query deployment request"
exit $rc
fi
DEPLOYMENT_REQUEST_ID=$(echo $deployment_response | jq -r '.id')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID
+1 -1
View File
@@ -4,6 +4,6 @@
"main": "index.js",
"private": true,
"devDependencies": {
"@cerc-io/laconic-registry-cli": "^0.1.10"
"@snowballtools/laconic-registry-cli": "^0.1.13"
}
}
@@ -14,5 +14,5 @@ record:
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository: "https://github.com/snowball-tools/snowballtools-base"
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
@@ -2,7 +2,7 @@ record:
type: ApplicationRecord
version: 0.0.2
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
repository: ["https://github.com/snowball-tools/snowballtools-base"]
app_type: webapp
name: snowballtools-base-frontend
app_version: 0.1.8
@@ -0,0 +1,24 @@
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: staging-snowballtools-base-frontend@0.0.0
application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@0.0.0
dns: dashboard.staging.apps.snowballtools.com
config:
env:
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
LACONIC_HOSTED_CONFIG_github_clientid: 905c09553f527d2cdff5
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: localhost
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
meta:
note: Added by Snowball @ Tuesday 21 May 2024 06:17:23 AM UTC
repository: "https://github.com/snowball-tools/snowballtools-base"
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
@@ -0,0 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.2
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
repository: ["https://github.com/snowball-tools/snowballtools-base"]
app_type: webapp
name: staging-snowballtools-base-frontend
app_version: 0.0.0
+23
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-platform/test-progressive-web-app (main branch)
- Config used: [../config.yml](../config.yml)
- The script [test-webapp-deployment-undeployment.sh](./test-webapp-deployment-undeployment.sh) performs the following:
- Create / update [`ApplicationRecord`](./records/application-record.yml) and [`ApplicationDeploymentRequest`](./records/application-deployment-request.yml) records with latest meta data from the repo
- 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
@@ -0,0 +1,4 @@
record:
deployment: <APPLICATION_DEPLOYMENT_RECORD_ID>
type: ApplicationDeploymentRemovalRequest
version: 1.0.0
@@ -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-platform/test-progressive-web-app"
repository_ref: 05819619487a0d2dbc5453b6d1ccff3044c0dd26
@@ -0,0 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.1
repository_ref: 05819619487a0d2dbc5453b6d1ccff3044c0dd26
repository: ["https://github.com/snowball-tools-platform/test-progressive-web-app"]
app_type: webapp
name: deployment-test-app
app_version: 0.1.24
@@ -0,0 +1,225 @@
#!/bin/bash
# Repository URL
REPO_URL="https://github.com/snowball-tools-platform/test-progressive-web-app"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# 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 cns record list --type ApplicationRecord --all --name "$APP_NAME" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
# Set application-record version if no previous records were found
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: crn://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 cns record publish --filename $RECORD_FILE | jq -r '.id')
echo "ApplicationRecord published"
echo $RECORD_ID
# Set name to record
REGISTRY_APP_CRN="crn://snowballtools/applications/$APP_NAME"
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
sleep 2
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
sleep 2
# Set name if latest release
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
# Check if record exists for REGISTRY_APP_CRN
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_CRN."
exit 1
fi
sleep 2
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $REQUEST_RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID
# 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 cns record list --type ApplicationDeploymentRecord --all --name "$APP_NAME" request $DEPLOYMENT_REQUEST_ID)
len_deployment_records=$(echo $deployment_records_response | jq 'length')
# Check if number of records returned is 0
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=5
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 cns record publish --filename $REMOVAL_REQUEST_RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRemovalRequest published"
echo $REMOVAL_REQUEST_ID
# Check that an ApplicationDeploymentRemovalRecord is published
retry_count=0
while true; do
removal_records_response=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationDeploymentRemovalRecord --all request $REMOVAL_REQUEST_ID)
len_removal_records=$(echo $removal_records_response | jq 'length')
# Check if number of records returned is 0
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"
+15 -5
View File
@@ -1,7 +1,17 @@
REACT_APP_SERVER_URL = 'http://localhost:8000'
VITE_SERVER_URL='http://localhost:8000'
REACT_APP_GITHUB_CLIENT_ID =
REACT_APP_GITHUB_PWA_TEMPLATE_REPO =
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO =
VITE_GITHUB_CLIENT_ID=
VITE_GITHUB_PWA_TEMPLATE_REPO="snowball-tools/test-progressive-web-app"
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO="snowball-tools/image-upload-pwa-example"
REACT_APP_WALLET_CONNECT_ID =
VITE_WALLET_CONNECT_ID=
VITE_LIT_RELAY_API_KEY=
VITE_ALCHEMY_API_KEY=
VITE_BUGSNAG_API_KEY=
VITE_PASSKEY_WALLET_RPID=
VITE_TURNKEY_API_BASE_URL=
VITE_TURNKEY_ORGANIZATION_ID=
-1
View File
@@ -1 +0,0 @@
build
+19
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 },
],
},
};
-25
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"
}
}
}
+1
View File
@@ -22,3 +22,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*storybook.log
+1
View File
@@ -0,0 +1 @@
v20.12.1
+1 -3
View File
@@ -1,3 +1 @@
# artifacts
build
coverage
dist/
+31
View File
@@ -0,0 +1,31 @@
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',
},
};
export default config;
+16
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;
+22 -38
View File
@@ -1,46 +1,30 @@
# Getting Started with Create React App
# React + TypeScript + Vite
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 template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
## Available Scripts
Currently, two official plugins are available:
In the project directory, you can run:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
### `yarn start`
## Expanding the ESLint configuration
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
The page will reload if you make edits.\
You will also see any lint errors in the console.
- Configure the top-level `parserOptions` property like this:
### `yarn test`
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
};
```
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
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.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
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.
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.
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.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
+4
View File
@@ -0,0 +1,4 @@
{
"projectId": "Project:663d04870db27ed66a48e466",
"zip": true
}
+22
View File
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="snowball tools dashboard" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<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>Snowball</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
-7
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

+52 -46
View File
@@ -1,11 +1,23 @@
{
"name": "frontend",
"version": "0.1.8",
"private": true,
"version": "0.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": {
"@bugsnag/browser-performance": "^2.4.1",
"@bugsnag/js": "^7.22.7",
"@bugsnag/plugin-react": "^7.22.7",
"@fontsource-variable/jetbrains-mono": "^5.0.19",
"@fontsource/inter": "^5.0.16",
"@material-tailwind/react": "^2.1.7",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
@@ -15,14 +27,22 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@snowballtools/auth": "^0.2.0",
"@snowballtools/auth-lit": "^0.2.0",
"@snowballtools/js-sdk": "^0.1.1",
"@snowballtools/link-lit-alchemy-light": "^0.2.0",
"@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",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.68",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"@turnkey/http": "^2.10.0",
"@turnkey/sdk-react": "^0.1.0",
"@turnkey/webauthn-stamper": "^0.5.0",
"@walletconnect/ethereum-provider": "^2.12.2",
"@web3modal/siwe": "^4.0.5",
"@web3modal/wagmi": "^4.0.5",
"assert": "^2.1.0",
@@ -30,7 +50,6 @@
"clsx": "^2.1.0",
"date-fns": "^3.3.1",
"downshift": "^8.3.2",
"eslint-config-react-app": "^7.0.1",
"framer-motion": "^11.0.8",
"gql-client": "^1.0.0",
"lottie-react": "^2.4.0",
@@ -46,54 +65,41 @@
"react-hot-toast": "^2.4.1",
"react-oauth-popup": "^1.0.5",
"react-router-dom": "^6.20.1",
"react-scripts": "5.0.1",
"react-timer-hook": "^3.0.7",
"siwe": "^2.1.4",
"tailwind-variants": "^0.2.0",
"typescript": "^4.9.5",
"usehooks-ts": "^2.15.1",
"vertical-stepper-nav": "^1.0.2",
"uuid": "^9.0.1",
"viem": "^2.7.11",
"wagmi": "^2.5.7",
"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": {
"@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",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.33.2",
"@types/node": "^16.18.68",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@types/uuid": "^9.0.8",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"chromatic": "^11.3.2",
"eslint-plugin-storybook": "^0.8.0",
"postcss": "^8.4.38",
"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"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
@@ -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

File diff suppressed because one or more lines are too long
+9
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
-1
View File
@@ -1,4 +1,3 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
+30 -5
View File
@@ -1,4 +1,3 @@
import React from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Projects from './pages/org-slug';
@@ -9,8 +8,10 @@ import {
} from './pages/org-slug/projects/routes';
import ProjectSearchLayout from './layouts/ProjectSearch';
import Index from './pages';
import Login from './pages/Login';
import { DashboardLayout } from 'pages/org-slug/layout';
import AuthPage from './pages/AuthPage';
import { DashboardLayout } from './pages/org-slug/layout';
import { useEffect } from 'react';
import Web3Provider from 'context/Web3Provider';
const router = createBrowserRouter([
{
@@ -46,12 +47,36 @@ const router = createBrowserRouter([
},
{
path: '/login',
element: <Login />,
element: <AuthPage />,
},
{
path: '/signup',
element: <AuthPage />,
},
]);
function App() {
return <RouterProvider router={router} />;
// Hacky way of checking session
// TODO: Handle redirect backs
useEffect(() => {
fetch(`${import.meta.env.VITE_SERVER_URL}/auth/session`, {
credentials: 'include',
}).then((res) => {
if (res.status !== 200) {
localStorage.clear();
const path = window.location.pathname;
if (path !== '/login' && path !== '/signup') {
window.location.pathname = '/login';
}
}
});
}, []);
return (
<Web3Provider>
<RouterProvider router={router} />
</Web3Provider>
);
}
export default App;
+2 -2
View File
@@ -3,14 +3,14 @@ export default [
id: '1',
name: 'Progressive Web App (PWA)',
icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_PWA_TEMPLATE_REPO}`,
repoFullName: `${import.meta.env.VITE_GITHUB_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
id: '2',
name: 'Image Upload PWA',
icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
repoFullName: `${import.meta.env.VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
@@ -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>
);
};
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { format } from 'date-fns';
import {
DayPicker,
@@ -12,7 +12,7 @@ import {
Popover,
PopoverContent,
PopoverHandler,
} from '@material-tailwind/react';
} from '@snowballtools/material-tailwind-react-fork';
import HorizontalLine from './HorizontalLine';
@@ -120,14 +120,11 @@ const DatePicker = ({
handler={(value) => setIsOpen(value)}
>
<PopoverHandler>
<Input
onChange={() => null}
value={inputValue}
crossOrigin={undefined}
/>
<Input onChange={() => null} value={inputValue} />
</PopoverHandler>
{/* TODO: Figure out what placeholder is for */}
<PopoverContent placeholder={''}>
{/* @ts-ignore */}
<PopoverContent>
{mode === 'single' && (
<DayPicker
mode="single"
@@ -152,7 +149,6 @@ const DatePicker = ({
className="rounded-full mr-2"
variant="outlined"
onClick={() => setIsOpen(false)}
placeholder={''}
>
Cancel
</Button>
@@ -162,7 +158,6 @@ const DatePicker = ({
className="rounded-full"
color="gray"
onClick={() => handleRangeSelect()}
placeholder={''}
>
Select
</Button>
@@ -1,4 +1,3 @@
import React from 'react';
import {
default as ReactDropdown,
Option as ReactDropdownOption,
@@ -1,5 +1,5 @@
import { Duration } from 'luxon';
import React, { ComponentPropsWithoutRef } from 'react';
import { ComponentPropsWithoutRef } from 'react';
import { cn } from 'utils/classnames';
export interface FormatMilliSecondProps
@@ -1,5 +1,3 @@
import React from 'react';
const HorizontalLine = () => {
return <hr className="h-px bg-gray-100 border-0" />;
};
@@ -1,4 +1,3 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Heading } from './shared/Heading';
@@ -6,7 +6,7 @@ import { Input, InputProps } from './shared/Input';
const SearchBar: React.ForwardRefRenderFunction<
HTMLInputElement,
InputProps & RefAttributes<HTMLInputElement>
> = ({ value, onChange, placeholder = 'Search', ...props }) => {
> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => {
return (
<div className="relative flex w-full">
<Input
@@ -18,6 +18,7 @@ const SearchBar: React.ForwardRefRenderFunction<
appearance="borderless"
className="w-full lg:w-[459px]"
{...props}
ref={ref}
/>
</div>
);
+1 -2
View File
@@ -1,5 +1,4 @@
import React from 'react';
import { StepperNav } from 'vertical-stepper-nav';
import { StepperNav } from './VerticalStepper';
const COLOR_COMPLETED = '#059669';
const COLOR_ACTIVE = '#CFE6FC';
@@ -1,4 +1,3 @@
import React from 'react';
import { useStopwatch } from 'react-timer-hook';
import FormatMillisecond, { FormatMilliSecondProps } from './FormatMilliSecond';
@@ -0,0 +1,120 @@
import * as CSS from 'csstype';
//
// Nav
//
export interface IStepDescription {
stepContent: () => JSX.Element;
stepStateColor?: string;
stepStatusCircleSize?: number;
onClickHandler?: () => void | undefined;
}
export interface IStepperNavProps {
steps: IStepDescription[];
}
export const StepperNav = (props: IStepperNavProps): JSX.Element => {
return (
<nav>
{props.steps.map(
(
{ stepContent, stepStateColor, onClickHandler, stepStatusCircleSize },
index,
) => (
<div key={index}>
<Step
stepContent={stepContent}
statusColor={stepStateColor}
onClickHandler={onClickHandler}
statusCircleSize={stepStatusCircleSize}
/>
{index !== props.steps.length - 1 && (
<div
style={{
paddingLeft: `${(stepStatusCircleSize ?? 16) / 2 + 1}px`,
}}
>
<Separator />
</div>
)}
</div>
),
)}
</nav>
);
};
//
// Separator
//
const separatorStyles = {
height: '5vh',
width: 2,
border: '1px solid #E1E1E1',
background: '#E1E1E1',
};
export interface ISeparator {
height?: string | number;
}
export const Separator = ({ height }: ISeparator): JSX.Element => {
return <div style={{ ...separatorStyles, height: height ?? '5vh' }} />;
};
//
// Step
//
export interface IStep {
stepContent: () => JSX.Element;
statusColor?: string;
statusCircleSize?: number;
onClickHandler?: (
event?: React.MouseEvent<HTMLDivElement>,
) => void | undefined;
}
const buttonContainerStyles: CSS.Properties = {
display: 'inline-flex',
flexWrap: 'wrap',
gap: '12px',
padding: '2px',
cursor: 'pointer',
};
export const Step = ({
stepContent,
statusColor,
statusCircleSize,
onClickHandler,
}: IStep): JSX.Element => {
const circleStyles = {
borderRadius: statusCircleSize ?? 16,
width: statusCircleSize ?? 16,
height: statusCircleSize ?? 16,
border: '2px solid #E1E1E1',
background: statusColor ?? 'white',
};
const keyDownHandler = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.keyCode === 13 || event.keyCode === 32) {
onClickHandler?.();
}
};
return (
<div
tabIndex={0}
onClick={onClickHandler}
onKeyDown={keyDownHandler}
role="button"
style={{ ...buttonContainerStyles }}
>
<div>
<div style={circleStyles} />
</div>
<div style={{ paddingBottom: 2 }}>{stepContent()}</div>
</div>
);
};
@@ -1,7 +1,6 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface CancelDeploymentDialogProps extends ConfirmDialogProps {}
@@ -2,7 +2,7 @@ import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import { Deployment, Domain } from 'gql-client';
import React from 'react';
import DeploymentDialogBodyCard from 'components/projects/project/deployments/DeploymentDialogBodyCard';
import { Button } from 'components/shared/Button';
import {
@@ -1,7 +1,6 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteDomainDialogProps extends ConfirmDialogProps {
projectName: string;
@@ -1,7 +1,6 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteVariableDialogProps extends ConfirmDialogProps {
variableKey: string;
@@ -1,7 +1,6 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteWebhookDialogProps extends ConfirmDialogProps {
webhookUrl: string;
@@ -1,7 +1,6 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DisconnectRepositoryDialogProps extends ConfirmDialogProps {}
@@ -1,7 +1,7 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
import { formatAddress } from 'utils/format';
interface RemoveMemberDialogProps extends ConfirmDialogProps {
@@ -1,7 +1,6 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface TransferProjectDialogProps extends ConfirmDialogProps {
projectName: string;
@@ -3,12 +3,8 @@ import {
MenuHandler,
MenuItem,
MenuList,
} from '@material-tailwind/react';
import React, {
ComponentPropsWithoutRef,
MouseEvent,
useCallback,
} from 'react';
} from '@snowballtools/material-tailwind-react-fork';
import { ComponentPropsWithoutRef, MouseEvent, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { Project } from 'gql-client';
import { Avatar } from 'components/shared/Avatar';
@@ -16,7 +12,7 @@ import { Button } from 'components/shared/Button';
import {
BranchIcon,
ClockIcon,
GitHubLogo,
GithubLogoIcon,
HorizontalDotIcon,
WarningDiamondIcon,
} from 'components/shared/CustomIcon';
@@ -57,6 +53,16 @@ export const ProjectCard = ({
navigate(`projects/${project.id}`);
}, [project.id, navigate]);
const navigateToSettingsOnClick = useCallback(
(
e: React.MouseEvent<HTMLLIElement> | React.MouseEvent<HTMLButtonElement>,
) => {
e.stopPropagation();
navigate(`projects/${project.id}/settings`);
},
[project.id, navigate],
);
return (
<div
{...props}
@@ -95,9 +101,14 @@ export const ProjectCard = ({
<HorizontalDotIcon />
</Button>
</MenuHandler>
<MenuList placeholder={''}>
<MenuItem placeholder={''}>Project settings</MenuItem>
<MenuItem className="text-red-500" placeholder={''}>
<MenuList>
<MenuItem onClick={navigateToSettingsOnClick}>
Project settings
</MenuItem>
<MenuItem
className="text-red-500"
onClick={navigateToSettingsOnClick}
>
Delete project
</MenuItem>
</MenuList>
@@ -124,7 +135,7 @@ export const ProjectCard = ({
<div className={theme.deploymentText()}>
{hasDeployment ? (
<>
<GitHubLogo />
<GithubLogoIcon />
<span>{relativeTimeMs(project.deployments[0].createdAt)} on</span>
<BranchIcon />
<span>{project.deployments[0].branch}</span>
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useCombobox } from 'downshift';
import { Project } from 'gql-client';
import { useDebounce } from 'usehooks-ts';
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { Button } from 'components/shared/Button';
import { CrossIcon, SearchIcon } from 'components/shared/CustomIcon';
@@ -96,9 +96,9 @@ export const ProjectSearchBarDialog = ({
</p>
</div>
<ProjectSearchBarItem
{...getItemProps({ item, index })}
key={item.id}
item={item}
{...getItemProps({ item, index })}
/>
</>
))
@@ -1,5 +1,5 @@
import { InfoRoundFilledIcon } from 'components/shared/CustomIcon';
import React, { ComponentPropsWithoutRef } from 'react';
import { ComponentPropsWithoutRef } from 'react';
import { cn } from 'utils/classnames';
interface ProjectSearchBarEmptyProps extends ComponentPropsWithoutRef<'div'> {}
@@ -1,7 +1,7 @@
import { Avatar } from 'components/shared/Avatar';
import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift';
import { Project } from 'gql-client';
import React, { ComponentPropsWithoutRef, forwardRef } from 'react';
import { ComponentPropsWithoutRef, forwardRef } from 'react';
import { OmitCommon } from 'types/common';
import { cn } from 'utils/classnames';
import { getInitials } from 'utils/geInitials';
@@ -2,22 +2,22 @@ import React from 'react';
import OauthPopup from 'react-oauth-popup';
import { useGQLClient } from '../../../context/GQLClientContext';
import { Button } from 'components/shared/Button';
import { Button } from '../../shared/Button';
import {
GitIcon,
EllipsesIcon,
SnowballIcon,
GithubIcon,
GitTeaIcon,
} from 'components/shared/CustomIcon';
import { useToast } from 'components/shared/Toast';
import { IconWithFrame } from 'components/shared/IconWithFrame';
import { Heading } from 'components/shared/Heading';
} from '../../shared/CustomIcon';
import { useToast } from '../../shared/Toast';
import { IconWithFrame } from '../../shared/IconWithFrame';
import { Heading } from '../../shared/Heading';
import { MockConnectGitCard } from './MockConnectGitCard';
const SCOPES = 'repo user';
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
process.env.REACT_APP_GITHUB_CLIENT_ID
import.meta.env.VITE_GITHUB_CLIENT_ID
}&scope=${encodeURIComponent(SCOPES)}`;
interface ConnectAccountInterface {
@@ -2,11 +2,11 @@ import React, { useCallback, useEffect } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { DeployStep, DeployStatus } from './DeployStep';
import { Stopwatch, setStopWatchOffset } from 'components/StopWatch';
import { Heading } from 'components/shared/Heading';
import { Button } from 'components/shared/Button';
import { ClockOutlineIcon, WarningIcon } from 'components/shared/CustomIcon';
import { CancelDeploymentDialog } from 'components/projects/Dialog/CancelDeploymentDialog';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import { Heading } from '../../shared/Heading';
import { Button } from '../../shared/Button';
import { ClockOutlineIcon, WarningIcon } from '../../shared/CustomIcon';
import { CancelDeploymentDialog } from '../../projects/Dialog/CancelDeploymentDialog';
const TIMEOUT_DURATION = 5000;
const Deploy = () => {
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { Collapse } from '@material-tailwind/react';
import { Collapse } from '@snowballtools/material-tailwind-react-fork';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import FormatMillisecond from '../../FormatMilliSecond';
@@ -74,12 +74,12 @@ export const MockConnectGitCard = () => {
return (
<div className="flex flex-col gap-2 relative z-0">
{IMPORT_CONTENT.map((repo, index) => (
<>
<MockProjectCard key={index} {...repo} />
<React.Fragment key={index}>
<MockProjectCard {...repo} />
{index !== IMPORT_CONTENT.length - 1 && (
<div className="border-b border-base-border" />
)}
</>
</React.Fragment>
))}
</div>
);
@@ -1,10 +1,10 @@
import React, { useCallback, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Spinner } from '@material-tailwind/react';
import { Spinner } from '@snowballtools/material-tailwind-react-fork';
import { relativeTimeISO } from 'utils/time';
import { GitRepositoryDetails } from 'types';
import { GitRepositoryDetails } from '../../../../types/types';
import { useGQLClient } from 'context/GQLClientContext';
import {
ArrowRightCircleIcon,
@@ -1,10 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Octokit } from 'octokit';
import { useCallback, useEffect, useMemo, useState } from 'react';
import assert from 'assert';
import { useDebounce } from 'usehooks-ts';
import { ProjectRepoCard } from 'components/projects/create/ProjectRepoCard';
import { GitOrgDetails, GitRepositoryDetails } from 'types';
import { GitOrgDetails, GitRepositoryDetails } from '../../../../types/types';
import {
ChevronGrabberHorizontal,
GithubIcon,
@@ -14,20 +13,18 @@ import {
import { Select, SelectOption } from 'components/shared/Select';
import { Input } from 'components/shared/Input';
import { Button } from 'components/shared/Button';
import { useOctokit } from 'context/OctokitContext';
const DEFAULT_SEARCHED_REPO = '';
const REPOS_PER_PAGE = 5;
interface RepositoryListProps {
octokit: Octokit;
}
export const RepositoryList = ({ octokit }: RepositoryListProps) => {
export const RepositoryList = () => {
const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO);
const [selectedAccount, setSelectedAccount] = useState<SelectOption>();
const [orgs, setOrgs] = useState<GitOrgDetails[]>([]);
// TODO: Add new type for Git user when required
const [gitUser, setGitUser] = useState<GitOrgDetails>();
const { octokit, isAuth } = useOctokit();
const [repositoryDetails, setRepositoryDetails] = useState<
GitRepositoryDetails[]
@@ -35,15 +32,23 @@ export const RepositoryList = ({ octokit }: RepositoryListProps) => {
useEffect(() => {
const fetchUserAndOrgs = async () => {
const user = await octokit.rest.users.getAuthenticated();
const orgs = await octokit.rest.orgs.listForAuthenticatedUser();
setOrgs(orgs.data);
setGitUser(user.data);
setSelectedAccount({ label: user.data.login, value: user.data.login });
try {
const user = await octokit.rest.users.getAuthenticated();
const orgs = await octokit.rest.orgs.listForAuthenticatedUser();
setOrgs(orgs.data);
setGitUser(user.data);
setSelectedAccount({ label: user.data.login, value: user.data.login });
} catch (error) {
// Error handled by octokit error hook interceptor in Octokit context
console.error(error);
return;
}
};
fetchUserAndOrgs();
}, [octokit]);
if (isAuth) {
fetchUserAndOrgs();
}
}, [octokit, isAuth]);
const debouncedSearchedRepo = useDebounce<string>(searchedRepo, 500);
@@ -150,13 +155,13 @@ export const RepositoryList = ({ octokit }: RepositoryListProps) => {
{Boolean(repositoryDetails.length) ? (
<div className="flex flex-col gap-2">
{repositoryDetails.map((repo, index) => (
<>
<ProjectRepoCard repository={repo} key={index} />
<div key={index}>
<ProjectRepoCard repository={repo} />
{/* Horizontal line */}
{index !== repositoryDetails.length - 1 && (
<div className="border-b border-border-separator/[0.06] w-full" />
)}
</>
</div>
))}
</div>
) : (
@@ -1,14 +1,8 @@
import React from 'react';
import { CopyBlock, atomOneLight } from 'react-code-blocks';
import { Link } from 'react-router-dom';
import {
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
} from '@material-tailwind/react';
import { Modal } from 'components/shared/Modal';
import { Button } from 'components/shared/Button';
interface AssignDomainProps {
open: boolean;
@@ -17,37 +11,31 @@ interface AssignDomainProps {
const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader placeholder={''}>Assign Domain</DialogHeader>
<DialogBody placeholder={''}>
In order to assign a domain to your production deployments, configure it
in the{' '}
{/* TODO: Fix selection of project settings tab on navigation to domains */}
<Link to="../settings/domains" className="text-light-blue-800 inline">
project settings{' '}
</Link>
(recommended). If you want to assign to this specific deployment,
however, you can do so using our command-line interface:
{/* https://github.com/rajinwonderland/react-code-blocks/issues/138 */}
<CopyBlock
text="snowball alias <deployment> <domain>"
language=""
showLineNumbers={false}
theme={atomOneLight}
/>
</DialogBody>
<DialogFooter className="flex justify-start" placeholder={''}>
<Button
className="rounded-3xl"
variant="gradient"
color="blue"
onClick={handleOpen}
placeholder={''}
>
<span>Okay</span>
</Button>
</DialogFooter>
</Dialog>
<Modal open={open} onOpenChange={handleOpen}>
<Modal.Content>
<Modal.Header>Assign Domain</Modal.Header>
<Modal.Body>
In order to assign a domain to your production deployments, configure
it in the{' '}
{/* TODO: Fix selection of project settings tab on navigation to domains */}
<Link to="../settings/domains" className="text-light-blue-800 inline">
project settings{' '}
</Link>
(recommended). If you want to assign to this specific deployment,
however, you can do so using our command-line interface:
{/* https://github.com/rajinwonderland/react-code-blocks/issues/138 */}
<CopyBlock
text="snowball alias <deployment> <domain>"
language=""
showLineNumbers={false}
theme={atomOneLight}
/>
</Modal.Body>
<Modal.Footer className="flex justify-start">
<Button onClick={handleOpen}>Okay</Button>
</Modal.Footer>
</Modal.Content>
</Modal>
);
};
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import {
Deployment,
DeploymentStatus,
@@ -38,6 +38,7 @@ const STATUS_COLORS: {
[DeploymentStatus.Building]: 'emphasized',
[DeploymentStatus.Ready]: 'positive',
[DeploymentStatus.Error]: 'negative',
[DeploymentStatus.Deleting]: 'neutral',
};
const DeploymentDetailsCard = ({
@@ -48,7 +49,10 @@ const DeploymentDetailsCard = ({
prodBranchDomains,
}: DeployDetailsCardProps) => {
const getIconByDeploymentStatus = (status: DeploymentStatus) => {
if (status === DeploymentStatus.Building) {
if (
status === DeploymentStatus.Building ||
status === DeploymentStatus.Deleting
) {
return <LoadingIcon className="animate-spin" />;
}
if (status === DeploymentStatus.Ready) {
@@ -1,4 +1,3 @@
import React from 'react';
import { Deployment } from 'gql-client';
import { relativeTimeMs } from 'utils/time';
@@ -1,5 +1,12 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { useState, ComponentPropsWithRef } from 'react';
import {
Menu,
MenuHandler,
MenuItem,
MenuList,
} from '@snowballtools/material-tailwind-react-fork';
import { Deployment, Domain, Environment, Project } from 'gql-client';
import { Button } from 'components/shared/Button';
import {
@@ -9,18 +16,13 @@ import {
RefreshIcon,
RocketIcon,
UndoIcon,
CrossCircleIcon,
} from 'components/shared/CustomIcon';
import {
Menu,
MenuHandler,
MenuItem,
MenuList,
} from '@material-tailwind/react';
import { ComponentPropsWithRef } from 'react';
import AssignDomainDialog from './AssignDomainDialog';
import { useGQLClient } from 'context/GQLClientContext';
import { cn } from 'utils/classnames';
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
import { useToast } from 'components/shared/Toast';
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
deployment: Deployment;
@@ -40,6 +42,7 @@ export const DeploymentMenu = ({
...props
}: DeploymentMenuProps) => {
const client = useGQLClient();
const { toast, dismiss } = useToast();
const [changeToProduction, setChangeToProduction] = useState(false);
const [redeployToProduction, setRedeployToProduction] = useState(false);
@@ -50,9 +53,19 @@ export const DeploymentMenu = ({
const isUpdated = await client.updateDeploymentToProd(deployment.id);
if (isUpdated) {
await onUpdate();
toast.success('Deployment changed to production');
toast({
id: 'deployment_changed_to_production',
title: 'Deployment changed to production',
variant: 'success',
onDismiss: dismiss,
});
} else {
toast.error('Unable to change deployment to production');
toast({
id: 'deployment_not_changed_to_production',
title: 'Error changing deployment to production',
variant: 'error',
onDismiss: dismiss,
});
}
};
@@ -60,9 +73,19 @@ export const DeploymentMenu = ({
const isRedeployed = await client.redeployToProd(deployment.id);
if (isRedeployed) {
await onUpdate();
toast.success('Redeployed to production');
toast({
id: 'redeployed_to_production',
title: 'Redeployed to production',
variant: 'success',
onDismiss: dismiss,
});
} else {
toast.error('Unable to redeploy to production');
toast({
id: 'redeployed_to_production_failed',
title: 'Error redeploying to production',
variant: 'error',
onDismiss: dismiss,
});
}
};
@@ -73,9 +96,39 @@ export const DeploymentMenu = ({
);
if (isRollbacked) {
await onUpdate();
toast.success('Deployment rolled back');
toast({
id: 'deployment_rolled_back',
title: 'Deployment rolled back',
variant: 'success',
onDismiss: dismiss,
});
} else {
toast.error('Unable to rollback deployment');
toast({
id: 'deployment_rollback_failed',
title: 'Error rolling back deployment',
variant: 'error',
onDismiss: dismiss,
});
}
};
const deleteDeployment = async () => {
const isDeleted = await client.deleteDeployment(deployment.id);
if (isDeleted) {
await onUpdate();
toast({
id: 'deployment_deleted',
title: 'Deployment deleted',
variant: 'success',
onDismiss: dismiss,
});
} else {
toast({
id: 'deployment_not_deleted',
title: 'Error deleting deployment',
variant: 'error',
onDismiss: dismiss,
});
}
};
@@ -96,11 +149,10 @@ export const DeploymentMenu = ({
aria-label="Toggle Menu"
/>
</MenuHandler>
<MenuList className="text-elements-high-em" placeholder={''}>
<MenuList className="text-elements-high-em">
<MenuItem
className="hover:bg-base-bg-emphasized"
disabled={!Boolean(deployment.url)}
placeholder={''}
>
<a
className="flex items-center gap-3"
@@ -114,7 +166,6 @@ export const DeploymentMenu = ({
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => setAssignDomainDialog(!assignDomainDialog)}
placeholder={''}
>
<GlobeIcon /> Assign domain
</MenuItem>
@@ -122,7 +173,6 @@ export const DeploymentMenu = ({
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => setChangeToProduction(!changeToProduction)}
disabled={!(deployment.environment !== Environment.Production)}
placeholder={''}
>
<RocketIcon /> Change to production
</MenuItem>
@@ -136,7 +186,6 @@ export const DeploymentMenu = ({
deployment.isCurrent
)
}
placeholder={''}
>
<RefreshIcon /> Redeploy to production
</MenuItem>
@@ -148,10 +197,15 @@ export const DeploymentMenu = ({
deployment.environment !== Environment.Production ||
!Boolean(currentDeployment)
}
placeholder={''}
>
<UndoIcon /> Rollback to this version
</MenuItem>
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => deleteDeployment()}
>
<CrossCircleIcon /> Delete deployment
</MenuItem>
</MenuList>
</Menu>
</div>
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Input } from 'components/shared/Input';
import {
@@ -10,8 +10,8 @@ import {
WarningTriangleIcon,
} from 'components/shared/CustomIcon';
import { DatePicker } from 'components/shared/DatePicker';
import { Value } from 'react-calendar/dist/cjs/shared/types';
import { Select, SelectOption } from 'components/shared/Select';
import { Value } from 'types/vendor';
export enum StatusOptions {
ALL_STATUS = 'All status',
@@ -1,36 +1,23 @@
import React from 'react';
import { GitCommitWithBranch } from 'types';
import { GitCommitWithBranch } from '../../../../../types/types';
import { Heading } from 'components/shared/Heading';
import ActivityCard from './ActivityCard';
import { Button } from 'components/shared/Button';
import { LoadingIcon, LinkIcon } from 'components/shared/CustomIcon';
import { Link } from 'react-router-dom';
import { LoadingIcon } from 'components/shared/CustomIcon';
export const Activity = ({
isLoading,
activities,
repoPath,
}: {
isLoading: boolean;
activities: GitCommitWithBranch[];
repoPath: string;
}) => {
return (
<div className="col-span-5 md:col-span-2 mr-1">
<div className="flex items-center justify-between">
<Heading className="text-lg leading-6 font-medium">Activity</Heading>
<Link to={`https://github.com/${repoPath}/activity`} target="_blank">
<Button
variant="tertiary"
size="sm"
rightIcon={
<LinkIcon className="group-hover:rotate-45 transition-transform" />
}
>
See all
</Button>
</Link>
<Button variant="tertiary" size="sm">
See all
</Button>
</div>
<div className="mt-5">
{isLoading ? (
@@ -1,6 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { GitCommitWithBranch } from '../../../../../types';
import { GitCommitWithBranch } from '../../../../../types/types';
import { Avatar } from 'components/shared/Avatar';
import { Button } from 'components/shared/Button';
import {
@@ -1,4 +1,3 @@
import React from 'react';
import { PropsWithChildren } from 'react';
import { cloneElement } from 'utils/cloneElement';
@@ -1,9 +1,9 @@
import React from 'react';
import { UseFormRegister } from 'react-hook-form';
import { Typography, Input, IconButton } from '@material-tailwind/react';
import { EnvironmentVariablesFormValues } from '../../../../types';
import { Button } from 'components/shared/Button';
import { TrashIcon } from 'components/shared/CustomIcon';
import { Input } from 'components/shared/Input';
interface AddEnvironmentVariableRowProps {
onDelete: () => void;
@@ -19,39 +19,24 @@ const AddEnvironmentVariableRow = ({
isDeleteDisabled,
}: AddEnvironmentVariableRowProps) => {
return (
<div className="flex gap-1 p-2">
<div>
<Typography variant="small" placeholder={''}>
Key
</Typography>
<Input
crossOrigin={undefined}
{...register(`variables.${index}.key`, {
required: 'Key field cannot be empty',
})}
/>
</div>
<div>
<Typography variant="small" placeholder={''}>
Value
</Typography>
<Input
crossOrigin={undefined}
{...register(`variables.${index}.value`, {
required: 'Value field cannot be empty',
})}
/>
</div>
<div className="self-end">
<IconButton
size="sm"
onClick={() => onDelete()}
disabled={isDeleteDisabled}
placeholder={''}
>
{'>'}
</IconButton>
</div>
<div className="flex gap-2 py-1 self-stretch items-end">
<Input
size="md"
{...register(`variables.${index}.key`, {
required: 'Key field cannot be empty',
})}
label={index === 0 ? 'Key' : undefined}
/>
<Input
size="md"
label={index === 0 ? 'Value' : undefined}
{...register(`variables.${index}.value`, {
required: 'Value field cannot be empty',
})}
/>
<Button size="md" iconOnly onClick={onDelete} disabled={isDeleteDisabled}>
<TrashIcon />
</Button>
</div>
);
};

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