create-aron-app 0.1.5 → 0.1.7

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 (261) hide show
  1. package/README.md +73 -38
  2. package/dist/index.js +50 -115
  3. package/package.json +5 -2
  4. package/templates/{_base/.cursor → .cursor}/rules/api_architecture.mdc +27 -33
  5. package/templates/{_base/.cursor → .cursor}/rules/coding_standards.mdc +1 -1
  6. package/templates/{_base/.cursor → .cursor}/rules/convex_rules.mdc +78 -422
  7. package/templates/.cursor/rules/frontend_architecture_core.mdc +495 -0
  8. package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +458 -0
  9. package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +473 -0
  10. package/templates/apps/api/_generated/api.d.ts +57 -0
  11. package/templates/apps/api/_generated/api.js +23 -0
  12. package/templates/apps/api/_generated/dataModel.d.ts +60 -0
  13. package/templates/apps/api/_generated/server.d.ts +143 -0
  14. package/templates/apps/api/_generated/server.js +93 -0
  15. package/templates/apps/api/http.ts +16 -0
  16. package/templates/apps/nextjs/.env.example +10 -0
  17. package/templates/{nextjs → apps/nextjs}/project.json +5 -5
  18. package/templates/{nextjs → apps/nextjs}/src/app/(auth)/not-allowed/page.tsx +1 -0
  19. package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +22 -0
  20. package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +12 -0
  21. package/templates/{nextjs → apps/nextjs}/src/app/(dashboard)/todos/[id]/page.tsx +5 -2
  22. package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +19 -0
  23. package/templates/apps/nextjs/src/middleware.ts +18 -0
  24. package/templates/apps/nextjs/src/surfaces/home/bootstrap.ts +9 -0
  25. package/templates/apps/nextjs/src/surfaces/home/home.tsx +27 -0
  26. package/templates/apps/nextjs/src/surfaces/home/install.tsx +17 -0
  27. package/templates/apps/nextjs/src/surfaces/home/layout.tsx +44 -0
  28. package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +34 -0
  29. package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +23 -0
  30. package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +118 -0
  31. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +19 -0
  32. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +22 -0
  33. package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +25 -0
  34. package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
  35. package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +33 -0
  36. package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +23 -0
  37. package/templates/{nextjs/src/ui/sidebar/nav_link.tsx → apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx} +13 -10
  38. package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +28 -0
  39. package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +42 -0
  40. package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +29 -0
  41. package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +61 -0
  42. package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
  43. package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +23 -0
  44. package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +23 -0
  45. package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +44 -0
  46. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +49 -0
  47. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +70 -0
  48. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +56 -0
  49. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +99 -0
  50. package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
  51. package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +32 -0
  52. package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/create.tsx +26 -0
  53. package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +22 -0
  54. package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +27 -0
  55. package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +55 -0
  56. package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +38 -0
  57. package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +49 -0
  58. package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +29 -0
  59. package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +13 -0
  60. package/templates/apps/nextjs/src/utils/auth.ts +18 -0
  61. package/templates/apps/react-router/.env.example +10 -0
  62. package/templates/apps/react-router/.react-router/types/+future.ts +9 -0
  63. package/templates/apps/react-router/.react-router/types/+routes.ts +71 -0
  64. package/templates/apps/react-router/.react-router/types/+server-build.d.ts +18 -0
  65. package/templates/apps/react-router/.react-router/types/src/+types/root.ts +59 -0
  66. package/templates/apps/react-router/.react-router/types/src/routes/(auth)/+types/layout.ts +62 -0
  67. package/templates/apps/react-router/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +65 -0
  68. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/index.ts +65 -0
  69. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/layout.ts +62 -0
  70. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/[id].ts +65 -0
  71. package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/index.ts +65 -0
  72. package/templates/{react-router → apps/react-router}/project.json +4 -4
  73. package/templates/apps/react-router/src/app.css +3 -0
  74. package/templates/{react-router → apps/react-router}/src/components/error_boundary.tsx +1 -1
  75. package/templates/{react-router → apps/react-router}/src/providers/api_auth_provider.tsx +2 -0
  76. package/templates/{react-router/src/routes/auth → apps/react-router/src/routes/(auth)}/layout.tsx +1 -1
  77. package/templates/apps/react-router/src/routes/(dashboard)/index.tsx +19 -0
  78. package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +37 -0
  79. package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +19 -0
  80. package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +19 -0
  81. package/templates/apps/react-router/src/routes.ts +12 -0
  82. package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +9 -0
  83. package/templates/apps/react-router/src/surfaces/home/home.tsx +25 -0
  84. package/templates/apps/react-router/src/surfaces/home/install.tsx +17 -0
  85. package/templates/apps/react-router/src/surfaces/home/layout.tsx +35 -0
  86. package/templates/apps/react-router/src/surfaces/home/main/create.tsx +32 -0
  87. package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +23 -0
  88. package/templates/apps/react-router/src/surfaces/sidebar/layout.tsx +110 -0
  89. package/templates/apps/react-router/src/surfaces/sidebar/nav_main/create.tsx +31 -0
  90. package/templates/apps/react-router/src/surfaces/sidebar/nav_main/nav_main.tsx +42 -0
  91. package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
  92. package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +31 -0
  93. package/templates/apps/react-router/src/surfaces/sidebar/sidebar.tsx +18 -0
  94. package/templates/apps/react-router/src/surfaces/sidebar/user_menu/create.tsx +26 -0
  95. package/templates/{react-router/src/layouts/sidebar/sidebar_aside → apps/react-router/src/surfaces/sidebar/user_menu}/user_menu.tsx +13 -9
  96. package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos.tsx +25 -0
  97. package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos_controller.ts +47 -0
  98. package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +18 -0
  99. package/templates/apps/react-router/src/surfaces/todos/all_todos/header/create.tsx +21 -0
  100. package/templates/apps/react-router/src/surfaces/todos/all_todos/install.tsx +20 -0
  101. package/templates/apps/react-router/src/surfaces/todos/all_todos/layout.tsx +35 -0
  102. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/create.tsx +47 -0
  103. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/main.tsx +68 -0
  104. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +54 -0
  105. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +97 -0
  106. package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
  107. package/templates/apps/react-router/src/surfaces/todos/single_todo/bootstrap.ts +36 -0
  108. package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +32 -0
  109. package/templates/apps/react-router/src/surfaces/todos/single_todo/header/header.tsx +25 -0
  110. package/templates/apps/react-router/src/surfaces/todos/single_todo/install.tsx +27 -0
  111. package/templates/apps/react-router/src/surfaces/todos/single_todo/layout.tsx +45 -0
  112. package/templates/apps/react-router/src/surfaces/todos/single_todo/main/create.tsx +35 -0
  113. package/templates/apps/react-router/src/surfaces/todos/single_todo/main/main.tsx +47 -0
  114. package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo.tsx +27 -0
  115. package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo_controller.ts +16 -0
  116. package/templates/{react-router → apps/react-router}/vite.config.ts +27 -3
  117. package/templates/{_base/biome.json → biome.json} +7 -0
  118. package/templates/bun.lock +3187 -0
  119. package/templates/{_base/emails → emails}/project.json +1 -1
  120. package/templates/{_base/nx.json → nx.json} +11 -0
  121. package/templates/package.json +92 -0
  122. package/templates/{_base/tsconfig.base.json → tsconfig.base.json} +4 -1
  123. package/templates/_base/.cursor/rules/frontend_rules.mdc +0 -268
  124. package/templates/_base/.env.convex.example +0 -3
  125. package/templates/_base/_gitignore +0 -58
  126. package/templates/_base/package.json +0 -73
  127. package/templates/nextjs/.env.example +0 -8
  128. package/templates/nextjs/src/app/(dashboard)/layout.tsx +0 -27
  129. package/templates/nextjs/src/app/(dashboard)/page.tsx +0 -5
  130. package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +0 -16
  131. package/templates/nextjs/src/middleware.ts +0 -18
  132. package/templates/nextjs/src/surfaces/home_surface.tsx +0 -22
  133. package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +0 -97
  134. package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +0 -107
  135. package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +0 -90
  136. package/templates/nextjs/src/ui/sidebar/sidebar.tsx +0 -125
  137. package/templates/react-router/.env.example +0 -8
  138. package/templates/react-router/src/app.css +0 -3
  139. package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +0 -76
  140. package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +0 -22
  141. package/templates/react-router/src/routes/index.tsx +0 -9
  142. package/templates/react-router/src/routes/layout.tsx +0 -26
  143. package/templates/react-router/src/routes/todos/[id].tsx +0 -22
  144. package/templates/react-router/src/routes/todos/index.tsx +0 -13
  145. package/templates/react-router/src/routes.ts +0 -12
  146. package/templates/react-router/src/surfaces/home_surface.tsx +0 -20
  147. package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +0 -87
  148. package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +0 -102
  149. package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +0 -81
  150. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/SKILL.md +0 -0
  151. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/SKILL.md +0 -0
  152. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +0 -0
  153. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +0 -0
  154. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +0 -0
  155. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +0 -0
  156. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +0 -0
  157. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/SKILL.md +0 -0
  158. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +0 -0
  159. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +0 -0
  160. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +0 -0
  161. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +0 -0
  162. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +0 -0
  163. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +0 -0
  164. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +0 -0
  165. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +0 -0
  166. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +0 -0
  167. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +0 -0
  168. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +0 -0
  169. /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-webhooks/SKILL.md +0 -0
  170. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/SKILL.md +0 -0
  171. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/agents/openai.yml +0 -0
  172. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn-small.png +0 -0
  173. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn.png +0 -0
  174. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/cli.md +0 -0
  175. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/customization.md +0 -0
  176. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/evals/evals.json +0 -0
  177. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/mcp.md +0 -0
  178. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/base-vs-radix.md +0 -0
  179. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/composition.md +0 -0
  180. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/forms.md +0 -0
  181. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/icons.md +0 -0
  182. /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/styling.md +0 -0
  183. /package/templates/{_base/.cursor → .cursor}/commands/builder.md +0 -0
  184. /package/templates/{_base/.cursor → .cursor}/commands/pr.md +0 -0
  185. /package/templates/{_base/.github → .github}/workflows/ci.yml +0 -0
  186. /package/templates/{_base/.nvmrc → .nvmrc} +0 -0
  187. /package/templates/{_base/.vscode → .vscode}/settings.json +0 -0
  188. /package/templates/{_base/apps → apps}/api/auth.config.ts +0 -0
  189. /package/templates/{_base/apps → apps}/api/functions.ts +0 -0
  190. /package/templates/{_base/apps → apps}/api/project.json +0 -0
  191. /package/templates/{_base/apps → apps}/api/schema.ts +0 -0
  192. /package/templates/{_base/apps → apps}/api/todos/crud.ts +0 -0
  193. /package/templates/{_base/apps → apps}/api/todos/schema.ts +0 -0
  194. /package/templates/{_base/apps → apps}/api/todos/types.ts +0 -0
  195. /package/templates/{_base/apps → apps}/api/tsconfig.json +0 -0
  196. /package/templates/{_base/apps → apps}/api/types.ts +0 -0
  197. /package/templates/{nextjs → apps/nextjs}/index.d.ts +0 -0
  198. /package/templates/{nextjs → apps/nextjs}/next-env.d.ts +0 -0
  199. /package/templates/{nextjs → apps/nextjs}/next.config.js +0 -0
  200. /package/templates/{nextjs → apps/nextjs}/postcss.config.js +0 -0
  201. /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/layout.tsx +0 -0
  202. /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -0
  203. /package/templates/{nextjs → apps/nextjs}/src/app/app.css +0 -0
  204. /package/templates/{nextjs → apps/nextjs}/src/app/layout.tsx +0 -0
  205. /package/templates/{nextjs → apps/nextjs}/src/providers/convex_provider.tsx +0 -0
  206. /package/templates/{nextjs/src → apps/nextjs/src/utils}/convex.ts +0 -0
  207. /package/templates/{nextjs → apps/nextjs}/src/utils/font.ts +0 -0
  208. /package/templates/{nextjs → apps/nextjs}/tsconfig.json +0 -0
  209. /package/templates/{react-router → apps/react-router}/postcss.config.js +0 -0
  210. /package/templates/{react-router → apps/react-router}/public/favicon.ico +0 -0
  211. /package/templates/{react-router → apps/react-router}/react-router.config.ts +0 -0
  212. /package/templates/{react-router → apps/react-router}/src/root.tsx +0 -0
  213. /package/templates/{react-router/src/routes/auth/sign-in.tsx → apps/react-router/src/routes/(auth)/sign-in/index.tsx} +0 -0
  214. /package/templates/{react-router → apps/react-router}/tsconfig.json +0 -0
  215. /package/templates/{_base/convex.json → convex.json} +0 -0
  216. /package/templates/{_base/emails → emails}/tsconfig.json +0 -0
  217. /package/templates/{_base/emails → emails}/welcome_email.tsx +0 -0
  218. /package/templates/{_base/scripts → scripts}/sync_convex_env.ts +0 -0
  219. /package/templates/{_base/shared → shared}/assets/image.d.ts +0 -0
  220. /package/templates/{_base/shared → shared}/assets/src/styles/global.css +0 -0
  221. /package/templates/{_base/shared → shared}/assets/tsconfig.json +0 -0
  222. /package/templates/{_base/shared → shared}/ui/src/base/alert_dialog.tsx +0 -0
  223. /package/templates/{_base/shared → shared}/ui/src/base/badge.tsx +0 -0
  224. /package/templates/{_base/shared → shared}/ui/src/base/basic_data_table.tsx +0 -0
  225. /package/templates/{_base/shared → shared}/ui/src/base/button.tsx +0 -0
  226. /package/templates/{_base/shared → shared}/ui/src/base/button_group.tsx +0 -0
  227. /package/templates/{_base/shared → shared}/ui/src/base/card.tsx +0 -0
  228. /package/templates/{_base/shared → shared}/ui/src/base/checkbox.tsx +0 -0
  229. /package/templates/{_base/shared → shared}/ui/src/base/command.tsx +0 -0
  230. /package/templates/{_base/shared → shared}/ui/src/base/dialog.tsx +0 -0
  231. /package/templates/{_base/shared → shared}/ui/src/base/dropdown_menu.tsx +0 -0
  232. /package/templates/{_base/shared → shared}/ui/src/base/form.tsx +0 -0
  233. /package/templates/{_base/shared → shared}/ui/src/base/input.tsx +0 -0
  234. /package/templates/{_base/shared → shared}/ui/src/base/label.tsx +0 -0
  235. /package/templates/{_base/shared → shared}/ui/src/base/popover.tsx +0 -0
  236. /package/templates/{_base/shared → shared}/ui/src/base/radio_group.tsx +0 -0
  237. /package/templates/{_base/shared → shared}/ui/src/base/resizable.tsx +0 -0
  238. /package/templates/{_base/shared → shared}/ui/src/base/scroll_area.tsx +0 -0
  239. /package/templates/{_base/shared → shared}/ui/src/base/select.tsx +0 -0
  240. /package/templates/{_base/shared → shared}/ui/src/base/separator.tsx +0 -0
  241. /package/templates/{_base/shared → shared}/ui/src/base/sheet.tsx +0 -0
  242. /package/templates/{_base/shared → shared}/ui/src/base/side_bar.tsx +0 -0
  243. /package/templates/{_base/shared → shared}/ui/src/base/skeleton.tsx +0 -0
  244. /package/templates/{_base/shared → shared}/ui/src/base/spinner.tsx +0 -0
  245. /package/templates/{_base/shared → shared}/ui/src/base/switch.tsx +0 -0
  246. /package/templates/{_base/shared → shared}/ui/src/base/table.tsx +0 -0
  247. /package/templates/{_base/shared → shared}/ui/src/base/text_area.tsx +0 -0
  248. /package/templates/{_base/shared → shared}/ui/src/base/tooltip.tsx +0 -0
  249. /package/templates/{_base/shared → shared}/ui/src/base/utils.ts +0 -0
  250. /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_press.tsx +0 -0
  251. /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_release.tsx +0 -0
  252. /package/templates/{_base/shared → shared}/ui/src/hooks/use_mobile.tsx +0 -0
  253. /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_click.tsx +0 -0
  254. /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_location.tsx +0 -0
  255. /package/templates/{_base/shared → shared}/ui/src/hooks/use_outside_click.tsx +0 -0
  256. /package/templates/{_base/shared → shared}/ui/src/hooks/use_query_params.tsx +0 -0
  257. /package/templates/{_base/shared → shared}/ui/tsconfig.json +0 -0
  258. /package/templates/{_base/shared → shared}/utils/src/convex.ts +0 -0
  259. /package/templates/{_base/shared → shared}/utils/src/time.ts +0 -0
  260. /package/templates/{_base/shared → shared}/utils/tsconfig.json +0 -0
  261. /package/templates/{_base/skills-lock.json → skills-lock.json} +0 -0
