create-aron-app 0.1.0 → 0.1.1

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.
Files changed (156) hide show
  1. package/package.json +5 -2
  2. package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
  3. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
  4. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
  5. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
  6. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
  7. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
  8. package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
  9. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
  10. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
  11. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
  12. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
  13. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
  14. package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
  15. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
  16. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
  17. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
  18. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
  19. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
  20. package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
  21. package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
  22. package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
  23. package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
  24. package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
  25. package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
  26. package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
  27. package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
  28. package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
  29. package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
  30. package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  31. package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
  32. package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
  33. package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
  34. package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
  35. package/templates/_base/.cursor/commands/builder.md +0 -0
  36. package/templates/_base/.cursor/commands/pr.md +7 -0
  37. package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
  38. package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
  39. package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
  40. package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
  41. package/templates/_base/.env.convex.example +3 -0
  42. package/templates/_base/.github/workflows/ci.yml +29 -0
  43. package/templates/_base/.nvmrc +1 -0
  44. package/templates/_base/.vscode/settings.json +9 -0
  45. package/templates/_base/apps/api/auth.config.ts +18 -0
  46. package/templates/_base/apps/api/functions.ts +99 -0
  47. package/templates/_base/apps/api/project.json +22 -0
  48. package/templates/_base/apps/api/schema.ts +11 -0
  49. package/templates/_base/apps/api/todos/crud.ts +81 -0
  50. package/templates/_base/apps/api/todos/schema.ts +11 -0
  51. package/templates/_base/apps/api/todos/types.ts +22 -0
  52. package/templates/_base/apps/api/tsconfig.json +23 -0
  53. package/templates/_base/apps/api/types.ts +16 -0
  54. package/templates/_base/biome.json +114 -0
  55. package/templates/_base/convex.json +4 -0
  56. package/templates/_base/emails/project.json +16 -0
  57. package/templates/_base/emails/tsconfig.json +5 -0
  58. package/templates/_base/emails/welcome_email.tsx +53 -0
  59. package/templates/_base/nx.json +29 -0
  60. package/templates/_base/package.json +73 -0
  61. package/templates/_base/scripts/sync_convex_env.ts +63 -0
  62. package/templates/_base/shared/assets/image.d.ts +4 -0
  63. package/templates/_base/shared/assets/src/styles/global.css +73 -0
  64. package/templates/_base/shared/assets/tsconfig.json +5 -0
  65. package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
  66. package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
  67. package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
  68. package/templates/_base/shared/ui/src/base/button.tsx +69 -0
  69. package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
  70. package/templates/_base/shared/ui/src/base/card.tsx +79 -0
  71. package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
  72. package/templates/_base/shared/ui/src/base/command.tsx +165 -0
  73. package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
  74. package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
  75. package/templates/_base/shared/ui/src/base/form.tsx +161 -0
  76. package/templates/_base/shared/ui/src/base/input.tsx +129 -0
  77. package/templates/_base/shared/ui/src/base/label.tsx +19 -0
  78. package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
  79. package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
  80. package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
  81. package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
  82. package/templates/_base/shared/ui/src/base/select.tsx +151 -0
  83. package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
  84. package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
  85. package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
  86. package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
  87. package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
  88. package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
  89. package/templates/_base/shared/ui/src/base/table.tsx +91 -0
  90. package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
  91. package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
  92. package/templates/_base/shared/ui/src/base/utils.ts +17 -0
  93. package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
  94. package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
  95. package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
  96. package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
  97. package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
  98. package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
  99. package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
  100. package/templates/_base/shared/ui/tsconfig.json +8 -0
  101. package/templates/_base/shared/utils/src/convex.ts +3 -0
  102. package/templates/_base/shared/utils/src/time.ts +12 -0
  103. package/templates/_base/shared/utils/tsconfig.json +5 -0
  104. package/templates/_base/skills-lock.json +35 -0
  105. package/templates/_base/tsconfig.base.json +34 -0
  106. package/templates/nextjs/.env.example +8 -0
  107. package/templates/nextjs/index.d.ts +6 -0
  108. package/templates/nextjs/next-env.d.ts +5 -0
  109. package/templates/nextjs/next.config.js +22 -0
  110. package/templates/nextjs/postcss.config.js +17 -0
  111. package/templates/nextjs/project.json +22 -0
  112. package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
  113. package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
  114. package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
  115. package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
  116. package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
  117. package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
  118. package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
  119. package/templates/nextjs/src/app/app.css +3 -0
  120. package/templates/nextjs/src/app/layout.tsx +26 -0
  121. package/templates/nextjs/src/convex.ts +11 -0
  122. package/templates/nextjs/src/middleware.ts +18 -0
  123. package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
  124. package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
  125. package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
  126. package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
  127. package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
  128. package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
  129. package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
  130. package/templates/nextjs/src/utils/font.ts +9 -0
  131. package/templates/nextjs/tsconfig.json +42 -0
  132. package/templates/react-router/.env.example +8 -0
  133. package/templates/react-router/postcss.config.js +15 -0
  134. package/templates/react-router/project.json +23 -0
  135. package/templates/react-router/public/favicon.ico +0 -0
  136. package/templates/react-router/react-router.config.ts +9 -0
  137. package/templates/react-router/src/app.css +3 -0
  138. package/templates/react-router/src/components/error_boundary.tsx +33 -0
  139. package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
  140. package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
  141. package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
  142. package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
  143. package/templates/react-router/src/root.tsx +37 -0
  144. package/templates/react-router/src/routes/auth/layout.tsx +13 -0
  145. package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
  146. package/templates/react-router/src/routes/index.tsx +9 -0
  147. package/templates/react-router/src/routes/layout.tsx +26 -0
  148. package/templates/react-router/src/routes/todos/[id].tsx +22 -0
  149. package/templates/react-router/src/routes/todos/index.tsx +13 -0
  150. package/templates/react-router/src/routes.ts +12 -0
  151. package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
  152. package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
  153. package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
  154. package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
  155. package/templates/react-router/tsconfig.json +20 -0
  156. package/templates/react-router/vite.config.ts +40 -0
