From 5d807db69e9be737a5f0ce8bcded10fd155aab27 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 10 Jun 2024 12:37:38 +0530 Subject: [PATCH] feat: app dir migration (#4743) * feat: creating new app dir structure for web app * fix: moving few pages to app dir * fix: adding profile settings layout * fix: errors on app dir. * chore: remove pages routes. * chore: add sign-in/ sign-up, invitations, onboarding pages. * [WEB-1374] fix: clear changes made on modal close (#4555) * [WEB-1480] fix: preserve page access when making a copy (#4568) * [WEB-1465] fix: theme fluctuation on initial load. (#4638) * [WEB-1445] fix: issue creation on sub groups when cycle/ module grouping is applied. (#4636) * [WEB-1244] fix: add better image insertion and replacement logic in the editor (#4508) * fix: add better image insertion and replacement logic * refactor: image handling in editor * chore: remove passing uploadKey around * refactor: remove unused code * fix: redundant files removed * fix: add is editor ready to discard api to control behvaiours from our app * fix: focus issues and image insertion position when not using slash command * fix: import order fixed * fix: notification mark all as read (#4643) * chore: remove enter key extension (#4648) * [WEB-1467] chore: run the API's required to bootstrap the application in parallel. (#4642) * [WEB - 1482] fix: uploads when using block storages other than s3 and minio (#4647) * fix: minio storage and redirection * dev: disconnect web url and app base url configuration. * fix: negate check while trying to discard (#4653) * fix: email notification preferences (#4656) * [WEB-1493] chore: product tour asset and app sidebar quick action hover (#4655) * chore: product tour asset updated * fix: app sidebar quick action hover * fix: project state setting state name remove camel case logic (#4652) * [WEB-1419] chore: enable module creation with dates older than today. (#4659) * [WEB-1216] chore: increase module empty state for consistency. (#4658) * fix: build errors * [WEB-1235] chore: module and cycle sidebar graph improvement (#4650) * chore: module and cycle sidebar graph improvement * chore: code refactor * [WEB-1424] chore: page and view logo implementation, and emoji/icon (#4662) * [WEB-1424] chore: page and view logo implementation, and emoji/icon picker improvement (#4583) * chore: added logo_props * chore: logo props in cycles, views and modules * chore: emoji icon picker types updated * chore: info icon added to plane ui package * chore: icon color adjust helper function added * style: icon picker ui improvement and default color options updated * chore: update page logo action added in store * chore: emoji code to unicode helper function added * chore: common logo renderer component added * chore: app header project logo updated * chore: project logo updated across platform * chore: page logo picker added * chore: control link component improvement * chore: list item improvement * chore: emoji picker component updated * chore: space app and package logo prop type updated * chore: migration * chore: logo added to project view * chore: page logo picker added in create modal and breadcrumbs * chore: view logo picker added in create modal and updated breadcrumbs * fix: build error * chore: AIO docker images for preview deployments (#4605) * fix: adding single docker base file * action added * fix action * dockerfile.base modified * action fix * dockerfile * fix: base aio dockerfile * fix: dockerfile.base * fix: dockerfile base * fix: modified folder structure * fix: action * fix: dockerfile * fix: dockerfile.base * fix: supervisor file name changed * fix: base dockerfile updated * fix dockerfile base * fix: base dockerfile * fix: docker files * fix: base dockerfile * update base image * modified docker aio base * aio base modified to debian-12-slim * fixes * finalize the dockerfiles with volume exposure * modified the aio build and dockerfile * fix: codacy suggestions implemented * fix: codacy fix * update aio build action --------- Co-authored-by: sriram veeraghanta * fix: merge conflict * chore: lucide react added to planu ui package * chore: new emoji picker component added with lucid icon and code refactor * chore: logo component updated * chore: emoji picker updated for pages and views --------- Co-authored-by: NarayanBavisetti Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Co-authored-by: sriram veeraghanta * fix: build error --------- Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: NarayanBavisetti Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia * refactor: drag handle component (#4663) * refactor: checkbox ui component (#4665) * [WEB-1325] chore: refactor inbox issue store to avoid data loss. (#4640) * [WEB-1325] chore: refactor inbox issue store to avoid data loss. * chore: inbox store improvement. * chore: priority dropdown accepts undefined (#4666) * chore: added buttonClassName prop to label dropdown (#4671) * chore: created new constants for marketing website page links (#4670) * chore: added a prop to render default state conditionally (#4669) * [WEB-1501] dev: multiple select core components (#4667) * dev: multiple select core components * chore: added export statement * chore: created a new constant for archivable state groups (#4668) * chore: added primary variant to the alert modal (#4664) * [WEB-1436] chore: pages improvement. (#4657) * add empty state if no pages are available. * set access to private in create page modal when the modal is open form private tab. * [WEB-1440] chore: update cycle empty state to use project level access. (#4672) * fix: checkbox ui component (#4675) * fix: ai buttons overlapping issue (#4621) * [WEB - 1500] chore: add extra fields on instance and create changelog table to store release change logs (#4673) * chore: add extra fields on instance and create changelog table to store release change logs * dev: rename new_version to latest_version * [WEB - 1505] chore: alter instance id field (#4676) * chore: instance id * dev: update to max length * feat: creating new app dir structure for web app * fix: moving few pages to app dir * feat: creating new app dir structure for web app * fix: moving few pages to app dir * fix: errors on app dir. * chore: remove pages routes. * chore: add sign-in/ sign-up, invitations, onboarding pages. * fix: instance serializer * fix: instance register script (#4681) * fix: instance register script * dev: remove api key and add latest version and current version in types * [WEB-1492] fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add (#4682) * fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add * fix: updated braces in conditions * fix: inbox issue store update logic. (#4683) * chore: update package version * [WEB-1184] feat: issue bulk operations (#4674) * feat: issue bulk operations * style: bulk operations action bar * chore: remove edition separation * style: fix overlapping of response container in AI popover. (#4684) * [WEB-1498] style: fix comments reaction alignment. (#4686) * [WEB-1503] chore: add `autofocus` to name field in inline create/ update state component. (#4685) * [WEB-1312] fix: trim file name before uploading (#4661) * fix: trim file name before uploading * fix: check the cursor position before inserting image * dev: add trimming for file assets * dev: add filename validation above if * dev: make the validation to 50 to support user uploads --------- Co-authored-by: pablohashescobar * [WEB-1481] fix: multiple API calls in inbox issues on closed issues tab. (#4691) * fix: multiple API calls on scroll and closed issues tab. * fix: pagination loader on initial load. * feat: Add components required for estimates (#4690) * Add sortable, radio and typography components * Remove stray css classes * Prevent drag of items from other draggable * Minor cleanup * Update yarn.lock * Remove radio input component as it was build on headless ui v2.0.0 and now we are using v1.7.0 * Fix build errors * Update dependencies in use memo. * [WEB-1521] chore: add configuration to enable/disable sign-ups. (#4697) * fix: regenerating lock file * fix: docker image build errors * fix: remove `setupInterceptors` to avoid circular dependency. * chore: migrate all `accounts` related routes. * chore: migrate all `profiles` related routes. * chore: workspace invitation and onboarding migration / fixes. * chore: installation provider migrations. * regression: focus changing issue with the peek overview editor (#4700) * [WEB-1459] chore: save users all / favorite project list collapse state into localstorage. (#4701) * [WEB-1501] chore: update selected entity details on entities list change (#4702) * chore: update selected entity detials on entities list change * chore: addd selectionHelpers as a prop * [WEB-1517] chore: remove drag handle from list drag block (#4698) * remove drag handle from list drag block * align list group header with list item * rearrange chevron for list subissues and rearrange spaces * adding default draggable property to control link * remove unnecessary dependencies for useEffect * fix: email validation (#4707) * fix: email validation on complete login or sign up functionality * dev: add try catch block * dev: split up code * dev: empty return * fix: cache invalidation on new members invite (#4699) * fix: build test pull request running on non draft PRs (#4708) * fix: cache invalidation on new members invite (#4699) * fix: add version max length (#4713) * chore: migrations for `routing` hooks. * [WEB-1533] chore: fix alignment issues in List and Spreadsheet view (#4714) * fix alignment issues in List and Spreadsheet view * fix spreadsheet indentation * chore: migration for workspace dashboard/ views/ analytics/ settings and active-cycles. * chore: handle undefined identifier case * fix: Overflowing loader in issue edit modal (#4720) * [WEB-1529] chore: workspace sidebar updates. (#4710) * fix: temporary fix exiting lines with slashes (#4725) * [WEB-1537] fix: inline code block size fixed for headers, etc (#4709) * fix: inline code block size fixed for headers, etc * feat: persisting focus accurately post converting the code block into text * fix: typo in error handling * [WEB-1526] feat: add auto merge behaviour to task lists and fix infinite backspace case (#4703) * feat: add auto merge behaviour to task lists * fix: unhandled cases for taskItem and taskList * fix: css task list such that toggling task list doesn't shift things * fix: task list jumps around while trying create/delete things in between two task lists * fix: remove filtering for generic transactions i.e. transactions with some meta data while tying to join things * chore: migration for profile activity along with headers refactor. * [WEB-1201] dev: dropdowns (#4721) * chore: lodash package added * chore: dropdown key down hook added * dev: dropdown component * chore: build error and code refactor * chore: readme file updated * chore: added disabled prop to multiple select components (#4724) * chore: added disabled prop to mutliple select group hoc * style: fix empty space * fix: don't add as a sub-issue if parent has been removed (#4731) * fix: member list item custom menu placement (#4729) * [WEB-1535] chore: project logo picker improvement (#4718) * chore: emoji icon picker improvement * chore: emoji icon picker improvement * fix: resolved border flicker on issue title (#4727) * chore: profile activity empty state added (#4732) * [WEB-1481] fix: inbox issue list update after changing issue status. (#4715) * style: fix ux copy style on project feature preview page. (#4734) * chore: remove clear seleciton logic on escape key press (#4735) * chore: migrations for projects and project issues. * chore: issue and properties filter dropdown improvement (#4733) * save all filters and properties for views (#4728) * chore: migrations for issue details route. * chore: migration for cycle routes. * chore: migration for module routes. * chore: migrations for project views routes. * chore: migrations for project pages routes. * chore: migration for project inbox routes. * chore: migration for project settings routes. * chore: migrations for draft issues routes. * chore: migrations for project archives routes. * chore: remove unused headers. * temp: comment out auth constant and use-reload-confirmation code to avoid errors. --------- Co-authored-by: Prateek Shourya Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: NarayanBavisetti Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia Co-authored-by: guru_sainath Co-authored-by: pablohashescobar Co-authored-by: Satish Gandham Co-authored-by: Henit Chobisa Co-authored-by: Aaryan Khandelwal --- packages/constants/src/auth.ts | 533 +++++------ .../tailwind-config-custom/tailwind.config.js | 1 + packages/ui/src/sortable/sortable.stories.tsx | 3 +- space/types/project.d.ts | 42 + .../@header/active-cycles/header.tsx} | 6 +- .../@header/active-cycles/page.tsx | 9 + .../@header/analytics/header.tsx} | 15 +- .../@header/analytics/page.tsx | 9 + .../[workspaceSlug]/@header/header.tsx} | 11 +- web/app/[workspaceSlug]/@header/page.tsx | 9 + .../profile/[userId]/activity/page.tsx | 8 + .../profile/[userId]/assigned/page.tsx | 12 + .../@header/profile/[userId]/created/page.tsx | 12 + .../@header/profile/[userId]/header.tsx} | 11 +- .../profile/[userId]/mobile-header.tsx} | 7 +- .../@header/profile/[userId]/page.tsx | 8 + .../profile/[userId]/subscribed/page.tsx | 12 + .../archives/[...default-header]/page.tsx | 2 + .../projects/[projectId]/archives/header.tsx} | 11 +- .../issues/[archivedIssueId]/header.tsx} | 28 +- .../issues/[archivedIssueId]/page.tsx | 9 + .../projects/[projectId]/archives/page.tsx | 9 + .../[projectId]/cycles/[cycleId]/header.tsx} | 16 +- .../cycles/[cycleId]/mobile-header.tsx} | 8 +- .../[projectId]/cycles/[cycleId]/page.tsx | 12 + .../projects/[projectId]/cycles/header.tsx} | 8 +- .../[projectId]/cycles/mobile-header.tsx} | 0 .../projects/[projectId]/cycles/page.tsx | 12 + .../[projectId]/draft-issues/header.tsx} | 9 +- .../[projectId]/draft-issues/page.tsx | 9 + .../projects/[projectId]/inbox/header.tsx} | 9 +- .../projects/[projectId]/inbox/page.tsx | 9 + .../[projectId]/issues/[issueId]/header.tsx} | 22 +- .../[projectId]/issues/[issueId]/page.tsx | 9 + .../projects/[projectId]/issues/header.tsx} | 8 +- .../[projectId]/issues/mobile-header.tsx} | 8 +- .../projects/[projectId]/issues/page.tsx | 12 + .../modules/[moduleId]/header.tsx} | 16 +- .../modules/[moduleId]/mobile-header.tsx} | 8 +- .../[projectId]/modules/[moduleId]/page.tsx | 12 + .../projects/[projectId]/modules/header.tsx} | 8 +- .../[projectId]/modules/mobile-header.tsx} | 0 .../projects/[projectId]/modules/page.tsx | 12 + .../[projectId]/pages/[pageId]/header.tsx | 170 ++++ .../[projectId]/pages/[pageId]/page.tsx | 9 + .../projects/[projectId]/pages/header.tsx} | 11 +- .../projects/[projectId]/pages/page.tsx | 9 + .../settings/[...default-header]/page.tsx | 2 + .../projects/[projectId]/settings/header.tsx} | 8 +- .../projects/[projectId]/settings/page.tsx | 9 + .../[projectId]/views/[viewId]/header.tsx} | 9 +- .../[projectId]/views/[viewId]/page.tsx | 9 + .../projects/[projectId]/views/header.tsx} | 9 +- .../projects/[projectId]/views/page.tsx | 9 + .../@header/projects/header.tsx} | 4 +- .../@header/projects/mobile-header.tsx} | 0 .../[workspaceSlug]/@header/projects/page.tsx | 13 + .../settings/[...default-header]/page.tsx | 2 + .../@header/settings/header.tsx} | 6 +- .../[workspaceSlug]/@header/settings/page.tsx | 9 + .../[...default-header]/page.tsx | 2 + .../@header/workspace-views/header.tsx} | 11 +- .../@header/workspace-views/page.tsx | 9 + .../[workspaceSlug]/active-cycles/page.tsx} | 18 +- .../[workspaceSlug]/analytics/page.tsx} | 30 +- .../[workspaceSlug]/app-header-wrapper.tsx | 26 + web/app/[workspaceSlug]/layout.tsx | 25 + .../[workspaceSlug]/page.tsx} | 14 +- .../profile/[userId]/activity/page.tsx} | 38 +- .../profile/[userId]/assigned/page.tsx | 15 + .../profile/[userId]/created/page.tsx | 16 + .../profile/[userId]}/layout.tsx | 28 +- .../profile/[userId]}/navbar.tsx | 8 +- .../profile/[userId]/page.tsx} | 29 +- .../profile/[userId]/subscribed/page.tsx | 16 + .../[projectId]/archives/cycles/page.tsx} | 25 +- .../issues/[archivedIssueId]/page.tsx} | 25 +- .../[projectId]/archives/issues/page.tsx} | 25 +- .../[projectId]/archives/modules/page.tsx} | 23 +- .../[projectId]/cycles/[cycleId]/page.tsx} | 28 +- .../projects/[projectId]/cycles/page.tsx} | 27 +- .../[projectId]/draft-issues/page.tsx} | 24 +- .../projects/[projectId]/inbox/page.tsx | 63 ++ .../[projectId]/issues/[issueId]/page.tsx} | 23 +- .../projects/[projectId]/issues/page.tsx} | 26 +- .../projects/[projectId]/layout.tsx | 13 + .../[projectId]/modules/[moduleId]/page.tsx} | 27 +- .../projects/[projectId]/modules/page.tsx} | 25 +- .../[projectId]/pages/[pageId]/page.tsx} | 23 +- .../projects/[projectId]/pages/page.tsx} | 24 +- .../settings/automations/page.tsx} | 50 +- .../[projectId]/settings/estimates/page.tsx} | 24 +- .../[projectId]/settings/features/page.tsx} | 44 +- .../[projectId]/settings/labels/page.tsx} | 26 +- .../projects/[projectId]/settings}/layout.tsx | 22 +- .../[projectId]/settings/members/page.tsx} | 19 +- .../projects/[projectId]/settings/page.tsx} | 25 +- .../[projectId]/settings}/sidebar.tsx | 21 +- .../[projectId]/settings/states/page.tsx} | 31 +- .../[projectId]/views/[viewId]/page.tsx} | 29 +- .../projects/[projectId]/views/page.tsx} | 23 +- .../[workspaceSlug]/projects/page.tsx} | 50 +- .../settings/api-tokens/page.tsx} | 33 +- .../settings/billing/page.tsx} | 27 +- .../settings/exports/page.tsx} | 28 +- .../settings/imports/page.tsx} | 16 +- .../settings/integrations/page.tsx} | 25 +- .../[workspaceSlug]/settings}/layout.tsx | 7 +- .../settings/members/page.tsx} | 38 +- web/app/[workspaceSlug]/settings/page.tsx | 24 + .../[workspaceSlug]/settings}/sidebar.tsx | 23 +- .../settings/webhooks/[webhookId]/page.tsx} | 29 +- .../settings/webhooks/page.tsx} | 35 +- .../[workspaceSlug]}/sidebar.tsx | 6 +- .../workspace-views/[globalViewId]/page.tsx} | 27 +- .../[workspaceSlug]/workspace-views/page.tsx} | 23 +- web/app/accounts/forgot-password/layout.tsx | 9 + web/app/accounts/forgot-password/page.tsx | 190 ++++ web/app/accounts/reset-password/layout.tsx | 9 + web/app/accounts/reset-password/page.tsx | 235 +++++ web/app/accounts/set-password/layout.tsx | 9 + web/app/accounts/set-password/page.tsx | 228 +++++ web/app/create-workspace/layout.tsx | 9 + .../create-workspace/page.tsx} | 34 +- web/app/error.tsx | 88 ++ web/app/installations/[provider]/layout.tsx | 3 + .../installations/[provider]/page.tsx} | 23 +- web/app/invitations/layout.tsx | 9 + .../index.tsx => app/invitations/page.tsx} | 33 +- web/app/layout.tsx | 61 ++ web/{pages/404.tsx => app/not-found.tsx} | 23 +- web/app/onboarding/layout.tsx | 9 + .../index.tsx => app/onboarding/page.tsx} | 29 +- web/app/page.tsx | 77 ++ .../profile/activity/page.tsx} | 12 +- .../profile/appearance/page.tsx} | 23 +- .../profile/layout.tsx | 22 +- .../profile/notifications/page.tsx} | 26 +- .../index.tsx => app/profile/page.tsx} | 21 +- .../profile/security/page.tsx} | 24 +- .../profile/sidebar.tsx | 8 +- web/app/provider.tsx | 55 ++ web/app/sign-up/layout.tsx | 9 + web/app/sign-up/page.tsx | 70 ++ web/app/workspace-invitations/layout.tsx | 9 + web/app/workspace-invitations/page.tsx | 127 +++ .../account/auth-forms/auth-root.tsx | 11 +- .../account/deactivate-account-modal.tsx | 2 +- .../account/oauth/github-button.tsx | 6 +- .../account/oauth/google-button.tsx | 6 +- .../custom-analytics/custom-analytics.tsx | 5 +- .../custom-analytics/main-content.tsx | 5 +- .../custom-analytics/select/segment.tsx | 5 +- .../custom-analytics/select/x-axis.tsx | 5 +- .../sidebar/sidebar-header.tsx | 5 +- .../custom-analytics/sidebar/sidebar.tsx | 5 +- .../scope-and-demand/scope-and-demand.tsx | 5 +- .../api-token/delete-token-modal.tsx | 7 +- .../api-token/modal/create-token-modal.tsx | 5 +- web/components/archives/archive-tabs-list.tsx | 8 +- .../auth-screens/not-authorized-view.tsx | 6 +- .../auth-screens/project/join-project.tsx | 5 +- .../automation/select-month-modal.tsx | 5 +- .../actions/issue-actions/actions-list.tsx | 9 +- .../actions/issue-actions/change-assignee.tsx | 9 +- .../actions/issue-actions/change-priority.tsx | 10 +- .../actions/issue-actions/change-state.tsx | 9 +- .../actions/search-results.tsx | 4 +- .../command-palette/actions/theme-actions.tsx | 2 + .../actions/workspace-settings-actions.tsx | 7 +- .../command-palette/command-modal.tsx | 7 +- .../command-palette/command-palette.tsx | 11 +- .../command-palette/shortcuts-modal/modal.tsx | 2 + web/components/core/activity.tsx | 14 +- web/components/core/image-picker-popover.tsx | 7 +- web/components/core/list/list-item.tsx | 2 +- .../core/modals/bulk-delete-issues-modal.tsx | 7 +- .../core/modals/gpt-assistant-popover.tsx | 5 +- .../modals/workspace-image-upload-modal.tsx | 8 +- .../sidebar/sidebar-menu-hamburger-toggle.tsx | 14 +- .../upcoming-cycles-list-item.tsx | 5 +- .../cycles/archived-cycles/header.tsx | 5 +- .../cycles/archived-cycles/modal.tsx | 2 +- .../cycles/archived-cycles/root.tsx | 5 +- .../cycles/board/cycles-board-card.tsx | 20 +- web/components/cycles/cycle-peek-overview.tsx | 14 +- web/components/cycles/delete-modal.tsx | 6 +- web/components/cycles/gantt-chart/blocks.tsx | 2 +- .../cycles/gantt-chart/cycles-list-layout.tsx | 5 +- .../cycles/list/cycles-list-item.tsx | 21 +- web/components/cycles/quick-actions.tsx | 2 +- web/components/cycles/sidebar.tsx | 6 +- .../cycles/transfer-issues-modal.tsx | 5 +- web/components/cycles/transfer-issues.tsx | 5 +- .../dashboard/widgets/issues-by-priority.tsx | 2 +- .../widgets/issues-by-state-group.tsx | 2 +- .../create-update-estimate-modal.tsx | 291 ++++++ .../estimates/delete-estimate-modal.tsx | 78 ++ web/components/estimates/estimates-list.tsx | 120 +++ web/components/exporter/export-modal.tsx | 4 +- web/components/exporter/guide.tsx | 6 +- web/components/headers/index.ts | 24 - .../headers/profile-preferences.tsx | 15 - web/components/headers/profile-settings.tsx | 35 - .../inbox/content/inbox-issue-header.tsx | 2 +- .../content/inbox-issue-mobile-header.tsx | 2 +- .../inbox/content/issue-properties.tsx | 2 +- web/components/inbox/content/issue-root.tsx | 8 +- web/components/inbox/content/root.tsx | 2 +- .../modals/create-edit-modal/create-root.tsx | 7 +- .../modals/create-edit-modal/edit-root.tsx | 8 +- .../inbox/modals/select-duplicate.tsx | 5 +- .../inbox/sidebar/inbox-list-item.tsx | 6 +- web/components/inbox/sidebar/root.tsx | 2 +- .../integration/delete-import-modal.tsx | 5 +- .../integration/github/repo-details.tsx | 5 +- web/components/integration/github/root.tsx | 6 +- .../integration/github/select-repository.tsx | 5 +- .../integration/github/single-user-select.tsx | 5 +- web/components/integration/guide.tsx | 7 +- .../integration/jira/import-users.tsx | 5 +- .../integration/jira/jira-project-detail.tsx | 5 +- web/components/integration/jira/root.tsx | 4 +- .../integration/single-integration-card.tsx | 5 +- .../integration/slack/select-channel.tsx | 5 +- .../issues/archived-issues-header.tsx | 5 +- .../issues/bulk-operations/properties.tsx | 200 ++++ web/components/issues/issue-detail/root.tsx | 37 +- .../issues/issue-detail/sidebar.tsx | 2 +- .../calendar/base-calendar-root.tsx | 5 +- .../calendar/dropdowns/options-dropdown.tsx | 5 +- .../calendar/quick-add-issue-form.tsx | 10 +- .../calendar/roots/cycle-root.tsx | 5 +- .../calendar/roots/module-root.tsx | 5 +- .../calendar/roots/project-view-root.tsx | 5 +- .../empty-states/archived-issues.tsx | 5 +- .../empty-states/draft-issues.tsx | 5 +- .../empty-states/project-issues.tsx | 5 +- .../applied-filters/roots/archived-issue.tsx | 5 +- .../applied-filters/roots/cycle-root.tsx | 5 +- .../applied-filters/roots/draft-issue.tsx | 5 +- .../roots/global-view-root.tsx | 5 +- .../applied-filters/roots/module-root.tsx | 5 +- .../roots/profile-issues-root.tsx | 5 +- .../applied-filters/roots/project-root.tsx | 5 +- .../roots/project-view-root.tsx | 5 +- .../issue-layouts/gantt/base-gantt-root.tsx | 5 +- .../issues/issue-layouts/gantt/cycle-root.tsx | 5 +- .../issue-layouts/gantt/module-root.tsx | 5 +- .../issue-layouts/gantt/project-view-root.tsx | 5 +- .../gantt/quick-add-issue-form.tsx | 10 +- .../issue-layouts/kanban/base-kanban-root.tsx | 8 +- .../kanban/headers/group-by-card.tsx | 8 +- .../kanban/quick-add-issue-form.tsx | 10 +- .../issue-layouts/kanban/roots/cycle-root.tsx | 5 +- .../kanban/roots/module-root.tsx | 5 +- .../kanban/roots/project-view-root.tsx | 5 +- .../list/headers/group-by-card.tsx | 8 +- .../list/quick-add-issue-form.tsx | 10 +- .../issue-layouts/list/roots/cycle-root.tsx | 5 +- .../list/roots/draft-issue-root.tsx | 5 +- .../issue-layouts/list/roots/module-root.tsx | 5 +- .../issue-layouts/list/roots/project-root.tsx | 5 +- .../list/roots/project-view-root.tsx | 5 +- .../properties/all-properties.tsx | 43 +- .../quick-action-dropdowns/all-issue.tsx | 5 +- .../quick-action-dropdowns/archived-issue.tsx | 5 +- .../quick-action-dropdowns/cycle-issue.tsx | 5 +- .../quick-action-dropdowns/module-issue.tsx | 5 +- .../quick-action-dropdowns/project-issue.tsx | 8 +- .../roots/all-issue-layout-root.tsx | 12 +- .../roots/archived-issue-layout-root.tsx | 5 +- .../issue-layouts/roots/cycle-layout-root.tsx | 5 +- .../roots/draft-issue-layout-root.tsx | 5 +- .../roots/module-layout-root.tsx | 5 +- .../roots/project-layout-root.tsx | 5 +- .../roots/project-view-layout-root.tsx | 5 +- .../spreadsheet/base-spreadsheet-root.tsx | 5 +- .../spreadsheet/columns/cycle-column.tsx | 10 +- .../spreadsheet/columns/module-column.tsx | 10 +- .../spreadsheet/columns/sub-issue-column.tsx | 9 +- .../spreadsheet/issue-column.tsx | 6 +- .../issue-layouts/spreadsheet/issue-row.tsx | 5 +- .../spreadsheet/quick-add-issue-form.tsx | 8 +- .../spreadsheet/roots/cycle-root.tsx | 5 +- .../spreadsheet/roots/module-root.tsx | 5 +- .../spreadsheet/roots/project-view-root.tsx | 5 +- .../spreadsheet/spreadsheet-header.tsx | 5 +- .../issues/issue-modal/draft-issue-layout.tsx | 13 +- web/components/issues/issue-modal/form.tsx | 24 +- web/components/issues/issue-modal/modal.tsx | 14 +- .../issues/parent-issues-list-modal.tsx | 5 +- web/components/issues/peek-overview/root.tsx | 42 +- web/components/issues/select/label.tsx | 5 +- web/components/issues/sub-issues/root.tsx | 26 +- web/components/labels/create-label-modal.tsx | 5 +- .../labels/create-update-label-inline.tsx | 5 +- web/components/labels/delete-label-modal.tsx | 5 +- .../labels/project-setting-label-item.tsx | 5 +- .../labels/project-setting-label-list.tsx | 5 +- .../modules/archived-modules/header.tsx | 5 +- .../modules/archived-modules/modal.tsx | 2 +- .../modules/archived-modules/root.tsx | 5 +- .../modules/delete-module-modal.tsx | 4 +- web/components/modules/gantt-chart/blocks.tsx | 2 +- .../gantt-chart/modules-list-layout.tsx | 5 +- web/components/modules/module-card-item.tsx | 22 +- .../modules/module-list-item-action.tsx | 5 +- web/components/modules/module-list-item.tsx | 23 +- .../modules/module-peek-overview.tsx | 14 +- web/components/modules/module-view-header.tsx | 5 +- web/components/modules/modules-list-view.tsx | 7 +- web/components/modules/quick-actions.tsx | 2 +- web/components/modules/sidebar.tsx | 6 +- .../notifications/notification-card.tsx | 5 +- .../select-snooze-till-modal.tsx | 5 +- .../onboarding/switch-account-modal.tsx | 2 +- web/components/pages/editor/editor-body.tsx | 5 +- .../pages/modals/create-page-modal.tsx | 2 +- .../profile/activity/download-button.tsx | 5 +- .../activity/workspace-activity-list.tsx | 5 +- web/components/profile/index.ts | 1 - .../email-notification-form.tsx | 8 - .../{preferences => notification}/index.ts | 0 web/components/profile/overview/activity.tsx | 5 +- web/components/profile/overview/stats.tsx | 5 +- .../preferences/preferences-mobile-header.tsx | 37 - .../profile/profile-issues-filter.tsx | 5 +- web/components/profile/profile-issues.tsx | 5 +- web/components/profile/sidebar.tsx | 5 +- web/components/project/card.tsx | 4 +- .../project/confirm-project-member-remove.tsx | 5 +- .../project/delete-project-modal.tsx | 4 +- web/components/project/integration-card.tsx | 5 +- web/components/project/join-project-modal.tsx | 2 +- .../project/leave-project-modal.tsx | 4 +- web/components/project/member-list-item.tsx | 4 +- .../project-settings-member-defaults.tsx | 5 +- .../project/publish-project/modal.tsx | 5 +- .../project/send-project-invitation-modal.tsx | 5 +- .../archive-project/archive-restore-modal.tsx | 2 +- web/components/project/sidebar-list-item.tsx | 11 +- web/components/project/sidebar-list.tsx | 7 +- web/components/states/create-state-modal.tsx | 5 +- .../states/create-update-state-inline.tsx | 5 +- web/components/states/delete-state-modal.tsx | 5 +- .../project-setting-state-list-item.tsx | 5 +- .../states/project-setting-state-list.tsx | 5 +- web/components/ui/index.ts | 1 - web/components/ui/multi-level-dropdown.tsx | 164 ---- web/components/views/delete-view-modal.tsx | 5 +- .../views/view-list-item-action.tsx | 5 +- web/components/views/view-list-item.tsx | 5 +- .../web-hooks/create-webhook-modal.tsx | 5 +- .../web-hooks/delete-webhook-modal.tsx | 4 +- web/components/web-hooks/form/secret-key.tsx | 5 +- .../web-hooks/webhooks-list-item.tsx | 5 +- .../workspace/create-workspace-form.tsx | 2 +- .../workspace/delete-workspace-modal.tsx | 2 +- .../settings/invitations-list-item.tsx | 5 +- .../workspace/settings/members-list-item.tsx | 4 +- .../workspace/settings/members-list.tsx | 5 +- .../mobile-workspace-settings-tabs.tsx | 8 +- web/components/workspace/sidebar-dropdown.tsx | 7 +- web/components/workspace/sidebar-menu.tsx | 11 +- .../views/default-view-list-item.tsx | 5 +- .../workspace/views/delete-view-modal.tsx | 5 +- web/components/workspace/views/header.tsx | 11 +- web/components/workspace/views/modal.tsx | 4 +- .../workspace/views/view-list-item.tsx | 5 +- web/components/workspace/views/views-list.tsx | 5 +- web/constants/profile.ts | 39 +- web/helpers/router.helper.ts | 9 + web/helpers/user.helper.ts | 3 +- web/hooks/store/use-mention.ts | 2 + web/hooks/use-group-dragndrop.ts | 5 +- web/hooks/use-integration-popup.tsx | 5 +- web/hooks/use-multiple-select.ts | 18 +- web/hooks/use-platform-os.tsx | 2 + web/hooks/use-reload-confirmation.tsx | 58 +- web/hooks/use-url-hash.tsx | 14 + web/hooks/use-user-notifications.tsx | 5 +- web/layouts/app-layout/index.ts | 2 - web/layouts/app-layout/layout.tsx | 48 - web/layouts/auth-layout/project-wrapper.tsx | 5 +- web/layouts/auth-layout/workspace-wrapper.tsx | 7 +- web/layouts/settings-layout/index.ts | 3 - web/layouts/settings-layout/profile/index.ts | 2 - .../profile/preferences/index.ts | 2 - .../profile/preferences/layout.tsx | 39 - .../profile/preferences/sidebar.tsx | 43 - web/layouts/settings-layout/project/index.ts | 2 - .../settings-layout/workspace/index.ts | 2 - web/layouts/user-profile-layout/index.ts | 1 - web/lib/app-provider.tsx | 8 +- web/lib/posthog-provider.tsx | 62 +- web/lib/store-context.tsx | 2 + web/lib/types.d.ts | 4 - web/lib/wrappers/authentication-wrapper.tsx | 7 +- web/lib/wrappers/crisp-wrapper.tsx | 11 +- web/lib/wrappers/store-wrapper.tsx | 12 +- .../profile/[userId]/assigned.tsx | 28 - .../profile/[userId]/created.tsx | 30 - .../profile/[userId]/subscribed.tsx | 30 - web/pages/[workspaceSlug]/settings/index.tsx | 37 - web/pages/_app.tsx | 42 - web/pages/_document.tsx | 60 -- web/pages/_error.tsx | 81 -- web/pages/accounts/forgot-password.tsx | 202 ---- web/pages/accounts/reset-password.tsx | 242 ----- web/pages/accounts/set-password.tsx | 236 ----- web/pages/index.tsx | 79 -- web/pages/sign-up.tsx | 82 -- web/pages/workspace-invitations.tsx | 131 --- web/services/api.service.ts | 18 - web/tailwind.config.js | 1 + yarn.lock | 891 +++++++++++++++++- 417 files changed, 5196 insertions(+), 3918 deletions(-) create mode 100644 space/types/project.d.ts rename web/{components/headers/workspace-active-cycles.tsx => app/[workspaceSlug]/@header/active-cycles/header.tsx} (89%) create mode 100644 web/app/[workspaceSlug]/@header/active-cycles/page.tsx rename web/{components/headers/workspace-analytics.tsx => app/[workspaceSlug]/@header/analytics/header.tsx} (89%) create mode 100644 web/app/[workspaceSlug]/@header/analytics/page.tsx rename web/{components/headers/workspace-dashboard.tsx => app/[workspaceSlug]/@header/header.tsx} (96%) create mode 100644 web/app/[workspaceSlug]/@header/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/profile/[userId]/activity/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/profile/[userId]/assigned/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/profile/[userId]/created/page.tsx rename web/{components/headers/user-profile.tsx => app/[workspaceSlug]/@header/profile/[userId]/header.tsx} (93%) rename web/{components/profile/profile-issues-mobile-header.tsx => app/[workspaceSlug]/@header/profile/[userId]/mobile-header.tsx} (98%) create mode 100644 web/app/[workspaceSlug]/@header/profile/[userId]/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/profile/[userId]/subscribed/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/archives/[...default-header]/page.tsx rename web/{components/headers/project-archives.tsx => app/[workspaceSlug]/@header/projects/[projectId]/archives/header.tsx} (92%) rename web/{components/headers/project-archived-issue-details.tsx => app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/header.tsx} (92%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/archives/page.tsx rename web/{components/headers/cycle-issues.tsx => app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/header.tsx} (96%) rename web/{components/cycles/cycle-mobile-header.tsx => app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/mobile-header.tsx} (97%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/page.tsx rename web/{components/headers/cycles.tsx => app/[workspaceSlug]/@header/projects/[projectId]/cycles/header.tsx} (93%) rename web/{components/cycles/cycles-list-mobile-header.tsx => app/[workspaceSlug]/@header/projects/[projectId]/cycles/mobile-header.tsx} (100%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/page.tsx rename web/{components/headers/project-draft-issues.tsx => app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/header.tsx} (96%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/page.tsx rename web/{components/headers/project-inbox.tsx => app/[workspaceSlug]/@header/projects/[projectId]/inbox/header.tsx} (93%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/page.tsx rename web/{components/headers/project-issue-details.tsx => app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/header.tsx} (92%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/page.tsx rename web/{components/headers/project-issues.tsx => app/[workspaceSlug]/@header/projects/[projectId]/issues/header.tsx} (97%) rename web/{components/issues/issues-mobile-header.tsx => app/[workspaceSlug]/@header/projects/[projectId]/issues/mobile-header.tsx} (97%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/issues/page.tsx rename web/{components/headers/module-issues.tsx => app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/header.tsx} (96%) rename web/{components/modules/module-mobile-header.tsx => app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/mobile-header.tsx} (97%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/page.tsx rename web/{components/headers/modules-list.tsx => app/[workspaceSlug]/@header/projects/[projectId]/modules/header.tsx} (93%) rename web/{components/modules/moduels-list-mobile-header.tsx => app/[workspaceSlug]/@header/projects/[projectId]/modules/mobile-header.tsx} (100%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/modules/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/header.tsx create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/page.tsx rename web/{components/headers/pages.tsx => app/[workspaceSlug]/@header/projects/[projectId]/pages/header.tsx} (90%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/pages/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/settings/[...default-header]/page.tsx rename web/{components/headers/project-settings.tsx => app/[workspaceSlug]/@header/projects/[projectId]/settings/header.tsx} (93%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/settings/page.tsx rename web/{components/headers/project-view-issues.tsx => app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/header.tsx} (97%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/page.tsx rename web/{components/headers/project-views.tsx => app/[workspaceSlug]/@header/projects/[projectId]/views/header.tsx} (93%) create mode 100644 web/app/[workspaceSlug]/@header/projects/[projectId]/views/page.tsx rename web/{components/headers/projects.tsx => app/[workspaceSlug]/@header/projects/header.tsx} (98%) rename web/{components/project/projects-mobile-header.tsx => app/[workspaceSlug]/@header/projects/mobile-header.tsx} (100%) create mode 100644 web/app/[workspaceSlug]/@header/projects/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/settings/[...default-header]/page.tsx rename web/{components/headers/workspace-settings.tsx => app/[workspaceSlug]/@header/settings/header.tsx} (91%) create mode 100644 web/app/[workspaceSlug]/@header/settings/page.tsx create mode 100644 web/app/[workspaceSlug]/@header/workspace-views/[...default-header]/page.tsx rename web/{components/headers/global-issues.tsx => app/[workspaceSlug]/@header/workspace-views/header.tsx} (96%) create mode 100644 web/app/[workspaceSlug]/@header/workspace-views/page.tsx rename web/{pages/[workspaceSlug]/active-cycles.tsx => app/[workspaceSlug]/active-cycles/page.tsx} (50%) rename web/{pages/[workspaceSlug]/analytics.tsx => app/[workspaceSlug]/analytics/page.tsx} (76%) create mode 100644 web/app/[workspaceSlug]/app-header-wrapper.tsx create mode 100644 web/app/[workspaceSlug]/layout.tsx rename web/{pages/[workspaceSlug]/index.tsx => app/[workspaceSlug]/page.tsx} (53%) rename web/{pages/[workspaceSlug]/profile/[userId]/activity.tsx => app/[workspaceSlug]/profile/[userId]/activity/page.tsx} (75%) create mode 100644 web/app/[workspaceSlug]/profile/[userId]/assigned/page.tsx create mode 100644 web/app/[workspaceSlug]/profile/[userId]/created/page.tsx rename web/{layouts/user-profile-layout => app/[workspaceSlug]/profile/[userId]}/layout.tsx (58%) rename web/{components/profile => app/[workspaceSlug]/profile/[userId]}/navbar.tsx (85%) rename web/{pages/[workspaceSlug]/profile/[userId]/index.tsx => app/[workspaceSlug]/profile/[userId]/page.tsx} (70%) create mode 100644 web/app/[workspaceSlug]/profile/[userId]/subscribed/page.tsx rename web/{pages/[workspaceSlug]/projects/[projectId]/archives/cycles/index.tsx => app/[workspaceSlug]/projects/[projectId]/archives/cycles/page.tsx} (52%) rename web/{pages/[workspaceSlug]/projects/[projectId]/archives/issues/[archivedIssueId].tsx => app/[workspaceSlug]/projects/[projectId]/archives/issues/[archivedIssueId]/page.tsx} (88%) rename web/{pages/[workspaceSlug]/projects/[projectId]/archives/issues/index.tsx => app/[workspaceSlug]/projects/[projectId]/archives/issues/page.tsx} (52%) rename web/{pages/[workspaceSlug]/projects/[projectId]/archives/modules/index.tsx => app/[workspaceSlug]/projects/[projectId]/archives/modules/page.tsx} (56%) rename web/{pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx => app/[workspaceSlug]/projects/[projectId]/cycles/[cycleId]/page.tsx} (80%) rename web/{pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx => app/[workspaceSlug]/projects/[projectId]/cycles/page.tsx} (81%) rename web/{pages/[workspaceSlug]/projects/[projectId]/draft-issues/index.tsx => app/[workspaceSlug]/projects/[projectId]/draft-issues/page.tsx} (67%) create mode 100644 web/app/[workspaceSlug]/projects/[projectId]/inbox/page.tsx rename web/{pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx => app/[workspaceSlug]/projects/[projectId]/issues/[issueId]/page.tsx} (85%) rename web/{pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx => app/[workspaceSlug]/projects/[projectId]/issues/page.tsx} (52%) create mode 100644 web/app/[workspaceSlug]/projects/[projectId]/layout.tsx rename web/{pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx => app/[workspaceSlug]/projects/[projectId]/modules/[moduleId]/page.tsx} (80%) rename web/{pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx => app/[workspaceSlug]/projects/[projectId]/modules/page.tsx} (78%) rename web/{pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx => app/[workspaceSlug]/projects/[projectId]/pages/[pageId]/page.tsx} (88%) rename web/{pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx => app/[workspaceSlug]/projects/[projectId]/pages/page.tsx} (70%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx => app/[workspaceSlug]/projects/[projectId]/settings/automations/page.tsx} (68%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx => app/[workspaceSlug]/projects/[projectId]/settings/estimates/page.tsx} (58%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx => app/[workspaceSlug]/projects/[projectId]/settings/features/page.tsx} (66%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx => app/[workspaceSlug]/projects/[projectId]/settings/labels/page.tsx} (65%) rename web/{layouts/settings-layout/project => app/[workspaceSlug]/projects/[projectId]/settings}/layout.tsx (83%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx => app/[workspaceSlug]/projects/[projectId]/settings/members/page.tsx} (54%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx => app/[workspaceSlug]/projects/[projectId]/settings/page.tsx} (79%) rename web/{layouts/settings-layout/project => app/[workspaceSlug]/projects/[projectId]/settings}/sidebar.tsx (84%) rename web/{pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx => app/[workspaceSlug]/projects/[projectId]/settings/states/page.tsx} (57%) rename web/{pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx => app/[workspaceSlug]/projects/[projectId]/views/[viewId]/page.tsx} (71%) rename web/{pages/[workspaceSlug]/projects/[projectId]/views/index.tsx => app/[workspaceSlug]/projects/[projectId]/views/page.tsx} (65%) rename web/{pages/[workspaceSlug]/projects/index.tsx => app/[workspaceSlug]/projects/page.tsx} (64%) rename web/{pages/[workspaceSlug]/settings/api-tokens.tsx => app/[workspaceSlug]/settings/api-tokens/page.tsx} (82%) rename web/{pages/[workspaceSlug]/settings/billing.tsx => app/[workspaceSlug]/settings/billing/page.tsx} (76%) rename web/{pages/[workspaceSlug]/settings/exports.tsx => app/[workspaceSlug]/settings/exports/page.tsx} (70%) rename web/{pages/[workspaceSlug]/settings/imports.tsx => app/[workspaceSlug]/settings/imports/page.tsx} (70%) rename web/{pages/[workspaceSlug]/settings/integrations.tsx => app/[workspaceSlug]/settings/integrations/page.tsx} (72%) rename web/{layouts/settings-layout/workspace => app/[workspaceSlug]/settings}/layout.tsx (85%) rename web/{pages/[workspaceSlug]/settings/members.tsx => app/[workspaceSlug]/settings/members/page.tsx} (84%) create mode 100644 web/app/[workspaceSlug]/settings/page.tsx rename web/{layouts/settings-layout/workspace => app/[workspaceSlug]/settings}/sidebar.tsx (73%) rename web/{pages/[workspaceSlug]/settings/webhooks/[webhookId].tsx => app/[workspaceSlug]/settings/webhooks/[webhookId]/page.tsx} (81%) rename web/{pages/[workspaceSlug]/settings/webhooks/index.tsx => app/[workspaceSlug]/settings/webhooks/page.tsx} (82%) rename web/{layouts/app-layout => app/[workspaceSlug]}/sidebar.tsx (92%) rename web/{pages/[workspaceSlug]/workspace-views/[globalViewId].tsx => app/[workspaceSlug]/workspace-views/[globalViewId]/page.tsx} (71%) rename web/{pages/[workspaceSlug]/workspace-views/index.tsx => app/[workspaceSlug]/workspace-views/page.tsx} (79%) create mode 100644 web/app/accounts/forgot-password/layout.tsx create mode 100644 web/app/accounts/forgot-password/page.tsx create mode 100644 web/app/accounts/reset-password/layout.tsx create mode 100644 web/app/accounts/reset-password/page.tsx create mode 100644 web/app/accounts/set-password/layout.tsx create mode 100644 web/app/accounts/set-password/page.tsx create mode 100644 web/app/create-workspace/layout.tsx rename web/{pages/create-workspace.tsx => app/create-workspace/page.tsx} (84%) create mode 100644 web/app/error.tsx create mode 100644 web/app/installations/[provider]/layout.tsx rename web/{pages/installations/[provider]/index.tsx => app/installations/[provider]/page.tsx} (82%) create mode 100644 web/app/invitations/layout.tsx rename web/{pages/invitations/index.tsx => app/invitations/page.tsx} (93%) create mode 100644 web/app/layout.tsx rename web/{pages/404.tsx => app/not-found.tsx} (78%) create mode 100644 web/app/onboarding/layout.tsx rename web/{pages/onboarding/index.tsx => app/onboarding/page.tsx} (90%) create mode 100644 web/app/page.tsx rename web/{pages/profile/activity.tsx => app/profile/activity/page.tsx} (88%) rename web/{pages/profile/preferences/theme.tsx => app/profile/appearance/page.tsx} (77%) rename web/{layouts/settings-layout => app}/profile/layout.tsx (67%) rename web/{pages/profile/preferences/email.tsx => app/profile/notifications/page.tsx} (51%) rename web/{pages/profile/index.tsx => app/profile/page.tsx} (97%) rename web/{pages/profile/change-password.tsx => app/profile/security/page.tsx} (93%) rename web/{layouts/settings-layout => app}/profile/sidebar.tsx (98%) create mode 100644 web/app/provider.tsx create mode 100644 web/app/sign-up/layout.tsx create mode 100644 web/app/sign-up/page.tsx create mode 100644 web/app/workspace-invitations/layout.tsx create mode 100644 web/app/workspace-invitations/page.tsx create mode 100644 web/components/estimates/create-update-estimate-modal.tsx create mode 100644 web/components/estimates/delete-estimate-modal.tsx create mode 100644 web/components/estimates/estimates-list.tsx delete mode 100644 web/components/headers/index.ts delete mode 100644 web/components/headers/profile-preferences.tsx delete mode 100644 web/components/headers/profile-settings.tsx create mode 100644 web/components/issues/bulk-operations/properties.tsx rename web/components/profile/{preferences => notification}/email-notification-form.tsx (93%) rename web/components/profile/{preferences => notification}/index.ts (100%) delete mode 100644 web/components/profile/preferences/preferences-mobile-header.tsx delete mode 100644 web/components/ui/multi-level-dropdown.tsx create mode 100644 web/helpers/router.helper.ts create mode 100644 web/hooks/use-url-hash.tsx delete mode 100644 web/layouts/app-layout/index.ts delete mode 100644 web/layouts/app-layout/layout.tsx delete mode 100644 web/layouts/settings-layout/index.ts delete mode 100644 web/layouts/settings-layout/profile/index.ts delete mode 100644 web/layouts/settings-layout/profile/preferences/index.ts delete mode 100644 web/layouts/settings-layout/profile/preferences/layout.tsx delete mode 100644 web/layouts/settings-layout/profile/preferences/sidebar.tsx delete mode 100644 web/layouts/settings-layout/project/index.ts delete mode 100644 web/layouts/settings-layout/workspace/index.ts delete mode 100644 web/layouts/user-profile-layout/index.ts delete mode 100644 web/lib/types.d.ts delete mode 100644 web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx delete mode 100644 web/pages/[workspaceSlug]/profile/[userId]/created.tsx delete mode 100644 web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx delete mode 100644 web/pages/[workspaceSlug]/settings/index.tsx delete mode 100644 web/pages/_app.tsx delete mode 100644 web/pages/_document.tsx delete mode 100644 web/pages/_error.tsx delete mode 100644 web/pages/accounts/forgot-password.tsx delete mode 100644 web/pages/accounts/reset-password.tsx delete mode 100644 web/pages/accounts/set-password.tsx delete mode 100644 web/pages/index.tsx delete mode 100644 web/pages/sign-up.tsx delete mode 100644 web/pages/workspace-invitations.tsx diff --git a/packages/constants/src/auth.ts b/packages/constants/src/auth.ts index c12b63d63..065b188cf 100644 --- a/packages/constants/src/auth.ts +++ b/packages/constants/src/auth.ts @@ -1,5 +1,6 @@ import { ReactNode } from "react"; -import Link from "next/link"; +// import Link from "next/link"; +// import { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; export enum EPageTypes { PUBLIC = "PUBLIC", @@ -41,7 +42,7 @@ export enum EAuthErrorCodes { // Password strength INVALID_PASSWORD = "5020", // Sign Up - USER_ACCOUNT_DEACTIVATED = "5019", + // USER_ACCOUNT_DEACTIVATED = "5019", USER_ALREADY_EXIST = "5030", AUTHENTICATION_FAILED_SIGN_UP = "5035", REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5040", @@ -91,281 +92,281 @@ export type TAuthErrorInfo = { message: ReactNode; }; -const errorCodeMessages: { - [key in EAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode }; -} = { - // global - [EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: { - title: `Instance not configured`, - message: () => `Instance not configured. Please contact your administrator.`, - }, - [EAuthErrorCodes.SIGNUP_DISABLED]: { - title: `Sign up disabled`, - message: () => `Sign up disabled. Please contact your administrator.`, - }, - [EAuthErrorCodes.INVALID_PASSWORD]: { - title: `Invalid password`, - message: () => `Invalid password. Please try again.`, - }, - [EAuthErrorCodes.SMTP_NOT_CONFIGURED]: { - title: `SMTP not configured`, - message: () => `SMTP not configured. Please contact your administrator.`, - }, +// const errorCodeMessages: { +// [key in EAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode }; +// } = { +// // global +// [EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: { +// title: `Instance not configured`, +// message: () => `Instance not configured. Please contact your administrator.`, +// }, +// [EAuthErrorCodes.SIGNUP_DISABLED]: { +// title: `Sign up disabled`, +// message: () => `Sign up disabled. Please contact your administrator.`, +// }, +// [EAuthErrorCodes.INVALID_PASSWORD]: { +// title: `Invalid password`, +// message: () => `Invalid password. Please try again.`, +// }, +// [EAuthErrorCodes.SMTP_NOT_CONFIGURED]: { +// title: `SMTP not configured`, +// message: () => `SMTP not configured. Please contact your administrator.`, +// }, // email check in both sign up and sign in - [EAuthErrorCodes.INVALID_EMAIL]: { - title: `Invalid email`, - message: () => `Invalid email. Please try again.`, - }, - [EAuthenticationErrorCodes.EMAIL_REQUIRED]: { - title: `Email required`, - message: () => `Email required. Please try again.`, - }, +// [EAuthErrorCodes.INVALID_EMAIL]: { +// title: `Invalid email`, +// message: () => `Invalid email. Please try again.`, +// }, +// [EAuthenticationErrorCodes.EMAIL_REQUIRED]: { +// title: `Email required`, +// message: () => `Email required. Please try again.`, +// }, - // sign up - [EAuthenticationErrorCodes.USER_ALREADY_EXIST]: { - title: `User already exists`, - message: (email = undefined) => ( -
- Your account is already registered.  - - Sign In - -  now. -
- ), - }, - [EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: { - title: `Email and password required`, - message: () => `Email and password required. Please try again.`, - }, - [EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: { - title: `Authentication failed`, - message: () => `Authentication failed. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP]: { - title: `Invalid email`, - message: () => `Invalid email. Please try again.`, - }, - [EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: { - title: `Email and code required`, - message: () => `Email and code required. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: { - title: `Invalid email`, - message: () => `Invalid email. Please try again.`, - }, +// // sign up +// [EAuthenticationErrorCodes.USER_ALREADY_EXIST]: { +// title: `User already exists`, +// message: (email = undefined) => ( +//
+// Your account is already registered.  +// +// Sign In +// +//  now. +//
+// ), +// }, +// [EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: { +// title: `Email and password required`, +// message: () => `Email and password required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: { +// title: `Authentication failed`, +// message: () => `Authentication failed. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP]: { +// title: `Invalid email`, +// message: () => `Invalid email. Please try again.`, +// }, +// [EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: { +// title: `Email and code required`, +// message: () => `Email and code required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: { +// title: `Invalid email`, +// message: () => `Invalid email. Please try again.`, +// }, - // sign in - [EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: { - title: `User account deactivated`, - message: () =>
Your account is deactivated. Contact support@plane.so.
, - }, - [EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: { - title: `User does not exist`, - message: (email = undefined) => ( -
- No account found.  - - Create one - -  to get started. -
- ), - }, - [EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: { - title: `Email and password required`, - message: () => `Email and password required. Please try again.`, - }, - [EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: { - title: `Authentication failed`, - message: () => `Authentication failed. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN]: { - title: `Invalid email`, - message: () => `Invalid email. Please try again.`, - }, - [EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: { - title: `Email and code required`, - message: () => `Email and code required. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: { - title: `Invalid email`, - message: () => `Invalid email. Please try again.`, - }, +// // sign in +// [EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: { +// title: `User account deactivated`, +// message: () =>
Your account is deactivated. Contact support@plane.so.
, +// }, +// [EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: { +// title: `User does not exist`, +// message: (email = undefined) => ( +//
+// No account found.  +// +// Create one +// +//  to get started. +//
+// ), +// }, +// [EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: { +// title: `Email and password required`, +// message: () => `Email and password required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: { +// title: `Authentication failed`, +// message: () => `Authentication failed. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN]: { +// title: `Invalid email`, +// message: () => `Invalid email. Please try again.`, +// }, +// [EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: { +// title: `Email and code required`, +// message: () => `Email and code required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: { +// title: `Invalid email`, +// message: () => `Invalid email. Please try again.`, +// }, - // Both Sign in and Sign up - [EAuthenticationErrorCodes.INVALID_MAGIC_CODE]: { - title: `Authentication failed`, - message: () => `Invalid magic code. Please try again.`, - }, - [EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE]: { - title: `Expired magic code`, - message: () => `Expired magic code. Please try again.`, - }, - [EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED]: { - title: `Expired magic code`, - message: () => `Expired magic code. Please try again.`, - }, +// // Both Sign in and Sign up +// [EAuthenticationErrorCodes.INVALID_MAGIC_CODE]: { +// title: `Authentication failed`, +// message: () => `Invalid magic code. Please try again.`, +// }, +// [EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE]: { +// title: `Expired magic code`, +// message: () => `Expired magic code. Please try again.`, +// }, +// [EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED]: { +// title: `Expired magic code`, +// message: () => `Expired magic code. Please try again.`, +// }, - // Oauth - [EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: { - title: `Google not configured`, - message: () => `Google not configured. Please contact your administrator.`, - }, - [EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED]: { - title: `GitHub not configured`, - message: () => `GitHub not configured. Please contact your administrator.`, - }, - [EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: { - title: `Google OAuth provider error`, - message: () => `Google OAuth provider error. Please try again.`, - }, - [EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: { - title: `GitHub OAuth provider error`, - message: () => `GitHub OAuth provider error. Please try again.`, - }, +// // Oauth +// [EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: { +// title: `Google not configured`, +// message: () => `Google not configured. Please contact your administrator.`, +// }, +// [EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED]: { +// title: `GitHub not configured`, +// message: () => `GitHub not configured. Please contact your administrator.`, +// }, +// [EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: { +// title: `Google OAuth provider error`, +// message: () => `Google OAuth provider error. Please try again.`, +// }, +// [EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: { +// title: `GitHub OAuth provider error`, +// message: () => `GitHub OAuth provider error. Please try again.`, +// }, - // Reset Password - [EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN]: { - title: `Invalid password token`, - message: () => `Invalid password token. Please try again.`, - }, - [EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN]: { - title: `Expired password token`, - message: () => `Expired password token. Please try again.`, - }, +// // Reset Password +// [EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN]: { +// title: `Invalid password token`, +// message: () => `Invalid password token. Please try again.`, +// }, +// [EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN]: { +// title: `Expired password token`, +// message: () => `Expired password token. Please try again.`, +// }, - // Change password +// // Change password - [EAuthenticationErrorCodes.MISSING_PASSWORD]: { - title: `Password required`, - message: () => `Password required. Please try again.`, - }, - [EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: { - title: `Incorrect old password`, - message: () => `Incorrect old password. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_NEW_PASSWORD]: { - title: `Invalid new password`, - message: () => `Invalid new password. Please try again.`, - }, +// [EAuthenticationErrorCodes.MISSING_PASSWORD]: { +// title: `Password required`, +// message: () => `Password required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: { +// title: `Incorrect old password`, +// message: () => `Incorrect old password. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_NEW_PASSWORD]: { +// title: `Invalid new password`, +// message: () => `Invalid new password. Please try again.`, +// }, - // set password - [EAuthenticationErrorCodes.PASSWORD_ALREADY_SET]: { - title: `Password already set`, - message: () => `Password already set. Please try again.`, - }, +// // set password +// [EAuthenticationErrorCodes.PASSWORD_ALREADY_SET]: { +// title: `Password already set`, +// message: () => `Password already set. Please try again.`, +// }, - // admin - [EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: { - title: `Admin already exists`, - message: () => `Admin already exists. Please try again.`, - }, - [EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: { - title: `Email, password and first name required`, - message: () => `Email, password and first name required. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: { - title: `Invalid admin email`, - message: () => `Invalid admin email. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: { - title: `Invalid admin password`, - message: () => `Invalid admin password. Please try again.`, - }, - [EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: { - title: `Email and password required`, - message: () => `Email and password required. Please try again.`, - }, - [EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: { - title: `Authentication failed`, - message: () => `Authentication failed. Please try again.`, - }, - [EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: { - title: `Admin user already exists`, - message: () => ( -
- Admin user already exists.  - - Sign In - -  now. -
- ), - }, - [EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: { - title: `Admin user does not exist`, - message: () => ( -
- Admin user does not exist.  - - Sign In - -  now. -
- ), - }, -}; +// // admin +// [EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: { +// title: `Admin already exists`, +// message: () => `Admin already exists. Please try again.`, +// }, +// [EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: { +// title: `Email, password and first name required`, +// message: () => `Email, password and first name required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: { +// title: `Invalid admin email`, +// message: () => `Invalid admin email. Please try again.`, +// }, +// [EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: { +// title: `Invalid admin password`, +// message: () => `Invalid admin password. Please try again.`, +// }, +// [EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: { +// title: `Email and password required`, +// message: () => `Email and password required. Please try again.`, +// }, +// [EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: { +// title: `Authentication failed`, +// message: () => `Authentication failed. Please try again.`, +// }, +// [EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: { +// title: `Admin user already exists`, +// message: () => ( +//
+// Admin user already exists.  +// +// Sign In +// +//  now. +//
+// ), +// }, +// [EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: { +// title: `Admin user does not exist`, +// message: () => ( +//
+// Admin user does not exist.  +// +// Sign In +// +//  now. +//
+// ), +// }, +// }; -export const authErrorHandler = ( - errorCode: EAuthenticationErrorCodes, - email?: string | undefined -): TAuthErrorInfo | undefined => { - const bannerAlertErrorCodes = [ - EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED, - EAuthenticationErrorCodes.INVALID_EMAIL, - EAuthenticationErrorCodes.EMAIL_REQUIRED, - EAuthenticationErrorCodes.SIGNUP_DISABLED, - EAuthenticationErrorCodes.INVALID_PASSWORD, - EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED, - EAuthenticationErrorCodes.USER_ALREADY_EXIST, - EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP, - EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP, - EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP, - EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP, - EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED, - EAuthenticationErrorCodes.USER_DOES_NOT_EXIST, - EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN, - EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN, - EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN, - EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN, - EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED, - EAuthenticationErrorCodes.INVALID_MAGIC_CODE, - EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE, - EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED, - EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED, - EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED, - EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR, - EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR, - EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN, - EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN, - EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD, - EAuthenticationErrorCodes.INVALID_NEW_PASSWORD, - EAuthenticationErrorCodes.PASSWORD_ALREADY_SET, - EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST, - EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME, - EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL, - EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD, - EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD, - EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED, - EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST, - EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST, - ]; +// export const authErrorHandler = ( +// errorCode: EAuthenticationErrorCodes, +// email?: string | undefined +// ): TAuthErrorInfo | undefined => { +// const bannerAlertErrorCodes = [ +// EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED, +// EAuthenticationErrorCodes.INVALID_EMAIL, +// EAuthenticationErrorCodes.EMAIL_REQUIRED, +// EAuthenticationErrorCodes.SIGNUP_DISABLED, +// EAuthenticationErrorCodes.INVALID_PASSWORD, +// EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED, +// EAuthenticationErrorCodes.USER_ALREADY_EXIST, +// EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP, +// EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP, +// EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP, +// EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP, +// EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED, +// EAuthenticationErrorCodes.USER_DOES_NOT_EXIST, +// EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN, +// EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN, +// EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN, +// EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN, +// EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED, +// // EAuthenticationErrorCodes.INVALID_MAGIC_CODE, +// // EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE, +// // EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED, +// EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED, +// EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED, +// EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR, +// EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR, +// EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN, +// EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN, +// EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD, +// EAuthenticationErrorCodes.INVALID_NEW_PASSWORD, +// EAuthenticationErrorCodes.PASSWORD_ALREADY_SET, +// EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST, +// EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME, +// EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL, +// EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD, +// EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD, +// EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED, +// EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST, +// EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST, +// ]; - if (bannerAlertErrorCodes.includes(errorCode)) - return { - type: EErrorAlertType.BANNER_ALERT, - code: errorCode, - title: errorCodeMessages[errorCode]?.title || "Error", - message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.", - }; +// if (bannerAlertErrorCodes.includes(errorCode)) +// return { +// type: EErrorAlertType.BANNER_ALERT, +// code: errorCode, +// title: errorCodeMessages[errorCode]?.title || "Error", +// message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.", +// }; - return undefined; -}; +// return undefined; +// }; diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 42e176043..9fca24b0a 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -8,6 +8,7 @@ module.exports = { content: { relative: true, files: [ + "./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.tsx", "./constants/**/*.{js,ts,jsx,tsx}", "./layouts/**/*.tsx", diff --git a/packages/ui/src/sortable/sortable.stories.tsx b/packages/ui/src/sortable/sortable.stories.tsx index 2d469b767..6d40ddc2e 100644 --- a/packages/ui/src/sortable/sortable.stories.tsx +++ b/packages/ui/src/sortable/sortable.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import React from "react"; +import { Draggable } from "./draggable"; import { Sortable } from "./sortable"; const meta: Meta = { @@ -12,7 +13,7 @@ type Story = StoryObj; const data = [ { id: "1", name: "John Doe" }, - { id: "2", name: "Satish" }, + { id: "2", name: "Jane Doe 2" }, { id: "3", name: "Alice" }, { id: "4", name: "Bob" }, { id: "5", name: "Charlie" }, diff --git a/space/types/project.d.ts b/space/types/project.d.ts new file mode 100644 index 000000000..90c89ed80 --- /dev/null +++ b/space/types/project.d.ts @@ -0,0 +1,42 @@ +import { TLogoProps } from "@plane/types"; + +export type TWorkspaceDetails = { + name: string; + slug: string; + id: string; +}; + +export type TViewDetails = { + list: boolean; + gantt: boolean; + kanban: boolean; + calendar: boolean; + spreadsheet: boolean; +}; + +export type TProjectDetails = { + id: string; + identifier: string; + name: string; + cover_image: string | undefined; + logo_props: TLogoProps; + description: string; +}; + +export type TProjectSettings = { + id: string; + anchor: string; + comments: boolean; + reactions: boolean; + votes: boolean; + inbox: unknown; + workspace: string; + workspace_detail: TWorkspaceDetails; + project: string; + project_details: TProjectDetails; + views: TViewDetails; + created_by: string; + updated_by: string; + created_at: string; + updated_at: string; +}; diff --git a/web/components/headers/workspace-active-cycles.tsx b/web/app/[workspaceSlug]/@header/active-cycles/header.tsx similarity index 89% rename from web/components/headers/workspace-active-cycles.tsx rename to web/app/[workspaceSlug]/@header/active-cycles/header.tsx index 5861cba60..eb6e8633d 100644 --- a/web/components/headers/workspace-active-cycles.tsx +++ b/web/app/[workspaceSlug]/@header/active-cycles/header.tsx @@ -1,3 +1,5 @@ +"use client"; + import { observer } from "mobx-react"; // ui import { Crown } from "lucide-react"; @@ -5,7 +7,7 @@ import { Breadcrumbs, ContrastIcon } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // icons -export const WorkspaceActiveCycleHeader = observer(() => ( +const WorkspaceActiveCycleHeader = observer(() => (
@@ -25,3 +27,5 @@ export const WorkspaceActiveCycleHeader = observer(() => (
)); + +export default WorkspaceActiveCycleHeader; diff --git a/web/app/[workspaceSlug]/@header/active-cycles/page.tsx b/web/app/[workspaceSlug]/@header/active-cycles/page.tsx new file mode 100644 index 000000000..56083aab7 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/active-cycles/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "../../app-header-wrapper"; +import WorkspaceActiveCycleHeader from "./header"; + +const WorkspaceActiveCycleHeaderPage = () => } />; + +export default WorkspaceActiveCycleHeaderPage; diff --git a/web/components/headers/workspace-analytics.tsx b/web/app/[workspaceSlug]/@header/analytics/header.tsx similarity index 89% rename from web/components/headers/workspace-analytics.tsx rename to web/app/[workspaceSlug]/@header/analytics/header.tsx index 98ceccbca..51254da58 100644 --- a/web/components/headers/workspace-analytics.tsx +++ b/web/app/[workspaceSlug]/@header/analytics/header.tsx @@ -1,17 +1,22 @@ +"use client"; + import { useEffect } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useSearchParams } from "next/navigation"; +// icons import { BarChart2, PanelRight } from "lucide-react"; // ui import { Breadcrumbs } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; +// helpers import { cn } from "@/helpers/common.helper"; +// hooks import { useAppTheme } from "@/hooks/store"; -export const WorkspaceAnalyticsHeader = observer(() => { - const router = useRouter(); - const { analytics_tab } = router.query; +const WorkspaceAnalyticsHeader = observer(() => { + const searchParams = useSearchParams(); + const analytics_tab = searchParams.get("analytics_tab"); // store hooks const { workspaceAnalyticsSidebarCollapsed, toggleWorkspaceAnalyticsSidebar } = useAppTheme(); @@ -66,3 +71,5 @@ export const WorkspaceAnalyticsHeader = observer(() => { ); }); + +export default WorkspaceAnalyticsHeader; diff --git a/web/app/[workspaceSlug]/@header/analytics/page.tsx b/web/app/[workspaceSlug]/@header/analytics/page.tsx new file mode 100644 index 000000000..405057b05 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/analytics/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "../../app-header-wrapper"; +import WorkspaceAnalyticsHeader from "./header"; + +const WorkspaceAnalyticsHeaderPage = () => } />; + +export default WorkspaceAnalyticsHeaderPage; diff --git a/web/components/headers/workspace-dashboard.tsx b/web/app/[workspaceSlug]/@header/header.tsx similarity index 96% rename from web/components/headers/workspace-dashboard.tsx rename to web/app/[workspaceSlug]/@header/header.tsx index 880b44406..4afd12399 100644 --- a/web/components/headers/workspace-dashboard.tsx +++ b/web/app/[workspaceSlug]/@header/header.tsx @@ -1,18 +1,21 @@ +"use client"; + import Image from "next/image"; import { useTheme } from "next-themes"; import { Home, Zap } from "lucide-react"; // images import githubBlackImage from "/public/logos/github-black.png"; import githubWhiteImage from "/public/logos/github-white.png"; -// hooks -// components +// ui import { Breadcrumbs } from "@plane/ui"; +// components import { BreadcrumbLink } from "@/components/common"; // constants import { CHANGELOG_REDIRECTED, GITHUB_REDIRECTED } from "@/constants/event-tracker"; +// hooks import { useEventTracker } from "@/hooks/store"; -export const WorkspaceDashboardHeader = () => { +const WorkspaceDashboardHeader = () => { // hooks const { captureEvent } = useEventTracker(); const { resolvedTheme } = useTheme(); @@ -69,3 +72,5 @@ export const WorkspaceDashboardHeader = () => { ); }; + +export default WorkspaceDashboardHeader; diff --git a/web/app/[workspaceSlug]/@header/page.tsx b/web/app/[workspaceSlug]/@header/page.tsx new file mode 100644 index 000000000..4175c5f38 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "../app-header-wrapper"; +import WorkspaceDashboardHeader from "./header"; + +const WorkspaceDashboardHeaderPage = () => } />; + +export default WorkspaceDashboardHeaderPage; diff --git a/web/app/[workspaceSlug]/@header/profile/[userId]/activity/page.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/activity/page.tsx new file mode 100644 index 000000000..56d3a1468 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/activity/page.tsx @@ -0,0 +1,8 @@ +"use client"; + +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import UserProfileHeader from "../header"; + +const ProfileActivityHeader = () => } />; + +export default ProfileActivityHeader; diff --git a/web/app/[workspaceSlug]/@header/profile/[userId]/assigned/page.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/assigned/page.tsx new file mode 100644 index 000000000..a6253fca3 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/assigned/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import UserProfileHeader from "../header"; +import ProfileIssuesMobileHeader from "../mobile-header"; + +const ProfileAssignedHeader = () => ( + } mobileHeader={} /> +); + +export default ProfileAssignedHeader; diff --git a/web/app/[workspaceSlug]/@header/profile/[userId]/created/page.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/created/page.tsx new file mode 100644 index 000000000..e46ef7b25 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/created/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import UserProfileHeader from "../header"; +import ProfileIssuesMobileHeader from "../mobile-header"; + +const ProfileCreatedHeader = () => ( + } mobileHeader={} /> +); + +export default ProfileCreatedHeader; diff --git a/web/components/headers/user-profile.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/header.tsx similarity index 93% rename from web/components/headers/user-profile.tsx rename to web/app/[workspaceSlug]/@header/profile/[userId]/header.tsx index 4f1f44659..1f15e2c48 100644 --- a/web/components/headers/user-profile.tsx +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/header.tsx @@ -1,8 +1,10 @@ +"use client"; + // ui import { FC } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { ChevronDown, PanelRight } from "lucide-react"; import { Breadcrumbs, CustomMenu } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; @@ -15,11 +17,10 @@ type TUserProfileHeader = { type?: string | undefined; }; -export const UserProfileHeader: FC = observer((props) => { +const UserProfileHeader: FC = observer((props) => { const { type = undefined } = props; // router - const router = useRouter(); - const { workspaceSlug, userId } = router.query; + const { workspaceSlug, userId } = useParams(); // store hooks const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme(); const { @@ -89,3 +90,5 @@ export const UserProfileHeader: FC = observer((props) => {
); }); + +export default UserProfileHeader; diff --git a/web/components/profile/profile-issues-mobile-header.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/mobile-header.tsx similarity index 98% rename from web/components/profile/profile-issues-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/profile/[userId]/mobile-header.tsx index 6088d0194..99f6e0c9c 100644 --- a/web/components/profile/profile-issues-mobile-header.tsx +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/mobile-header.tsx @@ -1,6 +1,8 @@ +"use client"; + import { useCallback } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // icons import { ChevronDown } from "lucide-react"; // types @@ -18,8 +20,7 @@ import { useIssues, useLabel } from "@/hooks/store"; const ProfileIssuesMobileHeader = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, userId } = router.query; + const { workspaceSlug, userId } = useParams(); // store hook const { issuesFilter: { issueFilters, updateFilters }, diff --git a/web/app/[workspaceSlug]/@header/profile/[userId]/page.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/page.tsx new file mode 100644 index 000000000..df2b0d83e --- /dev/null +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/page.tsx @@ -0,0 +1,8 @@ +"use client"; + +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import UserProfileHeader from "./header"; + +const ProfileOverviewHeader = () => } />; + +export default ProfileOverviewHeader; diff --git a/web/app/[workspaceSlug]/@header/profile/[userId]/subscribed/page.tsx b/web/app/[workspaceSlug]/@header/profile/[userId]/subscribed/page.tsx new file mode 100644 index 000000000..75caa98c9 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/profile/[userId]/subscribed/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import UserProfileHeader from "../header"; +import ProfileIssuesMobileHeader from "../mobile-header"; + +const ProfileSubscribedHeader = () => ( + } mobileHeader={} /> +); + +export default ProfileSubscribedHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/[...default-header]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/[...default-header]/page.tsx new file mode 100644 index 000000000..6e5cd0160 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/[...default-header]/page.tsx @@ -0,0 +1,2 @@ +import DefaultProjectArchivesHeader from "../page"; +export default DefaultProjectArchivesHeader; \ No newline at end of file diff --git a/web/components/headers/project-archives.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/header.tsx similarity index 92% rename from web/components/headers/project-archives.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/archives/header.tsx index 502241461..aba65352e 100644 --- a/web/components/headers/project-archives.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/header.tsx @@ -1,6 +1,6 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams, usePathname, useRouter } from "next/navigation"; // ui import { ArchiveIcon, Breadcrumbs, Tooltip } from "@plane/ui"; // components @@ -12,11 +12,12 @@ import { EIssuesStoreType } from "@/constants/issue"; import { useIssues, useProject } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; -export const ProjectArchivesHeader: FC = observer(() => { +const ProjectArchivesHeader: FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - const activeTab = router.pathname.split("/").pop(); + const { workspaceSlug, projectId } = useParams(); + const pathname = usePathname(); + const activeTab = pathname.split("/").pop(); // store hooks const { issuesFilter: { issueFilters }, @@ -93,3 +94,5 @@ export const ProjectArchivesHeader: FC = observer(() => { ); }); + +export default ProjectArchivesHeader; diff --git a/web/components/headers/project-archived-issue-details.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/header.tsx similarity index 92% rename from web/components/headers/project-archived-issue-details.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/header.tsx index c874745a4..75b8dce43 100644 --- a/web/components/headers/project-archived-issue-details.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/header.tsx @@ -1,27 +1,23 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import useSWR from "swr"; -// hooks -import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui"; -import { BreadcrumbLink, Logo } from "@/components/common"; -import { ISSUE_DETAILS } from "@/constants/fetch-keys"; -import { useProject } from "@/hooks/store"; -// components // ui -// types -import { IssueArchiveService } from "@/services/issue"; -// constants -// services -// helpers +import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui"; // components +import { BreadcrumbLink, Logo } from "@/components/common"; +// constants +import { ISSUE_DETAILS } from "@/constants/fetch-keys"; +// hooks +import { useProject } from "@/hooks/store"; +// services +import { IssueArchiveService } from "@/services/issue"; const issueArchiveService = new IssueArchiveService(); -export const ProjectArchivedIssueDetailsHeader: FC = observer(() => { +const ProjectArchivedIssueDetailsHeader: FC = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, projectId, archivedIssueId } = router.query; + const { workspaceSlug, projectId, archivedIssueId } = useParams(); // store hooks const { currentProjectDetails } = useProject(); @@ -96,3 +92,5 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => { ); }); + +export default ProjectArchivedIssueDetailsHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/page.tsx new file mode 100644 index 000000000..60fa754ce --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/issues/[archivedIssueId]/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectArchivedIssueDetailsHeader from "./header"; + +const ProjectArchivedIssueDetailsHeaderPage = () => } />; + +export default ProjectArchivedIssueDetailsHeaderPage; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/page.tsx new file mode 100644 index 000000000..aa1b03169 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/archives/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectArchivesHeader from "./header"; + +const ProjectArchivesHeaderPage = () => } />; + +export default ProjectArchivesHeaderPage; diff --git a/web/components/headers/cycle-issues.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/header.tsx similarity index 96% rename from web/components/headers/cycle-issues.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/header.tsx index 4e9c37c9d..3f70a647d 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/header.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // icons import { ArrowRight, PanelRight } from "lucide-react"; // types @@ -36,8 +36,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => { // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); // store hooks const { getCycleById } = useCycle(); // derived values @@ -56,12 +55,12 @@ const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => { ); }; -export const CycleIssuesHeader: React.FC = observer(() => { +const CycleIssuesHeader: React.FC = observer(() => { // states const [analyticsModal, setAnalyticsModal] = useState(false); // router const router = useRouter(); - const { workspaceSlug, projectId, cycleId } = router.query as { + const { workspaceSlug, projectId, cycleId } = useParams() as { workspaceSlug: string; projectId: string; cycleId: string; @@ -206,9 +205,8 @@ export const CycleIssuesHeader: React.FC = observer(() => { {issuesCount && issuesCount > 0 ? ( 1 ? "issues" : "issue" - } in this cycle`} + tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "issues" : "issue" + } in this cycle`} position="bottom" > @@ -303,3 +301,5 @@ export const CycleIssuesHeader: React.FC = observer(() => { ); }); + +export default CycleIssuesHeader; diff --git a/web/components/cycles/cycle-mobile-header.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/mobile-header.tsx similarity index 97% rename from web/components/cycles/cycle-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/mobile-header.tsx index ce1b45a9b..e68829774 100644 --- a/web/components/cycles/cycle-mobile-header.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/mobile-header.tsx @@ -1,5 +1,5 @@ import { useCallback, useState } from "react"; -import router from "next/router"; +import { useParams } from "next/navigation"; // icons import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // types @@ -16,7 +16,7 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store"; -export const CycleMobileHeader = () => { +const CycleIssuesMobileHeader = () => { const [analyticsModal, setAnalyticsModal] = useState(false); const { getCycleById } = useCycle(); const layouts = [ @@ -25,7 +25,7 @@ export const CycleMobileHeader = () => { { key: "calendar", title: "Calendar", icon: Calendar }, ]; - const { workspaceSlug, projectId, cycleId } = router.query; + const { workspaceSlug, projectId, cycleId } = useParams(); const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined; // store hooks const { currentProjectDetails } = useProject(); @@ -202,3 +202,5 @@ export const CycleMobileHeader = () => { ); }; + +export default CycleIssuesMobileHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/page.tsx new file mode 100644 index 000000000..6afb97002 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/[cycleId]/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import CycleIssuesHeader from "./header"; +import CycleIssuesMobileHeader from "./mobile-header"; + +const CycleIssuesHeaderPage = () => ( + } mobileHeader={} /> +); + +export default CycleIssuesHeaderPage; diff --git a/web/components/headers/cycles.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/header.tsx similarity index 93% rename from web/components/headers/cycles.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/header.tsx index 76493bd51..1a0993d80 100644 --- a/web/components/headers/cycles.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/header.tsx @@ -1,6 +1,6 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // ui import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui"; // components @@ -11,10 +11,10 @@ import { EUserProjectRoles } from "@/constants/project"; // hooks import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; -export const CyclesHeader: FC = observer(() => { +const CyclesHeader: FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); // store hooks const { toggleCreateCycleModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); @@ -72,3 +72,5 @@ export const CyclesHeader: FC = observer(() => { ); }); + +export default CyclesHeader; diff --git a/web/components/cycles/cycles-list-mobile-header.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/mobile-header.tsx similarity index 100% rename from web/components/cycles/cycles-list-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/mobile-header.tsx diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/page.tsx new file mode 100644 index 000000000..457e9b3dc --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/cycles/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import CyclesHeader from "./header"; +import CyclesListMobileHeader from "./mobile-header"; + +const CyclesHeaderPage = () => ( + } mobileHeader={} /> +); + +export default CyclesHeaderPage; diff --git a/web/components/headers/project-draft-issues.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/header.tsx similarity index 96% rename from web/components/headers/project-draft-issues.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/header.tsx index f3e70d86b..dc2a393bf 100644 --- a/web/components/headers/project-draft-issues.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/header.tsx @@ -1,6 +1,6 @@ import { FC, useCallback } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; // ui @@ -16,10 +16,9 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; -export const ProjectDraftIssueHeader: FC = observer(() => { +const ProjectDraftIssueHeader: FC = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; + const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks const { issuesFilter: { issueFilters, updateFilters }, @@ -168,3 +167,5 @@ export const ProjectDraftIssueHeader: FC = observer(() => { ); }); + +export default ProjectDraftIssueHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/page.tsx new file mode 100644 index 000000000..a31440f48 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/draft-issues/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectDraftIssueHeader from "./header"; + +const ProjectDraftIssueHeaderPage = () => } />; + +export default ProjectDraftIssueHeaderPage; diff --git a/web/components/headers/project-inbox.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/header.tsx similarity index 93% rename from web/components/headers/project-inbox.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/header.tsx index ce76f3e40..b7fd70295 100644 --- a/web/components/headers/project-inbox.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/header.tsx @@ -1,6 +1,6 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { RefreshCcw } from "lucide-react"; // ui import { Breadcrumbs, Button, LayersIcon } from "@plane/ui"; @@ -10,12 +10,11 @@ import { InboxIssueCreateEditModalRoot } from "@/components/inbox"; // hooks import { useProject, useProjectInbox } from "@/hooks/store"; -export const ProjectInboxHeader: FC = observer(() => { +const ProjectInboxHeader: FC = observer(() => { // states const [createIssueModal, setCreateIssueModal] = useState(false); // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); // store hooks const { currentProjectDetails } = useProject(); const { loader } = useProjectInbox(); @@ -77,3 +76,5 @@ export const ProjectInboxHeader: FC = observer(() => { ); }); + +export default ProjectInboxHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/page.tsx new file mode 100644 index 000000000..90d616606 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/inbox/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectInboxHeader from "./header"; + +const ProjectInboxHeaderPage = () => } />; + +export default ProjectInboxHeaderPage; diff --git a/web/components/headers/project-issue-details.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/header.tsx similarity index 92% rename from web/components/headers/project-issue-details.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/header.tsx index 890bd59e5..f8966e001 100644 --- a/web/components/headers/project-issue-details.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/header.tsx @@ -1,22 +1,20 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; -// hooks +import { useParams, useRouter } from "next/navigation"; import { PanelRight } from "lucide-react"; -import { Breadcrumbs, LayersIcon } from "@plane/ui"; -import { BreadcrumbLink, Logo } from "@/components/common"; -import { cn } from "@/helpers/common.helper"; -import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store"; // ui -// helpers -// services -// constants +import { Breadcrumbs, LayersIcon } from "@plane/ui"; // components +import { BreadcrumbLink, Logo } from "@/components/common"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store"; -export const ProjectIssueDetailsHeader: FC = observer(() => { +const ProjectIssueDetailsHeader: FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; + const { workspaceSlug, projectId, issueId } = useParams(); // store hooks const { currentProjectDetails } = useProject(); const { issueDetailSidebarCollapsed, toggleIssueDetailSidebar } = useAppTheme(); @@ -83,3 +81,5 @@ export const ProjectIssueDetailsHeader: FC = observer(() => { ); }); + +export default ProjectIssueDetailsHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/page.tsx new file mode 100644 index 000000000..1c64134f0 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/[issueId]/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectIssueDetailsHeader from "./header"; + +const ProjectIssueDetailsHeaderPage = () => } />; + +export default ProjectIssueDetailsHeaderPage; diff --git a/web/components/headers/project-issues.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/header.tsx similarity index 97% rename from web/components/headers/project-issues.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/issues/header.tsx index e3983d39c..44bebde89 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/header.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // icons import { Briefcase, Circle, ExternalLink } from "lucide-react"; // types @@ -30,12 +30,12 @@ import { import { useIssues } from "@/hooks/store/use-issues"; import { usePlatformOS } from "@/hooks/use-platform-os"; -export const ProjectIssuesHeader: React.FC = observer(() => { +const ProjectIssuesHeader: React.FC = observer(() => { // states const [analyticsModal, setAnalyticsModal] = useState(false); // router const router = useRouter(); - const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; + const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks const { project: { projectMemberIds }, @@ -234,3 +234,5 @@ export const ProjectIssuesHeader: React.FC = observer(() => { ); }); + +export default ProjectIssuesHeader; diff --git a/web/components/issues/issues-mobile-header.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/mobile-header.tsx similarity index 97% rename from web/components/issues/issues-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/issues/mobile-header.tsx index 956aa0d29..cab53f88f 100644 --- a/web/components/issues/issues-mobile-header.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/mobile-header.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from "react"; import { observer } from "mobx-react"; -import router from "next/router"; +import { useParams } from "next/navigation"; // icons import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // types @@ -17,14 +17,14 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; -export const IssuesMobileHeader = observer(() => { +const ProjectIssuesMobileHeader = observer(() => { const layouts = [ { key: "list", title: "List", icon: List }, { key: "kanban", title: "Kanban", icon: Kanban }, { key: "calendar", title: "Calendar", icon: Calendar }, ]; const [analyticsModal, setAnalyticsModal] = useState(false); - const { workspaceSlug, projectId } = router.query as { + const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string; }; @@ -180,3 +180,5 @@ export const IssuesMobileHeader = observer(() => { ); }); + +export default ProjectIssuesMobileHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/page.tsx new file mode 100644 index 000000000..7c4296d92 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/issues/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectIssuesHeader from "./header"; +import ProjectIssuesMobileHeader from "./mobile-header"; + +const ProjectIssuesHeaderPage = () => ( + } mobileHeader={} /> +); + +export default ProjectIssuesHeaderPage; diff --git a/web/components/headers/module-issues.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/header.tsx similarity index 96% rename from web/components/headers/module-issues.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/header.tsx index a433bead5..474bcd713 100644 --- a/web/components/headers/module-issues.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/header.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // icons import { ArrowRight, PanelRight } from "lucide-react"; // types @@ -37,8 +37,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); // store hooks const { getModuleById } = useModule(); // derived values @@ -59,12 +58,12 @@ const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { ); }; -export const ModuleIssuesHeader: React.FC = observer(() => { +const ModuleIssuesHeader: React.FC = observer(() => { // states const [analyticsModal, setAnalyticsModal] = useState(false); // router const router = useRouter(); - const { workspaceSlug, projectId, moduleId } = router.query; + const { workspaceSlug, projectId, moduleId } = useParams(); // hooks const { isMobile } = usePlatformOS(); // store hooks @@ -206,9 +205,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => { {issuesCount && issuesCount > 0 ? ( 1 ? "issues" : "issue" - } in this module`} + tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "issues" : "issue" + } in this module`} position="bottom" > @@ -310,3 +308,5 @@ export const ModuleIssuesHeader: React.FC = observer(() => { ); }); + +export default ModuleIssuesHeader; diff --git a/web/components/modules/module-mobile-header.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/mobile-header.tsx similarity index 97% rename from web/components/modules/module-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/mobile-header.tsx index ababa1ba1..db2e697e5 100644 --- a/web/components/modules/module-mobile-header.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/mobile-header.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from "react"; import { observer } from "mobx-react"; -import router from "next/router"; +import { useParams } from "next/navigation"; // icons import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // types @@ -17,7 +17,7 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks import { useIssues, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store"; -export const ModuleMobileHeader = observer(() => { +const ModuleIssuesMobileHeader = observer(() => { const [analyticsModal, setAnalyticsModal] = useState(false); const { currentProjectDetails } = useProject(); const { getModuleById } = useModule(); @@ -26,7 +26,7 @@ export const ModuleMobileHeader = observer(() => { { key: "kanban", title: "Kanban", icon: Kanban }, { key: "calendar", title: "Calendar", icon: Calendar }, ]; - const { workspaceSlug, projectId, moduleId } = router.query as { + const { workspaceSlug, projectId, moduleId } = useParams() as { workspaceSlug: string; projectId: string; moduleId: string; @@ -183,3 +183,5 @@ export const ModuleMobileHeader = observer(() => { ); }); + +export default ModuleIssuesMobileHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/page.tsx new file mode 100644 index 000000000..43ae996df --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/[moduleId]/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ModuleIssuesHeader from "./header"; +import ModuleIssuesMobileHeader from "./mobile-header"; + +const ModuleIssuesHeaderPage = () => ( + } mobileHeader={} /> +); + +export default ModuleIssuesHeaderPage; diff --git a/web/components/headers/modules-list.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/header.tsx similarity index 93% rename from web/components/headers/modules-list.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/modules/header.tsx index 0e1fd53fc..884bf1fb2 100644 --- a/web/components/headers/modules-list.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/header.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // ui import { Breadcrumbs, Button, DiceIcon } from "@plane/ui"; // components @@ -10,10 +10,10 @@ import { EUserProjectRoles } from "@/constants/project"; // hooks import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; -export const ModulesListHeader: React.FC = observer(() => { +const ModulesListHeader: React.FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); // store hooks const { toggleCreateModuleModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); @@ -72,3 +72,5 @@ export const ModulesListHeader: React.FC = observer(() => { ); }); + +export default ModulesListHeader; diff --git a/web/components/modules/moduels-list-mobile-header.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/mobile-header.tsx similarity index 100% rename from web/components/modules/moduels-list-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/modules/mobile-header.tsx diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/page.tsx new file mode 100644 index 000000000..dce9e103f --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/modules/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ModulesListHeader from "./header"; +import ModulesListMobileHeader from "./mobile-header"; + +const ModulesHeaderPage = () => ( + } mobileHeader={} /> +); + +export default ModulesHeaderPage; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/header.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/header.tsx new file mode 100644 index 000000000..f242db9a0 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/header.tsx @@ -0,0 +1,170 @@ +import { useState } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { FileText } from "lucide-react"; +// types +import { TLogoProps } from "@plane/types"; +// ui +import { Breadcrumbs, Button, EmojiIconPicker, EmojiIconPickerTypes, TOAST_TYPE, setToast } from "@plane/ui"; +// components +import { BreadcrumbLink, Logo } from "@/components/common"; +// helper +import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper"; +// hooks +import { usePage, useProject } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; + +export interface IPagesHeaderProps { + showButton?: boolean; +} + +const PageDetailsHeader = observer(() => { + // router + const { workspaceSlug, pageId } = useParams(); + // state + const [isOpen, setIsOpen] = useState(false); + // store hooks + const { currentProjectDetails } = useProject(); + const { isContentEditable, isSubmitting, name, logo_props, updatePageLogo } = usePage(pageId?.toString() ?? ""); + + const handlePageLogoUpdate = async (data: TLogoProps) => { + if (data) { + updatePageLogo(data) + .then(() => { + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Logo Updated successfully.", + }); + }) + .catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Something went wrong. Please try again.", + }); + }); + } + }; + // use platform + const { platform } = usePlatformOS(); + // derived values + const isMac = platform === "MacOS"; + + return ( +
+
+
+ + + + + + + ) + } + /> + + + + + + } + /> + + } + /> + } + /> + setIsOpen(val)} + className="flex items-center justify-center" + buttonClassName="flex items-center justify-center" + label={ + <> + {logo_props?.in_use ? ( + + ) : ( + + )} + + } + onChange={(val) => { + let logoValue = {}; + + if (val?.type === "emoji") + logoValue = { + value: convertHexEmojiToDecimal(val.value.unified), + url: val.value.imageUrl, + }; + else if (val?.type === "icon") logoValue = val.value; + + handlePageLogoUpdate({ + in_use: val?.type, + [val?.type]: logoValue, + }).finally(() => setIsOpen(false)); + }} + defaultIconColor={ + logo_props?.in_use && logo_props.in_use === "icon" ? logo_props?.icon?.color : undefined + } + defaultOpen={ + logo_props?.in_use && logo_props?.in_use === "emoji" + ? EmojiIconPickerTypes.EMOJI + : EmojiIconPickerTypes.ICON + } + /> + } + /> + } + /> + +
+
+ {isContentEditable && ( + + )} +
+ ); +}); + +export default PageDetailsHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/page.tsx new file mode 100644 index 000000000..6a426ea2a --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/[pageId]/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import PageDetailsHeader from "./header"; + +const PageDetailsHeaderPage = () => } />; + +export default PageDetailsHeaderPage; diff --git a/web/components/headers/pages.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/header.tsx similarity index 90% rename from web/components/headers/pages.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/pages/header.tsx index 893c4409d..bd35c875a 100644 --- a/web/components/headers/pages.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/header.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, useSearchParams } from "next/navigation"; import { FileText } from "lucide-react"; // ui import { Breadcrumbs, Button } from "@plane/ui"; @@ -11,10 +11,11 @@ import { EUserProjectRoles } from "@/constants/project"; // hooks import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; -export const PagesHeader = observer(() => { +const PagesHeader = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, type: pageType } = router.query; + const { workspaceSlug } = useParams(); + const searchParams = useSearchParams(); + const pageType = searchParams.get("type"); // store hooks const { toggleCreatePageModal } = useCommandPalette(); const { @@ -74,3 +75,5 @@ export const PagesHeader = observer(() => { ); }); + +export default PagesHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/page.tsx new file mode 100644 index 000000000..e59e271c7 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/pages/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import PagesHeader from "./header"; + +const PagesHeaderPage = () => } />; + +export default PagesHeaderPage; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/[...default-header]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/[...default-header]/page.tsx new file mode 100644 index 000000000..c9c53de65 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/[...default-header]/page.tsx @@ -0,0 +1,2 @@ +import DefaultProjectSettingHeader from "../page"; +export default DefaultProjectSettingHeader; \ No newline at end of file diff --git a/web/components/headers/project-settings.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/header.tsx similarity index 93% rename from web/components/headers/project-settings.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/settings/header.tsx index 2fe48969d..351c20d81 100644 --- a/web/components/headers/project-settings.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/header.tsx @@ -1,6 +1,6 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // ui import { Settings } from "lucide-react"; import { Breadcrumbs, CustomMenu } from "@plane/ui"; @@ -11,10 +11,10 @@ import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project"; // hooks import { useProject, useUser } from "@/hooks/store"; -export const ProjectSettingHeader: FC = observer(() => { +const ProjectSettingHeader: FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); // store hooks const { membership: { currentProjectRole }, @@ -80,3 +80,5 @@ export const ProjectSettingHeader: FC = observer(() => { ); }); + +export default ProjectSettingHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/page.tsx new file mode 100644 index 000000000..3da6a809a --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/settings/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectSettingHeader from "./header"; + +const ProjectSettingHeaderPage = () => } />; + +export default ProjectSettingHeaderPage; diff --git a/web/components/headers/project-view-issues.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/header.tsx similarity index 97% rename from web/components/headers/project-view-issues.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/header.tsx index 6270093a3..da44f2b68 100644 --- a/web/components/headers/project-view-issues.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/header.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; // ui @@ -28,10 +28,9 @@ import { useUser, } from "@/hooks/store"; -export const ProjectViewIssuesHeader: React.FC = observer(() => { +const ProjectViewIssuesHeader: React.FC = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, projectId, viewId } = router.query; + const { workspaceSlug, projectId, viewId } = useParams(); // store hooks const { issuesFilter: { issueFilters, updateFilters }, @@ -256,3 +255,5 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { ); }); + +export default ProjectViewIssuesHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/page.tsx new file mode 100644 index 000000000..14f135d4e --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/[viewId]/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectViewIssuesHeader from "./header"; + +const ProjectViewIssuesHeaderPage = () => } />; + +export default ProjectViewIssuesHeaderPage; diff --git a/web/components/headers/project-views.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/header.tsx similarity index 93% rename from web/components/headers/project-views.tsx rename to web/app/[workspaceSlug]/@header/projects/[projectId]/views/header.tsx index 7f1d1a725..edbf406ef 100644 --- a/web/components/headers/project-views.tsx +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/header.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // ui import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui"; // components @@ -10,10 +10,9 @@ import { EUserProjectRoles } from "@/constants/project"; // hooks import { useCommandPalette, useProject, useUser } from "@/hooks/store"; -export const ProjectViewsHeader: React.FC = observer(() => { +const ProjectViewsHeader: React.FC = observer(() => { // router - const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); // store hooks const { toggleCreateViewModal } = useCommandPalette(); const { @@ -69,3 +68,5 @@ export const ProjectViewsHeader: React.FC = observer(() => { ); }); + +export default ProjectViewsHeader; diff --git a/web/app/[workspaceSlug]/@header/projects/[projectId]/views/page.tsx b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/page.tsx new file mode 100644 index 000000000..e5f8ac92a --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/[projectId]/views/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectViewsHeader from "./header"; + +const ProjectViewsHeaderPage = () => } />; + +export default ProjectViewsHeaderPage; diff --git a/web/components/headers/projects.tsx b/web/app/[workspaceSlug]/@header/projects/header.tsx similarity index 98% rename from web/components/headers/projects.tsx rename to web/app/[workspaceSlug]/@header/projects/header.tsx index 7126b2697..57b0a50cd 100644 --- a/web/components/headers/projects.tsx +++ b/web/app/[workspaceSlug]/@header/projects/header.tsx @@ -18,7 +18,7 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; import { useAppRouter, useCommandPalette, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; -export const ProjectsHeader = observer(() => { +const ProjectsHeader = observer(() => { // states const [isSearchOpen, setIsSearchOpen] = useState(false); // refs @@ -181,3 +181,5 @@ export const ProjectsHeader = observer(() => { ); }); + +export default ProjectsHeader; diff --git a/web/components/project/projects-mobile-header.tsx b/web/app/[workspaceSlug]/@header/projects/mobile-header.tsx similarity index 100% rename from web/components/project/projects-mobile-header.tsx rename to web/app/[workspaceSlug]/@header/projects/mobile-header.tsx diff --git a/web/app/[workspaceSlug]/@header/projects/page.tsx b/web/app/[workspaceSlug]/@header/projects/page.tsx new file mode 100644 index 000000000..64108a13c --- /dev/null +++ b/web/app/[workspaceSlug]/@header/projects/page.tsx @@ -0,0 +1,13 @@ +"use client"; + +// components +import AppHeaderWrapper from "@/app/[workspaceSlug]/app-header-wrapper"; +import ProjectsHeader from "./header"; +import ProjectsMobileHeader from "./mobile-header"; + + +const ProjectsHeaderPage = () => ( + } mobileHeader={} /> +); + +export default ProjectsHeaderPage; diff --git a/web/app/[workspaceSlug]/@header/settings/[...default-header]/page.tsx b/web/app/[workspaceSlug]/@header/settings/[...default-header]/page.tsx new file mode 100644 index 000000000..9bb709262 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/settings/[...default-header]/page.tsx @@ -0,0 +1,2 @@ +import DefaultWorkspaceSettingsHeader from "../page"; +export default DefaultWorkspaceSettingsHeader; \ No newline at end of file diff --git a/web/components/headers/workspace-settings.tsx b/web/app/[workspaceSlug]/@header/settings/header.tsx similarity index 91% rename from web/components/headers/workspace-settings.tsx rename to web/app/[workspaceSlug]/@header/settings/header.tsx index 2d3e9649e..111cbaef4 100644 --- a/web/components/headers/workspace-settings.tsx +++ b/web/app/[workspaceSlug]/@header/settings/header.tsx @@ -1,3 +1,5 @@ +"use client"; + import { FC } from "react"; import { observer } from "mobx-react";; import { Settings } from "lucide-react"; @@ -8,7 +10,7 @@ import { BreadcrumbLink } from "@/components/common"; // hooks import { useWorkspace } from "@/hooks/store"; -export const WorkspaceSettingHeader: FC = observer(() => { +const WorkspaceSettingHeader: FC = observer(() => { const { currentWorkspace } = useWorkspace(); return ( @@ -33,3 +35,5 @@ export const WorkspaceSettingHeader: FC = observer(() => { ); }); + +export default WorkspaceSettingHeader; diff --git a/web/app/[workspaceSlug]/@header/settings/page.tsx b/web/app/[workspaceSlug]/@header/settings/page.tsx new file mode 100644 index 000000000..d7f4e2b53 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/settings/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "../../app-header-wrapper"; +import WorkspaceSettingHeader from "./header"; + +const WorkspaceSettingHeaderPage = () => } />; + +export default WorkspaceSettingHeaderPage; diff --git a/web/app/[workspaceSlug]/@header/workspace-views/[...default-header]/page.tsx b/web/app/[workspaceSlug]/@header/workspace-views/[...default-header]/page.tsx new file mode 100644 index 000000000..a057def83 --- /dev/null +++ b/web/app/[workspaceSlug]/@header/workspace-views/[...default-header]/page.tsx @@ -0,0 +1,2 @@ +import DefaultWorkspaceViewsHeader from "../page"; +export default DefaultWorkspaceViewsHeader; \ No newline at end of file diff --git a/web/components/headers/global-issues.tsx b/web/app/[workspaceSlug]/@header/workspace-views/header.tsx similarity index 96% rename from web/components/headers/global-issues.tsx rename to web/app/[workspaceSlug]/@header/workspace-views/header.tsx index a6a35d16f..091847d0a 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/app/[workspaceSlug]/@header/workspace-views/header.tsx @@ -1,6 +1,8 @@ +"use client"; + import { useCallback, useState } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui @@ -17,12 +19,11 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks import { useLabel, useMember, useUser, useIssues } from "@/hooks/store"; -export const GlobalIssuesHeader: React.FC = observer(() => { +const GlobalIssuesHeader: React.FC = observer(() => { // states const [createViewModal, setCreateViewModal] = useState(false); // router - const router = useRouter(); - const { workspaceSlug, globalViewId } = router.query; + const { workspaceSlug, globalViewId } = useParams(); // store hooks const { issuesFilter: { filters, updateFilters }, @@ -143,3 +144,5 @@ export const GlobalIssuesHeader: React.FC = observer(() => { ); }); + +export default GlobalIssuesHeader; diff --git a/web/app/[workspaceSlug]/@header/workspace-views/page.tsx b/web/app/[workspaceSlug]/@header/workspace-views/page.tsx new file mode 100644 index 000000000..51b81df2b --- /dev/null +++ b/web/app/[workspaceSlug]/@header/workspace-views/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +// components +import AppHeaderWrapper from "../../app-header-wrapper"; +import GlobalIssuesHeader from "./header"; + +const GlobalIssuesHeaderPage = () => } />; + +export default GlobalIssuesHeaderPage; diff --git a/web/pages/[workspaceSlug]/active-cycles.tsx b/web/app/[workspaceSlug]/active-cycles/page.tsx similarity index 50% rename from web/pages/[workspaceSlug]/active-cycles.tsx rename to web/app/[workspaceSlug]/active-cycles/page.tsx index 86bb37fa0..3ead5226b 100644 --- a/web/pages/[workspaceSlug]/active-cycles.tsx +++ b/web/app/[workspaceSlug]/active-cycles/page.tsx @@ -1,17 +1,13 @@ -import { ReactElement } from "react"; +"use client"; + import { observer } from "mobx-react"; // components import { PageHead } from "@/components/core"; -import { WorkspaceActiveCycleHeader } from "@/components/headers"; import { WorkspaceActiveCyclesUpgrade } from "@/components/workspace"; -// layouts -import { useWorkspace } from "@/hooks/store"; -import { AppLayout } from "@/layouts/app-layout"; -// types -import { NextPageWithLayout } from "@/lib/types"; // hooks +import { useWorkspace } from "@/hooks/store"; -const WorkspaceActiveCyclesPage: NextPageWithLayout = observer(() => { +const WorkspaceActiveCyclesPage = observer(() => { const { currentWorkspace } = useWorkspace(); // derived values const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined; @@ -24,8 +20,4 @@ const WorkspaceActiveCyclesPage: NextPageWithLayout = observer(() => { ); }); -WorkspaceActiveCyclesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; -}; - -export default WorkspaceActiveCyclesPage; +export default WorkspaceActiveCyclesPage; \ No newline at end of file diff --git a/web/pages/[workspaceSlug]/analytics.tsx b/web/app/[workspaceSlug]/analytics/page.tsx similarity index 76% rename from web/pages/[workspaceSlug]/analytics.tsx rename to web/app/[workspaceSlug]/analytics/page.tsx index 499efc917..2a4f9b9e9 100644 --- a/web/pages/[workspaceSlug]/analytics.tsx +++ b/web/app/[workspaceSlug]/analytics/page.tsx @@ -1,25 +1,22 @@ -import React, { Fragment, ReactElement } from "react"; +"use client"; + +import React, { Fragment } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useSearchParams } from "next/navigation"; import { Tab } from "@headlessui/react"; // components import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; import { PageHead } from "@/components/core"; import { EmptyState } from "@/components/empty-state"; -import { WorkspaceAnalyticsHeader } from "@/components/headers"; // constants import { ANALYTICS_TABS } from "@/constants/analytics"; import { EmptyStateType } from "@/constants/empty-state"; // hooks -import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store"; -// layouts -import { AppLayout } from "@/layouts/app-layout"; -// types -import { NextPageWithLayout } from "@/lib/types"; +import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store";; -const AnalyticsPage: NextPageWithLayout = observer(() => { - const router = useRouter(); - const { analytics_tab } = router.query; +const AnalyticsPage = observer(() => { + const searchParams = useSearchParams(); + const analytics_tab = searchParams.get("analytics_tab"); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); @@ -39,9 +36,8 @@ const AnalyticsPage: NextPageWithLayout = observer(() => { {({ selected }) => ( + + Back to sign in + + + + + + + + ); +}; + +export default ForgotPasswordPage; diff --git a/web/app/accounts/reset-password/layout.tsx b/web/app/accounts/reset-password/layout.tsx new file mode 100644 index 000000000..dbc0a29b4 --- /dev/null +++ b/web/app/accounts/reset-password/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Reset Password - Plane", +}; + +export default function ResetPasswordLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/app/accounts/reset-password/page.tsx b/web/app/accounts/reset-password/page.tsx new file mode 100644 index 000000000..cd661feb0 --- /dev/null +++ b/web/app/accounts/reset-password/page.tsx @@ -0,0 +1,235 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { useSearchParams } from "next/navigation"; +// icons +import { useTheme } from "next-themes"; +import { Eye, EyeOff } from "lucide-react"; +// ui +import { Button, Input } from "@plane/ui"; +// components +import { AuthBanner, PasswordStrengthMeter } from "@/components/account"; +// helpers +import { + EAuthenticationErrorCodes, + EErrorAlertType, + EPageTypes, + TAuthErrorInfo, + authErrorHandler, +} from "@/helpers/authentication.helper"; +import { API_BASE_URL } from "@/helpers/common.helper"; +import { getPasswordStrength } from "@/helpers/password.helper"; +// wrappers +import { AuthenticationWrapper } from "@/lib/wrappers"; +// services +import { AuthService } from "@/services/auth.service"; +// images +import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; +import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; +import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png"; +import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png"; + +type TResetPasswordFormValues = { + email: string; + password: string; + confirm_password?: string; +}; + +const defaultValues: TResetPasswordFormValues = { + email: "", + password: "", +}; + +// services +const authService = new AuthService(); + +const ResetPasswordPage = () => { + // search params + const searchParams = useSearchParams(); + const uidb64 = searchParams.get("uidb64"); + const token = searchParams.get("token"); + const email = searchParams.get("email"); + const error_code = searchParams.get("error_code"); + // states + const [showPassword, setShowPassword] = useState({ + password: false, + retypePassword: false, + }); + const [resetFormData, setResetFormData] = useState({ + ...defaultValues, + email: email ? email.toString() : "", + }); + const [csrfToken, setCsrfToken] = useState(undefined); + const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); + const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); + const [errorInfo, setErrorInfo] = useState(undefined); + + // hooks + const { resolvedTheme } = useTheme(); + + const handleShowPassword = (key: keyof typeof showPassword) => + setShowPassword((prev) => ({ ...prev, [key]: !prev[key] })); + + const handleFormChange = (key: keyof TResetPasswordFormValues, value: string) => + setResetFormData((prev) => ({ ...prev, [key]: value })); + + useEffect(() => { + if (csrfToken === undefined) + authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); + }, [csrfToken]); + + const isButtonDisabled = useMemo( + () => + !!resetFormData.password && + getPasswordStrength(resetFormData.password) >= 3 && + resetFormData.password === resetFormData.confirm_password + ? false + : true, + [resetFormData] + ); + + useEffect(() => { + if (error_code) { + const errorhandler = authErrorHandler(error_code?.toString() as EAuthenticationErrorCodes); + if (errorhandler) { + setErrorInfo(errorhandler); + } + } + }, [error_code]); + + const password = resetFormData?.password ?? ""; + const confirmPassword = resetFormData?.confirm_password ?? ""; + const renderPasswordMatchError = !isRetryPasswordInputFocused || confirmPassword.length >= password.length; + + const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; + + return ( + +
+
+ Plane background pattern +
+
+
+
+ + Plane logo + +
+
+
+
+
+

