Compare commits

...
Author SHA1 Message Date
nabarun 86e6600749 Update staging deployment records with new config values 2024-05-21 11:59:32 +05:30
nabarun 3f11ff3da1 Update build script for new VITE env variables 2024-05-21 11:02:43 +05:30
nabarun f614199bb8 Update subdomain 2024-05-17 12:34:36 +05:30
nabarun 0787d2008b Update GitHub client ID and subdomain 2024-05-17 12:16:07 +05:30
nabarun b9ffb691dc Add staging deployment records 2024-05-17 11:28:22 +05:30
nabarun 25e47aa5ec Add deployment script for staging 2024-05-17 11:02:10 +05:30
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
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
449 changed files with 18851 additions and 9630 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
+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
+6 -13
View File
@@ -3,15 +3,18 @@
"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",
@@ -35,30 +38,20 @@
"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: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;
};
}
+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']
})
+3 -6
View File
@@ -9,8 +9,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 +16,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 +32,7 @@ export const main = async (): Promise<void> => {
{ gitHubConfig: gitHub, registryConfig },
db,
app,
registry
registry,
);
const typeDefs = fs
+1 -1
View File
@@ -3,7 +3,7 @@ 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 {
+79 -16
View File
@@ -1,41 +1,104 @@
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) => {
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) => {
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;
+26 -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';
@@ -23,8 +23,7 @@ const log = debug('snowball:server');
declare module 'express-session' {
interface SessionData {
address: string;
chainId: number;
userId: string;
}
}
@@ -32,7 +31,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 +44,7 @@ export const createAndStartServer = async (
// Create the schema
const schema = makeExecutableSchema({
typeDefs,
resolvers
resolvers,
});
const server = new ApolloServer({
@@ -54,46 +53,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 +85,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 +100,8 @@ export const createAndStartServer = async (
path: gqlPath,
cors: {
origin: [appOriginUrl],
credentials: true
}
credentials: true,
},
});
app.use(express.json());
+42 -19
View File
@@ -161,32 +161,55 @@ export class Service {
});
}
async loadOrCreateUser (ethAddress: string): Promise<User> {
// Get user by ETH address
let user = await this.db.getUser({
async getUserByEmail(email: string): Promise<User | null> {
return await this.db.getUser({
where: {
email
}
});
}
async getUserBySubOrgId(subOrgId: string): Promise<User | null> {
return await this.db.getUser({
where: {
subOrgId
}
});
}
async getUserByEthAddress (ethAddress: string): Promise<User | null> {
return await this.db.getUser({
where: {
ethAddress
}
});
}
if (!user) {
const [org] = await this.db.getOrganizations({});
assert(org, 'No organizations exists in database');
async createUser (params: {
name: string
email: string
subOrgId: string
ethAddress: string
turnkeyWalletId: string
}): Promise<User> {
const [org] = await this.db.getOrganizations({});
assert(org, 'No organizations exists in database');
// Create user with new address
user = await this.db.addUser({
email: `${ethAddress}@example.com`,
name: ethAddress,
isVerified: true,
ethAddress
});
// Create user with new address
const user = await this.db.addUser({
email: params.email,
name: params.name,
subOrgId: params.subOrgId,
ethAddress: params.ethAddress,
isVerified: true,
turnkeyWalletId: params.turnkeyWalletId,
});
await this.db.addUserOrganization({
member: user,
organization: org,
role: Role.Owner
});
}
await this.db.addUserOrganization({
member: user,
organization: org,
role: Role.Owner
});
return user;
}
+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;
}
+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,7 +2,7 @@ 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';
+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: 098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661
chainId: laconic_9000-1
gas: 550000
fees: 200000aphoton
+6 -6
View File
@@ -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"
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
# Repository URL
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
# Get the latest commit hash from the repository
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
# Extract version from ../frontend/package.json
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
# Current date and time for note
CURRENT_DATE_TIME=$(date -u)
CONFIG_FILE=config.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: localhost
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
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
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://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"
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 found for REGISTRY_APP_CRN
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
echo "No record found for $REGISTRY_APP_CRN."
exit 1
fi
RECORD_FILE=staging-records/application-deployment-request.yml
sleep 2
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
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"
}
}
@@ -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://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
@@ -0,0 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.2
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
app_type: webapp
name: staging-snowballtools-base-frontend
app_version: 0.0.0
+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
-3
View File
@@ -1,3 +0,0 @@
# artifacts
build
coverage
+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