@@ -0,0 +1,125 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ "use client";
6
+
7
+ import { UserButton, useUser } from "@clerk/nextjs";
8
+ import {
9
+ CheckSquareIcon,
10
+ HomeIcon,
11
+ type LucideIcon,
12
+ SettingsIcon,
13
+ } from "lucide-react";
14
+ import Link from "next/link";
15
+
16
+ import {
17
+ Sidebar as BaseSidebar,
18
+ SidebarHeader as BaseSidebarHeader,
19
+ SidebarContent,
20
+ SidebarFooter,
21
+ SidebarGroup,
22
+ SidebarGroupContent,
23
+ SidebarMenu,
24
+ SidebarMenuItem,
25
+ } from "@/ui/base/side_bar";
26
+ import { Skeleton } from "@/ui/base/skeleton";
27
+ import { NavLink } from "@/web/ui/sidebar/nav_link";
28
+
29
+ export type NavItem = {
30
+ id: string;
31
+ title: string;
32
+ url: string;
33
+ icon: LucideIcon;
34
+ };
35
+
36
+ export const nav: {
37
+ main: NavItem[];
38
+ secondary: NavItem[];
39
+ } = {
40
+ main: [
41
+ {
42
+ id: "dashboard",
43
+ title: "Dashboard",
44
+ url: "/",
45
+ icon: HomeIcon,
46
+ },
47
+ {
48
+ id: "todos",
49
+ title: "Todos",
50
+ url: "/todos",
51
+ icon: CheckSquareIcon,
52
+ },
53
+ ],
54
+ secondary: [
55
+ {
56
+ id: "settings",
57
+ title: "Settings",
58
+ url: "/settings",
59
+ icon: SettingsIcon,
60
+ },
61
+ ],
62
+ };
63
+
64
+ export const Sidebar = ({
65
+ ...props
66
+ }: React.ComponentProps<typeof BaseSidebar>) => {
67
+ const user = useUser();
68
+
69
+ return (
70
+ <BaseSidebar collapsible="offcanvas" {...props}>
71
+ <BaseSidebarHeader>
72
+ <SidebarMenu>
73
+ <SidebarMenuItem>
74
+ <Link href="/" className="flex flex-row items-center gap-3 w-full">
75
+ <span className="truncate font-medium">App</span>
76
+ </Link>
77
+ </SidebarMenuItem>
78
+ </SidebarMenu>
79
+ </BaseSidebarHeader>
80
+ <SidebarContent>
81
+ <SidebarGroup className="pt-0">
82
+ <SidebarGroupContent className="flex flex-col gap-2">
83
+ <SidebarMenu>
84
+ {nav.main.map((item) => (
85
+ <NavLink key={item.id} item={item} />
86
+ ))}
87
+ </SidebarMenu>
88
+ </SidebarGroupContent>
89
+ </SidebarGroup>
90
+ </SidebarContent>
91
+ <SidebarFooter className="gap-0 p-2">
92
+ <SidebarGroup>
93
+ <SidebarGroupContent>
94
+ <SidebarMenu>
95
+ {nav.secondary.map((item) => (
96
+ <NavLink key={item.id} item={item} />
97
+ ))}
98
+ </SidebarMenu>
99
+ </SidebarGroupContent>
100
+ </SidebarGroup>
101
+ <SidebarMenu>
102
+ {user.isLoaded ? (
103
+ <SidebarMenuItem className="w-full">
104
+ <div className="flex items-start gap-2 border rounded-md shadow-sm px-2 py-2 w-full">
105
+ <UserButton />
106
+ <div className="flex flex-col gap-0 text-left leading-tight w-fit">
107
+ <span className="text-sm font-medium break-words truncate">
108
+ {user.user?.fullName}
109
+ </span>
110
+ <span className="truncate text-xs font-light">
111
+ {user.user?.emailAddresses[0].emailAddress}
112
+ </span>
113
+ </div>
114
+ </div>
115
+ </SidebarMenuItem>
116
+ ) : (
117
+ <SidebarMenuItem className="w-full">
118
+ <Skeleton className="h-12 w-full rounded-lg border" />
119
+ </SidebarMenuItem>
120
+ )}
121
+ </SidebarMenu>
122
+ </SidebarFooter>
123
+ </BaseSidebar>
124
+ );
125
+ };
@@ -0,0 +1,9 @@
1
+ import { Inter } from "next/font/google";
2
+
3
+ const inter = Inter({
4
+ variable: "--font-inter",
5
+ subsets: ["latin"],
6
+ display: "swap",
7
+ });
8
+
9
+ export const fontCn = `${inter.className}`;
@@ -0,0 +1,42 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "jsx": "preserve",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "noEmit": true,
7
+ "emitDeclarationOnly": false,
8
+ "esModuleInterop": true,
9
+ "resolveJsonModule": true,
10
+ "allowJs": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "incremental": true,
14
+ "plugins": [{"name": "next"}],
15
+ "outDir": "dist",
16
+ "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
17
+ },
18
+ "include": [
19
+ ".next/types/**/*.ts",
20
+ "index.d.ts",
21
+ "next-env.d.ts",
22
+ "next-env.d.ts",
23
+ "src/**/*.js",
24
+ "src/**/*.jsx",
25
+ "src/**/*.ts",
26
+ "src/**/*.tsx",
27
+ "../../dist/apps/web/.next/types/**/*.ts",
28
+ "../../apps/web/dist/.next/types/**/*.ts"
29
+ ],
30
+ "exclude": [
31
+ "out-tsc",
32
+ "dist",
33
+ "node_modules",
34
+ "jest.config.ts",
35
+ "src/**/*.spec.ts",
36
+ "src/**/*.test.ts",
37
+ ".next",
38
+ "eslint.config.js",
39
+ "eslint.config.cjs",
40
+ "eslint.config.mjs"
41
+ ]
42
+ }
@@ -0,0 +1,8 @@
1
+ # Convex
2
+ CONVEX_DEPLOYMENT=
3
+ CONVEX_DEPLOYMENT_URL=
4
+ VITE_CONVEX_URL=
5
+
6
+ # Authentication
7
+ CLERK_SECRET_KEY=
8
+ VITE_CLERK_PUBLISHABLE_KEY=
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Copyright (c) Sales Star Limited 2026.
3
+ */
4
+
5
+ // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
6
+ // option from your application's configuration (i.e. project.json).
7
+ //
8
+ // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
9
+
10
+ export default {
11
+ plugins: {
12
+ "@tailwindcss/postcss": {},
13
+ autoprefixer: {},
14
+ },
15
+ };
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "my-app:web",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "./apps/web",
5
+ "projectType": "application",
6
+ "tags": [],
7
+ "targets": {
8
+ "dev": {
9
+ "executor": "nx:run-commands",
10
+ "options": {
11
+ "cwd": "apps/web",
12
+ "command": "bunx react-router dev --port 3000"
13
+ }
14
+ },
15
+ "build": {
16
+ "executor": "nx:run-commands",
17
+ "options": {
18
+ "cwd": "apps/web",
19
+ "command": "bunx react-router build"
20
+ }
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ import type { Config } from "@react-router/dev/config";
2
+
3
+ export default {
4
+ appDirectory: "src",
5
+ ssr: false,
6
+ future: {
7
+ v8_middleware: true,
8
+ },
9
+ } satisfies Config;
@@ -0,0 +1,3 @@
1
+ @import "../../../../shared/assets/src/styles/global.css";
2
+ @source "../../../../shared/ui/src/**/*.{js,ts,jsx,tsx}";
3
+ @source "../../../../apps/web/src/**/*.{js,ts,jsx,tsx}";
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Copyright (c) SalesStar Limited 2026.
3
+ */
4
+
5
+ import type { Route } from "apps/web/.react-router/types/app/+types/root";
6
+ import { isRouteErrorResponse } from "react-router";
7
+
8
+ export const ErrorBoundary = ({ error }: Route.ErrorBoundaryProps) => {
9
+ let message = "Oops!";
10
+ let details = "An unexpected error occurred.";
11
+ let stack: string | undefined;
12
+
13
+ if (isRouteErrorResponse(error)) {
14
+ message = error.status === 404 ? "404" : "Error";
15
+ details =
16
+ error.status === 404 ? "The requested page could not be found." : error.statusText || details;
17
+ } else if (import.meta.env.DEV && error && error instanceof Error) {
18
+ details = error.message;
19
+ stack = error.stack;
20
+ }
21
+
22
+ return (
23
+ <main className="pt-16 p-4 container mx-auto">
24
+ <h1>{message}</h1>
25
+ <p>{details}</p>
26
+ {stack && (
27
+ <pre className="w-full p-4 overflow-x-auto">
28
+ <code>{stack}</code>
29
+ </pre>
30
+ )}
31
+ </main>
32
+ );
33
+ };
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { CheckSquare, Home } from "lucide-react";
6
+ import { Link, useLocation } from "react-router";
7
+
8
+ import {
9
+ Sidebar,
10
+ SidebarContent,
11
+ SidebarFooter,
12
+ SidebarHeader,
13
+ SidebarMenu,
14
+ SidebarMenuButton,
15
+ SidebarMenuItem,
16
+ useSidebar,
17
+ } from "@/ui/base/side_bar";
18
+ import { cn } from "@/ui/base/utils";
19
+ import { UserMenu } from "@/web/layouts/sidebar/sidebar_aside/user_menu";
20
+
21
+ type SidebarAsideProps = React.ComponentProps<typeof Sidebar>;
22
+
23
+ const NAV_ITEMS = [
24
+ { to: "/", label: "Home", icon: Home, exact: true },
25
+ { to: "/todos", label: "Todos", icon: CheckSquare, exact: false },
26
+ ];
27
+
28
+ export const SidebarAside = ({ className, ...props }: SidebarAsideProps) => {
29
+ const { setOpenMobile } = useSidebar();
30
+ const location = useLocation();
31
+
32
+ return (
33
+ <Sidebar
34
+ data-slot="app-sidebar"
35
+ className={cn("border-r border-sidebar-border", className)}
36
+ collapsible="offcanvas"
37
+ {...props}
38
+ >
39
+ <SidebarHeader>
40
+ <SidebarMenu>
41
+ <SidebarMenuItem>
42
+ <SidebarMenuButton size="lg" asChild>
43
+ <Link to="/" onClick={() => setOpenMobile(false)}>
44
+ <div className="flex size-8 items-center justify-center rounded-md bg-primary text-primary-foreground text-sm font-bold">
45
+ A
46
+ </div>
47
+ <span className="font-semibold">My App</span>
48
+ </Link>
49
+ </SidebarMenuButton>
50
+ </SidebarMenuItem>
51
+ </SidebarMenu>
52
+ </SidebarHeader>
53
+
54
+ <SidebarContent className="px-2">
55
+ <SidebarMenu>
56
+ {NAV_ITEMS.map(({ to, label, icon: Icon, exact }) => {
57
+ const isActive = exact ? location.pathname === to : location.pathname.startsWith(to);
58
+ return (
59
+ <SidebarMenuItem key={to}>
60
+ <SidebarMenuButton isActive={isActive} asChild onClick={() => setOpenMobile(false)}>
61
+ <Link to={to}>
62
+ <Icon className="size-4" />
63
+ <span>{label}</span>
64
+ </Link>
65
+ </SidebarMenuButton>
66
+ </SidebarMenuItem>
67
+ );
68
+ })}
69
+ </SidebarMenu>
70
+ </SidebarContent>
71
+ <SidebarFooter>
72
+ <UserMenu />
73
+ </SidebarFooter>
74
+ </Sidebar>
75
+ );
76
+ };
@@ -0,0 +1,36 @@
1
+ import { UserButton, useUser } from "@clerk/react-router";
2
+
3
+ import { SidebarMenu, SidebarMenuItem } from "@/ui/base/side_bar";
4
+ import { Skeleton } from "@/ui/base/skeleton";
5
+
6
+ export const UserMenu = () => {
7
+ const { user, isLoaded } = useUser();
8
+
9
+ if (!isLoaded) {
10
+ return (
11
+ <SidebarMenu>
12
+ <SidebarMenuItem className="w-full">
13
+ <Skeleton className="h-12 w-full rounded-lg" />
14
+ </SidebarMenuItem>
15
+ </SidebarMenu>
16
+ );
17
+ }
18
+
19
+ if (!user) return null;
20
+
21
+ return (
22
+ <SidebarMenu>
23
+ <SidebarMenuItem className="w-full">
24
+ <div className="flex w-full items-center gap-2 rounded-md border px-2 py-2">
25
+ <UserButton />
26
+ <div className="flex min-w-0 flex-col">
27
+ <span className="truncate text-sm font-medium">{user.fullName}</span>
28
+ <span className="truncate text-xs text-muted-foreground">
29
+ {user.primaryEmailAddress?.emailAddress}
30
+ </span>
31
+ </div>
32
+ </div>
33
+ </SidebarMenuItem>
34
+ </SidebarMenu>
35
+ );
36
+ };
@@ -0,0 +1,22 @@
1
+ import { SidebarInset, SidebarProvider } from "@/ui/base/side_bar";
2
+ import { SidebarAside } from "@/web/layouts/sidebar/sidebar_aside/sidebar_aside";
3
+
4
+ type SideBarLayoutProps = {
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ export const SideBarLayout = ({ children }: SideBarLayoutProps) => {
9
+ return (
10
+ <SidebarProvider
11
+ style={
12
+ {
13
+ "--sidebar-width": "calc(var(--spacing) * 45)",
14
+ "--header-height": "calc(var(--spacing) * 12)",
15
+ } as React.CSSProperties
16
+ }
17
+ >
18
+ <SidebarAside variant="inset" />
19
+ <SidebarInset>{children}</SidebarInset>
20
+ </SidebarProvider>
21
+ );
22
+ };
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { ClerkProvider, useAuth } from "@clerk/react-router";
6
+ import { ConvexQueryClient } from "@convex-dev/react-query";
7
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
8
+ import { ConvexReactClient } from "convex/react";
9
+ import { ConvexProviderWithClerk } from "convex/react-clerk";
10
+
11
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
12
+ const convexQueryClient = new ConvexQueryClient(convex);
13
+
14
+ const queryClient = new QueryClient({
15
+ defaultOptions: {
16
+ queries: {
17
+ queryFn: convexQueryClient.queryFn(),
18
+ gcTime: 0,
19
+ staleTime: 0,
20
+ },
21
+ },
22
+ });
23
+
24
+ convexQueryClient.connect(queryClient);
25
+
26
+ type ApiAuthProviderProps = {
27
+ children: React.ReactNode;
28
+ };
29
+
30
+ export const ApiAuthProvider = ({ children }: ApiAuthProviderProps) => {
31
+ return (
32
+ <ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string}>
33
+ <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
34
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
35
+ </ConvexProviderWithClerk>
36
+ </ClerkProvider>
37
+ );
38
+ };
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import "./app.css";
6
+
7
+ import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
8
+ import { Toaster } from "sonner";
9
+
10
+ import { ApiAuthProvider } from "@/web/providers/api_auth_provider";
11
+
12
+ export function Layout({ children }: { children: React.ReactNode }) {
13
+ return (
14
+ <html lang="en">
15
+ <head>
16
+ <meta charSet="utf-8" />
17
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
18
+ <Meta />
19
+ <Links />
20
+ </head>
21
+ <body>
22
+ {children}
23
+ <ScrollRestoration />
24
+ <Scripts />
25
+ </body>
26
+ </html>
27
+ );
28
+ }
29
+
30
+ export default function App() {
31
+ return (
32
+ <ApiAuthProvider>
33
+ <Outlet />
34
+ <Toaster richColors position="top-center" />
35
+ </ApiAuthProvider>
36
+ );
37
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { Outlet } from "react-router";
6
+
7
+ export default function AuthLayout() {
8
+ return (
9
+ <div className="flex flex-1 items-center justify-center h-svh">
10
+ <Outlet />
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { SignIn } from "@clerk/react-router";
6
+
7
+ export function meta() {
8
+ return [{ title: "Sign In", description: "Sign In to your account" }];
9
+ }
10
+
11
+ export default function SignInPage() {
12
+ return <SignIn routing="path" path="/sign-in" />;
13
+ }
@@ -0,0 +1,9 @@
1
+ import { HomeSurface } from "@/web/surfaces/home_surface";
2
+
3
+ export function meta() {
4
+ return [{ title: "Home" }, { name: "description", content: "Home" }];
5
+ }
6
+
7
+ export default function Home() {
8
+ return <HomeSurface />;
9
+ }
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Copyright (c) SalesStar Limited 2026.
3
+ */
4
+
5
+ import { useAuth } from "@clerk/react-router";
6
+ import { Navigate, Outlet } from "react-router";
7
+
8
+ import { SideBarLayout } from "@/web/layouts/sidebar/sidebar_layout";
9
+
10
+ export default function Layout() {
11
+ const { isLoaded, isSignedIn } = useAuth();
12
+
13
+ if (!isLoaded) {
14
+ return null;
15
+ }
16
+
17
+ if (!isSignedIn) {
18
+ return <Navigate to="/sign-in" replace />;
19
+ }
20
+
21
+ return (
22
+ <SideBarLayout>
23
+ <Outlet />
24
+ </SideBarLayout>
25
+ );
26
+ }
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { useParams } from "react-router";
6
+
7
+ import type { TodoId } from "@/api/todos/types";
8
+ import { SingleTodoSurface } from "@/web/surfaces/todos/single_todo_surface";
9
+
10
+ export function meta() {
11
+ return [{ title: "Todo" }, { name: "description", content: "Todo" }];
12
+ }
13
+
14
+ export default function TodoPage() {
15
+ const { id } = useParams<{ id: TodoId }>();
16
+
17
+ if (!id) {
18
+ return <div>Todo not found</div>;
19
+ }
20
+
21
+ return <SingleTodoSurface todoId={id} />;
22
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { AllTodosSurface } from "@/web/surfaces/todos/all_todos_surface";
6
+
7
+ export function meta() {
8
+ return [{ title: "Todos" }, { name: "description", content: "Todos" }];
9
+ }
10
+
11
+ export default function TodosPage() {
12
+ return <AllTodosSurface />;
13
+ }
@@ -0,0 +1,12 @@
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.tsx")]),
5
+ layout("./routes/layout.tsx", [
6
+ index("./routes/index.tsx"),
7
+ ...prefix("todos", [
8
+ index("./routes/todos/index.tsx"),
9
+ route(":id", "./routes/todos/[id].tsx"),
10
+ ]),
11
+ ]),
12
+ ] satisfies RouteConfig;
@@ -0,0 +1,20 @@
1
+ import { useUser } from "@clerk/react-router";
2
+ import { Link } from "react-router";
3
+
4
+ import { Button } from "@/ui/base/button";
5
+
6
+ export const HomeSurface = () => {
7
+ const { user } = useUser();
8
+
9
+ return (
10
+ <main className="flex flex-1 flex-col items-center justify-center gap-4 p-8">
11
+ <h1 className="text-3xl font-bold">
12
+ Welcome{user?.firstName ? `, ${user.firstName}` : ""}
13
+ </h1>
14
+ <p className="text-muted-foreground">Get started by managing your todos.</p>
15
+ <Button asChild>
16
+ <Link to="/todos">View Todos</Link>
17
+ </Button>
18
+ </main>
19
+ );
20
+ };