+ Set new password +

+

Secure your account with a strong password

+
+ {errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && ( + setErrorInfo(value)} /> + )} +
+ +
+ +
+ +
+
+
+ +
+ handleFormChange("password", e.target.value)} + //hasError={Boolean(errors.password)} + placeholder="Enter password" + className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" + minLength={8} + onFocus={() => setIsPasswordInputFocused(true)} + onBlur={() => setIsPasswordInputFocused(false)} + autoFocus + /> + {showPassword.password ? ( + handleShowPassword("password")} + /> + ) : ( + handleShowPassword("password")} + /> + )} +
+ {isPasswordInputFocused && } +
+
+ +
+ handleFormChange("confirm_password", e.target.value)} + placeholder="Confirm password" + className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" + onFocus={() => setIsRetryPasswordInputFocused(true)} + onBlur={() => setIsRetryPasswordInputFocused(false)} + /> + {showPassword.retypePassword ? ( + handleShowPassword("retypePassword")} + /> + ) : ( + handleShowPassword("retypePassword")} + /> + )} +
+ {!!resetFormData.confirm_password && + resetFormData.password !== resetFormData.confirm_password && + renderPasswordMatchError && Passwords don{"'"}t match} +
+ +
+
+
+
+
+
+ ); +}; + +export default ResetPasswordPage; diff --git a/web/app/accounts/set-password/layout.tsx b/web/app/accounts/set-password/layout.tsx new file mode 100644 index 000000000..dbd32e9e8 --- /dev/null +++ b/web/app/accounts/set-password/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Set Password - Plane", +}; + +export default function SetPasswordLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/app/accounts/set-password/page.tsx b/web/app/accounts/set-password/page.tsx new file mode 100644 index 000000000..26553e87b --- /dev/null +++ b/web/app/accounts/set-password/page.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { FormEvent, useEffect, useMemo, useState } from "react"; +import { observer } from "mobx-react-lite"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +// icons +import { useTheme } from "next-themes"; +import { Eye, EyeOff } from "lucide-react"; +// ui +import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; +// components +import { PasswordStrengthMeter } from "@/components/account"; +// helpers +import { EPageTypes } from "@/helpers/authentication.helper"; +import { getPasswordStrength } from "@/helpers/password.helper"; +// hooks +import { useUser } from "@/hooks/store"; +// wrappers +import { AuthenticationWrapper } from "@/lib/wrappers"; +// services +import { AuthService } from "@/services/auth.service"; +// images +import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; +import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; +import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png"; +import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png"; + +type TResetPasswordFormValues = { + email: string; + password: string; + confirm_password?: string; +}; + +const defaultValues: TResetPasswordFormValues = { + email: "", + password: "", +}; + +// services +const authService = new AuthService(); + +const SetPasswordPage = observer(() => { + // router + const router = useRouter(); + // search params + const searchParams = useSearchParams(); + const email = searchParams.get("email"); + // states + const [showPassword, setShowPassword] = useState({ + password: false, + retypePassword: false, + }); + const [passwordFormData, setPasswordFormData] = useState({ + ...defaultValues, + email: email ? email.toString() : "", + }); + const [csrfToken, setCsrfToken] = useState(undefined); + const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); + const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); + // hooks + const { resolvedTheme } = useTheme(); + // hooks + const { data: user, handleSetPassword } = useUser(); + + useEffect(() => { + if (csrfToken === undefined) + authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); + }, [csrfToken]); + + const handleShowPassword = (key: keyof typeof showPassword) => + setShowPassword((prev) => ({ ...prev, [key]: !prev[key] })); + + const handleFormChange = (key: keyof TResetPasswordFormValues, value: string) => + setPasswordFormData((prev) => ({ ...prev, [key]: value })); + + const isButtonDisabled = useMemo( + () => + !!passwordFormData.password && + getPasswordStrength(passwordFormData.password) >= 3 && + passwordFormData.password === passwordFormData.confirm_password + ? false + : true, + [passwordFormData] + ); + + const handleSubmit = async (e: FormEvent) => { + try { + e.preventDefault(); + if (!csrfToken) throw new Error("csrf token not found"); + await handleSetPassword(csrfToken, { password: passwordFormData.password }); + router.push("/"); + } catch (err: any) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }); + } + }; + + const password = passwordFormData?.password ?? ""; + const confirmPassword = passwordFormData?.confirm_password ?? ""; + const renderPasswordMatchError = !isRetryPasswordInputFocused || confirmPassword.length >= password.length; + + const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; + + return ( + +
+
+ Plane background pattern +
+
+
+
+ + Plane logo + +
+
+
+
+
+

