From 52512beaa2fac7570151fa74804ef3c9633756ef Mon Sep 17 00:00:00 2001 From: Ian Cameron Lyles Date: Mon, 24 Feb 2025 20:20:23 -0800 Subject: [PATCH] chore(layouts): Consolidation and comprehensive typing (#6) * Consolidate layout pattern * Duplicates cleared * Tsdoc function docs * Excess files * Full types transition * Thorough tsdoc of existing comoponents --- .gitignore | 1 + .vscode/settings.json | 9 - packages/backend/src/server.ts | 22 +- packages/frontend/config/tabPageConfig.tsx | 25 - packages/frontend/package.json | 2 +- packages/frontend/src/App.tsx | 51 +- packages/frontend/src/assets/templates.ts | 4 +- .../frontend/src/components/CloudyFlow.tsx | 219 - .../frontend/src/components/DatePicker.tsx | 172 - .../src/components/FormatMilliSecond.tsx | 2 +- .../src/components/PageWithSubNav.tsx | 39 - .../frontend/src/components/ScreenWrapper.tsx | 12 - .../frontend/src/components/SearchBar.tsx | 6 +- .../src/components/compatibility/Alert.tsx | 11 - .../src/components/compatibility/Avatar.tsx | 10 - .../src/components/compatibility/Badge.tsx | 5 - .../src/components/compatibility/Box.tsx | 15 - .../src/components/compatibility/Button.tsx | 5 - .../src/components/compatibility/Calendar.tsx | 5 - .../src/components/compatibility/Checkbox.tsx | 6 - .../components/compatibility/DatePicker.tsx | 44 - .../components/compatibility/FormControl.tsx | 31 - .../compatibility/IconWithFrame.tsx | 42 - .../src/components/compatibility/Input.tsx | 5 - .../src/components/compatibility/InputOTP.tsx | 11 - .../src/components/compatibility/Modal.tsx | 94 - .../compatibility/OverflownText.tsx | 51 - .../src/components/compatibility/Radio.tsx | 8 - .../src/components/compatibility/Select.tsx | 19 - .../components/compatibility/Separator.tsx | 3 - .../src/components/compatibility/Sheet.tsx | 18 - .../src/components/compatibility/Switch.tsx | 6 - .../src/components/compatibility/Table.tsx | 21 - .../src/components/compatibility/Tabs.tsx | 12 - .../src/components/compatibility/Tag.tsx | 60 - .../src/components/compatibility/Toast.tsx | 7 - .../components/compatibility/ToggleGroup.tsx | 7 - .../src/components/compatibility/Tooltip.tsx | 13 - .../components/compatibility/UserSelect.tsx | 91 - .../src/components/examples/ExamplePage.tsx | 57 + packages/frontend/src/components/index.ts | 1 + .../frontend/src/components/layout/index.ts | 9 + .../layout/navigation/NavigationWrapper.tsx | 31 + .../layout/navigation/TopNavigation.tsx | 149 + .../navigation/components/ColorModeToggle.tsx | 19 + .../components/GitHubSessionButton.tsx | 67 + .../navigation/components}/LaconicIcon.tsx | 39 +- .../components/NavigationActions.tsx | 44 + .../components}/WalletSessionId.tsx | 17 +- .../src/components/layout/navigation/index.ts | 9 + .../layout/screen-header/ActionButton.tsx | 56 + .../layout/screen-header/Header.tsx | 89 + .../layout/screen-wrapper/ScreenWrapper.tsx | 38 + .../layout/screen-wrapper/TabWrapper.tsx | 43 + .../ProjectSearchBar/ProjectSearchBar.tsx | 106 + .../ProjectSearchBarDialog.tsx | 146 + .../ProjectSearchBarEmpty.tsx | 33 + .../ProjectSearchBar/ProjectSearchBarItem.tsx | 75 + .../layout/search/ProjectSearchBar/index.ts | 2 + .../components/loading/loading-overlay.tsx | 83 + .../components/navigation/ColorModeToggle.tsx | 27 - .../navigation/GitHubSessionButton.tsx | 46 - .../navigation/NavigationWrapper.tsx | 20 - .../components/navigation/TopNavigation.tsx | 105 - .../Dialog/CancelDeploymentDialog.tsx | 4 +- .../Dialog/ChangeStateToProductionDialog.tsx | 36 +- .../Dialog/DeleteDeploymentDialog.tsx | 11 +- .../projects/Dialog/DeleteDomainDialog.tsx | 6 +- .../projects/Dialog/DeleteVariableDialog.tsx | 6 +- .../projects/Dialog/DeleteWebhookDialog.tsx | 6 +- .../Dialog/DisconnectRepositoryDialog.tsx | 6 +- .../projects/Dialog/RemoveMemberDialog.tsx | 8 +- .../projects/Dialog/TransferProjectDialog.tsx | 2 +- .../projects/ProjectCard/ProjectCard.tsx | 122 +- .../ProjectSearchBar/ProjectSearchBar.tsx | 10 +- .../ProjectSearchBarDialog.tsx | 55 +- .../ProjectSearchBarEmpty.tsx | 2 +- .../ProjectSearchBar/ProjectSearchBarItem.tsx | 8 +- .../create/ApproveTransactionModal.tsx | 31 +- .../projects/create/CheckBalanceIframe.tsx | 24 +- .../components/projects/create/Configure.tsx | 176 +- .../projects/create/ConnectAccount.tsx | 52 +- .../create/ConnectAccountTabPanel.tsx | 6 +- .../src/components/projects/create/Deploy.tsx | 43 +- .../components/projects/create/DeployStep.tsx | 19 +- .../projects/create/MockConnectGitCard.tsx | 60 +- .../ProjectRepoCard/ProjectRepoCard.tsx | 39 +- .../create/RepositoryList/RepositoryList.tsx | 42 +- .../create/TemplateCard/TemplateCard.tsx | 18 +- .../deployments/DeploymentDetailsCard.tsx | 116 +- .../deployments/DeploymentDialogBodyCard.tsx | 16 +- .../project/deployments/DeploymentMenu.tsx | 47 +- .../project/deployments/FilterForm.tsx | 53 +- .../project/overview/Activity/Activity.tsx | 16 +- .../overview/Activity/ActivityCard.tsx | 27 +- .../project/overview/Activity/AuctionCard.tsx | 30 +- .../project/overview/OverviewInfo.tsx | 2 +- .../settings/AddEnvironmentVariableRow.tsx | 16 +- .../project/settings/AddMemberDialog.tsx | 12 +- .../project/settings/DeleteProjectDialog.tsx | 23 +- .../projects/project/settings/DomainCard.tsx | 19 +- .../project/settings/EditDomainDialog.tsx | 25 +- .../settings/EditEnvironmentVariableRow.tsx | 22 +- .../projects/project/settings/MemberCard.tsx | 19 +- .../project/settings/ProjectSettingHeader.tsx | 2 +- .../project/settings/RepoConnectedSection.tsx | 4 +- .../projects/project/settings/SetupDomain.tsx | 22 +- .../projects/project/settings/WebhookCard.tsx | 19 +- .../components/screen-header/ActionButton.tsx | 30 - .../src/components/screen-header/Header.tsx | 22 - .../screen-wrapper/ScreenWrapper.tsx | 9 - .../components/shared/Button/Button.theme.ts | 185 - .../src/components/shared/Button/Button.tsx | 186 - .../src/components/shared/Button/index.ts | 3 - .../components/shared/Calendar/Calendar.tsx | 51 +- .../src/components/shared/ConfirmDialog.tsx | 8 +- .../shared/DatePicker/DatePicker.tsx | 19 +- .../components/shared/DotBorder/DotBorder.tsx | 2 +- .../src/components/shared/Heading/Heading.tsx | 2 +- .../shared/IconWithFrame/IconWithFrame.tsx | 2 +- .../InlineNotification/InlineNotification.tsx | 10 +- .../components/shared/Input/Input.theme.ts | 109 - .../src/components/shared/Input/Input.tsx | 121 - .../src/components/shared/Input/index.ts | 2 - .../shared/Modal/ModalBody/ModalBody.tsx | 2 +- .../Modal/ModalContent/ModalContent.tsx | 12 +- .../shared/Modal/ModalFooter/ModalFooter.tsx | 2 +- .../shared/Modal/ModalHeader/ModalHeader.tsx | 6 +- .../shared/OverflownText/OverflownText.tsx | 8 +- .../src/components/shared/Radio/RadioItem.tsx | 10 +- .../SegmentedControlItem.tsx | 2 +- .../src/components/shared/Select/Select.tsx | 21 +- .../shared/Select/SelectItem/SelectItem.tsx | 18 +- .../shared/Select/SelectValue/SelectValue.tsx | 16 +- .../src/components/shared/Sidebar/Sidebar.tsx | 31 +- .../shared/Tabs/TabsList/TabsList.tsx | 4 +- .../shared/Tabs/TabsTrigger/TabsTrigger.tsx | 6 +- .../src/components/shared/Tag/Tag.tsx | 4 +- .../components/shared/Toast/SimpleToast.tsx | 19 +- .../shared/UserSelect/UserSelect.tsx | 10 +- .../UserSelectItem/UserSelectItem.tsx | 4 +- .../VerifyCodeInput/VerifyCodeInput.tsx | 2 +- .../shared/WavyBorder/WavyBorder.tsx | 2 +- .../shared/auth/AutoSignInIFrameModal.tsx | 3 +- .../frontend/src/components/shared/index.ts | 31 - .../tab-navigation/TabPageNavigation.tsx | 27 - .../tab-navigation/tabPageConfig.tsx | 28 - .../src/components/tab-navigation/types.ts | 17 - .../frontend/src/components/ui/button.tsx | 101 +- .../components/ui/extended/button-w-icons.tsx | 134 + .../src/components/ui/extended/index.ts | 2 + .../components/ui/extended/input-w-icons.tsx | 135 + packages/frontend/src/components/ui/index.ts | 55 + packages/frontend/src/components/ui/input.tsx | 21 +- .../frontend/src/context/GQLClientContext.tsx | 18 +- .../frontend/src/context/OctokitContext.tsx | 51 +- .../src/context/OctokitProviderWithRouter.tsx | 18 + .../frontend/src/context/WalletContext.tsx | 24 +- packages/frontend/src/context/index.ts | 4 + .../frontend/src/hooks/useCheckBalance.tsx | 33 +- packages/frontend/src/hooks/useGitHubAuth.ts | 53 +- packages/frontend/src/index.tsx | 19 +- .../src/laconic-assets/laconic-mark.tsx | 42 + .../DashboardLayout.tsx} | 17 +- .../frontend/src/layouts/ProjectSearch.tsx | 100 +- packages/frontend/src/layouts/global.css | 6 - packages/frontend/src/pages/AuthPage.tsx | 13 +- .../frontend/src/pages/BuyPrepaidService.tsx | 55 +- packages/frontend/src/pages/auth/Done.tsx | 30 +- packages/frontend/src/pages/auth/Login.tsx | 2 +- .../frontend/src/pages/components/index.tsx | 338 - .../frontend/src/pages/components/modals.tsx | 274 - .../src/pages/components/renders/avatar.tsx | 18 - .../src/pages/components/renders/badge.tsx | 18 - .../src/pages/components/renders/button.tsx | 86 - .../src/pages/components/renders/checkbox.tsx | 26 - .../src/pages/components/renders/dropdown.tsx | 331 - .../renders/inlineNotifications.tsx | 42 - .../src/pages/components/renders/input.tsx | 56 - .../src/pages/components/renders/radio.ts | 16 - .../components/renders/segmentedControls.tsx | 43 - .../src/pages/components/renders/steps.tsx | 40 - .../src/pages/components/renders/tabs.tsx | 55 - .../src/pages/components/renders/tag.tsx | 62 - .../src/pages/components/renders/toast.tsx | 65 - .../src/pages/components/renders/tooltip.tsx | 29 - packages/frontend/src/pages/index.tsx | 32 +- .../src/pages/org-slug/ProjectsScreen.tsx | 79 + .../frontend/src/pages/org-slug/Settings.tsx | 2 +- .../frontend/src/pages/org-slug/index.tsx | 60 - .../pages/org-slug/projects/Deployments.tsx | 187 + .../src/pages/org-slug/projects/Id.tsx | 160 +- .../projects/create/CreateProjectLayout.tsx | 71 + .../pages/org-slug/projects/create/Import.tsx | 4 +- .../create/{index.tsx => NewProject.tsx} | 18 +- .../org-slug/projects/create/Template.tsx | 19 +- .../{routes.tsx => create-project-routes.tsx} | 8 +- .../pages/org-slug/projects/create/layout.tsx | 99 - .../org-slug/projects/create/success/Id.tsx | 43 +- .../projects/create/template/Deploy.tsx | 7 + .../projects/create/template/index.tsx | 42 +- .../projects/create/template/routes.tsx | 16 +- .../org-slug/projects/id/Deployments.tsx | 25 +- .../pages/org-slug/projects/id/Overview.tsx | 249 +- .../pages/org-slug/projects/id/Settings.tsx | 87 +- .../src/pages/org-slug/projects/id/routes.tsx | 30 +- .../projects/id/settings/Collaborators.tsx | 82 +- .../org-slug/projects/id/settings/Domains.tsx | 59 +- .../id/settings/EnvironmentVariables.tsx | 43 +- .../id/settings/EnvironmentVariablesForm.tsx | 41 +- .../org-slug/projects/id/settings/General.tsx | 126 +- .../org-slug/projects/id/settings/Git.tsx | 70 +- .../id/settings/domains/add/Config.tsx | 46 +- .../id/settings/domains/add/index.tsx | 21 +- .../id/settings/domains/add/routes.tsx | 16 +- .../{routes.tsx => project-routes.tsx} | 4 +- .../frontend/src/stories/MockStoriesData.ts | 148 - .../stories/Pages/Auth/AuthHeader.stories.ts | 18 - .../src/stories/Pages/Auth/AuthHeader.tsx | 12 - .../src/stories/assets/accessibility.png | Bin 42336 -> 0 bytes .../src/stories/assets/accessibility.svg | 5 - .../src/stories/assets/addon-library.png | Bin 467366 -> 0 bytes .../frontend/src/stories/assets/assets.png | Bin 3899 -> 0 bytes .../src/stories/assets/avif-test-image.avif | Bin 829 -> 0 bytes .../frontend/src/stories/assets/context.png | Bin 6119 -> 0 bytes .../frontend/src/stories/assets/discord.svg | 15 - packages/frontend/src/stories/assets/docs.png | Bin 27875 -> 0 bytes .../src/stories/assets/figma-plugin.png | Bin 44246 -> 0 bytes .../frontend/src/stories/assets/github.svg | 3 - .../frontend/src/stories/assets/share.png | Bin 40767 -> 0 bytes .../frontend/src/stories/assets/styling.png | Bin 7237 -> 0 bytes .../frontend/src/stories/assets/testing.png | Bin 49313 -> 0 bytes .../frontend/src/stories/assets/theming.png | Bin 44374 -> 0 bytes .../frontend/src/stories/assets/tutorials.svg | 12 - .../frontend/src/stories/assets/youtube.svg | 4 - packages/frontend/src/types/common.ts | 2 +- packages/frontend/src/types/git.ts | 23 + packages/frontend/tsconfig.json | 40 +- packages/frontend/tsconfig.node.json | 6 +- packages/frontend/tsconfig.node.tsbuildinfo | 1 + packages/frontend/tsconfig.tsbuildinfo | 1 + packages/frontend/vite.config.d.ts | 2 +- packages/frontend/vite.config.js | 66 + packages/frontend/vite.config.ts | 27 +- qwrk/docs/frontend/layouts/LAYOUT_STRATEGY.md | 181 +- .../frontend/{routes => layouts}/ROUTES.md | 0 qwrk/poa/02-shared-components.md | 70 - qwrk/poa/03-component-usage.md | 7 - yarn.lock | 14518 ++++++++-------- 249 files changed, 11663 insertions(+), 12479 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 packages/frontend/config/tabPageConfig.tsx delete mode 100644 packages/frontend/src/components/CloudyFlow.tsx delete mode 100644 packages/frontend/src/components/DatePicker.tsx delete mode 100644 packages/frontend/src/components/PageWithSubNav.tsx delete mode 100644 packages/frontend/src/components/ScreenWrapper.tsx delete mode 100644 packages/frontend/src/components/compatibility/Alert.tsx delete mode 100644 packages/frontend/src/components/compatibility/Avatar.tsx delete mode 100644 packages/frontend/src/components/compatibility/Badge.tsx delete mode 100644 packages/frontend/src/components/compatibility/Box.tsx delete mode 100644 packages/frontend/src/components/compatibility/Button.tsx delete mode 100644 packages/frontend/src/components/compatibility/Calendar.tsx delete mode 100644 packages/frontend/src/components/compatibility/Checkbox.tsx delete mode 100644 packages/frontend/src/components/compatibility/DatePicker.tsx delete mode 100644 packages/frontend/src/components/compatibility/FormControl.tsx delete mode 100644 packages/frontend/src/components/compatibility/IconWithFrame.tsx delete mode 100644 packages/frontend/src/components/compatibility/Input.tsx delete mode 100644 packages/frontend/src/components/compatibility/InputOTP.tsx delete mode 100644 packages/frontend/src/components/compatibility/Modal.tsx delete mode 100644 packages/frontend/src/components/compatibility/OverflownText.tsx delete mode 100644 packages/frontend/src/components/compatibility/Radio.tsx delete mode 100644 packages/frontend/src/components/compatibility/Select.tsx delete mode 100644 packages/frontend/src/components/compatibility/Separator.tsx delete mode 100644 packages/frontend/src/components/compatibility/Sheet.tsx delete mode 100644 packages/frontend/src/components/compatibility/Switch.tsx delete mode 100644 packages/frontend/src/components/compatibility/Table.tsx delete mode 100644 packages/frontend/src/components/compatibility/Tabs.tsx delete mode 100644 packages/frontend/src/components/compatibility/Tag.tsx delete mode 100644 packages/frontend/src/components/compatibility/Toast.tsx delete mode 100644 packages/frontend/src/components/compatibility/ToggleGroup.tsx delete mode 100644 packages/frontend/src/components/compatibility/Tooltip.tsx delete mode 100644 packages/frontend/src/components/compatibility/UserSelect.tsx create mode 100644 packages/frontend/src/components/examples/ExamplePage.tsx create mode 100644 packages/frontend/src/components/index.ts create mode 100644 packages/frontend/src/components/layout/index.ts create mode 100644 packages/frontend/src/components/layout/navigation/NavigationWrapper.tsx create mode 100644 packages/frontend/src/components/layout/navigation/TopNavigation.tsx create mode 100644 packages/frontend/src/components/layout/navigation/components/ColorModeToggle.tsx create mode 100644 packages/frontend/src/components/layout/navigation/components/GitHubSessionButton.tsx rename packages/frontend/src/components/{navigation => layout/navigation/components}/LaconicIcon.tsx (59%) create mode 100644 packages/frontend/src/components/layout/navigation/components/NavigationActions.tsx rename packages/frontend/src/components/{navigation => layout/navigation/components}/WalletSessionId.tsx (59%) create mode 100644 packages/frontend/src/components/layout/navigation/index.ts create mode 100644 packages/frontend/src/components/layout/screen-header/ActionButton.tsx create mode 100644 packages/frontend/src/components/layout/screen-header/Header.tsx create mode 100644 packages/frontend/src/components/layout/screen-wrapper/ScreenWrapper.tsx create mode 100644 packages/frontend/src/components/layout/screen-wrapper/TabWrapper.tsx create mode 100644 packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBar.tsx create mode 100644 packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarDialog.tsx create mode 100644 packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarEmpty.tsx create mode 100644 packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarItem.tsx create mode 100644 packages/frontend/src/components/layout/search/ProjectSearchBar/index.ts create mode 100644 packages/frontend/src/components/loading/loading-overlay.tsx delete mode 100644 packages/frontend/src/components/navigation/ColorModeToggle.tsx delete mode 100644 packages/frontend/src/components/navigation/GitHubSessionButton.tsx delete mode 100644 packages/frontend/src/components/navigation/NavigationWrapper.tsx delete mode 100644 packages/frontend/src/components/navigation/TopNavigation.tsx delete mode 100644 packages/frontend/src/components/screen-header/ActionButton.tsx delete mode 100644 packages/frontend/src/components/screen-header/Header.tsx delete mode 100644 packages/frontend/src/components/screen-wrapper/ScreenWrapper.tsx delete mode 100644 packages/frontend/src/components/shared/Button/Button.theme.ts delete mode 100644 packages/frontend/src/components/shared/Button/Button.tsx delete mode 100644 packages/frontend/src/components/shared/Button/index.ts delete mode 100644 packages/frontend/src/components/shared/Input/Input.theme.ts delete mode 100644 packages/frontend/src/components/shared/Input/Input.tsx delete mode 100644 packages/frontend/src/components/shared/Input/index.ts delete mode 100644 packages/frontend/src/components/shared/index.ts delete mode 100644 packages/frontend/src/components/tab-navigation/TabPageNavigation.tsx delete mode 100644 packages/frontend/src/components/tab-navigation/tabPageConfig.tsx delete mode 100644 packages/frontend/src/components/tab-navigation/types.ts create mode 100644 packages/frontend/src/components/ui/extended/button-w-icons.tsx create mode 100644 packages/frontend/src/components/ui/extended/index.ts create mode 100644 packages/frontend/src/components/ui/extended/input-w-icons.tsx create mode 100644 packages/frontend/src/components/ui/index.ts create mode 100644 packages/frontend/src/context/OctokitProviderWithRouter.tsx create mode 100644 packages/frontend/src/context/index.ts create mode 100644 packages/frontend/src/laconic-assets/laconic-mark.tsx rename packages/frontend/src/{pages/org-slug/layout.tsx => layouts/DashboardLayout.tsx} (64%) delete mode 100644 packages/frontend/src/layouts/global.css delete mode 100644 packages/frontend/src/pages/components/index.tsx delete mode 100644 packages/frontend/src/pages/components/modals.tsx delete mode 100644 packages/frontend/src/pages/components/renders/avatar.tsx delete mode 100644 packages/frontend/src/pages/components/renders/badge.tsx delete mode 100644 packages/frontend/src/pages/components/renders/button.tsx delete mode 100644 packages/frontend/src/pages/components/renders/checkbox.tsx delete mode 100644 packages/frontend/src/pages/components/renders/dropdown.tsx delete mode 100644 packages/frontend/src/pages/components/renders/inlineNotifications.tsx delete mode 100644 packages/frontend/src/pages/components/renders/input.tsx delete mode 100644 packages/frontend/src/pages/components/renders/radio.ts delete mode 100644 packages/frontend/src/pages/components/renders/segmentedControls.tsx delete mode 100644 packages/frontend/src/pages/components/renders/steps.tsx delete mode 100644 packages/frontend/src/pages/components/renders/tabs.tsx delete mode 100644 packages/frontend/src/pages/components/renders/tag.tsx delete mode 100644 packages/frontend/src/pages/components/renders/toast.tsx delete mode 100644 packages/frontend/src/pages/components/renders/tooltip.tsx create mode 100644 packages/frontend/src/pages/org-slug/ProjectsScreen.tsx delete mode 100644 packages/frontend/src/pages/org-slug/index.tsx create mode 100644 packages/frontend/src/pages/org-slug/projects/Deployments.tsx create mode 100644 packages/frontend/src/pages/org-slug/projects/create/CreateProjectLayout.tsx rename packages/frontend/src/pages/org-slug/projects/create/{index.tsx => NewProject.tsx} (51%) rename packages/frontend/src/pages/org-slug/projects/create/{routes.tsx => create-project-routes.tsx} (75%) delete mode 100644 packages/frontend/src/pages/org-slug/projects/create/layout.tsx rename packages/frontend/src/pages/org-slug/projects/{routes.tsx => project-routes.tsx} (80%) delete mode 100644 packages/frontend/src/stories/MockStoriesData.ts delete mode 100644 packages/frontend/src/stories/Pages/Auth/AuthHeader.stories.ts delete mode 100644 packages/frontend/src/stories/Pages/Auth/AuthHeader.tsx delete mode 100644 packages/frontend/src/stories/assets/accessibility.png delete mode 100644 packages/frontend/src/stories/assets/accessibility.svg delete mode 100644 packages/frontend/src/stories/assets/addon-library.png delete mode 100644 packages/frontend/src/stories/assets/assets.png delete mode 100644 packages/frontend/src/stories/assets/avif-test-image.avif delete mode 100644 packages/frontend/src/stories/assets/context.png delete mode 100644 packages/frontend/src/stories/assets/discord.svg delete mode 100644 packages/frontend/src/stories/assets/docs.png delete mode 100644 packages/frontend/src/stories/assets/figma-plugin.png delete mode 100644 packages/frontend/src/stories/assets/github.svg delete mode 100644 packages/frontend/src/stories/assets/share.png delete mode 100644 packages/frontend/src/stories/assets/styling.png delete mode 100644 packages/frontend/src/stories/assets/testing.png delete mode 100644 packages/frontend/src/stories/assets/theming.png delete mode 100644 packages/frontend/src/stories/assets/tutorials.svg delete mode 100644 packages/frontend/src/stories/assets/youtube.svg create mode 100644 packages/frontend/src/types/git.ts create mode 100644 packages/frontend/tsconfig.node.tsbuildinfo create mode 100644 packages/frontend/tsconfig.tsbuildinfo create mode 100644 packages/frontend/vite.config.js rename qwrk/docs/frontend/{routes => layouts}/ROUTES.md (100%) delete mode 100644 qwrk/poa/02-shared-components.md delete mode 100644 qwrk/poa/03-component-usage.md diff --git a/.gitignore b/.gitignore index d4a85489..76344a89 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ packages/frontend/dist/ # ignore all .DS_Store files **/.DS_Store +.vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ca7dfdad..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - // IntelliSense for taiwind variants - "tailwindCSS.experimental.classRegex": [ - "tv\\('([^)]*)\\')", - "(?:'|\"|`)([^\"'`]*)(?:'|\"|`)" - ], - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" -} diff --git a/packages/backend/src/server.ts b/packages/backend/src/server.ts index 734893e7..d9ed06c9 100644 --- a/packages/backend/src/server.ts +++ b/packages/backend/src/server.ts @@ -1,22 +1,22 @@ -import debug from 'debug'; -import express from 'express'; -import cors from 'cors'; -import { ApolloServer } from 'apollo-server-express'; -import { createServer } from 'http'; import { ApolloServerPluginDrainHttpServer, ApolloServerPluginLandingPageLocalDefault, AuthenticationError, } from 'apollo-server-core'; +import { ApolloServer } from 'apollo-server-express'; +import cors from 'cors'; +import debug from 'debug'; +import express from 'express'; import session from 'express-session'; +import { createServer } from 'http'; -import { TypeSource } from '@graphql-tools/utils'; import { makeExecutableSchema } from '@graphql-tools/schema'; +import { TypeSource } from '@graphql-tools/utils'; import { ServerConfig } from './config'; import { DEFAULT_GQL_PATH } from './constants'; -import githubRouter from './routes/github'; import authRouter from './routes/auth'; +import githubRouter from './routes/github'; import stagingRouter from './routes/staging'; import { Service } from './service'; @@ -101,7 +101,7 @@ export const createAndStartServer = async ( } app.use( - session(sessionOptions) + session(sessionOptions) as unknown as express.RequestHandler ); server.applyMiddleware({ @@ -116,9 +116,9 @@ export const createAndStartServer = async ( app.use(express.json()); app.set('service', service); - app.use('/auth', authRouter); - app.use('/api/github', githubRouter); - app.use('/staging', stagingRouter); + app.use('/auth', authRouter as express.RequestHandler); + app.use('/api/github', githubRouter as express.RequestHandler); + app.use('/staging', stagingRouter as express.RequestHandler); app.use((err: any, req: any, res: any, next: any) => { console.error(err); diff --git a/packages/frontend/config/tabPageConfig.tsx b/packages/frontend/config/tabPageConfig.tsx deleted file mode 100644 index f8d2ebee..00000000 --- a/packages/frontend/config/tabPageConfig.tsx +++ /dev/null @@ -1,25 +0,0 @@ -export const tabPageConfig: TabPageNavigationConfig = { - defaultTab: 'tab1', - tabs: [ - { - id: 'tab1', - label: 'Overview', - content:
This is the overview content
, - }, - { - id: 'tab2', - label: 'Details', - content:
This is the details content
, - }, - { - id: 'tab3', - label: 'Settings', - content:
This is the settings content
, - }, - { - id: 'tab4', - label: 'History', - content:
This is the history content
, - }, - ], -}; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index ae0f2352..9dfb2aed 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -115,7 +115,7 @@ "@types/jest": "^27.5.2", "@types/lodash": "^4.17.0", "@types/luxon": "^3.3.7", - "@types/node": "^16.18.68", + "@types/node": "^22.13.5", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@types/uuid": "^9.0.8", diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index e2bbfd26..1f8edbea 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -1,37 +1,32 @@ +import { ThemeProvider } from 'next-themes'; import { useEffect } from 'react'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; - -import { BASE_URL } from 'utils/constants'; -import ProjectSearchLayout from './layouts/ProjectSearch'; +import { DashboardLayout } from './layouts/DashboardLayout'; import Index from './pages'; import AuthPage from './pages/AuthPage'; import BuyPrepaidService from './pages/BuyPrepaidService'; -import Projects from './pages/org-slug'; +import { projectsRoutesWithoutSearch } from './pages/org-slug/projects/project-routes'; import Settings from './pages/org-slug/Settings'; -import { DashboardLayout } from './pages/org-slug/layout'; -import { - projectsRoutesWithoutSearch, - projectsRoutesWithSearch, -} from './pages/org-slug/projects/routes'; +import { BASE_URL } from './utils/constants'; const router = createBrowserRouter([ { path: ':orgSlug', element: , children: [ - { - element: , - children: [ - { - path: '', - element: , - }, - { - path: 'projects', - children: projectsRoutesWithSearch, - }, - ], - }, + // { + // element: , + // children: [ + // { + // path: '', + // element: , + // }, + // { + // path: 'projects', + // children: projectsRoutesWithSearch, + // }, + // ], + // }, { path: 'settings', element: , @@ -56,6 +51,11 @@ const router = createBrowserRouter([ }, ]); +/** + * Main application component. + * Sets up routing, authentication, and theme provider. + * @returns {JSX.Element} The rendered application. + */ function App() { // Hacky way of checking session // TODO: Handle redirect backs @@ -66,7 +66,6 @@ function App() { const path = window.location.pathname; if (res.status !== 200) { localStorage.clear(); - if (path !== '/login') { window.location.pathname = '/login'; } @@ -78,7 +77,11 @@ function App() { }); }, []); - return ; + return ( + + Loading...} /> + + ); } export default App; diff --git a/packages/frontend/src/assets/templates.ts b/packages/frontend/src/assets/templates.ts index 3dc7c4c8..6e7782a0 100644 --- a/packages/frontend/src/assets/templates.ts +++ b/packages/frontend/src/assets/templates.ts @@ -1,8 +1,8 @@ import { VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO, - VITE_GITHUB_PWA_TEMPLATE_REPO, VITE_GITHUB_NEXT_APP_TEMPLATE_REPO, -} from 'utils/constants'; + VITE_GITHUB_PWA_TEMPLATE_REPO, +} from '@/utils/constants'; export default [ { diff --git a/packages/frontend/src/components/CloudyFlow.tsx b/packages/frontend/src/components/CloudyFlow.tsx deleted file mode 100644 index 479779f8..00000000 --- a/packages/frontend/src/components/CloudyFlow.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import React from 'react'; - -type Props = React.PropsWithChildren<{ - className?: string; - snowZIndex?: number; -}>; - -export const CloudyFlow = ({ className, children, snowZIndex }: Props) => { - return ( -
- {children} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); -}; diff --git a/packages/frontend/src/components/DatePicker.tsx b/packages/frontend/src/components/DatePicker.tsx deleted file mode 100644 index b8f024dc..00000000 --- a/packages/frontend/src/components/DatePicker.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useCallback, useMemo, useState } from 'react'; -import { format } from 'date-fns'; -import { - DayPicker, - SelectSingleEventHandler, - DateRange, -} from 'react-day-picker'; - -import { - Button, - Input, - Popover, - PopoverContent, - PopoverHandler, -} from '@snowballtools/material-tailwind-react-fork'; - -import HorizontalLine from './HorizontalLine'; - -// https://www.material-tailwind.com/docs/react/plugins/date-picker#date-picker -const DAY_PICKER_CLASS_NAMES = { - caption: 'flex justify-center py-2 mb-4 relative items-center', - caption_label: 'text-sm font-medium text-gray-900', - nav: 'flex items-center', - nav_button: - 'h-6 w-6 bg-transparent hover:bg-blue-gray-50 p-1 rounded-md transition-colors duration-300', - nav_button_previous: 'absolute left-1.5', - nav_button_next: 'absolute right-1.5', - table: 'w-full border-collapse', - head_row: 'flex font-medium text-gray-900', - head_cell: 'm-0.5 w-9 font-normal text-sm', - row: 'flex w-full mt-2', - cell: 'text-gray-600 rounded-md h-9 w-9 text-center text-sm p-0 m-0.5 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-gray-900/20 [&:has([aria-selected].day-outside)]:text-white [&:has([aria-selected])]:bg-gray-900/50 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20', - day: 'h-9 w-9 p-0 font-normal', - day_range_end: 'day-range-end', - day_selected: - 'rounded-md bg-gray-900 text-white hover:bg-gray-900 hover:text-white focus:bg-gray-900 focus:text-white', - day_today: 'rounded-md bg-gray-200 text-gray-900', - day_outside: - 'day-outside text-gray-500 opacity-50 aria-selected:bg-gray-500 aria-selected:text-gray-900 aria-selected:bg-opacity-10', - day_disabled: 'text-gray-500 opacity-50', - day_hidden: 'invisible', -}; - -type SingleDateHandler = (value: Date) => void; -type RangeDateHandler = (value: DateRange) => void; - -interface SingleDatePickerProps { - mode: 'single'; - selected?: Date; - onSelect: SingleDateHandler; -} - -interface RangeDatePickerProps { - mode: 'range'; - selected?: DateRange; - onSelect: RangeDateHandler; -} - -const DatePicker = ({ - mode = 'single', - selected, - onSelect, -}: SingleDatePickerProps | RangeDatePickerProps) => { - const [isOpen, setIsOpen] = useState(false); - const [rangeSelected, setRangeSelected] = useState(); - - const inputValue = useMemo(() => { - if (mode === 'single') { - return selected ? format(selected as Date, 'PPP') : 'Select Date'; - } - - if (mode === 'range') { - const selectedRange = selected as DateRange | undefined; - return selectedRange && selectedRange.from && selectedRange.to - ? format(selectedRange.from, 'PP') + - '-' + - format(selectedRange.to, 'PP') - : 'All time'; - } - }, [selected, mode]); - - const handleSingleSelect = useCallback((value) => { - if (value) { - (onSelect as SingleDateHandler)(value); - setIsOpen(false); - } - }, []); - - const handleRangeSelect = useCallback(() => { - if (rangeSelected?.to) { - (onSelect as RangeDateHandler)(rangeSelected); - setIsOpen(false); - } - }, [rangeSelected]); - - const components = { - IconLeft: ({ ...props }) => ( - - {'<'} - - ), - IconRight: ({ ...props }) => ( - - {'>'} - - ), - }; - - const commonDayPickerProps = { - components, - className: 'border-0', - classNames: DAY_PICKER_CLASS_NAMES, - showOutsideDays: true, - }; - - return ( - setIsOpen(value)} - > - - null} value={inputValue} /> - - {/* TODO: Figure out what placeholder is for */} - {/* @ts-ignore */} - - {mode === 'single' && ( - - )} - {mode === 'range' && ( - <> - - -
- {/* TODO: Figure out what placeholder is for */} - - {/* TODO: Figure out what placeholder is for */} - -
- - )} -
-
- ); -}; - -export default DatePicker; diff --git a/packages/frontend/src/components/FormatMilliSecond.tsx b/packages/frontend/src/components/FormatMilliSecond.tsx index 4148ef77..38429906 100644 --- a/packages/frontend/src/components/FormatMilliSecond.tsx +++ b/packages/frontend/src/components/FormatMilliSecond.tsx @@ -1,6 +1,6 @@ +import { cn } from '@/utils/classnames'; import { Duration } from 'luxon'; import { ComponentPropsWithoutRef } from 'react'; -import { cn } from 'utils/classnames'; export interface FormatMilliSecondProps extends ComponentPropsWithoutRef<'div'> { diff --git a/packages/frontend/src/components/PageWithSubNav.tsx b/packages/frontend/src/components/PageWithSubNav.tsx deleted file mode 100644 index 8c1649fd..00000000 --- a/packages/frontend/src/components/PageWithSubNav.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { NavigationWrapper } from "@/components/navigation/NavigationWrapper" -import { ScreenWrapper } from "@/components/screen-wrapper/ScreenWrapper" -import { Header } from "@/components/screen-header/Header" -import { TabPageNavigation } from "@/components/tab-navigation/TabPageNavigation" -import { ActionButton } from "@/components/screen-header/ActionButton" -import { Circle, PlusCircle } from "lucide-react" -import { tabPageConfig } from "@/components/tab-navigation/tabPageConfig" - -export default function PageWithSubNav() { - return ( - - -
-
console.log("Secondary action clicked")} - />, - console.log("Primary action clicked")} - />, - ]} - /> - -
-
-
- ) -} - diff --git a/packages/frontend/src/components/ScreenWrapper.tsx b/packages/frontend/src/components/ScreenWrapper.tsx deleted file mode 100644 index ee24b414..00000000 --- a/packages/frontend/src/components/ScreenWrapper.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type React from 'react'; -interface ScreenWrapperProps { - children: React.ReactNode; -} - -export function ScreenWrapper({ children }: ScreenWrapperProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/frontend/src/components/SearchBar.tsx b/packages/frontend/src/components/SearchBar.tsx index d426794e..2ba5c2e4 100644 --- a/packages/frontend/src/components/SearchBar.tsx +++ b/packages/frontend/src/components/SearchBar.tsx @@ -1,7 +1,7 @@ import React, { forwardRef, RefAttributes } from 'react'; +import { IconInput, InputProps } from '@/components/ui'; import { Search } from 'lucide-react'; -import { Input, InputProps } from './shared/Input'; const SearchBar: React.ForwardRefRenderFunction< HTMLInputElement, @@ -9,13 +9,13 @@ const SearchBar: React.ForwardRefRenderFunction< > = ({ value, onChange, placeholder = 'Search', ...props }, ref) => { return (
- } onChange={onChange} value={value} type="search" placeholder={placeholder} - appearance="borderless" + // appearance="borderless" className="w-full lg:w-[459px]" {...props} ref={ref} diff --git a/packages/frontend/src/components/compatibility/Alert.tsx b/packages/frontend/src/components/compatibility/Alert.tsx deleted file mode 100644 index 341708d2..00000000 --- a/packages/frontend/src/components/compatibility/Alert.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { - AlertDescription, - AlertTitle, - Alert as UIAlert, -} from '@/components/ui/alert'; - -export const Alert = UIAlert; -export { - AlertDescription, - AlertTitle -}; diff --git a/packages/frontend/src/components/compatibility/Avatar.tsx b/packages/frontend/src/components/compatibility/Avatar.tsx deleted file mode 100644 index a8fad743..00000000 --- a/packages/frontend/src/components/compatibility/Avatar.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { - AvatarFallback, - AvatarImage, - Avatar as UIAvatar, -} from '@/components/ui/avatar'; - -export const Avatar = UIAvatar; -export { - AvatarFallback, AvatarImage -}; diff --git a/packages/frontend/src/components/compatibility/Badge.tsx b/packages/frontend/src/components/compatibility/Badge.tsx deleted file mode 100644 index e6e327e1..00000000 --- a/packages/frontend/src/components/compatibility/Badge.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import type { BadgeProps } from '@/components/ui/badge'; -import { Badge as UIBadge } from '@/components/ui/badge'; - -export const Badge = UIBadge; -export type { BadgeProps }; diff --git a/packages/frontend/src/components/compatibility/Box.tsx b/packages/frontend/src/components/compatibility/Box.tsx deleted file mode 100644 index 7389e744..00000000 --- a/packages/frontend/src/components/compatibility/Box.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { cn } from '@/lib/utils'; -import { type ComponentPropsWithoutRef, type ElementType } from 'react'; - -type BoxProps = { - component?: T; -} & ComponentPropsWithoutRef; - -export function Box({ - className, - component, - ...props -}: BoxProps) { - const Component = component || 'div'; - return ; -} \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/Button.tsx b/packages/frontend/src/components/compatibility/Button.tsx deleted file mode 100644 index 9c060433..00000000 --- a/packages/frontend/src/components/compatibility/Button.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Button as UIButton, type ButtonProps } from '@/components/ui/button'; - -// Re-export the UI Button directly -export const Button = UIButton; -export type { ButtonProps }; diff --git a/packages/frontend/src/components/compatibility/Calendar.tsx b/packages/frontend/src/components/compatibility/Calendar.tsx deleted file mode 100644 index 43c87541..00000000 --- a/packages/frontend/src/components/compatibility/Calendar.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import type { CalendarProps } from '@/components/ui/calendar'; -import { Calendar as UICalendar } from '@/components/ui/calendar'; - -export const Calendar = UICalendar; -export type { CalendarProps }; diff --git a/packages/frontend/src/components/compatibility/Checkbox.tsx b/packages/frontend/src/components/compatibility/Checkbox.tsx deleted file mode 100644 index ac4f7599..00000000 --- a/packages/frontend/src/components/compatibility/Checkbox.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Checkbox as UICheckbox } from '@/components/ui/checkbox'; -import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; -import type { ComponentPropsWithoutRef } from 'react'; - -export const Checkbox = UICheckbox; -export type CheckboxProps = ComponentPropsWithoutRef; diff --git a/packages/frontend/src/components/compatibility/DatePicker.tsx b/packages/frontend/src/components/compatibility/DatePicker.tsx deleted file mode 100644 index 60492bb6..00000000 --- a/packages/frontend/src/components/compatibility/DatePicker.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; -import { cn } from '@/lib/utils'; -import { format } from 'date-fns'; -import { CalendarIcon } from 'lucide-react'; -import { Button } from './Button'; -import { Calendar } from './Calendar'; - -interface DatePickerProps { - date?: Date; - onChange?: (date?: Date) => void; - className?: string; -} - -export function DatePicker({ date, onChange, className }: DatePickerProps) { - return ( - - - - - - - - - ); -} \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/FormControl.tsx b/packages/frontend/src/components/compatibility/FormControl.tsx deleted file mode 100644 index 007a4acb..00000000 --- a/packages/frontend/src/components/compatibility/FormControl.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { cn } from '@/lib/utils'; -import * as React from 'react'; - -interface FormControlProps extends React.HTMLAttributes { - error?: boolean; -} - -export function FormControl({ className, error, ...props }: FormControlProps) { - return ( -
- ); -} - -export function FormHelperText({ className, ...props }: React.HTMLAttributes) { - return ( -

- ); -} \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/IconWithFrame.tsx b/packages/frontend/src/components/compatibility/IconWithFrame.tsx deleted file mode 100644 index 9c557ae5..00000000 --- a/packages/frontend/src/components/compatibility/IconWithFrame.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { cn } from '@/lib/utils'; -import { type LucideIcon } from 'lucide-react'; -import * as React from 'react'; - -interface IconWithFrameProps extends React.HTMLAttributes { - icon: LucideIcon; - variant?: 'default' | 'success' | 'warning' | 'danger'; - size?: 'sm' | 'md' | 'lg'; -} - -export function IconWithFrame({ - icon: Icon, - variant = 'default', - size = 'md', - className, - ...props -}: IconWithFrameProps) { - return ( -

- -
- ); -} \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/Input.tsx b/packages/frontend/src/components/compatibility/Input.tsx deleted file mode 100644 index a3d3f812..00000000 --- a/packages/frontend/src/components/compatibility/Input.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Input as UIInput } from '@/components/ui/input'; -import type { ComponentProps } from 'react'; - -export const Input = UIInput; -export type InputProps = ComponentProps<'input'>; diff --git a/packages/frontend/src/components/compatibility/InputOTP.tsx b/packages/frontend/src/components/compatibility/InputOTP.tsx deleted file mode 100644 index c41d046f..00000000 --- a/packages/frontend/src/components/compatibility/InputOTP.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { - InputOTPGroup, - InputOTPSlot, - InputOTP as UIInputOTP, -} from '@/components/ui/input-otp'; - -export const InputOTP = UIInputOTP; -export { - InputOTPGroup, - InputOTPSlot -}; diff --git a/packages/frontend/src/components/compatibility/Modal.tsx b/packages/frontend/src/components/compatibility/Modal.tsx deleted file mode 100644 index cf0f4be4..00000000 --- a/packages/frontend/src/components/compatibility/Modal.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { - Dialog as UIDialog, - DialogContent as UIDialogContent, - DialogDescription as UIDialogDescription, - DialogFooter as UIDialogFooter, - DialogHeader as UIDialogHeader, - DialogTitle as UIDialogTitle, - DialogTrigger as UIDialogTrigger, -} from '@/components/ui/dialog'; -import { cn } from '@/lib/utils'; -import * as React from 'react'; - -interface ModalProps { - open?: boolean; - onClose?: () => void; - children?: React.ReactNode; - className?: string; -} - -// Main Modal component that matches MUI's Modal API -export function Modal({ open, onClose, children }: ModalProps) { - return ( - !isOpen && onClose?.()}> - {children} - - ); -} - -// Box-like wrapper for modal content to match MUI's style -export function ModalContent({ - children, - className, - ...props -}: React.HTMLAttributes) { - return ( - - {children} - - ); -} - -interface ModalTitleProps extends React.HTMLAttributes { - children?: React.ReactNode; -} - -export function ModalTitle({ children, className, ...props }: ModalTitleProps) { - return ( - - {children} - - ); -} - -export function ModalDescription({ children, className, ...props }: React.HTMLAttributes) { - return ( - - {children} - - ); -} - -export function ModalFooter({ children, className, ...props }: React.HTMLAttributes) { - return ( - - {children} - - ); -} - -export function ModalHeader({ children, className, ...props }: React.HTMLAttributes) { - return ( - - {children} - - ); -} - -export function ModalTrigger({ children, className, ...props }: React.ComponentProps) { - return ( - - {children} - - ); -} - -// For backwards compatibility with MUI Dialog naming -export const Dialog = Modal; -export const DialogContent = ModalContent; -export const DialogTitle = ModalTitle; -export const DialogDescription = ModalDescription; -export const DialogFooter = ModalFooter; -export const DialogHeader = ModalHeader; -export const DialogTrigger = ModalTrigger; -export const DialogActions = DialogFooter; // MUI specific alias \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/OverflownText.tsx b/packages/frontend/src/components/compatibility/OverflownText.tsx deleted file mode 100644 index 3d4229bc..00000000 --- a/packages/frontend/src/components/compatibility/OverflownText.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { cn } from '@/lib/utils'; -import * as React from 'react'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './Tooltip'; - -interface OverflownTextProps extends React.HTMLAttributes { - text: string; - maxWidth?: string | number; -} - -export function OverflownText({ text, maxWidth = '100%', className, ...props }: OverflownTextProps) { - const [isOverflown, setIsOverflown] = React.useState(false); - const textRef = React.useRef(null); - - React.useEffect(() => { - const element = textRef.current; - if (element) { - setIsOverflown(element.scrollWidth > element.clientWidth); - } - }, [text]); - - const content = ( -
- {text} -
- ); - - if (!isOverflown) { - return content; - } - - return ( - - - - {content} - - -

{text}

-
-
-
- ); -} \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/Radio.tsx b/packages/frontend/src/components/compatibility/Radio.tsx deleted file mode 100644 index b9b36011..00000000 --- a/packages/frontend/src/components/compatibility/Radio.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; -import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; -import type { ComponentPropsWithoutRef } from 'react'; - -export const Radio = RadioGroup; -export const RadioItem = RadioGroupItem; -export type RadioGroupProps = ComponentPropsWithoutRef; -export type RadioGroupItemProps = ComponentPropsWithoutRef; diff --git a/packages/frontend/src/components/compatibility/Select.tsx b/packages/frontend/src/components/compatibility/Select.tsx deleted file mode 100644 index 02604b21..00000000 --- a/packages/frontend/src/components/compatibility/Select.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, - Select as UISelect, -} from '@/components/ui/select'; - -export const Select = UISelect; -export { - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue -}; diff --git a/packages/frontend/src/components/compatibility/Separator.tsx b/packages/frontend/src/components/compatibility/Separator.tsx deleted file mode 100644 index f1918293..00000000 --- a/packages/frontend/src/components/compatibility/Separator.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { Separator as UISeparator } from '@/components/ui/separator'; - -export const Separator = UISeparator; \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/Sheet.tsx b/packages/frontend/src/components/compatibility/Sheet.tsx deleted file mode 100644 index dcae7e36..00000000 --- a/packages/frontend/src/components/compatibility/Sheet.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { - SheetClose, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, - Sheet as UISheet, -} from '@/components/ui/sheet'; - -export const Sheet = UISheet; -export { - SheetClose, SheetContent, - SheetDescription, SheetFooter, SheetHeader, - SheetTitle, - SheetTrigger -}; diff --git a/packages/frontend/src/components/compatibility/Switch.tsx b/packages/frontend/src/components/compatibility/Switch.tsx deleted file mode 100644 index 2e82f1bd..00000000 --- a/packages/frontend/src/components/compatibility/Switch.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Switch as UISwitch } from '@/components/ui/switch'; -import * as SwitchPrimitive from '@radix-ui/react-switch'; -import type { ComponentPropsWithoutRef } from 'react'; - -export const Switch = UISwitch; -export type SwitchProps = ComponentPropsWithoutRef; \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/Table.tsx b/packages/frontend/src/components/compatibility/Table.tsx deleted file mode 100644 index 7c8943ad..00000000 --- a/packages/frontend/src/components/compatibility/Table.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { - TableBody, - TableCaption, - TableCell, - TableFooter, - TableHead, - TableHeader, - TableRow, - Table as UITable, -} from '@/components/ui/table'; - -export const Table = UITable; -export { - TableBody, - TableCaption, - TableCell, - TableFooter, - TableHead, - TableHeader, - TableRow -}; diff --git a/packages/frontend/src/components/compatibility/Tabs.tsx b/packages/frontend/src/components/compatibility/Tabs.tsx deleted file mode 100644 index 7c730ae8..00000000 --- a/packages/frontend/src/components/compatibility/Tabs.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { - TabsContent, - TabsList, - TabsTrigger, - Tabs as UITabs, -} from '@/components/ui/tabs'; - -export const Tabs = UITabs; -export { - TabsContent, TabsList, - TabsTrigger -}; diff --git a/packages/frontend/src/components/compatibility/Tag.tsx b/packages/frontend/src/components/compatibility/Tag.tsx deleted file mode 100644 index e0c20d5a..00000000 --- a/packages/frontend/src/components/compatibility/Tag.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Badge } from '@/components/ui/badge'; -import { cn } from '@/lib/utils'; -import { type ComponentProps, type CSSProperties, type ReactNode } from 'react'; - -type BadgeProps = ComponentProps; - -interface TagProps extends Omit { - variant?: 'default' | 'success' | 'warning' | 'danger' | 'outline'; - type?: 'attention' | 'negative' | 'positive' | 'emphasized' | 'neutral'; - tagStyle?: 'default' | 'minimal'; - size?: 'sm' | 'xs'; - leftIcon?: ReactNode; - rightIcon?: ReactNode; - style?: CSSProperties; -} - -export function Tag({ - className, - variant, - type = 'attention', - tagStyle = 'default', - size = 'sm', - leftIcon, - rightIcon, - children, - style, - ...props -}: TagProps) { - // Map old type to new variant if variant not explicitly set - const mappedVariant = variant || { - attention: 'warning', - negative: 'danger', - positive: 'success', - emphasized: 'default', - neutral: 'default', - }[type] || 'default'; - - return ( - - {leftIcon} - {children} - {rightIcon} - - ); -} \ No newline at end of file diff --git a/packages/frontend/src/components/compatibility/Toast.tsx b/packages/frontend/src/components/compatibility/Toast.tsx deleted file mode 100644 index ce42aca9..00000000 --- a/packages/frontend/src/components/compatibility/Toast.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type { ToastActionElement, ToastProps } from '@/components/ui/toast'; -import { toast, useToast as useUIToast } from '@/hooks/use-toast'; - -export const useToast = useUIToast; -export { toast }; -export type { ToastActionElement, ToastProps }; - diff --git a/packages/frontend/src/components/compatibility/ToggleGroup.tsx b/packages/frontend/src/components/compatibility/ToggleGroup.tsx deleted file mode 100644 index dfd970ff..00000000 --- a/packages/frontend/src/components/compatibility/ToggleGroup.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { - ToggleGroupItem, - ToggleGroup as UIToggleGroup, -} from '@/components/ui/toggle-group'; - -export const ToggleGroup = UIToggleGroup; -export { ToggleGroupItem }; diff --git a/packages/frontend/src/components/compatibility/Tooltip.tsx b/packages/frontend/src/components/compatibility/Tooltip.tsx deleted file mode 100644 index f8306b30..00000000 --- a/packages/frontend/src/components/compatibility/Tooltip.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { - TooltipContent, - TooltipProvider, - TooltipTrigger, - Tooltip as UITooltip, -} from '@/components/ui/tooltip'; - -export const Tooltip = UITooltip; -export { - TooltipContent, - TooltipProvider, - TooltipTrigger -}; diff --git a/packages/frontend/src/components/compatibility/UserSelect.tsx b/packages/frontend/src/components/compatibility/UserSelect.tsx deleted file mode 100644 index 5641a035..00000000 --- a/packages/frontend/src/components/compatibility/UserSelect.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Check } from 'lucide-react'; -import * as React from 'react'; - -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from '@/components/ui/command'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; -import { Avatar, AvatarFallback, AvatarImage } from './Avatar'; -import { Button } from './Button'; - -interface User { - id: string; - name: string; - email: string; - avatar?: string; -} - -interface UserSelectProps { - users: User[]; - value?: string; - onChange?: (value: string) => void; - placeholder?: string; -} - -export function UserSelect({ users, value, onChange, placeholder = 'Select user...' }: UserSelectProps) { - const [open, setOpen] = React.useState(false); - const selectedUser = users.find(user => user.id === value); - - return ( - - - - - - - - No users found. - - {users.map(user => ( - { - onChange?.(user.id); - setOpen(false); - }} - className="flex items-center gap-2" - > - - - {user.name.charAt(0)} - -
- {user.name} - {user.email} -
- {value === user.id && ( - - )} -
- ))} -
-
-
-
- ); -} \ No newline at end of file diff --git a/packages/frontend/src/components/examples/ExamplePage.tsx b/packages/frontend/src/components/examples/ExamplePage.tsx new file mode 100644 index 00000000..4345f75f --- /dev/null +++ b/packages/frontend/src/components/examples/ExamplePage.tsx @@ -0,0 +1,57 @@ +import { ReactNode } from 'react'; +import { + Header, + NavigationWrapper, + ScreenWrapper, + TabWrapper, + TabsContent, + TabsList, + TabsTrigger, +} from '../layout'; + +interface ExamplePageProps { + children?: ReactNode; +} + +export function ExamplePage({ children }: ExamplePageProps) { + return ( + + +
+ Action + , + ]} + /> + + + + Overview + Details + Settings + + + +
+

