diff --git a/@types/index.d.ts b/@types/index.d.ts index 618d6500..f91a9dca 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -4,4 +4,5 @@ /// /// /// +/// /// diff --git a/@types/router.d.ts b/@types/router.d.ts new file mode 100644 index 00000000..6644b2fb --- /dev/null +++ b/@types/router.d.ts @@ -0,0 +1,10 @@ +declare module '@tanstack/react-router' { + interface Register { + router: typeof import('../app/main').router; + } + type AllRouteIds = import('@tanstack/react-router').RouteIds< + typeof import('../app/routeTree.gen').routeTree + >; +} + +export {}; diff --git a/app/Routes.tsx b/app/Routes.tsx deleted file mode 100644 index 83e5299b..00000000 --- a/app/Routes.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { AuthGuard, GuestGuard } from '@camp/auth'; -import { config } from '@camp/config'; -import { messages } from '@camp/messages'; -import { LoginPage } from '@camp/pages/Auth'; -import { - DashboardLayout, - HouseholdDetail, - HouseholdEmptyState, - HouseholdList, - HouseholdsLayout, - ProjectDetail, - ProjectList, - ProjectsLayout, -} from '@camp/pages/Dashboard'; -import type { Route } from '@camp/router'; -import { lazy, Navigate, ReactLocation, Router } from '@camp/router'; - -export const location = new ReactLocation(); - -const devRoutes: Route[] = [ - { - path: '/graphiql', - import: lazy(() => import('./GraphiQL')), - }, -]; - -const routes: Route[] = [ - { - path: '/auth', - element: , - children: [ - { path: '/login', element: }, - { element: }, - ], - }, - { - path: '/dashboard', - element: ( - - - - ), - children: [ - { - path: '/households', - element: , - meta: { breadcrumb: messages.households.title }, - children: [ - { - path: '/', - element: , - }, - { - path: '/:id', - element: , - meta: { breadcrumb: messages.householdDetail.title }, - }, - { element: }, - ], - }, - { - path: '/projects', - element: , - meta: { breadcrumb: messages.projects.title }, - children: [ - { path: '/', element: }, - { - path: '/:id', - element: , - meta: { breadcrumb: messages.projectDetail.title }, - }, - ], - }, - { element: }, - ], - }, - { element: }, -]; - -if (config.isDev) routes.unshift(...devRoutes); - -export const Routes = () => ; diff --git a/app/index.ts b/app/index.ts deleted file mode 100644 index c1b4bc33..00000000 --- a/app/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './GraphiQL'; -export * from '@camp/pages/Auth'; -export * from '@camp/pages/Dashboard'; diff --git a/app/main.tsx b/app/main.tsx index 38615032..4f0c181d 100644 --- a/app/main.tsx +++ b/app/main.tsx @@ -1,27 +1,38 @@ -import 'dayjs/locale/fa'; import '@camp/zod-addons/monkey-patch'; +import 'dayjs/locale/fa'; -import { ApolloProvider } from '@camp/api-client'; -import { AuthProvider } from '@camp/auth'; import { config } from '@camp/config'; import { debug, DebugScopes } from '@camp/debug'; -import { ThemeProvider } from '@camp/design'; +import type { AllRouteIds } from '@camp/router'; +import { createRouter, RouterProvider } from '@camp/router'; import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; -import { Routes } from './Routes'; +import { routeTree } from './routeTree.gen'; + +export interface RouterContext { + getTitle: (to: AllRouteIds) => string; +} + +export const router = createRouter({ + routeTree, + context: { + getTitle: x => { + const routeTitles: Partial> = { + '/dashboard/_layout/households/_layout': 'خانوار', + '/dashboard/_layout/households/_layout/$householdId': 'خانواده', + }; + + return routeTitles[x] ?? ''; + }, + }, +}); const root = ReactDOM.createRoot(document.getElementById('root')!); debug.log(DebugScopes.All, config); root.render( - - - - - - - + , ); diff --git a/app/routeTree.gen.ts b/app/routeTree.gen.ts new file mode 100644 index 00000000..c5b377a3 --- /dev/null +++ b/app/routeTree.gen.ts @@ -0,0 +1,321 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +import { createFileRoute } from '@tanstack/react-router' + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as LoginImport } from './routes/login' +import { Route as GraphiqlImport } from './routes/graphiql' +import { Route as DashboardLayoutImport } from './routes/dashboard/_layout' +import { Route as DashboardLayoutIndexImport } from './routes/dashboard/_layout/index' +import { Route as DashboardLayoutProjectsLayoutImport } from './routes/dashboard/_layout/projects/_layout' +import { Route as DashboardLayoutHouseholdsLayoutImport } from './routes/dashboard/_layout/households/_layout' +import { Route as DashboardLayoutProjectsLayoutIndexImport } from './routes/dashboard/_layout/projects/_layout/index' +import { Route as DashboardLayoutHouseholdsLayoutIndexImport } from './routes/dashboard/_layout/households/_layout/index' +import { Route as DashboardLayoutProjectsLayoutProjectIdImport } from './routes/dashboard/_layout/projects/_layout/$projectId' +import { Route as DashboardLayoutHouseholdsLayoutHouseholdIdImport } from './routes/dashboard/_layout/households/_layout/$householdId' + +// Create Virtual Routes + +const DashboardImport = createFileRoute('/dashboard')() +const DashboardLayoutProjectsImport = createFileRoute( + '/dashboard/_layout/projects', +)() +const DashboardLayoutHouseholdsImport = createFileRoute( + '/dashboard/_layout/households', +)() + +// Create/Update Routes + +const DashboardRoute = DashboardImport.update({ + path: '/dashboard', + getParentRoute: () => rootRoute, +} as any) + +const LoginRoute = LoginImport.update({ + path: '/login', + getParentRoute: () => rootRoute, +} as any) + +const GraphiqlRoute = GraphiqlImport.update({ + path: '/graphiql', + getParentRoute: () => rootRoute, +} as any) + +const DashboardLayoutRoute = DashboardLayoutImport.update({ + id: '/_layout', + getParentRoute: () => DashboardRoute, +} as any) + +const DashboardLayoutProjectsRoute = DashboardLayoutProjectsImport.update({ + path: '/projects', + getParentRoute: () => DashboardLayoutRoute, +} as any) + +const DashboardLayoutHouseholdsRoute = DashboardLayoutHouseholdsImport.update({ + path: '/households', + getParentRoute: () => DashboardLayoutRoute, +} as any) + +const DashboardLayoutIndexRoute = DashboardLayoutIndexImport.update({ + path: '/', + getParentRoute: () => DashboardLayoutRoute, +} as any) + +const DashboardLayoutProjectsLayoutRoute = + DashboardLayoutProjectsLayoutImport.update({ + id: '/_layout', + getParentRoute: () => DashboardLayoutProjectsRoute, + } as any) + +const DashboardLayoutHouseholdsLayoutRoute = + DashboardLayoutHouseholdsLayoutImport.update({ + id: '/_layout', + getParentRoute: () => DashboardLayoutHouseholdsRoute, + } as any) + +const DashboardLayoutProjectsLayoutIndexRoute = + DashboardLayoutProjectsLayoutIndexImport.update({ + path: '/', + getParentRoute: () => DashboardLayoutProjectsLayoutRoute, + } as any) + +const DashboardLayoutHouseholdsLayoutIndexRoute = + DashboardLayoutHouseholdsLayoutIndexImport.update({ + path: '/', + getParentRoute: () => DashboardLayoutHouseholdsLayoutRoute, + } as any) + +const DashboardLayoutProjectsLayoutProjectIdRoute = + DashboardLayoutProjectsLayoutProjectIdImport.update({ + path: '/$projectId', + getParentRoute: () => DashboardLayoutProjectsLayoutRoute, + } as any) + +const DashboardLayoutHouseholdsLayoutHouseholdIdRoute = + DashboardLayoutHouseholdsLayoutHouseholdIdImport.update({ + path: '/$householdId', + getParentRoute: () => DashboardLayoutHouseholdsLayoutRoute, + } as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/graphiql': { + id: '/graphiql' + path: '/graphiql' + fullPath: '/graphiql' + preLoaderRoute: typeof GraphiqlImport + parentRoute: typeof rootRoute + } + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginImport + parentRoute: typeof rootRoute + } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardImport + parentRoute: typeof rootRoute + } + '/dashboard/_layout': { + id: '/dashboard/_layout' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardLayoutImport + parentRoute: typeof DashboardRoute + } + '/dashboard/_layout/': { + id: '/dashboard/_layout/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardLayoutIndexImport + parentRoute: typeof DashboardLayoutImport + } + '/dashboard/_layout/households': { + id: '/dashboard/_layout/households' + path: '/households' + fullPath: '/dashboard/households' + preLoaderRoute: typeof DashboardLayoutHouseholdsImport + parentRoute: typeof DashboardLayoutImport + } + '/dashboard/_layout/households/_layout': { + id: '/dashboard/_layout/households/_layout' + path: '/households' + fullPath: '/dashboard/households' + preLoaderRoute: typeof DashboardLayoutHouseholdsLayoutImport + parentRoute: typeof DashboardLayoutHouseholdsRoute + } + '/dashboard/_layout/projects': { + id: '/dashboard/_layout/projects' + path: '/projects' + fullPath: '/dashboard/projects' + preLoaderRoute: typeof DashboardLayoutProjectsImport + parentRoute: typeof DashboardLayoutImport + } + '/dashboard/_layout/projects/_layout': { + id: '/dashboard/_layout/projects/_layout' + path: '/projects' + fullPath: '/dashboard/projects' + preLoaderRoute: typeof DashboardLayoutProjectsLayoutImport + parentRoute: typeof DashboardLayoutProjectsRoute + } + '/dashboard/_layout/households/_layout/$householdId': { + id: '/dashboard/_layout/households/_layout/$householdId' + path: '/$householdId' + fullPath: '/dashboard/households/$householdId' + preLoaderRoute: typeof DashboardLayoutHouseholdsLayoutHouseholdIdImport + parentRoute: typeof DashboardLayoutHouseholdsLayoutImport + } + '/dashboard/_layout/projects/_layout/$projectId': { + id: '/dashboard/_layout/projects/_layout/$projectId' + path: '/$projectId' + fullPath: '/dashboard/projects/$projectId' + preLoaderRoute: typeof DashboardLayoutProjectsLayoutProjectIdImport + parentRoute: typeof DashboardLayoutProjectsLayoutImport + } + '/dashboard/_layout/households/_layout/': { + id: '/dashboard/_layout/households/_layout/' + path: '/' + fullPath: '/dashboard/households/' + preLoaderRoute: typeof DashboardLayoutHouseholdsLayoutIndexImport + parentRoute: typeof DashboardLayoutHouseholdsLayoutImport + } + '/dashboard/_layout/projects/_layout/': { + id: '/dashboard/_layout/projects/_layout/' + path: '/' + fullPath: '/dashboard/projects/' + preLoaderRoute: typeof DashboardLayoutProjectsLayoutIndexImport + parentRoute: typeof DashboardLayoutProjectsLayoutImport + } + } +} + +// Create and export the route tree + +export const routeTree = rootRoute.addChildren({ + GraphiqlRoute, + LoginRoute, + DashboardRoute: DashboardRoute.addChildren({ + DashboardLayoutRoute: DashboardLayoutRoute.addChildren({ + DashboardLayoutIndexRoute, + DashboardLayoutHouseholdsRoute: + DashboardLayoutHouseholdsRoute.addChildren({ + DashboardLayoutHouseholdsLayoutRoute: + DashboardLayoutHouseholdsLayoutRoute.addChildren({ + DashboardLayoutHouseholdsLayoutHouseholdIdRoute, + DashboardLayoutHouseholdsLayoutIndexRoute, + }), + }), + DashboardLayoutProjectsRoute: DashboardLayoutProjectsRoute.addChildren({ + DashboardLayoutProjectsLayoutRoute: + DashboardLayoutProjectsLayoutRoute.addChildren({ + DashboardLayoutProjectsLayoutProjectIdRoute, + DashboardLayoutProjectsLayoutIndexRoute, + }), + }), + }), + }), +}) + +/* prettier-ignore-end */ + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/graphiql", + "/login", + "/dashboard" + ] + }, + "/graphiql": { + "filePath": "graphiql.tsx" + }, + "/login": { + "filePath": "login.tsx" + }, + "/dashboard": { + "filePath": "dashboard", + "children": [ + "/dashboard/_layout" + ] + }, + "/dashboard/_layout": { + "filePath": "dashboard/_layout.tsx", + "parent": "/dashboard", + "children": [ + "/dashboard/_layout/", + "/dashboard/_layout/households", + "/dashboard/_layout/projects" + ] + }, + "/dashboard/_layout/": { + "filePath": "dashboard/_layout/index.tsx", + "parent": "/dashboard/_layout" + }, + "/dashboard/_layout/households": { + "filePath": "dashboard/_layout/households", + "parent": "/dashboard/_layout", + "children": [ + "/dashboard/_layout/households/_layout" + ] + }, + "/dashboard/_layout/households/_layout": { + "filePath": "dashboard/_layout/households/_layout.tsx", + "parent": "/dashboard/_layout/households", + "children": [ + "/dashboard/_layout/households/_layout/$householdId", + "/dashboard/_layout/households/_layout/" + ] + }, + "/dashboard/_layout/projects": { + "filePath": "dashboard/_layout/projects", + "parent": "/dashboard/_layout", + "children": [ + "/dashboard/_layout/projects/_layout" + ] + }, + "/dashboard/_layout/projects/_layout": { + "filePath": "dashboard/_layout/projects/_layout.tsx", + "parent": "/dashboard/_layout/projects", + "children": [ + "/dashboard/_layout/projects/_layout/$projectId", + "/dashboard/_layout/projects/_layout/" + ] + }, + "/dashboard/_layout/households/_layout/$householdId": { + "filePath": "dashboard/_layout/households/_layout/$householdId.tsx", + "parent": "/dashboard/_layout/households/_layout" + }, + "/dashboard/_layout/projects/_layout/$projectId": { + "filePath": "dashboard/_layout/projects/_layout/$projectId.tsx", + "parent": "/dashboard/_layout/projects/_layout" + }, + "/dashboard/_layout/households/_layout/": { + "filePath": "dashboard/_layout/households/_layout/index.tsx", + "parent": "/dashboard/_layout/households/_layout" + }, + "/dashboard/_layout/projects/_layout/": { + "filePath": "dashboard/_layout/projects/_layout/index.tsx", + "parent": "/dashboard/_layout/projects/_layout" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/app/routes/__root.tsx b/app/routes/__root.tsx new file mode 100644 index 00000000..42fd32d1 --- /dev/null +++ b/app/routes/__root.tsx @@ -0,0 +1,30 @@ +import { ApolloProvider } from '@camp/api-client'; +import { AuthProvider } from '@camp/auth'; +import { ThemeProvider } from '@camp/design'; +import { createRootRouteWithContext, Navigate, Outlet } from '@camp/router'; +import { ModalsProvider } from '@mantine/modals'; +import { Notifications } from '@mantine/notifications'; +import { TanStackRouterDevtools } from '@tanstack/router-devtools'; + +import type { RouterContext } from '../main'; + +const Root = () => { + return ( + + + + + + + + + + + + ); +}; + +export const Route = createRootRouteWithContext()({ + component: Root, + notFoundComponent: () => , +}); diff --git a/app/routes/dashboard/_layout.tsx b/app/routes/dashboard/_layout.tsx new file mode 100644 index 00000000..197a560d --- /dev/null +++ b/app/routes/dashboard/_layout.tsx @@ -0,0 +1,13 @@ +import { AuthGuard } from '@camp/auth'; +import { DashboardLayout } from '@camp/pages/Dashboard'; +import { createFileRoute, Outlet } from '@camp/router'; + +export const Route = createFileRoute('/dashboard/_layout')({ + component: () => ( + + + + + + ), +}); diff --git a/app/routes/dashboard/_layout/households/_layout.tsx b/app/routes/dashboard/_layout/households/_layout.tsx new file mode 100644 index 00000000..25f73d87 --- /dev/null +++ b/app/routes/dashboard/_layout/households/_layout.tsx @@ -0,0 +1,6 @@ +import { HouseholdsLayout } from '@camp/pages/Households'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/dashboard/_layout/households/_layout')({ + component: () => , +}); diff --git a/app/routes/dashboard/_layout/households/_layout/$householdId.tsx b/app/routes/dashboard/_layout/households/_layout/$householdId.tsx new file mode 100644 index 00000000..6963a350 --- /dev/null +++ b/app/routes/dashboard/_layout/households/_layout/$householdId.tsx @@ -0,0 +1,8 @@ +import { HouseholdDetail } from '@camp/pages/Households'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute( + '/dashboard/_layout/households/_layout/$householdId', +)({ + component: HouseholdDetail, +}); diff --git a/app/routes/dashboard/_layout/households/_layout/index.tsx b/app/routes/dashboard/_layout/households/_layout/index.tsx new file mode 100644 index 00000000..217fce59 --- /dev/null +++ b/app/routes/dashboard/_layout/households/_layout/index.tsx @@ -0,0 +1,6 @@ +import { HouseholdList } from '@camp/pages/Households'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/dashboard/_layout/households/_layout/')({ + component: HouseholdList, +}); diff --git a/app/routes/dashboard/_layout/index.tsx b/app/routes/dashboard/_layout/index.tsx new file mode 100644 index 00000000..d015e089 --- /dev/null +++ b/app/routes/dashboard/_layout/index.tsx @@ -0,0 +1,5 @@ +import { createFileRoute, Navigate } from '@camp/router'; + +export const Route = createFileRoute('/dashboard/_layout/')({ + component: () => , +}); diff --git a/app/routes/dashboard/_layout/projects/_layout.tsx b/app/routes/dashboard/_layout/projects/_layout.tsx new file mode 100644 index 00000000..9adbe61e --- /dev/null +++ b/app/routes/dashboard/_layout/projects/_layout.tsx @@ -0,0 +1,6 @@ +import { ProjectsLayout } from '@camp/pages/Projects'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/dashboard/_layout/projects/_layout')({ + component: () => , +}); diff --git a/app/routes/dashboard/_layout/projects/_layout/$projectId.tsx b/app/routes/dashboard/_layout/projects/_layout/$projectId.tsx new file mode 100644 index 00000000..a93871f1 --- /dev/null +++ b/app/routes/dashboard/_layout/projects/_layout/$projectId.tsx @@ -0,0 +1,10 @@ +import { ProjectDetail } from '@camp/pages/Projects'; +import { createFileRoute } from '@camp/router'; + +export const Route = createFileRoute( + '/dashboard/_layout/projects/_layout/$projectId', +)({ + component: ProjectDetail, + errorComponent: ({ error }) => <>Error: ${error.message}, + notFoundComponent: () => <>Not Found, +}); diff --git a/app/routes/dashboard/_layout/projects/_layout/index.tsx b/app/routes/dashboard/_layout/projects/_layout/index.tsx new file mode 100644 index 00000000..91dd7e91 --- /dev/null +++ b/app/routes/dashboard/_layout/projects/_layout/index.tsx @@ -0,0 +1,6 @@ +import { ProjectList } from '@camp/pages/Projects'; +import { createFileRoute } from '@camp/router'; + +export const Route = createFileRoute('/dashboard/_layout/projects/_layout/')({ + component: ProjectList, +}); diff --git a/app/GraphiQL.tsx b/app/routes/graphiql.tsx similarity index 85% rename from app/GraphiQL.tsx rename to app/routes/graphiql.tsx index 09c0ac2c..4f07828e 100644 --- a/app/GraphiQL.tsx +++ b/app/routes/graphiql.tsx @@ -1,14 +1,12 @@ import 'graphiql/graphiql.css'; import { config } from '@camp/config'; +import { createFileRoute } from '@camp/router'; import { isNull } from '@fullstacksjs/toolbox'; import { createGraphiQLFetcher } from '@graphiql/toolkit'; import { GraphiQL } from 'graphiql'; import { useEffect } from 'react'; -if (config.isDev && (isNull(config.schemaUrl) || isNull(config.hasuraSecret))) - throw Error('Hasura Secret is needed'); - const fetcher = createGraphiQLFetcher({ url: config.schemaUrl!, headers: { @@ -25,8 +23,9 @@ const GraphiQLPage = () => { return ; }; +export const Route = createFileRoute('/graphiql')({ + component: GraphiQLPage, +}); -export const routes = { - path: '/graphiql', - element: , -}; +if (config.isDev && (isNull(config.schemaUrl) || isNull(config.hasuraSecret))) + throw Error('Hasura Secret is needed'); diff --git a/app/routes/login.tsx b/app/routes/login.tsx new file mode 100644 index 00000000..8972598d --- /dev/null +++ b/app/routes/login.tsx @@ -0,0 +1,11 @@ +import { GuestGuard } from '@camp/auth'; +import { LoginPage } from '@camp/pages/Auth'; +import { createFileRoute } from '@camp/router'; + +export const Route = createFileRoute('/login')({ + component: () => ( + + + + ), +}); diff --git a/cypress/e2e/createHousehold.cy.ts b/cypress/e2e/createHousehold.cy.ts index 172179c1..540e2290 100644 --- a/cypress/e2e/createHousehold.cy.ts +++ b/cypress/e2e/createHousehold.cy.ts @@ -1,6 +1,6 @@ -import { createHouseholdButtonId } from '../../app/Dashboard/Households/_components/CreateHousehold/CreateHouseholdButton/CreateHouseholdButton.ids'; -import { createHouseholdFormIds } from '../../app/Dashboard/Households/_components/CreateHousehold/CreateHouseholdForm/CreateHouseholdForm.ids'; import { dashboardHeaderId } from '../../libs/design/DashboardHeader/DashboardHeader.ids'; +import { createHouseholdButtonId } from '../../libs/pages/Households/CreateHousehold/CreateHouseholdButton/CreateHouseholdButton.ids'; +import { createHouseholdFormIds } from '../../libs/pages/Households/CreateHousehold/CreateHouseholdForm/CreateHouseholdForm.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; import { householdFixture } from '../fixtures/household'; diff --git a/cypress/e2e/createProject.cy.ts b/cypress/e2e/createProject.cy.ts index b9855d6d..9f5b5593 100644 --- a/cypress/e2e/createProject.cy.ts +++ b/cypress/e2e/createProject.cy.ts @@ -1,7 +1,7 @@ -import { dashboardSidebarIds } from '../../app/Dashboard/_components/DashboardSidebar/DashboardSidebar.ids'; -import { createProjectButtonId } from '../../app/Dashboard/Projects/CreateProject/CreateProjectButton/CreateProjectButton.ids'; -import { createProjectFormIds } from '../../app/Dashboard/Projects/CreateProject/CreateProjectForm/CreateProjectForm.ids'; import { dashboardHeaderId } from '../../libs/design/DashboardHeader/DashboardHeader.ids'; +import { dashboardSidebarIds } from '../../libs/pages/Dashboard/DashboardSidebar/DashboardSidebar.ids'; +import { createProjectButtonId } from '../../libs/pages/Projects/CreateProject/CreateProjectButton/CreateProjectButton.ids'; +import { createProjectFormIds } from '../../libs/pages/Projects/CreateProject/CreateProjectForm/CreateProjectForm.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; import { projectFixture } from '../fixtures/project'; diff --git a/cypress/e2e/deleteHousehold.cy.ts b/cypress/e2e/deleteHousehold.cy.ts index edb8e8f4..43c55aba 100644 --- a/cypress/e2e/deleteHousehold.cy.ts +++ b/cypress/e2e/deleteHousehold.cy.ts @@ -1,4 +1,4 @@ -import { householdActionIds } from '../../app/Dashboard/Households/_components/HouseholdActionButton/HouseholdActionButton.ids'; +import { householdActionIds } from '../../libs/pages/Households/HouseholdActionButton/HouseholdActionButton.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; import { householdFixture } from '../fixtures/household'; diff --git a/cypress/e2e/draftHouseholder.cy.ts b/cypress/e2e/draftHouseholder.cy.ts index 84a8e47a..b3c21fa4 100644 --- a/cypress/e2e/draftHouseholder.cy.ts +++ b/cypress/e2e/draftHouseholder.cy.ts @@ -1,7 +1,7 @@ import { isNull, pruneValueWhen } from '@fullstacksjs/toolbox'; -import { householderFormIds as ids } from '../../app/Dashboard/Households/HouseholdDetail/_components/HouseholderForm/_components/HouseholderIdentityForm/HouseholderIdentityForm.ids'; -import { householdDetailIds as tabIds } from '../../app/Dashboard/Households/HouseholdDetail/HouseholdDetail.ids'; +import { householdDetailIds as tabIds } from '../../libs/pages/Households/HouseholdDetail/HouseholdDetail.ids'; +import { householderFormIds as ids } from '../../libs/pages/Households/HouseholdDetail/HouseholderForm/HouseholderIdentityForm/HouseholderIdentityForm.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; import { householdFixture } from '../fixtures/household'; diff --git a/cypress/e2e/householdDetail.cy.ts b/cypress/e2e/householdDetail.cy.ts index 7dede9cd..2975ede2 100644 --- a/cypress/e2e/householdDetail.cy.ts +++ b/cypress/e2e/householdDetail.cy.ts @@ -1,10 +1,14 @@ -import { householdDetailIds } from '../../app/Dashboard/Households/HouseholdDetail/HouseholdDetail.ids'; -import type { Household } from '../../libs/domain'; +import { householdDetailIds } from '../../libs/pages/Households/HouseholdDetail/HouseholdDetail.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; import { householdFixture } from '../fixtures/household'; import * as API from './api'; +interface Household { + id: string; + name: string; +} + describe('Household', () => { beforeEach(() => { cy.login(admin); diff --git a/cypress/e2e/householdList.cy.ts b/cypress/e2e/householdList.cy.ts index 21f43a9f..a1aec97b 100644 --- a/cypress/e2e/householdList.cy.ts +++ b/cypress/e2e/householdList.cy.ts @@ -1,4 +1,4 @@ -import { dashboardSidebarIds } from '../../app/Dashboard/_components/DashboardSidebar/DashboardSidebar.ids'; +import { dashboardSidebarIds } from '../../libs/pages/Dashboard/DashboardSidebar/DashboardSidebar.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; import { householdFixture } from '../fixtures/household'; diff --git a/cypress/e2e/logout.cy.ts b/cypress/e2e/logout.cy.ts index ad77d25f..76d5799a 100644 --- a/cypress/e2e/logout.cy.ts +++ b/cypress/e2e/logout.cy.ts @@ -1,5 +1,5 @@ -import { exitNavLinkId } from '../../app/Dashboard/_components/ExitNavLink/ExitNavLink.ids'; -import { logoutModalIds } from '../../app/Dashboard/_components/LogoutModal/LogoutModal.ids'; +import { exitNavLinkId } from '../../libs/pages/Dashboard/ExitNavLink/ExitNavLink.ids'; +import { logoutModalIds } from '../../libs/pages/Dashboard/LogoutModal/LogoutModal.ids'; import { AppRoute } from '../../libs/router/AppRoutes'; import { admin } from '../fixtures/admin'; diff --git a/cypress/support/commands/mount.command.tsx b/cypress/support/commands/mount.command.tsx index 34e6c85e..43c1b4cb 100644 --- a/cypress/support/commands/mount.command.tsx +++ b/cypress/support/commands/mount.command.tsx @@ -5,7 +5,11 @@ import { InMemoryCache, } from '@apollo/client'; import { ModalsProvider } from '@mantine/modals'; -import { ReactLocation, Router } from '@tanstack/react-location'; +import { + createRootRoute, + createRouter, + RouterProvider, +} from '@tanstack/react-router'; import { mount } from 'cypress/react18'; import { ThemeProvider } from '../../../libs/design'; @@ -23,9 +27,13 @@ Cypress.Commands.add( } > - - {element} - + + element }), + })} + /> + , ...rest, diff --git a/libs/design/Breadcrumbs/Breadcrumbs.tsx b/libs/design/Breadcrumbs/Breadcrumbs.tsx index 3d680e52..bf182a2e 100644 --- a/libs/design/Breadcrumbs/Breadcrumbs.tsx +++ b/libs/design/Breadcrumbs/Breadcrumbs.tsx @@ -1,5 +1,4 @@ import { ChevronLeftIcon, HomeIcon } from '@camp/icons'; -import type { AppRoute } from '@camp/router'; import { Link } from '@camp/router'; import type { Styles } from '@mantine/core'; import { @@ -39,11 +38,7 @@ export const Breadcrumbs = ({ items }: Props) => { > {items.map(item => ( - + {item.name} ))} diff --git a/libs/design/DashboardHeader/DashboardHeader.tsx b/libs/design/DashboardHeader/DashboardHeader.tsx index b68cf2af..b99dcd3c 100644 --- a/libs/design/DashboardHeader/DashboardHeader.tsx +++ b/libs/design/DashboardHeader/DashboardHeader.tsx @@ -1,5 +1,5 @@ import { debug, DebugScopes } from '@camp/debug'; -import { useMatches } from '@camp/router'; +import { useRouterState } from '@camp/router'; import { tid } from '@camp/test'; import { Group } from '@mantine/core'; @@ -12,20 +12,15 @@ export interface HeaderProps { } export const useBreadcrumbsItems = (): BreadcrumbItem[] => { - const matches = useMatches(); + const matches = useRouterState().matches; debug.log(DebugScopes.Breadcrumbs, matches); return matches - .filter(match => Boolean(match.route.meta?.breadcrumb)) - .filter(match => Boolean(match.route.path)) - .map(match => { - const { meta } = match.route; - - return { - path: match.pathname, - name: meta!.breadcrumb, - }; - }); + .map(match => ({ + name: match.context.getTitle(match.routeId), + path: match.pathname, + })) + .filter(path => path.name); }; export const DashboardHeader = ({ button }: HeaderProps) => { diff --git a/libs/design/NavLink/NavLink.tsx b/libs/design/NavLink/NavLink.tsx index ff15f31e..61da959c 100644 --- a/libs/design/NavLink/NavLink.tsx +++ b/libs/design/NavLink/NavLink.tsx @@ -9,7 +9,7 @@ export interface NavLinkProps { icon: JSX.Element; to?: AppRoute; id: string; - onClick?: React.MouseEventHandler; + onClick?: React.MouseEventHandler<'a'>; variant?: 'destructive' | 'normal'; } const destructiveMixin = (theme: MantineTheme) => ({ @@ -30,9 +30,7 @@ export const NavLink = ({ variant, onClick, }: NavLinkProps) => { - const { - current: { pathname }, - } = useLocation(); + const { pathname } = useLocation(); return ( { value={activeTab} onTabChange={value => { if (value == null) throw Error("Assert: tab value shouldn't be null"); - setHash(value); + void setHash(value); setActiveTab(value); }} > diff --git a/libs/design/theme/theme.ts b/libs/design/theme/theme.ts index 0d51931e..f07788a8 100644 --- a/libs/design/theme/theme.ts +++ b/libs/design/theme/theme.ts @@ -36,6 +36,9 @@ export const theme: MantineThemeOverride = { backgroundColor: palette.secondary.default, borderRadius: '4px', }, + 'vite-plugin-checker-error-overlay': { + direction: 'ltr', + }, }), dir: 'rtl', fontFamily: 'IRANSansFaNum, IRANSans', diff --git a/libs/domain/Document.ts b/libs/domain/Document.ts index c2a10477..2f4c2e7c 100644 --- a/libs/domain/Document.ts +++ b/libs/domain/Document.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { messages } from '../../app/messages'; +import { messages } from '../pages/messages'; import { Schema } from './Schema'; export const documentSchema = { diff --git a/libs/pages/Dashboard/DashboardLayout.tsx b/libs/pages/Dashboard/DashboardLayout.tsx index 2336aae3..14bc9dec 100644 --- a/libs/pages/Dashboard/DashboardLayout.tsx +++ b/libs/pages/Dashboard/DashboardLayout.tsx @@ -1,12 +1,9 @@ -import { Outlet } from '@camp/router'; import { AppShell as MantineAppShell, createStyles, MediaQuery, Stack, } from '@mantine/core'; -import { ModalsProvider } from '@mantine/modals'; -import { Notifications } from '@mantine/notifications'; import { DashboardSidebar } from './DashboardSidebar'; @@ -23,26 +20,21 @@ const useStyles = createStyles(theme => ({ }, })); -const outlet = ; - -export const DashboardLayout = ({ children = outlet }) => { +export const DashboardLayout = ({ children }: React.PropsWithChildren) => { const { classes } = useStyles(); return ( - - - - - } - > - {children} - - - + + + + } + > + {children} + ); }; diff --git a/libs/pages/Households/CreateHousehold/CreateHouseholdForm/CreateHouseholdForm.tsx b/libs/pages/Households/CreateHousehold/CreateHouseholdForm/CreateHouseholdForm.tsx index f10e9afe..a5b79656 100644 --- a/libs/pages/Households/CreateHousehold/CreateHouseholdForm/CreateHouseholdForm.tsx +++ b/libs/pages/Households/CreateHousehold/CreateHouseholdForm/CreateHouseholdForm.tsx @@ -2,7 +2,6 @@ import { useCreateHouseholdMutation } from '@camp/data-layer'; import { showNotification } from '@camp/design'; import { createResolver, householdSchema } from '@camp/domain'; import { messages } from '@camp/messages'; -import type { AppRoute } from '@camp/router'; import { useNavigate } from '@camp/router'; import { tid } from '@camp/test'; import { isNull } from '@fullstacksjs/toolbox'; @@ -34,31 +33,30 @@ export const CreateHouseholdForm = ({ dismiss }: Props) => { const { nameInput, notification, submitBtn } = messages.households.createForm; - const onSubmit = handleSubmit(({ name }) => { - createDraftHousehold({ variables: { name } }) - .then(({ data }) => { - const household = data.household; - if (isNull(household)) - throw Error('Assert: household should not be null'); + const onSubmit = handleSubmit(async ({ name }) => { + try { + const { data } = await createDraftHousehold({ variables: { name } }); + const household = data.household; + if (isNull(household)) + throw Error('Assert: household should not be null'); - showNotification({ - title: messages.households.create, - message: notification.success(household.name), - type: 'success', - ...tid(ids.notification.success), - }); + showNotification({ + title: messages.households.create, + message: notification.success(household.name), + type: 'success', + ...tid(ids.notification.success), + }); - dismiss(); - navigate({ to: `/dashboard/households/${household.id}` as AppRoute }); - }) - .catch(() => - showNotification({ - title: messages.households.create, - message: notification.failure(), - type: 'failure', - ...tid(ids.notification.failure), - }), - ); + dismiss(); + await navigate({ to: `/dashboard/households/${household.id}` }); + } catch { + showNotification({ + title: messages.households.create, + message: notification.failure(), + type: 'failure', + ...tid(ids.notification.failure), + }); + } }); return ( diff --git a/libs/pages/Households/HouseholdActionButton/HouseholdActionButton.tsx b/libs/pages/Households/HouseholdActionButton/HouseholdActionButton.tsx index 374f7604..fa3e4c3a 100644 --- a/libs/pages/Households/HouseholdActionButton/HouseholdActionButton.tsx +++ b/libs/pages/Households/HouseholdActionButton/HouseholdActionButton.tsx @@ -3,7 +3,7 @@ import { debug } from '@camp/debug'; import { showNotification } from '@camp/design'; import { MenuIcon } from '@camp/icons'; import { messages } from '@camp/messages'; -import type { AppRoute, PathParams } from '@camp/router'; +import type { LinkProps } from '@camp/router'; import { Link } from '@camp/router'; import { tid } from '@camp/test'; import { isNull } from '@fullstacksjs/toolbox'; @@ -13,10 +13,10 @@ import { openDeleteHouseholdModal } from '../DeleteHouseholdModal'; import { householdActionIds as ids } from './HouseholdActionButton.ids'; interface Props { - to: AppRoute; + to: LinkProps['to']; householdName: string; householdId: string; - params?: PathParams; + params?: LinkProps['params']; menuId?: string; menuButtonId?: string; } @@ -68,6 +68,7 @@ export const HouseholdActionButton = ({ + {/* @ts-expect-error poor type */} {messages.actions.open} diff --git a/libs/pages/Households/HouseholdDetail/HouseholdDetail.tsx b/libs/pages/Households/HouseholdDetail/HouseholdDetail.tsx index f3e925b5..8938cd42 100644 --- a/libs/pages/Households/HouseholdDetail/HouseholdDetail.tsx +++ b/libs/pages/Households/HouseholdDetail/HouseholdDetail.tsx @@ -55,7 +55,10 @@ const resolver = createResolver({ export const HouseholdDetail = () => { const t = messages.householdDetail; const tNotification = messages.notification.household; - const { id } = useParams(); + const id = useParams({ + from: '/dashboard/_layout/households/_layout/$householdId', + select: params => params.householdId, + }); const navigate = useNavigate(); const { handleSubmit, @@ -135,7 +138,7 @@ export const HouseholdDetail = () => { message: tNotification.delete.success(household.name), type: 'success', }); - navigate({ to: AppRoute.dashboard }); + await navigate({ to: AppRoute.dashboard }); } catch (err) { debug.error(err); showNotification({ diff --git a/libs/pages/Households/HouseholdList/HouseholdList.tsx b/libs/pages/Households/HouseholdList/HouseholdList.tsx index dd616765..56829210 100644 --- a/libs/pages/Households/HouseholdList/HouseholdList.tsx +++ b/libs/pages/Households/HouseholdList/HouseholdList.tsx @@ -3,7 +3,6 @@ import { useHouseholdListQuery } from '@camp/data-layer'; import { DashboardCard, DashboardTitle, showNotification } from '@camp/design'; import { householdColumnHelper } from '@camp/domain'; import { errorMessages, messages } from '@camp/messages'; -import { AppRoute } from '@camp/router'; import { InformationBadge, SeverityBadge } from '@camp/shared-components'; import { tid } from '@camp/test'; import { isEmpty } from '@fullstacksjs/toolbox'; @@ -44,8 +43,8 @@ const columns = [ { const navigate = useNavigate(); const gotoDetail = useCallback( (householdId: string) => { - navigate({ to: `/dashboard/households/${householdId}` as AppRoute }); + return navigate({ to: `/dashboard/households/${householdId}` }); }, [navigate], ); diff --git a/libs/pages/Projects/CreateProject/CreateProjectForm/CreateProjectForm.tsx b/libs/pages/Projects/CreateProject/CreateProjectForm/CreateProjectForm.tsx index 26481f1b..7598e9a1 100644 --- a/libs/pages/Projects/CreateProject/CreateProjectForm/CreateProjectForm.tsx +++ b/libs/pages/Projects/CreateProject/CreateProjectForm/CreateProjectForm.tsx @@ -3,7 +3,6 @@ import { debug } from '@camp/debug'; import { showNotification } from '@camp/design'; import { createResolver, projectSchema } from '@camp/domain'; import { messages } from '@camp/messages'; -import type { AppRoute } from '@camp/router'; import { useNavigate } from '@camp/router'; import { tid } from '@camp/test'; import { isNull } from '@fullstacksjs/toolbox'; @@ -54,7 +53,7 @@ export const CreateProjectForm = ({ dismiss }: Props) => { ...tid(ids.notification.success), }); dismiss(); - navigate({ to: `/dashboard/projects/${project.id}` as AppRoute }); + await navigate({ to: `/dashboard/projects/${project.id}` }); } catch (err) { debug.error(err); diff --git a/libs/pages/Projects/ProjectActionButton/ProjectActionButton.tsx b/libs/pages/Projects/ProjectActionButton/ProjectActionButton.tsx index d1c22715..cf13b680 100644 --- a/libs/pages/Projects/ProjectActionButton/ProjectActionButton.tsx +++ b/libs/pages/Projects/ProjectActionButton/ProjectActionButton.tsx @@ -1,13 +1,13 @@ import { MenuIcon } from '@camp/icons'; import { messages } from '@camp/messages'; -import type { AppRoute, PathParams } from '@camp/router'; import { Link } from '@camp/router'; import { tid } from '@camp/test'; import { ActionIcon, Menu } from '@mantine/core'; +import type { LinkProps } from '@tanstack/react-router'; interface Props { - to: AppRoute; - params?: PathParams; + to: LinkProps['to']; + params?: LinkProps['params']; menuId?: string; menuButtonId?: string; } @@ -26,6 +26,7 @@ export const ProjectActionButton = ({ + {/* @ts-expect-error poor type */} {messages.actions.open} diff --git a/libs/pages/Projects/ProjectDetail/ProjectDetail.tsx b/libs/pages/Projects/ProjectDetail/ProjectDetail.tsx index d62e579d..6a15bf46 100644 --- a/libs/pages/Projects/ProjectDetail/ProjectDetail.tsx +++ b/libs/pages/Projects/ProjectDetail/ProjectDetail.tsx @@ -16,7 +16,10 @@ import { ProjectDetailIds as ids } from './ProjectDetail.ids'; export const ProjectDetail = () => { const t = messages.projectDetail; - const { id } = useParams(); + const id = useParams({ + from: '/dashboard/_layout/projects/_layout/$projectId', + select: params => params.projectId, + }); const { data, loading, error } = useProjectQuery({ variables: { id }, diff --git a/libs/pages/Projects/ProjectList/ProjectList.tsx b/libs/pages/Projects/ProjectList/ProjectList.tsx index f01eb2cb..83d79acc 100644 --- a/libs/pages/Projects/ProjectList/ProjectList.tsx +++ b/libs/pages/Projects/ProjectList/ProjectList.tsx @@ -10,7 +10,6 @@ import { } from '@camp/design'; import { projectColumnHelper } from '@camp/domain'; import { errorMessages, messages } from '@camp/messages'; -import { AppRoute } from '@camp/router'; import { tid } from '@camp/test'; import { isEmpty, isNull } from '@fullstacksjs/toolbox'; import { Group } from '@mantine/core'; @@ -61,8 +60,8 @@ const columns = [ ), diff --git a/libs/pages/Projects/ProjectList/ProjectTableRow.tsx b/libs/pages/Projects/ProjectList/ProjectTableRow.tsx index b669a01a..fdce61e4 100644 --- a/libs/pages/Projects/ProjectList/ProjectTableRow.tsx +++ b/libs/pages/Projects/ProjectList/ProjectTableRow.tsx @@ -1,5 +1,4 @@ import type { ProjectKeys, ProjectListItem } from '@camp/domain'; -import type { AppRoute } from '@camp/router'; import { useNavigate } from '@camp/router'; import type { Table } from '@tanstack/react-table'; import { flexRender } from '@tanstack/react-table'; @@ -12,7 +11,7 @@ export const ProjectTableRow = ({ rows }: Props) => { const navigate = useNavigate(); const gotoDetail = (ProjectId: string) => { - navigate({ to: `/dashboard/projects/${ProjectId}` as AppRoute }); + return navigate({ to: `/dashboard/projects/${ProjectId}` }); }; return rows?.getRowModel().rows.map(row => ( diff --git a/app/messages.ts b/libs/pages/messages.ts similarity index 100% rename from app/messages.ts rename to libs/pages/messages.ts diff --git a/libs/router/Link.tsx b/libs/router/Link.tsx deleted file mode 100644 index 5ac0c71c..00000000 --- a/libs/router/Link.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { LinkProps } from '@tanstack/react-location'; -import { Link as LocationLink } from '@tanstack/react-location'; - -import type { AppRoute } from './AppRoutes'; -import { buildUrl } from './buildUrl'; -import type { PathParams } from './PathParams'; - -interface Props