+ Secure your account +

+

Setting password helps you login securely

+
+
handleSubmit(e)}> +
+ +
+ +
+
+
+ +
+ handleFormChange("password", e.target.value)} + //hasError={Boolean(errors.password)} + placeholder="Enter password" + className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" + minLength={8} + onFocus={() => setIsPasswordInputFocused(true)} + onBlur={() => setIsPasswordInputFocused(false)} + autoFocus + /> + {showPassword.password ? ( + handleShowPassword("password")} + /> + ) : ( + handleShowPassword("password")} + /> + )} +
+ {isPasswordInputFocused && } +
+
+ +
+ handleFormChange("confirm_password", e.target.value)} + placeholder="Confirm password" + className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" + onFocus={() => setIsRetryPasswordInputFocused(true)} + onBlur={() => setIsRetryPasswordInputFocused(false)} + /> + {showPassword.retypePassword ? ( + handleShowPassword("retypePassword")} + /> + ) : ( + handleShowPassword("retypePassword")} + /> + )} +
+ {!!passwordFormData.confirm_password && + passwordFormData.password !== passwordFormData.confirm_password && + renderPasswordMatchError && Passwords don{"'"}t match} +
+ +
+
+
+
+
+
+ ); +}); + +export default SetPasswordPage; diff --git a/web/app/create-workspace/layout.tsx b/web/app/create-workspace/layout.tsx new file mode 100644 index 000000000..32a220df7 --- /dev/null +++ b/web/app/create-workspace/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Create Workspace", +}; + +export default function CreateWorkspaceLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/pages/create-workspace.tsx b/web/app/create-workspace/page.tsx similarity index 84% rename from web/pages/create-workspace.tsx rename to web/app/create-workspace/page.tsx index e5992e008..f2f92a771 100644 --- a/web/pages/create-workspace.tsx +++ b/web/app/create-workspace/page.tsx @@ -1,26 +1,23 @@ -import { ReactElement, useState } from "react"; +"use client"; + +import { useState } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { useTheme } from "next-themes"; import { IWorkspace } from "@plane/types"; -// hooks -import { PageHead } from "@/components/core"; -import { CreateWorkspaceForm } from "@/components/workspace"; -import { useUser, useUserProfile } from "@/hooks/store"; -// layouts -import DefaultLayout from "@/layouts/default-layout"; // components -// images -import { NextPageWithLayout } from "@/lib/types"; +import { CreateWorkspaceForm } from "@/components/workspace"; +// hooks +import { useUser, useUserProfile } from "@/hooks/store"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; +// images import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png"; import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png"; -// types -const CreateWorkspacePage: NextPageWithLayout = observer(() => { +const CreateWorkspacePage = observer(() => { // router const router = useRouter(); // store hooks @@ -42,8 +39,7 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => { const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; return ( - <> - +
@@ -72,16 +68,8 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
- +
); }); -CreateWorkspacePage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - export default CreateWorkspacePage; diff --git a/web/app/error.tsx b/web/app/error.tsx new file mode 100644 index 000000000..515a107fd --- /dev/null +++ b/web/app/error.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useEffect } from "react"; +import * as Sentry from "@sentry/nextjs"; +// import { useRouter } from "next/navigation"; +// services +import { Button } from "@plane/ui"; +// helpers +// import { API_BASE_URL } from "@/helpers/common.helper"; +// layouts +import DefaultLayout from "@/layouts/default-layout"; +// +// import { AuthService } from "@/services/auth.service"; +// layouts +// ui + +// services +// const authService = new AuthService(); + +type props = { + error: Error & { digest?: string }; +}; + +const CustomErrorComponent = ({ error }: props) => { + // const router = useRouter(); + + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + const handleRefresh = () => { + window.location.reload(); + }; + + const handleSignOut = async () => { + // await authService + // .signOut(API_BASE_URL) + // .catch(() => + // setToast({ + // type: TOAST_TYPE.ERROR, + // title: "Error!", + // message: "Failed to sign out. Please try again.", + // }) + // ) + // .finally(() => router.push("/")); + }; + + return ( + +
+
+
+
+

