create-einja-app 0.2.17 → 0.2.18

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 (270) hide show
  1. package/package.json +2 -2
  2. package/templates/default/.claude/hooks/einja/playwright-resize.sh +12 -2
  3. package/templates/default/.claude/settings.json +15 -0
  4. package/templates/default/.cursor/commands/task-vibe-kanban-loop.md +107 -42
  5. package/templates/default/.env.develop +0 -4
  6. package/templates/default/.env.example +1 -0
  7. package/templates/default/.env.preview +0 -4
  8. package/templates/default/.env.staging +19 -0
  9. package/templates/default/.github/actions/ci/action.yml +39 -0
  10. package/templates/default/.github/actions/migrate/action.yml +39 -0
  11. package/templates/default/.github/actions/neon-export-env/action.yml +28 -0
  12. package/templates/default/.github/actions/setup/action.yml +20 -0
  13. package/templates/default/.github/workflows/claude.yml +1 -0
  14. package/templates/default/.github/workflows/{cleanup-neon-branches.yml → cleanup-pr-preview-db.yml} +28 -24
  15. package/templates/default/.github/workflows/cleanup-pr-preview-on-close.yml +50 -0
  16. package/templates/default/.github/workflows/deploy-pr-preview.yml +398 -0
  17. package/templates/default/.github/workflows/deploy-stable-branches.yml +259 -0
  18. package/templates/default/.github/workflows/release-create-einja-app.yml +95 -0
  19. package/templates/default/.mcp.json +6 -9
  20. package/templates/default/CLAUDE.md +46 -9
  21. package/templates/default/README.md +5 -14
  22. package/templates/default/apps/admin/next.config.ts +11 -0
  23. package/templates/default/apps/admin/package.json +55 -0
  24. package/templates/default/apps/admin/postcss.config.cjs +5 -0
  25. package/templates/default/apps/admin/src/app/(auth)/forgot-password/page.tsx +97 -0
  26. package/templates/default/apps/admin/src/app/(auth)/layout.tsx +18 -0
  27. package/templates/default/apps/admin/src/app/(auth)/otp/page.tsx +121 -0
  28. package/templates/default/apps/admin/src/app/(auth)/sign-in/page.tsx +145 -0
  29. package/templates/default/apps/admin/src/app/(auth)/sign-up/page.tsx +199 -0
  30. package/templates/default/apps/admin/src/app/(errors)/401/page.tsx +27 -0
  31. package/templates/default/apps/admin/src/app/(errors)/403/page.tsx +28 -0
  32. package/templates/default/apps/admin/src/app/(errors)/500/page.tsx +29 -0
  33. package/templates/default/apps/admin/src/app/(errors)/layout.tsx +7 -0
  34. package/templates/default/apps/admin/src/app/(errors)/maintenance/page.tsx +25 -0
  35. package/templates/default/apps/admin/src/app/dashboard/_components/analytics-chart.tsx +68 -0
  36. package/templates/default/apps/admin/src/app/dashboard/_components/analytics.tsx +182 -0
  37. package/templates/default/apps/admin/src/app/dashboard/_components/dashboard-page.tsx +74 -0
  38. package/templates/default/apps/admin/src/app/dashboard/_components/metric-cards.tsx +49 -0
  39. package/templates/default/apps/admin/src/app/dashboard/_components/overview-chart.tsx +73 -0
  40. package/templates/default/apps/admin/src/app/dashboard/_components/recent-sales.tsx +75 -0
  41. package/templates/default/apps/admin/src/app/dashboard/apps/_components/apps-page.tsx +135 -0
  42. package/templates/default/apps/admin/src/app/dashboard/apps/page.tsx +10 -0
  43. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chat-list.tsx +82 -0
  44. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chat-messages.tsx +194 -0
  45. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chats-page.tsx +99 -0
  46. package/templates/default/apps/admin/src/app/dashboard/chats/_components/new-chat.tsx +118 -0
  47. package/templates/default/apps/admin/src/app/dashboard/chats/page.tsx +10 -0
  48. package/templates/default/apps/admin/src/app/dashboard/layout.tsx +9 -0
  49. package/templates/default/apps/admin/src/app/dashboard/not-found.tsx +14 -0
  50. package/templates/default/apps/admin/src/app/dashboard/page.tsx +10 -0
  51. package/templates/default/apps/admin/src/app/dashboard/settings/_components/content-section.tsx +20 -0
  52. package/templates/default/apps/admin/src/app/dashboard/settings/_components/sidebar-nav.tsx +66 -0
  53. package/templates/default/apps/admin/src/app/dashboard/settings/account/page.tsx +173 -0
  54. package/templates/default/apps/admin/src/app/dashboard/settings/appearance/page.tsx +156 -0
  55. package/templates/default/apps/admin/src/app/dashboard/settings/display/page.tsx +125 -0
  56. package/templates/default/apps/admin/src/app/dashboard/settings/layout.tsx +30 -0
  57. package/templates/default/apps/admin/src/app/dashboard/settings/notifications/page.tsx +196 -0
  58. package/templates/default/apps/admin/src/app/dashboard/settings/page.tsx +5 -0
  59. package/templates/default/apps/admin/src/app/dashboard/settings/profile/page.tsx +176 -0
  60. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/data-table-bulk-actions.tsx +183 -0
  61. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/data-table-row-actions.tsx +79 -0
  62. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-columns.tsx +107 -0
  63. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-dialogs.tsx +71 -0
  64. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-import-dialog.tsx +106 -0
  65. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-multi-delete-dialog.tsx +90 -0
  66. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-mutate-drawer.tsx +207 -0
  67. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-page.tsx +31 -0
  68. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-primary-buttons.tsx +19 -0
  69. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-provider.tsx +37 -0
  70. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-table.tsx +155 -0
  71. package/templates/default/apps/admin/src/app/dashboard/tasks/page.tsx +14 -0
  72. package/templates/default/apps/admin/src/app/dashboard/users/_components/data-table-bulk-actions.tsx +136 -0
  73. package/templates/default/apps/admin/src/app/dashboard/users/_components/data-table-row-actions.tsx +62 -0
  74. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-action-dialog.tsx +297 -0
  75. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-columns.tsx +121 -0
  76. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-delete-dialog.tsx +72 -0
  77. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-dialogs.tsx +49 -0
  78. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-invite-dialog.tsx +139 -0
  79. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-multi-delete-dialog.tsx +89 -0
  80. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-page.tsx +30 -0
  81. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-primary-buttons.tsx +19 -0
  82. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-provider.tsx +35 -0
  83. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-table.tsx +157 -0
  84. package/templates/default/apps/admin/src/app/dashboard/users/page.tsx +10 -0
  85. package/templates/default/apps/admin/src/app/globals.css +109 -0
  86. package/templates/default/apps/admin/src/app/layout.tsx +32 -0
  87. package/templates/default/apps/admin/src/app/not-found.tsx +14 -0
  88. package/templates/default/apps/admin/src/app/page.tsx +5 -0
  89. package/templates/default/apps/admin/src/components/layout/admin-layout.tsx +16 -0
  90. package/templates/default/apps/admin/src/components/layout/app-sidebar.tsx +52 -0
  91. package/templates/default/apps/admin/src/components/layout/nav-config.ts +131 -0
  92. package/templates/default/apps/admin/src/components/providers/theme-provider.tsx +10 -0
  93. package/templates/default/apps/admin/src/components/shared/long-text.tsx +78 -0
  94. package/templates/default/apps/admin/src/components/shared/search-input.tsx +16 -0
  95. package/templates/default/apps/admin/src/components/shared/select-dropdown.tsx +64 -0
  96. package/templates/default/apps/admin/src/data/apps.tsx +116 -0
  97. package/templates/default/apps/admin/src/data/chats.ts +114 -0
  98. package/templates/default/apps/admin/src/data/tasks.ts +114 -0
  99. package/templates/default/apps/admin/src/data/users.ts +90 -0
  100. package/templates/default/apps/admin/src/hooks/use-dialog-state.ts +17 -0
  101. package/templates/default/apps/admin/src/hooks/use-table-url-state.ts +243 -0
  102. package/templates/default/apps/admin/src/lib/show-submitted-data.tsx +12 -0
  103. package/templates/default/apps/admin/src/types/table.d.ts +9 -0
  104. package/templates/default/apps/admin/tsconfig.json +32 -0
  105. package/templates/default/apps/web/next.config.ts +1 -0
  106. package/templates/default/apps/web/package.json +0 -22
  107. package/templates/default/apps/web/postcss.config.cjs +0 -1
  108. package/templates/default/apps/web/src/app/(authenticated)/dashboard/page.tsx +4 -20
  109. package/templates/default/apps/web/src/app/(authenticated)/data/_components/UserTable.tsx +4 -4
  110. package/templates/default/apps/web/src/app/(authenticated)/data/page.tsx +1 -1
  111. package/templates/default/apps/web/src/app/(authenticated)/profile/page.tsx +1 -1
  112. package/templates/default/apps/web/src/app/error.tsx +8 -70
  113. package/templates/default/apps/web/src/app/global-error.tsx +8 -70
  114. package/templates/default/apps/web/src/app/globals.css +20 -0
  115. package/templates/default/apps/web/src/app/not-found.tsx +5 -39
  116. package/templates/default/apps/web/src/app/page.tsx +27 -203
  117. package/templates/default/apps/web/src/app/signin/page.tsx +27 -191
  118. package/templates/default/apps/web/src/app/signup/page.tsx +33 -240
  119. package/templates/default/apps/web/src/components/dashboard/dashboard-stats.tsx +11 -75
  120. package/templates/default/apps/web/src/components/shared/Sidebar.tsx +3 -3
  121. package/templates/default/apps/web/src/components/shared/header.tsx +17 -112
  122. package/templates/default/apps/web/tsconfig.json +0 -6
  123. package/templates/default/biome.json +1 -2
  124. package/templates/default/components.json +2 -2
  125. package/templates/default/docker-compose.yml +1 -1
  126. package/templates/default/gitignore +4 -0
  127. package/templates/default/package.json +1 -0
  128. package/templates/default/packages/admin-ui/catalog/catalog.css +54 -0
  129. package/templates/default/packages/admin-ui/catalog/catalog.tsx +401 -0
  130. package/templates/default/packages/admin-ui/catalog/index.html +12 -0
  131. package/templates/default/packages/admin-ui/catalog/main.tsx +9 -0
  132. package/templates/default/packages/admin-ui/components.json +21 -0
  133. package/templates/default/packages/admin-ui/package.json +105 -0
  134. package/templates/default/packages/admin-ui/src/command-menu/index.tsx +174 -0
  135. package/templates/default/packages/admin-ui/src/data-table/bulk-actions.tsx +215 -0
  136. package/templates/default/packages/admin-ui/src/data-table/column-header.tsx +73 -0
  137. package/templates/default/packages/admin-ui/src/data-table/data-table.tsx +127 -0
  138. package/templates/default/packages/admin-ui/src/data-table/faceted-filter.tsx +148 -0
  139. package/templates/default/packages/admin-ui/src/data-table/index.tsx +9 -0
  140. package/templates/default/packages/admin-ui/src/data-table/pagination.tsx +101 -0
  141. package/templates/default/packages/admin-ui/src/data-table/toolbar.tsx +87 -0
  142. package/templates/default/packages/admin-ui/src/data-table/view-options.tsx +57 -0
  143. package/templates/default/packages/admin-ui/src/hooks/use-mobile.tsx +23 -0
  144. package/templates/default/packages/admin-ui/src/layout/header.tsx +55 -0
  145. package/templates/default/packages/admin-ui/src/layout/index.ts +10 -0
  146. package/templates/default/packages/admin-ui/src/layout/main.tsx +23 -0
  147. package/templates/default/packages/admin-ui/src/layout/nav-group.tsx +111 -0
  148. package/templates/default/packages/admin-ui/src/layout/nav-user.tsx +114 -0
  149. package/templates/default/packages/admin-ui/src/layout/theme-switch.tsx +40 -0
  150. package/templates/default/packages/admin-ui/src/layout/types.ts +21 -0
  151. package/templates/default/packages/admin-ui/src/lib/utils.ts +6 -0
  152. package/templates/default/packages/admin-ui/src/styles/base.css +65 -0
  153. package/templates/default/packages/admin-ui/src/styles/tokens.css +91 -0
  154. package/templates/default/packages/admin-ui/src/tanstack-table.d.ts +10 -0
  155. package/templates/default/packages/admin-ui/src/ui/alert-dialog.tsx +157 -0
  156. package/templates/default/packages/admin-ui/src/ui/alert.tsx +66 -0
  157. package/templates/default/packages/admin-ui/src/ui/avatar.tsx +53 -0
  158. package/templates/default/packages/admin-ui/src/ui/badge.tsx +46 -0
  159. package/templates/default/packages/admin-ui/src/ui/breadcrumb.tsx +108 -0
  160. package/templates/default/packages/admin-ui/src/ui/button.tsx +59 -0
  161. package/templates/default/packages/admin-ui/src/ui/calendar.tsx +69 -0
  162. package/templates/default/packages/admin-ui/src/ui/card.tsx +92 -0
  163. package/templates/default/packages/admin-ui/src/ui/chart.tsx +345 -0
  164. package/templates/default/packages/admin-ui/src/ui/checkbox.tsx +32 -0
  165. package/templates/default/packages/admin-ui/src/ui/collapsible.tsx +27 -0
  166. package/templates/default/packages/admin-ui/src/ui/command.tsx +161 -0
  167. package/templates/default/packages/admin-ui/src/ui/confirm-dialog.tsx +72 -0
  168. package/templates/default/packages/admin-ui/src/ui/date-picker.tsx +53 -0
  169. package/templates/default/packages/admin-ui/src/ui/dialog.tsx +143 -0
  170. package/templates/default/packages/admin-ui/src/ui/dropdown-menu.tsx +257 -0
  171. package/templates/default/packages/admin-ui/src/ui/form.tsx +168 -0
  172. package/templates/default/packages/admin-ui/src/ui/input-otp.tsx +84 -0
  173. package/templates/default/packages/admin-ui/src/ui/input.tsx +21 -0
  174. package/templates/default/packages/admin-ui/src/ui/label.tsx +24 -0
  175. package/templates/default/packages/admin-ui/src/ui/pagination.tsx +126 -0
  176. package/templates/default/packages/admin-ui/src/ui/password-input.tsx +46 -0
  177. package/templates/default/packages/admin-ui/src/ui/popover.tsx +48 -0
  178. package/templates/default/packages/admin-ui/src/ui/progress.tsx +31 -0
  179. package/templates/default/packages/admin-ui/src/ui/radio-group.tsx +45 -0
  180. package/templates/default/packages/admin-ui/src/ui/scroll-area.tsx +52 -0
  181. package/templates/default/packages/admin-ui/src/ui/select.tsx +185 -0
  182. package/templates/default/packages/admin-ui/src/ui/separator.tsx +28 -0
  183. package/templates/default/packages/admin-ui/src/ui/sheet.tsx +149 -0
  184. package/templates/default/packages/admin-ui/src/ui/sidebar.tsx +728 -0
  185. package/templates/default/packages/admin-ui/src/ui/skeleton.tsx +13 -0
  186. package/templates/default/packages/admin-ui/src/ui/sonner.tsx +25 -0
  187. package/templates/default/packages/admin-ui/src/ui/switch.tsx +31 -0
  188. package/templates/default/packages/admin-ui/src/ui/table.tsx +116 -0
  189. package/templates/default/packages/admin-ui/src/ui/tabs.tsx +66 -0
  190. package/templates/default/packages/admin-ui/src/ui/textarea.tsx +18 -0
  191. package/templates/default/packages/admin-ui/src/ui/toggle-group.tsx +60 -0
  192. package/templates/default/packages/admin-ui/src/ui/toggle.tsx +44 -0
  193. package/templates/default/packages/admin-ui/src/ui/tooltip.tsx +61 -0
  194. package/templates/default/packages/admin-ui/tsconfig.json +8 -0
  195. package/templates/default/packages/admin-ui/vite.config.ts +11 -0
  196. package/templates/default/packages/config/package.json +0 -2
  197. package/templates/default/packages/server-core/package.json +1 -0
  198. package/templates/default/packages/ui/components.json +21 -0
  199. package/templates/default/packages/ui/package.json +42 -5
  200. package/templates/default/packages/ui/src/accordion.tsx +1 -1
  201. package/templates/default/packages/ui/src/alert-dialog.tsx +4 -4
  202. package/templates/default/packages/ui/src/alert.tsx +1 -1
  203. package/templates/default/packages/ui/src/avatar.tsx +1 -1
  204. package/templates/default/packages/ui/src/badge.tsx +1 -1
  205. package/templates/default/packages/ui/src/breadcrumb.tsx +1 -1
  206. package/templates/default/packages/ui/src/button.tsx +1 -1
  207. package/templates/default/packages/ui/src/card.tsx +1 -1
  208. package/templates/default/packages/ui/src/checkbox.tsx +1 -1
  209. package/templates/default/packages/ui/src/dialog.tsx +3 -3
  210. package/templates/default/packages/ui/src/drawer.tsx +3 -3
  211. package/templates/default/packages/ui/src/dropdown-menu.tsx +3 -3
  212. package/templates/default/packages/ui/src/form.tsx +2 -2
  213. package/templates/default/packages/ui/src/hover-card.tsx +2 -2
  214. package/templates/default/packages/ui/src/input.tsx +1 -1
  215. package/templates/default/packages/ui/src/label.tsx +1 -1
  216. package/templates/default/packages/ui/src/pagination.tsx +2 -2
  217. package/templates/default/packages/ui/src/popover.tsx +2 -2
  218. package/templates/default/packages/ui/src/progress.tsx +1 -1
  219. package/templates/default/packages/ui/src/select.tsx +2 -2
  220. package/templates/default/packages/ui/src/separator.tsx +1 -1
  221. package/templates/default/packages/ui/src/skeleton.tsx +1 -1
  222. package/templates/default/packages/ui/src/table.tsx +1 -1
  223. package/templates/default/packages/ui/src/tabs.tsx +1 -1
  224. package/templates/default/packages/ui/src/textarea.tsx +1 -1
  225. package/templates/default/packages/ui/src/tooltip.tsx +3 -3
  226. package/templates/default/packages/ui/src/typography.tsx +1 -1
  227. package/templates/default/packages/ui/tsconfig.json +1 -6
  228. package/templates/default/pnpm-lock.yaml +1319 -936
  229. package/templates/default/postcss.config.cjs +0 -1
  230. package/templates/default/turbo.json +11 -5
  231. package/templates/default/worktree.config.json +5 -0
  232. package/templates/default/.env.ci +0 -32
  233. package/templates/default/.github/workflows/ci.yml +0 -96
  234. package/templates/default/.github/workflows/preview-db.yml +0 -134
  235. package/templates/default/.playwright-mcp/dashboard.png +0 -0
  236. package/templates/default/.playwright-mcp/web-home.png +0 -0
  237. package/templates/default/apps/web/panda.config.ts +0 -114
  238. package/templates/default/apps/web/src/components/ui/accordion.tsx +0 -64
  239. package/templates/default/apps/web/src/components/ui/alert-dialog.tsx +0 -135
  240. package/templates/default/apps/web/src/components/ui/alert.tsx +0 -60
  241. package/templates/default/apps/web/src/components/ui/aspect-ratio.tsx +0 -9
  242. package/templates/default/apps/web/src/components/ui/avatar.tsx +0 -41
  243. package/templates/default/apps/web/src/components/ui/badge.tsx +0 -39
  244. package/templates/default/apps/web/src/components/ui/breadcrumb.tsx +0 -101
  245. package/templates/default/apps/web/src/components/ui/button.tsx +0 -56
  246. package/templates/default/apps/web/src/components/ui/card.tsx +0 -75
  247. package/templates/default/apps/web/src/components/ui/checkbox.tsx +0 -29
  248. package/templates/default/apps/web/src/components/ui/data-table.tsx +0 -189
  249. package/templates/default/apps/web/src/components/ui/dialog-hook.tsx +0 -210
  250. package/templates/default/apps/web/src/components/ui/dialog.tsx +0 -129
  251. package/templates/default/apps/web/src/components/ui/drawer.tsx +0 -124
  252. package/templates/default/apps/web/src/components/ui/dropdown-menu.tsx +0 -228
  253. package/templates/default/apps/web/src/components/ui/form.tsx +0 -152
  254. package/templates/default/apps/web/src/components/ui/hover-card.tsx +0 -38
  255. package/templates/default/apps/web/src/components/ui/input.tsx +0 -21
  256. package/templates/default/apps/web/src/components/ui/label.tsx +0 -21
  257. package/templates/default/apps/web/src/components/ui/pagination.tsx +0 -105
  258. package/templates/default/apps/web/src/components/ui/popover.tsx +0 -42
  259. package/templates/default/apps/web/src/components/ui/progress.tsx +0 -28
  260. package/templates/default/apps/web/src/components/ui/select.tsx +0 -170
  261. package/templates/default/apps/web/src/components/ui/separator.tsx +0 -28
  262. package/templates/default/apps/web/src/components/ui/skeleton.tsx +0 -13
  263. package/templates/default/apps/web/src/components/ui/sonner.tsx +0 -25
  264. package/templates/default/apps/web/src/components/ui/table.tsx +0 -92
  265. package/templates/default/apps/web/src/components/ui/tabs.tsx +0 -54
  266. package/templates/default/apps/web/src/components/ui/textarea.tsx +0 -18
  267. package/templates/default/apps/web/src/components/ui/tooltip.tsx +0 -57
  268. package/templates/default/apps/web/src/components/ui/typography.tsx +0 -158
  269. package/templates/default/packages/config/panda.config.ts +0 -114
  270. package/templates/default/panda.config.ts +0 -114