+53 -47
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';
+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';
@@ -95,11 +91,9 @@ export const ProjectCard = ({
<HorizontalDotIcon />
</Button>
</MenuHandler>
<MenuList placeholder={''}>
<MenuItem placeholder={''}>Project settings</MenuItem>
<MenuItem className="text-red-500" placeholder={''}>
Delete project
</MenuItem>
<MenuList>
<MenuItem>Project settings</MenuItem>
<MenuItem className="text-red-500">Delete project</MenuItem>
</MenuList>
</Menu>
</div>
@@ -124,7 +118,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';
@@ -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);
@@ -1,4 +1,3 @@
import React from 'react';
import { CopyBlock, atomOneLight } from 'react-code-blocks';
import { Link } from 'react-router-dom';
@@ -8,7 +7,7 @@ import {
DialogHeader,
DialogBody,
DialogFooter,
} from '@material-tailwind/react';
} from '@snowballtools/material-tailwind-react-fork';
interface AssignDomainProps {
open: boolean;
@@ -17,9 +16,9 @@ interface AssignDomainProps {
const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader placeholder={''}>Assign Domain</DialogHeader>
<DialogBody placeholder={''}>
<Dialog open={open} handler={handleOpen}>
<DialogHeader>Assign Domain</DialogHeader>
<DialogBody>
In order to assign a domain to your production deployments, configure it
in the{' '}
{/* TODO: Fix selection of project settings tab on navigation to domains */}
@@ -36,13 +35,12 @@ const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
theme={atomOneLight}
/>
</DialogBody>
<DialogFooter className="flex justify-start" placeholder={''}>
<DialogFooter className="flex justify-start">
<Button
className="rounded-3xl"
variant="gradient"
color="blue"
onClick={handleOpen}
placeholder={''}
>
<span>Okay</span>
</Button>
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import {
Deployment,
DeploymentStatus,
@@ -1,4 +1,3 @@
import React from 'react';
import { Deployment } from 'gql-client';
import { relativeTimeMs } from 'utils/time';
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import toast from 'react-hot-toast';
import { Deployment, Domain, Environment, Project } from 'gql-client';
import { Button } from 'components/shared/Button';
@@ -15,7 +15,7 @@ import {
MenuHandler,
MenuItem,
MenuList,
} from '@material-tailwind/react';
} from '@snowballtools/material-tailwind-react-fork';
import { ComponentPropsWithRef } from 'react';
import AssignDomainDialog from './AssignDomainDialog';
import { useGQLClient } from 'context/GQLClientContext';
@@ -96,11 +96,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 +113,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 +120,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 +133,6 @@ export const DeploymentMenu = ({
deployment.isCurrent
)
}
placeholder={''}
>
<RefreshIcon /> Redeploy to production
</MenuItem>
@@ -148,7 +144,6 @@ export const DeploymentMenu = ({
deployment.environment !== Environment.Production ||
!Boolean(currentDeployment)
}
placeholder={''}
>
<UndoIcon /> Rollback to this version
</MenuItem>
@@ -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,6 +1,4 @@
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';
@@ -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,38 +19,30 @@ 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="flex py-4 self-stretch">
<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',
})}
/>
<div className="self-end">
<IconButton
size="sm"
onClick={() => onDelete()}
<Button
size="md"
iconOnly
onClick={onDelete}
disabled={isDeleteDisabled}
placeholder={''}
>
{'>'}
</IconButton>
<TrashIcon />
</Button>
</div>
</div>
);
@@ -1,17 +1,13 @@
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { AddProjectMemberInput, Permission } from 'gql-client';
import {
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
Input,
Typography,
Checkbox,
} from '@material-tailwind/react';
import { Typography } from '@snowballtools/material-tailwind-react-fork';
import { Button } from 'components/shared/Button';
import { Modal } from 'components/shared/Modal';
import { Input } from 'components/shared/Input';
import { Checkbox } from 'components/shared/Checkbox';
interface AddMemberDialogProp {
open: boolean;
@@ -61,73 +57,47 @@ const AddMemberDialog = ({
}, []);
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader className="flex justify-between" placeholder={''}>
<div>Add member</div>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1 rounded-3xl"
placeholder={''}
>
X
</Button>
</DialogHeader>
<form onSubmit={handleSubmit(submitHandler)}>
<DialogBody className="flex flex-col gap-2 p-4" placeholder={''}>
<Typography variant="small" placeholder={''}>
We will send an invitation link to this email address.
</Typography>
<Typography variant="small" placeholder={''}>
Email address
</Typography>
<Input
type="email"
crossOrigin={undefined}
{...register('emailAddress', {
required: 'email field cannot be empty',
})}
/>
<Typography variant="small" placeholder={''}>
Permissions
</Typography>
<Typography variant="small" placeholder={''}>
You can change this later if required.
</Typography>
<Checkbox
crossOrigin={undefined}
label={Permission.View}
{...register(`permissions.view`)}
color="blue"
/>
<Checkbox
crossOrigin={undefined}
label={Permission.Edit}
{...register(`permissions.edit`)}
color="blue"
/>
</DialogBody>
<DialogFooter className="flex justify-start" placeholder={''}>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1"
placeholder={''}
>
Cancel
</Button>
<Button
variant="gradient"
color="blue"
type="submit"
disabled={!isValid}
placeholder={''}
>
Send invite
</Button>
</DialogFooter>
</form>
</Dialog>
<Modal open={open} onOpenChange={handleOpen}>
<Modal.Content>
<Modal.Header>Add member</Modal.Header>
<form onSubmit={handleSubmit(submitHandler)}>
<Modal.Body className="flex flex-col gap-2 p-4">
<Typography variant="small">
We will send an invitation link to this email address.
</Typography>
<Typography variant="small">Email address</Typography>
<Input
type="email"
{...register('emailAddress', {
required: 'email field cannot be empty',
})}
/>
<Typography variant="small">Permissions</Typography>
<Typography variant="small">
You can change this later if required.
</Typography>
<Checkbox
label={Permission.View}
{...register(`permissions.view`)}
color="blue"
/>
<Checkbox
label={Permission.Edit}
{...register(`permissions.edit`)}
color="blue"
/>
</Modal.Body>
<Modal.Footer>
<Button onClick={handleOpen} variant="secondary">
Cancel
</Button>
<Button type="submit" disabled={!isValid}>
Send invite
</Button>
</Modal.Footer>
</form>
</Modal.Content>
</Modal>
);
};
@@ -1,7 +1,6 @@
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { Project } from 'gql-client';
import {
@@ -12,8 +11,10 @@ import {
DialogFooter,
Input,
Typography,
} from '@material-tailwind/react';
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useToast } from 'components/shared/Toast';
interface DeleteProjectDialogProp {
open: boolean;
@@ -26,6 +27,7 @@ const DeleteProjectDialog = ({
handleOpen,
project,
}: DeleteProjectDialogProp) => {
const { toast, dismiss } = useToast();
const { orgSlug } = useParams();
const navigate = useNavigate();
const client = useGQLClient();
@@ -46,28 +48,32 @@ const DeleteProjectDialog = ({
if (deleteProject) {
navigate(`/${orgSlug}`);
} else {
toast.error('Project not deleted');
toast({
id: 'project_not_deleted',
title: 'Project not deleted',
variant: 'error',
onDismiss: dismiss,
});
}
handleOpen();
}, [client, project, handleOpen]);
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader className="flex justify-between" placeholder={''}>
<Dialog open={open} handler={handleOpen}>
<DialogHeader className="flex justify-between">
<div>Delete project?</div>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1 rounded-3xl"
placeholder={''}
>
X
</Button>
</DialogHeader>
<form onSubmit={handleSubmit(deleteProjectHandler)}>
<DialogBody className="flex flex-col gap-2" placeholder={''}>
<Typography variant="paragraph" placeholder={''}>
<DialogBody className="flex flex-col gap-2">
<Typography variant="paragraph">
Deleting your project is irreversible. Enter your projects
name&nbsp;
<span className="bg-blue-100 text-blue-700">({project.name})</span>
@@ -75,23 +81,17 @@ const DeleteProjectDialog = ({
</Typography>
<Input
id="input"
crossOrigin={undefined}
{...register('projectName', {
required: 'Project name is required',
validate: (value) => value === project.name,
})}
/>
<Typography variant="small" color="red" placeholder={''}>
<Typography variant="small" color="red">
^ Deleting your project is irreversible.
</Typography>
</DialogBody>
<DialogFooter className="flex justify-start" placeholder={''}>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1"
placeholder={''}
>
<DialogFooter className="flex justify-start">
<Button variant="outlined" onClick={handleOpen} className="mr-1">
Cancel
</Button>
<Button
@@ -99,7 +99,6 @@ const DeleteProjectDialog = ({
color="red"
type="submit"
disabled={!isValid}
placeholder={''}
>
Yes, Delete project
</Button>
@@ -1,8 +1,12 @@
import React, { useState } from 'react';
import { Card, Collapse, Typography } from '@material-tailwind/react';
import { useState } from 'react';
import { Collapse } from '@snowballtools/material-tailwind-react-fork';
import EditEnvironmentVariableRow from './EditEnvironmentVariableRow';
import { Environment, EnvironmentVariable } from 'gql-client';
import {
ChevronDownSmallIcon,
ChevronUpSmallIcon,
} from 'components/shared/CustomIcon';
interface DisplayEnvironmentVariablesProps {
environment: Environment;
@@ -20,23 +24,19 @@ const DisplayEnvironmentVariables = ({
return (
<>
<div
className="flex gap-4 p-2 "
className="flex gap-4 p-2"
onClick={() => setOpenCollapse((cur) => !cur)}
>
<div>^</div>
{openCollapse ? <ChevronUpSmallIcon /> : <ChevronDownSmallIcon />}
<div className="grow capitalize">{environment}</div>
<div>{variables.length} variables</div>
</div>
<Collapse open={openCollapse}>
{variables.length === 0 ? (
<Card className="bg-gray-300 flex items-center p-4" placeholder={''}>
<Typography variant="small" className="text-black" placeholder={''}>
No environment variables added yet.
</Typography>
<Typography variant="small" placeholder={''}>
Once you add them, theyll show up here.
</Typography>
</Card>
<div className="bg-slate-100 rounded-xl flex-col p-4">
No environment variables added yet. Once you add them, they'll show
up here.
</div>
) : (
variables.map((variable: EnvironmentVariable) => {
return (
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { useState } from 'react';
import { Domain, DomainStatus, Project } from 'gql-client';
import {
@@ -10,11 +9,12 @@ import {
MenuList,
MenuItem,
Card,
} from '@material-tailwind/react';
} from '@snowballtools/material-tailwind-react-fork';
import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
import { useToast } from 'components/shared/Toast';
enum RefreshStatus {
IDLE,
@@ -47,6 +47,7 @@ const DomainCard = ({
project,
onUpdate,
}: DomainCardProps) => {
const { toast, dismiss } = useToast();
const [refreshStatus, SetRefreshStatus] = useState(RefreshStatus.IDLE);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [editDialogOpen, setEditDialogOpen] = useState(false);
@@ -58,9 +59,19 @@ const DomainCard = ({
if (deleteDomain) {
onUpdate();
toast.success(`Domain ${domain.name} deleted successfully`);
toast({
id: 'domain_deleted_success',
title: 'Domain deleted',
variant: 'success',
onDismiss: dismiss,
});
} else {
toast.error(`Error deleting domain ${domain.name}`);
toast({
id: 'domain_deleted_error',
title: `Error deleting domain ${domain.name}`,
variant: 'error',
onDismiss: dismiss,
});
}
};
@@ -68,7 +79,7 @@ const DomainCard = ({
<>
<div className="flex justify-between py-3">
<div className="flex justify-start gap-1">
<Typography variant="h6" placeholder={''}>
<Typography variant="h6">
<i>^</i> {domain.name}
</Typography>
<Chip
@@ -97,22 +108,20 @@ const DomainCard = ({
<MenuHandler>
<button className="border-2 rounded-full w-8 h-8">...</button>
</MenuHandler>
<MenuList placeholder={''}>
<MenuList>
<MenuItem
className="text-black"
onClick={() => {
setEditDialogOpen((preVal) => !preVal);
}}
placeholder={''}
>
^ Edit domain
Edit Domain
</MenuItem>
<MenuItem
className="text-red-500"
onClick={() => setDeleteDialogOpen((preVal) => !preVal)}
placeholder={''}
>
^ Delete domain
Delete domain
</MenuItem>
</MenuList>
</Menu>
@@ -130,26 +139,19 @@ const DomainCard = ({
/>
</div>
<Typography variant="small" placeholder={''}>
Production
</Typography>
<Typography variant="small">Production</Typography>
{domain.status === DomainStatus.Pending && (
<Card className="bg-gray-200 p-4 text-sm" placeholder={''}>
<Card className="bg-slate-100 p-4 text-sm">
{refreshStatus === RefreshStatus.IDLE ? (
<Typography variant="small" placeholder={''}>
<Typography variant="small">
^ Add these records to your domain and refresh to check
</Typography>
) : refreshStatus === RefreshStatus.CHECKING ? (
<Typography
variant="small"
className="text-blue-500"
placeholder={''}
>
<Typography variant="small" className="text-blue-500">
^ Checking records for {domain.name}
</Typography>
) : (
<div className="flex gap-2 text-red-500 mb-2">
<div>^</div>
<div className="grow">
Failed to verify records. DNS propagation can take up to 48
hours. Please ensure you added the correct records and refresh.
@@ -1,21 +1,18 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { Controller, useForm, SubmitHandler } from 'react-hook-form';
import toast from 'react-hot-toast';
import { Domain } from 'gql-client';
import {
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
Input,
Typography,
Select,
Option,
} from '@material-tailwind/react';
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { Modal } from 'components/shared/Modal';
import { Button } from 'components/shared/Button';
import { Input } from 'components/shared/Input';
const DEFAULT_REDIRECT_OPTIONS = ['none'];
@@ -122,95 +119,67 @@ const EditDomainDialog = ({
}, [domain]);
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader className="flex justify-between" placeholder={''}>
<div>Edit domain</div>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1 rounded-3xl"
placeholder={''}
>
X
</Button>
</DialogHeader>
<form onSubmit={handleSubmit(updateDomainHandler)}>
<DialogBody className="flex flex-col gap-2 p-4" placeholder={''}>
<Typography variant="small" placeholder={''}>
Domain name
</Typography>
<Input crossOrigin={undefined} {...register('name')} />
<Typography variant="small" placeholder={''}>
Redirect to
</Typography>
<Controller
name="redirectedTo"
control={control}
render={({ field }) => (
<Select {...field} disabled={isDisableDropdown} placeholder={''}>
{redirectOptions.map((option, key) => (
<Option key={key} value={option}>
^ {option}
</Option>
))}
</Select>
<Modal open={open} onOpenChange={handleOpen}>
<Modal.Content>
<Modal.Header>Edit domain</Modal.Header>
<form onSubmit={handleSubmit(updateDomainHandler)}>
<Modal.Body className="flex flex-col gap-2">
<Typography variant="small">Domain name</Typography>
<Input {...register('name')} />
<Typography variant="small">Redirect to</Typography>
<Controller
name="redirectedTo"
control={control}
render={({ field }) => (
<Select {...field} disabled={isDisableDropdown}>
{redirectOptions.map((option, key) => (
<Option key={key} value={option}>
^ {option}
</Option>
))}
</Select>
)}
/>
{isDisableDropdown && (
<div className="flex p-2 gap-2 text-black bg-gray-300 rounded-lg">
<div>^</div>
<Typography variant="small">
Domain
{domainRedirectedFrom ? domainRedirectedFrom.name : ''}
redirects to this domain so you can not redirect this doman
further.
</Typography>
</div>
)}
/>
{isDisableDropdown && (
<div className="flex p-2 gap-2 text-black bg-gray-300 rounded-lg">
<div>^</div>
<Typography variant="small" placeholder={''}>
Domain {domainRedirectedFrom ? domainRedirectedFrom.name : ''}
redirects to this domain so you can not redirect this doman
further.
<Typography variant="small">Git branch</Typography>
<Input
{...register('branch', {
validate: (value) =>
Boolean(branches.length) ? branches.includes(value) : true,
})}
disabled={
!Boolean(branches.length) ||
watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]
}
/>
{!isValid && (
<Typography variant="small" className="text-red-500">
We couldn&apos;t find this branch in the connected Git
repository.
</Typography>
</div>
)}
<Typography variant="small" placeholder={''}>
Git branch
</Typography>
<Input
crossOrigin={undefined}
{...register('branch', {
validate: (value) =>
Boolean(branches.length) ? branches.includes(value) : true,
})}
disabled={
!Boolean(branches.length) ||
watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]
}
/>
{!isValid && (
<Typography
variant="small"
className="text-red-500"
placeholder={''}
>
We couldn&apos;t find this branch in the connected Git repository.
</Typography>
)}
</DialogBody>
<DialogFooter className="flex justify-start" placeholder={''}>
<Button
variant="outlined"
onClick={handleOpen}
className="mr-1"
placeholder={''}
>
Cancel
</Button>
<Button
variant="gradient"
color="blue"
type="submit"
disabled={!isDirty}
placeholder={''}
>
Save changes
</Button>
</DialogFooter>
</form>
</Dialog>
)}
</Modal.Body>
<Modal.Footer>
<Button onClick={handleOpen} className="mr-1">
Cancel
</Button>
<Button type="submit" disabled={!isDirty}>
Save changes
</Button>
</Modal.Footer>
</form>
</Modal.Content>
</Modal>
);
};
@@ -1,12 +1,16 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { EnvironmentVariable } from 'gql-client';
import { IconButton, Input, Typography } from '@material-tailwind/react';
import {
IconButton,
Typography,
} from '@snowballtools/material-tailwind-react-fork';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteVariableDialog } from 'components/projects/Dialog/DeleteVariableDialog';
import { Input } from 'components/shared/Input';
const ShowPasswordIcon = ({
handler,
@@ -84,24 +88,15 @@ const EditEnvironmentVariableRow = ({
<>
<div className="flex gap-1 p-2">
<div>
<Typography variant="small" placeholder={''}>
Key
</Typography>
<Input
crossOrigin={undefined}
disabled={!edit}
{...register(`key`)}
/>
<Typography variant="small">Key</Typography>
<Input disabled={!edit} {...register(`key`)} />
</div>
<div>
<Typography variant="small" placeholder={''}>
Value
</Typography>
<Typography variant="small">Value</Typography>
<Input
crossOrigin={undefined}
disabled={!edit}
type={showPassword ? 'text' : 'password'}
icon={
leftIcon={
<ShowPasswordIcon
handler={() => {
setShowPassword((prevShowPassword) => !prevShowPassword);
@@ -118,7 +113,6 @@ const EditEnvironmentVariableRow = ({
<IconButton
onClick={handleSubmit(updateEnvironmentVariableHandler)}
size="sm"
placeholder={''}
>
{'S'}
</IconButton>
@@ -130,7 +124,6 @@ const EditEnvironmentVariableRow = ({
reset();
setEdit((preVal) => !preVal);
}}
placeholder={''}
>
{'C'}
</IconButton>
@@ -144,7 +137,6 @@ const EditEnvironmentVariableRow = ({
onClick={() => {
setEdit((preVal) => !preVal);
}}
placeholder={''}
>
{'E'}
</IconButton>
@@ -153,7 +145,6 @@ const EditEnvironmentVariableRow = ({
<IconButton
size="sm"
onClick={() => setDeleteDialogOpen((preVal) => !preVal)}
placeholder={''}
>
{'D'}
</IconButton>
@@ -1,5 +1,3 @@
import React from 'react';
import { GitSelect } from '../../../../types';
const GitSelectionSection = ({
@@ -1,18 +1,16 @@
import React, { useCallback, useState } from 'react';
import { Permission, User } from 'gql-client';
import { useCallback, useState } from 'react';
import {
Select,
Option,
Chip,
IconButton,
Tooltip,
} from '@material-tailwind/react';
import { formatAddress } from 'utils/format';
import { RemoveMemberDialog } from 'components/projects/Dialog/RemoveMemberDialog';
import { Select, SelectOption } from 'components/shared/Select';
import { LoaderIcon } from 'components/shared/CustomIcon';
import { Tooltip } from 'components/shared/Tooltip';
import { Button } from 'components/shared/Button';
import { Permission, User } from 'gql-client';
import { formatAddress } from 'utils/format';
import { Tag } from 'components/shared/Tag';
import { Input } from 'components/shared/Input';
const PERMISSION_OPTIONS = [
const PERMISSION_OPTIONS: SelectOption[] = [
{
label: 'View only',
value: 'View',
@@ -23,7 +21,7 @@ const PERMISSION_OPTIONS = [
},
];
const DROPDOWN_OPTIONS = [
const DROPDOWN_OPTIONS: SelectOption[] = [
...PERMISSION_OPTIONS,
{ label: 'Remove member', value: 'remove' },
];
@@ -50,16 +48,21 @@ const MemberCard = ({
onUpdateProjectMember,
}: MemberCardProps) => {
const [ethAddress, emailDomain] = member.email.split('@');
const [selectedPermission, setSelectedPermission] = useState(
permissions.join('+'),
const [selectedPermission, setSelectedPermission] = useState<SelectOption>(
PERMISSION_OPTIONS.map((value) => {
permissions.join('+') === value.value;
}).pop() ?? {
label: 'View only',
value: 'View',
},
);
const [removeMemberDialogOpen, setRemoveMemberDialogOpen] = useState(false);
const handlePermissionChange = useCallback(
async (value: string) => {
async (value: SelectOption) => {
setSelectedPermission(value);
if (value === 'remove') {
if (value.value === 'remove') {
setRemoveMemberDialogOpen((prevVal) => !prevVal);
// To display updated label in next render
setTimeout(() => {
@@ -67,7 +70,7 @@ const MemberCard = ({
});
} else {
if (onUpdateProjectMember) {
const permissions = value.split('+') as Permission[];
const permissions = value.value.split('+') as Permission[];
await onUpdateProjectMember({ permissions });
}
}
@@ -77,9 +80,8 @@ const MemberCard = ({
return (
<div
className={`flex p-1 ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
className={`flex p-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
>
<div>^</div>
<div className="basis-1/2">
{member.name && (
<Tooltip content={member.name}>
@@ -94,48 +96,36 @@ const MemberCard = ({
</div>
<div className="basis-1/2">
{!isPending ? (
<Select
size="lg"
label={isOwner ? 'Owner' : ''}
disabled={isOwner}
value={selectedPermission}
onChange={(value) => handlePermissionChange(value!)}
selected={(_, index) => (
<span>{DROPDOWN_OPTIONS[index!]?.label}</span>
)}
placeholder={''}
>
{DROPDOWN_OPTIONS.map((permission, key) => (
<Option key={key} value={permission.value}>
^ {permission.label}
{permission.value === selectedPermission && (
<p className="float-right">^</p>
)}
</Option>
))}
</Select>
isOwner ? (
<Input size="md" value="Owner" disabled />
) : (
<Select
options={DROPDOWN_OPTIONS}
size="md"
placeholder="Select permission"
value={selectedPermission}
onChange={(value) =>
handlePermissionChange(value as SelectOption)
}
/>
)
) : (
<div className="flex justify-end gap-2">
<div>
<Chip
value="Pending"
variant="outlined"
color="orange"
size="sm"
icon={'^'}
/>
<Tag type="positive" size="sm" leftIcon={<LoaderIcon />}>
Pending
</Tag>
</div>
<div>
<IconButton
size="sm"
className="rounded-full"
<Button
size="md"
iconOnly
onClick={() => {
setRemoveMemberDialogOpen((prevVal) => !prevVal);
}}
placeholder={''}
>
D
</IconButton>
</Button>
</div>
</div>
)}
@@ -0,0 +1,35 @@
import { PropsWithChildren } from 'react';
import { ProjectSettingHeader } from './ProjectSettingHeader';
export interface ProjectSettingContainerProps extends PropsWithChildren {
headingText: string;
className?: string;
button?: React.ReactNode;
badge?: React.ReactNode;
}
const ProjectSettingContainer: React.FC<ProjectSettingContainerProps> = ({
headingText,
className,
button,
children,
badge,
...props
}: ProjectSettingContainerProps) => {
return (
<div
className={'flex-col justify-start gap-8 space-y-3 px-2 pb-6'}
{...props}
>
<ProjectSettingHeader
headingText={headingText}
button={button}
badge={badge}
/>
{children}
</div>
);
};
export { ProjectSettingContainer };
@@ -0,0 +1,30 @@
import { PropsWithChildren } from 'react';
import { Heading } from 'components/shared/Heading';
export interface ProjectSettingHeaderProps extends PropsWithChildren {
headingText: string;
button?: React.ReactNode;
badge?: React.ReactNode;
}
const ProjectSettingHeader: React.FC<ProjectSettingHeaderProps> = ({
headingText,
button,
badge,
...props
}) => {
return (
<div className="flex justify-between items-center" {...props}>
<div className="flex space-x-2 items-center">
<Heading className="text-lg font-medium leading-normal">
{headingText}
</Heading>
{badge}
</div>
{button ?? button}
</div>
);
};
export { ProjectSettingHeader };
@@ -1,9 +1,10 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { Button, Typography } from '@material-tailwind/react';
import { Typography } from '@snowballtools/material-tailwind-react-fork';
import { GitRepositoryDetails } from 'types';
import { GitRepositoryDetails } from '../../../../types';
import { DisconnectRepositoryDialog } from 'components/projects/Dialog/DisconnectRepositoryDialog';
import { Button } from 'components/shared/Button';
const RepoConnectedSection = ({
linkedRepo,
@@ -17,21 +18,12 @@ const RepoConnectedSection = ({
<div className="flex gap-4">
<div>^</div>
<div className="grow">
<Typography variant="small" placeholder={''}>
{linkedRepo.full_name}
</Typography>
<Typography variant="small" placeholder={''}>
Connected just now
</Typography>
<Typography variant="small">{linkedRepo.full_name}</Typography>
<Typography variant="small">Connected just now</Typography>
</div>
<div>
<Button
onClick={() => setDisconnectRepoDialogOpen(true)}
variant="outlined"
size="sm"
placeholder={''}
>
^ Disconnect
<Button onClick={() => setDisconnectRepoDialogOpen(true)} size="sm">
Disconnect
</Button>
</div>
<DisconnectRepositoryDialog

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