Exception Detected!

+

+ We{"'"}re Sorry! An exception has been detected, and our engineering team has been notified. We + apologize for any inconvenience this may have caused. Please reach out to our engineering team at{" "} + + support@plane.so + {" "} + or on our{" "} + + Discord + {" "} + server for further assistance. +

+
+
+ + +
+
+
+
+
+ ); +}; + +export default CustomErrorComponent; diff --git a/web/app/installations/[provider]/layout.tsx b/web/app/installations/[provider]/layout.tsx new file mode 100644 index 000000000..51978de9e --- /dev/null +++ b/web/app/installations/[provider]/layout.tsx @@ -0,0 +1,3 @@ +export default function InstallationProviderLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/pages/installations/[provider]/index.tsx b/web/app/installations/[provider]/page.tsx similarity index 82% rename from web/pages/installations/[provider]/index.tsx rename to web/app/installations/[provider]/page.tsx index eb2e850c7..78e8f682d 100644 --- a/web/pages/installations/[provider]/index.tsx +++ b/web/app/installations/[provider]/page.tsx @@ -1,18 +1,23 @@ -import React, { useEffect, ReactElement } from "react"; -import { useRouter } from "next/router"; +"use client"; + +import React, { useEffect } from "react"; +import { useParams, useSearchParams } from "next/navigation"; // ui import { LogoSpinner } from "@/components/common"; -// types -import { NextPageWithLayout } from "@/lib/types"; // services import { AppInstallationService } from "@/services/app_installation.service"; // services const appInstallationService = new AppInstallationService(); -const AppPostInstallation: NextPageWithLayout = () => { - const router = useRouter(); - const { installation_id, state, provider, code } = router.query; +const AppPostInstallation = () => { + // params + const { provider } = useParams(); + // query params + const searchParams = useSearchParams(); + const installation_id = searchParams.get("installation_id"); + const state = searchParams.get("state"); + const code = searchParams.get("code"); useEffect(() => { if (provider === "github" && state && installation_id) { @@ -69,8 +74,4 @@ const AppPostInstallation: NextPageWithLayout = () => { ); }; -AppPostInstallation.getLayout = function getLayout(page: ReactElement) { - return
{page}
; -}; - export default AppPostInstallation; diff --git a/web/app/invitations/layout.tsx b/web/app/invitations/layout.tsx new file mode 100644 index 000000000..2d9a7e688 --- /dev/null +++ b/web/app/invitations/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Invitations", +}; + +export default function InvitationsLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/pages/invitations/index.tsx b/web/app/invitations/page.tsx similarity index 93% rename from web/pages/invitations/index.tsx rename to web/app/invitations/page.tsx index aac6c833b..c15b25d1a 100644 --- a/web/pages/invitations/index.tsx +++ b/web/app/invitations/page.tsx @@ -1,8 +1,10 @@ -import React, { useState, ReactElement } from "react"; +"use client"; + +import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import Image from "next/image"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { useTheme } from "next-themes"; import useSWR, { mutate } from "swr"; // icons @@ -13,7 +15,6 @@ import type { IWorkspaceMemberInvitation } from "@plane/types"; import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // components import { EmptyState } from "@/components/common"; -import { PageHead } from "@/components/core"; // constants import { MEMBER_ACCEPTED } from "@/constants/event-tracker"; import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys"; @@ -22,12 +23,8 @@ import { ROLE } from "@/constants/workspace"; import { truncateText } from "@/helpers/string.helper"; import { getUserRole } from "@/helpers/user.helper"; import { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/store"; -import DefaultLayout from "@/layouts/default-layout"; -// types -import { NextPageWithLayout } from "@/lib/types"; -// wrappers -import { AuthenticationWrapper } from "@/lib/wrappers"; // services +import { AuthenticationWrapper } from "@/lib/wrappers"; import { WorkspaceService } from "@/services/workspace.service"; // images import emptyInvitation from "public/empty-state/invitation.svg"; @@ -36,7 +33,7 @@ import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-l const workspaceService = new WorkspaceService(); -const UserInvitationsPage: NextPageWithLayout = observer(() => { +const UserInvitationsPage = observer(() => { // states const [invitationsRespond, setInvitationsRespond] = useState([]); const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); @@ -130,8 +127,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; return ( - <> - +
@@ -157,11 +153,10 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { return (
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")} >
@@ -230,16 +225,8 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => { ) ) : null}
- + ); }); -UserInvitationsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - export default UserInvitationsPage; diff --git a/web/app/layout.tsx b/web/app/layout.tsx new file mode 100644 index 000000000..49d160412 --- /dev/null +++ b/web/app/layout.tsx @@ -0,0 +1,61 @@ +import { Metadata } from "next"; +import Script from "next/script"; +// styles +import "@/styles/globals.css"; +import "@/styles/command-pallette.css"; +import "@/styles/nprogress.css"; +import "@/styles/emoji.css"; +import "@/styles/react-day-picker.css"; +// local +import { AppProvider } from "./provider"; + +export const metadata: Metadata = { + title: "Plane | Simple, extensible, open-source project management tool.", + description: + "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", + openGraph: { + title: "Plane | Simple, extensible, open-source project management tool.", + description: "Plane Deploy is a customer feedback management tool built on top of plane.so", + url: "https://app.plane.so/", + }, + keywords: + "software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration", + twitter: { + site: "@planepowers", + }, +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + const isSessionRecorderEnabled = parseInt(process.env.NEXT_PUBLIC_ENABLE_SESSION_RECORDER || "0"); + + return ( + + + + + + + + + + +
+ +
{children}
+
+ + {process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN && ( + + )} + + ); +} diff --git a/web/pages/404.tsx b/web/app/not-found.tsx similarity index 78% rename from web/pages/404.tsx rename to web/app/not-found.tsx index 514169dea..10040faab 100644 --- a/web/pages/404.tsx +++ b/web/app/not-found.tsx @@ -1,21 +1,20 @@ -import React from "react"; +"use client"; -import type { NextPage } from "next"; +import React from "react"; +import { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; -// components -import { Button } from "@plane/ui"; -import { PageHead } from "@/components/core"; -// layouts -import DefaultLayout from "@/layouts/default-layout"; // ui +import { Button } from "@plane/ui"; // images import Image404 from "public/404.svg"; -// types -const PageNotFound: NextPage = () => ( - - +export const metadata: Metadata = { + title: "404 - Page Not Found", +}; + +const PageNotFound = () => ( +
@@ -37,7 +36,7 @@ const PageNotFound: NextPage = () => (
- +
); export default PageNotFound; diff --git a/web/app/onboarding/layout.tsx b/web/app/onboarding/layout.tsx new file mode 100644 index 000000000..492ebc402 --- /dev/null +++ b/web/app/onboarding/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Onboarding", +}; + +export default function OnboardingLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/pages/onboarding/index.tsx b/web/app/onboarding/page.tsx similarity index 90% rename from web/pages/onboarding/index.tsx rename to web/app/onboarding/page.tsx index 89263e57d..0e95e0f47 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/app/onboarding/page.tsx @@ -1,24 +1,20 @@ -import { ReactElement, useEffect, useState } from "react"; +"use client"; + +import { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import useSWR from "swr"; // types +import { EPageTypes } from "@plane/constants"; import { TOnboardingSteps, TUserProfile } from "@plane/types"; // components import { LogoSpinner } from "@/components/common"; -import { PageHead } from "@/components/core"; import { InviteMembers, CreateOrJoinWorkspaces, ProfileSetup } from "@/components/onboarding"; // constants import { USER_ONBOARDING_COMPLETED } from "@/constants/event-tracker"; import { USER_WORKSPACES_LIST } from "@/constants/fetch-keys"; -// helpers -import { EPageTypes } from "@/helpers/authentication.helper"; // hooks import { useUser, useWorkspace, useUserProfile, useEventTracker } from "@/hooks/store"; -// layouts -import DefaultLayout from "@/layouts/default-layout"; -// lib types -import { NextPageWithLayout } from "@/lib/types"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; // services @@ -32,7 +28,7 @@ export enum EOnboardingSteps { const workspaceService = new WorkspaceService(); -const OnboardingPage: NextPageWithLayout = observer(() => { +const OnboardingPage = observer(() => { // states const [step, setStep] = useState(null); const [totalSteps, setTotalSteps] = useState(null); @@ -143,8 +139,7 @@ const OnboardingPage: NextPageWithLayout = observer(() => { }, [user, step, profile.onboarding_step, updateCurrentUser, workspacesList]); return ( - <> - + {user && totalSteps && step !== null && invitations ? (
{step === EOnboardingSteps.PROFILE_SETUP ? ( @@ -179,16 +174,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
)} - +
); }); -OnboardingPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - export default OnboardingPage; diff --git a/web/app/page.tsx b/web/app/page.tsx new file mode 100644 index 000000000..8c502a7db --- /dev/null +++ b/web/app/page.tsx @@ -0,0 +1,77 @@ +"use client"; + +import React from "react"; +import { observer } from "mobx-react"; +import Image from "next/image"; +import Link from "next/link"; +// ui +import { useTheme } from "next-themes"; +// components +import { AuthRoot } from "@/components/account"; +import { PageHead } from "@/components/core"; +// constants +import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker"; +// helpers +import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; +// hooks +import { useEventTracker } from "@/hooks/store"; +// layouts +import DefaultLayout from "@/layouts/default-layout"; +// wrappers +import { AuthenticationWrapper } from "@/lib/wrappers"; +// assets +import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; +import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; +import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png"; +import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png"; + +const HomePage = observer(() => { + const { resolvedTheme } = useTheme(); + // hooks + const { captureEvent } = useEventTracker(); + + const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; + + return ( + + + <> +
+ +
+ Plane background pattern +
+
+
+
+ + Plane logo + +
+
+ New to Plane?{" "} + captureEvent(NAVIGATE_TO_SIGNUP, {})} + className="font-semibold text-custom-primary-100 hover:underline" + > + Create an account + +
+
+
+ +
+
+
+ +
+
+ ); +}); + +export default HomePage; diff --git a/web/pages/profile/activity.tsx b/web/app/profile/activity/page.tsx similarity index 88% rename from web/pages/profile/activity.tsx rename to web/app/profile/activity/page.tsx index 1f724b4ff..c9f089e09 100644 --- a/web/pages/profile/activity.tsx +++ b/web/app/profile/activity/page.tsx @@ -1,4 +1,6 @@ -import { ReactElement, useState } from "react"; +"use client"; + +import { useState } from "react"; import { observer } from "mobx-react"; // ui import { Button } from "@plane/ui"; @@ -18,7 +20,7 @@ import { NextPageWithLayout } from "@/lib/types"; const PER_PAGE = 100; -const ProfileActivityPage: NextPageWithLayout = observer(() => { +const ProfileActivityPage = observer(() => { // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); @@ -59,7 +61,7 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => {
- toggleSidebar()} /> +

