create-aron-app 0.1.6 → 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 +39 -115
- package/package.json +7 -4
- package/templates/.cursor/rules/backend.mdc +112 -0
- package/templates/.cursor/rules/coding_standards.mdc +145 -0
- package/templates/.cursor/rules/frontend_architecture.mdc +334 -0
- package/templates/.github/workflows/ci.yml +40 -0
- package/templates/apps/api/_generated/api.d.ts +57 -0
- package/templates/apps/api/_generated/api.js +23 -0
- package/templates/apps/api/_generated/dataModel.d.ts +60 -0
- package/templates/apps/api/_generated/server.d.ts +143 -0
- package/templates/apps/api/_generated/server.js +93 -0
- package/templates/apps/api/http.ts +16 -0
- package/templates/apps/web/.env.example +10 -0
- package/templates/apps/web/.react-router/types/+future.ts +9 -0
- package/templates/apps/web/.react-router/types/+routes.ts +76 -0
- package/templates/apps/web/.react-router/types/+server-build.d.ts +18 -0
- package/templates/apps/web/.react-router/types/src/+types/root.ts +59 -0
- package/templates/apps/web/.react-router/types/src/routes/(auth)/+types/layout.ts +62 -0
- package/templates/apps/web/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +65 -0
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/[id].ts +68 -0
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/index.ts +68 -0
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/layout.ts +65 -0
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/+types/index.ts +65 -0
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/+types/layout.ts +62 -0
- package/templates/{react-router → apps/web}/project.json +9 -2
- package/templates/{react-router → apps/web}/react-router.config.ts +1 -1
- package/templates/apps/web/src/app.css +3 -0
- package/templates/{react-router → apps/web}/src/components/error_boundary.tsx +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/{react-router/src/routes/auth → apps/web/src/routes/(auth)}/layout.tsx +1 -1
- 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/web/src/routes/(dashboard)/index.tsx +20 -0
- package/templates/apps/web/src/routes/(dashboard)/layout.tsx +20 -0
- package/templates/apps/web/src/routes.ts +14 -0
- package/templates/apps/web/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/web/src/surfaces/home/home.tsx +25 -0
- package/templates/apps/web/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/web/src/surfaces/home/layout.tsx +35 -0
- package/templates/apps/web/src/surfaces/home/main/create.tsx +32 -0
- package/templates/apps/web/src/surfaces/sidebar/install.tsx +19 -0
- package/templates/apps/web/src/surfaces/sidebar/layout.tsx +119 -0
- package/templates/apps/web/src/surfaces/sidebar/nav_main/create.tsx +31 -0
- package/templates/apps/web/src/surfaces/sidebar/nav_main/nav_main.tsx +42 -0
- package/templates/apps/web/src/surfaces/sidebar/sidebar.tsx +18 -0
- package/templates/apps/web/src/surfaces/sidebar/user_menu/create.tsx +26 -0
- package/templates/{react-router/src/layouts/sidebar/sidebar_aside → apps/web/src/surfaces/sidebar/user_menu}/user_menu.tsx +13 -9
- package/templates/apps/web/src/surfaces/todos/all_todos/all_todos.tsx +25 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/all_todos_controller.ts +47 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/header/create.tsx +21 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/install.tsx +20 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/layout.tsx +35 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/main/create.tsx +47 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/main/main.tsx +68 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +54 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +97 -0
- package/templates/apps/web/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/bootstrap.ts +35 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/header/create.tsx +24 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/header/header.tsx +25 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/layout.tsx +45 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/main/create.tsx +35 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/main/main.tsx +47 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/single_todo.tsx +27 -0
- package/templates/apps/web/src/surfaces/todos/single_todo/single_todo_controller.ts +16 -0
- package/templates/{react-router → apps/web}/vite.config.ts +27 -3
- package/templates/{_base/biome.json → biome.json} +12 -0
- package/templates/bun.lock +1917 -0
- package/templates/{_base/emails → emails}/project.json +1 -1
- package/templates/package.json +91 -0
- package/templates/{_base/shared → 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/{_base/shared → shared}/ui/src/hooks/use_mobile.tsx +1 -1
- package/templates/{_base/shared → shared}/ui/src/hooks/use_query_params.tsx +6 -7
- package/templates/{_base/shared → shared}/ui/tsconfig.json +1 -1
- package/templates/shared/utils/src/convex.ts +4 -0
- package/templates/{_base/tsconfig.base.json → tsconfig.base.json} +2 -1
- package/templates/_base/.cursor/commands/builder.md +0 -0
- package/templates/_base/.cursor/rules/api_architecture.mdc +0 -268
- package/templates/_base/.cursor/rules/coding_standards.mdc +0 -64
- package/templates/_base/.cursor/rules/convex_rules.mdc +0 -675
- package/templates/_base/.cursor/rules/frontend_rules.mdc +0 -268
- package/templates/_base/.env.convex.example +0 -3
- package/templates/_base/.github/workflows/ci.yml +0 -29
- package/templates/_base/_gitignore +0 -58
- package/templates/_base/package.json +0 -73
- package/templates/_base/shared/utils/src/convex.ts +0 -3
- package/templates/nextjs/.env.example +0 -8
- package/templates/nextjs/index.d.ts +0 -6
- package/templates/nextjs/next-env.d.ts +0 -5
- package/templates/nextjs/next.config.js +0 -22
- package/templates/nextjs/postcss.config.js +0 -17
- package/templates/nextjs/project.json +0 -22
- package/templates/nextjs/src/app/(auth)/layout.tsx +0 -21
- package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +0 -22
- package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -15
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +0 -27
- package/templates/nextjs/src/app/(dashboard)/page.tsx +0 -5
- package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +0 -23
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +0 -16
- package/templates/nextjs/src/app/app.css +0 -3
- package/templates/nextjs/src/app/layout.tsx +0 -26
- package/templates/nextjs/src/convex.ts +0 -11
- package/templates/nextjs/src/middleware.ts +0 -18
- package/templates/nextjs/src/providers/convex_provider.tsx +0 -44
- package/templates/nextjs/src/surfaces/home_surface.tsx +0 -22
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +0 -97
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +0 -107
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +0 -90
- package/templates/nextjs/src/ui/sidebar/nav_link.tsx +0 -36
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +0 -125
- package/templates/nextjs/src/utils/font.ts +0 -9
- package/templates/nextjs/tsconfig.json +0 -42
- package/templates/react-router/.env.example +0 -8
- package/templates/react-router/src/app.css +0 -3
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +0 -76
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +0 -22
- package/templates/react-router/src/providers/api_auth_provider.tsx +0 -38
- package/templates/react-router/src/root.tsx +0 -37
- package/templates/react-router/src/routes/index.tsx +0 -9
- package/templates/react-router/src/routes/layout.tsx +0 -26
- package/templates/react-router/src/routes/todos/[id].tsx +0 -22
- package/templates/react-router/src/routes/todos/index.tsx +0 -13
- package/templates/react-router/src/routes.ts +0 -12
- package/templates/react-router/src/surfaces/home_surface.tsx +0 -20
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +0 -87
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +0 -102
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +0 -81
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-webhooks/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/agents/openai.yml +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/cli.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/customization.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/evals/evals.json +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/mcp.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/base-vs-radix.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/composition.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/forms.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/icons.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/styling.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/pr.md +0 -0
- /package/templates/{_base/.nvmrc → .nvmrc} +0 -0
- /package/templates/{_base/.vscode → .vscode}/settings.json +0 -0
- /package/templates/{_base/apps → apps}/api/auth.config.ts +0 -0
- /package/templates/{_base/apps → apps}/api/functions.ts +0 -0
- /package/templates/{_base/apps → apps}/api/project.json +0 -0
- /package/templates/{_base/apps → apps}/api/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/crud.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/types.ts +0 -0
- /package/templates/{_base/apps → apps}/api/tsconfig.json +0 -0
- /package/templates/{_base/apps → apps}/api/types.ts +0 -0
- /package/templates/{react-router → apps/web}/postcss.config.js +0 -0
- /package/templates/{react-router → apps/web}/public/favicon.ico +0 -0
- /package/templates/{react-router/src/routes/auth/sign-in.tsx → apps/web/src/routes/(auth)/sign-in/index.tsx} +0 -0
- /package/templates/{react-router → apps/web}/tsconfig.json +0 -0
- /package/templates/{_base/convex.json → convex.json} +0 -0
- /package/templates/{_base/emails → emails}/tsconfig.json +0 -0
- /package/templates/{_base/emails → emails}/welcome_email.tsx +0 -0
- /package/templates/{_base/nx.json → nx.json} +0 -0
- /package/templates/{_base/scripts → scripts}/sync_convex_env.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/image.d.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/alert_dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/badge.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/basic_data_table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/card.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/checkbox.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/command.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dropdown_menu.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/form.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/input.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/label.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/popover.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/radio_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/resizable.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/scroll_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/select.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/separator.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/sheet.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/side_bar.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/skeleton.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/spinner.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/switch.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/text_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/tooltip.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/utils.ts +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_press.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_release.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_location.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_outside_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/utils/src/time.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/tsconfig.json +0 -0
- /package/templates/{_base/skills-lock.json → skills-lock.json} +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import "./app.css";
|
|
6
|
+
|
|
7
|
+
import { clerkMiddleware, rootAuthLoader } from "@clerk/react-router/server";
|
|
8
|
+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
|
|
9
|
+
import { Toaster } from "sonner";
|
|
10
|
+
|
|
11
|
+
import { protectedRoute } from "@/web/libs/server/protected";
|
|
12
|
+
import { ApiAuthProvider } from "@/web/providers/api_auth_provider";
|
|
13
|
+
import { GlobalProvider } from "@/web/providers/global_provider";
|
|
14
|
+
import { NavigationLoadingBarProvider } from "@/web/providers/navigation_loading_bar_provider";
|
|
15
|
+
|
|
16
|
+
import type { Route } from "./+types/root";
|
|
17
|
+
|
|
18
|
+
export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware(), protectedRoute];
|
|
19
|
+
export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args);
|
|
20
|
+
|
|
21
|
+
export const links: Route.LinksFunction = () => {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
rel: "preconnect",
|
|
25
|
+
href: "https://fonts.googleapis.com",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
rel: "preconnect",
|
|
29
|
+
href: "https://fonts.gstatic.com",
|
|
30
|
+
crossOrigin: "anonymous",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
rel: "stylesheet",
|
|
34
|
+
href: "https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&display=swap",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function Layout({ children }: { children: React.ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<html lang="en">
|
|
42
|
+
<head>
|
|
43
|
+
<meta charSet="utf-8" />
|
|
44
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
45
|
+
<Meta />
|
|
46
|
+
<Links />
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
{children}
|
|
50
|
+
<ScrollRestoration />
|
|
51
|
+
<Scripts />
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function App({ loaderData }: Route.ComponentProps) {
|
|
58
|
+
return (
|
|
59
|
+
<NavigationLoadingBarProvider>
|
|
60
|
+
<ApiAuthProvider loaderData={loaderData}>
|
|
61
|
+
<GlobalProvider>
|
|
62
|
+
<Outlet />
|
|
63
|
+
<Toaster richColors position="top-center" />
|
|
64
|
+
</GlobalProvider>
|
|
65
|
+
</ApiAuthProvider>
|
|
66
|
+
</NavigationLoadingBarProvider>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createConvexClient } from "@/web/libs/server/auth";
|
|
6
|
+
import { bootstrapSingleTodo } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
7
|
+
import { SingleTodo } from "@/web/surfaces/todos/single_todo/single_todo";
|
|
8
|
+
|
|
9
|
+
import type { Route } from "./+types/[id]";
|
|
10
|
+
|
|
11
|
+
export async function loader(args: Route.LoaderArgs) {
|
|
12
|
+
const client = await createConvexClient(args);
|
|
13
|
+
return bootstrapSingleTodo({ params: args.params, client });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function clientLoader({ serverLoader }: Route.ClientLoaderArgs) {
|
|
17
|
+
return serverLoader();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function meta({ loaderData: bootstrap }: Route.MetaArgs) {
|
|
21
|
+
if (!bootstrap) {
|
|
22
|
+
return [{ title: "Todo" }];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
{ title: `${bootstrap.todo.title} - Todos` },
|
|
27
|
+
{ name: "description", content: bootstrap.todo.description ?? "Todo details" },
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function TodoPage({ loaderData: bootstrap }: Route.ComponentProps) {
|
|
32
|
+
return <SingleTodo bootstrap={bootstrap} />;
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createConvexClient } from "@/web/libs/server/auth";
|
|
6
|
+
import { AllTodos } from "@/web/surfaces/todos/all_todos/all_todos";
|
|
7
|
+
import { bootstrapAllTodos } from "@/web/surfaces/todos/all_todos/bootstrap";
|
|
8
|
+
|
|
9
|
+
import type { Route } from "./+types/index";
|
|
10
|
+
|
|
11
|
+
export async function loader(args: Route.LoaderArgs) {
|
|
12
|
+
const client = await createConvexClient(args);
|
|
13
|
+
return bootstrapAllTodos({ client });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function clientLoader({ serverLoader }: Route.ClientLoaderArgs) {
|
|
17
|
+
return serverLoader();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function meta(_args: Route.MetaArgs) {
|
|
21
|
+
return [{ title: "Todos" }, { name: "description", content: "Manage your todos" }];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function TodosPage({ loaderData: bootstrap }: Route.ComponentProps) {
|
|
25
|
+
return <AllTodos bootstrap={bootstrap} />;
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
import type { Route } from "./+types/index";
|
|
9
|
+
|
|
10
|
+
export async function clientLoader(_args: Route.ClientLoaderArgs) {
|
|
11
|
+
return bootstrapHome();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function meta({ loaderData: bootstrap }: Route.MetaArgs) {
|
|
15
|
+
return [{ title: "Home" }, { name: "description", content: "Home" }];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function HomePage({ loaderData: bootstrap }: Route.ComponentProps) {
|
|
19
|
+
return <Home bootstrap={bootstrap} />;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CSSProperties } from "react";
|
|
6
|
+
import { Outlet } from "react-router";
|
|
7
|
+
|
|
8
|
+
import { SidebarInset, SidebarProvider } from "@/ui/base/side_bar";
|
|
9
|
+
import { Sidebar } from "@/web/surfaces/sidebar/sidebar";
|
|
10
|
+
|
|
11
|
+
export default function DashboardLayout() {
|
|
12
|
+
return (
|
|
13
|
+
<SidebarProvider style={{ "--sidebar-width": "265px" } as CSSProperties} defaultOpen={true}>
|
|
14
|
+
<Sidebar />
|
|
15
|
+
<SidebarInset className="bg-white">
|
|
16
|
+
<Outlet />
|
|
17
|
+
</SidebarInset>
|
|
18
|
+
</SidebarProvider>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { index, layout, prefix, type RouteConfig, route } from "@react-router/dev/routes";
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
layout("./routes/(auth)/layout.tsx", [route("/sign-in/*", "./routes/(auth)/sign-in/index.tsx")]),
|
|
5
|
+
layout("./routes/(dashboard)/layout.tsx", [
|
|
6
|
+
index("./routes/(dashboard)/index.tsx"),
|
|
7
|
+
...prefix("todos", [
|
|
8
|
+
layout("./routes/(dashboard)/(todos)/layout.tsx", [
|
|
9
|
+
index("./routes/(dashboard)/(todos)/index.tsx"),
|
|
10
|
+
route(":id", "./routes/(dashboard)/(todos)/[id].tsx"),
|
|
11
|
+
]),
|
|
12
|
+
]),
|
|
13
|
+
]),
|
|
14
|
+
] satisfies RouteConfig;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMemo, useRef } from "react";
|
|
6
|
+
|
|
7
|
+
import type { HomeBootstrap } from "@/web/surfaces/home/bootstrap";
|
|
8
|
+
import { installHome } from "@/web/surfaces/home/install";
|
|
9
|
+
import { createLayout } from "@/web/surfaces/home/layout";
|
|
10
|
+
|
|
11
|
+
export type HomeProps = {
|
|
12
|
+
bootstrap: HomeBootstrap;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const Home = ({ bootstrap }: HomeProps) => {
|
|
16
|
+
const { layout, Layout } = useMemo(() => createLayout(), []);
|
|
17
|
+
const installed = useRef(false);
|
|
18
|
+
|
|
19
|
+
if (!installed.current) {
|
|
20
|
+
installed.current = true;
|
|
21
|
+
installHome({ layout, bootstrap });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return <Layout />;
|
|
25
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { HomeBootstrap } from "@/web/surfaces/home/bootstrap";
|
|
6
|
+
import type { HomeLayoutController } from "@/web/surfaces/home/layout";
|
|
7
|
+
|
|
8
|
+
export type InstallHomeOpts = {
|
|
9
|
+
layout: HomeLayoutController;
|
|
10
|
+
bootstrap: HomeBootstrap;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const installHome = ({ layout, bootstrap }: InstallHomeOpts) => {
|
|
14
|
+
import("@/web/surfaces/home/main/create").then(({ createMain }) => {
|
|
15
|
+
layout.setMain(createMain({ bootstrap }));
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { action, observable } from "mobx";
|
|
6
|
+
import { observer } from "mobx-react-lite";
|
|
7
|
+
import type { ComponentType } from "react";
|
|
8
|
+
|
|
9
|
+
import { Skeleton } from "@/ui/base/skeleton";
|
|
10
|
+
|
|
11
|
+
export class HomeLayoutController {
|
|
12
|
+
@observable.ref
|
|
13
|
+
accessor Main: ComponentType | undefined = undefined;
|
|
14
|
+
|
|
15
|
+
@action
|
|
16
|
+
setMain(Main: ComponentType) {
|
|
17
|
+
this.Main = Main;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const createLayout = () => {
|
|
22
|
+
const layout = new HomeLayoutController();
|
|
23
|
+
return {
|
|
24
|
+
layout,
|
|
25
|
+
Layout: observer(() => {
|
|
26
|
+
const Main = layout.Main;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex w-full flex-1 flex-col">
|
|
30
|
+
{Main ? <Main /> : <Skeleton className="h-64 w-full rounded-md" />}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useUser } from "@clerk/react-router";
|
|
6
|
+
import { observer } from "mobx-react-lite";
|
|
7
|
+
import { Link } from "react-router";
|
|
8
|
+
|
|
9
|
+
import { Button } from "@/ui/base/button";
|
|
10
|
+
import type { HomeBootstrap } from "@/web/surfaces/home/bootstrap";
|
|
11
|
+
|
|
12
|
+
export type CreateHomeMainOpts = {
|
|
13
|
+
bootstrap: HomeBootstrap;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const createMain = ({ bootstrap: _bootstrap }: CreateHomeMainOpts) => {
|
|
17
|
+
return observer(() => {
|
|
18
|
+
const { user } = useUser();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<main className="flex flex-1 flex-col items-center justify-center gap-4 p-8">
|
|
22
|
+
<h1 className="text-3xl font-bold">
|
|
23
|
+
Welcome{user?.firstName ? `, ${user.firstName}` : ""}
|
|
24
|
+
</h1>
|
|
25
|
+
<p className="text-muted-foreground">Get started by managing your todos.</p>
|
|
26
|
+
<Button asChild>
|
|
27
|
+
<Link to="/todos">View Todos</Link>
|
|
28
|
+
</Button>
|
|
29
|
+
</main>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { SidebarLayoutController } from "@/web/surfaces/sidebar/layout";
|
|
6
|
+
|
|
7
|
+
type InstallSidebarOpts = {
|
|
8
|
+
layout: SidebarLayoutController;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const installSidebar = ({ layout }: InstallSidebarOpts) => {
|
|
12
|
+
import("@/web/surfaces/sidebar/nav_main/create").then(({ createNavMain }) => {
|
|
13
|
+
layout.setNavLinks(createNavMain());
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
import("@/web/surfaces/sidebar/user_menu/create").then(({ createUserMenu }) => {
|
|
17
|
+
layout.setUserMenu(createUserMenu());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Plus } from "lucide-react";
|
|
6
|
+
import { action, observable } from "mobx";
|
|
7
|
+
import { observer } from "mobx-react-lite";
|
|
8
|
+
import type { ComponentType } from "react";
|
|
9
|
+
import { Link } from "react-router";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
Sidebar as BaseSidebar,
|
|
13
|
+
SidebarContent,
|
|
14
|
+
SidebarFooter,
|
|
15
|
+
SidebarHeader,
|
|
16
|
+
SidebarMenu,
|
|
17
|
+
SidebarMenuButton,
|
|
18
|
+
SidebarMenuItem,
|
|
19
|
+
} from "@/ui/base/side_bar";
|
|
20
|
+
import { Skeleton } from "@/ui/base/skeleton";
|
|
21
|
+
|
|
22
|
+
export class SidebarLayoutController {
|
|
23
|
+
@observable.ref
|
|
24
|
+
accessor NavLinks: ComponentType | undefined = undefined;
|
|
25
|
+
|
|
26
|
+
@observable.ref
|
|
27
|
+
accessor UserMenu: ComponentType | undefined = undefined;
|
|
28
|
+
|
|
29
|
+
@action
|
|
30
|
+
setNavLinks(NavLinks: ComponentType) {
|
|
31
|
+
this.NavLinks = NavLinks;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@action
|
|
35
|
+
setUserMenu(UserMenu: ComponentType) {
|
|
36
|
+
this.UserMenu = UserMenu;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const BrandLink = () => {
|
|
41
|
+
return (
|
|
42
|
+
<SidebarMenu>
|
|
43
|
+
<SidebarMenuItem>
|
|
44
|
+
<SidebarMenuButton
|
|
45
|
+
size="lg"
|
|
46
|
+
className="h-auto flex-col items-start gap-0.5 p-0"
|
|
47
|
+
asChild
|
|
48
|
+
>
|
|
49
|
+
<Link
|
|
50
|
+
to="/"
|
|
51
|
+
className="flex flex-row w-full items-center gap-3 px-2 py-2 transition-opacity hover:opacity-80 active:opacity-70 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0 group-data-[collapsible=icon]:[&>div:last-child]:hidden"
|
|
52
|
+
>
|
|
53
|
+
<div className="flex size-10 shrink-0 items-center justify-center rounded-md bg-sidebar-brand text-sm text-white font-semibold group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:text-xs">
|
|
54
|
+
A
|
|
55
|
+
</div>
|
|
56
|
+
<div className="flex min-w-0 flex-col">
|
|
57
|
+
<span className="truncate text-base font-bold text-sidebar-foreground">
|
|
58
|
+
App
|
|
59
|
+
</span>
|
|
60
|
+
<span className="truncate text-xs text-sidebar-muted-foreground">
|
|
61
|
+
Template
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
</Link>
|
|
65
|
+
</SidebarMenuButton>
|
|
66
|
+
</SidebarMenuItem>
|
|
67
|
+
</SidebarMenu>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const createSidebarLayout = () => {
|
|
72
|
+
const layout = new SidebarLayoutController();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
layout,
|
|
76
|
+
Layout: observer(() => {
|
|
77
|
+
const NavLinks = layout.NavLinks;
|
|
78
|
+
const UserMenu = layout.UserMenu;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<BaseSidebar
|
|
82
|
+
data-slot="app-sidebar"
|
|
83
|
+
className="shadow-sm"
|
|
84
|
+
collapsible="icon"
|
|
85
|
+
>
|
|
86
|
+
<SidebarHeader>
|
|
87
|
+
<BrandLink />
|
|
88
|
+
</SidebarHeader>
|
|
89
|
+
<SidebarContent className="flex flex-1 flex-col gap-4 overflow-hidden px-2">
|
|
90
|
+
<SidebarMenu>
|
|
91
|
+
<SidebarMenuItem>
|
|
92
|
+
<SidebarMenuButton
|
|
93
|
+
size="sm"
|
|
94
|
+
tooltip="Create a new todo"
|
|
95
|
+
className="bg-sidebar-primary text-sidebar-primary-foreground hover:bg-sidebar-primary/90 active:bg-sidebar-primary/80 justify-center font-semibold shadow-sm"
|
|
96
|
+
asChild
|
|
97
|
+
>
|
|
98
|
+
<Link
|
|
99
|
+
to="/todos"
|
|
100
|
+
className="flex items-center gap-2"
|
|
101
|
+
>
|
|
102
|
+
<Plus className="size-4" />
|
|
103
|
+
<span>New Todo</span>
|
|
104
|
+
</Link>
|
|
105
|
+
</SidebarMenuButton>
|
|
106
|
+
</SidebarMenuItem>
|
|
107
|
+
</SidebarMenu>
|
|
108
|
+
</SidebarContent>
|
|
109
|
+
<SidebarFooter className="gap-2">
|
|
110
|
+
{NavLinks ? <NavLinks /> : <Skeleton className="h-10 bg-sidebar-accent" />}
|
|
111
|
+
{UserMenu ? <UserMenu /> : <Skeleton className="h-12 bg-sidebar-accent" />}
|
|
112
|
+
</SidebarFooter>
|
|
113
|
+
</BaseSidebar>
|
|
114
|
+
);
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type SidebarLayoutControllerType = SidebarLayoutController;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CheckSquare, Home } from "lucide-react";
|
|
6
|
+
import { observer } from "mobx-react-lite";
|
|
7
|
+
import { useLocation } from "react-router";
|
|
8
|
+
|
|
9
|
+
import { useSidebar } from "@/ui/base/side_bar";
|
|
10
|
+
|
|
11
|
+
import { NavMain } from "@/web/surfaces/sidebar/nav_main/nav_main";
|
|
12
|
+
|
|
13
|
+
const NAV_ITEMS = [
|
|
14
|
+
{ to: "/", label: "Home", icon: Home, exact: true },
|
|
15
|
+
{ to: "/todos", label: "Todos", icon: CheckSquare, exact: false },
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export const createNavMain = () => {
|
|
19
|
+
return observer(() => {
|
|
20
|
+
const location = useLocation();
|
|
21
|
+
const { setOpenMobile } = useSidebar();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<NavMain
|
|
25
|
+
items={[...NAV_ITEMS]}
|
|
26
|
+
pathname={location.pathname}
|
|
27
|
+
onNavigate={() => setOpenMobile(false)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { LucideIcon } from "lucide-react";
|
|
6
|
+
import { Link } from "react-router";
|
|
7
|
+
|
|
8
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/ui/base/side_bar";
|
|
9
|
+
|
|
10
|
+
export type NavMainItem = {
|
|
11
|
+
to: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon: LucideIcon;
|
|
14
|
+
exact: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type NavMainProps = {
|
|
18
|
+
items: NavMainItem[];
|
|
19
|
+
pathname: string;
|
|
20
|
+
onNavigate: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const NavMain = ({ items, pathname, onNavigate }: NavMainProps) => {
|
|
24
|
+
return (
|
|
25
|
+
<SidebarMenu>
|
|
26
|
+
{items.map(({ to, label, icon: Icon, exact }) => {
|
|
27
|
+
const isActive = exact ? pathname === to : pathname.startsWith(to);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<SidebarMenuItem key={to}>
|
|
31
|
+
<SidebarMenuButton isActive={isActive} asChild onClick={onNavigate}>
|
|
32
|
+
<Link to={to}>
|
|
33
|
+
<Icon className="size-4" />
|
|
34
|
+
<span>{label}</span>
|
|
35
|
+
</Link>
|
|
36
|
+
</SidebarMenuButton>
|
|
37
|
+
</SidebarMenuItem>
|
|
38
|
+
);
|
|
39
|
+
})}
|
|
40
|
+
</SidebarMenu>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useEffect, useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
import { installSidebar } from "@/web/surfaces/sidebar/install";
|
|
8
|
+
import { createSidebarLayout } from "@/web/surfaces/sidebar/layout";
|
|
9
|
+
|
|
10
|
+
export const Sidebar = () => {
|
|
11
|
+
const { layout, Layout } = useMemo(() => createSidebarLayout(), []);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
installSidebar({ layout });
|
|
15
|
+
}, [layout]);
|
|
16
|
+
|
|
17
|
+
return <Layout />;
|
|
18
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useUser } from "@clerk/react-router";
|
|
6
|
+
import { observer } from "mobx-react-lite";
|
|
7
|
+
|
|
8
|
+
import { UserMenu } from "@/web/surfaces/sidebar/user_menu/user_menu";
|
|
9
|
+
|
|
10
|
+
export const createUserMenu = () => {
|
|
11
|
+
return observer(() => {
|
|
12
|
+
const { user, isLoaded } = useUser();
|
|
13
|
+
|
|
14
|
+
if (isLoaded && !user) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<UserMenu
|
|
20
|
+
isLoaded={isLoaded}
|
|
21
|
+
fullName={user?.fullName}
|
|
22
|
+
email={user?.primaryEmailAddress?.emailAddress}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { UserButton } from "@clerk/react-router";
|
|
2
6
|
|
|
3
7
|
import { SidebarMenu, SidebarMenuItem } from "@/ui/base/side_bar";
|
|
4
8
|
import { Skeleton } from "@/ui/base/skeleton";
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
type UserMenuProps = {
|
|
11
|
+
isLoaded: boolean;
|
|
12
|
+
fullName: string | null | undefined;
|
|
13
|
+
email: string | null | undefined;
|
|
14
|
+
};
|
|
8
15
|
|
|
16
|
+
export const UserMenu = ({ isLoaded, fullName, email }: UserMenuProps) => {
|
|
9
17
|
if (!isLoaded) {
|
|
10
18
|
return (
|
|
11
19
|
<SidebarMenu>
|
|
@@ -16,18 +24,14 @@ export const UserMenu = () => {
|
|
|
16
24
|
);
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
if (!user) return null;
|
|
20
|
-
|
|
21
27
|
return (
|
|
22
28
|
<SidebarMenu>
|
|
23
29
|
<SidebarMenuItem className="w-full">
|
|
24
30
|
<div className="flex w-full items-center gap-2 rounded-md border px-2 py-2">
|
|
25
31
|
<UserButton />
|
|
26
32
|
<div className="flex min-w-0 flex-col">
|
|
27
|
-
<span className="truncate text-sm font-medium">{
|
|
28
|
-
<span className="truncate text-xs text-muted-foreground">
|
|
29
|
-
{user.primaryEmailAddress?.emailAddress}
|
|
30
|
-
</span>
|
|
33
|
+
<span className="truncate text-sm font-medium">{fullName}</span>
|
|
34
|
+
<span className="truncate text-xs text-muted-foreground">{email}</span>
|
|
31
35
|
</div>
|
|
32
36
|
</div>
|
|
33
37
|
</SidebarMenuItem>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMemo, useRef } from "react";
|
|
6
|
+
|
|
7
|
+
import type { AllTodosBootstrap } from "@/web/surfaces/todos/all_todos/bootstrap";
|
|
8
|
+
import { installAllTodos } from "@/web/surfaces/todos/all_todos/install";
|
|
9
|
+
import { createLayout } from "@/web/surfaces/todos/all_todos/layout";
|
|
10
|
+
|
|
11
|
+
export type AllTodosProps = {
|
|
12
|
+
bootstrap: AllTodosBootstrap;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const AllTodos = ({ bootstrap }: AllTodosProps) => {
|
|
16
|
+
const { layout, Layout } = useMemo(createLayout, []);
|
|
17
|
+
const installed = useRef(false);
|
|
18
|
+
|
|
19
|
+
if (!installed.current) {
|
|
20
|
+
installed.current = true;
|
|
21
|
+
installAllTodos({ layout, bootstrap });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return <Layout />;
|
|
25
|
+
};
|