@@ -0,0 +1,110 @@
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
+ import { Link } from "react-router";
9
+
10
+ import {
11
+ Sidebar as BaseSidebar,
12
+ SidebarContent,
13
+ SidebarFooter,
14
+ SidebarHeader,
15
+ SidebarMenu,
16
+ SidebarMenuButton,
17
+ SidebarMenuItem,
18
+ } from "@/ui/base/side_bar";
19
+ import { Skeleton } from "@/ui/base/skeleton";
20
+
21
+ export class SidebarLayoutController {
22
+ @observable.ref
23
+ accessor NavMain: ComponentType | undefined = undefined;
24
+
25
+ @observable.ref
26
+ accessor NavSecondary: ComponentType | undefined = undefined;
27
+
28
+ @observable.ref
29
+ accessor UserMenu: ComponentType | undefined = undefined;
30
+
31
+ @action
32
+ setNavMain(NavMain: ComponentType) {
33
+ this.NavMain = NavMain;
34
+ }
35
+
36
+ @action
37
+ setNavSecondary(NavSecondary: ComponentType) {
38
+ this.NavSecondary = NavSecondary;
39
+ }
40
+
41
+ @action
42
+ setUserMenu(UserMenu: ComponentType) {
43
+ this.UserMenu = UserMenu;
44
+ }
45
+ }
46
+
47
+ const BrandLink = () => {
48
+ return (
49
+ <SidebarMenu>
50
+ <SidebarMenuItem>
51
+ <SidebarMenuButton
52
+ size="lg"
53
+ className="h-auto flex-col items-start gap-0.5 p-0"
54
+ asChild
55
+ >
56
+ <Link
57
+ to="/"
58
+ 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"
59
+ >
60
+ <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">
61
+ A
62
+ </div>
63
+ <div className="flex min-w-0 flex-col">
64
+ <span className="truncate text-base font-bold text-sidebar-foreground">
65
+ App
66
+ </span>
67
+ <span className="truncate text-xs text-sidebar-muted-foreground">
68
+ Template
69
+ </span>
70
+ </div>
71
+ </Link>
72
+ </SidebarMenuButton>
73
+ </SidebarMenuItem>
74
+ </SidebarMenu>
75
+ );
76
+ };
77
+
78
+ export const createSidebarLayout = () => {
79
+ const layout = new SidebarLayoutController();
80
+
81
+ return {
82
+ layout,
83
+ Layout: observer(() => {
84
+ const NavMain = layout.NavMain;
85
+ const NavSecondary = layout.NavSecondary;
86
+ const UserMenu = layout.UserMenu;
87
+
88
+ return (
89
+ <BaseSidebar
90
+ data-slot="app-sidebar"
91
+ className="shadow-sm"
92
+ collapsible="icon"
93
+ >
94
+ <SidebarHeader>
95
+ <BrandLink />
96
+ </SidebarHeader>
97
+ <SidebarContent className="flex flex-1 flex-col gap-4 overflow-hidden px-2">
98
+ {NavMain ? <NavMain /> : <Skeleton className="h-24 bg-sidebar-accent" />}
99
+ </SidebarContent>
100
+ <SidebarFooter className="gap-2">
101
+ {NavSecondary ? <NavSecondary /> : <Skeleton className="h-10 bg-sidebar-accent" />}
102
+ {UserMenu ? <UserMenu /> : <Skeleton className="h-12 bg-sidebar-accent" />}
103
+ </SidebarFooter>
104
+ </BaseSidebar>
105
+ );
106
+ }),
107
+ };
108
+ };
109
+
110
+ 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,21 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { observer } from "mobx-react-lite";
6
+ import { useLocation } from "react-router";
7
+
8
+ import { useSidebar } from "@/ui/base/side_bar";
9
+
10
+ import { NavSecondary } from "@/web/surfaces/sidebar/nav_secondary/nav_secondary";
11
+
12
+ export const createNavSecondary = () => {
13
+ return observer(() => {
14
+ const location = useLocation();
15
+ const { setOpenMobile } = useSidebar();
16
+
17
+ return (
18
+ <NavSecondary pathname={location.pathname} onNavigate={() => setOpenMobile(false)} />
19
+ );
20
+ });
21
+ };
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { Settings } from "lucide-react";
6
+ import { Link } from "react-router";
7
+
8
+ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/ui/base/side_bar";
9
+
10
+ type NavSecondaryProps = {
11
+ pathname: string;
12
+ onNavigate: () => void;
13
+ };
14
+
15
+ export const NavSecondary = ({ pathname, onNavigate }: NavSecondaryProps) => {
16
+ const settingsPath = "/settings";
17
+ const isActive = pathname === settingsPath || pathname.startsWith(`${settingsPath}/`);
18
+
19
+ return (
20
+ <SidebarMenu>
21
+ <SidebarMenuItem>
22
+ <SidebarMenuButton isActive={isActive} asChild onClick={onNavigate}>
23
+ <Link to={settingsPath}>
24
+ <Settings className="size-4" />
25
+ <span>Settings</span>
26
+ </Link>
27
+ </SidebarMenuButton>
28
+ </SidebarMenuItem>
29
+ </SidebarMenu>
30
+ );
31
+ };
@@ -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
- import { UserButton, useUser } from "@clerk/react-router";
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
- export const UserMenu = () => {
7
- const { user, isLoaded } = useUser();
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">{user.fullName}</span>
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
+ };
@@ -0,0 +1,47 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { action, observable } from "mobx";
6
+
7
+ import { api } from "@/api/_generated/api";
8
+ import type { TodoId } from "@/api/todos/types";
9
+ import { convex } from "@/web/providers/api_auth_provider";
10
+
11
+ export class AllTodosController {
12
+ @observable
13
+ accessor createTodoPending = false;
14
+
15
+ @observable
16
+ accessor isNewTodoSheetOpen = false;
17
+
18
+ @action
19
+ openNewTodoSheet() {
20
+ this.isNewTodoSheetOpen = true;
21
+ }
22
+
23
+ @action
24
+ closeNewTodoSheet() {
25
+ this.isNewTodoSheetOpen = false;
26
+ }
27
+
28
+ @action
29
+ async updateTodo(args: { todoId: TodoId; isCompleted: boolean }) {
30
+ await convex.mutation(api.todos.crud.updateTodo, args);
31
+ }
32
+
33
+ @action
34
+ async deleteTodo(args: { todoId: TodoId }) {
35
+ await convex.mutation(api.todos.crud.deleteTodo, args);
36
+ }
37
+
38
+ @action
39
+ async createTodo(args: { title: string; description?: string }) {
40
+ this.createTodoPending = true;
41
+ try {
42
+ await convex.mutation(api.todos.crud.createTodo, args);
43
+ } finally {
44
+ this.createTodoPending = false;
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { convexQuery } from "@convex-dev/react-query";
6
+
7
+ import { api } from "@/api/_generated/api";
8
+ import type { Todo } from "@/api/todos/types";
9
+ import { queryClient } from "@/web/providers/api_auth_provider";
10
+
11
+ export type AllTodosBootstrap = {
12
+ todos: Todo[];
13
+ };
14
+
15
+ export const bootstrapAllTodos = async (): Promise<AllTodosBootstrap> => {
16
+ const todos = await queryClient.ensureQueryData(convexQuery(api.todos.crud.listTodos, {}));
17
+ return { todos };
18
+ };
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { observer } from "mobx-react-lite";
6
+ import type { ComponentType } from "react";
7
+
8
+ export type CreateHeaderOpts = {
9
+ NewTodoSheet: ComponentType;
10
+ };
11
+
12
+ export const createHeader = ({ NewTodoSheet }: CreateHeaderOpts) => {
13
+ return observer(() => {
14
+ return (
15
+ <div className="mb-6 flex items-center justify-between">
16
+ <h1 className="text-2xl font-semibold">Todos</h1>
17
+ <NewTodoSheet />
18
+ </div>
19
+ );
20
+ });
21
+ };
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { AllTodosController } from "@/web/surfaces/todos/all_todos/all_todos_controller";
6
+ import type { AllTodosBootstrap } from "@/web/surfaces/todos/all_todos/bootstrap";
7
+ import type { AllTodosLayoutController } from "@/web/surfaces/todos/all_todos/layout";
8
+
9
+ export type InstallAllTodosOpts = {
10
+ layout: AllTodosLayoutController;
11
+ bootstrap: AllTodosBootstrap;
12
+ };
13
+
14
+ export const installAllTodos = ({ layout, bootstrap }: InstallAllTodosOpts) => {
15
+ const controller = new AllTodosController();
16
+
17
+ import("@/web/surfaces/todos/all_todos/main/create").then(({ createMain }) => {
18
+ layout.setMain(createMain({ controller, bootstrap }));
19
+ });
20
+ };
@@ -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 AllTodosLayoutController {
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 AllTodosLayoutController();
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,47 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { convexQuery } from "@convex-dev/react-query";
6
+ import { useQuery } from "@tanstack/react-query";
7
+ import { observer } from "mobx-react-lite";
8
+
9
+ import { api } from "@/api/_generated/api";
10
+ import type { AllTodosController } from "@/web/surfaces/todos/all_todos/all_todos_controller";
11
+ import type { AllTodosBootstrap } from "@/web/surfaces/todos/all_todos/bootstrap";
12
+ import { createHeader } from "@/web/surfaces/todos/all_todos/header/create";
13
+ import { Main } from "@/web/surfaces/todos/all_todos/main/main";
14
+ import { createNewTodoSheet } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/create";
15
+
16
+ export type CreateMainOpts = {
17
+ controller: AllTodosController;
18
+ bootstrap: AllTodosBootstrap;
19
+ };
20
+
21
+ export const createMain = ({ controller, bootstrap }: CreateMainOpts) => {
22
+ const NewTodoSheet = createNewTodoSheet({ controller });
23
+ const Header = createHeader({ NewTodoSheet });
24
+
25
+ return observer(() => {
26
+ const { data: todos, isPending } = useQuery({
27
+ ...convexQuery(api.todos.crud.listTodos, {}),
28
+ initialData: bootstrap.todos as never,
29
+ });
30
+
31
+ return (
32
+ <main className="flex flex-1 flex-col overflow-auto p-6">
33
+ <Header />
34
+ <Main
35
+ todos={todos}
36
+ isPending={isPending}
37
+ onToggle={(todoId, isCompleted) => {
38
+ void controller.updateTodo({ todoId, isCompleted });
39
+ }}
40
+ onDelete={(todoId) => {
41
+ void controller.deleteTodo({ todoId });
42
+ }}
43
+ />
44
+ </main>
45
+ );
46
+ });
47
+ };
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { Trash2 } from "lucide-react";
6
+ import { Link } from "react-router";
7
+
8
+ import type { Todo, TodoId } from "@/api/todos/types";
9
+ import { Button } from "@/ui/base/button";
10
+ import { Checkbox } from "@/ui/base/checkbox";
11
+ import { Spinner } from "@/ui/base/spinner";
12
+
13
+ type MainProps = {
14
+ todos: Todo[] | undefined;
15
+ isPending: boolean;
16
+ onToggle: (todoId: TodoId, isCompleted: boolean) => void;
17
+ onDelete: (todoId: TodoId) => void;
18
+ };
19
+
20
+ export const Main = ({ todos, isPending, onToggle, onDelete }: MainProps) => {
21
+ if (isPending) {
22
+ return (
23
+ <div className="flex flex-1 items-center justify-center">
24
+ <Spinner />
25
+ </div>
26
+ );
27
+ }
28
+
29
+ if (!todos?.length) {
30
+ return (
31
+ <div className="flex flex-1 flex-col items-center justify-center gap-2 text-muted-foreground">
32
+ <p>No todos yet.</p>
33
+ <p className="text-sm">Use New Todo above to add one.</p>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ return (
39
+ <ul className="flex max-w-2xl flex-col gap-2">
40
+ {todos.map((todo) => (
41
+ <li key={todo._id} className="flex items-center gap-3 rounded-lg border bg-card px-4 py-3">
42
+ <Checkbox
43
+ checked={todo.isCompleted}
44
+ onCheckedChange={(checked) => onToggle(todo._id, !!checked)}
45
+ />
46
+ <Link
47
+ to={`/todos/${todo._id}`}
48
+ className="flex-1 truncate text-sm hover:underline"
49
+ style={{
50
+ textDecoration: todo.isCompleted ? "line-through" : undefined,
51
+ }}
52
+ >
53
+ {todo.title}
54
+ </Link>
55
+ <Button
56
+ variant="ghost"
57
+ size="icon"
58
+ className="size-7 shrink-0 text-muted-foreground hover:text-destructive"
59
+ type="button"
60
+ onClick={() => onDelete(todo._id)}
61
+ >
62
+ <Trash2 className="size-4" />
63
+ </Button>
64
+ </li>
65
+ ))}
66
+ </ul>
67
+ );
68
+ };
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright (c) Aron Weston 2026.
3
+ */
4
+
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { observer } from "mobx-react-lite";
7
+ import { FormProvider, useForm } from "react-hook-form";
8
+
9
+ import type { AllTodosController } from "@/web/surfaces/todos/all_todos/all_todos_controller";
10
+ import { NewTodoSheet } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet";
11
+ import { type NewTodoSchema, newTodoSchema } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/schema";
12
+
13
+ export type CreateNewTodoSheetOpts = {
14
+ controller: AllTodosController;
15
+ };
16
+
17
+ export const createNewTodoSheet = ({ controller }: CreateNewTodoSheetOpts) => {
18
+ return observer(() => {
19
+ const form = useForm<NewTodoSchema>({
20
+ resolver: zodResolver(newTodoSchema),
21
+ defaultValues: {
22
+ title: "",
23
+ description: "",
24
+ },
25
+ });
26
+
27
+ const handleSubmit = form.handleSubmit(async (values) => {
28
+ await controller.createTodo({
29
+ title: values.title.trim(),
30
+ description: values.description?.trim() || undefined,
31
+ });
32
+
33
+ form.reset();
34
+ controller.closeNewTodoSheet();
35
+ });
36
+
37
+ return (
38
+ <FormProvider {...form}>
39
+ <NewTodoSheet
40
+ isPending={controller.createTodoPending}
41
+ onSubmit={handleSubmit}
42
+ open={controller.isNewTodoSheetOpen}
43
+ onOpenChange={(open) => {
44
+ if (open) {
45
+ controller.openNewTodoSheet();
46
+ } else {
47
+ controller.closeNewTodoSheet();
48
+ }
49
+ }}
50
+ />
51
+ </FormProvider>
52
+ );
53
+ });
54
+ };