Overview Content

+ {children} +
+
+ +
+

Details Content

+
+
+ +
+

Settings Content

+
+
+
+ + + ); +} diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts new file mode 100644 index 00000000..5ecdd1f3 --- /dev/null +++ b/packages/frontend/src/components/index.ts @@ -0,0 +1 @@ +export * from './ui'; diff --git a/packages/frontend/src/components/layout/index.ts b/packages/frontend/src/components/layout/index.ts new file mode 100644 index 00000000..6b7e5fa5 --- /dev/null +++ b/packages/frontend/src/components/layout/index.ts @@ -0,0 +1,9 @@ +export { NavigationWrapper } from './navigation'; +export { Header } from './screen-header/Header'; +export { ScreenWrapper } from './screen-wrapper/ScreenWrapper'; +export { TabWrapper } from './screen-wrapper/TabWrapper'; +// Re-export tab components for convenience +export { + TabsContent, TabsList, + TabsTrigger +} from '@/components/ui/tabs'; diff --git a/packages/frontend/src/components/layout/navigation/NavigationWrapper.tsx b/packages/frontend/src/components/layout/navigation/NavigationWrapper.tsx new file mode 100644 index 00000000..36ff0951 --- /dev/null +++ b/packages/frontend/src/components/layout/navigation/NavigationWrapper.tsx @@ -0,0 +1,31 @@ +import { cn } from '@/lib/utils'; +import { TopNavigation } from './TopNavigation'; + +/** + * NavigationWrapperProps interface extends React.HTMLProps to include all standard HTML div attributes. + */ +interface NavigationWrapperProps extends React.HTMLProps {} + +/** + * Wraps the application's navigation and content within a consistent layout. + * It provides a basic structure with a top navigation bar and a content area. + * + * @param {NavigationWrapperProps} props - The props passed to the NavigationWrapper component. + * @param {React.ReactNode} props.children - The content to be rendered within the wrapper. + * @param {string} [props.className] - Optional CSS class names to apply to the wrapper. + * @param {React.HTMLAttributes} props.props - Other standard HTML attributes. + * + * @returns {React.ReactElement} A div element containing the top navigation and the children. + */ +export function NavigationWrapper({ + children, + className, + ...props +}: NavigationWrapperProps) { + return ( +
+ + {children} +
+ ); +} diff --git a/packages/frontend/src/components/layout/navigation/TopNavigation.tsx b/packages/frontend/src/components/layout/navigation/TopNavigation.tsx new file mode 100644 index 00000000..db7162f7 --- /dev/null +++ b/packages/frontend/src/components/layout/navigation/TopNavigation.tsx @@ -0,0 +1,149 @@ +'use client'; +import { Button } from '@/components/ui/button'; +import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; +import { LaconicMark } from '@/laconic-assets/laconic-mark'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; +import { Menu, Shapes, Wallet } from 'lucide-react'; +import { Link, useNavigate } from 'react-router-dom'; +import { ProjectSearchBar } from '../search/ProjectSearchBar'; +import { ColorModeToggle } from './components/ColorModeToggle'; +import { GitHubSessionButton } from './components/GitHubSessionButton'; +import { WalletSessionId } from './components/WalletSessionId'; + +/** + * TopNavigation Component + * + * Renders the top navigation bar, adapting its layout for desktop and mobile views. + * It includes the project search bar, navigation links, user authentication via GitHub, + * color mode toggle, and wallet session information. On mobile, it utilizes a sheet + * for a responsive and accessible navigation experience. + * + * @returns {React.ReactElement} A PopoverPrimitive.Root element containing the top navigation bar. + */ +export function TopNavigation() { + const navigate = useNavigate(); + + return ( + +
+ {/* Top Navigation - Desktop */} + + + {/* Top Navigation - Mobile */} + +
+
+ ); +} diff --git a/packages/frontend/src/components/layout/navigation/components/ColorModeToggle.tsx b/packages/frontend/src/components/layout/navigation/components/ColorModeToggle.tsx new file mode 100644 index 00000000..be770362 --- /dev/null +++ b/packages/frontend/src/components/layout/navigation/components/ColorModeToggle.tsx @@ -0,0 +1,19 @@ +import { Button } from '@/components/ui/button'; +import { Moon, Sun } from 'lucide-react'; +import { useTheme } from 'next-themes'; + +export function ColorModeToggle() { + const { theme, setTheme } = useTheme(); + + function toggleTheme() { + setTheme(theme === 'dark' ? 'light' : 'dark'); + } + + return ( + + ); +} diff --git a/packages/frontend/src/components/layout/navigation/components/GitHubSessionButton.tsx b/packages/frontend/src/components/layout/navigation/components/GitHubSessionButton.tsx new file mode 100644 index 00000000..a5bd46f7 --- /dev/null +++ b/packages/frontend/src/components/layout/navigation/components/GitHubSessionButton.tsx @@ -0,0 +1,67 @@ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuPortal, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { useGitHubAuth } from '@/hooks/useGitHubAuth'; +import { Github } from 'lucide-react'; + +/** + * Renders a button that allows users to log in or log out with their GitHub account. + * It displays the user's avatar and username when logged in, and provides a logout option. + * + * @returns {React.ReactElement} A DropdownMenu element containing the GitHub session button. + */ +export function GitHubSessionButton() { + const { isAuthenticated, user, login, logout } = useGitHubAuth(); + + return ( + + + + + + + {isAuthenticated && user ? ( + <> +
+ + + + {user.login.slice(0, 2).toUpperCase()} + + +
+ + {user.name || user.login} + + + {user.login} + +
+
+ Log out + + ) : ( + + Log in with GitHub + + )} +
+
+
+ ); +} diff --git a/packages/frontend/src/components/navigation/LaconicIcon.tsx b/packages/frontend/src/components/layout/navigation/components/LaconicIcon.tsx similarity index 59% rename from packages/frontend/src/components/navigation/LaconicIcon.tsx rename to packages/frontend/src/components/layout/navigation/components/LaconicIcon.tsx index c4435906..b7da2810 100644 --- a/packages/frontend/src/components/navigation/LaconicIcon.tsx +++ b/packages/frontend/src/components/layout/navigation/components/LaconicIcon.tsx @@ -1,12 +1,36 @@ -import type React from "react" +import type React from 'react'; +/** + * LaconicIconProps interface defines the props for the LaconicIcon component. + */ interface LaconicIconProps { - className?: string - width?: number - height?: number + /** + * Optional CSS class names to apply to the component. + */ + className?: string; + /** + * The width of the icon. + * @default 40 + */ + width?: number; + /** + * The height of the icon. + * @default 40 + */ + height?: number; } -export const LaconicIcon: React.FC = ({ className = "", width = 40, height = 40 }) => { +/** + * A component that renders the Laconic icon. + * + * @param {LaconicIconProps} props - The props for the component. + * @returns {React.ReactElement} An SVG element representing the Laconic icon. + */ +export const LaconicIcon: React.FC = ({ + className = '', + width = 40, + height = 40, +}) => { return ( = ({ className = "", width className="fill-current" /> - ) -} - + ); +}; diff --git a/packages/frontend/src/components/layout/navigation/components/NavigationActions.tsx b/packages/frontend/src/components/layout/navigation/components/NavigationActions.tsx new file mode 100644 index 00000000..1807b6e2 --- /dev/null +++ b/packages/frontend/src/components/layout/navigation/components/NavigationActions.tsx @@ -0,0 +1,44 @@ +import { Button } from '@/components/ui/button'; +import { useGQLClient } from '@/context/GQLClientContext'; +import { Bell, Plus } from 'lucide-react'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; + +/** + * Renders the navigation actions, including buttons for creating a new project and displaying notifications. + * + * @returns {React.ReactElement} A div element containing the navigation actions. + */ +export function NavigationActions() { + const navigate = useNavigate(); + const client = useGQLClient(); + + /** + * Fetches the organization slug. + * @returns {Promise} The organization slug. + */ + const fetchOrgSlug = useCallback(async () => { + const { organizations } = await client.getOrganizations(); + // TODO: Get the selected organization. This is temp + return organizations[0].slug; + }, [client]); + + return ( +
+ + +
+ ); +} diff --git a/packages/frontend/src/components/navigation/WalletSessionId.tsx b/packages/frontend/src/components/layout/navigation/components/WalletSessionId.tsx similarity index 59% rename from packages/frontend/src/components/navigation/WalletSessionId.tsx rename to packages/frontend/src/components/layout/navigation/components/WalletSessionId.tsx index d861b1f4..5c642f68 100644 --- a/packages/frontend/src/components/navigation/WalletSessionId.tsx +++ b/packages/frontend/src/components/layout/navigation/components/WalletSessionId.tsx @@ -1,16 +1,31 @@ import type React from 'react'; +/** + * WalletSessionIdProps interface defines the props for the WalletSessionId component. + */ interface WalletSessionIdProps { + /** + * The wallet ID to display. + */ walletId?: string; + /** + * Optional CSS class names to apply to the component. + */ className?: string; } +/** + * A component that displays the wallet session ID. + * + * @param {WalletSessionIdProps} props - The props for the component. + * @returns {React.ReactElement} A div element containing the wallet session ID. + */ export const WalletSessionId: React.FC = ({ walletId, className = '', }) => { // const { wallet } = useWallet(); - const wallet = {id: 'x123xxx'} + const wallet = { id: 'x123xxx' }; const displayId = walletId || wallet?.id || 'Not Connected'; return ( diff --git a/packages/frontend/src/components/layout/navigation/index.ts b/packages/frontend/src/components/layout/navigation/index.ts new file mode 100644 index 00000000..997f63ce --- /dev/null +++ b/packages/frontend/src/components/layout/navigation/index.ts @@ -0,0 +1,9 @@ +export { ProjectSearchBar } from '../search/ProjectSearchBar'; +export { ColorModeToggle } from './components/ColorModeToggle'; +export { GitHubSessionButton } from './components/GitHubSessionButton'; +export { LaconicIcon } from './components/LaconicIcon'; +export { NavigationActions } from './components/NavigationActions'; +export { WalletSessionId } from './components/WalletSessionId'; +export { NavigationWrapper } from './NavigationWrapper'; +export { TopNavigation } from './TopNavigation'; + diff --git a/packages/frontend/src/components/layout/screen-header/ActionButton.tsx b/packages/frontend/src/components/layout/screen-header/ActionButton.tsx new file mode 100644 index 00000000..16aaae66 --- /dev/null +++ b/packages/frontend/src/components/layout/screen-header/ActionButton.tsx @@ -0,0 +1,56 @@ +import { Button } from '@/components/ui/button'; +import { useToast } from '@/hooks/use-toast'; +import type { LucideIcon } from 'lucide-react'; + +/** + * ActionButtonProps interface defines the props for the ActionButton component. + */ +interface ActionButtonProps { + /** + * The label of the button. + */ + label: string; + /** + * The icon to display in the button. + */ + icon: LucideIcon; + /** + * The variant of the button. + * @default 'default' + */ + variant?: 'default' | 'outline'; + /** + * Callback function to be called when the button is clicked. + */ + onClick?: () => void; +} + +/** + * A button component that displays an icon and a label, and triggers an action when clicked. + * + * @param {ActionButtonProps} props - The props for the component. + * @returns {React.ReactElement} A Button element. + */ +export function ActionButton({ + label, + icon: Icon, + variant = 'default', + onClick, +}: ActionButtonProps) { + const { toast } = useToast(); + + const handleClick = () => { + onClick?.(); + toast({ + title: 'Action Triggered', + description: 'TODO: Connect action', + }); + }; + + return ( + + ); +} diff --git a/packages/frontend/src/components/layout/screen-header/Header.tsx b/packages/frontend/src/components/layout/screen-header/Header.tsx new file mode 100644 index 00000000..694dd248 --- /dev/null +++ b/packages/frontend/src/components/layout/screen-header/Header.tsx @@ -0,0 +1,89 @@ +import { cn } from '@/lib/utils'; +import { ReactNode } from 'react'; + +/** + * HeaderProps interface defines the props for the Header component. + */ +interface HeaderProps { + /** + * The title of the header. + */ + title: string; + /** + * The subtitle of the header. + */ + subtitle?: string; + /** + * An array of actions to display in the header. + */ + actions?: ReactNode[]; + /** + * A back button to display in the header. + */ + backButton?: ReactNode; + /** + * Optional CSS class names to apply to the header. + */ + className?: string; + /** + * The layout of the header. + * @default 'default' + */ + layout?: 'default' | 'compact'; +} + +/** + * A component that renders a header with a title, subtitle, actions, and back button. + * + * @param {HeaderProps} props - The props for the component. + * @returns {React.ReactElement} A div element containing the header content. + */ +export function Header({ + title, + subtitle, + actions, + backButton, + className, + layout = 'default', +}: HeaderProps) { + return ( +
+
+ {backButton} +
+

+ {title} +

+ {subtitle && ( +

{subtitle}

+ )} +
+
+ {actions && actions.length > 0 && ( +
+ {actions.map((action, index) => ( +
{action}
+ ))} +
+ )} +
+ ); +} diff --git a/packages/frontend/src/components/layout/screen-wrapper/ScreenWrapper.tsx b/packages/frontend/src/components/layout/screen-wrapper/ScreenWrapper.tsx new file mode 100644 index 00000000..bb868d41 --- /dev/null +++ b/packages/frontend/src/components/layout/screen-wrapper/ScreenWrapper.tsx @@ -0,0 +1,38 @@ +import { cn } from '@/lib/utils'; + +/** + * ScreenWrapperProps interface extends React.HTMLProps to include all standard HTML div attributes. + */ +interface ScreenWrapperProps extends React.HTMLProps { + /** + * Whether the screen should be padded. + * @default true + */ + padded?: boolean; +} + +/** + * A wrapper component for screens, providing consistent padding and layout. + * + * @param {ScreenWrapperProps} props - The props for the component. + * @returns {React.ReactElement} A div element containing the screen content. + */ +export function ScreenWrapper({ + children, + className, + // padded = true, + ...props +}: ScreenWrapperProps) { + return ( +
+ {children} +
+ ); +} diff --git a/packages/frontend/src/components/layout/screen-wrapper/TabWrapper.tsx b/packages/frontend/src/components/layout/screen-wrapper/TabWrapper.tsx new file mode 100644 index 00000000..387f7534 --- /dev/null +++ b/packages/frontend/src/components/layout/screen-wrapper/TabWrapper.tsx @@ -0,0 +1,43 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { cn } from '@/lib/utils'; + +/** + * TabWrapperProps interface extends React.ComponentProps to include all standard Tabs attributes. + */ +interface TabWrapperProps extends React.ComponentProps { + /** + * The orientation of the tabs. + * @default 'horizontal' + */ + orientation?: 'horizontal' | 'vertical'; +} + +/** + * A wrapper component for the Tabs component from `ui/tabs`. + * + * @param {TabWrapperProps} props - The props for the component. + * @returns {React.ReactElement} A Tabs element containing the tabs and content. + */ +export function TabWrapper({ + children, + className, + orientation = 'horizontal', + ...props +}: TabWrapperProps) { + return ( + + {children} + + ); +} + +// Re-export tab components for convenience +export { TabsContent, TabsList, TabsTrigger }; diff --git a/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBar.tsx b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBar.tsx new file mode 100644 index 00000000..4f03d930 --- /dev/null +++ b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBar.tsx @@ -0,0 +1,106 @@ +import { useCombobox } from 'downshift'; +import { Project } from 'gql-client'; +import { useCallback, useEffect, useState } from 'react'; +import { useDebounceValue } from 'usehooks-ts'; + +import SearchBar from '@/components/SearchBar'; +import { useGQLClient } from '@/context/GQLClientContext'; +import { cn } from '@/utils/classnames'; +import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; +import { ProjectSearchBarItem } from './ProjectSearchBarItem'; + +/** + * ProjectSearchBarProps interface defines the props for the ProjectSearchBar component. + */ +interface ProjectSearchBarProps { + /** + * Callback function to be called when a project is selected. + * @param data - The selected project data. + */ + onChange?: (data: Project) => void; +} + +/** + * A search bar component that allows the user to search for projects. + * + * @param {ProjectSearchBarProps} props - The props for the component. + * @returns {React.ReactElement} A div element containing the search bar and project list. + */ +export const ProjectSearchBar = ({ onChange }: ProjectSearchBarProps) => { + const [items, setItems] = useState([]); + const [selectedItem, setSelectedItem] = useState(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, _] = useDebounceValue(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 ( +
+ +
+ {items.length ? ( + <> +
+

+ Suggestions +

+
+ {items.map((item, index) => ( + + ))} + + ) : ( + + )} +
+
+ ); +}; diff --git a/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarDialog.tsx b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarDialog.tsx new file mode 100644 index 00000000..dbaf6d9e --- /dev/null +++ b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarDialog.tsx @@ -0,0 +1,146 @@ +import { Button } from '@/components/ui/button'; +import { IconInput } from '@/components/ui/extended/input-w-icons'; +import { useGQLClient } from '@/context/GQLClientContext'; +import * as Dialog from '@radix-ui/react-dialog'; +import { useCombobox } from 'downshift'; +import { Project } from 'gql-client'; +import { Search, X } from 'lucide-react'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useDebounceValue } from 'usehooks-ts'; +import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; +import { ProjectSearchBarItem } from './ProjectSearchBarItem'; + +/** + * ProjectSearchBarDialogProps interface defines the props for the ProjectSearchBarDialog component. + */ +interface ProjectSearchBarDialogProps extends Dialog.DialogProps { + /** + * Whether the dialog is open. + */ + open?: boolean; + /** + * Callback function to be called when the dialog is closed. + */ + onClose?: () => void; + /** + * Callback function to be called when a project item is clicked. + * @param data - The selected project data. + */ + onClickItem?: (data: Project) => void; +} + +/** + * A dialog component that allows the user to search for projects. + * + * @param {ProjectSearchBarDialogProps} props - The props for the component. + * @returns {React.ReactElement} A Dialog.Root element containing the search bar and project list. + */ +export const ProjectSearchBarDialog = ({ + onClose, + onClickItem, + ...props +}: ProjectSearchBarDialogProps) => { + const [items, setItems] = useState([]); + const [selectedItem, setSelectedItem] = useState(null); + const client = useGQLClient(); + const navigate = useNavigate(); + + const { + getInputProps, + getItemProps, + getMenuProps, + inputValue, + setInputValue, + } = useCombobox({ + items, + itemToString(item) { + return item ? item.name : ''; + }, + selectedItem, + onSelectedItemChange: ({ selectedItem: newSelectedItem }) => { + if (newSelectedItem) { + setSelectedItem(newSelectedItem); + onClickItem?.(newSelectedItem); + navigate( + `/${newSelectedItem.organization.slug}/projects/${newSelectedItem.id}`, + ); + } + }, + }); + + const [debouncedInputValue, _] = useDebounceValue(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 ( + + + + +
+
+ } + placeholder="Search" + className="border-none shadow-none" + autoFocus + /> + +
+ {/* Content */} +
+ {items.length > 0 ? ( + <> +
+

+ Suggestions +

+
+ {items.map((item, index) => ( + + ))} + + ) : ( + inputValue && + )} +
+
+
+
+
+ ); +}; diff --git a/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarEmpty.tsx b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarEmpty.tsx new file mode 100644 index 00000000..4d3dec5b --- /dev/null +++ b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarEmpty.tsx @@ -0,0 +1,33 @@ +import { cn } from '@/utils/classnames'; +import { Search } from 'lucide-react'; +import { ComponentPropsWithoutRef } from 'react'; + +/** + * ProjectSearchBarEmptyProps interface defines the props for the ProjectSearchBarEmpty component. + */ +interface ProjectSearchBarEmptyProps extends ComponentPropsWithoutRef<'div'> {} + +/** + * A component that renders a message when no projects match the search query. + * + * @param {ProjectSearchBarEmptyProps} props - The props for the component. + * @returns {React.ReactElement} A div element displaying a "no projects found" message. + */ +export const ProjectSearchBarEmpty = ({ + className, + ...props +}: ProjectSearchBarEmptyProps) => { + return ( +
+
+ +
+

+ No projects matching this name +

+
+ ); +}; diff --git a/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarItem.tsx b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarItem.tsx new file mode 100644 index 00000000..dc8f88db --- /dev/null +++ b/packages/frontend/src/components/layout/search/ProjectSearchBar/ProjectSearchBarItem.tsx @@ -0,0 +1,75 @@ +import { Avatar } from '@/components/shared/Avatar'; +import { OmitCommon } from '@/types/common'; +import { cn } from '@/utils/classnames'; + +import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift'; +import { Project } from 'gql-client'; +import { ComponentPropsWithoutRef, forwardRef } from 'react'; +import { getInitials } from '../../../../utils/geInitials'; + +/** + * Represents a type that merges ComponentPropsWithoutRef<'li'> with certain exclusions. + * @type {MergedComponentPropsWithoutRef} + */ +type MergedComponentPropsWithoutRef = OmitCommon< + ComponentPropsWithoutRef<'button'>, + Omit< + Overwrite, + 'index' | 'item' + > +>; + +/** + * ProjectSearchBarItemProps interface defines the props for the ProjectSearchBarItem component. + */ +interface ProjectSearchBarItemProps extends MergedComponentPropsWithoutRef { + /** + * The project item to display. + */ + item: Project; + /** + * Whether the item is active. + */ + active?: boolean; +} + +/** + * A component that renders a project item in the search bar. + * + * @param {ProjectSearchBarItemProps} props - The props for the component. + * @returns {React.ReactElement} A button element representing a project item. + */ +const ProjectSearchBarItem = forwardRef< + HTMLButtonElement, + ProjectSearchBarItemProps +>(({ item, active, ...props }, ref) => { + return ( + + ); +}); + +ProjectSearchBarItem.displayName = 'ProjectSearchBarItem'; + +export { ProjectSearchBarItem }; diff --git a/packages/frontend/src/components/layout/search/ProjectSearchBar/index.ts b/packages/frontend/src/components/layout/search/ProjectSearchBar/index.ts new file mode 100644 index 00000000..6fd8929b --- /dev/null +++ b/packages/frontend/src/components/layout/search/ProjectSearchBar/index.ts @@ -0,0 +1,2 @@ +export * from './ProjectSearchBar'; +export * from './ProjectSearchBarDialog'; diff --git a/packages/frontend/src/components/loading/loading-overlay.tsx b/packages/frontend/src/components/loading/loading-overlay.tsx new file mode 100644 index 00000000..f83511ff --- /dev/null +++ b/packages/frontend/src/components/loading/loading-overlay.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { LaconicMark } from '@/laconic-assets/laconic-mark'; +import { cn } from '@/lib/utils'; +import { Loader2 } from 'lucide-react'; + +export interface LoadingOverlayProps { + /** + * Controls the visibility of the overlay. + * When false, the component returns null. + * @default true + */ + isLoading?: boolean; + + /** + * Optional className for styling the overlay container. + * This will be merged with the default styles. + */ + className?: string; + + /** + * Whether to show the Laconic logo in the overlay. + * @default true + */ + showLogo?: boolean; + + /** + * Whether to show the loading spinner below the logo. + * @default true + */ + showSpinner?: boolean; + + /** + * The z-index value for the overlay. + * Adjust this if you need the overlay to appear above or below other elements. + * @default 50 + */ + zIndex?: number; + + /** + * Whether to use solid black background instead of semi-transparent. + * Useful for initial page load and full-screen loading states. + * @default false + */ + solid?: boolean; +} + +export function LoadingOverlay({ + isLoading = true, + className, + showLogo = true, + showSpinner = true, + zIndex = 50, + solid = false, +}: LoadingOverlayProps) { + if (!isLoading) return null; + + return ( +
+ {showLogo && ( +
+ +
+ )} + {showSpinner && ( +
+ ); +} diff --git a/packages/frontend/src/components/navigation/ColorModeToggle.tsx b/packages/frontend/src/components/navigation/ColorModeToggle.tsx deleted file mode 100644 index afa8e3a2..00000000 --- a/packages/frontend/src/components/navigation/ColorModeToggle.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Moon, Sun } from "lucide-react" -import { useTheme } from "next-themes" - -import { Button } from "@/components/ui/button" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" - -export function ColorModeToggle() { - const { setTheme } = useTheme() - - return ( - - - - - - setTheme("light")}>Light - setTheme("dark")}>Dark - setTheme("system")}>System - - - ) -} - diff --git a/packages/frontend/src/components/navigation/GitHubSessionButton.tsx b/packages/frontend/src/components/navigation/GitHubSessionButton.tsx deleted file mode 100644 index 976bd19b..00000000 --- a/packages/frontend/src/components/navigation/GitHubSessionButton.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { Button } from "@/components/ui/button" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" -import { useGitHubAuth } from "@/hooks/useGitHubAuth" -import { Github } from "lucide-react" - -export function GitHubSessionButton() { - const { isAuthenticated, user, login, logout } = useGitHubAuth() - - return ( - - - - - - - {isAuthenticated && user ? ( - <> -
- - - {user.login.slice(0, 2).toUpperCase()} - -
- {user.name || user.login} - {user.login} -
-
- Log out - - ) : ( - Log in with GitHub - )} -
-
-
- ) -} - diff --git a/packages/frontend/src/components/navigation/NavigationWrapper.tsx b/packages/frontend/src/components/navigation/NavigationWrapper.tsx deleted file mode 100644 index d97a271d..00000000 --- a/packages/frontend/src/components/navigation/NavigationWrapper.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type ReactNode } from 'react'; -import { Outlet } from 'react-router-dom'; -import { TopNavigation } from './TopNavigation'; - -interface NavigationWrapperProps { - children?: ReactNode; -} - -export function NavigationWrapper({ children }: NavigationWrapperProps) { - return ( - <> -
- -
- {/*
*/} - {children || } - {/*
*/} - - ); -} diff --git a/packages/frontend/src/components/navigation/TopNavigation.tsx b/packages/frontend/src/components/navigation/TopNavigation.tsx deleted file mode 100644 index 371f52c4..00000000 --- a/packages/frontend/src/components/navigation/TopNavigation.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client" -import { Button } from "@/components/ui/button" -import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet" -import * as PopoverPrimitive from "@radix-ui/react-popover" -import { Menu, Shapes, Wallet } from "lucide-react" -import { Link } from "react-router-dom" -import { ColorModeToggle } from "./ColorModeToggle" -import { GitHubSessionButton } from "./GitHubSessionButton" -import { LaconicIcon } from "./LaconicIcon" -import { WalletSessionId } from "./WalletSessionId" - -export function TopNavigation() { - // This is a placeholder. In a real app, you'd manage this state with your auth system - - return ( - -
- {/* Top Navigation - Desktop */} - - - {/* Top Navigation - Mobile */} - -
-
- ) -} - diff --git a/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx b/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx index 3b453536..b11e815a 100644 --- a/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx @@ -1,6 +1,6 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; interface CancelDeploymentDialogProps extends ConfirmDialogProps {} @@ -18,7 +18,7 @@ export const CancelDeploymentDialog = ({ open={open} confirmButtonTitle="Yes, cancel deployment" handleConfirm={handleConfirm} - confirmButtonProps={{ variant: 'danger' }} + confirmButtonProps={{ variant: 'destructive' }} >

This will halt the deployment and you'll have to start the process diff --git a/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx b/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx index f761bffb..528f3fda 100644 --- a/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx @@ -1,17 +1,18 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; import { Deployment, Domain } from 'gql-client'; +import { Link } from 'react-router-dom'; -import DeploymentDialogBodyCard from 'components/projects/project/deployments/DeploymentDialogBodyCard'; -import { Button } from 'components/shared/Button'; +import DeploymentDialogBodyCard from '@/components/projects/project/deployments/DeploymentDialogBodyCard'; +import { TagProps } from '@/components/shared/Tag'; +import { Button } from '@/components/ui'; import { - ChevronDown, - Link, ArrowRightCircle, + ChevronDown, + Link as LinkIcon, Loader2, } from 'lucide-react'; -import { TagProps } from 'components/shared/Tag'; interface ChangeStateToProductionDialogProps extends ConfirmDialogProps { deployment: Deployment; @@ -55,7 +56,7 @@ export const ChangeStateToProductionDialog = ({ ), }} > -

+

Upon confirmation, this deployment will be changed to production. @@ -66,7 +67,7 @@ export const ChangeStateToProductionDialog = ({ /> {newDeployment && ( <> -

+
{Array.from({ length: 7 }).map((_, index) => ( ))} @@ -85,15 +86,16 @@ export const ChangeStateToProductionDialog = ({ {domains.length > 0 && domains.map((value) => { return ( - + + + ); })}
diff --git a/packages/frontend/src/components/projects/Dialog/DeleteDeploymentDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteDeploymentDialog.tsx index 878cf460..c8d384c5 100644 --- a/packages/frontend/src/components/projects/Dialog/DeleteDeploymentDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/DeleteDeploymentDialog.tsx @@ -1,10 +1,7 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; -import { - AlertTriangle, -} from 'lucide-react'; - +} from '@/components/shared/ConfirmDialog'; +import { AlertTriangle } from 'lucide-react'; interface DeleteDeploymentDialogProps extends ConfirmDialogProps { isConfirmButtonLoading?: boolean; @@ -30,7 +27,7 @@ export const DeleteDeploymentDialog = ({ } handleConfirm={handleConfirm} confirmButtonProps={{ - variant: 'danger', + variant: 'destructive', disabled: isConfirmButtonLoading, rightIcon: isConfirmButtonLoading ? ( @@ -39,7 +36,7 @@ export const DeleteDeploymentDialog = ({ ), }} > -

+

Once deleted, the deployment will not be accessible.

diff --git a/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx index 3d2d5c16..0ef568cc 100644 --- a/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx @@ -1,6 +1,6 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; interface DeleteDomainDialogProps extends ConfirmDialogProps { projectName: string; @@ -23,9 +23,9 @@ export const DeleteDomainDialog = ({ open={open} confirmButtonTitle="Yes, delete domain" handleConfirm={handleConfirm} - confirmButtonProps={{ variant: 'danger' }} + confirmButtonProps={{ variant: 'destructive' }} > -

+

Once deleted, the project{' '} {projectName} diff --git a/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx index ba014480..b5f6f45c 100644 --- a/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx @@ -1,6 +1,6 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; interface DeleteVariableDialogProps extends ConfirmDialogProps { variableKey: string; @@ -21,9 +21,9 @@ export const DeleteVariableDialog = ({ open={open} confirmButtonTitle="Yes, confirm delete" handleConfirm={handleConfirm} - confirmButtonProps={{ variant: 'danger' }} + confirmButtonProps={{ variant: 'destructive' }} > -

+

Are you sure you want to delete the variable{' '} {variableKey} diff --git a/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx index 2017d4c0..97c9dfaf 100644 --- a/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx @@ -1,6 +1,6 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; interface DeleteWebhookDialogProps extends ConfirmDialogProps { webhookUrl: string; @@ -21,9 +21,9 @@ export const DeleteWebhookDialog = ({ open={open} confirmButtonTitle="Yes, confirm delete" handleConfirm={handleConfirm} - confirmButtonProps={{ variant: 'danger' }} + confirmButtonProps={{ variant: 'destructive' }} > -

+

Are you sure you want to delete{' '} {webhookUrl} diff --git a/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx b/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx index 43d1f372..793b299c 100644 --- a/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx @@ -1,6 +1,6 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; interface DisconnectRepositoryDialogProps extends ConfirmDialogProps {} @@ -18,9 +18,9 @@ export const DisconnectRepositoryDialog = ({ open={open} confirmButtonTitle="Yes, confirm disconnect" handleConfirm={handleConfirm} - confirmButtonProps={{ variant: 'danger' }} + confirmButtonProps={{ variant: 'destructive' }} > -

+

Any data tied to your Git project may become misconfigured. Are you sure you want to continue?

diff --git a/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx b/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx index 01067a71..db224671 100644 --- a/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx @@ -1,8 +1,8 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; -import { formatAddress } from 'utils/format'; +import { formatAddress } from '@/utils/format'; interface RemoveMemberDialogProps extends ConfirmDialogProps { memberName: string; @@ -27,9 +27,9 @@ export const RemoveMemberDialog = ({ open={open} confirmButtonTitle="Yes, remove member" handleConfirm={handleConfirm} - confirmButtonProps={{ variant: 'danger' }} + confirmButtonProps={{ variant: 'destructive' }} > -

+

Once removed, {formatAddress(memberName)} ({formatAddress(ethAddress)}@ {emailDomain}) will not be able to access this project.

diff --git a/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx b/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx index 9e412e38..facac9db 100644 --- a/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx +++ b/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx @@ -1,6 +1,6 @@ import ConfirmDialog, { ConfirmDialogProps, -} from 'components/shared/ConfirmDialog'; +} from '@/components/shared/ConfirmDialog'; interface TransferProjectDialogProps extends ConfirmDialogProps { projectName: string; diff --git a/packages/frontend/src/components/projects/ProjectCard/ProjectCard.tsx b/packages/frontend/src/components/projects/ProjectCard/ProjectCard.tsx index b3ffbc26..4c3f8882 100644 --- a/packages/frontend/src/components/projects/ProjectCard/ProjectCard.tsx +++ b/packages/frontend/src/components/projects/ProjectCard/ProjectCard.tsx @@ -1,28 +1,24 @@ +import { WavyBorder } from '@/components/shared/WavyBorder'; +import { Button } from '@/components/ui'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { - Menu, - MenuHandler, - MenuItem, - MenuList, -} from '@snowballtools/material-tailwind-react-fork'; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { - ComponentPropsWithoutRef, - MouseEvent, - useCallback, -} from 'react'; -import { useNavigate } from 'react-router-dom'; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { getInitials } from '@/utils/geInitials'; +import { relativeTimeMs } from '@/utils/time'; import { Project } from 'gql-client'; -import { Avatar } from 'components/shared/Avatar'; -import { Button } from 'components/shared/Button'; -import { - GitBranch, - AlertTriangle, - MoreHorizontal, - Clock, -} from 'lucide-react'; -import { Tooltip } from 'components/shared/Tooltip'; -import { WavyBorder } from 'components/shared/WavyBorder'; -import { relativeTimeMs } from 'utils/time'; -import { getInitials } from 'utils/geInitials'; +import { AlertTriangle, Clock, GitBranch, MoreHorizontal } from 'lucide-react'; +import { ComponentPropsWithoutRef, MouseEvent, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme'; export interface ProjectCardProps @@ -66,6 +62,25 @@ export const ProjectCard = ({ [project.id, navigate], ); + const handleDeleteClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + // TODO: Add delete functionality + navigate(`projects/${project.id}/settings`); + }, + [project.id, navigate], + ); + + const handleSettingsClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + navigate(`projects/${project.id}/settings`); + }, + [project.id, navigate], + ); + return (
{/* Icon container */} - + + + {getInitials(project.name)} + {/* Title and website */}
- -

{project.name}

-
+ + + +

{project.name}

+
+ +

{project.name}

+
+
+

- {project.deployments[0]?.applicationDeploymentRecordData?.url ?? 'No domain'} + {project.deployments[0]?.applicationDeploymentRecordData?.url ?? + 'No domain'}

{/* Icons */}
{hasError && } - - - - - - + + e.stopPropagation()} + > + Project settings - - + Delete project - - - + + +
{/* Wave */} diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx index 465b833d..8d67094b 100644 --- a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBar.tsx @@ -1,13 +1,13 @@ -import { useCallback, useEffect, useState } from 'react'; import { useCombobox } from 'downshift'; import { Project } from 'gql-client'; +import { useCallback, useEffect, useState } from 'react'; import { useDebounceValue } from 'usehooks-ts'; -import SearchBar from 'components/SearchBar'; -import { useGQLClient } from 'context/GQLClientContext'; -import { cn } from 'utils/classnames'; -import { ProjectSearchBarItem } from './ProjectSearchBarItem'; +import SearchBar from '@/components/SearchBar'; +import { useGQLClient } from '@/context/GQLClientContext'; +import { cn } from '@/utils/classnames'; import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; +import { ProjectSearchBarItem } from './ProjectSearchBarItem'; interface ProjectSearchBarProps { onChange?: (data: Project) => void; diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx index a758bc04..9b20732a 100644 --- a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx @@ -1,15 +1,15 @@ -import { useCallback, useEffect, useState } from 'react'; +import { ProjectSearchBarItem } from '@/components/projects/ProjectSearchBar/ProjectSearchBarItem'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { useGQLClient } from '@/context/GQLClientContext'; import * as Dialog from '@radix-ui/react-dialog'; -import { Button } from 'components/shared/Button'; -import { X, Search } from 'lucide-react'; -import { Input } from 'components/shared/Input'; -import { useGQLClient } from 'context/GQLClientContext'; -import { Project } from 'gql-client'; -import { useDebounceValue } from 'usehooks-ts'; -import { ProjectSearchBarItem } from './ProjectSearchBarItem'; -import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; -import { useNavigate } from 'react-router-dom'; import { useCombobox } from 'downshift'; +import { Project } from 'gql-client'; +import { Search, X } from 'lucide-react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useDebounceValue } from 'usehooks-ts'; +import { ProjectSearchBarEmpty } from './ProjectSearchBarEmpty'; interface ProjectSearchBarDialogProps extends Dialog.DialogProps { open?: boolean; @@ -26,6 +26,7 @@ export const ProjectSearchBarDialog = ({ const [selectedItem, setSelectedItem] = useState(null); const client = useGQLClient(); const navigate = useNavigate(); + const inputRef = useRef(null); const { getInputProps, @@ -75,22 +76,32 @@ export const ProjectSearchBarDialog = ({ return ( - + -
+
- } - placeholder="Search" - appearance="borderless" - autoFocus - /> -
- {/* Content */}
{} diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx index 959af589..3375066c 100644 --- a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx +++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarItem.tsx @@ -1,10 +1,10 @@ -import { Avatar } from 'components/shared/Avatar'; +import { Avatar } from '@/components/shared/Avatar'; +import { OmitCommon } from '@/types/common'; +import { cn } from '@/utils/classnames'; +import { getInitials } from '@/utils/geInitials'; import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift'; import { Project } from 'gql-client'; import { 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. diff --git a/packages/frontend/src/components/projects/create/ApproveTransactionModal.tsx b/packages/frontend/src/components/projects/create/ApproveTransactionModal.tsx index a377bb13..5c7ac027 100644 --- a/packages/frontend/src/components/projects/create/ApproveTransactionModal.tsx +++ b/packages/frontend/src/components/projects/create/ApproveTransactionModal.tsx @@ -1,10 +1,20 @@ -import { Modal, ModalContent } from 'components/shared'; -import { useCallback, useEffect } from 'react'; +import { Modal } from '@/components/shared/Modal'; +import { ModalContent } from '@/components/shared/Modal/ModalContent'; +// import { Modal } from '@mui/material'; import { - VITE_LACONICD_CHAIN_ID, - VITE_WALLET_IFRAME_URL, -} from 'utils/constants'; + VITE_LACONICD_CHAIN_ID, + VITE_WALLET_IFRAME_URL, +} from '@/utils/constants'; +import { useCallback, useEffect } from 'react'; +/** + * ApproveTransactionModal component that displays a modal with an iframe to approve transactions. + * @param {Object} props - The component props. + * @param {function} props.setAccount - Callback function to set the account. + * @param {function} props.setIsDataReceived - Callback function to set whether the data is received. + * @param {boolean} props.isVisible - Determines whether the modal is visible. + * @returns {JSX.Element} - The ApproveTransactionModal component. + */ const ApproveTransactionModal = ({ setAccount, setIsDataReceived, @@ -22,7 +32,9 @@ const ApproveTransactionModal = ({ setIsDataReceived(true); if (event.data.data.length === 0) { - console.error(`Accounts not present for chainId: ${VITE_LACONICD_CHAIN_ID}`); + console.error( + `Accounts not present for chainId: ${VITE_LACONICD_CHAIN_ID}`, + ); return; } @@ -41,6 +53,11 @@ const ApproveTransactionModal = ({ }; }, []); + /** + * Gets the data from the wallet. + * @function getDataFromWallet + * @returns {void} + */ const getDataFromWallet = useCallback(() => { const iframe = document.getElementById('walletIframe') as HTMLIFrameElement; @@ -59,7 +76,7 @@ const ApproveTransactionModal = ({ }, []); return ( - {}}> +