create-aron-app 0.1.0 → 0.1.2

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,114 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
3
+ "assist": {
4
+ "actions": {
5
+ "source": {
6
+ "organizeImports": {
7
+ "level": "on",
8
+ "options": {
9
+ "groups": [
10
+ ":BUN:",
11
+ ":BLANK_LINE:",
12
+ ":NODE:",
13
+ ":BLANK_LINE:",
14
+ ":PACKAGE:",
15
+ ":BLANK_LINE:",
16
+ ":PACKAGE_WITH_PROTOCOL:",
17
+ ":BLANK_LINE:",
18
+ ":ALIAS:",
19
+ ":BLANK_LINE:",
20
+ ":PATH:",
21
+ ":BLANK_LINE:",
22
+ ":URL:"
23
+ ]
24
+ }
25
+ },
26
+ "recommended": true
27
+ }
28
+ },
29
+ "enabled": true
30
+ },
31
+ "css": {
32
+ "formatter": {
33
+ "enabled": true,
34
+ "indentStyle": "space",
35
+ "indentWidth": 2,
36
+ "lineWidth": 80,
37
+ "quoteStyle": "double"
38
+ },
39
+ "linter": {
40
+ "enabled": true
41
+ }
42
+ },
43
+ "files": {
44
+ "ignoreUnknown": false,
45
+ "includes": [
46
+ "**/*",
47
+ "!.nx",
48
+ "!**/*/.next",
49
+ "!node_modules",
50
+ "!**/*/dist",
51
+ "!**/*/output",
52
+ "!**/*/public",
53
+ "!**/*/tmp",
54
+ "!convex/_generated/**/*",
55
+ "!convex/_generated/**/*.d.ts",
56
+ "!package.json"
57
+ ]
58
+ },
59
+ "formatter": {
60
+ "enabled": true,
61
+ "indentStyle": "space"
62
+ },
63
+ "javascript": {
64
+ "formatter": {
65
+ "indentStyle": "space",
66
+ "indentWidth": 2,
67
+ "lineEnding": "lf",
68
+ "lineWidth": 100,
69
+ "quoteStyle": "double"
70
+ }
71
+ },
72
+ "json": {
73
+ "formatter": {
74
+ "enabled": true,
75
+ "indentStyle": "space",
76
+ "indentWidth": 2,
77
+ "lineEnding": "lf",
78
+ "lineWidth": 100
79
+ },
80
+ "linter": {
81
+ "enabled": true
82
+ }
83
+ },
84
+ "linter": {
85
+ "enabled": true,
86
+ "rules": {
87
+ "correctness": {
88
+ "noUnusedImports": {
89
+ "fix": "safe",
90
+ "level": "error"
91
+ },
92
+ "useExhaustiveDependencies": "warn"
93
+ },
94
+ "recommended": true,
95
+ "style": {
96
+ "noInferrableTypes": "error",
97
+ "noParameterAssign": "warn",
98
+ "noUnusedTemplateLiteral": "error",
99
+ "noUselessElse": "error",
100
+ "useAsConstAssertion": "error",
101
+ "useDefaultParameterLast": "error",
102
+ "useEnumInitializers": "error",
103
+ "useNumberNamespace": "error",
104
+ "useSelfClosingElements": "error",
105
+ "useSingleVarDeclarator": "error"
106
+ }
107
+ }
108
+ },
109
+ "vcs": {
110
+ "clientKind": "git",
111
+ "enabled": false,
112
+ "useIgnoreFile": false
113
+ }
114
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/get-convex/convex-backend/refs/heads/main/npm-packages/convex/schemas/convex.schema.json",
3
+ "functions": "apps/api"
4
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "project:emails",
3
+ "$schema": "../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "emails",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "dev": {
9
+ "executor": "nx:run-commands",
10
+ "options": {
11
+ "command": "bunx email dev",
12
+ "port": 4444
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "include": ["**/*.ts", "**/*.tsx"],
4
+ "exclude": ["node_modules"]
5
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ Body,
3
+ Button,
4
+ Container,
5
+ Head,
6
+ Heading,
7
+ Html,
8
+ Preview,
9
+ Section,
10
+ Tailwind,
11
+ Text,
12
+ } from "@react-email/components";
13
+
14
+ type WelcomeEmailProps = {
15
+ name: string;
16
+ actionUrl: string;
17
+ };
18
+
19
+ export const WelcomeEmail = ({ name, actionUrl }: WelcomeEmailProps) => {
20
+ return (
21
+ <Html>
22
+ <Head />
23
+ <Preview>Welcome — you're all set.</Preview>
24
+ <Tailwind>
25
+ <Body className="bg-gray-50 font-sans m-0 min-h-screen">
26
+ <Container className="mx-auto w-full max-w-2xl my-16">
27
+ <Section className="bg-white rounded-lg border border-gray-200 p-10">
28
+ <Heading className="text-2xl font-bold text-gray-900 mb-2">
29
+ Welcome, {name}
30
+ </Heading>
31
+ <Text className="text-base text-gray-600 leading-relaxed mb-6">
32
+ Your account is ready. Click the button below to get started.
33
+ </Text>
34
+ <Button
35
+ href={actionUrl}
36
+ className="bg-gray-900 text-white text-sm font-medium px-5 py-3 rounded-md"
37
+ >
38
+ Get Started
39
+ </Button>
40
+ </Section>
41
+ <Section className="text-center mt-6">
42
+ <Text className="text-xs text-gray-400">
43
+ You received this email because you signed up. If this wasn't you, you can ignore it.
44
+ </Text>
45
+ </Section>
46
+ </Container>
47
+ </Body>
48
+ </Tailwind>
49
+ </Html>
50
+ );
51
+ };
52
+
53
+ export default WelcomeEmail;
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "./node_modules/nx/schemas/nx-schema.json",
3
+ "namedInputs": {
4
+ "default": ["{projectRoot}/**/*", "sharedGlobals"],
5
+ "production": [
6
+ "default",
7
+ "!{projectRoot}/.eslintrc.json",
8
+ "!{projectRoot}/eslint.config.mjs"
9
+ ],
10
+ "sharedGlobals": ["{workspaceRoot}/.github/workflows/ci.yml"]
11
+ },
12
+ "nxCloudId": "67c2b4e892be1c371f9f8810",
13
+ "plugins": [
14
+ {
15
+ "plugin": "@nx/js/typescript",
16
+ "options": {
17
+ "typecheck": {
18
+ "targetName": "typecheck"
19
+ },
20
+ "build": {
21
+ "targetName": "build",
22
+ "configName": "tsconfig.lib.json",
23
+ "buildDepsName": "build-deps",
24
+ "watchDepsName": "watch-deps"
25
+ }
26
+ }
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@/source",
3
+ "version": "0.0.0",
4
+ "license": "MIT",
5
+ "private": true,
6
+ "scripts": {},
7
+ "dependencies": {
8
+ "@convex-dev/react-query": "^0.0.0-alpha.8",
9
+ "@hookform/resolvers": "^3.9.1",
10
+ "@radix-ui/react-accordion": "^1.2.2",
11
+ "@radix-ui/react-alert-dialog": "^1.1.4",
12
+ "@radix-ui/react-aspect-ratio": "^1.1.1",
13
+ "@radix-ui/react-checkbox": "^1.1.3",
14
+ "@radix-ui/react-collapsible": "^1.1.2",
15
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
16
+ "@radix-ui/react-hover-card": "^1.1.4",
17
+ "@radix-ui/react-label": "^2.1.1",
18
+ "@radix-ui/react-popover": "^1.1.4",
19
+ "@radix-ui/react-progress": "^1.1.1",
20
+ "@radix-ui/react-radio-group": "^1.2.2",
21
+ "@radix-ui/react-scroll-area": "^1.2.2",
22
+ "@radix-ui/react-select": "^2.1.4",
23
+ "@radix-ui/react-separator": "^1.1.1",
24
+ "@radix-ui/react-slider": "^1.2.2",
25
+ "@radix-ui/react-switch": "^1.1.2",
26
+ "@radix-ui/react-tabs": "^1.1.2",
27
+ "@radix-ui/react-tooltip": "^1.1.6",
28
+ "@stepperize/react": "^5.1.5",
29
+ "@tailwindcss/typography": "^0.5.16",
30
+ "@tanstack/react-query": "^5.66.0",
31
+ "@tanstack/react-store": "^0.7.0",
32
+ "@tanstack/react-table": "^8.20.6",
33
+ "class-variance-authority": "^0.7.1",
34
+ "cmdk": "^1.0.4",
35
+ "convex": "^1.28.0",
36
+ "convex-ents": "^0.16.0",
37
+ "convex-helpers": "^0.1.104",
38
+ "dayjs": "^1.11.13",
39
+ "lodash-es": "^4.17.21",
40
+ "lucide-react": "^0.469.0",
41
+ "nanoid": "^5.1.6",
42
+ "react": "19.0.0",
43
+ "react-dom": "19.0.0",
44
+ "react-hook-form": "^7.54.2",
45
+ "react-resizable-panels": "^4.7.2",
46
+ "sonner": "^2.0.1",
47
+ "ts-essentials": "^10.1.1",
48
+ "tw-animate-css": "^1.3.8",
49
+ "zod": "^3"
50
+ },
51
+ "devDependencies": {
52
+ "@biomejs/biome": "^2.2.3",
53
+ "@nx/js": "20.6.2",
54
+ "@nx/workspace": "20.6.2",
55
+ "@swc-node/register": "~1.9.1",
56
+ "@swc/core": "~1.5.7",
57
+ "@swc/helpers": "~0.5.11",
58
+ "@tailwindcss/postcss": "^4.1.11",
59
+ "@types/react": "19.0.0",
60
+ "@types/react-dom": "19.0.0",
61
+ "autoprefixer": "10.4.13",
62
+ "nx": "20.6.2",
63
+ "postcss": "8.4.38",
64
+ "tailwind-merge": "^3.0.2",
65
+ "tailwindcss": "^4.1.13",
66
+ "tailwindcss-animate": "^1.0.7",
67
+ "tslib": "^2.3.0",
68
+ "typescript": "~5.7.2"
69
+ },
70
+ "workspaces": [
71
+ "apps/*"
72
+ ]
73
+ }
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bun
2
+ import { spawnSync } from "child_process";
3
+ import { readFileSync } from "fs";
4
+
5
+ const ENV_FILE = ".env.convex";
6
+
7
+ function parseEnvContent(content: string): Record<string, string> {
8
+ const vars: Record<string, string> = {};
9
+ for (const line of content.split("\n")) {
10
+ const trimmed = line.trim();
11
+ if (!trimmed || trimmed.startsWith("#")) continue;
12
+ const eqIdx = trimmed.indexOf("=");
13
+ if (eqIdx === -1) continue;
14
+ const key = trimmed.slice(0, eqIdx).trim();
15
+ const value = trimmed.slice(eqIdx + 1);
16
+ if (key) vars[key] = value;
17
+ }
18
+ return vars;
19
+ }
20
+
21
+ function convex(...args: string[]): string {
22
+ const result = spawnSync("bunx", ["convex", ...args], { encoding: "utf-8" });
23
+ if (result.status !== 0) {
24
+ console.error(`convex ${args.join(" ")} failed:\n${result.stderr}`);
25
+ process.exit(1);
26
+ }
27
+ return result.stdout ?? "";
28
+ }
29
+
30
+ let localEnv: Record<string, string>;
31
+ try {
32
+ localEnv = parseEnvContent(readFileSync(ENV_FILE, "utf-8"));
33
+ } catch {
34
+ console.error(`Could not read ${ENV_FILE}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ const remoteEnv = parseEnvContent(convex("env", "list"));
39
+
40
+ let sets = 0;
41
+ let removes = 0;
42
+
43
+ for (const [key, value] of Object.entries(localEnv)) {
44
+ if (remoteEnv[key] !== value) {
45
+ console.log(` set ${key}`);
46
+ convex("env", "set", key, value);
47
+ sets++;
48
+ }
49
+ }
50
+
51
+ for (const key of Object.keys(remoteEnv)) {
52
+ if (!(key in localEnv)) {
53
+ console.log(` remove ${key}`);
54
+ convex("env", "remove", key);
55
+ removes++;
56
+ }
57
+ }
58
+
59
+ if (sets === 0 && removes === 0) {
60
+ console.log("Convex env vars already in sync.");
61
+ } else {
62
+ console.log(`Done: ${sets} set, ${removes} removed.`);
63
+ }
@@ -0,0 +1,4 @@
1
+ declare module "*.png";
2
+ declare module "*.svg";
3
+ declare module "*.jpeg";
4
+ declare module "*.jpg";
@@ -0,0 +1,73 @@
1
+ /** biome-ignore-all lint/suspicious/noUnknownAtRules: Tailwind CSS v4 directives */
2
+ @import "tailwindcss";
3
+ @import "tw-animate-css";
4
+
5
+ :root {
6
+ --spacing: 0.222222rem;
7
+ --radius: 0.625rem;
8
+
9
+ --background: oklch(1 0 0);
10
+ --foreground: oklch(0.145 0 0);
11
+ --card: oklch(1 0 0);
12
+ --card-foreground: oklch(0.145 0 0);
13
+ --popover: oklch(1 0 0);
14
+ --popover-foreground: oklch(0.145 0 0);
15
+ --primary: oklch(0.205 0 0);
16
+ --primary-foreground: oklch(0.985 0 0);
17
+ --secondary: oklch(0.97 0 0);
18
+ --secondary-foreground: oklch(0.205 0 0);
19
+ --muted: oklch(0.97 0 0);
20
+ --muted-foreground: oklch(0.556 0 0);
21
+ --accent: oklch(0.97 0 0);
22
+ --accent-foreground: oklch(0.205 0 0);
23
+ --destructive: oklch(0.577 0.245 27.325);
24
+ --border: oklch(0.922 0 0);
25
+ --input: oklch(0.922 0 0);
26
+ --ring: oklch(0.708 0 0);
27
+ --chart-1: oklch(0.646 0.222 41.116);
28
+ --chart-2: oklch(0.6 0.118 184.704);
29
+ --chart-3: oklch(0.398 0.07 227.392);
30
+ --chart-4: oklch(0.828 0.189 84.429);
31
+ --chart-5: oklch(0.769 0.188 70.08);
32
+ --sidebar: oklch(0.985 0 0);
33
+ --sidebar-foreground: oklch(0.145 0 0);
34
+ --sidebar-primary: oklch(0.205 0 0);
35
+ --sidebar-primary-foreground: oklch(0.985 0 0);
36
+ --sidebar-accent: oklch(0.97 0 0);
37
+ --sidebar-accent-foreground: oklch(0.205 0 0);
38
+ --sidebar-border: oklch(0.922 0 0);
39
+ --sidebar-ring: oklch(0.708 0 0);
40
+ }
41
+
42
+ @theme inline {
43
+ --font-heading: var(--font-heading);
44
+ --font-body: var(--font-body);
45
+ --color-background: var(--background);
46
+ --color-foreground: var(--foreground);
47
+ --color-card: var(--card);
48
+ --color-card-foreground: var(--card-foreground);
49
+ --color-popover: var(--popover);
50
+ --color-popover-foreground: var(--popover-foreground);
51
+ --color-primary: var(--primary);
52
+ --color-primary-foreground: var(--primary-foreground);
53
+ --color-secondary: var(--secondary);
54
+ --color-secondary-foreground: var(--secondary-foreground);
55
+ --color-muted: var(--muted);
56
+ --color-muted-foreground: var(--muted-foreground);
57
+ --color-accent: var(--accent);
58
+ --color-accent-foreground: var(--accent-foreground);
59
+ --color-destructive: var(--destructive);
60
+ --color-destructive-foreground: var(--destructive-foreground);
61
+ --color-border: var(--border);
62
+ --color-input: var(--input);
63
+ --color-ring: var(--ring);
64
+ --color-sidebar: var(--sidebar);
65
+ --color-sidebar-foreground: var(--sidebar-foreground);
66
+ --color-sidebar-primary: var(--sidebar-primary);
67
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
68
+ --color-sidebar-accent: var(--sidebar-accent);
69
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
70
+ --color-sidebar-border: var(--sidebar-border);
71
+ --color-sidebar-ring: var(--sidebar-ring);
72
+ }
73
+
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["src/**/*", "./image.d.ts"],
4
+ "exclude": ["node_modules"]
5
+ }
@@ -0,0 +1,139 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2025.
3
+ */
4
+
5
+ "use client";
6
+
7
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
8
+ import type * as React from "react";
9
+
10
+ import { buttonVariants } from "@/ui/base/button";
11
+ import { cn } from "@/ui/base/utils";
12
+
13
+ function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
14
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
15
+ }
16
+
17
+ function AlertDialogTrigger({
18
+ ...props
19
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
20
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
21
+ }
22
+
23
+ function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
24
+ return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
25
+ }
26
+
27
+ function AlertDialogOverlay({
28
+ className,
29
+ ...props
30
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
31
+ return (
32
+ <AlertDialogPrimitive.Overlay
33
+ data-slot="alert-dialog-overlay"
34
+ className={cn(
35
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
36
+ className,
37
+ )}
38
+ {...props}
39
+ />
40
+ );
41
+ }
42
+
43
+ function AlertDialogContent({
44
+ className,
45
+ ...props
46
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
47
+ return (
48
+ <AlertDialogPortal>
49
+ <AlertDialogOverlay />
50
+ <AlertDialogPrimitive.Content
51
+ data-slot="alert-dialog-content"
52
+ className={cn(
53
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
54
+ className,
55
+ )}
56
+ {...props}
57
+ />
58
+ </AlertDialogPortal>
59
+ );
60
+ }
61
+
62
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
63
+ return (
64
+ <div
65
+ data-slot="alert-dialog-header"
66
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
67
+ {...props}
68
+ />
69
+ );
70
+ }
71
+
72
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="alert-dialog-footer"
76
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ function AlertDialogTitle({
83
+ className,
84
+ ...props
85
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
86
+ return (
87
+ <AlertDialogPrimitive.Title
88
+ data-slot="alert-dialog-title"
89
+ className={cn("text-lg font-semibold", className)}
90
+ {...props}
91
+ />
92
+ );
93
+ }
94
+
95
+ function AlertDialogDescription({
96
+ className,
97
+ ...props
98
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
99
+ return (
100
+ <AlertDialogPrimitive.Description
101
+ data-slot="alert-dialog-description"
102
+ className={cn("text-muted-foreground text-sm", className)}
103
+ {...props}
104
+ />
105
+ );
106
+ }
107
+
108
+ function AlertDialogAction({
109
+ className,
110
+ ...props
111
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
112
+ return <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />;
113
+ }
114
+
115
+ function AlertDialogCancel({
116
+ className,
117
+ ...props
118
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
119
+ return (
120
+ <AlertDialogPrimitive.Cancel
121
+ className={cn(buttonVariants({ variant: "outline" }), className)}
122
+ {...props}
123
+ />
124
+ );
125
+ }
126
+
127
+ export {
128
+ AlertDialog,
129
+ AlertDialogPortal,
130
+ AlertDialogOverlay,
131
+ AlertDialogTrigger,
132
+ AlertDialogContent,
133
+ AlertDialogHeader,
134
+ AlertDialogFooter,
135
+ AlertDialogTitle,
136
+ AlertDialogDescription,
137
+ AlertDialogAction,
138
+ AlertDialogCancel,
139
+ };
@@ -0,0 +1,33 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+
4
+ import { cn } from "@/ui/base/utils";
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-color_tool.tsx focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary:
13
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ destructive:
15
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
16
+ outline: "text-foreground",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "default",
21
+ },
22
+ },
23
+ );
24
+
25
+ export interface BadgeProps
26
+ extends React.HTMLAttributes<HTMLDivElement>,
27
+ VariantProps<typeof badgeVariants> {}
28
+
29
+ function Badge({ className, variant, ...props }: BadgeProps) {
30
+ return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
31
+ }
32
+
33
+ export { Badge, badgeVariants };
@@ -0,0 +1,61 @@
1
+ import type { ColumnDef } from "@tanstack/react-table";
2
+ import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
3
+
4
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/ui/base/table";
5
+
6
+ export type BasicDataTableProps<TData, TValue> = {
7
+ columns: ColumnDef<TData, TValue>[];
8
+ data: TData[];
9
+ };
10
+
11
+ export function BasicDataTable<TData, TValue>({
12
+ columns,
13
+ data,
14
+ }: BasicDataTableProps<TData, TValue>) {
15
+ const table = useReactTable({
16
+ data,
17
+ columns,
18
+ getCoreRowModel: getCoreRowModel(),
19
+ });
20
+
21
+ return (
22
+ <div className="rounded-md border">
23
+ <Table>
24
+ <TableHeader>
25
+ {table.getHeaderGroups().map((headerGroup) => (
26
+ <TableRow key={headerGroup.id}>
27
+ {headerGroup.headers.map((header) => {
28
+ return (
29
+ <TableHead key={header.id}>
30
+ {header.isPlaceholder
31
+ ? null
32
+ : flexRender(header.column.columnDef.header, header.getContext())}
33
+ </TableHead>
34
+ );
35
+ })}
36
+ </TableRow>
37
+ ))}
38
+ </TableHeader>
39
+ <TableBody>
40
+ {table.getRowModel().rows?.length ? (
41
+ table.getRowModel().rows.map((row) => (
42
+ <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
43
+ {row.getVisibleCells().map((cell) => (
44
+ <TableCell key={cell.id}>
45
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
46
+ </TableCell>
47
+ ))}
48
+ </TableRow>
49
+ ))
50
+ ) : (
51
+ <TableRow>
52
+ <TableCell colSpan={columns.length} className="h-24 text-center">
53
+ No results.
54
+ </TableCell>
55
+ </TableRow>
56
+ )}
57
+ </TableBody>
58
+ </Table>
59
+ </div>
60
+ );
61
+ }