Activity

@@ -77,8 +79,4 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { ); }); -ProfileActivityPage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - export default ProfileActivityPage; diff --git a/web/pages/profile/preferences/theme.tsx b/web/app/profile/appearance/page.tsx similarity index 77% rename from web/pages/profile/preferences/theme.tsx rename to web/app/profile/appearance/page.tsx index f35fe72b8..e34fe8cdd 100644 --- a/web/pages/profile/preferences/theme.tsx +++ b/web/app/profile/appearance/page.tsx @@ -1,21 +1,19 @@ -import { useEffect, useState, ReactElement } from "react"; +"use client"; + +import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useTheme } from "next-themes"; // ui import { setPromiseToast } from "@plane/ui"; // components import { LogoSpinner } from "@/components/common"; -import { CustomThemeSelector, ThemeSwitch, PageHead } from "@/components/core"; +import { CustomThemeSelector, ThemeSwitch, PageHead, SidebarHamburgerToggle } from "@/components/core"; // constants import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes"; // hooks import { useUserProfile } from "@/hooks/store"; -// layouts -import { ProfilePreferenceSettingsLayout } from "@/layouts/settings-layout/profile/preferences"; -// type -import { NextPageWithLayout } from "@/lib/types"; -const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { +const ProfileAppearancePage = observer(() => { const { setTheme } = useTheme(); // states const [currentTheme, setCurrentTheme] = useState(null); @@ -53,8 +51,9 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { {userProfile ? (
-
-

