create-aron-app 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -31
- package/dist/index.js +38 -49
- package/package.json +3 -3
- package/templates/.cursor/rules/backend.mdc +112 -0
- package/templates/.cursor/rules/coding_standards.mdc +85 -4
- package/templates/.cursor/rules/frontend_architecture.mdc +334 -0
- package/templates/.github/workflows/ci.yml +17 -6
- package/templates/apps/{react-router → web}/.react-router/types/+routes.ts +11 -6
- package/templates/apps/{react-router/.react-router/types/src/routes/(dashboard)/todos → web/.react-router/types/src/routes/(dashboard)/(todos)}/+types/[id].ts +5 -2
- package/templates/apps/{react-router/.react-router/types/src/routes/(dashboard)/todos → web/.react-router/types/src/routes/(dashboard)/(todos)}/+types/index.ts +5 -2
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/layout.ts +65 -0
- package/templates/apps/{react-router → web}/project.json +11 -4
- package/templates/apps/{react-router → web}/react-router.config.ts +1 -1
- package/templates/apps/web/src/libs/convex_query_client.ts +11 -0
- package/templates/apps/web/src/libs/react_query_client.ts +17 -0
- package/templates/apps/web/src/libs/server/auth.ts +32 -0
- package/templates/apps/web/src/libs/server/protected.ts +17 -0
- package/templates/apps/web/src/providers/api_auth_provider.tsx +26 -0
- package/templates/apps/web/src/providers/global_provider.tsx +28 -0
- package/templates/apps/web/src/providers/navigation_loading_bar_provider.tsx +72 -0
- package/templates/apps/web/src/root.tsx +68 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/[id].tsx +33 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/index.tsx +26 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/layout.tsx +9 -0
- package/templates/apps/{react-router → web}/src/routes/(dashboard)/index.tsx +3 -2
- package/templates/apps/web/src/routes/(dashboard)/layout.tsx +20 -0
- package/templates/apps/{react-router → web}/src/routes.ts +4 -2
- package/templates/apps/{nextjs → web}/src/surfaces/sidebar/install.tsx +1 -5
- package/templates/apps/{react-router → web}/src/surfaces/sidebar/layout.tsx +24 -15
- package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos.tsx +1 -1
- package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos_controller.ts +1 -1
- package/templates/apps/web/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/bootstrap.ts +4 -5
- package/templates/apps/{nextjs → web}/src/surfaces/todos/single_todo/header/create.tsx +4 -6
- package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo_controller.ts +1 -1
- package/templates/biome.json +5 -0
- package/templates/bun.lock +11 -1281
- package/templates/nx.json +0 -11
- package/templates/package.json +2 -3
- package/templates/shared/assets/src/styles/global.css +14 -8
- package/templates/shared/ui/src/base/collapsible.tsx +31 -0
- package/templates/shared/ui/src/base/hover-card.tsx +42 -0
- package/templates/shared/ui/src/base/input-group.tsx +168 -0
- package/templates/shared/ui/src/base/panel.tsx +93 -0
- package/templates/shared/ui/src/hooks/use_mobile.tsx +1 -1
- package/templates/shared/ui/src/hooks/use_query_params.tsx +6 -7
- package/templates/shared/ui/tsconfig.json +1 -1
- package/templates/shared/utils/src/convex.ts +2 -1
- package/templates/tsconfig.base.json +2 -4
- package/templates/.cursor/commands/builder.md +0 -0
- package/templates/.cursor/rules/api_architecture.mdc +0 -262
- package/templates/.cursor/rules/convex_rules.mdc +0 -331
- package/templates/.cursor/rules/frontend_architecture_core.mdc +0 -495
- package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +0 -458
- package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +0 -473
- package/templates/apps/nextjs/.env.example +0 -10
- package/templates/apps/nextjs/index.d.ts +0 -6
- package/templates/apps/nextjs/next-env.d.ts +0 -5
- package/templates/apps/nextjs/next.config.js +0 -22
- package/templates/apps/nextjs/postcss.config.js +0 -17
- package/templates/apps/nextjs/project.json +0 -22
- package/templates/apps/nextjs/src/app/(auth)/layout.tsx +0 -21
- package/templates/apps/nextjs/src/app/(auth)/not-allowed/page.tsx +0 -23
- package/templates/apps/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -15
- package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +0 -22
- package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +0 -12
- package/templates/apps/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +0 -26
- package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +0 -19
- package/templates/apps/nextjs/src/app/app.css +0 -3
- package/templates/apps/nextjs/src/app/layout.tsx +0 -26
- package/templates/apps/nextjs/src/middleware.ts +0 -18
- package/templates/apps/nextjs/src/providers/convex_provider.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/home/home.tsx +0 -27
- package/templates/apps/nextjs/src/surfaces/home/layout.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +0 -34
- package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +0 -118
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +0 -19
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +0 -22
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -25
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -33
- package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx +0 -39
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +0 -28
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -42
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +0 -29
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +0 -61
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +0 -21
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +0 -49
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +0 -70
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -56
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -99
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +0 -32
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +0 -22
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +0 -27
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +0 -55
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +0 -38
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +0 -49
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +0 -29
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +0 -13
- package/templates/apps/nextjs/src/utils/auth.ts +0 -18
- package/templates/apps/nextjs/src/utils/convex.ts +0 -11
- package/templates/apps/nextjs/src/utils/font.ts +0 -9
- package/templates/apps/nextjs/tsconfig.json +0 -42
- package/templates/apps/react-router/src/providers/api_auth_provider.tsx +0 -40
- package/templates/apps/react-router/src/root.tsx +0 -37
- package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +0 -37
- package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +0 -19
- package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +0 -19
- package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +0 -9
- package/templates/apps/react-router/src/surfaces/home/install.tsx +0 -17
- package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +0 -23
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -31
- package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +0 -18
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -11
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +0 -32
- /package/templates/apps/{react-router → web}/.env.example +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/+future.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/+server-build.d.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/+types/root.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/+types/layout.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/index.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/layout.ts +0 -0
- /package/templates/apps/{react-router → web}/postcss.config.js +0 -0
- /package/templates/apps/{react-router → web}/public/favicon.ico +0 -0
- /package/templates/apps/{react-router → web}/src/app.css +0 -0
- /package/templates/apps/{react-router → web}/src/components/error_boundary.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/routes/(auth)/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/routes/(auth)/sign-in/index.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/home/bootstrap.ts +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/home.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/home/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/sidebar.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/header/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/header/header.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo.tsx +0 -0
- /package/templates/apps/{react-router → web}/tsconfig.json +0 -0
- /package/templates/apps/{react-router → web}/vite.config.ts +0 -0
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Frontend surface architecture — React Router + Convex variant. Covers MobX decorator setup, clientLoader bootstrap pattern, and React Router-specific wiring. Extends frontend_architecture_core.
|
|
3
|
-
globs: apps/react-router/src/**
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Frontend Surface Architecture — React Router + Convex
|
|
8
|
-
|
|
9
|
-
This document covers the React Router-specific implementation of the surface architecture defined in `frontend_architecture_core`. Read that document first for the layer model, MobX philosophy, and component rules.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Setup
|
|
14
|
-
|
|
15
|
-
### MobX
|
|
16
|
-
|
|
17
|
-
MobX 2022.3 (Stage 3) decorators. Requires TypeScript 5.0+ with `experimentalDecorators` **off**.
|
|
18
|
-
|
|
19
|
-
**`tsconfig.json`:**
|
|
20
|
-
```json
|
|
21
|
-
{ "compilerOptions": { "experimentalDecorators": false } }
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**`vite.config.ts`:**
|
|
25
|
-
```typescript
|
|
26
|
-
react({ babel: { plugins: [["@babel/plugin-proposal-decorators", { version: "2023-05" }]] } })
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Rules:
|
|
30
|
-
- Every `@observable` field must use the `accessor` keyword
|
|
31
|
-
- No `makeObservable` or `makeAutoObservable` needed when using decorators
|
|
32
|
-
- Direct observable mutations inside a Promise `.then` must be wrapped in an action — `@action` decorated methods satisfy this requirement, so no additional `runInAction` is needed
|
|
33
|
-
|
|
34
|
-
### Convex Client
|
|
35
|
-
|
|
36
|
-
The `ConvexReactClient` singleton is exported from the provider so presenters can call mutations directly outside of React:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// src/providers/api_auth_provider.tsx
|
|
40
|
-
export const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Rules:
|
|
44
|
-
- Presenters import `convex` from `@/web/providers/api_auth_provider` to call `convex.mutation()` directly
|
|
45
|
-
- `create.tsx` files use `useQuery(convexQuery(...))` and `useMutation({ mutationFn: useConvexMutation(...) })` for reactive data and hook-based mutations
|
|
46
|
-
|
|
47
|
-
### TanStack Query client for `clientLoader`
|
|
48
|
-
|
|
49
|
-
Export a **module singleton** `queryClient` from the same provider module that wires `ConvexQueryClient` (e.g. `api_auth_provider.tsx`). Dashboard and page `clientLoader` functions import it and call `queryClient.ensureQueryData(convexQuery(...))` so bootstrap can warm the cache before render.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## App routing: `(auth)` vs `(dashboard)`
|
|
54
|
-
|
|
55
|
-
Mirror the filesystem shape: **`routes/(auth)/…`** and **`routes/(dashboard)/…`**. Parentheses are route-group folders only; **URLs are declared in `routes.ts`**, not by folder names.
|
|
56
|
-
|
|
57
|
-
**`routes.ts` shape:**
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
import { index, layout, prefix, type RouteConfig, route } from "@react-router/dev/routes";
|
|
61
|
-
|
|
62
|
-
export default [
|
|
63
|
-
layout("./routes/(auth)/layout.tsx", [
|
|
64
|
-
route("sign-in/*", "./routes/(auth)/sign-in/index.tsx"),
|
|
65
|
-
]),
|
|
66
|
-
layout("./routes/(dashboard)/layout.tsx", [
|
|
67
|
-
index("./routes/(dashboard)/index.tsx"),
|
|
68
|
-
...prefix("todos", [
|
|
69
|
-
index("./routes/(dashboard)/todos/index.tsx"),
|
|
70
|
-
route(":id", "./routes/(dashboard)/todos/[id].tsx"),
|
|
71
|
-
]),
|
|
72
|
-
]),
|
|
73
|
-
] satisfies RouteConfig;
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
- One **`layout()`** for `(auth)` (sign-in only, no sidebar).
|
|
77
|
-
- One **`layout()`** for `(dashboard)` with `index` + `prefix("todos", …)`.
|
|
78
|
-
|
|
79
|
-
| Group | Role |
|
|
80
|
-
| --- | --- |
|
|
81
|
-
| `(auth)` | Sign-in pages. **No** sidebar. Optional `(auth)/layout.tsx` for centered chrome. |
|
|
82
|
-
| `(dashboard)` | **`clientLoader`** validates session (e.g. `ensureQueryData` on an auth-gated Convex query; on failure `redirect("/sign-in")`). Renders `SidebarProvider` + **Sidebar** surface + `SidebarInset` + `<Outlet />`. |
|
|
83
|
-
|
|
84
|
-
**Root** (`root.tsx`): providers + `<Outlet />` only — **no** sidebar and **no** per-route auth gate here.
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## Shell surface vs page surfaces
|
|
89
|
-
|
|
90
|
-
- **Shell:** `surfaces/sidebar/` — mounted only from `(dashboard)/layout.tsx`. **`SidebarLayout`** + **`installSidebar`** + lazy section creates (same mental model as Next).
|
|
91
|
-
- **Pages:** `surfaces/home/`, `surfaces/todos/all_todos/`, `surfaces/todos/single_todo/` — each route file imports `bootstrap*` and mounts the surface entry component with `loaderData`.
|
|
92
|
-
|
|
93
|
-
Naming matches `frontend_architecture_core`: folders **`all_todos`**, **`single_todo`**; components **`Home`**, **`AllTodos`**, **`SingleTodo`** (optional `Surface` suffix if used consistently).
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Sidebar surface (layout chrome)
|
|
98
|
-
|
|
99
|
-
Same structure as the Next.js rule doc: `sidebar.tsx` (entry), `layout.tsx` (`SidebarLayout` / `createSidebarLayout`), `install.tsx`, section folders with `create.tsx` + dumb `*.tsx`. **Slots are product-dependent.**
|
|
100
|
-
|
|
101
|
-
**Install timing:** Layout-mounted sidebar uses **`useEffect`** to call `installSidebar` once. **Route-mounted** surfaces use the **synchronous `useRef` install guard**.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Route → surface map
|
|
106
|
-
|
|
107
|
-
| URL | Mount |
|
|
108
|
-
| --- | --- |
|
|
109
|
-
| `/` | `Home` |
|
|
110
|
-
| `/todos` | `AllTodos` |
|
|
111
|
-
| `/todos/:id` | `SingleTodo` |
|
|
112
|
-
| `(dashboard)` layout | `Sidebar` |
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Dashboard layout `clientLoader` (auth gate)
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
import { redirect } from "react-router";
|
|
120
|
-
|
|
121
|
-
import { convexQuery } from "@convex-dev/react-query";
|
|
122
|
-
|
|
123
|
-
import { api } from "@/api/_generated/api";
|
|
124
|
-
import type { Route } from "@/router/app/routes/(dashboard)/+types/layout";
|
|
125
|
-
import { queryClient } from "@/web/providers/api_auth_provider";
|
|
126
|
-
|
|
127
|
-
export async function clientLoader(_args: Route.ClientLoaderArgs) {
|
|
128
|
-
try {
|
|
129
|
-
await queryClient.ensureQueryData(convexQuery(api.todos.crud.listTodos, {}));
|
|
130
|
-
} catch {
|
|
131
|
-
return redirect("/sign-in");
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Rules:
|
|
138
|
-
|
|
139
|
-
- Use a **real authenticated Convex query** (or dedicated `api.user.me`-style query) so unauthenticated users fail fast.
|
|
140
|
-
- Import **`queryClient`** from the provider module — do not instantiate a new `QueryClient` in the loader.
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Route + Bootstrap Pattern
|
|
145
|
-
|
|
146
|
-
Each route that mounts a surface uses a **bootstrap** to validate params, handle redirects, and optionally **warm the TanStack Query cache** via `queryClient.ensureQueryData(convexQuery(...))` (import the **`queryClient` singleton** from `@/web/providers/api_auth_provider`). The bootstrap return value is passed into the surface as `bootstrap`; section `create.tsx` files spread `initialData: bootstrap.*` into `useQuery` so the first paint matches the server-loaded data.
|
|
147
|
-
|
|
148
|
-
### Router Types
|
|
149
|
-
|
|
150
|
-
The `@/router` alias maps to `./apps/react-router/.react-router/types/*`. After `react-router typegen`, import generated types from paths that mirror the filesystem, e.g.:
|
|
151
|
-
|
|
152
|
-
- `@/router/app/routes/(dashboard)/+types/layout` — dashboard layout `clientLoader`
|
|
153
|
-
- `@/router/app/routes/(dashboard)/todos/+types/[id]` — todo detail route
|
|
154
|
-
|
|
155
|
-
Use:
|
|
156
|
-
|
|
157
|
-
- `Route.ClientLoaderArgs` — params, request, etc. for `clientLoader`
|
|
158
|
-
- `Route.MetaArgs` — includes `loaderData` from `clientLoader`
|
|
159
|
-
- `Route.ComponentProps` — includes `loaderData` for the page component
|
|
160
|
-
|
|
161
|
-
### Route File (`routes/(dashboard)/todos/[id].tsx`)
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import type { Route } from "@/router/app/routes/(dashboard)/todos/+types/[id]";
|
|
165
|
-
import { bootstrapSingleTodo } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
166
|
-
import { SingleTodo } from "@/web/surfaces/todos/single_todo/single_todo";
|
|
167
|
-
|
|
168
|
-
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
|
|
169
|
-
return bootstrapSingleTodo({ params });
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function meta({ loaderData: bootstrap }: Route.MetaArgs) {
|
|
173
|
-
return [
|
|
174
|
-
{ title: "Todo — App Name" },
|
|
175
|
-
{ name: "description", content: "" },
|
|
176
|
-
];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default function SingleTodoPage({ loaderData: bootstrap }: Route.ComponentProps) {
|
|
180
|
-
return <SingleTodo bootstrap={bootstrap} />;
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Rules:
|
|
185
|
-
- Types come from `@/router/app/routes/(dashboard)/…/+types/…` — React Router generates these from the route module path
|
|
186
|
-
- `clientLoader` runs before render; bootstrap is the single source of initial context
|
|
187
|
-
- `meta` receives `loaderData` (bootstrap output) for document title and meta tags
|
|
188
|
-
- Component receives `loaderData` as `bootstrap` and passes it into the surface
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## Layer Reference (React Router-specific)
|
|
193
|
-
|
|
194
|
-
### `bootstrap.ts`
|
|
195
|
-
|
|
196
|
-
Validates params, handles auth redirects, and pre-populates the TanStack Query cache with the primary entity. The fetched data is passed as `initialData` to queries in the `create` layer.
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import { redirect } from "react-router";
|
|
200
|
-
|
|
201
|
-
import { convexQuery } from "@convex-dev/react-query";
|
|
202
|
-
import type { Route } from "@/router/app/routes/(dashboard)/todos/+types/[id]";
|
|
203
|
-
import type { TodoId } from "@/api/todos/types";
|
|
204
|
-
import { api } from "@/api/_generated/api";
|
|
205
|
-
import { queryClient } from "@/web/providers/api_auth_provider";
|
|
206
|
-
|
|
207
|
-
export type SingleTodoBootstrapArgs = Pick<Route.ClientLoaderArgs, "params">;
|
|
208
|
-
export type SingleTodoBootstrap = Awaited<ReturnType<typeof bootstrapSingleTodo>>;
|
|
209
|
-
|
|
210
|
-
export const bootstrapSingleTodo = async ({ params }: SingleTodoBootstrapArgs) => {
|
|
211
|
-
if (!params.id) {
|
|
212
|
-
throw redirect("/todos");
|
|
213
|
-
}
|
|
214
|
-
const todoId = params.id as TodoId;
|
|
215
|
-
const todo = await queryClient.ensureQueryData(
|
|
216
|
-
convexQuery(api.todos.crud.getTodo, { todoId }),
|
|
217
|
-
);
|
|
218
|
-
return { todoId, todo };
|
|
219
|
-
};
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Rules:
|
|
223
|
-
- Use `queryClient.ensureQueryData` with `convexQuery` — pre-populates the TanStack Query cache
|
|
224
|
-
- Import **`queryClient`** from `@/web/providers/api_auth_provider` (singleton shared with `QueryClientProvider`)
|
|
225
|
-
- Auth failures/redirects belong here, not in components
|
|
226
|
-
|
|
227
|
-
### `[feature].tsx` — Surface Entry Component
|
|
228
|
-
|
|
229
|
-
No `"use client"` directive — React Router is fully client-side. **Does not instantiate controllers** — that happens in `install.tsx`.
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import { useMemo, useRef } from "react";
|
|
233
|
-
import { useNavigate } from "react-router";
|
|
234
|
-
|
|
235
|
-
import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
236
|
-
import { installSingleTodo } from "@/web/surfaces/todos/single_todo/install";
|
|
237
|
-
import { createLayout } from "@/web/surfaces/todos/single_todo/layout";
|
|
238
|
-
|
|
239
|
-
export type SingleTodoProps = {
|
|
240
|
-
bootstrap: SingleTodoBootstrap;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
export const SingleTodo = ({ bootstrap }: SingleTodoProps) => {
|
|
244
|
-
const navigate = useNavigate();
|
|
245
|
-
const { layout, Layout } = useMemo(() => createLayout(), []);
|
|
246
|
-
const installed = useRef(false);
|
|
247
|
-
|
|
248
|
-
if (!installed.current) {
|
|
249
|
-
installed.current = true;
|
|
250
|
-
installSingleTodo({ layout, bootstrap, navigate });
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return <Layout />;
|
|
254
|
-
};
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
Rules:
|
|
258
|
-
- Use `useNavigate` from `react-router`
|
|
259
|
-
- No `"use client"` directive needed
|
|
260
|
-
- **Never instantiate controllers here** — pass deps to install, which creates the controller
|
|
261
|
-
- All imports use `@/` aliases — no relative imports
|
|
262
|
-
|
|
263
|
-
### `install.tsx`
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
import type { NavigateFunction } from "react-router";
|
|
267
|
-
|
|
268
|
-
import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
269
|
-
import type { SingleTodoLayoutController } from "@/web/surfaces/todos/single_todo/layout";
|
|
270
|
-
import { SingleTodoController } from "@/web/surfaces/todos/single_todo/single_todo_controller";
|
|
271
|
-
|
|
272
|
-
export type InstallSingleTodoOpts = {
|
|
273
|
-
layout: SingleTodoLayoutController;
|
|
274
|
-
bootstrap: SingleTodoBootstrap;
|
|
275
|
-
navigate: NavigateFunction;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
export const installSingleTodo = ({ layout, bootstrap, navigate }: InstallSingleTodoOpts) => {
|
|
279
|
-
const controller = new SingleTodoController();
|
|
280
|
-
|
|
281
|
-
import("@/web/surfaces/todos/single_todo/header/create").then(({ createHeader }) => {
|
|
282
|
-
layout.setHeader(createHeader({ navigate }));
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
import("@/web/surfaces/todos/single_todo/main/create").then(({ createMain }) => {
|
|
286
|
-
layout.setMain(createMain({ controller, bootstrap }));
|
|
287
|
-
});
|
|
288
|
-
};
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Rules:
|
|
292
|
-
- Use `NavigateFunction` from `react-router` for the navigate type
|
|
293
|
-
- **Controller instantiated here** — exactly once
|
|
294
|
-
- Dynamic `import()` paths use `@/` aliases
|
|
295
|
-
|
|
296
|
-
### `layout.tsx`
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
import { action, observable } from "mobx";
|
|
300
|
-
import { observer } from "mobx-react-lite";
|
|
301
|
-
import type { ComponentType } from "react";
|
|
302
|
-
|
|
303
|
-
import { Skeleton } from "@/ui/base/skeleton";
|
|
304
|
-
|
|
305
|
-
export class LayoutController {
|
|
306
|
-
@observable.ref
|
|
307
|
-
accessor Header: ComponentType | undefined = undefined;
|
|
308
|
-
|
|
309
|
-
@observable.ref
|
|
310
|
-
accessor Main: ComponentType | undefined = undefined;
|
|
311
|
-
|
|
312
|
-
@action
|
|
313
|
-
setHeader(Header: ComponentType) {
|
|
314
|
-
this.Header = Header;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
@action
|
|
318
|
-
setMain(Main: ComponentType) {
|
|
319
|
-
this.Main = Main;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export const createLayout = () => {
|
|
324
|
-
const layout = new LayoutController();
|
|
325
|
-
return {
|
|
326
|
-
layout,
|
|
327
|
-
Layout: observer(() => {
|
|
328
|
-
const Header = layout.Header;
|
|
329
|
-
const Main = layout.Main;
|
|
330
|
-
return (
|
|
331
|
-
<div className="flex flex-col gap-8 w-full">
|
|
332
|
-
{Header ? <Header /> : <Skeleton className="h-12" />}
|
|
333
|
-
{Main ? <Main /> : <Skeleton className="h-64" />}
|
|
334
|
-
</div>
|
|
335
|
-
);
|
|
336
|
-
}),
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
Rules:
|
|
342
|
-
- Use `@observable.ref accessor` for component slots
|
|
343
|
-
- `@action` decorated methods create an action boundary — calling them from Promise `.then` is safe without additional `runInAction`
|
|
344
|
-
|
|
345
|
-
### `[feature]_controller.ts`
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
import { action, computed, observable } from "mobx";
|
|
349
|
-
|
|
350
|
-
import type { TodoId } from "@/api/todos/types";
|
|
351
|
-
|
|
352
|
-
export class SingleTodoController {
|
|
353
|
-
@observable.ref
|
|
354
|
-
accessor isEditOpen = false;
|
|
355
|
-
|
|
356
|
-
@observable.ref
|
|
357
|
-
accessor selectedTodoId: TodoId | undefined = undefined;
|
|
358
|
-
|
|
359
|
-
@computed
|
|
360
|
-
get editTitle() {
|
|
361
|
-
return this.selectedTodoId ? "Edit Todo" : "New Todo";
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
@action
|
|
365
|
-
openEdit(todoId: TodoId) {
|
|
366
|
-
this.selectedTodoId = todoId;
|
|
367
|
-
this.isEditOpen = true;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
@action
|
|
371
|
-
closeEdit() {
|
|
372
|
-
this.isEditOpen = false;
|
|
373
|
-
this.selectedTodoId = undefined;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
### `[section]_presenter.ts`
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
import { action, computed, observable } from "mobx";
|
|
382
|
-
import type { NavigateFunction } from "react-router";
|
|
383
|
-
|
|
384
|
-
import { api } from "@/api/_generated/api";
|
|
385
|
-
import { convex } from "@/web/providers/api_auth_provider";
|
|
386
|
-
|
|
387
|
-
export type NewTodoPresenterOpts = {
|
|
388
|
-
navigate: NavigateFunction;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
export class NewTodoPresenter {
|
|
392
|
-
private readonly navigate: NavigateFunction;
|
|
393
|
-
|
|
394
|
-
constructor({ navigate }: NewTodoPresenterOpts) {
|
|
395
|
-
this.navigate = navigate;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
@observable
|
|
399
|
-
accessor input = "";
|
|
400
|
-
|
|
401
|
-
@observable
|
|
402
|
-
accessor isPending = false;
|
|
403
|
-
|
|
404
|
-
@computed
|
|
405
|
-
get isSubmitDisabled() {
|
|
406
|
-
return !this.input.trim() || this.isPending;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
@action
|
|
410
|
-
setInput(value: string) {
|
|
411
|
-
this.input = value;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
@action
|
|
415
|
-
async submit() {
|
|
416
|
-
this.isPending = true;
|
|
417
|
-
try {
|
|
418
|
-
await convex.mutation(api.todos.crud.createTodo, { title: this.input.trim() });
|
|
419
|
-
this.input = "";
|
|
420
|
-
} finally {
|
|
421
|
-
this.isPending = false;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
Rules:
|
|
428
|
-
- `@action` decorated methods satisfy the action boundary — mutations inside an async `@action` method are automatically tracked, no `runInAction` needed
|
|
429
|
-
|
|
430
|
-
### `[section]/create.tsx`
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
import { convexQuery } from "@convex-dev/react-query";
|
|
434
|
-
import { useQuery } from "@tanstack/react-query";
|
|
435
|
-
import { observer } from "mobx-react-lite";
|
|
436
|
-
|
|
437
|
-
import { api } from "@/api/_generated/api";
|
|
438
|
-
import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
439
|
-
import type { SingleTodoController } from "@/web/surfaces/todos/single_todo/single_todo_controller";
|
|
440
|
-
import { Main } from "@/web/surfaces/todos/single_todo/main/main";
|
|
441
|
-
|
|
442
|
-
export type CreateMainOpts = {
|
|
443
|
-
controller: SingleTodoController;
|
|
444
|
-
bootstrap: SingleTodoBootstrap;
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
export const createMain = ({ controller, bootstrap }: CreateMainOpts) => {
|
|
448
|
-
return observer(() => {
|
|
449
|
-
const { data: todo } = useQuery({
|
|
450
|
-
initialData: bootstrap.todo,
|
|
451
|
-
...convexQuery(api.todos.crud.getTodo, { todoId: bootstrap.todoId }),
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
return (
|
|
455
|
-
<Main
|
|
456
|
-
todo={todo}
|
|
457
|
-
onToggle={(isCompleted) =>
|
|
458
|
-
controller.updateTodo({
|
|
459
|
-
todoId: bootstrap.todoId,
|
|
460
|
-
isCompleted,
|
|
461
|
-
})
|
|
462
|
-
}
|
|
463
|
-
onEdit={() => controller.openEdit(bootstrap.todoId)}
|
|
464
|
-
/>
|
|
465
|
-
);
|
|
466
|
-
});
|
|
467
|
-
};
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
Rules:
|
|
471
|
-
- No `"use client"` directive needed
|
|
472
|
-
- Spread `convexQuery` with `initialData: bootstrap.[entity]` — eliminates loading flash, query stays reactive
|
|
473
|
-
- All imports use `@/` aliases — no relative imports
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
|
|
3
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
4
|
-
const { composePlugins, withNx } = require("@nx/next");
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
|
|
8
|
-
**/
|
|
9
|
-
const nextConfig = {
|
|
10
|
-
nx: {
|
|
11
|
-
// Set this to true if you would like to use SVGR
|
|
12
|
-
// See: https://github.com/gregberge/svgr
|
|
13
|
-
svgr: false,
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const plugins = [
|
|
18
|
-
// Add more Next.js plugins to this list if needed.
|
|
19
|
-
withNx,
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
module.exports = composePlugins(...plugins)(nextConfig);
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2025.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { join } = require("node:path");
|
|
6
|
-
|
|
7
|
-
// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
|
|
8
|
-
// option from your application's configuration (i.e. project.json).
|
|
9
|
-
//
|
|
10
|
-
// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
|
|
11
|
-
|
|
12
|
-
module.exports = {
|
|
13
|
-
plugins: {
|
|
14
|
-
"@tailwindcss/postcss": {},
|
|
15
|
-
autoprefixer: {},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nextjs",
|
|
3
|
-
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "apps/nextjs",
|
|
5
|
-
"projectType": "application",
|
|
6
|
-
"tags": [],
|
|
7
|
-
"targets": {
|
|
8
|
-
"dev": {
|
|
9
|
-
"executor": "nx:run-commands",
|
|
10
|
-
"options": {
|
|
11
|
-
"command": "bunx next dev apps/nextjs --port 5001"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"build": {
|
|
15
|
-
"executor": "@nx/next:build",
|
|
16
|
-
"options": {
|
|
17
|
-
"root": "apps/nextjs",
|
|
18
|
-
"outputPath": "dist/apps/nextjs"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2025.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import "../app.css";
|
|
6
|
-
|
|
7
|
-
import { Toaster } from "sonner";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type RootLayoutProps = {
|
|
11
|
-
children: React.ReactNode;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export default function AuthLayout({ children }: RootLayoutProps) {
|
|
15
|
-
return (
|
|
16
|
-
<main className="flex min-h-screen flex-col justify-center items-center overflow-hidden">
|
|
17
|
-
{children}
|
|
18
|
-
<Toaster />
|
|
19
|
-
</main>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2025.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { Metadata } from "next";
|
|
6
|
-
import Link from "next/link";
|
|
7
|
-
|
|
8
|
-
export const metadata: Metadata = {
|
|
9
|
-
title: "Not Allowed",
|
|
10
|
-
description: "You are not allowed to access this page.",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export default async function NotAllowed() {
|
|
14
|
-
return (
|
|
15
|
-
<div className="flex flex-col items-center justify-center h-screen">
|
|
16
|
-
<h1 className="text-2xl font-bold">Not Allowed</h1>
|
|
17
|
-
<p className="text-sm text-gray-500">You are not allowed to access this page.</p>
|
|
18
|
-
<Link href="/sign-in" className="text-blue-500">
|
|
19
|
-
Continue to login page
|
|
20
|
-
</Link>
|
|
21
|
-
</div>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2025.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { SignIn } from "@clerk/nextjs";
|
|
6
|
-
import type { Metadata } from "next";
|
|
7
|
-
|
|
8
|
-
export const metadata: Metadata = {
|
|
9
|
-
title: "Sign In",
|
|
10
|
-
description: "Sign In to your account",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export default function LoginPage() {
|
|
14
|
-
return <SignIn withSignUp={true} />;
|
|
15
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { SidebarInset, SidebarProvider } from "@/ui/base/side_bar";
|
|
6
|
-
import { Sidebar } from "@/web/surfaces/sidebar/sidebar";
|
|
7
|
-
|
|
8
|
-
type DashboardLayoutProps = {
|
|
9
|
-
children: React.ReactNode;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default function DashboardLayout({ children }: DashboardLayoutProps) {
|
|
13
|
-
return (
|
|
14
|
-
<SidebarProvider
|
|
15
|
-
style={{ "--sidebar-width": "265px" } as React.CSSProperties}
|
|
16
|
-
defaultOpen={true}
|
|
17
|
-
>
|
|
18
|
-
<Sidebar />
|
|
19
|
-
<SidebarInset>{children}</SidebarInset>
|
|
20
|
-
</SidebarProvider>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { bootstrapHome } from "@/web/surfaces/home/bootstrap";
|
|
6
|
-
import { Home } from "@/web/surfaces/home/home";
|
|
7
|
-
|
|
8
|
-
export default async function HomePage() {
|
|
9
|
-
const bootstrap = await bootstrapHome();
|
|
10
|
-
|
|
11
|
-
return <Home bootstrap={bootstrap} />;
|
|
12
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { Metadata } from "next";
|
|
6
|
-
|
|
7
|
-
import type { TodoId } from "@/api/todos/types";
|
|
8
|
-
import { bootstrapSingleTodo } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
9
|
-
import { SingleTodo } from "@/web/surfaces/todos/single_todo/single_todo";
|
|
10
|
-
|
|
11
|
-
type SingleTodoPageProps = {
|
|
12
|
-
params: Promise<{ id: TodoId }>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const metadata: Metadata = {
|
|
16
|
-
title: "Todo",
|
|
17
|
-
description: "Todo",
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export default async function SingleTodoPage({ params }: SingleTodoPageProps) {
|
|
21
|
-
const { id } = await params;
|
|
22
|
-
|
|
23
|
-
const bootstrap = await bootstrapSingleTodo({ todoId: id });
|
|
24
|
-
|
|
25
|
-
return <SingleTodo bootstrap={bootstrap} />;
|
|
26
|
-
}
|