Compare commits

...
Author SHA1 Message Date
Eric Lewis 9f25059c3c feat: see all button functions 2024-04-09 14:34:04 -04:00
Eric Lewis f38dfb5604 chore: bump deploy 2024-04-04 10:50:39 -04:00
nabarunandneeraj 351db16336 Upgrade laconic-sdk package for showing error with reason in console (#165)
* Upgrade laconic sdk version

* Update toast message for add project

* Log error messages

* Update bond id in readme

---------

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

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

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

* Move reference up

* Remove steps to manually update record values

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

* Add comment for filtering AppDeploymentRecord

* Fix lint

* Handle delete gitHub event

* Add sleep before registry write methods

---------

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

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

* fix

* repurpose existing script

* clean up

* fix

* fix

* Update latest record version

---------

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

* ️ feat: make the whole card clickable

* 🎨 style: adjust hovered background for wavy border

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

* 🎨 style: add transition when hover

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

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

* ♻️ refactor: org slug dashboard layout

* ♻️ refactor: create project layout and restyling it

* ♻️ refactor: remove unused style

* ️ feat: restyling template card

* ️ feat: create template icon component

* ️ feat: use `h2` for layout title

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

* ♻️ refactor: WavyBorder component and update CreateProjectLayout

* 🎨 style: update button medium size padding

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

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

* refactor: update wavy border svg

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

---------

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

* feat: add sepeator calc in comment

* chore: limit commit message to 4 line

* comment fixes

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

* ♻️ refactor: move sidebar inside shared components

* ️ feat: add polymorphic prop type for heading component

* 🎨 style: re-styling project list page

* ♻️ refactor: remove `.env`

* 🎨 style: set default font weight to normal for heading component
2024-02-28 12:50:30 +07:00
Andre H 89306ddcc7 🔧 chore: add toast 2024-02-28 11:19:09 +07:00
Andre H 3b67396c43 ️ feat: reskin connect github 2024-02-28 10:55:10 +07:00
Andre H 3535bd3a58 🔧 chore: add GitTeaIcon 2024-02-28 10:53:05 +07:00
185 changed files with 6391 additions and 2126 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
{
// IntelliSense for taiwind variants
"tailwindCSS.experimental.classRegex": [
["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
"tv\\('([^)]*)\\')",
"(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"
]
}
+201 -238
View File
@@ -6,9 +6,10 @@
```bash
git clone git@github.com:snowball-tools/snowballtools-base.git
cd snowballtools-base
```
- In root of the repo, install depedencies
- Install dependencies in root
```bash
yarn
@@ -20,250 +21,212 @@
yarn build --ignore frontend
```
## Backend
- Change directory to `packages/backend`
```bash
cd packages/backend
```
- Rename backend config file from [environments/local.toml.example](packages/backend/environments/local.toml.example) to `local.toml`
```bash
mv environments/local.toml.example environments/local.toml
```
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
- In "Homepage URL", type `http://localhost:3000`
- In "Authorization callback URL", type `http://localhost:3000/organization/projects/create`
- Generate a new client secret after app is created
### Backend Production
- Let us assume the following domains for backend and frontend
Let us assume the following domains for backend and frontend
- Backend server: `api.snowballtools.com`
- Frontend app: `dashboard.snowballtools.com`
- Update the following in backend [config file](packages/backend/environments/local.toml)
- Configuration Files
- For Backend:
```toml
[server]
...
[server.session]
# Secret should be changed to a different random string
secret = "p4yfpkqnddkui2iw7t6hbhwq74lbqs7sidnc382"
# Set URL of the frontend app
appOriginUrl = "https://dashboard.snowballtools.com"
# Set to true for session cookies to work behind proxy
trustProxy = true
# Set empty domain when using secure connection
domain = ""
```bash
cp packages/backend/environments/local.toml.example packages/backend/environments/local.toml
```
- Production
- Update the following in backend [config file](packages/backend/environments/local.toml)
```toml
[server]
...
[server.session]
# Secret should be changed to a different random string
secret = "p4yfpkqnddkui2iw7t6hbhwq74lbqs7sidnc382"
# Set URL of the frontend app
appOriginUrl = "https://dashboard.snowballtools.com"
# Set to true for session cookies to work behind proxy
trustProxy = true
# Set empty domain when using secure connection
domain = ""
```
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- [OAuth App Creation](https://github.com/organizations/<org>/settings/applications/new)
- Homepage URL: `https://dashboard.snowballtools.com`
- Authorization callback URL: `https://dashboard.snowballtools.com/organization/projects/create`
- Generate a new client secret after app is created
- Set `gitHub.webhookUrl` in backend [config file](packages/backend/environments/local.toml)
```toml
[gitHub]
webhookUrl = "https://api.snowballtools.com"
```
- Let us assume domain for Laconicd to be `api.laconic.com` and set the following in backend [config file](packages/backend/environments/local.toml)
```toml
[registryConfig]
fetchDeploymentRecordDelay = 5000
# Use actual port for REST endpoint
restEndpoint = "http://api.laconic.com:1317"
# Use actual port for GQL endpoint
gqlEndpoint = "http://api.laconic.com:9473/api"
# Set private key of account to be used in Laconicd
privateKey = "0wtu92cd4f1y791ezpjwgzzazni4dmd3q3mzqc3t6i6r9v06ji784tey6hwmnn69"
# Set Bond ID to be used for publishing records
bondId = "8xk8c2pb61kajwixpm223zvptr2x2ncajq0vd998p6aqhvqqep2reu6pik245epf"
chainId = "laconic_9000-1"
# Set authority that is existing in the chain
authority = "laconic"
[registryConfig.fee]
amount = "200000"
denom = "aphoton"
gas = "750000"
```
- Development
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- [OAuth App Creation](https://github.com/organizations/<org>/settings/applications/new)
- Homepage URL: `http://localhost:3000`
- Authorization callback URL: `http://localhost:3000/organization/projects/create`
- Generate a new client secret after app is created
- Setup Laconicd
- Run the laconicd stack following this [doc](https://git.vdb.to/cerc-io/stack-orchestrator/src/branch/main/docs/laconicd-with-console.md)
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
```
- Get the REST and GQL endpoint ports of Laconicd and replace the ports for `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
```bash
# For registryConfig.restEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
# 0.0.0.0:32777
# For registryConfig.gqlEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
# 0.0.0.0:32771
```
- Set authority in `registryConfig.authority` in backend [config file](packages/backend/environments/local.toml)
- Run the script to create bond, reserve the authority and set authority bond
```bash
yarn test:registry:init
# snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms
```
- Get the bond id and set `registryConfig.bondId` in backend [config file](packages/backend/environments/local.toml)
- Setup ngrok for GitHub webhooks
- [ngrok getting started](https://ngrok.com/docs/getting-started/)
- Start ngrok and point to backend server endpoint
```bash
ngrok http http://localhost:8000
```
- Look for the forwarding URL in ngrok
```bash
Forwarding https://19c1-61-95-158-116.ngrok-free.app -> http://localhost:8000
```
- Set `gitHub.webhookUrl` in backend [config file](packages/backend/environments/local.toml)
```toml
[gitHub]
webhookUrl = "https://19c1-61-95-158-116.ngrok-free.app"
```
- For Frontend:
```bash
cp packages/frontend/.env.example packages/frontend/.env
```
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
```
- Set `REACT_APP_GITHUB_PWA_TEMPLATE_REPO` and `REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
```
- Production
- Set the following values in [.env](packages/frontend/.env) file
```env
REACT_APP_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Set project home page URL to `https://dashboard.snowballtools.com`
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <PROJECT_ID>
```
- Build the React application
```bash
yarn build
```
- Use a web server for hosting static built files
```bash
python3 -m http.server -d build 3000
```
- Development
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
```env
REACT_APP_SERVER_URL = 'http://localhost:8000'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID.
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Project home page URL is not required to be set
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <Project_ID>
```
- The React application will be running in `http://localhost:3000/`
## Development
- Start the backend server
```bash
yarn start:backend
```
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
- In "Homepage URL", type `https://dashboard.snowballtools.com`
- In "Authorization callback URL", type `https://dashboard.snowballtools.com/organization/projects/create`
- Generate a new client secret after app is created
- Set `gitHub.webhookUrl` in backend [config file](packages/backend/environments/local.toml)
```toml
...
[gitHub]
webhookUrl = "https://api.snowballtools.com"
...
```
- Let us assume domain for Laconicd to be `api.laconic.com` and set the following in backend [config file](packages/backend/environments/local.toml)
```toml
...
[registryConfig]
fetchDeploymentRecordDelay = 5000
# Use actual port for REST endpoint
restEndpoint = "http://api.laconic.com:1317"
# Use actual port for GQL endpoint
gqlEndpoint = "http://api.laconic.com:9473/api"
# Set private key of account to be used in Laconicd
privateKey = "0wtu92cd4f1y791ezpjwgzzazni4dmd3q3mzqc3t6i6r9v06ji784tey6hwmnn69"
# Set Bond ID to be used for publishing records
bondId = "8xk8c2pb61kajwixpm223zvptr2x2ncajq0vd998p6aqhvqqep2reu6pik245epf"
chainId = "laconic_9000-1"
# Set authority that is existing in the chain
authority = "laconic"
[registryConfig.fee]
amount = "200000"
denom = "aphoton"
gas = "750000"
...
```
- Start the server in `packages/backend`
- Start the frontend
```bash
yarn start
```
### Backend Development
- Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml)
- Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
- In "Homepage URL", type `http://localhost:3000`
- In "Authorization callback URL", type `http://localhost:3000/organization/projects/create`
- Generate a new client secret after app is created
- Setup Laconicd
- Run the laconicd stack following this [doc](https://git.vdb.to/cerc-io/stack-orchestrator/src/branch/main/docs/laconicd-with-console.md)
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
```
- Get the REST and GQL endpoint ports of Laconicd and replace the ports for `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
```bash
# For registryConfig.restEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
# 0.0.0.0:32777
# For registryConfig.gqlEndpoint
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
# 0.0.0.0:32771
```
- Set authority in `registryConfig.authority` in backend [config file](packages/backend/environments/local.toml)
- Run the script to create bond, reserve the authority and set authority bond
```bash
yarn test:registry:init
# snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms
```
- Get the bond id and set `registryConfig.bondId` in backend [config file](packages/backend/environments/local.toml)
- Setup ngrok for GitHub webhooks
- [ngrok getting started](https://ngrok.com/docs/getting-started/)
- Start ngrok and point to backend server endpoint
```bash
ngrok http http://localhost:8000
```
- Look for the forwarding URL in ngrok
```bash
...
Forwarding https://19c1-61-95-158-116.ngrok-free.app -> http://localhost:8000
...
```
- Set `gitHub.webhookUrl` in backend [config file](packages/backend/environments/local.toml)
```toml
...
[gitHub]
webhookUrl = "https://19c1-61-95-158-116.ngrok-free.app"
...
```
- Start the server in `packages/backend`
```bash
yarn start:dev
```
## Frontend
- Change directory to `packages/frontend` in a new terminal
```bash
cd packages/frontend
```
- Rename [.env.example](packages/frontend/.env.example) to `.env`
```bash
mv .env.example .env
```
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
```env
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
```
- Set `REACT_APP_GITHUB_PWA_TEMPLATE_REPO` and `REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
```env
# Set actual owner/name of the template repo that will be used for creating new repo
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = cerc-io/test-progressive-web-app
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = cerc-io/image-upload-pwa-example
```
### Frontend Production
- Let us assume the following domains for backend and frontend
- Backend server: `api.snowballtools.com`
- Frontend app: `dashboard.snowballtools.com`
- Set the following values in [.env](packages/frontend/.env) file
```env
# Backend server endpoint
REACT_APP_SERVER_URL = 'https://api.snowballtools.com'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Set project home page URL to `https://dashboard.snowballtools.com`
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <PROJECT_ID>
```
- Build the React application
```bash
yarn build
```
- Use a web server for hosting static built files
```bash
python3 -m http.server -d build 3000
```
### Frontend Development
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
```env
REACT_APP_SERVER_URL = 'http://localhost:8000'
```
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID.
- Create a project and add information to use wallet connect SDK
- Add project name and select project type as `App`
- Project home page URL is not required to be set
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
```env
REACT_APP_WALLET_CONNECT_ID = <Project_ID>
```
- Start the React application
```bash
yarn start
```
- The React application will be running in `http://localhost:3000/`
-8
View File
@@ -9,14 +9,6 @@ if [[ -d "$DEST_DIR" ]]; then
exit 1
fi
if [[ -f "$PKG_DIR/.env" ]]; then
echo "Using existing .env file"
else
mv "$PKG_DIR/.env.example" "$PKG_DIR/.env"
echo "Created .env file. Please populate with the correct values."
exit 1
fi
cat > $PKG_DIR/.env <<EOF
REACT_APP_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_server_url'
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid'
+1 -1
View File
@@ -3,7 +3,7 @@
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@cerc-io/laconic-sdk": "^0.1.14",
"@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",
+14 -5
View File
@@ -12,12 +12,14 @@ import {
ApplicationDeploymentRequest
} from './entity/Deployment';
import { AppDeploymentRecord, PackageJSON } from './types';
import { sleep } from './utils';
const log = debug('snowball:registry');
const APP_RECORD_TYPE = 'ApplicationRecord';
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
const SLEEP_DURATION = 1000;
// TODO: Move registry code to laconic-sdk/watcher-ts
export class Registry {
@@ -111,16 +113,21 @@ export class Registry {
const crn = this.getCrn(appName);
log(`Setting name: ${crn} for record ID: ${result.data.id}`);
await sleep(SLEEP_DURATION);
await this.registry.setName(
{ cid: result.data.id, crn },
this.registryConfig.privateKey,
this.registryConfig.fee
);
await sleep(SLEEP_DURATION);
await this.registry.setName(
{ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` },
this.registryConfig.privateKey,
this.registryConfig.fee
);
await sleep(SLEEP_DURATION);
await this.registry.setName(
{
cid: result.data.id,
@@ -139,9 +146,9 @@ export class Registry {
async createApplicationDeploymentRequest (data: {
deployment: Deployment,
appName: string,
packageJsonName: string,
repository: string,
environmentVariables: { [key: string]: string }
environmentVariables: { [key: string]: string },
dns: string,
}): Promise<{
applicationDeploymentRequestId: string;
applicationDeploymentRequestData: ApplicationDeploymentRequest;
@@ -160,7 +167,7 @@ export class Registry {
version: '1.0.0',
name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`,
application: `${crn}@${applicationRecord.attributes.app_version}`,
dns: `${data.deployment.project.name}-${data.deployment.id}`,
dns: data.dns,
// TODO: Not set in test-progressive-web-app CI
// deployment: '$CERC_REGISTRY_DEPLOYMENT_CRN',
@@ -178,6 +185,7 @@ export class Registry {
})
};
await sleep(SLEEP_DURATION);
const result = await this.registry.setRecord(
{
privateKey: this.registryConfig.privateKey,
@@ -211,11 +219,12 @@ export class Registry {
true
);
// Filter records with ApplicationRecord ids
// Filter records with ApplicationRecord ID and Deployment specific URL
return records.filter((record: AppDeploymentRecord) =>
deployments.some(
(deployment) =>
deployment.applicationRecordId === record.attributes.application
deployment.applicationRecordId === record.attributes.application &&
record.attributes.url.includes(deployment.id)
)
);
}
+29 -20
View File
@@ -382,8 +382,7 @@ export class Service {
async createDeployment (
userId: string,
octokit: Octokit,
data: DeepPartial<Deployment>,
recordData: { repoUrl?: string } = {}
data: DeepPartial<Deployment>
): Promise<Deployment> {
assert(data.project?.repository, 'Project repository not found');
log(
@@ -407,13 +406,10 @@ export class Service {
assert(packageJSON.name, "name field doesn't exist in package.json");
if (!recordData.repoUrl) {
const { data: repoDetails } = await octokit.rest.repos.get({
owner,
repo
});
recordData.repoUrl = repoDetails.html_url;
}
const repoUrl = (await octokit.rest.repos.get({
owner,
repo
})).data.html_url;
// TODO: Set environment variables for each deployment (environment variables can`t be set in application record)
const { applicationRecordId, applicationRecordData } =
@@ -422,7 +418,7 @@ export class Service {
packageJSON,
appType: data.project!.template!,
commitHash: data.commitHash!,
repoUrl: recordData.repoUrl
repoUrl
});
// Update previous deployment with prod branch domain
@@ -464,11 +460,23 @@ export class Service {
{
deployment: newDeployment,
appName: repo,
packageJsonName: packageJSON.name,
repository: recordData.repoUrl,
environmentVariables: environmentVariablesObj
repository: repoUrl,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}-${newDeployment.id}`
});
// To set project DNS
if (data.environment === Environment.Production) {
await this.registry.createApplicationDeploymentRequest(
{
deployment: newDeployment,
appName: repo,
repository: repoUrl,
environmentVariables: environmentVariablesObj,
dns: `${newDeployment.project.name}`
});
}
await this.db.updateDeploymentById(newDeployment.id, { applicationDeploymentRequestId, applicationDeploymentRequestData });
return newDeployment;
@@ -498,8 +506,6 @@ export class Service {
per_page: 1
});
const { data: repoDetails } = await octokit.rest.repos.get({ owner, repo });
// Create deployment with prod branch and latest commit
await this.createDeployment(user.id,
octokit,
@@ -510,9 +516,6 @@ export class Service {
domain: null,
commitHash: latestCommit.sha,
commitMessage: latestCommit.commit.message
},
{
repoUrl: repoDetails.html_url
}
);
@@ -555,8 +558,14 @@ export class Service {
}
async handleGitHubPush (data: GitPushEventPayload): Promise<void> {
const { repository, ref, head_commit: headCommit } = data;
log(`Handling GitHub push event from repository: ${repository.full_name}`);
const { repository, ref, head_commit: headCommit, deleted } = data;
if (deleted) {
log(`Branch ${ref} deleted for project ${repository.full_name}`);
return;
}
log(`Handling GitHub push event from repository: ${repository.full_name}, branch: ${ref}`);
const projects = await this.db.getProjects({
where: { repository: repository.full_name }
});
+1
View File
@@ -24,6 +24,7 @@ export interface GitPushEventPayload {
id: string;
message: string;
};
deleted: boolean;
}
export interface AppDeploymentRecordAttributes {
+2
View File
@@ -66,3 +66,5 @@ export const loadAndSaveData = async <Entity extends ObjectLiteral>(
return savedEntity;
};
export const sleep = async (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
+4 -55
View File
@@ -8,57 +8,6 @@
brew install jq # if you do not have jq installed already
```
Example of how to make the necessary deploy edits [here](https://github.com/snowball-tools/snowballtools-base/pull/131/files).
- Replace variables in the following files
- [records/application-deployment-request.yml](records/application-deployment-request.yml)
- update the name & application version numbers
- `<CURRENT_DATE_TIME>`: Replace with current time which can be generated by command `date -u`
```yml
# Example
record:
...
meta:
note: Added by Snowball @ Friday 23 February 2024 06:35:50 AM UTC
...
```
- Update record version in [records/application-record.yml](records/application-record.yml)
```yml
record:
type: ApplicationRecord
version: <NEW_VERSION>
...
```
- Update commit hash in the following places:
- [records/application-record.yml](records/application-record.yml)
```yml
record:
...
repository_ref: <COMMIT_HASH>
...
```
- [records/application-deployment-request.yml](records/application-deployment-request.yml)
```yml
record:
...
meta:
...
repository_ref: <COMMIT_HASH>
```
- [deploy-frontend.sh](deploy-frontend.sh)
Also be sure to update the app version
```bash
...
RCD_APP_VERSION="<NEW_VERSION>"
REPO_REF="<COMMIT_HASH>"
...
```
- Run script to deploy app
```
./deploy-frontend.sh
@@ -81,8 +30,8 @@ Example of how to make the necessary deploy edits [here](https://github.com/snow
## Troubleshoot
- Check deployment status [here](https://console.laconic.com/deployer).
- Check records [here](https://console.laconic.com/#/registry).
- Check deployment status in [web-app deployer](https://console.laconic.com/deployer).
- Check records in [registry console app](https://console.laconic.com/#/registry).
- If deployment fails due to low bond balance
- Check balances
@@ -91,9 +40,9 @@ Example of how to make the necessary deploy edits [here](https://github.com/snow
yarn laconic cns account get
# Bond balance
yarn laconic cns bond get --id 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac
yarn laconic cns bond get --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
```
- Command to refill bond
```bash
yarn laconic cns bond refill --id 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac --type aphoton --quantity 10000000
yarn laconic cns bond refill --id 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32 --type aphoton --quantity 10000000
```
+3 -3
View File
@@ -2,8 +2,8 @@ services:
cns:
restEndpoint: http://console.laconic.com:1317
gqlEndpoint: http://console.laconic.com:9473/api
userKey: 489c9dd3931c2a2d4dd77973302dc5eb01e2a49552f9d932c58d9da823512311
bondId: 99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32
chainId: laconic_9000-1
gas: 1000000
gas: 1200000
fees: 200000aphoton
userKey: 0524fc22ea0a12e6c5cc4cfe08e73c95dffd0ab5ed72a59f459ed33134fa3b16
bondId: 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac
+65 -5
View File
@@ -1,11 +1,67 @@
#!/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.yml
REGISTRY_BOND_ID="99c0e9aec0ac1b8187faa579be3b54f93fafb6060ac1fd29170b860df605be32"
# 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 "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 > ./records/application-deployment-request.yml <<EOF
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: snowballtools-base-frontend@$PACKAGE_VERSION
application: crn://snowballtools/applications/snowballtools-base-frontend@$PACKAGE_VERSION
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
meta:
note: Added by Snowball @ $CURRENT_DATE_TIME
repository: "$REPO_URL"
repository_ref: $LATEST_HASH
EOF
# Generate application-record.yml with incremented version
cat > ./records/application-record.yml <<EOF
record:
type: ApplicationRecord
version: $NEW_APPLICATION_VERSION
repository_ref: $LATEST_HASH
repository: ["$REPO_URL"]
app_type: webapp
name: snowballtools-base-frontend
app_version: $PACKAGE_VERSION
EOF
echo "Files generated successfully."
RECORD_FILE=records/application-record.yml
CONFIG_FILE=config.yml
RCD_APP_VERSION="0.1.3"
REPO_REF="513ca69d01bee857cf207a0605483205b384e218"
# Publish ApplicationRecord
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
@@ -15,8 +71,11 @@ echo $RECORD_ID
# Set name to record
REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${RCD_APP_VERSION}" "$RECORD_ID"
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${REPO_REF}" "$RECORD_ID"
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"
@@ -30,6 +89,7 @@ fi
RECORD_FILE=records/application-deployment-request.yml
sleep 2
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
echo "ApplicationDeploymentRequest published"
echo $DEPLOYMENT_REQUEST_ID
@@ -1,21 +1,18 @@
record:
type: ApplicationDeploymentRequest
version: '1.0.0'
name: snowballtools-base-frontend@0.1.3
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.3
name: snowballtools-base-frontend@0.1.8
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.8
dns: dashboard
config:
env:
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
# If GitHub client ID is changed, same ID and corresponding secret has to be set in backend config
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
# New config env after changes for image upload PWA
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
meta:
# Set CURRENT_DATE_TIME; Use command date -u
note: Added by Snowball @ Tue Feb 27 17:24:06 UTC 2024
note: Added by Snowball @ Thu Apr 4 14:49:41 UTC 2024
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
@@ -1,10 +1,8 @@
record:
type: ApplicationRecord
version: 0.0.11
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
version: 0.0.2
repository_ref: 351db16336eacc3e1f9119ceb8d1282b8e27a27e
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
app_type: webapp
# name is set to repo name
name: snowballtools-base-frontend
# app_version is set from package.json
app_version: 0.1.3
app_version: 0.1.8
-7
View File
@@ -1,7 +0,0 @@
REACT_APP_SERVER_URL = 'http://localhost:8000'
REACT_APP_GITHUB_CLIENT_ID =
REACT_APP_GITHUB_PWA_TEMPLATE_REPO =
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO =
REACT_APP_WALLET_CONNECT_ID =
+5 -2
View File
@@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "0.1.3",
"version": "0.1.8",
"private": true,
"dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.0.19",
@@ -8,6 +8,7 @@
"@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",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-switch": "^1.0.3",
@@ -30,7 +31,9 @@
"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",
"luxon": "^3.4.4",
"octokit": "^3.1.2",
"react": "^18.2.0",
@@ -48,7 +51,7 @@
"siwe": "^2.1.4",
"tailwind-variants": "^0.2.0",
"typescript": "^4.9.5",
"usehooks-ts": "^2.10.0",
"usehooks-ts": "^2.15.1",
"vertical-stepper-nav": "^1.0.2",
"viem": "^2.7.11",
"wagmi": "^2.5.7",
File diff suppressed because one or more lines are too long
-10
View File
@@ -1,10 +0,0 @@
<svg width="333" height="5" viewBox="0 0 333 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 4L6.39555 1.30714C8.38078 0.47125 10.6192 0.47125 12.6045 1.30714L15.8955 2.69286C17.8808 3.52875 20.1192 3.52875 22.1045 2.69286L25.3955 1.30714C27.3808 0.47125 29.6192 0.47125 31.6045 1.30714L34.8955 2.69286C36.8808 3.52875 39.1192 3.52875 41.1045 2.69286L44.3955 1.30714C46.3808 0.47125 48.6192 0.47125 50.6045 1.30714L53.8955 2.69286C55.8808 3.52875 58.1192 3.52875 60.1045 2.69286L63.3955 1.30714C65.3808 0.47125 67.6192 0.47125 69.6045 1.30714L72.8955 2.69286C74.8808 3.52875 77.1192 3.52875 79.1045 2.69286L82.3955 1.30714C84.3808 0.47125 86.6192 0.47125 88.6045 1.30714L91.8955 2.69286C93.8808 3.52875 96.1192 3.52875 98.1045 2.69286L101.396 1.30714C103.381 0.47125 105.619 0.47125 107.604 1.30714L110.896 2.69286C112.881 3.52875 115.119 3.52875 117.104 2.69286L120.396 1.30714C122.381 0.47125 124.619 0.47125 126.604 1.30714L129.896 2.69286C131.881 3.52875 134.119 3.52875 136.104 2.69286L139.396 1.30714C141.381 0.47125 143.619 0.47125 145.604 1.30714L148.896 2.69286C150.881 3.52875 153.119 3.52875 155.104 2.69286L158.396 1.30714C160.381 0.47125 162.619 0.47125 164.604 1.30714L167.896 2.69286C169.881 3.52875 172.119 3.52875 174.104 2.69286L177.396 1.30714C179.381 0.47125 181.619 0.47125 183.604 1.30714L186.896 2.69286C188.881 3.52875 191.119 3.52875 193.104 2.69286L196.396 1.30714C198.381 0.47125 200.619 0.47125 202.604 1.30714L205.896 2.69286C207.881 3.52875 210.119 3.52875 212.104 2.69286L215.396 1.30714C217.381 0.47125 219.619 0.47125 221.604 1.30714L224.896 2.69286C226.881 3.52875 229.119 3.52875 231.104 2.69286L234.396 1.30714C236.381 0.47125 238.619 0.47125 240.604 1.30714L243.896 2.69286C245.881 3.52875 248.119 3.52875 250.104 2.69286L253.396 1.30714C255.381 0.47125 257.619 0.47125 259.604 1.30714L262.896 2.69286C264.881 3.52875 267.119 3.52875 269.104 2.69286L272.396 1.30714C274.381 0.47125 276.619 0.47125 278.604 1.30714L281.896 2.69286C283.881 3.52875 286.119 3.52875 288.104 2.69286L291.396 1.30714C293.381 0.47125 295.619 0.47125 297.604 1.30714L300.896 2.69286C302.881 3.52875 305.119 3.52875 307.104 2.69286L310.396 1.30714C312.381 0.47125 314.619 0.47125 316.604 1.30714L319.973 2.72566C321.913 3.54216 324.095 3.56183 326.049 2.78039L330.029 1.18845C331.936 0.425535 334.064 0.425535 335.971 1.18845L343 4" stroke="#DBEBF9"/>
<path d="M6.39555 1.30714L0 4H342.5L336.027 1.27434C334.087 0.457837 331.905 0.438174 329.951 1.21961L326.049 2.78039C324.095 3.56183 321.913 3.54216 319.973 2.72566L316.604 1.30714C314.619 0.47125 312.381 0.47125 310.396 1.30714L307.104 2.69286C305.119 3.52875 302.881 3.52875 300.896 2.69286L297.604 1.30714C295.619 0.47125 293.381 0.47125 291.396 1.30714L288.104 2.69286C286.119 3.52875 283.881 3.52875 281.896 2.69286L278.604 1.30714C276.619 0.47125 274.381 0.47125 272.396 1.30714L269.104 2.69286C267.119 3.52875 264.881 3.52875 262.896 2.69286L259.604 1.30714C257.619 0.47125 255.381 0.47125 253.396 1.30714L250.104 2.69286C248.119 3.52875 245.881 3.52875 243.896 2.69286L240.604 1.30714C238.619 0.47125 236.381 0.47125 234.396 1.30714L231.104 2.69286C229.119 3.52875 226.881 3.52875 224.896 2.69286L221.604 1.30714C219.619 0.47125 217.381 0.47125 215.396 1.30714L212.104 2.69286C210.119 3.52875 207.881 3.52875 205.896 2.69286L202.604 1.30714C200.619 0.47125 198.381 0.47125 196.396 1.30714L193.104 2.69286C191.119 3.52875 188.881 3.52875 186.896 2.69286L183.604 1.30714C181.619 0.47125 179.381 0.47125 177.396 1.30714L174.104 2.69286C172.119 3.52875 169.881 3.52875 167.896 2.69286L164.604 1.30714C162.619 0.47125 160.381 0.47125 158.396 1.30714L155.104 2.69286C153.119 3.52875 150.881 3.52875 148.896 2.69286L145.604 1.30714C143.619 0.47125 141.381 0.47125 139.396 1.30714L136.104 2.69286C134.119 3.52875 131.881 3.52875 129.896 2.69286L126.604 1.30714C124.619 0.47125 122.381 0.47125 120.396 1.30714L117.104 2.69286C115.119 3.52875 112.881 3.52875 110.896 2.69286L107.604 1.30714C105.619 0.47125 103.381 0.47125 101.396 1.30714L98.1045 2.69286C96.1192 3.52875 93.8808 3.52875 91.8955 2.69286L88.6045 1.30714C86.6192 0.47125 84.3808 0.47125 82.3955 1.30714L79.1045 2.69286C77.1192 3.52875 74.8808 3.52875 72.8955 2.69286L69.6045 1.30714C67.6192 0.47125 65.3808 0.47125 63.3955 1.30714L60.1045 2.69286C58.1192 3.52875 55.8808 3.52875 53.8955 2.69286L50.6045 1.30714C48.6192 0.47125 46.3808 0.47125 44.3955 1.30714L41.1045 2.69286C39.1192 3.52875 36.8808 3.52875 34.8955 2.69286L31.6045 1.30714C29.6192 0.47125 27.3808 0.47125 25.3955 1.30714L22.1045 2.69286C20.1192 3.52875 17.8808 3.52875 15.8955 2.69286L12.6045 1.30714C10.6192 0.47125 8.38078 0.47125 6.39555 1.30714Z" fill="url(#paint0_linear_1729_11298)"/>
<defs>
<linearGradient id="paint0_linear_1729_11298" x1="171.25" y1="0" x2="171.25" y2="4" gradientUnits="userSpaceOnUse">
<stop stop-color="#E6F4FF"/>
<stop offset="1" stop-color="#F9FCFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

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

After

Width:  |  Height:  |  Size: 1.1 KiB

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

After

Width:  |  Height:  |  Size: 1.5 KiB

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

After

Width:  |  Height:  |  Size: 507 B

+2 -2
View File
@@ -1,7 +1,6 @@
import React from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import OrgSlug from './pages/OrgSlug';
import Projects from './pages/org-slug';
import Settings from './pages/org-slug/Settings';
import {
@@ -11,11 +10,12 @@ import {
import ProjectSearchLayout from './layouts/ProjectSearch';
import Index from './pages';
import Login from './pages/Login';
import { DashboardLayout } from 'pages/org-slug/layout';
const router = createBrowserRouter([
{
path: ':orgSlug',
element: <OrgSlug />,
element: <DashboardLayout />,
children: [
{
element: <ProjectSearchLayout />,
+10 -5
View File
@@ -2,31 +2,36 @@ export default [
{
id: '1',
name: 'Progressive Web App (PWA)',
icon: '^',
icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
id: '2',
name: 'Image Upload PWA',
icon: '^',
icon: 'pwa',
repoFullName: `${process.env.REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO}`,
isComingSoon: false,
},
{
id: '3',
name: 'Kotlin',
icon: '^',
icon: 'kotlin',
repoFullName: '',
isComingSoon: true,
},
{
id: '4',
name: 'React Native',
icon: '^',
icon: 'react-native',
repoFullName: '',
isComingSoon: true,
},
{
id: '5',
name: 'Swift',
icon: '^',
icon: 'swift',
repoFullName: '',
isComingSoon: true,
},
];
@@ -1,13 +1,22 @@
import { Duration } from 'luxon';
import React from 'react';
import React, { ComponentPropsWithoutRef } from 'react';
import { cn } from 'utils/classnames';
const FormatMillisecond = ({ time }: { time: number }) => {
export interface FormatMilliSecondProps
extends ComponentPropsWithoutRef<'div'> {
time: number;
}
const FormatMillisecond = ({ time, ...props }: FormatMilliSecondProps) => {
const formatTime = Duration.fromMillis(time)
.shiftTo('days', 'hours', 'minutes', 'seconds')
.toObject();
return (
<div>
<div
{...props}
className={cn('text-sm text-elements-mid-em', props?.className)}
>
{formatTime.days !== 0 && <span>{formatTime.days}d&nbsp;</span>}
{formatTime.hours !== 0 && <span>{formatTime.hours}h&nbsp;</span>}
{formatTime.minutes !== 0 && <span>{formatTime.minutes}m&nbsp;</span>}
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Heading } from './shared/Heading';
interface LogoProps {
orgSlug?: string;
}
export const Logo = ({ orgSlug }: LogoProps) => {
return (
<Link to={`/${orgSlug}`}>
<div className="flex items-center gap-3 px-0 lg:px-2">
<img
src="/logo.svg"
alt="Snowball Logo"
className="lg:h-10 lg:w-10 h-8 w-8 rounded-lg"
/>
<Heading className="lg:text-[24px] text-[19px] font-semibold">
Snowball
</Heading>
</div>
</Link>
);
};
@@ -15,7 +15,8 @@ const SearchBar: React.ForwardRefRenderFunction<
value={value}
type="search"
placeholder={placeholder}
appearance={'borderless'}
appearance="borderless"
className="w-full lg:w-[459px]"
{...props}
/>
</div>
@@ -1,143 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Link, NavLink, useNavigate, useParams } from 'react-router-dom';
import { Organization } from 'gql-client';
import { Option } from '@material-tailwind/react';
import { useDisconnect } from 'wagmi';
import { useGQLClient } from '../context/GQLClientContext';
import AsyncSelect from './shared/AsyncSelect';
import {
ChevronGrabberHorizontal,
FolderIcon,
GlobeIcon,
LifeBuoyIcon,
QuestionMarkRoundIcon,
SettingsSlidersIcon,
} from './shared/CustomIcon';
import { Tabs } from 'components/shared/Tabs';
const Sidebar = () => {
const { orgSlug } = useParams();
const navigate = useNavigate();
const client = useGQLClient();
const { disconnect } = useDisconnect();
const [selectedOrgSlug, setSelectedOrgSlug] = useState(orgSlug);
const [organizations, setOrganizations] = useState<Organization[]>([]);
const fetchUserOrganizations = useCallback(async () => {
const { organizations } = await client.getOrganizations();
setOrganizations(organizations);
}, [orgSlug]);
useEffect(() => {
fetchUserOrganizations();
setSelectedOrgSlug(orgSlug);
}, [orgSlug]);
const handleLogOut = useCallback(() => {
disconnect();
navigate('/login');
}, [disconnect, navigate]);
return (
<div className="flex flex-col h-full p-4 pt-10">
<div className="grow">
<Link to={`/${orgSlug}`}>
<div className="flex items-center space-x-3 mb-10 ml-2">
<img
src="/logo.svg"
alt="Snowball Logo"
className="h-8 w-8 rounded-lg"
/>
<span className="text-2xl font-bold text-snowball-900">
Snowball
</span>
</div>
</Link>
<AsyncSelect
containerProps={{ className: 'h-14 border-none' }}
labelProps={{ className: 'before:border-none after:border-none' }}
className="bg-white rounded-lg shadow border-none"
value={selectedOrgSlug}
onChange={(value) => {
setSelectedOrgSlug(value!);
navigate(`/${value}`);
}}
selected={(_, index) => (
<div className="flex items-center space-x-3">
<img
src="/logo.svg"
alt="Application Logo"
className="h-8 w-8 rounded-lg"
/>
<div>
<div className="text-sm font-semibold">
{organizations[index!]?.name}
</div>
<div className="text-xs text-gray-500">Organization</div>
</div>
</div>
)}
arrow={<ChevronGrabberHorizontal className="h-4 w-4 text-gray-500" />}
>
{/* TODO: Show label organization and manage in option */}
{organizations.map((org) => (
<Option key={org.id} value={org.slug}>
<div className="flex items-center space-x-3">
<img
src="/logo.svg"
alt="Application Logo"
className="h-8 w-8 rounded-lg"
/>
<div>
<div className="text-sm font-semibold">{org.name}</div>
<div className="text-xs text-gray-500">Organization</div>
</div>
</div>
</Option>
))}
</AsyncSelect>
<Tabs defaultValue="Projects" orientation="vertical" className="mt-10">
<Tabs.List>
{[
{ title: 'Projects', url: `/${orgSlug}/`, icon: <FolderIcon /> },
{
title: 'Settings',
url: `/${orgSlug}/settings`,
icon: <SettingsSlidersIcon />,
},
].map(({ title, icon, url }, index) => (
<NavLink to={url} key={index}>
<Tabs.Trigger icon={icon} value={title}>
{title}
</Tabs.Trigger>
</NavLink>
))}
</Tabs.List>
</Tabs>
</div>
<div className="grow flex flex-col justify-end mb-8">
<Tabs defaultValue="Projects" orientation="vertical">
{/* TODO: use proper link buttons */}
<Tabs.List>
<Tabs.Trigger icon={<GlobeIcon />} value="">
<a className="cursor-pointer" onClick={handleLogOut}>
Log Out
</a>
</Tabs.Trigger>
<Tabs.Trigger icon={<QuestionMarkRoundIcon />} value="">
<a className="cursor-pointer">Documentation</a>
</Tabs.Trigger>
<Tabs.Trigger icon={<LifeBuoyIcon />} value="">
<a className="cursor-pointer">Support</a>
</Tabs.Trigger>
</Tabs.List>
</Tabs>
</div>
</div>
);
};
export default Sidebar;
@@ -1,7 +1,7 @@
import React from 'react';
import { useStopwatch } from 'react-timer-hook';
import FormatMillisecond from './FormatMilliSecond';
import FormatMillisecond, { FormatMilliSecondProps } from './FormatMilliSecond';
const setStopWatchOffset = (time: string) => {
const providedTime = new Date(time);
@@ -11,13 +11,17 @@ const setStopWatchOffset = (time: string) => {
return currentTime;
};
const Stopwatch = ({ offsetTimestamp }: { offsetTimestamp: Date }) => {
interface StopwatchProps extends Omit<FormatMilliSecondProps, 'time'> {
offsetTimestamp: Date;
}
const Stopwatch = ({ offsetTimestamp, ...props }: StopwatchProps) => {
const { totalSeconds } = useStopwatch({
autoStart: true,
offsetTimestamp: offsetTimestamp,
});
return <FormatMillisecond time={totalSeconds * 1000} />;
return <FormatMillisecond time={totalSeconds * 1000} {...props} />;
};
export { Stopwatch, setStopWatchOffset };
@@ -0,0 +1,30 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface CancelDeploymentDialogProps extends ConfirmDialogProps {}
export const CancelDeploymentDialog = ({
open,
handleCancel,
handleConfirm,
...props
}: CancelDeploymentDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Cancel deployment?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, cancel deployment"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em tracking-[-0.006em]">
This will halt the deployment and you&apos;ll have to start the process
from scratch.
</p>
</ConfirmDialog>
);
};
@@ -0,0 +1,90 @@
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 {
ChevronDoubleDownIcon,
LinkChainIcon,
} from 'components/shared/CustomIcon';
import { TagProps } from 'components/shared/Tag';
interface ChangeStateToProductionDialogProps extends ConfirmDialogProps {
deployment: Deployment;
newDeployment?: Deployment;
domains: Domain[];
}
export const ChangeStateToProductionDialog = ({
deployment,
newDeployment,
domains,
open,
handleCancel,
handleConfirm,
...props
}: ChangeStateToProductionDialogProps) => {
const currentChip = {
value: 'Live Deployment',
type: 'positive' as TagProps['type'],
};
const newChip = {
value: 'New Deployment',
type: 'attention' as TagProps['type'],
};
return (
<ConfirmDialog
{...props}
handleCancel={handleCancel}
open={open}
handleConfirm={handleConfirm}
>
<div className="flex flex-col gap-7">
<div className="flex flex-col gap-3">
<p className="text-sm text-elements-high-em tracking-[-0.006em]">
Upon confirmation, this deployment will be changed to production.
</p>
<DeploymentDialogBodyCard
deployment={deployment}
chip={newDeployment ? currentChip : undefined}
/>
{newDeployment && (
<>
<div className="flex items-center justify-between w-full text-elements-info">
{Array.from({ length: 7 }).map((_, index) => (
<ChevronDoubleDownIcon key={index} />
))}
</div>
<DeploymentDialogBodyCard
deployment={newDeployment}
chip={newChip}
/>
</>
)}
</div>
<div className="flex flex-col items-start gap-3">
<p className="text-sm text-elements-high-em tracking-[-0.006em]">
The new deployment will be associated with these domains:
</p>
{domains.length > 0 &&
domains.map((value) => {
return (
<Button
as="a"
href={value.name}
leftIcon={<LinkChainIcon size={18} />}
variant="link"
key={value.id}
>
{value.name}
</Button>
);
})}
</div>
</div>
</ConfirmDialog>
);
};
@@ -0,0 +1,42 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteDomainDialogProps extends ConfirmDialogProps {
projectName: string;
domainName: string;
}
export const DeleteDomainDialog = ({
projectName,
domainName,
open,
handleCancel,
handleConfirm,
...props
}: DeleteDomainDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete domain?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, delete domain"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em">
Once deleted, the project{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{projectName}
</span>{' '}
will not be accessible from the domain{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{domainName}
</span>
.
</p>
</ConfirmDialog>
);
};
@@ -0,0 +1,36 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteVariableDialogProps extends ConfirmDialogProps {
variableKey: string;
}
export const DeleteVariableDialog = ({
variableKey,
open,
handleCancel,
handleConfirm,
...props
}: DeleteVariableDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete variable"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm delete"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-mid-em">
Are you sure you want to delete the variable{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{variableKey}
</span>
?
</p>
</ConfirmDialog>
);
};
@@ -0,0 +1,36 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteWebhookDialogProps extends ConfirmDialogProps {
webhookUrl: string;
}
export const DeleteWebhookDialog = ({
webhookUrl,
open,
handleCancel,
handleConfirm,
...props
}: DeleteWebhookDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete webhook?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm delete"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-mid-em">
Are you sure you want to delete{' '}
<span className="text-sm font-mono text-elements-high-em px-0.5">
{webhookUrl}
</span>
?
</p>
</ConfirmDialog>
);
};
@@ -0,0 +1,30 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DisconnectRepositoryDialogProps extends ConfirmDialogProps {}
export const DisconnectRepositoryDialog = ({
open,
handleCancel,
handleConfirm,
...props
}: DisconnectRepositoryDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Disconnect repository?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm disconnect"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em">
Any data tied to your Git project may become misconfigured. Are you sure
you want to continue?
</p>
</ConfirmDialog>
);
};
@@ -0,0 +1,38 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
import { formatAddress } from 'utils/format';
interface RemoveMemberDialogProps extends ConfirmDialogProps {
memberName: string;
ethAddress: string;
emailDomain: string;
}
export const RemoveMemberDialog = ({
memberName,
ethAddress,
emailDomain,
open,
handleCancel,
handleConfirm,
...props
}: RemoveMemberDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Remove member?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, remove member"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em">
Once removed, {formatAddress(memberName)} ({formatAddress(ethAddress)}@
{emailDomain}) will not be able to access this project.
</p>
</ConfirmDialog>
);
};
@@ -0,0 +1,47 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface TransferProjectDialogProps extends ConfirmDialogProps {
projectName: string;
from: string;
to: string;
}
export const TransferProjectDialog = ({
projectName,
from,
to,
open,
handleCancel,
handleConfirm,
...props
}: TransferProjectDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Transfer project?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm transfer"
handleConfirm={handleConfirm}
>
<p className="text-sm text-elements-high-em">
Upon confirmation, your project{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{projectName}
</span>{' '}
will be transferred from{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{from}
</span>{' '}
to{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{to}
</span>
.
</p>
</ConfirmDialog>
);
};
@@ -8,25 +8,30 @@ export const projectCardTheme = tv({
'rounded-2xl',
'flex',
'flex-col',
'group',
'cursor-pointer',
],
upperContent: ['px-4', 'py-4', 'flex', 'items-start', 'gap-3', 'relative'],
content: ['flex', 'flex-col', 'gap-1', 'flex-1'],
content: ['flex', 'flex-col', 'gap-1', 'flex-1', 'overflow-hidden'],
title: [
'text-sm',
'font-medium',
'text-elements-high-em',
'tracking-[-0.006em]',
'truncate',
],
description: ['text-xs', 'text-elements-low-em'],
description: ['text-xs', 'text-elements-low-em', 'truncate'],
icons: ['flex', 'items-center', 'gap-1'],
lowerContent: [
'bg-surface-card-hovered',
'transition-colors',
'duration-150',
'px-4',
'py-4',
'flex',
'flex-col',
'gap-2',
'rounded-b-2xl',
'group-hover:bg-surface-card-hovered',
],
latestDeployment: ['flex', 'items-center', 'gap-2'],
deploymentStatusContainer: [
@@ -46,6 +51,12 @@ export const projectCardTheme = tv({
'items-center',
'gap-2',
],
wavyBorder: [
'bg-surface-card',
'transition-colors',
'duration-150',
'group-hover:bg-surface-card-hovered',
],
},
variants: {
status: {
@@ -1,8 +1,18 @@
import React, { ComponentPropsWithoutRef, MouseEvent } from 'react';
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
import {
Menu,
MenuHandler,
MenuItem,
MenuList,
} from '@material-tailwind/react';
import React, {
ComponentPropsWithoutRef,
MouseEvent,
useCallback,
} from 'react';
import { useNavigate } from 'react-router-dom';
import { Project } from 'gql-client';
import { Avatar } from 'components/shared/Avatar';
import { Button } from 'components/shared/Button';
import { WavyBorder } from 'components/shared/WavyBorder';
import {
BranchIcon,
ClockIcon,
@@ -10,16 +20,11 @@ import {
HorizontalDotIcon,
WarningDiamondIcon,
} from 'components/shared/CustomIcon';
import { Tooltip } from 'components/shared/Tooltip';
import { WavyBorder } from 'components/shared/WavyBorder';
import { relativeTimeMs } from 'utils/time';
import { Link } from 'react-router-dom';
import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials';
import {
Menu,
MenuHandler,
MenuItem,
MenuList,
} from '@material-tailwind/react';
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
export interface ProjectCardProps
extends ComponentPropsWithoutRef<'div'>,
@@ -27,6 +32,8 @@ export interface ProjectCardProps
project: Project;
}
// TODO: Update the whole component to use `Link` from `react-router-dom` and remove the `useNavigate` hook,
// currently it's not possible to use `Link` because the dot menu is not a direct child of the `Link` component
export const ProjectCard = ({
className,
project,
@@ -38,14 +45,24 @@ export const ProjectCard = ({
// TODO: Update this to use the actual status from the API
const hasError = status === 'failure';
const navigate = useNavigate();
const handleOptionsClick = (
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
) => {
e.stopPropagation();
};
const handleClick = useCallback(() => {
navigate(`projects/${project.id}`);
}, [project.id, navigate]);
return (
<div {...props} className={theme.wrapper({ className })}>
<div
{...props}
className={theme.wrapper({ className })}
onClick={handleClick}
>
{/* Upper content */}
<div className={theme.upperContent()}>
{/* Icon container */}
@@ -54,14 +71,15 @@ export const ProjectCard = ({
imageSrc={project.icon}
initials={getInitials(project.name)}
/>
{/* </div> */}
{/* Title and website */}
<Link to={`projects/${project.id}`} className={theme.content()}>
<p className={theme.title()}>{project.name}</p>
<div className={theme.content()}>
<Tooltip content={project.name}>
<p className={theme.title()}>{project.name}</p>
</Tooltip>
<p className={theme.description()}>
{project.deployments[0]?.domain?.name ?? 'No domain'}
</p>
</Link>
</div>
{/* Icons */}
<div className={theme.icons()}>
{hasError && <WarningDiamondIcon className="text-elements-danger" />}
@@ -87,7 +105,7 @@ export const ProjectCard = ({
</div>
</div>
{/* Wave */}
<WavyBorder />
<WavyBorder variant="stroke-and-fill" className={theme.wavyBorder()} />
{/* Lower content */}
<div className={theme.lowerContent()}>
{/* Latest deployment */}
@@ -1,127 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useCombobox } from 'downshift';
import { Project } from 'gql-client';
import { useDebounce } from 'usehooks-ts';
import {
List,
ListItem,
ListItemPrefix,
Card,
Typography,
Avatar,
} from '@material-tailwind/react';
import SearchBar from '../SearchBar';
import { useGQLClient } from '../../context/GQLClientContext';
interface ProjectsSearchProps {
onChange?: (data: Project) => void;
}
const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => {
const [items, setItems] = useState<Project[]>([]);
const [selectedItem, setSelectedItem] = useState<Project | null>(null);
const client = useGQLClient();
const {
isOpen,
getMenuProps,
getInputProps,
getItemProps,
highlightedIndex,
inputValue,
} = useCombobox({
items,
itemToString(item) {
return item ? item.name : '';
},
selectedItem,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
if (newSelectedItem) {
setSelectedItem(newSelectedItem);
if (onChange) {
onChange(newSelectedItem);
}
}
},
});
const debouncedInputValue = useDebounce<string>(inputValue, 500);
const fetchProjects = useCallback(
async (inputValue: string) => {
const { searchProjects } = await client.searchProjects(inputValue);
setItems(searchProjects);
},
[client],
);
useEffect(() => {
if (debouncedInputValue) {
fetchProjects(debouncedInputValue);
}
}, [fetchProjects, debouncedInputValue]);
return (
<div className="relative">
<SearchBar {...getInputProps()} />
<Card
className={`absolute w-1/2 max-h-52 -mt-1 overflow-y-auto ${
(!inputValue || !isOpen) && 'hidden'
}`}
placeholder={''}
>
<List {...getMenuProps()}>
{items.length ? (
<>
<div className="p-3">
<Typography variant="small" color="gray" placeholder={''}>
Suggestions
</Typography>
</div>
{items.map((item, index) => (
<ListItem
selected={highlightedIndex === index || selectedItem === item}
key={item.id}
placeholder={''}
{...getItemProps({ item, index })}
>
<ListItemPrefix placeholder={''}>
<Avatar
src={item.icon || '/gray.png'}
variant="rounded"
placeholder={''}
/>
</ListItemPrefix>
<div>
<Typography variant="h6" color="blue-gray" placeholder={''}>
{item.name}
</Typography>
<Typography
variant="small"
color="gray"
className="font-normal"
placeholder={''}
>
{item.organization.name}
</Typography>
</div>
</ListItem>
))}
</>
) : (
<div className="p-3">
<Typography placeholder={''}>
^ No projects matching this name
</Typography>
</div>
)}
</List>
</Card>
</div>
);
};
export default ProjectSearchBar;
@@ -0,0 +1,93 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useCombobox } from 'downshift';
import { Project } from 'gql-client';
import { useDebounce } from 'usehooks-ts';
import SearchBar from 'components/SearchBar';
import { useGQLClient } from 'context/GQLClientContext';
import { cn } from 'utils/classnames';
import { ProjectSearchBarItem } from './ProjectSearchBarItem';
import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty';
interface ProjectSearchBarProps {
onChange?: (data: Project) => void;
}
export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => {
const [items, setItems] = useState<Project[]>([]);
const [selectedItem, setSelectedItem] = useState<Project | null>(null);
const client = useGQLClient();
const {
isOpen,
getMenuProps,
getInputProps,
getItemProps,
highlightedIndex,
inputValue,
} = useCombobox({
items,
itemToString(item) {
return item ? item.name : '';
},
selectedItem,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
if (newSelectedItem) {
setSelectedItem(newSelectedItem);
if (onChange) {
onChange(newSelectedItem);
}
}
},
});
const debouncedInputValue = useDebounce<string>(inputValue, 300);
const fetchProjects = useCallback(
async (inputValue: string) => {
const { searchProjects } = await client.searchProjects(inputValue);
setItems(searchProjects);
},
[client],
);
useEffect(() => {
if (debouncedInputValue) {
fetchProjects(debouncedInputValue);
}
}, [fetchProjects, debouncedInputValue]);
return (
<div className="relative w-full lg:w-fit">
<SearchBar {...getInputProps()} />
<div
{...getMenuProps()}
className={cn(
'flex flex-col shadow-dropdown rounded-xl bg-surface-card absolute w-[459px] max-h-52 overflow-y-auto px-2 py-2 gap-1 z-50',
{ hidden: !inputValue || !isOpen },
)}
>
{items.length ? (
<>
<div className="px-2 py-2">
<p className="text-elements-mid-em text-xs font-medium">
Suggestions
</p>
</div>
{items.map((item, index) => (
<ProjectSearchBarItem
{...getItemProps({ item, index })}
key={item.id}
item={item}
active={highlightedIndex === index || selectedItem === item}
/>
))}
</>
) : (
<ProjectSearchBarEmpty />
)}
</div>
</div>
);
};
@@ -0,0 +1,112 @@
import React, { 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';
import { Input } from 'components/shared/Input';
import { useGQLClient } from 'context/GQLClientContext';
import { Project } from 'gql-client';
import { useDebounce } from 'usehooks-ts';
import { ProjectSearchBarItem } from './ProjectSearchBarItem';
import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty';
import { useNavigate } from 'react-router-dom';
import { useCombobox } from 'downshift';
interface ProjectSearchBarDialogProps extends Dialog.DialogProps {
open?: boolean;
onClose?: () => void;
onClickItem?: (data: Project) => void;
}
export const ProjectSearchBarDialog = ({
onClose,
onClickItem,
...props
}: ProjectSearchBarDialogProps) => {
const [items, setItems] = useState<Project[]>([]);
const [selectedItem, setSelectedItem] = useState<Project | null>(null);
const client = useGQLClient();
const navigate = useNavigate();
const { getInputProps, getItemProps, inputValue, setInputValue } =
useCombobox({
items,
itemToString(item) {
return item ? item.name : '';
},
selectedItem,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
if (newSelectedItem) {
setSelectedItem(newSelectedItem);
onClickItem?.(newSelectedItem);
navigate(
`/${newSelectedItem.organization.slug}/projects/${newSelectedItem.id}`,
);
}
},
});
const debouncedInputValue = useDebounce<string>(inputValue, 300);
const fetchProjects = useCallback(
async (inputValue: string) => {
const { searchProjects } = await client.searchProjects(inputValue);
setItems(searchProjects);
},
[client],
);
useEffect(() => {
if (debouncedInputValue) {
fetchProjects(debouncedInputValue);
}
}, [fetchProjects, debouncedInputValue]);
const handleClose = () => {
setInputValue('');
setItems([]);
onClose?.();
};
return (
<Dialog.Root {...props}>
<Dialog.Portal>
<Dialog.Overlay className="bg-base-bg fixed inset-0 md:hidden overflow-y-auto" />
<Dialog.Content>
<div className="h-full flex flex-col fixed top-0 inset-0">
<div className="py-2.5 px-4 flex items-center justify-between border-b border-border-separator/[0.06]">
<Input
{...getInputProps()}
leftIcon={<SearchIcon />}
placeholder="Search"
appearance="borderless"
autoFocus
/>
<Button iconOnly variant="ghost" onClick={handleClose}>
<CrossIcon size={16} />
</Button>
</div>
{/* Content */}
<div className="flex flex-col gap-1 px-2 py-2">
{items.length > 0
? items.map((item, index) => (
<>
<div className="px-2 py-2">
<p className="text-elements-mid-em text-xs font-medium">
Suggestions
</p>
</div>
<ProjectSearchBarItem
{...getItemProps({ item, index })}
key={item.id}
item={item}
/>
</>
))
: inputValue && <ProjectSearchBarEmpty />}
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};
@@ -0,0 +1,24 @@
import { InfoRoundFilledIcon } from 'components/shared/CustomIcon';
import React, { ComponentPropsWithoutRef } from 'react';
import { cn } from 'utils/classnames';
interface ProjectSearchBarEmptyProps extends ComponentPropsWithoutRef<'div'> {}
export const ProjectSearchBarEmpty = ({
className,
...props
}: ProjectSearchBarEmptyProps) => {
return (
<div
{...props}
className={cn('flex items-center px-2 py-2 gap-3', className)}
>
<div className="w-8 h-8 rounded-lg flex items-center justify-center bg-orange-50 text-elements-warning">
<InfoRoundFilledIcon size={16} />
</div>
<p className="text-elements-low-em text-sm tracking-[-0.006em]">
No projects matching this name
</p>
</div>
);
};
@@ -0,0 +1,59 @@
import { Avatar } from 'components/shared/Avatar';
import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift';
import { Project } from 'gql-client';
import React, { ComponentPropsWithoutRef, forwardRef } from 'react';
import { OmitCommon } from 'types/common';
import { cn } from 'utils/classnames';
import { getInitials } from 'utils/geInitials';
/**
* Represents a type that merges ComponentPropsWithoutRef<'li'> with certain exclusions.
* @type {MergedComponentPropsWithoutRef}
*/
type MergedComponentPropsWithoutRef = OmitCommon<
ComponentPropsWithoutRef<'button'>,
Omit<
Overwrite<UseComboboxGetItemPropsReturnValue, Project[]>,
'index' | 'item'
>
>;
interface ProjectSearchBarItemProps extends MergedComponentPropsWithoutRef {
item: Project;
active?: boolean;
}
const ProjectSearchBarItem = forwardRef<
HTMLButtonElement,
ProjectSearchBarItemProps
>(({ item, active, ...props }, ref) => {
return (
<button
{...props}
ref={ref}
key={item.id}
className={cn(
'px-2 py-2 flex items-center gap-3 rounded-lg text-left hover:bg-base-bg-emphasized',
{
'bg-base-bg-emphasized': active,
},
)}
>
<Avatar
size={32}
imageSrc={item.icon}
initials={getInitials(item.name)}
/>
<div className="flex flex-col flex-1">
<p className="text-sm tracking-[-0.006em] text-elements-high-em">
{item.name}
</p>
<p className="text-xs text-elements-low-em">{item.organization.name}</p>
</div>
</button>
);
});
ProjectSearchBarItem.displayName = 'ProjectSearchBarItem';
export { ProjectSearchBarItem };
@@ -0,0 +1,2 @@
export * from './ProjectSearchBar';
export * from './ProjectSearchBarDialog';
@@ -7,7 +7,13 @@ 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';
import { MockConnectGitCard } from './MockConnectGitCard';
const SCOPES = 'repo user';
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
@@ -23,50 +29,72 @@ const ConnectAccount: React.FC<ConnectAccountInterface> = ({
}: ConnectAccountInterface) => {
const client = useGQLClient();
const { toast, dismiss } = useToast();
const handleCode = async (code: string) => {
// Pass code to backend and get access token
const {
authenticateGitHub: { token },
} = await client.authenticateGitHub(code);
onToken(token);
toast({
onDismiss: dismiss,
id: 'connected-to-github',
title: 'The Git account is connected.',
variant: 'success',
});
};
// TODO: Use correct height
return (
<div className="bg-gray-100 flex flex-col p-4 gap-7 justify-center items-center text-center text-sm h-full rounded-2xl">
<div className="w-52 h-16 justify-center items-center gap-4 inline-flex">
<div className="w-16 h-16 bg-blue-100 rounded-2xl shadow-inner border border-sky-950 border-opacity-10 justify-center items-center gap-2.5 inline-flex">
<GitIcon />
<div className="flex flex-col items-center max-w-[420px]">
{/** Icons */}
<div className="w-52 h-16 justify-center items-center gap-4 inline-flex mb-7">
<IconWithFrame icon={<GitIcon />} />
<EllipsesIcon className="items-center gap-1.5 flex" />
<IconWithFrame className="bg-blue-400" icon={<SnowballIcon />} />
</div>
<EllipsesIcon className="items-center gap-1.5 flex" />
<div className="w-16 h-16 bg-blue-400 rounded-2xl shadow-inner border border-sky-950 border-opacity-10 justify-center items-center gap-2.5 flex">
<SnowballIcon />
{/** Text */}
<div className="flex flex-col gap-1.5 mb-6">
<Heading className="text-xl font-medium">
Connect to your Git account
</Heading>
<p className="text-center text-elements-mid-em">
Once connected, you can import a repository from your account or
start with one of our templates.
</p>
</div>
</div>
<div>
<div className="text-center text-slate-900 text-xl font-medium leading-7">
Connect to your Git account
{/** CTA Buttons */}
<div className="flex flex-col w-full sm:w-auto sm:flex-row gap-2 sm:gap-3">
<OauthPopup
url={GITHUB_OAUTH_URL}
onCode={handleCode}
onClose={() => {}}
title="Snowball"
width={1000}
height={1000}
>
<Button
className="w-full sm:w-auto"
leftIcon={<GithubIcon />}
variant="tertiary"
>
Connect to GitHub
</Button>
</OauthPopup>
<Button
className="w-full sm:w-auto"
leftIcon={<GitTeaIcon />}
variant="tertiary"
>
Connect to GitTea
</Button>
</div>
<div className="text-center text-slate-600 text-base font-normal leading-normal">
Once connected, you can import a repository from your account or start
with one of our templates.
</div>
</div>
<div className="mt-2 flex gap-3">
<OauthPopup
url={GITHUB_OAUTH_URL}
onCode={handleCode}
onClose={() => {}}
title="Snowball"
width={1000}
height={1000}
>
<Button>Connect to GitHub</Button>
</OauthPopup>
<Button>Connect to Gitea</Button>
</div>
{/* TODO: Add ConnectAccountTabPanel */}
<MockConnectGitCard />
{/* <div className="rounded-l shadow p-2 flex-col justify-start items-start gap-2 inline-flex">
<ConnectAccountTabPanel />
</div> */}
@@ -1,11 +1,12 @@
import React, { useCallback, useEffect } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { Button, Typography } from '@material-tailwind/react';
import { DeployStep, DeployStatus } from './DeployStep';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import ConfirmDialog from 'components/shared/ConfirmDialog';
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';
const TIMEOUT_DURATION = 5000;
const Deploy = () => {
@@ -31,63 +32,58 @@ const Deploy = () => {
}, []);
return (
<div>
<div className="flex justify-between mb-6">
<div>
<h4>Deployment started ...</h4>
<div className="flex">
^&nbsp;
<div className="space-y-7">
<div className="flex justify-between">
<div className="space-y-1.5">
<Heading as="h4" className="md:text-lg font-medium">
Deployment started ...
</Heading>
<div className="flex items-center gap-1.5">
<ClockOutlineIcon size={16} className="text-elements-mid-em" />
<Stopwatch
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
/>
</div>
</div>
<div>
<Button
onClick={handleOpen}
variant="outlined"
size="sm"
placeholder={''}
>
^ Cancel
</Button>
</div>
<ConfirmDialog
dialogTitle="Cancel deployment?"
handleOpen={handleOpen}
open={open}
confirmButtonTitle="Yes, Cancel deployment"
handleConfirm={handleCancel}
color="red"
<Button
onClick={handleOpen}
size="sm"
variant="tertiary"
leftIcon={<WarningIcon size={16} />}
>
<Typography variant="small" placeholder={''}>
This will halt the deployment and you will have to start the process
from scratch.
</Typography>
</ConfirmDialog>
Cancel
</Button>
<CancelDeploymentDialog
handleCancel={handleOpen}
open={open}
handleConfirm={handleCancel}
/>
</div>
<div>
<DeployStep
title="Building"
status={DeployStatus.COMPLETE}
step="1"
processTime="72000"
/>
<DeployStep
title="Deployment summary"
status={DeployStatus.PROCESSING}
step="2"
startTime={Date.now().toString()}
/>
<DeployStep
title="Running checks"
status={DeployStatus.NOT_STARTED}
step="3"
/>
<DeployStep
title="Assigning domains"
status={DeployStatus.NOT_STARTED}
step="4"
/>
</div>
<DeployStep
title="Building"
status={DeployStatus.COMPLETE}
step="1"
processTime="72000"
/>
<DeployStep
title="Deployment summary"
status={DeployStatus.PROCESSING}
step="2"
startTime={Date.now().toString()}
/>
<DeployStep
title="Running checks"
status={DeployStatus.NOT_STARTED}
step="3"
/>
<DeployStep
title="Assigning domains"
status={DeployStatus.NOT_STARTED}
step="4"
/>
</div>
);
};
@@ -1,11 +1,22 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { Collapse, Button, Typography } from '@material-tailwind/react';
import { Collapse } from '@material-tailwind/react';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import FormatMillisecond from '../../FormatMilliSecond';
import processLogs from '../../../assets/process-logs.json';
import { cn } from 'utils/classnames';
import {
CheckRoundFilledIcon,
ClockOutlineIcon,
CopyIcon,
LoaderIcon,
MinusCircleIcon,
PlusIcon,
} from 'components/shared/CustomIcon';
import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast';
import { useIntersectionObserver } from 'usehooks-ts';
enum DeployStatus {
PROCESSING = 'progress',
@@ -28,61 +39,115 @@ const DeployStep = ({
startTime,
processTime,
}: DeployStepsProps) => {
const [collapse, setCollapse] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const { toast, dismiss } = useToast();
const { isIntersecting: hideGradientOverlay, ref } = useIntersectionObserver({
threshold: 1,
});
const disableCollapse = status !== DeployStatus.COMPLETE;
return (
<div className="border-b-2">
<div className="flex justify-between p-2 gap-2">
{status === DeployStatus.NOT_STARTED && <div>{step}</div>}
{status === DeployStatus.PROCESSING && <div>O</div>}
{status === DeployStatus.COMPLETE && (
<div>
<button
onClick={() => {
setCollapse(!collapse);
}}
>
{collapse ? '-' : '+'}
</button>
<div className="border-b border-border-separator">
{/* Collapisble trigger */}
<button
className={cn(
'flex justify-between w-full py-5 gap-2',
disableCollapse && 'cursor-auto',
)}
tabIndex={disableCollapse ? -1 : undefined}
onClick={() => {
if (!disableCollapse) {
setIsOpen((val) => !val);
}
}}
>
<div className={cn('grow flex items-center gap-3')}>
{/* Icon */}
<div className="w-6 h-6 grid place-content-center">
{status === DeployStatus.NOT_STARTED && (
<div className="grid place-content-center w-6 h-6 rounded-[48px] bg-base-bg-emphasized font-mono text-xs">
{step}
</div>
)}
{status === DeployStatus.PROCESSING && (
<LoaderIcon className="animate-spin text-elements-link" />
)}
{status === DeployStatus.COMPLETE && (
<div className="text-controls-primary">
{!isOpen && <PlusIcon size={24} />}
{isOpen && <MinusCircleIcon size={24} />}
</div>
)}
</div>
{/* Title */}
<span
className={cn(
'text-left text-sm md:text-base',
status === DeployStatus.PROCESSING && 'text-elements-link',
)}
>
{title}
</span>
</div>
{/* Timer */}
{status === DeployStatus.PROCESSING && (
<div className="flex items-center gap-1.5">
<ClockOutlineIcon size={16} className="text-elements-low-em" />
<Stopwatch offsetTimestamp={setStopWatchOffset(startTime!)} />
</div>
)}
<div className="grow">{title}</div>
{status === DeployStatus.PROCESSING && (
<>
^<Stopwatch offsetTimestamp={setStopWatchOffset(startTime!)} />
</>
)}
{status === DeployStatus.COMPLETE && (
<>
^<FormatMillisecond time={Number(processTime)} />{' '}
</>
<div className="flex items-center gap-1.5">
<div className="w-4.5 h-4.5 grid place-content-center">
<CheckRoundFilledIcon
className="text-elements-success"
size={15}
/>
</div>
<FormatMillisecond time={Number(processTime)} />{' '}
</div>
)}
</div>
<Collapse open={collapse}>
<div className="p-2 text-sm text-gray-500 h-36 overflow-y-scroll">
</button>
{/* Collapsible */}
<Collapse open={isOpen}>
<div className="relative text-xs text-elements-low-em h-36 overflow-y-auto">
{/* Logs */}
{processLogs.map((log, key) => {
return (
<Typography
variant="small"
color="gray"
key={key}
placeholder={''}
>
<p className="font-mono" key={key}>
{log}
</Typography>
</p>
);
})}
<div className="sticky bottom-0 left-1/2 flex justify-center">
{/* End of logs ref used for hiding gradient overlay */}
<div ref={ref} />
{/* Overflow gradient overlay */}
{!hideGradientOverlay && (
<div className="h-14 w-full sticky bottom-0 inset-x-0 bg-gradient-to-t from-white to-transparent" />
)}
{/* Copy log button */}
<div className={cn('sticky bottom-4 left-1/2 flex justify-center')}>
<Button
size="sm"
size="xs"
onClick={() => {
navigator.clipboard.writeText(processLogs.join('\n'));
toast.success('Logs copied');
toast({
title: 'Logs copied',
variant: 'success',
id: 'logs',
onDismiss: dismiss,
});
}}
color="blue"
placeholder={''}
leftIcon={<CopyIcon size={16} />}
>
^ Copy log
Copy log
</Button>
</div>
</div>
@@ -0,0 +1,163 @@
import React, { useMemo } from 'react';
import { SegmentedControls } from 'components/shared/SegmentedControls';
import { useState } from 'react';
import {
GithubIcon,
LockIcon,
TemplateIcon,
TemplateIconType,
} from 'components/shared/CustomIcon';
import { relativeTimeISO } from 'utils/time';
import { useMediaQuery } from 'usehooks-ts';
export const MockConnectGitCard = () => {
const [segmentedControlsValue, setSegmentedControlsValue] =
useState<string>('import');
const isDesktopView = useMediaQuery('(min-width: 960px)'); // lg:
const segmentedControlsProps = isDesktopView ? {} : { size: 'sm' as const };
const fiveMinutesAgo = new Date(Date.now() - 1000 * 300).toISOString();
const SEGMENTED_CONTROLS_OPTIONS = [
{
label: 'Import a repository',
value: 'import',
},
{
label: 'Start with a template',
value: 'template',
},
];
const IMPORT_CONTENT = [
{
full_name: 'snowball/igloo',
updated_at: fiveMinutesAgo,
},
{
full_name: 'snowball/android-sdk',
updated_at: fiveMinutesAgo,
visibility: 'private',
},
{
full_name: 'snowball/landing-page',
updated_at: fiveMinutesAgo,
},
];
const TEMPLATE_CONTENT = [
{
name: 'Web app',
icon: 'web',
},
{
name: 'Progressive Web App (PWA)',
icon: 'pwa',
},
{
name: 'React Native',
icon: 'react-native',
},
{
name: 'Kotlin',
icon: 'kotlin',
},
{
name: 'Swift',
icon: 'swift',
},
];
const renderContent = useMemo(() => {
if (segmentedControlsValue === 'import') {
return (
<div className="flex flex-col gap-2 relative z-0">
{IMPORT_CONTENT.map((repo, index) => (
<>
<MockProjectCard key={index} {...repo} />
{index !== IMPORT_CONTENT.length - 1 && (
<div className="border-b border-base-border" />
)}
</>
))}
</div>
);
}
return (
<div className="grid grid-cols-1 lg:grid-cols-2 relative z-0">
{TEMPLATE_CONTENT.map((template, index) => (
<MockTemplateCard key={index} {...template} />
))}
</div>
);
}, [segmentedControlsValue]);
return (
<div className="relative bg-base-bg shadow-card rounded-2xl px-2 py-2 w-full max-w-[560px] flex flex-col gap-2">
{/* Content */}
<SegmentedControls
value={segmentedControlsValue}
onChange={setSegmentedControlsValue}
options={SEGMENTED_CONTROLS_OPTIONS}
type="full-width"
{...segmentedControlsProps}
/>
{renderContent}
{/* Shade */}
<div className="pointer-events-none z-99 absolute inset-0 rounded-2xl bg-gradient-to-t from-white to-transparent" />
</div>
);
};
const MockProjectCard = ({
full_name,
updated_at,
visibility,
}: {
full_name: string;
updated_at?: string;
visibility?: string;
}) => {
return (
<div className="group flex items-start sm:items-center gap-3 pl-3 py-3 cursor-pointer rounded-xl hover:bg-base-bg-emphasized relative">
{/* Icon container */}
<div className="w-10 h-10 bg-base-bg rounded-md justify-center items-center flex">
<GithubIcon />
</div>
{/* Content */}
<div className="flex flex-1 gap-3 flex-wrap">
<div className="flex flex-col items-start gap-1">
<p className="text-elements-high-em text-sm font-medium tracking-[-0.006em]">
{full_name}
</p>
<p className="text-elements-low-em text-xs">
{updated_at && relativeTimeISO(updated_at)}
</p>
</div>
{visibility === 'private' && (
<div className="bg-orange-50 border border-orange-200 px-2 py-1 flex items-center gap-1 rounded-lg text-xs text-orange-600 h-fit">
<LockIcon />
Private
</div>
)}
</div>
</div>
);
};
const MockTemplateCard = ({ icon, name }: { icon: string; name: string }) => {
return (
<div className="flex items-center gap-3 px-3 py-3 hover:bg-base-bg-emphasized rounded-2xl group relative cursor-default">
{/* Icon */}
<div className="px-1 py-1 rounded-xl bg-base-bg border border-border-interactive/10 shadow-card-sm">
<TemplateIcon type={icon as TemplateIconType} />
</div>
{/* Name */}
<p className="flex-1 text-left text-sm tracking-tighter text-elements-high-em">
{name}
</p>
</div>
);
};
@@ -1,81 +0,0 @@
import React, { useCallback } from 'react';
import toast from 'react-hot-toast';
import { useNavigate, useParams } from 'react-router-dom';
import { Chip, IconButton, Spinner } from '@material-tailwind/react';
import { relativeTimeISO } from '../../../utils/time';
import { GitRepositoryDetails } from '../../../types';
import { useGQLClient } from '../../../context/GQLClientContext';
import { GithubIcon, LockIcon } from 'components/shared/CustomIcon';
interface ProjectRepoCardProps {
repository: GitRepositoryDetails;
}
const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => {
const client = useGQLClient();
const navigate = useNavigate();
const [isLoading, setIsLoading] = React.useState(false);
const { orgSlug } = useParams();
const createProject = useCallback(async () => {
if (!repository) {
return;
}
setIsLoading(true);
const { addProject } = await client.addProject(orgSlug!, {
name: `${repository.owner!.login}-${repository.name}`,
prodBranch: repository.default_branch!,
repository: repository.full_name,
// TODO: Compute template from repo
template: 'webapp',
});
if (Boolean(addProject)) {
setIsLoading(false);
navigate(`import?projectId=${addProject.id}`);
} else {
setIsLoading(false);
toast.error('Failed to create project');
}
}, [client, repository]);
return (
<div
className="group flex items-center gap-4 text-gray-500 text-xs hover:bg-gray-100 p-2 cursor-pointer"
onClick={createProject}
>
<div className="w-10 h-10 bg-white rounded-md justify-center items-center gap-1.5 inline-flex">
<GithubIcon />
</div>
<div className="grow">
<div>
<span className="text-black">{repository.full_name}</span>
{repository.visibility === 'private' && (
<Chip
className="normal-case inline ml-6 font-normal text-xs text-xs bg-orange-50 border border-orange-200 text-orange-600 items-center gap-1 inline-flex"
size="sm"
value="Private"
icon={<LockIcon />}
/>
)}
</div>
<p>{repository.updated_at && relativeTimeISO(repository.updated_at)}</p>
</div>
{isLoading ? (
<Spinner className="h-4 w-4" />
) : (
<div className="hidden group-hover:block">
<IconButton size="sm" placeholder={''}>
{'>'}
</IconButton>
</div>
)}
</div>
);
};
export default ProjectRepoCard;
@@ -0,0 +1,114 @@
import React, { useCallback, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Spinner } from '@material-tailwind/react';
import { relativeTimeISO } from 'utils/time';
import { GitRepositoryDetails } from 'types';
import { useGQLClient } from 'context/GQLClientContext';
import {
ArrowRightCircleIcon,
GithubIcon,
LockIcon,
} from 'components/shared/CustomIcon';
import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast';
interface ProjectRepoCardProps {
repository: GitRepositoryDetails;
}
export const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({
repository,
}) => {
const client = useGQLClient();
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const { orgSlug } = useParams();
const { toast, dismiss } = useToast();
const createProject = useCallback(async () => {
if (!repository || !orgSlug) {
return toast({
id: 'missing-repository-or-org-slug',
title: 'Repository or organization slug is missing',
variant: 'error',
onDismiss: dismiss,
});
}
try {
setIsLoading(true);
const { addProject } = await client.addProject(orgSlug, {
name: `${repository.owner?.login}-${repository.name}`,
prodBranch: repository.default_branch as string,
repository: repository.full_name,
// TODO: Compute template from repo
template: 'webapp',
});
if (addProject) {
navigate(`import?projectId=${addProject.id}`);
} else {
toast({
id: 'failed-to-create-project',
title: 'Failed to create project',
variant: 'error',
onDismiss: dismiss,
});
}
} catch (error) {
console.error((error as Error).message);
toast({
id: 'failed-to-create-project',
title: 'Failed to create project',
variant: 'error',
onDismiss: dismiss,
});
} finally {
setIsLoading(false);
}
}, [client, repository, orgSlug, setIsLoading, navigate, toast]);
return (
<div
className="group flex items-start sm:items-center gap-3 px-3 py-3 cursor-pointer rounded-xl hover:bg-base-bg-emphasized relative"
onClick={createProject}
>
{/* Icon container */}
<div className="w-10 h-10 bg-base-bg rounded-md justify-center items-center flex">
<GithubIcon />
</div>
{/* Content */}
<div className="flex flex-1 gap-3 flex-wrap">
<div className="flex flex-col gap-1">
<p className="text-elements-high-em text-sm font-medium tracking-[-0.006em]">
{repository.full_name}
</p>
<p className="text-elements-low-em text-xs">
{repository.updated_at && relativeTimeISO(repository.updated_at)}
</p>
</div>
{repository.visibility === 'private' && (
<div className="bg-orange-50 border border-orange-200 px-2 py-1 flex items-center gap-1 rounded-lg text-xs text-orange-600 h-fit">
<LockIcon />
Private
</div>
)}
</div>
{/* Right action */}
{isLoading ? (
<Spinner className="h-4 w-4 absolute right-3" />
) : (
<Button
variant="tertiary"
size="sm"
iconOnly
className="sm:group-hover:flex hidden absolute right-3"
>
<ArrowRightCircleIcon />
</Button>
)}
</div>
);
};
@@ -0,0 +1 @@
export * from './ProjectRepoCard';
@@ -3,13 +3,17 @@ import { Octokit } from 'octokit';
import assert from 'assert';
import { useDebounce } from 'usehooks-ts';
import { Button, Typography, Option } from '@material-tailwind/react';
import SearchBar from '../../SearchBar';
import ProjectRepoCard from './ProjectRepoCard';
import { GitOrgDetails, GitRepositoryDetails } from '../../../types';
import AsyncSelect from '../../shared/AsyncSelect';
import { GithubIcon } from 'components/shared/CustomIcon';
import { ProjectRepoCard } from 'components/projects/create/ProjectRepoCard';
import { GitOrgDetails, GitRepositoryDetails } from 'types';
import {
ChevronGrabberHorizontal,
GithubIcon,
RefreshIcon,
SearchIcon,
} from 'components/shared/CustomIcon';
import { Select, SelectOption } from 'components/shared/Select';
import { Input } from 'components/shared/Input';
import { Button } from 'components/shared/Button';
const DEFAULT_SEARCHED_REPO = '';
const REPOS_PER_PAGE = 5;
@@ -18,9 +22,9 @@ interface RepositoryListProps {
octokit: Octokit;
}
const RepositoryList = ({ octokit }: RepositoryListProps) => {
export const RepositoryList = ({ octokit }: RepositoryListProps) => {
const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO);
const [selectedAccount, setSelectedAccount] = useState('');
const [selectedAccount, setSelectedAccount] = useState<SelectOption>();
const [orgs, setOrgs] = useState<GitOrgDetails[]>([]);
// TODO: Add new type for Git user when required
const [gitUser, setGitUser] = useState<GitOrgDetails>();
@@ -35,7 +39,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
const orgs = await octokit.rest.orgs.listForAuthenticatedUser();
setOrgs(orgs.data);
setGitUser(user.data);
setSelectedAccount(user.data.login);
setSelectedAccount({ label: user.data.login, value: user.data.login });
};
fetchUserAndOrgs();
@@ -54,7 +58,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
let query = `${debouncedSearchedRepo} in:name fork:true`;
// Check if selected account is an organization
if (selectedAccount === gitUser.login) {
if (selectedAccount.value === gitUser.login) {
query = query + ` user:${selectedAccount}`;
} else {
query = query + ` org:${selectedAccount}`;
@@ -69,7 +73,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
return;
}
if (selectedAccount === gitUser.login) {
if (selectedAccount.value === gitUser.login) {
const result = await octokit.rest.repos.listForAuthenticatedUser({
per_page: REPOS_PER_PAGE,
affiliation: 'owner',
@@ -78,7 +82,9 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
return;
}
const selectedOrg = orgs.find((org) => org.login === selectedAccount);
const selectedOrg = orgs.find(
(org) => org.login === selectedAccount.value,
);
assert(selectedOrg, 'Selected org not found in list');
const result = await octokit.rest.repos.listForOrg({
@@ -96,7 +102,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
const handleResetFilters = useCallback(() => {
assert(gitUser, 'Git user is not available');
setSearchedRepo(DEFAULT_SEARCHED_REPO);
setSelectedAccount(gitUser.login);
setSelectedAccount({ label: gitUser.login, value: gitUser.login });
}, [gitUser]);
const accounts = useMemo(() => {
@@ -107,52 +113,65 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
return [gitUser, ...orgs];
}, [octokit, orgs, gitUser]);
const options = useMemo(() => {
return accounts.map((account) => ({
label: account.login,
value: account.login,
leftIcon: <GithubIcon />,
}));
}, [accounts]);
return (
<div className="p-4">
<div className="flex gap-2 mb-2 items-center">
<div className="basis-1/3">
<AsyncSelect
<section className="space-y-3">
{/* Dropdown and search */}
<div className="flex flex-col lg:flex-row gap-0 lg:gap-3 items-center">
<div className="lg:basis-1/3 w-full">
<Select
options={options}
placeholder="Select a repository"
value={selectedAccount}
onChange={(value) => setSelectedAccount(value!)}
>
{accounts.map((account) => (
<Option key={account.id} value={account.login}>
<div className="flex items-center gap-2 justify-start">
<GithubIcon /> {account.login}
</div>
</Option>
))}
</AsyncSelect>
leftIcon={selectedAccount ? <GithubIcon /> : undefined}
rightIcon={<ChevronGrabberHorizontal />}
onChange={(value) => setSelectedAccount(value as SelectOption)}
/>
</div>
<div className="basis-2/3 flex-grow flex items-center">
<SearchBar
<div className="basis-2/3 flex w-full flex-grow">
<Input
className="w-full"
value={searchedRepo}
onChange={(event) => setSearchedRepo(event.target.value)}
placeholder="Search for repository"
leftIcon={<SearchIcon />}
onChange={(e) => setSearchedRepo(e.target.value)}
/>
</div>
</div>
{/* Repository list */}
{Boolean(repositoryDetails.length) ? (
repositoryDetails.map((repo, key) => {
return <ProjectRepoCard repository={repo} key={key} />;
})
<div className="flex flex-col gap-2">
{repositoryDetails.map((repo, index) => (
<>
<ProjectRepoCard repository={repo} key={index} />
{/* Horizontal line */}
{index !== repositoryDetails.length - 1 && (
<div className="border-b border-border-separator/[0.06] w-full" />
)}
</>
))}
</div>
) : (
<div className="mt-4 p-6 flex items-center justify-center">
<div className="text-center">
<Typography placeholder={''}>No repository found</Typography>
<Button
className="rounded-full mt-5"
size="sm"
onClick={handleResetFilters}
placeholder={''}
>
^ Reset filters
</Button>
</div>
<div className="mt-4 p-6 flex flex-col gap-4 items-center justify-center">
<p className="text-elements-high-em font-sans">No repository found</p>
<Button
variant="tertiary"
leftIcon={<RefreshIcon />}
size="sm"
onClick={handleResetFilters}
>
Reset filters
</Button>
</div>
)}
</div>
</section>
);
};
export default RepositoryList;
@@ -0,0 +1 @@
export * from './RepositoryList';
@@ -1,53 +0,0 @@
import React from 'react';
import toast from 'react-hot-toast';
import { IconButton, Typography } from '@material-tailwind/react';
import { Link } from 'react-router-dom';
interface TemplateDetails {
id: string;
name: string;
icon: string;
}
interface TemplateCardProps {
template: TemplateDetails;
isGitAuth: boolean;
}
const CardDetails = ({ template }: { template: TemplateDetails }) => {
return (
<div className="h-14 group bg-gray-200 border-gray-200 rounded-lg shadow p-4 flex items-center justify-between">
<Typography className="grow" placeholder={''}>
{template.icon} {template.name}
</Typography>
<div>
<IconButton
size="sm"
className="rounded-full hidden group-hover:block"
placeholder={''}
>
{'>'}
</IconButton>
</div>
</div>
);
};
const TemplateCard: React.FC<TemplateCardProps> = ({ template, isGitAuth }) => {
return isGitAuth ? (
<Link to={`template?templateId=${template.id}`}>
<CardDetails template={template} />
</Link>
) : (
<a
onClick={() =>
toast.error('Connect Git account to start with a template')
}
>
<CardDetails template={template} />
</a>
);
};
export default TemplateCard;
@@ -0,0 +1,91 @@
import React, { ComponentPropsWithoutRef, useCallback } from 'react';
import { Button } from 'components/shared/Button';
import {
ArrowRightCircleIcon,
ClockOutlineIcon,
TemplateIcon,
TemplateIconType,
} from 'components/shared/CustomIcon';
import { Tag } from 'components/shared/Tag';
import { useNavigate, useParams } from 'react-router-dom';
import { useToast } from 'components/shared/Toast';
import { cn } from 'utils/classnames';
export interface TemplateDetail {
id: string;
name: string;
icon: string;
repoFullName?: string;
isComingSoon?: boolean;
}
export interface TemplateCardProps extends ComponentPropsWithoutRef<'div'> {
template: TemplateDetail;
isGitAuth: boolean;
}
export const TemplateCard: React.FC<TemplateCardProps> = ({
template,
isGitAuth,
}: TemplateCardProps) => {
const { toast, dismiss } = useToast();
const navigate = useNavigate();
const { orgSlug } = useParams();
const handleClick = useCallback(() => {
if (template?.isComingSoon) {
return toast({
id: 'coming-soon',
title: 'This template is coming soon',
variant: 'info',
onDismiss: dismiss,
});
}
if (isGitAuth) {
return navigate(
`/${orgSlug}/projects/create/template?templateId=${template.id}`,
);
}
return toast({
id: 'connect-git-account',
title: 'Connect Git account to start with a template',
variant: 'error',
onDismiss: dismiss,
});
}, [orgSlug, dismiss, isGitAuth, navigate, template, toast]);
return (
<div
className={cn(
'flex items-center gap-3 px-3 py-3 bg-base-bg-alternate hover:bg-base-bg-emphasized rounded-2xl group relative cursor-pointer',
{
'cursor-default': template?.isComingSoon,
},
)}
onClick={handleClick}
>
{/* Icon */}
<div className="px-1 py-1 rounded-xl bg-base-bg border border-border-interactive/10 shadow-card-sm">
<TemplateIcon type={template.icon as TemplateIconType} />
</div>
{/* Name */}
<p className="flex-1 text-left text-sm tracking-tighter text-elements-high-em">
{template.name}
</p>
{template?.isComingSoon ? (
<Tag size="xs" type="neutral" leftIcon={<ClockOutlineIcon />}>
Soon
</Tag>
) : (
<Button
variant="tertiary"
size="sm"
iconOnly
className="group-hover:flex hidden absolute right-3"
>
<ArrowRightCircleIcon />
</Button>
)}
</div>
);
};
@@ -0,0 +1 @@
export * from './TemplateCard';
@@ -1,46 +0,0 @@
import React from 'react';
import { Typography, IconButton, Avatar } from '@material-tailwind/react';
import { relativeTimeISO } from '../../../utils/time';
import { GitCommitWithBranch } from '../../../types';
interface ActivityCardProps {
activity: GitCommitWithBranch;
}
const ActivityCard = ({ activity }: ActivityCardProps) => {
return (
<div className="group flex gap-2 hover:bg-gray-200 rounded mt-1">
<div className="w-8">
<Avatar
src={activity.author?.avatar_url}
variant="rounded"
size="sm"
placeholder={''}
/>
</div>
<div className="grow">
<Typography placeholder={''}>{activity.commit.author?.name}</Typography>
<Typography variant="small" color="gray" placeholder={''}>
{relativeTimeISO(activity.commit.author!.date!)} ^{' '}
{activity.branch.name}
</Typography>
<Typography variant="small" color="gray" placeholder={''}>
{activity.commit.message}
</Typography>
</div>
<div className="mr-2 self-center hidden group-hover:block">
<IconButton
size="sm"
className="rounded-full bg-gray-600"
placeholder={''}
>
{'>'}
</IconButton>
</div>
</div>
);
};
export default ActivityCard;
@@ -1,31 +1,28 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import React, { useCallback } from 'react';
import {
Deployment,
DeploymentStatus,
Domain,
Environment,
Project,
Domain,
DeploymentStatus,
Deployment,
} from 'gql-client';
import { Avatar } from 'components/shared/Avatar';
import {
Menu,
MenuHandler,
MenuList,
MenuItem,
Typography,
Chip,
ChipProps,
Tooltip,
} from '@material-tailwind/react';
import { relativeTimeMs } from '../../../../utils/time';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import DeploymentDialogBodyCard from './DeploymentDialogBodyCard';
import AssignDomainDialog from './AssignDomainDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
BranchStrokeIcon,
CheckRoundFilledIcon,
ClockOutlineIcon,
CommitIcon,
LoadingIcon,
WarningIcon,
} from 'components/shared/CustomIcon';
import { Heading } from 'components/shared/Heading';
import { OverflownText } from 'components/shared/OverflownText';
import { Tag, TagTheme } from 'components/shared/Tag';
import { getInitials } from 'utils/geInitials';
import { relativeTimeMs } from 'utils/time';
import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants';
import { formatAddress } from '../../../../utils/format';
import { DeploymentMenu } from './DeploymentMenu';
interface DeployDetailsCardProps {
deployment: Deployment;
@@ -35,10 +32,12 @@ interface DeployDetailsCardProps {
prodBranchDomains: Domain[];
}
const STATUS_COLORS: { [key in DeploymentStatus]: ChipProps['color'] } = {
[DeploymentStatus.Building]: 'blue',
[DeploymentStatus.Ready]: 'green',
[DeploymentStatus.Error]: 'red',
const STATUS_COLORS: {
[key in DeploymentStatus]: TagTheme['type'];
} = {
[DeploymentStatus.Building]: 'emphasized',
[DeploymentStatus.Ready]: 'positive',
[DeploymentStatus.Error]: 'negative',
};
const DeploymentDetailsCard = ({
@@ -48,241 +47,117 @@ const DeploymentDetailsCard = ({
project,
prodBranchDomains,
}: DeployDetailsCardProps) => {
const client = useGQLClient();
const getIconByDeploymentStatus = (status: DeploymentStatus) => {
if (status === DeploymentStatus.Building) {
return <LoadingIcon className="animate-spin" />;
}
if (status === DeploymentStatus.Ready) {
return <CheckRoundFilledIcon />;
}
const [changeToProduction, setChangeToProduction] = useState(false);
const [redeployToProduction, setRedeployToProduction] = useState(false);
const [rollbackDeployment, setRollbackDeployment] = useState(false);
const [assignDomainDialog, setAssignDomainDialog] = useState(false);
const updateDeployment = async () => {
const isUpdated = await client.updateDeploymentToProd(deployment.id);
if (isUpdated) {
await onUpdate();
toast.success('Deployment changed to production');
} else {
toast.error('Unable to change deployment to production');
if (status === DeploymentStatus.Error) {
return <WarningIcon />;
}
};
const redeployToProd = async () => {
const isRedeployed = await client.redeployToProd(deployment.id);
if (isRedeployed) {
await onUpdate();
toast.success('Redeployed to production');
} else {
toast.error('Unable to redeploy to production');
}
};
const rollbackDeploymentHandler = async () => {
const isRollbacked = await client.rollbackDeployment(
project.id,
deployment.id,
);
if (isRollbacked) {
await onUpdate();
toast.success('Deployment rolled back');
} else {
toast.error('Unable to rollback deployment');
}
};
const renderDeploymentStatus = useCallback(
(className?: string) => {
return (
<div className={className}>
<Tag
leftIcon={getIconByDeploymentStatus(deployment.status)}
size="xs"
type={STATUS_COLORS[deployment.status] ?? 'neutral'}
>
{deployment.status}
</Tag>
</div>
);
},
[deployment.status, deployment.commitHash],
);
return (
<div className="grid grid-cols-8 gap-2 border-b border-gray-300 p-3 my-2">
<div className="col-span-3">
<div className="flex">
<div className="flex md:flex-row flex-col gap-6 py-4 px-3 pb-6 mb-2 last:mb-0 last:pb-4 border-b border-border-separator last:border-b-transparent relative">
<div className="flex-1 flex justify-between w-full md:max-w-[25%] lg:max-w-[28%]">
<div className="flex-1 w-full space-y-2 max-w-[90%] sm:max-w-full">
{/* DEPLOYMENT URL */}
{deployment.url && (
<Typography className="basis-3/4" placeholder={''}>
{deployment.url}
</Typography>
<Heading
className="text-sm font-medium text-elements-high-em tracking-tight"
as="h2"
>
<OverflownText content={deployment.url}>
{deployment.url}
</OverflownText>
</Heading>
)}
<span className="text-sm text-elements-low-em tracking-tight">
{deployment.environment === Environment.Production
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
: 'Preview'}
</span>
</div>
<Typography color="gray" placeholder={''}>
{deployment.environment === Environment.Production
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
: 'Preview'}
</Typography>
</div>
<div className="col-span-1">
<Chip
value={deployment.status}
color={STATUS_COLORS[deployment.status] ?? 'gray'}
variant="ghost"
icon={<i>^</i>}
{/* DEPLOYMENT STATUS */}
{renderDeploymentStatus('w-[10%] max-w-[110px] hidden md:flex h-fit')}
{/* DEPLOYMENT COMMIT DETAILS */}
<div className="flex w-full justify-between md:w-[25%]">
<div className="text-sm max-w-[60%] md:max-w-full space-y-2 w-full text-elements-low-em">
<span className="flex gap-1.5 items-center">
<BranchStrokeIcon className="h-4 w-4" />
<OverflownText content={deployment.branch}>
{deployment.branch}
</OverflownText>
</span>
<span className="flex w-full gap-2 items-center">
<CommitIcon />
<OverflownText content={deployment.commitMessage}>
{deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
{deployment.commitMessage}
</OverflownText>
</span>
</div>
{renderDeploymentStatus('flex md:hidden h-fit')}
</div>
{/* DEPLOYMENT INFOs */}
<div className="md:ml-auto w-full md:max-w-[312px] md:w-[30%] gap-1 2xl:gap-5 flex items-center justify-between text-elements-low-em text-sm">
<div className="flex md:w-[70%] xl:items-center gap-2 flex-1 xl:flex-row md:flex-col">
<div className="flex gap-2 items-center">
<ClockOutlineIcon className="h-4 w-4" />
<OverflownText content={relativeTimeMs(deployment.createdAt) ?? ''}>
{relativeTimeMs(deployment.createdAt)}
</OverflownText>
</div>
<div className="flex gap-2 items-center">
<div>
<Avatar
type="orange"
initials={getInitials(deployment.createdBy.name ?? '')}
className="lg:size-5 2xl:size-6"
// TODO: Add avatarUrl
// imageSrc={deployment.createdBy.avatarUrl}
></Avatar>
</div>
<OverflownText
content={formatAddress(deployment.createdBy?.name ?? '')}
>
{formatAddress(deployment.createdBy.name ?? '')}
</OverflownText>
</div>
</div>
<DeploymentMenu
className="ml-auto md:static absolute top-4 right-0"
deployment={deployment}
currentDeployment={currentDeployment}
onUpdate={onUpdate}
project={project}
prodBranchDomains={prodBranchDomains}
/>
</div>
<div className="col-span-2">
<Typography color="gray" placeholder={''}>
^ {deployment.branch}
</Typography>
<Typography color="gray" placeholder={''}>
^ {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
{deployment.commitMessage}
</Typography>
</div>
<div className="col-span-2 flex items-center">
<Typography color="gray" className="grow" placeholder={''}>
^ {relativeTimeMs(deployment.createdAt)} ^{' '}
<Tooltip content={deployment.createdBy.name}>
{formatAddress(deployment.createdBy.name ?? '')}
</Tooltip>
</Typography>
<Menu placement="bottom-start">
<MenuHandler>
<button className="self-start">...</button>
</MenuHandler>
<MenuList placeholder={''}>
<a href={deployment.url} target="_blank" rel="noreferrer">
<MenuItem disabled={!Boolean(deployment.url)} placeholder={''}>
^ Visit
</MenuItem>
</a>
<MenuItem
onClick={() => setAssignDomainDialog(!assignDomainDialog)}
placeholder={''}
>
^ Assign domain
</MenuItem>
<MenuItem
onClick={() => setChangeToProduction(!changeToProduction)}
disabled={!(deployment.environment !== Environment.Production)}
placeholder={''}
>
^ Change to production
</MenuItem>
<hr className="my-3" />
<MenuItem
onClick={() => setRedeployToProduction(!redeployToProduction)}
disabled={
!(
deployment.environment === Environment.Production &&
deployment.isCurrent
)
}
placeholder={''}
>
^ Redeploy to production
</MenuItem>
<MenuItem
onClick={() => setRollbackDeployment(!rollbackDeployment)}
disabled={
deployment.isCurrent ||
deployment.environment !== Environment.Production ||
!Boolean(currentDeployment)
}
placeholder={''}
>
^ Rollback to this version
</MenuItem>
</MenuList>
</Menu>
</div>
<ConfirmDialog
dialogTitle="Change to production?"
handleOpen={() => setChangeToProduction((preVal) => !preVal)}
open={changeToProduction}
confirmButtonTitle="Change"
color="blue"
handleConfirm={async () => {
await updateDeployment();
setChangeToProduction((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small" placeholder={''}>
Upon confirmation, this deployment will be changed to production.
</Typography>
<DeploymentDialogBodyCard deployment={deployment} />
<Typography variant="small" placeholder={''}>
The new deployment will be associated with these domains:
</Typography>
{prodBranchDomains.length > 0 &&
prodBranchDomains.map((value) => {
return (
<Typography
variant="small"
color="blue"
key={value.id}
placeholder={''}
>
^ {value.name}
</Typography>
);
})}
</div>
</ConfirmDialog>
<ConfirmDialog
dialogTitle="Redeploy to production?"
handleOpen={() => setRedeployToProduction((preVal) => !preVal)}
open={redeployToProduction}
confirmButtonTitle="Redeploy"
color="blue"
handleConfirm={async () => {
await redeployToProd();
setRedeployToProduction((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small" placeholder={''}>
Upon confirmation, new deployment will be created with the same
source code as current deployment.
</Typography>
<DeploymentDialogBodyCard deployment={deployment} />
<Typography variant="small" placeholder={''}>
These domains will point to your new deployment:
</Typography>
{deployment.domain?.name && (
<Typography variant="small" color="blue" placeholder={''}>
{deployment.domain?.name}
</Typography>
)}
</div>
</ConfirmDialog>
{Boolean(currentDeployment) && (
<ConfirmDialog
dialogTitle="Rollback to this deployment?"
handleOpen={() => setRollbackDeployment((preVal) => !preVal)}
open={rollbackDeployment}
confirmButtonTitle="Rollback"
color="blue"
handleConfirm={async () => {
await rollbackDeploymentHandler();
setRollbackDeployment((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small" placeholder={''}>
Upon confirmation, this deployment will replace your current
deployment
</Typography>
<DeploymentDialogBodyCard
deployment={currentDeployment}
chip={{
value: 'Live Deployment',
color: 'green',
}}
/>
<DeploymentDialogBodyCard
deployment={deployment}
chip={{
value: 'New Deployment',
color: 'orange',
}}
/>
<Typography variant="small" placeholder={''}>
These domains will point to your new deployment:
</Typography>
<Typography variant="small" color="blue" placeholder={''}>
^ {currentDeployment.domain?.name}
</Typography>
</div>
</ConfirmDialog>
)}
<AssignDomainDialog
open={assignDomainDialog}
handleOpen={() => setAssignDomainDialog(!assignDomainDialog)}
/>
</div>
);
};
@@ -1,17 +1,23 @@
import React from 'react';
import { Deployment } from 'gql-client';
import { Typography, Chip, Card } from '@material-tailwind/react';
import { color } from '@material-tailwind/react/types/components/chip';
import { relativeTimeMs } from '../../../../utils/time';
import { relativeTimeMs } from 'utils/time';
import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants';
import { formatAddress } from '../../../../utils/format';
import {
BranchStrokeIcon,
ClockOutlineIcon,
CommitIcon,
} from 'components/shared/CustomIcon';
import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials';
import { OverflownText } from 'components/shared/OverflownText';
import { Tag, TagProps } from 'components/shared/Tag';
interface DeploymentDialogBodyCardProps {
deployment: Deployment;
chip?: {
value: string;
color?: color;
type?: TagProps['type'];
};
}
@@ -19,31 +25,54 @@ const DeploymentDialogBodyCard = ({
chip,
deployment,
}: DeploymentDialogBodyCardProps) => {
const commit =
deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH) +
' ' +
deployment.commitMessage;
return (
<Card className="p-2 shadow-none" placeholder={''}>
<div className="flex flex-col gap-4 px-4 py-4 rounded-xl bg-base-bg-emphasized text-elements-low-em">
{chip && (
<Chip
className={`w-fit normal-case font-normal`}
size="sm"
value={chip.value}
color={chip.color}
/>
<Tag className="w-fit" size="xs" type={chip.type}>
{chip.value}
</Tag>
)}
{deployment.url && (
<Typography variant="small" className="text-black" placeholder={''}>
<div className="flex flex-col gap-3">
{/* Title */}
<p className="text-sm font-medium text-elements-high-em tracking-[0.006em]">
{deployment.url}
</Typography>
)}
<Typography variant="small" placeholder={''}>
^ {deployment.branch} ^{' '}
{deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
{deployment.commitMessage}
</Typography>
<Typography variant="small" placeholder={''}>
^ {relativeTimeMs(deployment.createdAt)} ^{' '}
{formatAddress(deployment.createdBy.name ?? '')}
</Typography>
</Card>
</p>
{/* Branch & commit */}
<div className="flex items-center gap-6 text-elements-low-em">
<div className="flex items-center gap-2">
<BranchStrokeIcon size={16} />
<p className="text-sm tracking-[0.006em]">{deployment.branch}</p>
</div>
<div className="flex items-center gap-2 w-full">
<CommitIcon size={16} />
<p className="text-sm tracking-[0.006em] max-w-[67.5%] sm:max-w-[80%]">
<OverflownText content={commit}>{commit}</OverflownText>
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 text-elements-low-em">
<ClockOutlineIcon size={16} />
<p className="text-sm tracking-[0.006em]">
{relativeTimeMs(deployment.createdAt)}
</p>
<Avatar
size={20}
type="orange"
initials={getInitials(deployment.createdBy.name ?? '')}
// TODO: Add avatar image URL
// imageSrc={deployment.createdBy.imageUrl}
/>
<p className="text-sm tracking-[0.006em]">
{deployment.createdBy.name ?? 'Unknown'}
</p>
</div>
</div>
);
};
@@ -0,0 +1,204 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { Deployment, Domain, Environment, Project } from 'gql-client';
import { Button } from 'components/shared/Button';
import {
GlobeIcon,
HorizontalDotIcon,
LinkIcon,
RefreshIcon,
RocketIcon,
UndoIcon,
} from 'components/shared/CustomIcon';
import {
Menu,
MenuHandler,
MenuItem,
MenuList,
} from '@material-tailwind/react';
import { ComponentPropsWithRef } from 'react';
import AssignDomainDialog from './AssignDomainDialog';
import { useGQLClient } from 'context/GQLClientContext';
import { cn } from 'utils/classnames';
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
deployment: Deployment;
currentDeployment: Deployment;
onUpdate: () => Promise<void>;
project: Project;
prodBranchDomains: Domain[];
}
export const DeploymentMenu = ({
deployment,
currentDeployment,
onUpdate,
project,
prodBranchDomains,
className,
...props
}: DeploymentMenuProps) => {
const client = useGQLClient();
const [changeToProduction, setChangeToProduction] = useState(false);
const [redeployToProduction, setRedeployToProduction] = useState(false);
const [rollbackDeployment, setRollbackDeployment] = useState(false);
const [assignDomainDialog, setAssignDomainDialog] = useState(false);
const updateDeployment = async () => {
const isUpdated = await client.updateDeploymentToProd(deployment.id);
if (isUpdated) {
await onUpdate();
toast.success('Deployment changed to production');
} else {
toast.error('Unable to change deployment to production');
}
};
const redeployToProd = async () => {
const isRedeployed = await client.redeployToProd(deployment.id);
if (isRedeployed) {
await onUpdate();
toast.success('Redeployed to production');
} else {
toast.error('Unable to redeploy to production');
}
};
const rollbackDeploymentHandler = async () => {
const isRollbacked = await client.rollbackDeployment(
project.id,
deployment.id,
);
if (isRollbacked) {
await onUpdate();
toast.success('Deployment rolled back');
} else {
toast.error('Unable to rollback deployment');
}
};
return (
<>
<div className={cn('max-w-[32px]', className)} {...props}>
<Menu placement="bottom-start">
<MenuHandler>
<Button
shape="default"
size="xs"
variant="unstyled"
className={cn(
'h-8 w-8 rounded-full border border-transparent transition-colors background-transparent',
'[&[aria-expanded=true]]:border [&[aria-expanded=true]]:border-border-interactive [&[aria-expanded=true]]:bg-controls-tertiary [&[aria-expanded=true]]:shadow-button',
)}
leftIcon={<HorizontalDotIcon />}
aria-label="Toggle Menu"
/>
</MenuHandler>
<MenuList className="text-elements-high-em" placeholder={''}>
<MenuItem
className="hover:bg-base-bg-emphasized"
disabled={!Boolean(deployment.url)}
placeholder={''}
>
<a
className="flex items-center gap-3"
href={deployment.url}
target="_blank"
rel="noreferrer"
>
<LinkIcon /> Visit
</a>
</MenuItem>
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => setAssignDomainDialog(!assignDomainDialog)}
placeholder={''}
>
<GlobeIcon /> Assign domain
</MenuItem>
<MenuItem
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>
<hr className="my-3" />
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => setRedeployToProduction(!redeployToProduction)}
disabled={
!(
deployment.environment === Environment.Production &&
deployment.isCurrent
)
}
placeholder={''}
>
<RefreshIcon /> Redeploy to production
</MenuItem>
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => setRollbackDeployment(!rollbackDeployment)}
disabled={
deployment.isCurrent ||
deployment.environment !== Environment.Production ||
!Boolean(currentDeployment)
}
placeholder={''}
>
<UndoIcon /> Rollback to this version
</MenuItem>
</MenuList>
</Menu>
</div>
{/* Dialogs */}
<ChangeStateToProductionDialog
dialogTitle="Change to production?"
confirmButtonTitle="Change"
handleCancel={() => setChangeToProduction((preVal) => !preVal)}
open={changeToProduction}
handleConfirm={async () => {
await updateDeployment();
setChangeToProduction((preVal) => !preVal);
}}
deployment={deployment}
domains={prodBranchDomains}
/>
<ChangeStateToProductionDialog
dialogTitle="Redeploy to production?"
handleCancel={() => setRedeployToProduction((preVal) => !preVal)}
open={redeployToProduction}
confirmButtonTitle="Redeploy"
handleConfirm={async () => {
await redeployToProd();
setRedeployToProduction((preVal) => !preVal);
}}
deployment={deployment}
domains={deployment.domain ? [deployment.domain] : []}
/>
{Boolean(currentDeployment) && (
<ChangeStateToProductionDialog
dialogTitle="Rollback to this deployment?"
handleCancel={() => setRollbackDeployment((preVal) => !preVal)}
open={rollbackDeployment}
confirmButtonTitle="Rollback"
handleConfirm={async () => {
await rollbackDeploymentHandler();
setRollbackDeployment((preVal) => !preVal);
}}
deployment={currentDeployment}
newDeployment={deployment}
domains={currentDeployment.domain ? [currentDeployment.domain] : []}
/>
)}
<AssignDomainDialog
open={assignDomainDialog}
handleOpen={() => setAssignDomainDialog(!assignDomainDialog)}
/>
</>
);
};
@@ -1,10 +1,17 @@
import React, { useEffect, useState } from 'react';
import { DateRange } from 'react-day-picker';
import { IconButton, Option, Select } from '@material-tailwind/react';
import SearchBar from '../../../SearchBar';
import DatePicker from '../../../DatePicker';
import { Input } from 'components/shared/Input';
import {
CheckRadioOutlineIcon,
CrossCircleIcon,
LoaderIcon,
SearchIcon,
TrendingIcon,
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';
export enum StatusOptions {
ALL_STATUS = 'All status',
@@ -15,8 +22,8 @@ export enum StatusOptions {
export interface FilterValue {
searchedBranch: string;
status: StatusOptions;
updateAtRange?: DateRange;
status: StatusOptions | string;
updateAtRange?: Value;
}
interface FilterFormProps {
@@ -27,7 +34,7 @@ interface FilterFormProps {
const FilterForm = ({ value, onChange }: FilterFormProps) => {
const [searchedBranch, setSearchedBranch] = useState(value.searchedBranch);
const [selectedStatus, setSelectedStatus] = useState(value.status);
const [dateRange, setDateRange] = useState<DateRange>();
const [dateRange, setDateRange] = useState<Value>();
useEffect(() => {
onChange({
@@ -43,46 +50,68 @@ const FilterForm = ({ value, onChange }: FilterFormProps) => {
setDateRange(value.updateAtRange);
}, [value]);
const getOptionIcon = (status: StatusOptions) => {
switch (status) {
case StatusOptions.BUILDING:
return <LoaderIcon />;
case StatusOptions.READY:
return <CheckRadioOutlineIcon />;
case StatusOptions.ERROR:
return <WarningTriangleIcon />;
case StatusOptions.ALL_STATUS:
default:
return <TrendingIcon />;
}
};
const statusOptions = Object.values(StatusOptions).map((status) => ({
label: status,
value: status,
leftIcon: getOptionIcon(status),
}));
const handleReset = () => {
setSearchedBranch('');
};
return (
<div className="grid grid-cols-8 gap-2 text-sm text-gray-600">
<div className="col-span-4">
<SearchBar
<div className="xl:grid xl:grid-cols-8 flex flex-col xl:gap-3 gap-3">
<div className="col-span-4 flex items-center">
<Input
placeholder="Search branches"
leftIcon={<SearchIcon />}
rightIcon={
searchedBranch && <CrossCircleIcon onClick={handleReset} />
}
value={searchedBranch}
onChange={(event) => setSearchedBranch(event.target.value)}
onChange={(e) => setSearchedBranch(e.target.value)}
/>
</div>
<div className="col-span-2">
<DatePicker mode="range" selected={dateRange} onSelect={setDateRange} />
<div className="col-span-2 flex items-center">
<DatePicker
className="w-full"
selectRange
value={dateRange}
onChange={setDateRange}
onReset={() => setDateRange(undefined)}
/>
</div>
<div className="col-span-2 relative">
<div className="col-span-2 flex items-center">
<Select
value={selectedStatus}
onChange={(value) => setSelectedStatus(value as StatusOptions)}
leftIcon={getOptionIcon(selectedStatus as StatusOptions)}
options={statusOptions}
clearable
placeholder="All status"
>
{Object.values(StatusOptions).map((status) => (
<Option
className={status === StatusOptions.ALL_STATUS ? 'hidden' : ''}
key={status}
value={status}
>
^ {status}
</Option>
))}
</Select>
{selectedStatus !== StatusOptions.ALL_STATUS && (
<div className="absolute end-1 inset-y-0 my-auto h-8">
<IconButton
onClick={() => setSelectedStatus(StatusOptions.ALL_STATUS)}
className="rounded-full"
size="sm"
placeholder={''}
>
X
</IconButton>
</div>
)}
value={
selectedStatus
? { label: selectedStatus, value: selectedStatus }
: undefined
}
onChange={(item) =>
setSelectedStatus((item as SelectOption).value as StatusOptions)
}
onClear={() => setSelectedStatus('')}
/>
</div>
</div>
);
@@ -0,0 +1,50 @@
import React from 'react';
import { GitCommitWithBranch } from 'types';
import { Heading } from 'components/shared/Heading';
import ActivityCard from './ActivityCard';
import { Button } from 'components/shared/Button';
import { LoadingIcon, LinkIcon } from 'components/shared/CustomIcon';
import { Link } from 'react-router-dom';
export const Activity = ({
isLoading,
activities,
repoPath,
}: {
isLoading: boolean;
activities: GitCommitWithBranch[];
repoPath: string;
}) => {
return (
<div className="col-span-5 md:col-span-2 mr-1">
<div className="flex items-center justify-between">
<Heading className="text-lg leading-6 font-medium">Activity</Heading>
<Link to={`https://github.com/${repoPath}/activity`} target="_blank">
<Button
variant="tertiary"
size="sm"
rightIcon={
<LinkIcon className="group-hover:rotate-45 transition-transform" />
}
>
See all
</Button>
</Link>
</div>
<div className="mt-5">
{isLoading ? (
<div className="grid place-content-center mt-10">
<LoadingIcon className="animate-spin" />
</div>
) : (
activities.map((activity, index) => {
return (
<ActivityCard activity={activity} key={`activity-${index}`} />
);
})
)}
</div>
</div>
);
};
@@ -0,0 +1,86 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { GitCommitWithBranch } from '../../../../../types';
import { Avatar } from 'components/shared/Avatar';
import { Button } from 'components/shared/Button';
import {
ArrowRightCircleFilledIcon,
BranchStrokeIcon,
} from 'components/shared/CustomIcon';
import { formatDistance } from 'date-fns';
import { getInitials } from 'utils/geInitials';
interface ActivityCardProps {
activity: GitCommitWithBranch;
}
const ActivityCard = ({ activity }: ActivityCardProps) => {
const formattedDate = formatDistance(
new Date(activity.commit.author!.date!),
new Date(),
{
addSuffix: true,
},
);
return (
<>
<Link
to={activity.html_url}
target="_blank"
className="p-3 gap-2 focus-within:ring-2 focus-within:ring-controls-primary/40 focus:outline-none rounded-xl transition-colors hover:bg-base-bg-alternate flex group"
>
<div>
<Avatar
type="orange"
size={36}
initials={getInitials(activity.commit.author?.name ?? '')}
imageSrc={activity.author?.avatar_url}
/>
</div>
<div className="flex-1">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
{activity.commit.author?.name}
</span>
<span className="text-elements-low-em text-xs flex items-center gap-2">
<span title={formattedDate} className="whitespace-nowrap">
{formattedDate}
</span>
{/* Dot */}
<span className="w-0.5 h-0.5 rounded-full bg-border-interactive-hovered"></span>
<span className="flex justify-center items-center gap-1.5">
<div>
<BranchStrokeIcon className="w-3 h-3" />
</div>
<span
title={activity.branch.name}
// pseudo to increase hover area
className="before:absolute relative before:h-5 before:-top-4 before:inset-x-0"
>
<span className="line-clamp-1">{activity.branch.name}</span>
</span>
</span>
</span>
<p
title={activity.commit.message}
className="text-sm line-clamp-4 tracking-tight text-elements-mid-em mt-2"
>
{activity.commit.message}
</p>
</div>
<Button
aria-label="Go to commit"
variant="unstyled"
tabIndex={-1}
className="p-0 text-elements-low-em group-focus-within:opacity-100 group-hover:opacity-100 opacity-0 transition-all"
leftIcon={<ArrowRightCircleFilledIcon className="w-5 h-5" />}
/>
</Link>
{/* Separator calc => 100% - 36px (avatar) - 12px (padding-left) - 8px (gap)
*/}
<div className="pt-2 mb-2 ml-auto h-1 w-[calc(100%-36px-12px-8px)] border-b border-border-separator last:border-b-transparent"></div>
</>
);
};
export default ActivityCard;
@@ -0,0 +1 @@
export * from './Activity';
@@ -0,0 +1,29 @@
import React from 'react';
import { PropsWithChildren } from 'react';
import { cloneElement } from 'utils/cloneElement';
interface OverviewInfoProps {
label: string;
icon: React.ReactNode;
}
export const OverviewInfo = ({
label,
icon,
children,
}: PropsWithChildren<OverviewInfoProps>) => {
const styledIcon = cloneElement({
element: icon,
className: 'w-4 h-4',
});
return (
<div className="flex justify-between gap-2 py-3 text-sm items-center">
<div className="flex gap-2 items-center text-elements-high-em">
{styledIcon}
{label}
</div>
<div>{children}</div>
</div>
);
};
@@ -12,9 +12,9 @@ import {
Card,
} from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
enum RefreshStatus {
IDLE,
@@ -118,28 +118,16 @@ const DomainCard = ({
</Menu>
</div>
<ConfirmDialog
dialogTitle="Delete domain?"
handleOpen={() => setDeleteDialogOpen((preVal) => !preVal)}
<DeleteDomainDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
confirmButtonTitle="Yes, Delete domain"
handleConfirm={() => {
deleteDomain();
setDeleteDialogOpen((preVal) => !preVal);
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Once deleted, the project{' '}
<span className="bg-blue-100 rounded-sm p-0.5 text-blue-700">
{project.name}
</span>{' '}
will not be accessible from the domain{' '}
<span className="bg-blue-100 rounded-sm p-0.5 text-blue-700">
{domain.name}.
</span>
</Typography>
</ConfirmDialog>
projectName={project.name}
domainName={domain.name}
/>
</div>
<Typography variant="small" placeholder={''}>
@@ -5,8 +5,8 @@ import { EnvironmentVariable } from 'gql-client';
import { IconButton, Input, Typography } from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteVariableDialog } from 'components/projects/Dialog/DeleteVariableDialog';
const ShowPasswordIcon = ({
handler,
@@ -161,20 +161,12 @@ const EditEnvironmentVariableRow = ({
</>
)}
</div>
<ConfirmDialog
dialogTitle="Delete variable"
handleOpen={() => setDeleteDialogOpen((preVal) => !preVal)}
<DeleteVariableDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
confirmButtonTitle="Yes, Confirm delete"
handleConfirm={removeEnvironmentVariableHandler}
color="red"
>
<Typography variant="small" placeholder={''}>
Are you sure you want to delete the variable&nbsp;
<span className="bg-blue-100">{variable.key}</span>?
</Typography>
</ConfirmDialog>
variableKey={variable.key}
/>
</>
);
};
@@ -3,15 +3,14 @@ import { Permission, User } from 'gql-client';
import {
Select,
Typography,
Option,
Chip,
IconButton,
Tooltip,
} from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { formatAddress } from '../../../../utils/format';
import { formatAddress } from 'utils/format';
import { RemoveMemberDialog } from 'components/projects/Dialog/RemoveMemberDialog';
const PERMISSION_OPTIONS = [
{
@@ -141,25 +140,19 @@ const MemberCard = ({
</div>
)}
</div>
<ConfirmDialog
dialogTitle="Remove member?"
handleOpen={() => setRemoveMemberDialogOpen((preVal) => !preVal)}
<RemoveMemberDialog
handleCancel={() => setRemoveMemberDialogOpen((preVal) => !preVal)}
open={removeMemberDialogOpen}
confirmButtonTitle="Yes, Remove member"
handleConfirm={() => {
setRemoveMemberDialogOpen((preVal) => !preVal);
if (onRemoveProjectMember) {
onRemoveProjectMember();
}
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Once removed, {formatAddress(member.name ?? '')} (
{formatAddress(ethAddress)}@{emailDomain}) will not be able to access
this project.
</Typography>
</ConfirmDialog>
memberName={member.name ?? ''}
ethAddress={ethAddress}
emailDomain={emailDomain}
/>
</div>
);
};
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
import { Button, Typography } from '@material-tailwind/react';
import { GitRepositoryDetails } from '../../../../types';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { GitRepositoryDetails } from 'types';
import { DisconnectRepositoryDialog } from 'components/projects/Dialog/DisconnectRepositoryDialog';
const RepoConnectedSection = ({
linkedRepo,
@@ -34,21 +34,13 @@ const RepoConnectedSection = ({
^ Disconnect
</Button>
</div>
<ConfirmDialog
dialogTitle="Disconnect repository?"
handleOpen={() => setDisconnectRepoDialogOpen((preVal) => !preVal)}
<DisconnectRepositoryDialog
handleCancel={() => setDisconnectRepoDialogOpen((preVal) => !preVal)}
open={disconnectRepoDialogOpen}
confirmButtonTitle="Yes, confirm disconnect"
handleConfirm={() => {
setDisconnectRepoDialogOpen((preVal) => !preVal);
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Any data tied to your Git project may become misconfigured. Are you
sure you want to continue?
</Typography>
</ConfirmDialog>
/>
</div>
);
};
@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { Button, Typography } from '@material-tailwind/react';
import { Button } from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog';
interface WebhookCardProps {
webhookUrl: string;
@@ -15,7 +15,6 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
return (
<div className="flex justify-between w-full mb-3">
{webhookUrl}
<div className="flex gap-3">
<Button
size="sm"
@@ -38,23 +37,15 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
X
</Button>
</div>
<ConfirmDialog
dialogTitle="Delete webhook"
handleOpen={() => setDeleteDialogOpen((preVal) => !preVal)}
<DeleteWebhookDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
confirmButtonTitle="Yes, Confirm delete"
handleConfirm={() => {
setDeleteDialogOpen((preVal) => !preVal);
onDelete();
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Are you sure you want to delete the variable{' '}
<span className="bg-blue-100 p-0.5 rounded-sm">{webhookUrl}</span>?
</Typography>
</ConfirmDialog>
webhookUrl={webhookUrl}
/>
</div>
);
};
@@ -2,7 +2,7 @@ import { tv, type VariantProps } from 'tailwind-variants';
export const avatarTheme = tv(
{
base: ['relative', 'block', 'rounded-full', 'overflow-hidden'],
base: ['relative', 'block', 'rounded-full', 'overflow-hidden', 'shrink-0'],
slots: {
image: [
'h-full',
@@ -14,11 +14,13 @@ export const buttonTheme = tv(
'whitespace-nowrap',
'focus-ring',
'disabled:cursor-not-allowed',
'transition-colors',
'duration-150',
],
variants: {
size: {
lg: ['gap-2', 'py-3.5', 'px-5', 'text-base', 'tracking-[-0.011em]'],
md: ['gap-2', 'py-3.25', 'px-5', 'text-sm', 'tracking-[-0.006em]'],
md: ['gap-2', 'py-3', 'px-5', 'text-sm', 'tracking-[-0.006em]'],
sm: ['gap-1', 'py-2', 'px-3', 'text-xs'],
xs: ['gap-1', 'py-1', 'px-2', 'text-xs'],
},
@@ -61,14 +63,13 @@ export const buttonTheme = tv(
tertiary: [
'text-elements-on-tertiary',
'border',
'border-border-interactive/10',
'border-border-interactive',
'bg-controls-tertiary',
'shadow-button',
'hover:bg-controls-tertiary-hovered',
'hover:border-border-interactive-hovered',
'hover:border-border-interactive-hovered/[0.14]',
'focus-visible:bg-controls-tertiary-hovered',
'focus-visible:border-border-interactive-hovered',
'focus-visible:border-border-interactive-hovered/[0.14]',
'disabled:text-elements-on-disabled',
'disabled:bg-controls-disabled',
'disabled:border-transparent',
@@ -81,10 +82,8 @@ export const buttonTheme = tv(
'bg-transparent',
'hover:bg-controls-tertiary-hovered',
'hover:border-border-interactive-hovered',
'hover:border-border-interactive-hovered/[0.14]',
'focus-visible:bg-controls-tertiary-hovered',
'focus-visible:border-border-interactive-hovered',
'focus-visible:border-border-interactive-hovered/[0.14]',
'disabled:text-elements-on-disabled',
'disabled:bg-controls-disabled',
'disabled:border-transparent',
@@ -109,10 +108,8 @@ export const buttonTheme = tv(
'bg-transparent',
'hover:bg-controls-tertiary-hovered',
'hover:border-border-interactive-hovered',
'hover:border-border-interactive-hovered/[0.14]',
'focus-visible:bg-controls-tertiary-hovered',
'focus-visible:border-border-interactive-hovered',
'focus-visible:border-border-interactive-hovered/[0.14]',
'disabled:text-elements-on-disabled',
'disabled:bg-controls-disabled',
'disabled:border-transparent',
@@ -153,7 +150,7 @@ export const buttonTheme = tv(
{
size: 'md',
iconOnly: true,
class: ['py-3.25', 'px-3.25'],
class: ['py-3', 'px-3'],
},
{
size: 'sm',
@@ -19,6 +19,7 @@ import {
import './Calendar.css';
import { format } from 'date-fns';
import { cn } from 'utils/classnames';
type ValuePiece = Date | null;
export type Value = ValuePiece | [ValuePiece, ValuePiece];
@@ -63,6 +64,11 @@ export interface CalendarProps extends CustomReactCalendarProps, CalendarTheme {
* @returns None
*/
onCancel?: () => void;
/**
* Optional callback function that is called when a reset action is triggered.
* @returns None
*/
onReset?: () => void;
}
/**
@@ -80,6 +86,7 @@ export const Calendar = ({
actions,
onSelect,
onCancel,
onReset,
onChange: onChangeProp,
...props
}: CalendarProps): JSX.Element => {
@@ -217,6 +224,11 @@ export const Calendar = ({
[setValue, setActiveDate, changeNavigationLabel, selectRange],
);
const handleReset = useCallback(() => {
setValue(null);
onReset?.();
}, [setValue, onReset]);
return (
<div
{...wrapperProps}
@@ -276,21 +288,30 @@ export const Calendar = ({
{/* Footer or CTA */}
<div
{...footerProps}
className={footer({ className: footerProps?.className })}
className={cn(footer({ className: footerProps?.className }), {
'justify-between': Boolean(value),
})}
>
{actions ? (
actions
) : (
<>
<Button variant="tertiary" onClick={onCancel}>
Cancel
</Button>
<Button
disabled={!value}
onClick={() => (value ? onSelect?.(value) : null)}
>
Select
</Button>
{value && (
<Button variant="danger" onClick={handleReset}>
Reset
</Button>
)}
<div className="space-x-3">
<Button variant="tertiary" onClick={onCancel}>
Cancel
</Button>
<Button
disabled={!value}
onClick={() => (value ? onSelect?.(value) : null)}
>
Select
</Button>
</div>
</>
)}
</div>
@@ -1,64 +1,52 @@
import React from 'react';
import React, { ReactNode } from 'react';
import { Modal, ModalProps } from './Modal';
import { Button, ButtonOrLinkProps } from './Button';
import { color } from '@material-tailwind/react/types/components/button';
import {
Typography,
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
} from '@material-tailwind/react';
type ConfirmDialogProp = {
children: React.ReactNode;
dialogTitle: string;
export type ConfirmDialogProps = ModalProps & {
children?: ReactNode;
dialogTitle?: string;
open: boolean;
handleOpen: () => void;
confirmButtonTitle: string;
handleCancel: () => void;
confirmButtonTitle?: string;
handleConfirm?: () => void;
color: color;
cancelButtonProps?: ButtonOrLinkProps;
confirmButtonProps?: ButtonOrLinkProps;
};
const ConfirmDialog = ({
children,
dialogTitle,
open,
handleOpen,
handleCancel,
confirmButtonTitle,
handleConfirm,
color,
}: ConfirmDialogProp) => {
cancelButtonProps,
confirmButtonProps,
...props
}: ConfirmDialogProps) => {
// Close the dialog when the user clicks outside of it
const handleOpenChange = (open: boolean) => {
if (!open) return handleCancel?.();
};
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader className="flex justify-between" placeholder={''}>
<Typography variant="h6" placeholder={''}>
{dialogTitle}{' '}
</Typography>
<Button
variant="outlined"
onClick={handleOpen}
className=" rounded-full"
placeholder={''}
>
X
</Button>
</DialogHeader>
<DialogBody placeholder={''}>{children}</DialogBody>
<DialogFooter className="flex justify-start gap-2" placeholder={''}>
<Button variant="outlined" onClick={handleOpen} placeholder={''}>
Cancel
</Button>
<Button
variant="gradient"
color={color}
onClick={handleConfirm}
placeholder={''}
>
{confirmButtonTitle}
</Button>
</DialogFooter>
</Dialog>
<Modal {...props} onOpenChange={handleOpenChange}>
<Modal.Content>
<Modal.Header>{dialogTitle}</Modal.Header>
<Modal.Body>{children}</Modal.Body>
<Modal.Footer>
<Button
variant="tertiary"
onClick={handleCancel}
{...cancelButtonProps}
>
Cancel
</Button>
<Button onClick={handleConfirm} {...confirmButtonProps}>
{confirmButtonTitle}
</Button>
</Modal.Footer>
</Modal.Content>
</Modal>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ArrowLeftCircleFilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM10.4697 15.7803C10.7626 16.0732 11.2374 16.0732 11.5303 15.7803C11.8232 15.4874 11.8232 15.0126 11.5303 14.7197L9.56066 12.75H16.25C16.6642 12.75 17 12.4142 17 12C17 11.5858 16.6642 11.25 16.25 11.25H9.56066L11.5303 9.28033C11.8232 8.98744 11.8232 8.51256 11.5303 8.21967C11.2374 7.92678 10.7626 7.92678 10.4697 8.21967L7.21967 11.4697C6.92678 11.7626 6.92678 12.2374 7.21967 12.5303L10.4697 15.7803Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ArrowRightCircleFilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon
{...props}
fill="none"
height="32"
viewBox="0 0 32 32"
width="32"
>
<path
clipRule="evenodd"
d="M29.3334 16C29.3334 8.63619 23.3638 2.66666 16 2.66666C8.63622 2.66666 2.66669 8.63619 2.66669 16C2.66669 23.3638 8.63622 29.3333 16 29.3333C23.3638 29.3333 29.3334 23.3638 29.3334 16ZM18.1144 17.3333L16.3905 19.0572C15.8698 19.5779 15.8698 20.4221 16.3905 20.9428C16.9112 21.4635 17.7555 21.4635 18.2762 20.9428L21.3334 17.8856C22.3748 16.8442 22.3748 15.1558 21.3334 14.1144L18.2762 11.0572C17.7555 10.5365 16.9112 10.5365 16.3905 11.0572C15.8698 11.5779 15.8698 12.4221 16.3905 12.9428L18.1144 14.6667H10.6667C9.93031 14.6667 9.33335 15.2636 9.33335 16C9.33335 16.7364 9.93031 17.3333 10.6667 17.3333H18.1144Z"
fill="currentColor"
fillRule="evenodd"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ArrowRightCircleIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="17"
height="16"
viewBox="0 0 17 16"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.3333 8C15.3333 4.3181 12.3486 1.33333 8.66667 1.33333C4.98477 1.33333 2 4.3181 2 8C2 11.6819 4.98477 14.6667 8.66667 14.6667C12.3486 14.6667 15.3333 11.6819 15.3333 8ZM10.2929 8.5L8.97978 9.81311C8.78452 10.0084 8.78452 10.325 8.97978 10.5202C9.17504 10.7155 9.49162 10.7155 9.68689 10.5202L11.3821 8.82495C11.8378 8.36934 11.8378 7.63065 11.3821 7.17504L9.68689 5.47977C9.49162 5.28451 9.17504 5.28451 8.97978 5.47977C8.78452 5.67504 8.78452 5.99162 8.97978 6.18688L10.2929 7.5H5.83333C5.55719 7.5 5.33333 7.72385 5.33333 8C5.33333 8.27614 5.55719 8.5 5.83333 8.5H10.2929Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,20 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const BranchStrokeIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M4.66667 4.99984C5.58714 4.99984 6.33333 4.25365 6.33333 3.33317C6.33333 2.4127 5.58714 1.6665 4.66667 1.6665C3.74619 1.6665 3 2.4127 3 3.33317C3 4.25365 3.74619 4.99984 4.66667 4.99984ZM4.66667 4.99984V10.9998M4.66667 10.9998C3.74619 10.9998 3 11.746 3 12.6665C3 13.587 3.74619 14.3332 4.66667 14.3332C5.58714 14.3332 6.33333 13.587 6.33333 12.6665C6.33333 11.746 5.58714 10.9998 4.66667 10.9998ZM4.66667 10.9998V9.33317C4.66667 8.59679 5.26362 7.99984 6 7.99984H10C10.7364 7.99984 11.3333 7.40288 11.3333 6.6665V4.99984M11.3333 4.99984C12.2538 4.99984 13 4.25365 13 3.33317C13 2.4127 12.2538 1.6665 11.3333 1.6665C10.4129 1.6665 9.66667 2.4127 9.66667 3.33317C9.66667 4.25365 10.4129 4.99984 11.3333 4.99984Z"
stroke="currentColor"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,22 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const BuildingIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M3.75 19.25H14.25M3.75 19.25V5.75C3.75 4.64543 4.64543 3.75 5.75 3.75H12.25C13.3546 3.75 14.25 4.64543 14.25 5.75V8M3.75 19.25H1.75M14.25 19.25V8M14.25 19.25H20.25M14.25 8H18.25C19.3546 8 20.25 8.89543 20.25 10V19.25M20.25 19.25H22.25M10.25 8.75H7.75M7.75 12.75H10.25"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,41 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CalendarDaysIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M2.3335 5.00016H13.6668M4.33349 13.6668H11.6668C12.7714 13.6668 13.6668 12.7714 13.6668 11.6668V4.3335C13.6668 3.22893 12.7714 2.3335 11.6668 2.3335H4.3335C3.22893 2.3335 2.3335 3.22893 2.3335 4.33349V11.6668C2.3335 12.7714 3.22893 13.6668 4.33349 13.6668Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.6665 7.8335C4.6665 7.55735 4.89036 7.3335 5.1665 7.3335C5.44265 7.3335 5.6665 7.55735 5.6665 7.8335C5.6665 8.10964 5.44265 8.3335 5.1665 8.3335C4.89036 8.3335 4.6665 8.10964 4.6665 7.8335Z"
fill="currentColor"
/>
<path
d="M4.6665 10.8335C4.6665 10.5574 4.89036 10.3335 5.1665 10.3335C5.44265 10.3335 5.6665 10.5574 5.6665 10.8335C5.6665 11.1096 5.44265 11.3335 5.1665 11.3335C4.89036 11.3335 4.6665 11.1096 4.6665 10.8335Z"
fill="currentColor"
/>
<path
d="M7.49984 10.8335C7.49984 10.5574 7.72369 10.3335 7.99984 10.3335C8.27598 10.3335 8.49984 10.5574 8.49984 10.8335C8.49984 11.1096 8.27598 11.3335 7.99984 11.3335C7.72369 11.3335 7.49984 11.1096 7.49984 10.8335Z"
fill="currentColor"
/>
<path
d="M7.49984 7.8335C7.49984 7.55735 7.72369 7.3335 7.99984 7.3335C8.27598 7.3335 8.49984 7.55735 8.49984 7.8335C8.49984 8.10964 8.27598 8.3335 7.99984 8.3335C7.72369 8.3335 7.49984 8.10964 7.49984 7.8335Z"
fill="currentColor"
/>
<path
d="M10.3332 7.8335C10.3332 7.55735 10.557 7.3335 10.8332 7.3335C11.1093 7.3335 11.3332 7.55735 11.3332 7.8335C11.3332 8.10964 11.1093 8.3335 10.8332 8.3335C10.557 8.3335 10.3332 8.10964 10.3332 7.8335Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -5,16 +5,16 @@ export const CalendarIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="19"
viewBox="0 0 18 19"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5 0C5.55228 0 6 0.447715 6 1V2H12V1C12 0.447715 12.4477 0 13 0C13.5523 0 14 0.447715 14 1V2H16C17.1046 2 18 2.89543 18 4V17C18 18.1046 17.1046 19 16 19H2C0.89543 19 0 18.1046 0 17V4C0 2.89543 0.895431 2 2 2H4V1C4 0.447715 4.44772 0 5 0ZM2 9V17H16V9H2Z"
fill="currentColor"
d="M2.625 7.125H15.375M5.625 3.375V1.875M12.375 3.375V1.875M4.125 15.375H13.875C14.7034 15.375 15.375 14.7034 15.375 13.875V4.875C15.375 4.04657 14.7034 3.375 13.875 3.375H4.125C3.29657 3.375 2.625 4.04657 2.625 4.875V13.875C2.625 14.7034 3.29657 15.375 4.125 15.375Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CheckRadioOutlineIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
d="M11.25 7.125L7.875 11.25L6.375 9.75M16.125 9C16.125 12.935 12.935 16.125 9 16.125C5.06497 16.125 1.875 12.935 1.875 9C1.875 5.06497 5.06497 1.875 9 1.875C12.935 1.875 16.125 5.06497 16.125 9Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -3,17 +3,11 @@ import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CheckRoundFilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<CustomIcon width="20" height="20" viewBox="0 0 20 20" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM15.774 10.1333C16.1237 9.70582 16.0607 9.0758 15.6332 8.72607C15.2058 8.37635 14.5758 8.43935 14.226 8.86679L10.4258 13.5116L9.20711 12.2929C8.81658 11.9024 8.18342 11.9024 7.79289 12.2929C7.40237 12.6834 7.40237 13.3166 7.79289 13.7071L9.79289 15.7071C9.99267 15.9069 10.2676 16.0129 10.5498 15.9988C10.832 15.9847 11.095 15.8519 11.274 15.6333L15.774 10.1333Z"
d="M10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0ZM13.774 8.13327C14.1237 7.70582 14.0607 7.0758 13.6332 6.72607C13.2058 6.37635 12.5758 6.43935 12.226 6.86679L8.42576 11.5116L7.20711 10.2929C6.81658 9.9024 6.18342 9.9024 5.79289 10.2929C5.40237 10.6834 5.40237 11.3166 5.79289 11.7071L7.79289 13.7071C7.99267 13.9069 8.26764 14.0129 8.54981 13.9988C8.83199 13.9847 9.09505 13.8519 9.27396 13.6333L13.774 8.13327Z"
fill="currentColor"
/>
</CustomIcon>
@@ -0,0 +1,20 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ChevronDoubleDownIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="20"
height="21"
viewBox="0 0 20 21"
fill="none"
{...props}
>
<path
d="M6.66699 12.0174L10.0003 15.3507L13.3337 12.0174M6.66699 6.18408L10.0003 9.51742L13.3337 6.18408"
stroke="currentColor"
strokeLinecap="square"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ChevronUpDown = (props: CustomIconProps) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M8 15L12 19L16 15M8 9L12 5L16 9"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,20 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CirclePlaceholderOnIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
d="M16.125 9C16.125 12.935 12.935 16.125 9 16.125C5.06497 16.125 1.875 12.935 1.875 9C1.875 5.06497 5.06497 1.875 9 1.875C12.935 1.875 16.125 5.06497 16.125 9Z"
stroke="currentColor"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ClockOutlineIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M7.99984 5V8L9.99984 10M14.3332 8C14.3332 11.4978 11.4976 14.3333 7.99984 14.3333C4.50203 14.3333 1.6665 11.4978 1.6665 8C1.6665 4.5022 4.50203 1.66667 7.99984 1.66667C11.4976 1.66667 14.3332 4.5022 14.3332 8Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,26 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CommitIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<g clipPath="url(#clip0_755_4058)">
<path
d="M15.5 7.50001H11.4637C11.343 6.66752 10.9264 5.90636 10.2904 5.35589C9.65427 4.80541 8.84121 4.50244 8 4.50244C7.15879 4.50244 6.34573 4.80541 5.70964 5.35589C5.07355 5.90636 4.65701 6.66752 4.53625 7.50001H0.5C0.367392 7.50001 0.240215 7.55269 0.146447 7.64646C0.0526784 7.74023 0 7.8674 0 8.00001C0 8.13262 0.0526784 8.2598 0.146447 8.35357C0.240215 8.44733 0.367392 8.50001 0.5 8.50001H4.53625C4.65701 9.33251 5.07355 10.0937 5.70964 10.6441C6.34573 11.1946 7.15879 11.4976 8 11.4976C8.84121 11.4976 9.65427 11.1946 10.2904 10.6441C10.9264 10.0937 11.343 9.33251 11.4637 8.50001H15.5C15.6326 8.50001 15.7598 8.44733 15.8536 8.35357C15.9473 8.2598 16 8.13262 16 8.00001C16 7.8674 15.9473 7.74023 15.8536 7.64646C15.7598 7.55269 15.6326 7.50001 15.5 7.50001ZM8 10.5C7.50555 10.5 7.0222 10.3534 6.61107 10.0787C6.19995 9.80398 5.87952 9.41354 5.6903 8.95672C5.50108 8.49991 5.45157 7.99724 5.54804 7.51229C5.6445 7.02733 5.8826 6.58188 6.23223 6.23224C6.58186 5.88261 7.02732 5.64451 7.51227 5.54805C7.99723 5.45158 8.49989 5.50109 8.95671 5.69031C9.41352 5.87953 9.80397 6.19996 10.0787 6.61109C10.3534 7.02221 10.5 7.50556 10.5 8.00001C10.5 8.66305 10.2366 9.29894 9.76777 9.76778C9.29893 10.2366 8.66304 10.5 8 10.5Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_755_4058">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CopyIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.33301 2.66665C1.33301 1.93027 1.92996 1.33331 2.66634 1.33331H9.33301C10.0694 1.33331 10.6663 1.93027 10.6663 2.66665V5.33331H13.333C14.0694 5.33331 14.6663 5.93027 14.6663 6.66665V13.3333C14.6663 14.0697 14.0694 14.6666 13.333 14.6666H6.66634C5.92996 14.6666 5.33301 14.0697 5.33301 13.3333V10.6666H2.66634C1.92996 10.6666 1.33301 10.0697 1.33301 9.33331V2.66665ZM9.66634 5.33331H6.66634C5.92996 5.33331 5.33301 5.93027 5.33301 6.66665V9.66665H2.66634C2.48225 9.66665 2.33301 9.51741 2.33301 9.33331V2.66665C2.33301 2.48255 2.48225 2.33331 2.66634 2.33331H9.33301C9.5171 2.33331 9.66634 2.48255 9.66634 2.66665V5.33331Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CrossCircleIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.5 9C1.5 4.85786 4.85786 1.5 9 1.5C13.1421 1.5 16.5 4.85786 16.5 9C16.5 13.1421 13.1421 16.5 9 16.5C4.85786 16.5 1.5 13.1421 1.5 9ZM7.01516 6.48484C6.86872 6.33839 6.63128 6.33839 6.48484 6.48484C6.33839 6.63128 6.33839 6.86872 6.48484 7.01516L8.46967 9L6.48484 10.9848C6.33839 11.1313 6.33839 11.3687 6.48484 11.5152C6.63128 11.6616 6.86872 11.6616 7.01516 11.5152L9 9.53033L10.9848 11.5152C11.1313 11.6616 11.3687 11.6616 11.5152 11.5152C11.6616 11.3687 11.6616 11.1313 11.5152 10.9848L9.53033 9L11.5152 7.01516C11.6616 6.86872 11.6616 6.63128 11.5152 6.48484C11.3687 6.33839 11.1313 6.33839 10.9848 6.48484L9 8.46967L7.01516 6.48484Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,22 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CursorBoxIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M13.5 7V3.83333C13.5 3.09695 12.903 2.5 12.1667 2.5H3.83333C3.09695 2.5 2.5 3.09695 2.5 3.83333V12.1667C2.5 12.903 3.09695 13.5 3.83333 13.5H7M9.43391 14.0852L7.5218 7.9391C7.44202 7.68269 7.68269 7.44202 7.9391 7.5218L14.0852 9.43391C14.3658 9.52122 14.4043 9.90262 14.1468 10.0443L11.5848 11.4534C11.5294 11.4838 11.4838 11.5294 11.4534 11.5848L10.0443 14.1468C9.90262 14.4043 9.52122 14.3658 9.43391 14.0852Z"
stroke="currentColor"
strokeWidth="1.05263"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,27 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const GitTeaIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
d="M12.372 15.1312L8.40635 13.225C8.01573 13.0375 7.84698 12.5625 8.0376 12.1687L9.94385 8.20308C10.1314 7.81245 10.6064 7.6437 11.0001 7.83433C11.5376 8.0937 11.847 8.24058 11.847 8.24058L11.8439 4.82808L12.3657 4.82495L12.3689 8.48433C12.3689 8.48433 14.1626 9.24058 14.9657 9.73745C15.0814 9.80933 15.2845 9.94995 15.3689 10.1875C15.4345 10.3781 15.4314 10.5968 15.3376 10.7906L13.4314 14.7562C13.2376 15.1531 12.7626 15.3218 12.372 15.1312Z"
fill="white"
/>
<path
d="M19.4595 4.68123C19.3314 4.5531 19.1595 4.55623 19.1595 4.55623C19.1595 4.55623 15.497 4.76248 13.6001 4.80623C13.1845 4.8156 12.772 4.82498 12.3626 4.8281C12.3626 6.04998 12.3626 7.27185 12.3626 8.4906C12.1907 8.40935 12.0157 8.32498 11.8439 8.24373C11.8439 7.10623 11.8407 4.83123 11.8407 4.83123C10.9345 4.84373 9.05324 4.76248 9.05324 4.76248C9.05324 4.76248 4.63449 4.5406 4.15324 4.49685C3.84699 4.4781 3.45011 4.43123 2.93449 4.54373C2.66261 4.59998 1.88761 4.77498 1.25324 5.38435C-0.153014 6.63748 0.206361 8.63123 0.250111 8.93123C0.303236 9.29685 0.465736 10.3125 1.24074 11.1969C2.67199 12.95 5.75324 12.9094 5.75324 12.9094C5.75324 12.9094 6.13136 13.8125 6.70949 14.6437C7.49074 15.6781 8.29386 16.4844 9.07511 16.5812C11.0439 16.5812 14.9782 16.5781 14.9782 16.5781C14.9782 16.5781 15.3532 16.5812 15.8626 16.2562C16.3001 15.9906 16.6907 15.525 16.6907 15.525C16.6907 15.525 17.0939 15.0937 17.6564 14.1094C17.8282 13.8062 17.972 13.5125 18.097 13.2344C18.097 13.2344 19.822 9.57498 19.822 6.01248C19.7876 4.93435 19.522 4.74373 19.4595 4.68123ZM3.92511 11.0594C3.11574 10.7937 2.77199 10.475 2.77199 10.475C2.77199 10.475 2.17511 10.0562 1.87511 9.23123C1.35949 7.84998 1.83136 7.00623 1.83136 7.00623C1.83136 7.00623 2.09386 6.3031 3.03449 6.06873C3.46574 5.9531 4.00324 5.97185 4.00324 5.97185C4.00324 5.97185 4.22511 7.8281 4.49386 8.9156C4.71886 9.8281 5.26886 11.3437 5.26886 11.3437C5.26886 11.3437 4.45324 11.2469 3.92511 11.0594ZM13.3095 14.4219C13.3095 14.4219 13.1189 14.875 12.697 14.9031C12.5157 14.9156 12.3751 14.8656 12.3751 14.8656C12.3751 14.8656 12.3657 14.8625 12.2095 14.8L8.68136 13.0812C8.68136 13.0812 8.34074 12.9031 8.28136 12.5937C8.21261 12.3406 8.36574 12.0281 8.36574 12.0281L10.0626 8.53123C10.0626 8.53123 10.2126 8.2281 10.4439 8.12498C10.4626 8.1156 10.5157 8.09373 10.5845 8.0781C10.8376 8.01248 11.147 8.1656 11.147 8.1656L14.6064 9.84373C14.6064 9.84373 15.0001 10.0219 15.0845 10.35C15.1439 10.5812 15.0689 10.7875 15.0282 10.8875C14.8314 11.3687 13.3095 14.4219 13.3095 14.4219Z"
fill="#609926"
/>
<path
d="M10.2126 11.8781C9.95632 11.8813 9.73132 12.0594 9.67195 12.3094C9.61257 12.5594 9.73445 12.8187 9.95632 12.9344C10.1969 13.0594 10.5032 12.9906 10.6657 12.7656C10.8251 12.5437 10.8001 12.2375 10.6094 12.0437L11.3594 10.5094C11.4063 10.5125 11.4751 10.5156 11.5532 10.4937C11.6813 10.4656 11.7751 10.3813 11.7751 10.3813C11.9063 10.4375 12.0438 10.5 12.1876 10.5719C12.3376 10.6469 12.4782 10.725 12.6063 10.8C12.6344 10.8156 12.6626 10.8344 12.6938 10.8594C12.7438 10.9 12.8001 10.9562 12.8407 11.0312C12.9001 11.2031 12.7813 11.4969 12.7813 11.4969C12.7094 11.7344 12.2063 12.7656 12.2063 12.7656C11.9532 12.7594 11.7282 12.9219 11.6532 13.1562C11.5719 13.4094 11.6876 13.6969 11.9313 13.8219C12.1751 13.9469 12.4751 13.875 12.6344 13.6562C12.7907 13.4437 12.7782 13.1469 12.6001 12.95C12.6594 12.8344 12.7157 12.7188 12.7751 12.5969C12.9313 12.2719 13.1969 11.6469 13.1969 11.6469C13.2251 11.5938 13.3751 11.325 13.2813 10.9812C13.2032 10.625 12.8876 10.4594 12.8876 10.4594C12.5063 10.2125 11.9751 9.98438 11.9751 9.98438C11.9751 9.98438 11.9751 9.85625 11.9407 9.7625C11.9063 9.66562 11.8532 9.60313 11.8188 9.56563C11.9657 9.2625 12.1126 8.9625 12.2594 8.65938C12.1313 8.59688 12.0063 8.53437 11.8782 8.46875C11.7282 8.775 11.5751 9.08438 11.4251 9.39062C11.2157 9.3875 11.0219 9.5 10.9219 9.68437C10.8157 9.88125 10.8376 10.125 10.9813 10.3031C10.7251 10.8281 10.4688 11.3531 10.2126 11.8781Z"
fill="#609926"
/>
</CustomIcon>
);
};
@@ -0,0 +1,19 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const GithubStrokeIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
d="M13.0194 4.73001C13.1722 4.23701 13.2212 3.71768 13.1634 3.20479C13.1055 2.6919 12.942 2.19656 12.6831 1.75001C12.6392 1.67398 12.5761 1.61085 12.5001 1.56697C12.424 1.52308 12.3378 1.49999 12.25 1.50001C11.6676 1.49879 11.093 1.6338 10.5721 1.89425C10.0512 2.1547 9.59845 2.53337 9.25 3.00001H7.75C7.40155 2.53337 6.94878 2.1547 6.42788 1.89425C5.90698 1.6338 5.33238 1.49879 4.75 1.50001C4.66221 1.49999 4.57597 1.52308 4.49994 1.56697C4.4239 1.61085 4.36077 1.67398 4.31687 1.75001C4.05803 2.19656 3.89452 2.6919 3.83664 3.20479C3.77877 3.71768 3.8278 4.23701 3.98062 4.73001C3.6717 5.26921 3.50623 5.87862 3.5 6.50001V7.00001C3.50105 7.84601 3.80817 8.66306 4.36464 9.30029C4.92111 9.93752 5.68935 10.3519 6.5275 10.4669C6.18538 10.9047 5.99968 11.4444 6 12V12.5H4.5C4.10218 12.5 3.72064 12.342 3.43934 12.0607C3.15804 11.7794 3 11.3978 3 11C3 10.6717 2.93534 10.3466 2.8097 10.0433C2.68406 9.73999 2.49991 9.46439 2.26777 9.23224C2.03562 9.00009 1.76002 8.81595 1.45671 8.69031C1.15339 8.56467 0.828305 8.50001 0.5 8.50001C0.367392 8.50001 0.240215 8.55269 0.146447 8.64645C0.0526784 8.74022 0 8.8674 0 9.00001C0 9.13262 0.0526784 9.25979 0.146447 9.35356C0.240215 9.44733 0.367392 9.50001 0.5 9.50001C0.897825 9.50001 1.27936 9.65804 1.56066 9.93935C1.84196 10.2207 2 10.6022 2 11C2 11.663 2.26339 12.2989 2.73223 12.7678C3.20107 13.2366 3.83696 13.5 4.5 13.5H6V14.5C6 14.6326 6.05268 14.7598 6.14645 14.8536C6.24021 14.9473 6.36739 15 6.5 15C6.63261 15 6.75979 14.9473 6.85355 14.8536C6.94732 14.7598 7 14.6326 7 14.5V12C7 11.6022 7.15804 11.2207 7.43934 10.9393C7.72064 10.658 8.10218 10.5 8.5 10.5C8.89782 10.5 9.27936 10.658 9.56066 10.9393C9.84196 11.2207 10 11.6022 10 12V14.5C10 14.6326 10.0527 14.7598 10.1464 14.8536C10.2402 14.9473 10.3674 15 10.5 15C10.6326 15 10.7598 14.9473 10.8536 14.8536C10.9473 14.7598 11 14.6326 11 14.5V12C11.0003 11.4444 10.8146 10.9047 10.4725 10.4669C11.3107 10.3519 12.0789 9.93752 12.6354 9.30029C13.1918 8.66306 13.4989 7.84601 13.5 7.00001V6.50001C13.4938 5.87862 13.3283 5.26921 13.0194 4.73001ZM12.5 7.00001C12.5 7.66305 12.2366 8.29893 11.7678 8.76778C11.2989 9.23662 10.663 9.50001 10 9.50001H7C6.33696 9.50001 5.70107 9.23662 5.23223 8.76778C4.76339 8.29893 4.5 7.66305 4.5 7.00001V6.50001C4.50613 6.00002 4.65582 5.51233 4.93125 5.09501C4.98259 5.02733 5.01585 4.94769 5.02788 4.8636C5.03991 4.77951 5.03031 4.69374 5 4.61438C4.86976 4.27851 4.80709 3.92023 4.81556 3.56009C4.82403 3.19995 4.90349 2.84501 5.04938 2.51563C5.45857 2.55965 5.85262 2.6952 6.2023 2.91224C6.55198 3.12928 6.84832 3.42223 7.06938 3.76938C7.11442 3.83982 7.17641 3.89784 7.24968 3.93813C7.32294 3.97842 7.40514 3.9997 7.48875 4.00001H9.51062C9.59455 4.00001 9.67713 3.97889 9.75075 3.93858C9.82437 3.89828 9.88666 3.84009 9.93188 3.76938C10.1529 3.4222 10.4492 3.12922 10.7989 2.91218C11.1486 2.69514 11.5427 2.55961 11.9519 2.51563C12.0976 2.8451 12.1768 3.20007 12.1851 3.56021C12.1933 3.92036 12.1304 4.27859 12 4.61438C11.9698 4.69298 11.9597 4.77788 11.9706 4.86138C11.9815 4.94487 12.0131 5.02432 12.0625 5.09251C12.3407 5.50984 12.4926 5.99853 12.5 6.50001V7.00001Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const LinkChainIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M9.75027 5.52371L10.7168 4.55722C13.1264 2.14759 17.0332 2.14759 19.4428 4.55722C21.8524 6.96684 21.8524 10.8736 19.4428 13.2832L18.4742 14.2519M5.52886 9.74513L4.55722 10.7168C2.14759 13.1264 2.1476 17.0332 4.55722 19.4428C6.96684 21.8524 10.8736 21.8524 13.2832 19.4428L14.2478 18.4782M9.5 14.5L14.5 9.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const LinkIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.99634 4.3125C5.99634 4.00184 6.24818 3.75 6.55884 3.75H12.1875C13.3266 3.75 14.25 4.67341 14.25 5.8125V12.1844C14.25 12.495 13.9982 12.7469 13.6875 12.7469C13.3769 12.7469 13.125 12.495 13.125 12.1844V5.8125C13.125 5.76745 13.1218 5.72315 13.1157 5.6798L5.27275 13.5227C5.05308 13.7424 4.69692 13.7424 4.47725 13.5227C4.25758 13.3031 4.25758 12.9469 4.47725 12.7273L12.3202 4.88432C12.2768 4.87818 12.2326 4.875 12.1875 4.875H6.55884C6.24818 4.875 5.99634 4.62316 5.99634 4.3125Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,22 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const LoaderIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
{...props}
>
<path
d="M12.5003 3V6M12.5003 18V21M6.13634 5.63604L8.25766 7.75736M16.7429 16.2426L18.8643 18.364M3.5 12.0007H6.5M18.5 12.0007H21.5M6.13634 18.364L8.25766 16.2426M16.7429 7.75736L18.8643 5.63604"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const LogoutIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.3125 3.375C3.79473 3.375 3.375 3.79473 3.375 4.3125L3.375 13.6875C3.375 14.2053 3.79473 14.625 4.3125 14.625H8.4375C8.74816 14.625 9 14.8768 9 15.1875C9 15.4982 8.74816 15.75 8.4375 15.75H4.3125C3.17341 15.75 2.25 14.8266 2.25 13.6875L2.25 4.3125C2.25 3.17341 3.17341 2.25 4.3125 2.25L8.4375 2.25C8.74816 2.25 9 2.50184 9 2.8125C9 3.12316 8.74816 3.375 8.4375 3.375L4.3125 3.375ZM11.4148 5.22725C11.6344 5.00758 11.9906 5.00758 12.2102 5.22725L15.5852 8.60224C15.8049 8.82191 15.8049 9.17807 15.5852 9.39774L12.2102 12.7727C11.9906 12.9924 11.6344 12.9924 11.4148 12.7727C11.1951 12.5531 11.1951 12.1969 11.4148 11.9773L13.8295 9.56249L6.75 9.56249C6.43934 9.56249 6.1875 9.31065 6.1875 8.99999C6.1875 8.68933 6.43934 8.43749 6.75 8.43749L13.8295 8.43749L11.4148 6.02275C11.1951 5.80308 11.1951 5.44692 11.4148 5.22725Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,27 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const MenuIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
d="M2 4.5C2 4.22386 2.22386 4 2.5 4H18.5C18.7761 4 19 4.22386 19 4.5C19 4.77614 18.7761 5 18.5 5H2.5C2.22386 5 2 4.77614 2 4.5Z"
fill="currentColor"
/>
<path
d="M2 10.5C2 10.2239 2.22386 10 2.5 10H10.5C10.7761 10 11 10.2239 11 10.5C11 10.7761 10.7761 11 10.5 11H2.5C2.22386 11 2 10.7761 2 10.5Z"
fill="currentColor"
/>
<path
d="M2 16.5C2 16.2239 2.22386 16 2.5 16H18.5C18.7761 16 19 16.2239 19 16.5C19 16.7761 18.7761 17 18.5 17H2.5C2.22386 17 2 16.7761 2 16.5Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const MinusCircleIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.5 12C2.5 6.47715 6.97715 2 12.5 2C18.0228 2 22.5 6.47715 22.5 12C22.5 17.5228 18.0228 22 12.5 22C6.97715 22 2.5 17.5228 2.5 12ZM16.5 12.9999C17.0523 12.9999 17.5 12.5522 17.5 11.9999C17.5 11.4476 17.0523 10.9999 16.5 10.9999L8.49997 11.0001C7.94769 11.0001 7.49998 11.4479 7.5 12.0001C7.50002 12.5524 7.94774 13.0001 8.50003 13.0001L16.5 12.9999Z"
fill="#0F86F5"
/>
</CustomIcon>
);
};
@@ -0,0 +1,21 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const QuestionMarkRoundFilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0ZM10 7C9.62268 7 9.29263 7.2086 9.12148 7.52152C8.85646 8.00606 8.24881 8.18401 7.76427 7.91899C7.27973 7.65396 7.10178 7.04632 7.3668 6.56178C7.87463 5.63331 8.86264 5 10 5C11.5148 5 12.5669 6.00643 12.8664 7.189C13.1676 8.37785 12.7101 9.76299 11.3416 10.4472C11.1323 10.5519 11 10.7659 11 11C11 11.5523 10.5523 12 10 12C9.44772 12 9.00001 11.5523 9.00001 11C9.00001 10.0084 9.56027 9.10183 10.4472 8.65836C10.902 8.43099 11.0188 8.03973 10.9277 7.6801C10.835 7.31417 10.5283 7 10 7ZM10 15C10.5523 15 11 14.5523 11 14C11 13.4477 10.5523 13 10 13C9.44771 13 9 13.4477 9 14C9 14.5523 9.44771 15 10 15Z"
fill="currentColor"
/>
</CustomIcon>
);
};
@@ -0,0 +1,23 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const RefreshIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
d="M4.7586 5.25C5.80171 4.1001 7.33209 3.375 9 3.375C12.1066 3.375 14.625 5.8934 14.625 9C14.625 9.23861 14.6102 9.47349 14.5815 9.70383C14.543 10.0121 14.7618 10.2932 15.07 10.3316C15.3783 10.3701 15.6594 10.1513 15.6978 9.84304C15.7323 9.56663 15.75 9.28526 15.75 9C15.75 5.27208 12.7279 2.25 9 2.25C7.10933 2.25 5.36655 3.02807 4.125 4.28342V2.8125C4.125 2.50184 3.87316 2.25 3.5625 2.25C3.25184 2.25 3 2.50184 3 2.8125L3 5.25C3 5.87132 3.50368 6.375 4.125 6.375H6.5625C6.87316 6.375 7.125 6.12316 7.125 5.8125C7.125 5.50184 6.87316 5.25 6.5625 5.25H4.7586Z"
fill="currentColor"
/>
<path
d="M3.41855 8.29617C3.45699 7.98789 3.23825 7.70683 2.92997 7.66839C2.6217 7.62994 2.34063 7.84869 2.30219 8.15696C2.26773 8.43337 2.25 8.71474 2.25 9C2.25 12.7279 5.27208 15.75 9 15.75C10.8952 15.75 12.6418 14.9682 13.8839 13.7076V15.1875C13.8839 15.4982 14.1357 15.75 14.4464 15.75C14.757 15.75 15.0089 15.4982 15.0089 15.1875V12.75C15.0089 12.1287 14.5052 11.625 13.8839 11.625L11.4464 11.625C11.1357 11.625 10.8839 11.8768 10.8839 12.1875C10.8839 12.4982 11.1357 12.75 11.4464 12.75H13.2414C12.1983 13.8999 10.6679 14.625 9 14.625C5.8934 14.625 3.375 12.1066 3.375 9C3.375 8.76139 3.38982 8.52651 3.41855 8.29617Z"
fill="currentColor"
/>
</CustomIcon>
);
};

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