Preferences

+
+ +

Appearance

@@ -76,8 +75,4 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { ); }); -ProfilePreferencesThemePage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default ProfilePreferencesThemePage; +export default ProfileAppearancePage; \ No newline at end of file diff --git a/web/layouts/settings-layout/profile/layout.tsx b/web/app/profile/layout.tsx similarity index 67% rename from web/layouts/settings-layout/profile/layout.tsx rename to web/app/profile/layout.tsx index e2d11155f..1f1b1dff4 100644 --- a/web/layouts/settings-layout/profile/layout.tsx +++ b/web/app/profile/layout.tsx @@ -1,18 +1,19 @@ -import { FC, ReactNode } from "react"; -// layout +"use client"; + +import { ReactNode } from "react"; +// components import { CommandPalette } from "@/components/command-palette"; -import { ProfileLayoutSidebar } from "@/layouts/settings-layout"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; -// components +// layout +import { ProfileLayoutSidebar } from "./sidebar"; -interface IProfileSettingsLayout { +type Props = { children: ReactNode; - header?: ReactNode; -} +}; -export const ProfileSettingsLayout: FC = (props) => { - const { children, header } = props; +export default function ProfileSettingsLayout(props: Props) { + const { children } = props; return ( <> @@ -21,11 +22,10 @@ export const ProfileSettingsLayout: FC = (props) => {
- {header}
{children}
); -}; +} diff --git a/web/pages/profile/preferences/email.tsx b/web/app/profile/notifications/page.tsx similarity index 51% rename from web/pages/profile/preferences/email.tsx rename to web/app/profile/notifications/page.tsx index 69dcb6ee6..8e8ff3f04 100644 --- a/web/pages/profile/preferences/email.tsx +++ b/web/app/profile/notifications/page.tsx @@ -1,21 +1,20 @@ -import { ReactElement } from "react"; +"use client"; + import useSWR from "swr"; // layouts -import { PageHead } from "@/components/core"; -import { EmailNotificationForm } from "@/components/profile/preferences"; +import { PageHead, SidebarHamburgerToggle } from "@/components/core"; +import { EmailNotificationForm } from "@/components/profile/notification"; import { EmailSettingsLoader } from "@/components/ui"; -import { ProfilePreferenceSettingsLayout } from "@/layouts/settings-layout/profile/preferences"; // ui // components // services -import { NextPageWithLayout } from "@/lib/types"; import { UserService } from "@/services/user.service"; // type // services const userService = new UserService(); -const ProfilePreferencesThemePage: NextPageWithLayout = () => { +const ProfileNotificationPage = () => { // fetching user email notification settings const { data, isLoading } = useSWR("CURRENT_USER_EMAIL_NOTIFICATION_SETTINGS", () => userService.currentUserEmailNotificationSettings() @@ -29,14 +28,19 @@ const ProfilePreferencesThemePage: NextPageWithLayout = () => { <>
+
+ +
+
Email notifications
+
+ Stay in the loop on Issues you are subscribed to. Enable this to get notified. +
+
+
); }; -ProfilePreferencesThemePage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default ProfilePreferencesThemePage; +export default ProfileNotificationPage; \ No newline at end of file diff --git a/web/pages/profile/index.tsx b/web/app/profile/page.tsx similarity index 97% rename from web/pages/profile/index.tsx rename to web/app/profile/page.tsx index a7415cf3e..ebc0dfaf7 100644 --- a/web/pages/profile/index.tsx +++ b/web/app/profile/page.tsx @@ -1,4 +1,6 @@ -import React, { useEffect, useState, ReactElement } from "react"; +"use client"; + +import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { ChevronDown, CircleUserRound } from "lucide-react"; @@ -20,11 +22,9 @@ import { SidebarHamburgerToggle } from "@/components/core/sidebar"; import { TIME_ZONES } from "@/constants/timezones"; import { USER_ROLES } from "@/constants/workspace"; // hooks -import { useAppTheme, useUser } from "@/hooks/store"; -import { ProfileSettingsLayout } from "@/layouts/settings-layout"; +import { useUser } from "@/hooks/store"; +// import { ProfileSettingsLayout } from "@/layouts/settings-layout"; // layouts -// lib types -import type { NextPageWithLayout } from "@/lib/types"; import { FileService } from "@/services/file.service"; // services // types @@ -42,7 +42,7 @@ const defaultValues: Partial = { const fileService = new FileService(); -const ProfileSettingsPage: NextPageWithLayout = observer(() => { +const ProfileSettingsPage = observer(() => { // states const [isLoading, setIsLoading] = useState(false); const [isRemoving, setIsRemoving] = useState(false); @@ -58,7 +58,6 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { } = useForm({ defaultValues }); // store hooks const { data: currentUser, updateCurrentUser } = useUser(); - const { toggleSidebar } = useAppTheme(); useEffect(() => { reset({ ...defaultValues, ...currentUser }); @@ -135,7 +134,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
- toggleSidebar()} /> +
{ ); }); -ProfileSettingsPage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +// ProfileSettingsPage.getLayout = function getLayout(page: ReactElement) { +// return {page}; +// }; export default ProfileSettingsPage; diff --git a/web/pages/profile/change-password.tsx b/web/app/profile/security/page.tsx similarity index 93% rename from web/pages/profile/change-password.tsx rename to web/app/profile/security/page.tsx index 2ddc02090..121ecb85f 100644 --- a/web/pages/profile/change-password.tsx +++ b/web/app/profile/security/page.tsx @@ -1,6 +1,8 @@ -import { ReactElement, useEffect, useState } from "react"; +"use client"; + +import { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; import { Eye, EyeOff } from "lucide-react"; // ui @@ -14,11 +16,7 @@ import { SidebarHamburgerToggle } from "@/components/core/sidebar"; import { authErrorHandler } from "@/helpers/authentication.helper"; import { getPasswordStrength } from "@/helpers/password.helper"; // hooks -import { useAppTheme, useUser } from "@/hooks/store"; -// layout -import { ProfileSettingsLayout } from "@/layouts/settings-layout"; -// types -import { NextPageWithLayout } from "@/lib/types"; +import { useUser } from "@/hooks/store"; // services import { AuthService } from "@/services/auth.service"; import { UserService } from "@/services/user.service"; @@ -44,7 +42,7 @@ const defaultShowPassword = { confirmPassword: false, }; -const ChangePasswordPage: NextPageWithLayout = observer(() => { +const SecurityPage = observer(() => { // states const [isPageLoading, setIsPageLoading] = useState(true); const [showPassword, setShowPassword] = useState(defaultShowPassword); @@ -52,8 +50,6 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => { const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); // router const router = useRouter(); - // store hooks - const { toggleSidebar } = useAppTheme(); const { data: currentUser } = useUser(); // use form const { @@ -136,7 +132,7 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
- toggleSidebar()} /> +
{ ); }); -ChangePasswordPage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default ChangePasswordPage; +export default SecurityPage; \ No newline at end of file diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/app/profile/sidebar.tsx similarity index 98% rename from web/layouts/settings-layout/profile/sidebar.tsx rename to web/app/profile/sidebar.tsx index 9e7fc95cc..f3f87f8e4 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/app/profile/sidebar.tsx @@ -1,7 +1,9 @@ +"use client"; + import { useEffect, useRef, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { usePathname } from "next/navigation"; // icons import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react"; // ui @@ -32,7 +34,7 @@ export const ProfileLayoutSidebar = observer(() => { // states const [isSigningOut, setIsSigningOut] = useState(false); // router - const router = useRouter(); + const pathname = usePathname(); // store hooks const { sidebarCollapsed, toggleSidebar } = useAppTheme(); const { data: currentUser, signOut } = useUser(); @@ -133,7 +135,7 @@ export const ProfileLayoutSidebar = observer(() => { >
import("@/lib/wrappers/store-wrapper"), { ssr: false }); +const PostHogProvider = dynamic(() => import("@/lib/posthog-provider"), { ssr: false }); +const CrispWrapper = dynamic(() => import("@/lib/wrappers/crisp-wrapper"), { ssr: false }); +// nprogress +NProgress.configure({ showSpinner: false }); +// Router.events.on("routeChangeStart", NProgress.start); +// Router.events.on("routeChangeError", NProgress.done); +// Router.events.on("routeChangeComplete", NProgress.done); + +export interface IAppProvider { + children: ReactNode; +} + +export const AppProvider: FC = (props) => { + const { children } = props; + // themes + const { resolvedTheme } = useTheme(); + return ( + <> + + + + + + + + {children} + + + + + + + + ); +}; diff --git a/web/app/sign-up/layout.tsx b/web/app/sign-up/layout.tsx new file mode 100644 index 000000000..f7f405c27 --- /dev/null +++ b/web/app/sign-up/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Sign up - Plane", +}; + +export default function SignUpLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/app/sign-up/page.tsx b/web/app/sign-up/page.tsx new file mode 100644 index 000000000..073277d35 --- /dev/null +++ b/web/app/sign-up/page.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { observer } from "mobx-react"; +import Image from "next/image"; +import Link from "next/link"; +// ui +import { useTheme } from "next-themes"; +// components +import { AuthRoot } from "@/components/account"; +// constants +import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker"; +// helpers +import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; +// hooks +import { useEventTracker } from "@/hooks/store"; +// assets +import { AuthenticationWrapper } from "@/lib/wrappers"; +import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; +import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; +import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png"; +import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png"; + +export type AuthType = "sign-in" | "sign-up"; + +const SignInPage = observer(() => { + // store hooks + const { captureEvent } = useEventTracker(); + // hooks + const { resolvedTheme } = useTheme(); + + const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; + + return ( + +
+
+ Plane background pattern +
+
+
+
+ + Plane logo + +
+
+ Already have an account?{" "} + captureEvent(NAVIGATE_TO_SIGNIN, {})} + className="font-semibold text-custom-primary-100 hover:underline" + > + Log in + +
+
+
+ +
+
+
+
+ ); +}); + +export default SignInPage; diff --git a/web/app/workspace-invitations/layout.tsx b/web/app/workspace-invitations/layout.tsx new file mode 100644 index 000000000..8361dddfa --- /dev/null +++ b/web/app/workspace-invitations/layout.tsx @@ -0,0 +1,9 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Workspace Invitations", +}; + +export default function WorkspaceInvitationsLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/web/app/workspace-invitations/page.tsx b/web/app/workspace-invitations/page.tsx new file mode 100644 index 000000000..2c958fdeb --- /dev/null +++ b/web/app/workspace-invitations/page.tsx @@ -0,0 +1,127 @@ +"use client"; + +import React from "react"; +import { observer } from "mobx-react"; +import { useRouter, useSearchParams } from "next/navigation"; +import useSWR from "swr"; +import { Boxes, Check, Share2, Star, User2, X } from "lucide-react"; +// components +import { LogoSpinner } from "@/components/common"; +import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space"; +// constants +import { WORKSPACE_INVITATION } from "@/constants/fetch-keys"; +// helpers +import { EPageTypes } from "@/helpers/authentication.helper"; +// hooks +import { useUser } from "@/hooks/store"; +// wrappers +import { AuthenticationWrapper } from "@/lib/wrappers"; +// services +import { WorkspaceService } from "@/services/workspace.service"; + +// service initialization +const workspaceService = new WorkspaceService(); + +const WorkspaceInvitationPage = observer(() => { + // router + const router = useRouter(); + // query params + const searchParams = useSearchParams(); + const invitation_id = searchParams.get("invitation_id"); + const email = searchParams.get("email"); + const slug = searchParams.get("slug"); + // store hooks + const { data: currentUser } = useUser(); + + const { data: invitationDetail, error } = useSWR( + invitation_id && slug && WORKSPACE_INVITATION(invitation_id.toString()), + invitation_id && slug + ? () => workspaceService.getWorkspaceInvitation(slug.toString(), invitation_id.toString()) + : null + ); + + const handleAccept = () => { + if (!invitationDetail) return; + workspaceService + .joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { + accepted: true, + email: invitationDetail.email, + }) + .then(() => { + if (email === currentUser?.email) { + router.push("/invitations"); + } else { + router.push(`/?${searchParams.toString()}`); + } + }) + .catch((err) => console.error(err)); + }; + + const handleReject = () => { + if (!invitationDetail) return; + workspaceService + .joinWorkspace(invitationDetail.workspace.slug, invitationDetail.id, { + accepted: false, + email: invitationDetail.email, + }) + .then(() => { + router.push("/"); + }) + .catch((err) => console.error(err)); + }; + + return ( + +
+ {invitationDetail && !invitationDetail.responded_at ? ( + error ? ( +
+

INVITATION NOT FOUND

+
+ ) : ( + + + + + ) + ) : error || invitationDetail?.responded_at ? ( + invitationDetail?.accepted ? ( + + + + ) : ( + + {!currentUser ? ( + + ) : ( + + )} + + + + ) + ) : ( +
+ +
+ )} +
+
+ ); +}); + +export default WorkspaceInvitationPage; diff --git a/web/components/account/auth-forms/auth-root.tsx b/web/components/account/auth-forms/auth-root.tsx index 66f32f9fd..9c89d488f 100644 --- a/web/components/account/auth-forms/auth-root.tsx +++ b/web/components/account/auth-forms/auth-root.tsx @@ -1,6 +1,6 @@ import React, { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useRouter, useSearchParams } from "next/navigation"; import { IEmailCheckData } from "@plane/types"; // components import { @@ -35,7 +35,12 @@ type TAuthRoot = { export const AuthRoot: FC = observer((props) => { //router const router = useRouter(); - const { email: emailParam, invitation_id, slug: workspaceSlug, error_code } = router.query; + const searchParams = useSearchParams(); + // query params + const emailParam = searchParams.get("email"); + const invitation_id = searchParams.get("invitation_id"); + const workspaceSlug = searchParams.get("slug"); + const error_code = searchParams.get("error_code"); // props const { authMode: currentAuthMode } = props; // states @@ -130,7 +135,7 @@ export const AuthRoot: FC = observer((props) => { setErrorInfo(undefined); setEmail(""); setAuthStep(EAuthSteps.EMAIL); - router.push(currentAuthMode === EAuthModes.SIGN_IN ? `/` : "/sign-up", undefined, { shallow: true }); + router.push(currentAuthMode === EAuthModes.SIGN_IN ? `/` : "/sign-up"); }; // generating the unique code diff --git a/web/components/account/deactivate-account-modal.tsx b/web/components/account/deactivate-account-modal.tsx index ee5100e36..c2c6f20b1 100644 --- a/web/components/account/deactivate-account-modal.tsx +++ b/web/components/account/deactivate-account-modal.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { Trash2 } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; // hooks diff --git a/web/components/account/oauth/github-button.tsx b/web/components/account/oauth/github-button.tsx index 6d274c125..02b5f55bd 100644 --- a/web/components/account/oauth/github-button.tsx +++ b/web/components/account/oauth/github-button.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { useRouter } from "next/router"; +import { useSearchParams } from "next/navigation"; import Image from "next/image"; import { useTheme } from "next-themes"; // helpers @@ -13,8 +13,8 @@ export type GithubOAuthButtonProps = { }; export const GithubOAuthButton: FC = (props) => { - const { query } = useRouter(); - const { next_path } = query; + const searchParams = useSearchParams(); + const next_path = searchParams.get("next_path"); const { text } = props; // hooks const { resolvedTheme } = useTheme(); diff --git a/web/components/account/oauth/google-button.tsx b/web/components/account/oauth/google-button.tsx index 279c5ea42..c125589ac 100644 --- a/web/components/account/oauth/google-button.tsx +++ b/web/components/account/oauth/google-button.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { useRouter } from "next/router"; +import { useSearchParams } from "next/navigation"; import Image from "next/image"; import { useTheme } from "next-themes"; // helpers @@ -12,8 +12,8 @@ export type GoogleOAuthButtonProps = { }; export const GoogleOAuthButton: FC = (props) => { - const { query } = useRouter(); - const { next_path } = query; + const searchParams = useSearchParams(); + const next_path = searchParams.get("next_path"); const { text } = props; // hooks const { resolvedTheme } = useTheme(); diff --git a/web/components/analytics/custom-analytics/custom-analytics.tsx b/web/components/analytics/custom-analytics/custom-analytics.tsx index e5243cff5..413126c48 100644 --- a/web/components/analytics/custom-analytics/custom-analytics.tsx +++ b/web/components/analytics/custom-analytics/custom-analytics.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { useForm } from "react-hook-form"; import useSWR from "swr"; import { IAnalyticsParams } from "@plane/types"; @@ -30,8 +30,7 @@ const analyticsService = new AnalyticsService(); export const CustomAnalytics: React.FC = observer((props) => { const { additionalParams, fullScreen } = props; - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); const { control, watch, setValue } = useForm({ defaultValues }); diff --git a/web/components/analytics/custom-analytics/main-content.tsx b/web/components/analytics/custom-analytics/main-content.tsx index f57edbefd..b83f9cce2 100644 --- a/web/components/analytics/custom-analytics/main-content.tsx +++ b/web/components/analytics/custom-analytics/main-content.tsx @@ -1,4 +1,4 @@ -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { mutate } from "swr"; import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types"; @@ -22,8 +22,7 @@ type Props = { export const CustomAnalyticsMainContent: React.FC = (props) => { const { analytics, error, fullScreen, params } = props; - const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); const yAxisKey = params.y_axis === "issue_count" ? "count" : "estimate"; const barGraphData = convertResponseToBarGraphData(analytics?.distribution, params); diff --git a/web/components/analytics/custom-analytics/select/segment.tsx b/web/components/analytics/custom-analytics/select/segment.tsx index f9dedc965..842bcb623 100644 --- a/web/components/analytics/custom-analytics/select/segment.tsx +++ b/web/components/analytics/custom-analytics/select/segment.tsx @@ -1,4 +1,4 @@ -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { IAnalyticsParams, TXAxisValues } from "@plane/types"; // ui @@ -12,8 +12,7 @@ type Props = { }; export const SelectSegment: React.FC = ({ value, onChange, params, analyticsOptions }) => { - const router = useRouter(); - const { cycleId, moduleId } = router.query; + const { cycleId, moduleId } = useParams(); return ( = (props) => { const { value, onChange, params, analyticsOptions } = props; - const router = useRouter(); - const { cycleId, moduleId } = router.query; + const { cycleId, moduleId } = useParams(); return ( { - const router = useRouter(); - const { projectId, cycleId, moduleId } = router.query; + const { projectId, cycleId, moduleId } = useParams(); const { getProjectById } = useProject(); const { getCycleById } = useCycle(); diff --git a/web/components/analytics/custom-analytics/sidebar/sidebar.tsx b/web/components/analytics/custom-analytics/sidebar/sidebar.tsx index 93c91343c..876074b85 100644 --- a/web/components/analytics/custom-analytics/sidebar/sidebar.tsx +++ b/web/components/analytics/custom-analytics/sidebar/sidebar.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { mutate } from "swr"; // icons import { CalendarDays, Download, RefreshCw } from "lucide-react"; @@ -31,8 +31,7 @@ const analyticsService = new AnalyticsService(); export const CustomAnalyticsSidebar: React.FC = observer((props) => { const { analytics, params, isProjectLevel = false } = props; // router - const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); // store hooks const { data: currentUser } = useUser(); const { workspaceProjectIds, getProjectById } = useProject(); diff --git a/web/components/analytics/scope-and-demand/scope-and-demand.tsx b/web/components/analytics/scope-and-demand/scope-and-demand.tsx index ede3a8e25..047553804 100644 --- a/web/components/analytics/scope-and-demand/scope-and-demand.tsx +++ b/web/components/analytics/scope-and-demand/scope-and-demand.tsx @@ -1,4 +1,4 @@ -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import useSWR from "swr"; @@ -21,8 +21,7 @@ const analyticsService = new AnalyticsService(); export const ScopeAndDemand: React.FC = (props) => { const { fullScreen = true } = props; - const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); const isProjectLevel = projectId ? true : false; diff --git a/web/components/api-token/delete-token-modal.tsx b/web/components/api-token/delete-token-modal.tsx index 6918df463..2075e2e6e 100644 --- a/web/components/api-token/delete-token-modal.tsx +++ b/web/components/api-token/delete-token-modal.tsx @@ -1,5 +1,5 @@ import { useState, FC } from "react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { mutate } from "swr"; // types import { IApiToken } from "@plane/types"; @@ -24,9 +24,8 @@ export const DeleteApiTokenModal: FC = (props) => { const { isOpen, onClose, tokenId } = props; // states const [deleteLoading, setDeleteLoading] = useState(false); - // router - const router = useRouter(); - const { workspaceSlug } = router.query; + // router params + const { workspaceSlug } = useParams(); const handleClose = () => { onClose(); diff --git a/web/components/api-token/modal/create-token-modal.tsx b/web/components/api-token/modal/create-token-modal.tsx index f71b5fcdd..1b5bf63ca 100644 --- a/web/components/api-token/modal/create-token-modal.tsx +++ b/web/components/api-token/modal/create-token-modal.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { mutate } from "swr"; // types import { IApiToken } from "@plane/types"; @@ -30,8 +30,7 @@ export const CreateApiTokenModal: React.FC = (props) => { const [neverExpires, setNeverExpires] = useState(false); const [generatedToken, setGeneratedToken] = useState(null); // router - const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); const handleClose = () => { onClose(); diff --git a/web/components/archives/archive-tabs-list.tsx b/web/components/archives/archive-tabs-list.tsx index 57d1c36a1..8088d745b 100644 --- a/web/components/archives/archive-tabs-list.tsx +++ b/web/components/archives/archive-tabs-list.tsx @@ -1,7 +1,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams, usePathname } from "next/navigation"; // constants import { ARCHIVES_TAB_LIST } from "@/constants/archives"; // hooks @@ -9,9 +9,9 @@ import { useProject } from "@/hooks/store"; export const ArchiveTabsList: FC = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - const activeTab = router.pathname.split("/").pop(); + const { workspaceSlug, projectId } = useParams(); + const pathname = usePathname(); + const activeTab = pathname.split("/").pop(); // store hooks const { getProjectById } = useProject(); diff --git a/web/components/auth-screens/not-authorized-view.tsx b/web/components/auth-screens/not-authorized-view.tsx index 68d3def3b..10a04f81d 100644 --- a/web/components/auth-screens/not-authorized-view.tsx +++ b/web/components/auth-screens/not-authorized-view.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useSearchParams } from "next/navigation"; // hooks import { useUser } from "@/hooks/store"; // layouts @@ -19,8 +19,8 @@ type Props = { export const NotAuthorizedView: React.FC = observer((props) => { const { actionButton, type } = props; // router - const { query } = useRouter(); - const { next_path } = query; + const searchParams = useSearchParams(); + const next_path = searchParams.get("next_path"); // hooks const { data: currentUser } = useUser(); diff --git a/web/components/auth-screens/project/join-project.tsx b/web/components/auth-screens/project/join-project.tsx index 0c5d4740a..8065c0708 100644 --- a/web/components/auth-screens/project/join-project.tsx +++ b/web/components/auth-screens/project/join-project.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import Image from "next/image"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // hooks import { ClipboardList } from "lucide-react"; import { Button } from "@plane/ui"; @@ -19,8 +19,7 @@ export const JoinProject: React.FC = () => { } = useUser(); const { fetchProjects } = useProject(); - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); const handleJoin = () => { if (!workspaceSlug || !projectId) return; diff --git a/web/components/automation/select-month-modal.tsx b/web/components/automation/select-month-modal.tsx index 7b068fa41..fc40cc31a 100644 --- a/web/components/automation/select-month-modal.tsx +++ b/web/components/automation/select-month-modal.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // headless ui @@ -19,8 +19,7 @@ type Props = { }; export const SelectMonthModal: React.FC = ({ type, initialValues, isOpen, handleClose, handleChange }) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); const { formState: { errors, isSubmitting }, diff --git a/web/components/command-palette/actions/issue-actions/actions-list.tsx b/web/components/command-palette/actions/issue-actions/actions-list.tsx index 040eb2e3c..6c1c01aca 100644 --- a/web/components/command-palette/actions/issue-actions/actions-list.tsx +++ b/web/components/command-palette/actions/issue-actions/actions-list.tsx @@ -1,6 +1,8 @@ +"use client"; + import { Command } from "cmdk"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2, Users } from "lucide-react"; import { TIssue } from "@plane/types"; // hooks @@ -24,8 +26,7 @@ type Props = { export const CommandPaletteIssueActions: React.FC = observer((props) => { const { closePalette, issueDetails, pages, setPages, setPlaceholder, setSearchTerm } = props; // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId, issueId } = useParams(); // hooks const { issues: { updateIssue }, @@ -60,7 +61,7 @@ export const CommandPaletteIssueActions: React.FC = observer((props) => { }; const copyIssueUrlToClipboard = () => { - if (!router.query.issueId) return; + if (!issueId) return; const url = new URL(window.location.href); copyTextToClipboard(url.href) diff --git a/web/components/command-palette/actions/issue-actions/change-assignee.tsx b/web/components/command-palette/actions/issue-actions/change-assignee.tsx index aee7eceaf..30ef22aaf 100644 --- a/web/components/command-palette/actions/issue-actions/change-assignee.tsx +++ b/web/components/command-palette/actions/issue-actions/change-assignee.tsx @@ -1,6 +1,8 @@ +"use client"; + import { Command } from "cmdk"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { Check } from "lucide-react"; import { TIssue } from "@plane/types"; // mobx store @@ -17,9 +19,8 @@ type Props = { export const ChangeIssueAssignee: React.FC = observer((props) => { const { closePalette, issue } = props; - // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + // router params + const { workspaceSlug, projectId } = useParams(); // store const { issues: { updateIssue }, diff --git a/web/components/command-palette/actions/issue-actions/change-priority.tsx b/web/components/command-palette/actions/issue-actions/change-priority.tsx index c154cc44c..5bd8ce850 100644 --- a/web/components/command-palette/actions/issue-actions/change-priority.tsx +++ b/web/components/command-palette/actions/issue-actions/change-priority.tsx @@ -1,6 +1,8 @@ +"use client"; `` + import { Command } from "cmdk"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { Check } from "lucide-react"; import { TIssue, TIssuePriorities } from "@plane/types"; // mobx store @@ -18,10 +20,8 @@ type Props = { export const ChangeIssuePriority: React.FC = observer((props) => { const { closePalette, issue } = props; - - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - + // router params + const { workspaceSlug, projectId } = useParams(); const { issues: { updateIssue }, } = useIssues(EIssuesStoreType.PROJECT); diff --git a/web/components/command-palette/actions/issue-actions/change-state.tsx b/web/components/command-palette/actions/issue-actions/change-state.tsx index fce081dfb..5d512f4ac 100644 --- a/web/components/command-palette/actions/issue-actions/change-state.tsx +++ b/web/components/command-palette/actions/issue-actions/change-state.tsx @@ -1,6 +1,8 @@ +"use client"; + import { Command } from "cmdk"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // hooks import { Check } from "lucide-react"; import { TIssue } from "@plane/types"; @@ -18,9 +20,8 @@ type Props = { export const ChangeIssueState: React.FC = observer((props) => { const { closePalette, issue } = props; - // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + // router params + const { workspaceSlug, projectId } = useParams(); // store hooks const { issues: { updateIssue }, diff --git a/web/components/command-palette/actions/search-results.tsx b/web/components/command-palette/actions/search-results.tsx index 489794295..6d779d654 100644 --- a/web/components/command-palette/actions/search-results.tsx +++ b/web/components/command-palette/actions/search-results.tsx @@ -1,5 +1,7 @@ +"use client"; + import { Command } from "cmdk"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { IWorkspaceSearchResults } from "@plane/types"; // helpers import { commandGroups } from "@/components/command-palette"; diff --git a/web/components/command-palette/actions/theme-actions.tsx b/web/components/command-palette/actions/theme-actions.tsx index 7b0ddc72b..254dda42c 100644 --- a/web/components/command-palette/actions/theme-actions.tsx +++ b/web/components/command-palette/actions/theme-actions.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { FC, useEffect, useState } from "react"; import { Command } from "cmdk"; import { observer } from "mobx-react"; diff --git a/web/components/command-palette/actions/workspace-settings-actions.tsx b/web/components/command-palette/actions/workspace-settings-actions.tsx index 56c118a51..98a0a720b 100644 --- a/web/components/command-palette/actions/workspace-settings-actions.tsx +++ b/web/components/command-palette/actions/workspace-settings-actions.tsx @@ -1,7 +1,9 @@ +"use client"; + import { Command } from "cmdk"; // hooks import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; // constants import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "@/constants/workspace"; import { useUser } from "@/hooks/store"; @@ -14,7 +16,8 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC = (props) = const { closePalette } = props; // router const router = useRouter(); - const { workspaceSlug } = router.query; + // router params + const { workspaceSlug } = useParams(); // mobx store const { membership: { currentWorkspaceRole }, diff --git a/web/components/command-palette/command-modal.tsx b/web/components/command-palette/command-modal.tsx index 6c05c9b17..e00af3d30 100644 --- a/web/components/command-palette/command-modal.tsx +++ b/web/components/command-palette/command-modal.tsx @@ -1,7 +1,9 @@ +"use client"; + import React, { useEffect, useState } from "react"; import { Command } from "cmdk"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, useRouter } from "next/navigation"; import useSWR from "swr"; import { FolderPlus, Search, Settings } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; @@ -67,7 +69,8 @@ export const CommandModal: React.FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; + // router params + const { workspaceSlug, projectId, issueId } = useParams(); const page = pages[pages.length - 1]; diff --git a/web/components/command-palette/command-palette.tsx b/web/components/command-palette/command-palette.tsx index 41afbb5e2..4fe2e4438 100644 --- a/web/components/command-palette/command-palette.tsx +++ b/web/components/command-palette/command-palette.tsx @@ -1,6 +1,8 @@ +"use client"; + import React, { useCallback, useEffect, FC, useMemo } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, usePathname, useRouter } from "next/navigation"; import useSWR from "swr"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; @@ -32,7 +34,10 @@ const issueService = new IssueService(); export const CommandPalette: FC = observer(() => { // router const router = useRouter(); - const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query; + // router params + const { workspaceSlug, projectId, issueId, cycleId, moduleId } = useParams(); + // pathname + const pathname = usePathname(); // store hooks const { toggleSidebar } = useAppTheme(); const { setTrackElement } = useEventTracker(); @@ -260,7 +265,7 @@ export const CommandPalette: FC = observer(() => { return () => document.removeEventListener("keydown", handleKeyDown); }, [handleKeyDown]); - const isDraftIssue = router?.asPath?.includes("draft-issues") || false; + const isDraftIssue = pathname?.includes("draft-issues") || false; if (!currentUser) return null; diff --git a/web/components/command-palette/shortcuts-modal/modal.tsx b/web/components/command-palette/shortcuts-modal/modal.tsx index bbaa55464..f4076db4c 100644 --- a/web/components/command-palette/shortcuts-modal/modal.tsx +++ b/web/components/command-palette/shortcuts-modal/modal.tsx @@ -1,3 +1,5 @@ +"use client"; + import { FC, useState, Fragment } from "react"; import { Search, X } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 5def2d7a9..27ac7bcea 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // store hooks // icons import { @@ -29,8 +29,8 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; // types export const IssueLink = ({ activity }: { activity: IIssueActivity }) => { - const router = useRouter(); - const { workspaceSlug } = router.query; + // router params + const { workspaceSlug } = useParams(); const { isMobile } = usePlatformOS(); return ( @@ -61,8 +61,8 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => { }; const UserLink = ({ activity }: { activity: IIssueActivity }) => { - const router = useRouter(); - const { workspaceSlug } = router.query; + // router params + const { workspaceSlug } = useParams(); return ( { - const router = useRouter(); - const { workspaceSlug } = router.query; + // router params + const { workspaceSlug } = useParams(); return ( <> diff --git a/web/components/core/image-picker-popover.tsx b/web/components/core/image-picker-popover.tsx index 4a080fb70..c4e692f73 100644 --- a/web/components/core/image-picker-popover.tsx +++ b/web/components/core/image-picker-popover.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useRef, useCallback } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { useDropzone } from "react-dropzone"; import { Control, Controller } from "react-hook-form"; import useSWR from "swr"; @@ -58,9 +58,8 @@ export const ImagePickerPopover: React.FC = observer((props) => { }); // refs const ref = useRef(null); - // router - const router = useRouter(); - const { workspaceSlug } = router.query; + // router params + const { workspaceSlug } = useParams(); // store hooks const { config } = useInstance(); const { currentWorkspace } = useWorkspace(); diff --git a/web/components/core/list/list-item.tsx b/web/components/core/list/list-item.tsx index 8527d56b5..fba22bc91 100644 --- a/web/components/core/list/list-item.tsx +++ b/web/components/core/list/list-item.tsx @@ -1,5 +1,5 @@ import React, { FC } from "react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; // ui import { ControlLink, Tooltip } from "@plane/ui"; // helpers diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index 22d9401cf..dd1b35307 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { SubmitHandler, useForm } from "react-hook-form"; import useSWR from "swr"; import { Search } from "lucide-react"; @@ -36,9 +36,8 @@ const issueService = new IssueService(); export const BulkDeleteIssuesModal: React.FC = observer((props) => { const { isOpen, onClose } = props; - // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + // router params + const { workspaceSlug, projectId } = useParams(); // hooks const { getProjectById } = useProject(); const { diff --git a/web/components/core/modals/gpt-assistant-popover.tsx b/web/components/core/modals/gpt-assistant-popover.tsx index 0c9c30b31..3f4680172 100644 --- a/web/components/core/modals/gpt-assistant-popover.tsx +++ b/web/components/core/modals/gpt-assistant-popover.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef, Fragment, Ref } from "react"; import { Placement } from "@popperjs/core"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; // services import { usePopper } from "react-popper"; // ui @@ -42,8 +42,7 @@ export const GptAssistantPopover: React.FC = (props) => { const editorRef = useRef(null); const responseRef = useRef(null); // router - const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); // popper const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: placement ?? "auto", diff --git a/web/components/core/modals/workspace-image-upload-modal.tsx b/web/components/core/modals/workspace-image-upload-modal.tsx index 878217780..c9aef7f01 100644 --- a/web/components/core/modals/workspace-image-upload-modal.tsx +++ b/web/components/core/modals/workspace-image-upload-modal.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, usePathname } from "next/navigation"; import { useDropzone } from "react-dropzone"; import { UserCircle2 } from "lucide-react"; import { Transition, Dialog } from "@headlessui/react"; @@ -31,8 +31,8 @@ export const WorkspaceImageUploadModal: React.FC = observer((props) => { const [image, setImage] = useState(null); const [isImageUploading, setIsImageUploading] = useState(false); // router - const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); + const pathname = usePathname(); // store hooks const { config } = useInstance(); const { currentWorkspace } = useWorkspace(); @@ -55,7 +55,7 @@ export const WorkspaceImageUploadModal: React.FC = observer((props) => { }; const handleSubmit = async () => { - if (!image || (!workspaceSlug && router.pathname !== "/onboarding")) return; + if (!image || (!workspaceSlug && pathname !== "/onboarding")) return; setIsImageUploading(true); diff --git a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx index 33370378e..fb847ec17 100644 --- a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx +++ b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx @@ -1,24 +1,18 @@ +"use client"; + import { FC } from "react"; import { observer } from "mobx-react"; import { Menu } from "lucide-react"; import { useAppTheme } from "@/hooks/store"; -type Props = { - onClick?: () => void; -}; - -export const SidebarHamburgerToggle: FC = observer((props) => { - const { onClick } = props; +export const SidebarHamburgerToggle: FC = observer(() => { // store hooks const { toggleSidebar } = useAppTheme(); return (
{ - if (onClick) onClick(); - else toggleSidebar(); - }} + onClick={() => toggleSidebar()} >
diff --git a/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx b/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx index a66af73c3..89d154cd2 100644 --- a/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx +++ b/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx @@ -1,7 +1,7 @@ import { useRef } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { Users } from "lucide-react"; // ui import { Avatar, AvatarGroup, setPromiseToast } from "@plane/ui"; @@ -24,8 +24,7 @@ export const UpcomingCycleListItem: React.FC = observer((props) => { // refs const parentRef = useRef(null); // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); // store hooks const { captureEvent } = useEventTracker(); const { addCycleToFavorites, getCycleById, removeCycleFromFavorites } = useCycle(); diff --git a/web/components/cycles/archived-cycles/header.tsx b/web/components/cycles/archived-cycles/header.tsx index e45b50baa..950128848 100644 --- a/web/components/cycles/archived-cycles/header.tsx +++ b/web/components/cycles/archived-cycles/header.tsx @@ -1,6 +1,6 @@ import { FC, useCallback, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; // icons import { ListFilter, Search, X } from "lucide-react"; // types @@ -18,8 +18,7 @@ import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; export const ArchivedCyclesHeader: FC = observer(() => { // router - const router = useRouter(); - const { projectId } = router.query; + const { projectId } = useParams(); // refs const inputRef = useRef(null); // hooks diff --git a/web/components/cycles/archived-cycles/modal.tsx b/web/components/cycles/archived-cycles/modal.tsx index b613d0de9..d4c2b0930 100644 --- a/web/components/cycles/archived-cycles/modal.tsx +++ b/web/components/cycles/archived-cycles/modal.tsx @@ -1,5 +1,5 @@ import { useState, Fragment } from "react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { Dialog, Transition } from "@headlessui/react"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; diff --git a/web/components/cycles/archived-cycles/root.tsx b/web/components/cycles/archived-cycles/root.tsx index 4d47c8f34..bb175fe0e 100644 --- a/web/components/cycles/archived-cycles/root.tsx +++ b/web/components/cycles/archived-cycles/root.tsx @@ -1,6 +1,6 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import useSWR from "swr"; // types import { TCycleFilters } from "@plane/types"; @@ -17,8 +17,7 @@ import { useCycle, useCycleFilter } from "@/hooks/store"; export const ArchivedCycleLayoutRoot: React.FC = observer(() => { // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = useParams(); // hooks const { fetchArchivedCycles, currentProjectArchivedCycleIds, loader } = useCycle(); // cycle filters hook diff --git a/web/components/cycles/board/cycles-board-card.tsx b/web/components/cycles/board/cycles-board-card.tsx index 641007798..700c6a882 100644 --- a/web/components/cycles/board/cycles-board-card.tsx +++ b/web/components/cycles/board/cycles-board-card.tsx @@ -1,7 +1,7 @@ import { FC, MouseEvent, useRef } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { CalendarCheck2, CalendarClock, Info, MoveRight } from "lucide-react"; // types import type { TCycleGroups } from "@plane/types"; @@ -16,6 +16,7 @@ import { CYCLE_FAVORITED, CYCLE_UNFAVORITED } from "@/constants/event-tracker"; import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers import { findHowManyDaysLeft, getDate, renderFormattedDate } from "@/helpers/date-time.helper"; +import { generateQueryParams } from "@/helpers/router.helper"; // hooks import { useEventTracker, useCycle, useUser, useMember } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -32,6 +33,8 @@ export const CyclesBoardCard: FC = observer((props) => { const parentRef = useRef(null); // router const router = useRouter(); + const searchParams = useSearchParams(); + const pathname = usePathname(); // store const { captureEvent } = useEventTracker(); const { @@ -130,21 +133,14 @@ export const CyclesBoardCard: FC = observer((props) => { }; const openCycleOverview = (e: MouseEvent) => { - const { query } = router; e.preventDefault(); e.stopPropagation(); - if (query.peekCycle) { - delete query.peekCycle; - router.push({ - pathname: router.pathname, - query: { ...query }, - }); + const query = generateQueryParams(searchParams, ['peekCycle']); + if (searchParams.has('peekCycle')) { + router.push(`${pathname}?${query}`); } else { - router.push({ - pathname: router.pathname, - query: { ...query, peekCycle: cycleId }, - }); + router.push(`${pathname}?${query}&peekCycle=${cycleId}`); } }; diff --git a/web/components/cycles/cycle-peek-overview.tsx b/web/components/cycles/cycle-peek-overview.tsx index 316f4538c..3a5c1d9b0 100644 --- a/web/components/cycles/cycle-peek-overview.tsx +++ b/web/components/cycles/cycle-peek-overview.tsx @@ -1,7 +1,8 @@ import React, { useEffect } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; // hooks +import { generateQueryParams } from "@/helpers/router.helper"; import { useCycle } from "@/hooks/store"; // components import { CycleDetailsSidebar } from "./sidebar"; @@ -15,18 +16,17 @@ type Props = { export const CyclePeekOverview: React.FC = observer(({ projectId, workspaceSlug, isArchived = false }) => { // router const router = useRouter(); - const { peekCycle } = router.query; + const pathname = usePathname(); + const searchParams = useSearchParams(); + const peekCycle = searchParams.get("peekCycle"); // refs const ref = React.useRef(null); // store hooks const { fetchCycleDetails, fetchArchivedCycleDetails } = useCycle(); const handleClose = () => { - delete router.query.peekCycle; - router.push({ - pathname: router.pathname, - query: { ...router.query }, - }); + const query = generateQueryParams(searchParams, ['peekCycle']); + router.push(`${pathname}?${query}`); }; useEffect(() => { diff --git a/web/components/cycles/delete-modal.tsx b/web/components/cycles/delete-modal.tsx index 9a6578f49..93407a236 100644 --- a/web/components/cycles/delete-modal.tsx +++ b/web/components/cycles/delete-modal.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; // types import { ICycle } from "@plane/types"; // ui @@ -29,7 +29,9 @@ export const CycleDeleteModal: React.FC = observer((props) => { const { deleteCycle } = useCycle(); // router const router = useRouter(); - const { cycleId, peekCycle } = router.query; + const { cycleId } = useParams(); + const searchParams = useSearchParams(); + const peekCycle = searchParams.get("peekCycle"); const formSubmit = async () => { if (!cycle) return; diff --git a/web/components/cycles/gantt-chart/blocks.tsx b/web/components/cycles/gantt-chart/blocks.tsx index 68e25a43c..d0c7be800 100644 --- a/web/components/cycles/gantt-chart/blocks.tsx +++ b/web/components/cycles/gantt-chart/blocks.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; // hooks // ui import { Tooltip, ContrastIcon } from "@plane/ui"; diff --git a/web/components/cycles/gantt-chart/cycles-list-layout.tsx b/web/components/cycles/gantt-chart/cycles-list-layout.tsx index 1b0e0ab82..aa0aeea41 100644 --- a/web/components/cycles/gantt-chart/cycles-list-layout.tsx +++ b/web/components/cycles/gantt-chart/cycles-list-layout.tsx @@ -1,6 +1,6 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { ICycle } from "@plane/types"; // hooks import { CycleGanttBlock } from "@/components/cycles"; @@ -19,8 +19,7 @@ type Props = { export const CyclesListGanttChartView: FC = observer((props) => { const { cycleIds } = props; // router - const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug } = useParams(); // store hooks const { getCycleById, updateCycleDetails } = useCycle(); diff --git a/web/components/cycles/list/cycles-list-item.tsx b/web/components/cycles/list/cycles-list-item.tsx index 414c8081a..78a1cfb7b 100644 --- a/web/components/cycles/list/cycles-list-item.tsx +++ b/web/components/cycles/list/cycles-list-item.tsx @@ -1,6 +1,6 @@ import { FC, MouseEvent, useRef } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; // icons import { Check, Info } from "lucide-react"; // types @@ -10,6 +10,8 @@ import { CircularProgressIndicator } from "@plane/ui"; // components import { ListItem } from "@/components/core/list"; import { CycleListItemAction } from "@/components/cycles/list"; +// helpers +import { generateQueryParams } from "@/helpers/router.helper"; // hooks import { useCycle } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -31,6 +33,8 @@ export const CyclesListItem: FC = observer((props) => { const parentRef = useRef(null); // router const router = useRouter(); + const searchParams = useSearchParams(); + const pathname = usePathname(); // hooks const { isMobile } = usePlatformOS(); // store hooks @@ -59,21 +63,14 @@ export const CyclesListItem: FC = observer((props) => { // handlers const openCycleOverview = (e: MouseEvent) => { - const { query } = router; e.preventDefault(); e.stopPropagation(); - if (query.peekCycle) { - delete query.peekCycle; - router.push({ - pathname: router.pathname, - query: { ...query }, - }); + const query = generateQueryParams(searchParams, ["peekCycle"]); + if (searchParams.has("peekCycle")) { + router.push(`${pathname}?${query}`); } else { - router.push({ - pathname: router.pathname, - query: { ...query, peekCycle: cycleId }, - }); + router.push(`${pathname}?${query}&peekCycle=${cycleId}`); } }; diff --git a/web/components/cycles/quick-actions.tsx b/web/components/cycles/quick-actions.tsx index 9c130ef7a..084395956 100644 --- a/web/components/cycles/quick-actions.tsx +++ b/web/components/cycles/quick-actions.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; // icons import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react"; // ui diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index 595fe9b7a..cc40b8157 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from "react"; import isEmpty from "lodash/isEmpty"; import isEqual from "lodash/isEqual"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; // icons import { @@ -60,7 +60,9 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const [cycleDeleteModal, setCycleDeleteModal] = useState(false); // router const router = useRouter(); - const { workspaceSlug, projectId, peekCycle } = router.query; + const { workspaceSlug, projectId } = useParams(); + const searchParams = useSearchParams(); + const peekCycle = searchParams.get("peekCycle"); // store hooks const { setTrackElement, captureCycleEvent } = useEventTracker(); const { diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index 399774faa..5d547bcfd 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import { AlertCircle, Search, X } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; // hooks @@ -28,8 +28,7 @@ export const TransferIssuesModal: React.FC = observer((props) => { issues: { transferIssuesFromCycle }, } = useIssues(EIssuesStoreType.CYCLE); - const router = useRouter(); - const { workspaceSlug, projectId, cycleId } = router.query; + const { workspaceSlug, projectId, cycleId } = useParams(); const transferIssue = async (payload: any) => { if (!workspaceSlug || !projectId || !cycleId) return; diff --git a/web/components/cycles/transfer-issues.tsx b/web/components/cycles/transfer-issues.tsx index d7a5a2178..881b00175 100644 --- a/web/components/cycles/transfer-issues.tsx +++ b/web/components/cycles/transfer-issues.tsx @@ -1,7 +1,7 @@ import React from "react"; import isEmpty from "lodash/isEmpty"; -import { useRouter } from "next/router"; +import { useParams } from "next/navigation"; import useSWR from "swr"; @@ -24,8 +24,7 @@ const cycleService = new CycleService(); export const TransferIssues: React.FC = (props) => { const { handleClick, disabled = false } = props; - const router = useRouter(); - const { workspaceSlug, projectId, cycleId } = router.query; + const { workspaceSlug, projectId, cycleId } = useParams(); const { data: cycleDetails } = useSWR( cycleId ? CYCLE_DETAILS(cycleId as string) : null, diff --git a/web/components/dashboard/widgets/issues-by-priority.tsx b/web/components/dashboard/widgets/issues-by-priority.tsx index 9862b440b..34bcae240 100644 --- a/web/components/dashboard/widgets/issues-by-priority.tsx +++ b/web/components/dashboard/widgets/issues-by-priority.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { TIssuesByPriorityWidgetFilters, TIssuesByPriorityWidgetResponse } from "@plane/types"; // hooks // components diff --git a/web/components/dashboard/widgets/issues-by-state-group.tsx b/web/components/dashboard/widgets/issues-by-state-group.tsx index 22552ea8c..bfdc7d669 100644 --- a/web/components/dashboard/widgets/issues-by-state-group.tsx +++ b/web/components/dashboard/widgets/issues-by-state-group.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useRouter } from "next/router"; +import { useRouter } from "next/navigation"; import { TIssuesByStateGroupsWidgetFilters, TIssuesByStateGroupsWidgetResponse, TStateGroups } from "@plane/types"; // hooks import { diff --git a/web/components/estimates/create-update-estimate-modal.tsx b/web/components/estimates/create-update-estimate-modal.tsx new file mode 100644 index 000000000..5e68095f3 --- /dev/null +++ b/web/components/estimates/create-update-estimate-modal.tsx @@ -0,0 +1,291 @@ +import React, { useEffect } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { Controller, useForm } from "react-hook-form"; +// types +import { IEstimate, IEstimateFormData } from "@plane/types"; +// ui +import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui"; +// components +import { EModalPosition, EModalWidth, ModalCore } from "@/components/core"; +// helpers +import { checkDuplicates } from "@/helpers/array.helper"; +// hooks +import { useEstimate } from "@/hooks/store"; + +type Props = { + isOpen: boolean; + handleClose: () => void; + data?: IEstimate; +}; + +const defaultValues = { + name: "", + description: "", + value1: "", + value2: "", + value3: "", + value4: "", + value5: "", + value6: "", +}; + +type FormValues = typeof defaultValues; + +export const CreateUpdateEstimateModal: React.FC = observer((props) => { + const { handleClose, data, isOpen } = props; + // router + const { workspaceSlug, projectId } = useParams(); + // store hooks + const { createEstimate, updateEstimate } = useEstimate(); + // form info + const { + formState: { errors, isSubmitting }, + handleSubmit, + control, + reset, + } = useForm({ + defaultValues, + }); + + const onClose = () => { + handleClose(); + reset(); + }; + + const handleCreateEstimate = async (payload: IEstimateFormData) => { + if (!workspaceSlug || !projectId) return; + + await createEstimate(workspaceSlug.toString(), projectId.toString(), payload) + .then(() => { + onClose(); + }) + .catch((err) => { + const error = err?.error; + const errorString = Array.isArray(error) ? error[0] : error; + + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: + errorString ?? err.status === 400 + ? "Estimate with that name already exists. Please try again with another name." + : "Estimate could not be created. Please try again.", + }); + }); + }; + + const handleUpdateEstimate = async (payload: IEstimateFormData) => { + if (!workspaceSlug || !projectId || !data) return; + + await updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload) + .then(() => { + onClose(); + }) + .catch((err) => { + const error = err?.error; + const errorString = Array.isArray(error) ? error[0] : error; + + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: errorString ?? "Estimate could not be updated. Please try again.", + }); + }); + }; + + const onSubmit = async (formData: FormValues) => { + if (!formData.name || formData.name === "") { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Estimate title cannot be empty.", + }); + return; + } + + if ( + formData.value1 === "" || + formData.value2 === "" || + formData.value3 === "" || + formData.value4 === "" || + formData.value5 === "" || + formData.value6 === "" + ) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Estimate point cannot be empty.", + }); + return; + } + + if ( + formData.value1.length > 20 || + formData.value2.length > 20 || + formData.value3.length > 20 || + formData.value4.length > 20 || + formData.value5.length > 20 || + formData.value6.length > 20 + ) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Estimate point cannot have more than 20 characters.", + }); + return; + } + + if ( + checkDuplicates([ + formData.value1, + formData.value2, + formData.value3, + formData.value4, + formData.value5, + formData.value6, + ]) + ) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Estimate points cannot have duplicate values.", + }); + return; + } + + const payload: IEstimateFormData = { + estimate: { + name: formData.name, + description: formData.description, + }, + estimate_points: [], + }; + + for (let i = 0; i < 6; i++) { + const point = { + key: i, + value: formData[`value${i + 1}` as keyof FormValues], + }; + + if (data) + payload.estimate_points.push({ + id: data.points[i].id, + ...point, + }); + else payload.estimate_points.push({ ...point }); + } + + if (data) await handleUpdateEstimate(payload); + else await handleCreateEstimate(payload); + }; + + useEffect(() => { + if (data) + reset({ + ...defaultValues, + ...data, + value1: data.points[0]?.value, + value2: data.points[1]?.value, + value3: data.points[2]?.value, + value4: data.points[3]?.value, + value5: data.points[4]?.value, + value6: data.points[5]?.value, + }); + else reset({ ...defaultValues }); + }, [data, reset]); + + return ( + + +
+
{data ? "Update" : "Create"} Estimate
+
+
+ ( + + )} + /> +
+
+ ( +