extends Omit { - to: P; - params?: PathParams

; -} - -export const Link =

({ - to, - params, - ...props -}: Props

) => { - return ; -}; diff --git a/libs/router/LinkProps.ts b/libs/router/LinkProps.ts new file mode 100644 index 00000000..b4a22b7d --- /dev/null +++ b/libs/router/LinkProps.ts @@ -0,0 +1,12 @@ +import type { + LinkProps as BasLinkProps, + RegisteredRouter, + RoutePaths, +} from '@tanstack/react-router'; + +export type LinkProps = BasLinkProps< + RegisteredRouter, + RoutePaths, + '', + RoutePaths +>; diff --git a/libs/router/LocationGenerics.ts b/libs/router/LocationGenerics.ts deleted file mode 100644 index 8c796b69..00000000 --- a/libs/router/LocationGenerics.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { MakeGenerics } from '@tanstack/react-location'; - -export type LocationGenerics = MakeGenerics<{ - Params: { - id: string; - }; - RouteMeta: { - breadcrumb: string; - }; -}>; diff --git a/libs/router/Navigate.tsx b/libs/router/Navigate.tsx deleted file mode 100644 index 61666bef..00000000 --- a/libs/router/Navigate.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { - DefaultGenerics, - NavigateOptions, -} from '@tanstack/react-location'; -import { Navigate as LocationNavigate } from '@tanstack/react-location'; - -import type { AppRoute } from './AppRoutes'; -import { buildUrl } from './buildUrl'; -import type { PathParams } from './PathParams'; - -interface Props< - TGeneric extends Partial = DefaultGenerics, - P extends AppRoute = AppRoute, -> extends Omit, 'to'> { - to: P | number; - params?: PathParams

; -} - -export const Navigate = < - TGeneric extends Partial, - P extends AppRoute, ->({ - to, - params, - ...props -}: Props) => ( - -); diff --git a/libs/router/Route.ts b/libs/router/Route.ts deleted file mode 100644 index 836a4b00..00000000 --- a/libs/router/Route.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { - Route as LocationRoute, - RouteLoaders, -} from '@tanstack/react-location'; - -import type { AppRoute } from './AppRoutes'; -import type { LocationGenerics } from './LocationGenerics'; -import type { RouteSegment } from './RouteSegment'; - -export interface Route extends Omit, 'path'> { - path?: RouteSegment; - children?: Route[]; -} - -export const lazy = - (path: () => Promise<{ routes: RouteLoaders }>) => () => - path().then(x => x.routes); diff --git a/libs/router/RouteSegment.ts b/libs/router/RouteSegment.ts deleted file mode 100644 index 52eb2070..00000000 --- a/libs/router/RouteSegment.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type RouteSegment = - T extends `${infer Head}/${infer Tail}` - ? RouteSegment | `/${Head}` - : `/${T}`; diff --git a/libs/router/index.ts b/libs/router/index.ts index ebafeee5..b5fb3b9f 100644 --- a/libs/router/index.ts +++ b/libs/router/index.ts @@ -1,14 +1,7 @@ export * from './AppRoutes'; export * from './getFileName'; export * from './getFileType'; -export * from './Link'; -export * from './Navigate'; +export type { LinkProps } from './LinkProps'; export * from './PathParams'; -export * from './Route'; -export * from './RouteSegment'; export * from './useHash'; -export * from './useLocation'; -export * from './useMatches'; -export * from './useNavigate'; -export * from './useParams'; -export { Outlet, ReactLocation, Router } from '@tanstack/react-location'; +export * from '@tanstack/react-router'; diff --git a/libs/router/useHash.tsx b/libs/router/useHash.tsx index e8a18bd4..14c88f21 100644 --- a/libs/router/useHash.tsx +++ b/libs/router/useHash.tsx @@ -1,15 +1,13 @@ -import { useNavigate } from '@tanstack/react-location'; - -import { useLocation } from './useLocation'; +import { useLocation, useNavigate } from '@tanstack/react-router'; export const useHash = () => { const location = useLocation(); const navigate = useNavigate(); const setHash = (hash: string) => { - navigate({ hash }); + return navigate({ hash }); }; - const hash = location.current.hash; + const hash = location.hash; return [hash, setHash] as const; }; diff --git a/libs/router/useLocation.ts b/libs/router/useLocation.ts deleted file mode 100644 index ca184dc5..00000000 --- a/libs/router/useLocation.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useLocation as useReactLocation } from '@tanstack/react-location'; - -import type { LocationGenerics } from './LocationGenerics'; - -export const useLocation = useReactLocation; diff --git a/libs/router/useMatches.tsx b/libs/router/useMatches.tsx deleted file mode 100644 index 7902ff98..00000000 --- a/libs/router/useMatches.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useMatches as useReactLocationMatches } from '@tanstack/react-location'; - -import type { LocationGenerics } from './LocationGenerics'; - -export const useMatches = () => { - const matches = useReactLocationMatches(); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - return matches.map(m => ({ ...m, route: m.route ?? {} })); -}; diff --git a/libs/router/useNavigate.tsx b/libs/router/useNavigate.tsx deleted file mode 100644 index 24a099d8..00000000 --- a/libs/router/useNavigate.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useNavigate as useReactNavigate } from '@tanstack/react-location'; - -import type { AppRoute } from './AppRoutes'; -import type { LocationGenerics } from './LocationGenerics'; - -const typedUseReactNavigate = useReactNavigate; - -type Option = Parameters>[0]; -type NavigateOptions = Omit & { - to: AppRoute; -}; -type Navigate = (options: NavigateOptions) => void; - -export const useNavigate = (): Navigate => { - return useReactNavigate(); -}; diff --git a/libs/router/useParams.ts b/libs/router/useParams.ts deleted file mode 100644 index e92b5378..00000000 --- a/libs/router/useParams.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useMatch } from '@tanstack/react-location'; - -import type { LocationGenerics } from './LocationGenerics'; - -export const useParams = () => { - return useMatch().params; -}; diff --git a/package-lock.json b/package-lock.json index e488e6a7..b71ed88e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@mantine/modals": "6.0.21", "@mantine/notifications": "6.0.21", "@mantine/styles": "6.0.21", - "@tanstack/react-location": "3.7.4", + "@tanstack/react-router": "1.47.1", "@tanstack/react-table": "8.20.1", "@total-typescript/ts-reset": "0.5.1", "ahooks": "3.8.1", @@ -63,6 +63,8 @@ "@storybook/react": "8.2.8", "@storybook/react-vite": "8.2.8", "@storybook/test": "8.2.8", + "@tanstack/router-devtools": "1.47.1", + "@tanstack/router-plugin": "1.47.0", "@testing-library/cypress": "10.0.2", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", @@ -10518,14 +10520,29 @@ "@svgr/core": "*" } }, - "node_modules/@tanstack/react-location": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@tanstack/react-location/-/react-location-3.7.4.tgz", - "integrity": "sha512-6rH2vNHGr0uyeUz5ZHvWMYjeYKGgIKFzvs5749QtnS9f+FU7t7fQE0hKZAzltBZk82LT7iYbcHBRyUg2lW13VA==", + "node_modules/@tanstack/history": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.45.3.tgz", + "integrity": "sha512-n4XXInV9irIq0obRvINIkESkGk280Q+xkIIbswmM0z9nAu2wsIRZNvlmPrtYh6bgNWtItOWWoihFUjLTW8g6Jg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.47.1.tgz", + "integrity": "sha512-pHV6jgUlTLVyqD4/pARvOYFVI3XT1Qx7m+cCGTMkM8Jw/4YHFA4hQI7VFjvEq8CpfTvVYP1lzdQVly6xYV8aag==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.16.7", - "history": "^5.2.0" + "@tanstack/history": "1.45.3", + "@tanstack/react-store": "^0.5.5", + "tiny-invariant": "^1.3.3", + "tiny-warning": "^1.0.3" }, "engines": { "node": ">=12" @@ -10535,8 +10552,26 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.5.5.tgz", + "integrity": "sha512-1orYXGatBqXCYKuroFwV8Ll/6aDa5E3pU6RR4h7RvRk7TmxF1+zLCsWALZaeijXkySNMGmvawSbUXRypivg2XA==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.5.5", + "use-sync-external-store": "^1.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" } }, "node_modules/@tanstack/react-table": { @@ -10576,6 +10611,116 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@tanstack/router-devtools": { + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.47.1.tgz", + "integrity": "sha512-8mXuvBUoydvgh+aoK3cDpsf2/CaHPRTub3sSGkhJUm/FgUK6+1M8XAzxq35KJlL33Cse6Ufj8Xck50Irm3m5Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "goober": "^2.1.14" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-router": "^1.47.1", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@tanstack/router-devtools/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.47.0.tgz", + "integrity": "sha512-b2xhsD7CGO8fwdqqHJV5vGykNMckeIXQ2TBGinmi7GLsRDnFkxjnHy6vPXSIPEnnZb5n0ePB4g1ggofvIMKdYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier": "^3.3.3", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-plugin": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.47.0.tgz", + "integrity": "sha512-2MOpkHQO5HWwgbhaxmo+HbmUPGBc3X+0faJAUxb7Odc4qfX+s7OfZ0dtyh02z1uIRXETh0kZoyuNokO/ctDmew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "@tanstack/router-generator": "^1.47.0", + "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.6.8", + "@types/babel__template": "^7.4.4", + "@types/babel__traverse": "^7.20.6", + "babel-dead-code-elimination": "^1.0.6", + "chokidar": "^3.6.0", + "unplugin": "^1.11.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": ">=0.7.9", + "vite": ">=5.0.13", + "webpack": ">=5.92.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@tanstack/store": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.5.5.tgz", + "integrity": "sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/table-core": { "version": "8.20.1", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.1.tgz", @@ -12489,6 +12634,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.6.tgz", + "integrity": "sha512-JxFi9qyRJpN0LjEbbjbN8g0ux71Qppn9R8Qe3k6QzHg2CaKsbUQtbn307LQGiDLGjV6JCtEFqfxzVig9MyDCHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -17836,6 +17994,16 @@ "dev": true, "license": "MIT" }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -18188,15 +18356,6 @@ "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", "license": "MIT" }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -25498,7 +25657,12 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, "node_modules/tinybench": { @@ -26465,6 +26629,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", diff --git a/package.json b/package.json index a2be57a7..909f19f5 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,11 @@ "@graphiql/toolkit": "0.9.2", "@hookform/resolvers": "3.9.0", "@mantine/core": "6.0.21", - "@mantine/styles": "6.0.21", "@mantine/hooks": "6.0.21", "@mantine/modals": "6.0.21", "@mantine/notifications": "6.0.21", - "@tanstack/react-location": "3.7.4", + "@mantine/styles": "6.0.21", + "@tanstack/react-router": "1.47.1", "@tanstack/react-table": "8.20.1", "@total-typescript/ts-reset": "0.5.1", "ahooks": "3.8.1", @@ -83,6 +83,8 @@ "@storybook/react": "8.2.8", "@storybook/react-vite": "8.2.8", "@storybook/test": "8.2.8", + "@tanstack/router-devtools": "1.47.1", + "@tanstack/router-plugin": "1.47.0", "@testing-library/cypress": "10.0.2", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", diff --git a/tsconfig.json b/tsconfig.json index 3d7f45ca..a66bd695 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,7 @@ "@camp/hooks": ["./app/hooks/index.ts"], "@camp/icons": ["./libs/icons/index.ts"], "@camp/infra": ["./libs/infra/index.ts"], - "@camp/messages": ["./app/messages.ts"], + "@camp/messages": ["./libs/pages/messages.ts"], "@camp/pages/*": ["./libs/pages/*"], "@camp/router": ["./libs/router/index.ts"], "@camp/shared-components": ["./libs/shared-components/index.ts"], diff --git a/vite.config.ts b/vite.config.ts index f5871cbe..324620ad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,9 @@ /// +import path from 'node:path'; + import { Config } from '@fullstacksjs/config'; import { compact } from '@fullstacksjs/toolbox'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; import basicSsl from '@vitejs/plugin-basic-ssl'; import react from '@vitejs/plugin-react'; import type { UserConfig } from 'vite'; @@ -32,6 +35,10 @@ export const config = ({ https = true }: Options = {}): UserConfig => ({ checker({ typescript: true }), tsconfigPaths(), cypressAliases(), + TanStackRouterVite({ + routesDirectory: path.join(__dirname, 'app/routes'), + generatedRouteTree: path.join(__dirname, 'app/routeTree.gen.ts'), + }), react({ exclude: /\.stories\.(t|j)sx?$/ }), svgr(), ]),