@@ -0,0 +1,176 @@
1
+ "use client";
2
+
3
+ import { showSubmittedData } from "@/lib/show-submitted-data";
4
+ import { zodResolver } from "@hookform/resolvers/zod";
5
+ import { cn } from "{{packageName}}/admin-ui/lib/utils";
6
+ import { Button } from "{{packageName}}/admin-ui/ui/button";
7
+ import {
8
+ Form,
9
+ FormControl,
10
+ FormDescription,
11
+ FormField,
12
+ FormItem,
13
+ FormLabel,
14
+ FormMessage,
15
+ } from "{{packageName}}/admin-ui/ui/form";
16
+ import { Input } from "{{packageName}}/admin-ui/ui/input";
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue,
23
+ } from "{{packageName}}/admin-ui/ui/select";
24
+ import { Textarea } from "{{packageName}}/admin-ui/ui/textarea";
25
+ import Link from "next/link";
26
+ import { useFieldArray, useForm } from "react-hook-form";
27
+ import { z } from "zod";
28
+ import { ContentSection } from "../_components/content-section";
29
+
30
+ const profileFormSchema = z.object({
31
+ username: z
32
+ .string()
33
+ .min(2, "Username must be at least 2 characters.")
34
+ .max(30, "Username must not be longer than 30 characters."),
35
+ email: z.string().email("Please select a valid email."),
36
+ bio: z.string().max(160).min(4),
37
+ urls: z
38
+ .array(
39
+ z.object({
40
+ value: z.string().url("Please enter a valid URL."),
41
+ })
42
+ )
43
+ .optional(),
44
+ });
45
+
46
+ type ProfileFormValues = z.infer<typeof profileFormSchema>;
47
+
48
+ const defaultValues: Partial<ProfileFormValues> = {
49
+ username: "shadcn",
50
+ email: "m@example.com",
51
+ bio: "I own a computer.",
52
+ urls: [{ value: "https://shadcn.com" }, { value: "http://twitter.com/shadcn" }],
53
+ };
54
+
55
+ export default function ProfilePage() {
56
+ const form = useForm<ProfileFormValues>({
57
+ resolver: zodResolver(profileFormSchema),
58
+ defaultValues,
59
+ mode: "onChange",
60
+ });
61
+
62
+ const { fields, append } = useFieldArray({
63
+ name: "urls",
64
+ control: form.control,
65
+ });
66
+
67
+ function onSubmit(data: ProfileFormValues) {
68
+ showSubmittedData(data);
69
+ }
70
+
71
+ return (
72
+ <ContentSection title="Profile" desc="This is how others will see you on the site.">
73
+ <Form {...form}>
74
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
75
+ <FormField
76
+ control={form.control}
77
+ name="username"
78
+ render={({ field }) => (
79
+ <FormItem>
80
+ <FormLabel>Username</FormLabel>
81
+ <FormControl>
82
+ <Input placeholder="shadcn" {...field} />
83
+ </FormControl>
84
+ <FormDescription>
85
+ This is your public display name. It can be your real name or a pseudonym. You can
86
+ only change this once every 30 days.
87
+ </FormDescription>
88
+ <FormMessage />
89
+ </FormItem>
90
+ )}
91
+ />
92
+ <FormField
93
+ control={form.control}
94
+ name="email"
95
+ render={({ field }) => (
96
+ <FormItem>
97
+ <FormLabel>Email</FormLabel>
98
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
99
+ <FormControl>
100
+ <SelectTrigger>
101
+ <SelectValue placeholder="Select a verified email to display" />
102
+ </SelectTrigger>
103
+ </FormControl>
104
+ <SelectContent>
105
+ <SelectItem value="m@example.com">m@example.com</SelectItem>
106
+ <SelectItem value="m@google.com">m@google.com</SelectItem>
107
+ <SelectItem value="m@support.com">m@support.com</SelectItem>
108
+ </SelectContent>
109
+ </Select>
110
+ <FormDescription>
111
+ You can manage verified email addresses in your{" "}
112
+ <Link href="#" className="underline">
113
+ email settings
114
+ </Link>
115
+ .
116
+ </FormDescription>
117
+ <FormMessage />
118
+ </FormItem>
119
+ )}
120
+ />
121
+ <FormField
122
+ control={form.control}
123
+ name="bio"
124
+ render={({ field }) => (
125
+ <FormItem>
126
+ <FormLabel>Bio</FormLabel>
127
+ <FormControl>
128
+ <Textarea
129
+ placeholder="Tell us a little bit about yourself"
130
+ className="resize-none"
131
+ {...field}
132
+ />
133
+ </FormControl>
134
+ <FormDescription>
135
+ You can <span>@mention</span> other users and organizations to link to them.
136
+ </FormDescription>
137
+ <FormMessage />
138
+ </FormItem>
139
+ )}
140
+ />
141
+ <div>
142
+ {fields.map((field, index) => (
143
+ <FormField
144
+ control={form.control}
145
+ key={field.id}
146
+ name={`urls.${index}.value`}
147
+ render={({ field }) => (
148
+ <FormItem>
149
+ <FormLabel className={cn(index !== 0 && "sr-only")}>URLs</FormLabel>
150
+ <FormDescription className={cn(index !== 0 && "sr-only")}>
151
+ Add links to your website, blog, or social media profiles.
152
+ </FormDescription>
153
+ <FormControl>
154
+ <Input {...field} />
155
+ </FormControl>
156
+ <FormMessage />
157
+ </FormItem>
158
+ )}
159
+ />
160
+ ))}
161
+ <Button
162
+ type="button"
163
+ variant="outline"
164
+ size="sm"
165
+ className="mt-2"
166
+ onClick={() => append({ value: "" })}
167
+ >
168
+ Add URL
169
+ </Button>
170
+ </div>
171
+ <Button type="submit">Update profile</Button>
172
+ </form>
173
+ </Form>
174
+ </ContentSection>
175
+ );
176
+ }
@@ -0,0 +1,183 @@
1
+ "use client";
2
+
3
+ import { type Task, priorities, statuses } from "@/data/tasks";
4
+ import { DataTableBulkActions as BulkActionsToolbar } from "{{packageName}}/admin-ui/data-table";
5
+ import { Button } from "{{packageName}}/admin-ui/ui/button";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuTrigger,
11
+ } from "{{packageName}}/admin-ui/ui/dropdown-menu";
12
+ import { Tooltip, TooltipContent, TooltipTrigger } from "{{packageName}}/admin-ui/ui/tooltip";
13
+ import type { Table } from "@tanstack/react-table";
14
+ import { ArrowUpDown, CircleArrowUp, Download, Trash2 } from "lucide-react";
15
+ import { useState } from "react";
16
+ import { toast } from "sonner";
17
+ import { TasksMultiDeleteDialog } from "./tasks-multi-delete-dialog";
18
+
19
+ type DataTableBulkActionsProps<TData> = {
20
+ table: Table<TData>;
21
+ };
22
+
23
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
24
+
25
+ export function DataTableBulkActions<TData>({ table }: DataTableBulkActionsProps<TData>) {
26
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
27
+ const selectedRows = table.getFilteredSelectedRowModel().rows;
28
+
29
+ const handleBulkStatusChange = (status: string) => {
30
+ const selectedTasks = selectedRows.map((row) => row.original as Task);
31
+ toast.promise(sleep(2000), {
32
+ loading: "Updating status...",
33
+ success: () => {
34
+ table.resetRowSelection();
35
+ return `Status updated to "${status}" for ${selectedTasks.length} task${selectedTasks.length > 1 ? "s" : ""}.`;
36
+ },
37
+ error: "Error",
38
+ });
39
+ table.resetRowSelection();
40
+ };
41
+
42
+ const handleBulkPriorityChange = (priority: string) => {
43
+ const selectedTasks = selectedRows.map((row) => row.original as Task);
44
+ toast.promise(sleep(2000), {
45
+ loading: "Updating priority...",
46
+ success: () => {
47
+ table.resetRowSelection();
48
+ return `Priority updated to "${priority}" for ${selectedTasks.length} task${selectedTasks.length > 1 ? "s" : ""}.`;
49
+ },
50
+ error: "Error",
51
+ });
52
+ table.resetRowSelection();
53
+ };
54
+
55
+ const handleBulkExport = () => {
56
+ const selectedTasks = selectedRows.map((row) => row.original as Task);
57
+ toast.promise(sleep(2000), {
58
+ loading: "Exporting tasks...",
59
+ success: () => {
60
+ table.resetRowSelection();
61
+ return `Exported ${selectedTasks.length} task${selectedTasks.length > 1 ? "s" : ""} to CSV.`;
62
+ },
63
+ error: "Error",
64
+ });
65
+ table.resetRowSelection();
66
+ };
67
+
68
+ return (
69
+ <>
70
+ <BulkActionsToolbar table={table} entityName="task">
71
+ <DropdownMenu>
72
+ <Tooltip>
73
+ <TooltipTrigger asChild>
74
+ <DropdownMenuTrigger asChild>
75
+ <Button
76
+ variant="outline"
77
+ size="icon"
78
+ className="size-8"
79
+ aria-label="Update status"
80
+ title="Update status"
81
+ >
82
+ <CircleArrowUp />
83
+ <span className="sr-only">Update status</span>
84
+ </Button>
85
+ </DropdownMenuTrigger>
86
+ </TooltipTrigger>
87
+ <TooltipContent>
88
+ <p>Update status</p>
89
+ </TooltipContent>
90
+ </Tooltip>
91
+ <DropdownMenuContent sideOffset={14}>
92
+ {statuses.map((status) => (
93
+ <DropdownMenuItem
94
+ key={status.value}
95
+ onClick={() => handleBulkStatusChange(status.value)}
96
+ >
97
+ {status.icon && <status.icon className="size-4 text-muted-foreground" />}
98
+ {status.label}
99
+ </DropdownMenuItem>
100
+ ))}
101
+ </DropdownMenuContent>
102
+ </DropdownMenu>
103
+
104
+ <DropdownMenu>
105
+ <Tooltip>
106
+ <TooltipTrigger asChild>
107
+ <DropdownMenuTrigger asChild>
108
+ <Button
109
+ variant="outline"
110
+ size="icon"
111
+ className="size-8"
112
+ aria-label="Update priority"
113
+ title="Update priority"
114
+ >
115
+ <ArrowUpDown />
116
+ <span className="sr-only">Update priority</span>
117
+ </Button>
118
+ </DropdownMenuTrigger>
119
+ </TooltipTrigger>
120
+ <TooltipContent>
121
+ <p>Update priority</p>
122
+ </TooltipContent>
123
+ </Tooltip>
124
+ <DropdownMenuContent sideOffset={14}>
125
+ {priorities.map((priority) => (
126
+ <DropdownMenuItem
127
+ key={priority.value}
128
+ onClick={() => handleBulkPriorityChange(priority.value)}
129
+ >
130
+ {priority.icon && <priority.icon className="size-4 text-muted-foreground" />}
131
+ {priority.label}
132
+ </DropdownMenuItem>
133
+ ))}
134
+ </DropdownMenuContent>
135
+ </DropdownMenu>
136
+
137
+ <Tooltip>
138
+ <TooltipTrigger asChild>
139
+ <Button
140
+ variant="outline"
141
+ size="icon"
142
+ onClick={() => handleBulkExport()}
143
+ className="size-8"
144
+ aria-label="Export tasks"
145
+ title="Export tasks"
146
+ >
147
+ <Download />
148
+ <span className="sr-only">Export tasks</span>
149
+ </Button>
150
+ </TooltipTrigger>
151
+ <TooltipContent>
152
+ <p>Export tasks</p>
153
+ </TooltipContent>
154
+ </Tooltip>
155
+
156
+ <Tooltip>
157
+ <TooltipTrigger asChild>
158
+ <Button
159
+ variant="destructive"
160
+ size="icon"
161
+ onClick={() => setShowDeleteConfirm(true)}
162
+ className="size-8"
163
+ aria-label="Delete selected tasks"
164
+ title="Delete selected tasks"
165
+ >
166
+ <Trash2 />
167
+ <span className="sr-only">Delete selected tasks</span>
168
+ </Button>
169
+ </TooltipTrigger>
170
+ <TooltipContent>
171
+ <p>Delete selected tasks</p>
172
+ </TooltipContent>
173
+ </Tooltip>
174
+ </BulkActionsToolbar>
175
+
176
+ <TasksMultiDeleteDialog
177
+ open={showDeleteConfirm}
178
+ onOpenChange={setShowDeleteConfirm}
179
+ table={table}
180
+ />
181
+ </>
182
+ );
183
+ }
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { labels, taskSchema } from "@/data/tasks";
4
+ import { DotsHorizontalIcon } from "@radix-ui/react-icons";
5
+ import { Button } from "{{packageName}}/admin-ui/ui/button";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuRadioGroup,
11
+ DropdownMenuRadioItem,
12
+ DropdownMenuSeparator,
13
+ DropdownMenuShortcut,
14
+ DropdownMenuSub,
15
+ DropdownMenuSubContent,
16
+ DropdownMenuSubTrigger,
17
+ DropdownMenuTrigger,
18
+ } from "{{packageName}}/admin-ui/ui/dropdown-menu";
19
+ import type { Row } from "@tanstack/react-table";
20
+ import { Trash2 } from "lucide-react";
21
+ import { useTasks } from "./tasks-provider";
22
+
23
+ type DataTableRowActionsProps<TData> = {
24
+ row: Row<TData>;
25
+ };
26
+
27
+ export function DataTableRowActions<TData>({ row }: DataTableRowActionsProps<TData>) {
28
+ const task = taskSchema.parse(row.original);
29
+
30
+ const { setOpen, setCurrentRow } = useTasks();
31
+
32
+ return (
33
+ <DropdownMenu modal={false}>
34
+ <DropdownMenuTrigger asChild>
35
+ <Button variant="ghost" className="flex h-8 w-8 p-0 data-[state=open]:bg-muted">
36
+ <DotsHorizontalIcon className="h-4 w-4" />
37
+ <span className="sr-only">Open menu</span>
38
+ </Button>
39
+ </DropdownMenuTrigger>
40
+ <DropdownMenuContent align="end" className="w-[160px]">
41
+ <DropdownMenuItem
42
+ onClick={() => {
43
+ setCurrentRow(task);
44
+ setOpen("update");
45
+ }}
46
+ >
47
+ Edit
48
+ </DropdownMenuItem>
49
+ <DropdownMenuItem disabled>Make a copy</DropdownMenuItem>
50
+ <DropdownMenuItem disabled>Favorite</DropdownMenuItem>
51
+ <DropdownMenuSeparator />
52
+ <DropdownMenuSub>
53
+ <DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>
54
+ <DropdownMenuSubContent>
55
+ <DropdownMenuRadioGroup value={task.label}>
56
+ {labels.map((label) => (
57
+ <DropdownMenuRadioItem key={label.value} value={label.value}>
58
+ {label.label}
59
+ </DropdownMenuRadioItem>
60
+ ))}
61
+ </DropdownMenuRadioGroup>
62
+ </DropdownMenuSubContent>
63
+ </DropdownMenuSub>
64
+ <DropdownMenuSeparator />
65
+ <DropdownMenuItem
66
+ onClick={() => {
67
+ setCurrentRow(task);
68
+ setOpen("delete");
69
+ }}
70
+ >
71
+ Delete
72
+ <DropdownMenuShortcut>
73
+ <Trash2 size={16} />
74
+ </DropdownMenuShortcut>
75
+ </DropdownMenuItem>
76
+ </DropdownMenuContent>
77
+ </DropdownMenu>
78
+ );
79
+ }
@@ -0,0 +1,107 @@
1
+ "use client";
2
+
3
+ import { type Task, labels, priorities, statuses } from "@/data/tasks";
4
+ import { DataTableColumnHeader } from "{{packageName}}/admin-ui/data-table";
5
+ import { Badge } from "{{packageName}}/admin-ui/ui/badge";
6
+ import { Checkbox } from "{{packageName}}/admin-ui/ui/checkbox";
7
+ import type { ColumnDef } from "@tanstack/react-table";
8
+ import { DataTableRowActions } from "./data-table-row-actions";
9
+
10
+ export const tasksColumns: ColumnDef<Task>[] = [
11
+ {
12
+ id: "select",
13
+ header: ({ table }) => (
14
+ <Checkbox
15
+ checked={
16
+ table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")
17
+ }
18
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
19
+ aria-label="Select all"
20
+ className="translate-y-[2px]"
21
+ />
22
+ ),
23
+ cell: ({ row }) => (
24
+ <Checkbox
25
+ checked={row.getIsSelected()}
26
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
27
+ aria-label="Select row"
28
+ className="translate-y-[2px]"
29
+ />
30
+ ),
31
+ enableSorting: false,
32
+ enableHiding: false,
33
+ },
34
+ {
35
+ accessorKey: "id",
36
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Task" />,
37
+ cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
38
+ enableSorting: false,
39
+ enableHiding: false,
40
+ },
41
+ {
42
+ accessorKey: "title",
43
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Title" />,
44
+ meta: {
45
+ className: "ps-1 max-w-0 w-2/3",
46
+ tdClassName: "ps-4",
47
+ },
48
+ cell: ({ row }) => {
49
+ const label = labels.find((label) => label.value === row.original.label);
50
+
51
+ return (
52
+ <div className="flex space-x-2">
53
+ {label && <Badge variant="outline">{label.label}</Badge>}
54
+ <span className="truncate font-medium">{row.getValue("title")}</span>
55
+ </div>
56
+ );
57
+ },
58
+ },
59
+ {
60
+ accessorKey: "status",
61
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Status" />,
62
+ meta: { className: "ps-1", tdClassName: "ps-4" },
63
+ cell: ({ row }) => {
64
+ const status = statuses.find((status) => status.value === row.getValue("status"));
65
+
66
+ if (!status) {
67
+ return null;
68
+ }
69
+
70
+ return (
71
+ <div className="flex w-[100px] items-center gap-2">
72
+ {status.icon && <status.icon className="size-4 text-muted-foreground" />}
73
+ <span>{status.label}</span>
74
+ </div>
75
+ );
76
+ },
77
+ filterFn: (row, id, value) => {
78
+ return value.includes(row.getValue(id));
79
+ },
80
+ },
81
+ {
82
+ accessorKey: "priority",
83
+ header: ({ column }) => <DataTableColumnHeader column={column} title="Priority" />,
84
+ meta: { className: "ps-1", tdClassName: "ps-3" },
85
+ cell: ({ row }) => {
86
+ const priority = priorities.find((priority) => priority.value === row.getValue("priority"));
87
+
88
+ if (!priority) {
89
+ return null;
90
+ }
91
+
92
+ return (
93
+ <div className="flex items-center gap-2">
94
+ {priority.icon && <priority.icon className="size-4 text-muted-foreground" />}
95
+ <span>{priority.label}</span>
96
+ </div>
97
+ );
98
+ },
99
+ filterFn: (row, id, value) => {
100
+ return value.includes(row.getValue(id));
101
+ },
102
+ },
103
+ {
104
+ id: "actions",
105
+ cell: ({ row }) => <DataTableRowActions row={row} />,
106
+ },
107
+ ];
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { ConfirmDialog } from "{{packageName}}/admin-ui/ui/confirm-dialog";
4
+ import { toast } from "sonner";
5
+ import { TasksImportDialog } from "./tasks-import-dialog";
6
+ import { TasksMutateDrawer } from "./tasks-mutate-drawer";
7
+ import { useTasks } from "./tasks-provider";
8
+
9
+ export function TasksDialogs() {
10
+ const { open, setOpen, currentRow, setCurrentRow } = useTasks();
11
+
12
+ return (
13
+ <>
14
+ <TasksMutateDrawer
15
+ key="task-create"
16
+ open={open === "create"}
17
+ onOpenChange={() => setOpen("create")}
18
+ />
19
+
20
+ <TasksImportDialog
21
+ key="tasks-import"
22
+ open={open === "import"}
23
+ onOpenChange={() => setOpen("import")}
24
+ />
25
+
26
+ {currentRow && (
27
+ <>
28
+ <TasksMutateDrawer
29
+ key={`task-update-${currentRow.id}`}
30
+ open={open === "update"}
31
+ onOpenChange={() => {
32
+ setOpen("update");
33
+ setTimeout(() => {
34
+ setCurrentRow(null);
35
+ }, 500);
36
+ }}
37
+ currentRow={currentRow}
38
+ />
39
+
40
+ <ConfirmDialog
41
+ key="task-delete"
42
+ destructive
43
+ open={open === "delete"}
44
+ onOpenChange={() => {
45
+ setOpen("delete");
46
+ setTimeout(() => {
47
+ setCurrentRow(null);
48
+ }, 500);
49
+ }}
50
+ handleConfirm={() => {
51
+ setOpen(null);
52
+ setTimeout(() => {
53
+ setCurrentRow(null);
54
+ }, 500);
55
+ toast.success(`The following task has been deleted: ${currentRow.id}`);
56
+ }}
57
+ className="max-w-md"
58
+ title={`Delete this task: ${currentRow.id}?`}
59
+ desc={
60
+ <>
61
+ You are about to delete a task with the ID <strong>{currentRow.id}</strong>. <br />
62
+ This action cannot be undone.
63
+ </>
64
+ }
65
+ confirmText="Delete"
66
+ />
67
+ </>
68
+ )}
69
+ </>
70
+ );
71
+ }