create-einja-app 0.2.15 → 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 (272) hide show
  1. package/package.json +2 -2
  2. package/templates/default/.claude/hooks/einja/biome-format.sh +2 -2
  3. package/templates/default/.claude/hooks/einja/playwright-resize.sh +12 -2
  4. package/templates/default/.claude/settings.json +15 -0
  5. package/templates/default/.cursor/commands/task-vibe-kanban-loop.md +107 -42
  6. package/templates/default/.env.develop +0 -4
  7. package/templates/default/.env.example +1 -0
  8. package/templates/default/.env.preview +0 -4
  9. package/templates/default/.env.staging +19 -0
  10. package/templates/default/.github/actions/ci/action.yml +39 -0
  11. package/templates/default/.github/actions/migrate/action.yml +39 -0
  12. package/templates/default/.github/actions/neon-export-env/action.yml +28 -0
  13. package/templates/default/.github/actions/setup/action.yml +20 -0
  14. package/templates/default/.github/workflows/claude.yml +1 -0
  15. package/templates/default/.github/workflows/{cleanup-neon-branches.yml → cleanup-pr-preview-db.yml} +28 -24
  16. package/templates/default/.github/workflows/cleanup-pr-preview-on-close.yml +50 -0
  17. package/templates/default/.github/workflows/deploy-pr-preview.yml +398 -0
  18. package/templates/default/.github/workflows/deploy-stable-branches.yml +259 -0
  19. package/templates/default/.github/workflows/release-create-einja-app.yml +95 -0
  20. package/templates/default/.mcp.json +6 -9
  21. package/templates/default/.serena/project.yml +22 -1
  22. package/templates/default/CLAUDE.md +52 -10
  23. package/templates/default/README.md +5 -14
  24. package/templates/default/apps/admin/next.config.ts +11 -0
  25. package/templates/default/apps/admin/package.json +55 -0
  26. package/templates/default/apps/admin/postcss.config.cjs +5 -0
  27. package/templates/default/apps/admin/src/app/(auth)/forgot-password/page.tsx +97 -0
  28. package/templates/default/apps/admin/src/app/(auth)/layout.tsx +18 -0
  29. package/templates/default/apps/admin/src/app/(auth)/otp/page.tsx +121 -0
  30. package/templates/default/apps/admin/src/app/(auth)/sign-in/page.tsx +145 -0
  31. package/templates/default/apps/admin/src/app/(auth)/sign-up/page.tsx +199 -0
  32. package/templates/default/apps/admin/src/app/(errors)/401/page.tsx +27 -0
  33. package/templates/default/apps/admin/src/app/(errors)/403/page.tsx +28 -0
  34. package/templates/default/apps/admin/src/app/(errors)/500/page.tsx +29 -0
  35. package/templates/default/apps/admin/src/app/(errors)/layout.tsx +7 -0
  36. package/templates/default/apps/admin/src/app/(errors)/maintenance/page.tsx +25 -0
  37. package/templates/default/apps/admin/src/app/dashboard/_components/analytics-chart.tsx +68 -0
  38. package/templates/default/apps/admin/src/app/dashboard/_components/analytics.tsx +182 -0
  39. package/templates/default/apps/admin/src/app/dashboard/_components/dashboard-page.tsx +74 -0
  40. package/templates/default/apps/admin/src/app/dashboard/_components/metric-cards.tsx +49 -0
  41. package/templates/default/apps/admin/src/app/dashboard/_components/overview-chart.tsx +73 -0
  42. package/templates/default/apps/admin/src/app/dashboard/_components/recent-sales.tsx +75 -0
  43. package/templates/default/apps/admin/src/app/dashboard/apps/_components/apps-page.tsx +135 -0
  44. package/templates/default/apps/admin/src/app/dashboard/apps/page.tsx +10 -0
  45. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chat-list.tsx +82 -0
  46. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chat-messages.tsx +194 -0
  47. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chats-page.tsx +99 -0
  48. package/templates/default/apps/admin/src/app/dashboard/chats/_components/new-chat.tsx +118 -0
  49. package/templates/default/apps/admin/src/app/dashboard/chats/page.tsx +10 -0
  50. package/templates/default/apps/admin/src/app/dashboard/layout.tsx +9 -0
  51. package/templates/default/apps/admin/src/app/dashboard/not-found.tsx +14 -0
  52. package/templates/default/apps/admin/src/app/dashboard/page.tsx +10 -0
  53. package/templates/default/apps/admin/src/app/dashboard/settings/_components/content-section.tsx +20 -0
  54. package/templates/default/apps/admin/src/app/dashboard/settings/_components/sidebar-nav.tsx +66 -0
  55. package/templates/default/apps/admin/src/app/dashboard/settings/account/page.tsx +173 -0
  56. package/templates/default/apps/admin/src/app/dashboard/settings/appearance/page.tsx +156 -0
  57. package/templates/default/apps/admin/src/app/dashboard/settings/display/page.tsx +125 -0
  58. package/templates/default/apps/admin/src/app/dashboard/settings/layout.tsx +30 -0
  59. package/templates/default/apps/admin/src/app/dashboard/settings/notifications/page.tsx +196 -0
  60. package/templates/default/apps/admin/src/app/dashboard/settings/page.tsx +5 -0
  61. package/templates/default/apps/admin/src/app/dashboard/settings/profile/page.tsx +176 -0
  62. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/data-table-bulk-actions.tsx +183 -0
  63. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/data-table-row-actions.tsx +79 -0
  64. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-columns.tsx +107 -0
  65. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-dialogs.tsx +71 -0
  66. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-import-dialog.tsx +106 -0
  67. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-multi-delete-dialog.tsx +90 -0
  68. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-mutate-drawer.tsx +207 -0
  69. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-page.tsx +31 -0
  70. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-primary-buttons.tsx +19 -0
  71. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-provider.tsx +37 -0
  72. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-table.tsx +155 -0
  73. package/templates/default/apps/admin/src/app/dashboard/tasks/page.tsx +14 -0
  74. package/templates/default/apps/admin/src/app/dashboard/users/_components/data-table-bulk-actions.tsx +136 -0
  75. package/templates/default/apps/admin/src/app/dashboard/users/_components/data-table-row-actions.tsx +62 -0
  76. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-action-dialog.tsx +297 -0
  77. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-columns.tsx +121 -0
  78. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-delete-dialog.tsx +72 -0
  79. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-dialogs.tsx +49 -0
  80. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-invite-dialog.tsx +139 -0
  81. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-multi-delete-dialog.tsx +89 -0
  82. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-page.tsx +30 -0
  83. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-primary-buttons.tsx +19 -0
  84. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-provider.tsx +35 -0
  85. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-table.tsx +157 -0
  86. package/templates/default/apps/admin/src/app/dashboard/users/page.tsx +10 -0
  87. package/templates/default/apps/admin/src/app/globals.css +109 -0
  88. package/templates/default/apps/admin/src/app/layout.tsx +32 -0
  89. package/templates/default/apps/admin/src/app/not-found.tsx +14 -0
  90. package/templates/default/apps/admin/src/app/page.tsx +5 -0
  91. package/templates/default/apps/admin/src/components/layout/admin-layout.tsx +16 -0
  92. package/templates/default/apps/admin/src/components/layout/app-sidebar.tsx +52 -0
  93. package/templates/default/apps/admin/src/components/layout/nav-config.ts +131 -0
  94. package/templates/default/apps/admin/src/components/providers/theme-provider.tsx +10 -0
  95. package/templates/default/apps/admin/src/components/shared/long-text.tsx +78 -0
  96. package/templates/default/apps/admin/src/components/shared/search-input.tsx +16 -0
  97. package/templates/default/apps/admin/src/components/shared/select-dropdown.tsx +64 -0
  98. package/templates/default/apps/admin/src/data/apps.tsx +116 -0
  99. package/templates/default/apps/admin/src/data/chats.ts +114 -0
  100. package/templates/default/apps/admin/src/data/tasks.ts +114 -0
  101. package/templates/default/apps/admin/src/data/users.ts +90 -0
  102. package/templates/default/apps/admin/src/hooks/use-dialog-state.ts +17 -0
  103. package/templates/default/apps/admin/src/hooks/use-table-url-state.ts +243 -0
  104. package/templates/default/apps/admin/src/lib/show-submitted-data.tsx +12 -0
  105. package/templates/default/apps/admin/src/types/table.d.ts +9 -0
  106. package/templates/default/apps/admin/tsconfig.json +32 -0
  107. package/templates/default/apps/web/next.config.ts +1 -0
  108. package/templates/default/apps/web/package.json +1 -22
  109. package/templates/default/apps/web/postcss.config.cjs +0 -1
  110. package/templates/default/apps/web/src/app/(authenticated)/dashboard/page.tsx +4 -20
  111. package/templates/default/apps/web/src/app/(authenticated)/data/_components/UserTable.tsx +4 -4
  112. package/templates/default/apps/web/src/app/(authenticated)/data/page.tsx +1 -1
  113. package/templates/default/apps/web/src/app/(authenticated)/profile/page.tsx +1 -1
  114. package/templates/default/apps/web/src/app/error.tsx +8 -70
  115. package/templates/default/apps/web/src/app/global-error.tsx +8 -70
  116. package/templates/default/apps/web/src/app/globals.css +20 -0
  117. package/templates/default/apps/web/src/app/not-found.tsx +5 -39
  118. package/templates/default/apps/web/src/app/page.tsx +27 -203
  119. package/templates/default/apps/web/src/app/signin/page.tsx +27 -191
  120. package/templates/default/apps/web/src/app/signup/page.tsx +33 -240
  121. package/templates/default/apps/web/src/components/dashboard/dashboard-stats.tsx +11 -75
  122. package/templates/default/apps/web/src/components/shared/Sidebar.tsx +3 -3
  123. package/templates/default/apps/web/src/components/shared/header.tsx +17 -112
  124. package/templates/default/apps/web/tsconfig.json +0 -6
  125. package/templates/default/biome.json +30 -2
  126. package/templates/default/components.json +2 -2
  127. package/templates/default/docker-compose.yml +1 -1
  128. package/templates/default/gitignore +4 -0
  129. package/templates/default/package.json +2 -0
  130. package/templates/default/packages/admin-ui/catalog/catalog.css +54 -0
  131. package/templates/default/packages/admin-ui/catalog/catalog.tsx +401 -0
  132. package/templates/default/packages/admin-ui/catalog/index.html +12 -0
  133. package/templates/default/packages/admin-ui/catalog/main.tsx +9 -0
  134. package/templates/default/packages/admin-ui/components.json +21 -0
  135. package/templates/default/packages/admin-ui/package.json +105 -0
  136. package/templates/default/packages/admin-ui/src/command-menu/index.tsx +174 -0
  137. package/templates/default/packages/admin-ui/src/data-table/bulk-actions.tsx +215 -0
  138. package/templates/default/packages/admin-ui/src/data-table/column-header.tsx +73 -0
  139. package/templates/default/packages/admin-ui/src/data-table/data-table.tsx +127 -0
  140. package/templates/default/packages/admin-ui/src/data-table/faceted-filter.tsx +148 -0
  141. package/templates/default/packages/admin-ui/src/data-table/index.tsx +9 -0
  142. package/templates/default/packages/admin-ui/src/data-table/pagination.tsx +101 -0
  143. package/templates/default/packages/admin-ui/src/data-table/toolbar.tsx +87 -0
  144. package/templates/default/packages/admin-ui/src/data-table/view-options.tsx +57 -0
  145. package/templates/default/packages/admin-ui/src/hooks/use-mobile.tsx +23 -0
  146. package/templates/default/packages/admin-ui/src/layout/header.tsx +55 -0
  147. package/templates/default/packages/admin-ui/src/layout/index.ts +10 -0
  148. package/templates/default/packages/admin-ui/src/layout/main.tsx +23 -0
  149. package/templates/default/packages/admin-ui/src/layout/nav-group.tsx +111 -0
  150. package/templates/default/packages/admin-ui/src/layout/nav-user.tsx +114 -0
  151. package/templates/default/packages/admin-ui/src/layout/theme-switch.tsx +40 -0
  152. package/templates/default/packages/admin-ui/src/layout/types.ts +21 -0
  153. package/templates/default/packages/admin-ui/src/lib/utils.ts +6 -0
  154. package/templates/default/packages/admin-ui/src/styles/base.css +65 -0
  155. package/templates/default/packages/admin-ui/src/styles/tokens.css +91 -0
  156. package/templates/default/packages/admin-ui/src/tanstack-table.d.ts +10 -0
  157. package/templates/default/packages/admin-ui/src/ui/alert-dialog.tsx +157 -0
  158. package/templates/default/packages/admin-ui/src/ui/alert.tsx +66 -0
  159. package/templates/default/packages/admin-ui/src/ui/avatar.tsx +53 -0
  160. package/templates/default/packages/admin-ui/src/ui/badge.tsx +46 -0
  161. package/templates/default/packages/admin-ui/src/ui/breadcrumb.tsx +108 -0
  162. package/templates/default/packages/admin-ui/src/ui/button.tsx +59 -0
  163. package/templates/default/packages/admin-ui/src/ui/calendar.tsx +69 -0
  164. package/templates/default/packages/admin-ui/src/ui/card.tsx +92 -0
  165. package/templates/default/packages/admin-ui/src/ui/chart.tsx +345 -0
  166. package/templates/default/packages/admin-ui/src/ui/checkbox.tsx +32 -0
  167. package/templates/default/packages/admin-ui/src/ui/collapsible.tsx +27 -0
  168. package/templates/default/packages/admin-ui/src/ui/command.tsx +161 -0
  169. package/templates/default/packages/admin-ui/src/ui/confirm-dialog.tsx +72 -0
  170. package/templates/default/packages/admin-ui/src/ui/date-picker.tsx +53 -0
  171. package/templates/default/packages/admin-ui/src/ui/dialog.tsx +143 -0
  172. package/templates/default/packages/admin-ui/src/ui/dropdown-menu.tsx +257 -0
  173. package/templates/default/packages/admin-ui/src/ui/form.tsx +168 -0
  174. package/templates/default/packages/admin-ui/src/ui/input-otp.tsx +84 -0
  175. package/templates/default/packages/admin-ui/src/ui/input.tsx +21 -0
  176. package/templates/default/packages/admin-ui/src/ui/label.tsx +24 -0
  177. package/templates/default/packages/admin-ui/src/ui/pagination.tsx +126 -0
  178. package/templates/default/packages/admin-ui/src/ui/password-input.tsx +46 -0
  179. package/templates/default/packages/admin-ui/src/ui/popover.tsx +48 -0
  180. package/templates/default/packages/admin-ui/src/ui/progress.tsx +31 -0
  181. package/templates/default/packages/admin-ui/src/ui/radio-group.tsx +45 -0
  182. package/templates/default/packages/admin-ui/src/ui/scroll-area.tsx +52 -0
  183. package/templates/default/packages/admin-ui/src/ui/select.tsx +185 -0
  184. package/templates/default/packages/admin-ui/src/ui/separator.tsx +28 -0
  185. package/templates/default/packages/admin-ui/src/ui/sheet.tsx +149 -0
  186. package/templates/default/packages/admin-ui/src/ui/sidebar.tsx +728 -0
  187. package/templates/default/packages/admin-ui/src/ui/skeleton.tsx +13 -0
  188. package/templates/default/packages/admin-ui/src/ui/sonner.tsx +25 -0
  189. package/templates/default/packages/admin-ui/src/ui/switch.tsx +31 -0
  190. package/templates/default/packages/admin-ui/src/ui/table.tsx +116 -0
  191. package/templates/default/packages/admin-ui/src/ui/tabs.tsx +66 -0
  192. package/templates/default/packages/admin-ui/src/ui/textarea.tsx +18 -0
  193. package/templates/default/packages/admin-ui/src/ui/toggle-group.tsx +60 -0
  194. package/templates/default/packages/admin-ui/src/ui/toggle.tsx +44 -0
  195. package/templates/default/packages/admin-ui/src/ui/tooltip.tsx +61 -0
  196. package/templates/default/packages/admin-ui/tsconfig.json +8 -0
  197. package/templates/default/packages/admin-ui/vite.config.ts +11 -0
  198. package/templates/default/packages/config/package.json +0 -2
  199. package/templates/default/packages/server-core/package.json +2 -0
  200. package/templates/default/packages/ui/components.json +21 -0
  201. package/templates/default/packages/ui/package.json +42 -5
  202. package/templates/default/packages/ui/src/accordion.tsx +1 -1
  203. package/templates/default/packages/ui/src/alert-dialog.tsx +4 -4
  204. package/templates/default/packages/ui/src/alert.tsx +1 -1
  205. package/templates/default/packages/ui/src/avatar.tsx +1 -1
  206. package/templates/default/packages/ui/src/badge.tsx +1 -1
  207. package/templates/default/packages/ui/src/breadcrumb.tsx +1 -1
  208. package/templates/default/packages/ui/src/button.tsx +1 -1
  209. package/templates/default/packages/ui/src/card.tsx +1 -1
  210. package/templates/default/packages/ui/src/checkbox.tsx +1 -1
  211. package/templates/default/packages/ui/src/dialog.tsx +3 -3
  212. package/templates/default/packages/ui/src/drawer.tsx +3 -3
  213. package/templates/default/packages/ui/src/dropdown-menu.tsx +3 -3
  214. package/templates/default/packages/ui/src/form.tsx +2 -2
  215. package/templates/default/packages/ui/src/hover-card.tsx +2 -2
  216. package/templates/default/packages/ui/src/input.tsx +1 -1
  217. package/templates/default/packages/ui/src/label.tsx +1 -1
  218. package/templates/default/packages/ui/src/pagination.tsx +2 -2
  219. package/templates/default/packages/ui/src/popover.tsx +2 -2
  220. package/templates/default/packages/ui/src/progress.tsx +1 -1
  221. package/templates/default/packages/ui/src/select.tsx +2 -2
  222. package/templates/default/packages/ui/src/separator.tsx +1 -1
  223. package/templates/default/packages/ui/src/skeleton.tsx +1 -1
  224. package/templates/default/packages/ui/src/table.tsx +1 -1
  225. package/templates/default/packages/ui/src/tabs.tsx +1 -1
  226. package/templates/default/packages/ui/src/textarea.tsx +1 -1
  227. package/templates/default/packages/ui/src/tooltip.tsx +3 -3
  228. package/templates/default/packages/ui/src/typography.tsx +1 -1
  229. package/templates/default/packages/ui/tsconfig.json +1 -6
  230. package/templates/default/pnpm-lock.yaml +1319 -936
  231. package/templates/default/postcss.config.cjs +0 -1
  232. package/templates/default/turbo.json +17 -8
  233. package/templates/default/worktree.config.json +5 -0
  234. package/templates/default/.env.ci +0 -32
  235. package/templates/default/.github/workflows/ci.yml +0 -99
  236. package/templates/default/.github/workflows/preview-db.yml +0 -134
  237. package/templates/default/.playwright-mcp/dashboard.png +0 -0
  238. package/templates/default/.playwright-mcp/web-home.png +0 -0
  239. package/templates/default/apps/web/panda.config.ts +0 -114
  240. package/templates/default/apps/web/src/components/ui/accordion.tsx +0 -64
  241. package/templates/default/apps/web/src/components/ui/alert-dialog.tsx +0 -135
  242. package/templates/default/apps/web/src/components/ui/alert.tsx +0 -60
  243. package/templates/default/apps/web/src/components/ui/aspect-ratio.tsx +0 -9
  244. package/templates/default/apps/web/src/components/ui/avatar.tsx +0 -41
  245. package/templates/default/apps/web/src/components/ui/badge.tsx +0 -39
  246. package/templates/default/apps/web/src/components/ui/breadcrumb.tsx +0 -101
  247. package/templates/default/apps/web/src/components/ui/button.tsx +0 -56
  248. package/templates/default/apps/web/src/components/ui/card.tsx +0 -75
  249. package/templates/default/apps/web/src/components/ui/checkbox.tsx +0 -29
  250. package/templates/default/apps/web/src/components/ui/data-table.tsx +0 -189
  251. package/templates/default/apps/web/src/components/ui/dialog-hook.tsx +0 -210
  252. package/templates/default/apps/web/src/components/ui/dialog.tsx +0 -129
  253. package/templates/default/apps/web/src/components/ui/drawer.tsx +0 -124
  254. package/templates/default/apps/web/src/components/ui/dropdown-menu.tsx +0 -228
  255. package/templates/default/apps/web/src/components/ui/form.tsx +0 -152
  256. package/templates/default/apps/web/src/components/ui/hover-card.tsx +0 -38
  257. package/templates/default/apps/web/src/components/ui/input.tsx +0 -21
  258. package/templates/default/apps/web/src/components/ui/label.tsx +0 -21
  259. package/templates/default/apps/web/src/components/ui/pagination.tsx +0 -105
  260. package/templates/default/apps/web/src/components/ui/popover.tsx +0 -42
  261. package/templates/default/apps/web/src/components/ui/progress.tsx +0 -28
  262. package/templates/default/apps/web/src/components/ui/select.tsx +0 -170
  263. package/templates/default/apps/web/src/components/ui/separator.tsx +0 -28
  264. package/templates/default/apps/web/src/components/ui/skeleton.tsx +0 -13
  265. package/templates/default/apps/web/src/components/ui/sonner.tsx +0 -25
  266. package/templates/default/apps/web/src/components/ui/table.tsx +0 -92
  267. package/templates/default/apps/web/src/components/ui/tabs.tsx +0 -54
  268. package/templates/default/apps/web/src/components/ui/textarea.tsx +0 -18
  269. package/templates/default/apps/web/src/components/ui/tooltip.tsx +0 -57
  270. package/templates/default/apps/web/src/components/ui/typography.tsx +0 -158
  271. package/templates/default/packages/config/panda.config.ts +0 -114
  272. package/templates/default/panda.config.ts +0 -114
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { zodResolver } from "@hookform/resolvers/zod";
4
+ import { Button } from "{{packageName}}/admin-ui/ui/button";
5
+ import {
6
+ Dialog,
7
+ DialogClose,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "{{packageName}}/admin-ui/ui/dialog";
14
+ import {
15
+ Form,
16
+ FormControl,
17
+ FormField,
18
+ FormItem,
19
+ FormLabel,
20
+ FormMessage,
21
+ } from "{{packageName}}/admin-ui/ui/form";
22
+ import { Input } from "{{packageName}}/admin-ui/ui/input";
23
+ import { useForm } from "react-hook-form";
24
+ import { toast } from "sonner";
25
+ import { z } from "zod";
26
+
27
+ const formSchema = z.object({
28
+ file: z
29
+ .instanceof(FileList)
30
+ .refine((files) => files.length > 0, {
31
+ message: "Please upload a file",
32
+ })
33
+ .refine((files) => ["text/csv"].includes(files?.[0]?.type), "Please upload csv format."),
34
+ });
35
+
36
+ type TaskImportDialogProps = {
37
+ open: boolean;
38
+ onOpenChange: (open: boolean) => void;
39
+ };
40
+
41
+ export function TasksImportDialog({ open, onOpenChange }: TaskImportDialogProps) {
42
+ const form = useForm<z.infer<typeof formSchema>>({
43
+ resolver: zodResolver(formSchema),
44
+ defaultValues: { file: undefined },
45
+ });
46
+
47
+ const fileRef = form.register("file");
48
+
49
+ const onSubmit = () => {
50
+ const file = form.getValues("file");
51
+
52
+ if (file?.[0]) {
53
+ const fileDetails = {
54
+ name: file[0].name,
55
+ size: file[0].size,
56
+ type: file[0].type,
57
+ };
58
+ toast.success(
59
+ `You have imported the following file: ${fileDetails.name} (${fileDetails.size} bytes)`
60
+ );
61
+ }
62
+ onOpenChange(false);
63
+ };
64
+
65
+ return (
66
+ <Dialog
67
+ open={open}
68
+ onOpenChange={(val) => {
69
+ onOpenChange(val);
70
+ form.reset();
71
+ }}
72
+ >
73
+ <DialogContent className="gap-2 sm:max-w-sm">
74
+ <DialogHeader className="text-start">
75
+ <DialogTitle>Import Tasks</DialogTitle>
76
+ <DialogDescription>Import tasks quickly from a CSV file.</DialogDescription>
77
+ </DialogHeader>
78
+ <Form {...form}>
79
+ <form id="task-import-form" onSubmit={form.handleSubmit(onSubmit)}>
80
+ <FormField
81
+ control={form.control}
82
+ name="file"
83
+ render={() => (
84
+ <FormItem className="my-2">
85
+ <FormLabel>File</FormLabel>
86
+ <FormControl>
87
+ <Input type="file" {...fileRef} className="h-8 py-0" />
88
+ </FormControl>
89
+ <FormMessage />
90
+ </FormItem>
91
+ )}
92
+ />
93
+ </form>
94
+ </Form>
95
+ <DialogFooter className="gap-2">
96
+ <DialogClose asChild>
97
+ <Button variant="outline">Close</Button>
98
+ </DialogClose>
99
+ <Button type="submit" form="task-import-form">
100
+ Import
101
+ </Button>
102
+ </DialogFooter>
103
+ </DialogContent>
104
+ </Dialog>
105
+ );
106
+ }
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import { Alert, AlertDescription, AlertTitle } from "{{packageName}}/admin-ui/ui/alert";
4
+ import { ConfirmDialog } from "{{packageName}}/admin-ui/ui/confirm-dialog";
5
+ import { Input } from "{{packageName}}/admin-ui/ui/input";
6
+ import { Label } from "{{packageName}}/admin-ui/ui/label";
7
+ import type { Table } from "@tanstack/react-table";
8
+ import { AlertTriangle } from "lucide-react";
9
+ import { useState } from "react";
10
+ import { toast } from "sonner";
11
+
12
+ type TaskMultiDeleteDialogProps<TData> = {
13
+ open: boolean;
14
+ onOpenChange: (open: boolean) => void;
15
+ table: Table<TData>;
16
+ };
17
+
18
+ const CONFIRM_WORD = "DELETE";
19
+
20
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
21
+
22
+ export function TasksMultiDeleteDialog<TData>({
23
+ open,
24
+ onOpenChange,
25
+ table,
26
+ }: TaskMultiDeleteDialogProps<TData>) {
27
+ const [value, setValue] = useState("");
28
+
29
+ const selectedRows = table.getFilteredSelectedRowModel().rows;
30
+
31
+ const handleDelete = () => {
32
+ if (value.trim() !== CONFIRM_WORD) {
33
+ toast.error(`Please type "${CONFIRM_WORD}" to confirm.`);
34
+ return;
35
+ }
36
+
37
+ onOpenChange(false);
38
+
39
+ toast.promise(sleep(2000), {
40
+ loading: "Deleting tasks...",
41
+ success: () => {
42
+ setValue("");
43
+ table.resetRowSelection();
44
+ return `Deleted ${selectedRows.length} ${selectedRows.length > 1 ? "tasks" : "task"}`;
45
+ },
46
+ error: "Error",
47
+ });
48
+ };
49
+
50
+ return (
51
+ <ConfirmDialog
52
+ open={open}
53
+ onOpenChange={onOpenChange}
54
+ handleConfirm={handleDelete}
55
+ disabled={value.trim() !== CONFIRM_WORD}
56
+ title={
57
+ <span className="text-destructive">
58
+ <AlertTriangle className="me-1 inline-block stroke-destructive" size={18} /> Delete{" "}
59
+ {selectedRows.length} {selectedRows.length > 1 ? "tasks" : "task"}
60
+ </span>
61
+ }
62
+ desc={
63
+ <div className="space-y-4">
64
+ <p className="mb-2">
65
+ Are you sure you want to delete the selected tasks? <br />
66
+ This action cannot be undone.
67
+ </p>
68
+
69
+ <Label className="my-4 flex flex-col items-start gap-1.5">
70
+ <span className="">Confirm by typing "{CONFIRM_WORD}":</span>
71
+ <Input
72
+ value={value}
73
+ onChange={(e) => setValue(e.target.value)}
74
+ placeholder={`Type "${CONFIRM_WORD}" to confirm.`}
75
+ />
76
+ </Label>
77
+
78
+ <Alert variant="destructive">
79
+ <AlertTitle>Warning!</AlertTitle>
80
+ <AlertDescription>
81
+ Please be careful, this operation can not be rolled back.
82
+ </AlertDescription>
83
+ </Alert>
84
+ </div>
85
+ }
86
+ confirmText="Delete"
87
+ destructive
88
+ />
89
+ );
90
+ }
@@ -0,0 +1,207 @@
1
+ "use client";
2
+
3
+ import { SelectDropdown } from "@/components/shared/select-dropdown";
4
+ import type { Task } from "@/data/tasks";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import { Button } from "{{packageName}}/admin-ui/ui/button";
7
+ import {
8
+ Form,
9
+ FormControl,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from "{{packageName}}/admin-ui/ui/form";
15
+ import { Input } from "{{packageName}}/admin-ui/ui/input";
16
+ import { RadioGroup, RadioGroupItem } from "{{packageName}}/admin-ui/ui/radio-group";
17
+ import {
18
+ Sheet,
19
+ SheetClose,
20
+ SheetContent,
21
+ SheetDescription,
22
+ SheetFooter,
23
+ SheetHeader,
24
+ SheetTitle,
25
+ } from "{{packageName}}/admin-ui/ui/sheet";
26
+ import { useForm } from "react-hook-form";
27
+ import { toast } from "sonner";
28
+ import { z } from "zod";
29
+
30
+ type TaskMutateDrawerProps = {
31
+ open: boolean;
32
+ onOpenChange: (open: boolean) => void;
33
+ currentRow?: Task;
34
+ };
35
+
36
+ const formSchema = z.object({
37
+ title: z.string().min(1, "Title is required."),
38
+ status: z.string().min(1, "Please select a status."),
39
+ label: z.string().min(1, "Please select a label."),
40
+ priority: z.string().min(1, "Please choose a priority."),
41
+ });
42
+ type TaskForm = z.infer<typeof formSchema>;
43
+
44
+ export function TasksMutateDrawer({ open, onOpenChange, currentRow }: TaskMutateDrawerProps) {
45
+ const isUpdate = !!currentRow;
46
+
47
+ const form = useForm<TaskForm>({
48
+ resolver: zodResolver(formSchema),
49
+ defaultValues: currentRow ?? {
50
+ title: "",
51
+ status: "",
52
+ label: "",
53
+ priority: "",
54
+ },
55
+ });
56
+
57
+ const onSubmit = (data: TaskForm) => {
58
+ onOpenChange(false);
59
+ form.reset();
60
+ toast.success(isUpdate ? `Task updated: ${data.title}` : `Task created: ${data.title}`);
61
+ };
62
+
63
+ return (
64
+ <Sheet
65
+ open={open}
66
+ onOpenChange={(v) => {
67
+ onOpenChange(v);
68
+ form.reset();
69
+ }}
70
+ >
71
+ <SheetContent className="flex flex-col">
72
+ <SheetHeader className="text-start">
73
+ <SheetTitle>{isUpdate ? "Update" : "Create"} Task</SheetTitle>
74
+ <SheetDescription>
75
+ {isUpdate
76
+ ? "Update the task by providing necessary info."
77
+ : "Add a new task by providing necessary info."}
78
+ Click save when you&apos;re done.
79
+ </SheetDescription>
80
+ </SheetHeader>
81
+ <Form {...form}>
82
+ <form
83
+ id="tasks-form"
84
+ onSubmit={form.handleSubmit(onSubmit)}
85
+ className="flex-1 space-y-6 overflow-y-auto px-4"
86
+ >
87
+ <FormField
88
+ control={form.control}
89
+ name="title"
90
+ render={({ field }) => (
91
+ <FormItem>
92
+ <FormLabel>Title</FormLabel>
93
+ <FormControl>
94
+ <Input {...field} placeholder="Enter a title" />
95
+ </FormControl>
96
+ <FormMessage />
97
+ </FormItem>
98
+ )}
99
+ />
100
+ <FormField
101
+ control={form.control}
102
+ name="status"
103
+ render={({ field }) => (
104
+ <FormItem>
105
+ <FormLabel>Status</FormLabel>
106
+ <SelectDropdown
107
+ defaultValue={field.value}
108
+ onValueChange={field.onChange}
109
+ placeholder="Select dropdown"
110
+ items={[
111
+ { label: "In Progress", value: "in progress" },
112
+ { label: "Backlog", value: "backlog" },
113
+ { label: "Todo", value: "todo" },
114
+ { label: "Canceled", value: "canceled" },
115
+ { label: "Done", value: "done" },
116
+ ]}
117
+ />
118
+ <FormMessage />
119
+ </FormItem>
120
+ )}
121
+ />
122
+ <FormField
123
+ control={form.control}
124
+ name="label"
125
+ render={({ field }) => (
126
+ <FormItem className="relative">
127
+ <FormLabel>Label</FormLabel>
128
+ <FormControl>
129
+ <RadioGroup
130
+ onValueChange={field.onChange}
131
+ defaultValue={field.value}
132
+ className="flex flex-col space-y-1"
133
+ >
134
+ <FormItem className="flex items-center">
135
+ <FormControl>
136
+ <RadioGroupItem value="documentation" />
137
+ </FormControl>
138
+ <FormLabel className="font-normal">Documentation</FormLabel>
139
+ </FormItem>
140
+ <FormItem className="flex items-center">
141
+ <FormControl>
142
+ <RadioGroupItem value="feature" />
143
+ </FormControl>
144
+ <FormLabel className="font-normal">Feature</FormLabel>
145
+ </FormItem>
146
+ <FormItem className="flex items-center">
147
+ <FormControl>
148
+ <RadioGroupItem value="bug" />
149
+ </FormControl>
150
+ <FormLabel className="font-normal">Bug</FormLabel>
151
+ </FormItem>
152
+ </RadioGroup>
153
+ </FormControl>
154
+ <FormMessage />
155
+ </FormItem>
156
+ )}
157
+ />
158
+ <FormField
159
+ control={form.control}
160
+ name="priority"
161
+ render={({ field }) => (
162
+ <FormItem className="relative">
163
+ <FormLabel>Priority</FormLabel>
164
+ <FormControl>
165
+ <RadioGroup
166
+ onValueChange={field.onChange}
167
+ defaultValue={field.value}
168
+ className="flex flex-col space-y-1"
169
+ >
170
+ <FormItem className="flex items-center">
171
+ <FormControl>
172
+ <RadioGroupItem value="high" />
173
+ </FormControl>
174
+ <FormLabel className="font-normal">High</FormLabel>
175
+ </FormItem>
176
+ <FormItem className="flex items-center">
177
+ <FormControl>
178
+ <RadioGroupItem value="medium" />
179
+ </FormControl>
180
+ <FormLabel className="font-normal">Medium</FormLabel>
181
+ </FormItem>
182
+ <FormItem className="flex items-center">
183
+ <FormControl>
184
+ <RadioGroupItem value="low" />
185
+ </FormControl>
186
+ <FormLabel className="font-normal">Low</FormLabel>
187
+ </FormItem>
188
+ </RadioGroup>
189
+ </FormControl>
190
+ <FormMessage />
191
+ </FormItem>
192
+ )}
193
+ />
194
+ </form>
195
+ </Form>
196
+ <SheetFooter className="gap-2">
197
+ <SheetClose asChild>
198
+ <Button variant="outline">Close</Button>
199
+ </SheetClose>
200
+ <Button form="tasks-form" type="submit">
201
+ Save changes
202
+ </Button>
203
+ </SheetFooter>
204
+ </SheetContent>
205
+ </Sheet>
206
+ );
207
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { tasks } from "@/data/tasks";
4
+ import { Header, Main } from "{{packageName}}/admin-ui/layout";
5
+ import { TasksDialogs } from "./tasks-dialogs";
6
+ import { TasksPrimaryButtons } from "./tasks-primary-buttons";
7
+ import { TasksProvider } from "./tasks-provider";
8
+ import { TasksTable } from "./tasks-table";
9
+
10
+ export function TasksPage() {
11
+ return (
12
+ <TasksProvider>
13
+ <Header />
14
+ <Main>
15
+ <div className="flex flex-1 flex-col gap-4 sm:gap-6">
16
+ <div className="flex flex-wrap items-end justify-between gap-2">
17
+ <div>
18
+ <h2 className="text-2xl font-bold tracking-tight">Tasks</h2>
19
+ <p className="text-muted-foreground">
20
+ Here&apos;s a list of your tasks for this month!
21
+ </p>
22
+ </div>
23
+ <TasksPrimaryButtons />
24
+ </div>
25
+ <TasksTable data={tasks} />
26
+ </div>
27
+ </Main>
28
+ <TasksDialogs />
29
+ </TasksProvider>
30
+ );
31
+ }
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { Button } from "{{packageName}}/admin-ui/ui/button";
4
+ import { Download, Plus } from "lucide-react";
5
+ import { useTasks } from "./tasks-provider";
6
+
7
+ export function TasksPrimaryButtons() {
8
+ const { setOpen } = useTasks();
9
+ return (
10
+ <div className="flex gap-2">
11
+ <Button variant="outline" className="space-x-1" onClick={() => setOpen("import")}>
12
+ <span>Import</span> <Download size={18} />
13
+ </Button>
14
+ <Button className="space-x-1" onClick={() => setOpen("create")}>
15
+ <span>Create</span> <Plus size={18} />
16
+ </Button>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ import type { Task } from "@/data/tasks";
4
+ import useDialogState from "@/hooks/use-dialog-state";
5
+ import React, { useState } from "react";
6
+
7
+ type TasksDialogType = "create" | "update" | "delete" | "import";
8
+
9
+ type TasksContextType = {
10
+ open: TasksDialogType | null;
11
+ setOpen: (str: TasksDialogType | null) => void;
12
+ currentRow: Task | null;
13
+ setCurrentRow: React.Dispatch<React.SetStateAction<Task | null>>;
14
+ };
15
+
16
+ const TasksContext = React.createContext<TasksContextType | null>(null);
17
+
18
+ export function TasksProvider({ children }: { children: React.ReactNode }) {
19
+ const [open, setOpen] = useDialogState<TasksDialogType>(null);
20
+ const [currentRow, setCurrentRow] = useState<Task | null>(null);
21
+
22
+ return (
23
+ <TasksContext.Provider value={{ open, setOpen, currentRow, setCurrentRow }}>
24
+ {children}
25
+ </TasksContext.Provider>
26
+ );
27
+ }
28
+
29
+ export const useTasks = () => {
30
+ const tasksContext = React.useContext(TasksContext);
31
+
32
+ if (!tasksContext) {
33
+ throw new Error("useTasks has to be used within <TasksProvider>");
34
+ }
35
+
36
+ return tasksContext;
37
+ };
@@ -0,0 +1,155 @@
1
+ "use client";
2
+
3
+ import { type Task, priorities, statuses } from "@/data/tasks";
4
+ import { DataTablePagination, DataTableToolbar } from "{{packageName}}/admin-ui/data-table";
5
+ import { cn } from "{{packageName}}/admin-ui/lib/utils";
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from "{{packageName}}/admin-ui/ui/table";
14
+ import type {
15
+ ColumnFiltersState,
16
+ PaginationState,
17
+ SortingState,
18
+ VisibilityState,
19
+ } from "@tanstack/react-table";
20
+ import {
21
+ flexRender,
22
+ getCoreRowModel,
23
+ getFacetedRowModel,
24
+ getFacetedUniqueValues,
25
+ getFilteredRowModel,
26
+ getPaginationRowModel,
27
+ getSortedRowModel,
28
+ useReactTable,
29
+ } from "@tanstack/react-table";
30
+ import { useState } from "react";
31
+ import { DataTableBulkActions } from "./data-table-bulk-actions";
32
+ import { tasksColumns as columns } from "./tasks-columns";
33
+
34
+ type DataTableProps = {
35
+ data: Task[];
36
+ };
37
+
38
+ export function TasksTable({ data }: DataTableProps) {
39
+ const [rowSelection, setRowSelection] = useState({});
40
+ const [sorting, setSorting] = useState<SortingState>([]);
41
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
42
+ const [globalFilter, setGlobalFilter] = useState("");
43
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
44
+ const [pagination, setPagination] = useState<PaginationState>({
45
+ pageIndex: 0,
46
+ pageSize: 10,
47
+ });
48
+
49
+ const table = useReactTable({
50
+ data,
51
+ columns,
52
+ state: {
53
+ sorting,
54
+ columnVisibility,
55
+ rowSelection,
56
+ columnFilters,
57
+ globalFilter,
58
+ pagination,
59
+ },
60
+ enableRowSelection: true,
61
+ onRowSelectionChange: setRowSelection,
62
+ onSortingChange: setSorting,
63
+ onColumnVisibilityChange: setColumnVisibility,
64
+ onGlobalFilterChange: setGlobalFilter,
65
+ onColumnFiltersChange: setColumnFilters,
66
+ onPaginationChange: setPagination,
67
+ globalFilterFn: (row, _columnId, filterValue) => {
68
+ const id = String(row.getValue("id")).toLowerCase();
69
+ const title = String(row.getValue("title")).toLowerCase();
70
+ const searchValue = String(filterValue).toLowerCase();
71
+
72
+ return id.includes(searchValue) || title.includes(searchValue);
73
+ },
74
+ getCoreRowModel: getCoreRowModel(),
75
+ getFilteredRowModel: getFilteredRowModel(),
76
+ getPaginationRowModel: getPaginationRowModel(),
77
+ getSortedRowModel: getSortedRowModel(),
78
+ getFacetedRowModel: getFacetedRowModel(),
79
+ getFacetedUniqueValues: getFacetedUniqueValues(),
80
+ });
81
+
82
+ return (
83
+ <div className={cn("max-sm:has-[div[role='toolbar']]:mb-16", "flex flex-1 flex-col gap-4")}>
84
+ <DataTableToolbar
85
+ table={table}
86
+ searchPlaceholder="Filter by title or ID..."
87
+ filters={[
88
+ {
89
+ columnId: "status",
90
+ title: "Status",
91
+ options: statuses,
92
+ },
93
+ {
94
+ columnId: "priority",
95
+ title: "Priority",
96
+ options: priorities,
97
+ },
98
+ ]}
99
+ />
100
+ <div className="overflow-hidden rounded-md border">
101
+ <Table className="min-w-xl">
102
+ <TableHeader>
103
+ {table.getHeaderGroups().map((headerGroup) => (
104
+ <TableRow key={headerGroup.id}>
105
+ {headerGroup.headers.map((header) => {
106
+ return (
107
+ <TableHead
108
+ key={header.id}
109
+ colSpan={header.colSpan}
110
+ className={cn(
111
+ header.column.columnDef.meta?.className,
112
+ header.column.columnDef.meta?.thClassName
113
+ )}
114
+ >
115
+ {header.isPlaceholder
116
+ ? null
117
+ : flexRender(header.column.columnDef.header, header.getContext())}
118
+ </TableHead>
119
+ );
120
+ })}
121
+ </TableRow>
122
+ ))}
123
+ </TableHeader>
124
+ <TableBody>
125
+ {table.getRowModel().rows?.length ? (
126
+ table.getRowModel().rows.map((row) => (
127
+ <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
128
+ {row.getVisibleCells().map((cell) => (
129
+ <TableCell
130
+ key={cell.id}
131
+ className={cn(
132
+ cell.column.columnDef.meta?.className,
133
+ cell.column.columnDef.meta?.tdClassName
134
+ )}
135
+ >
136
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
137
+ </TableCell>
138
+ ))}
139
+ </TableRow>
140
+ ))
141
+ ) : (
142
+ <TableRow>
143
+ <TableCell colSpan={columns.length} className="h-24 text-center">
144
+ No results.
145
+ </TableCell>
146
+ </TableRow>
147
+ )}
148
+ </TableBody>
149
+ </Table>
150
+ </div>
151
+ <DataTablePagination table={table} className="mt-auto" />
152
+ <DataTableBulkActions table={table} />
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,14 @@
1
+ import type { Metadata } from "next";
2
+ import { TasksPage } from "./_components/tasks-page";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "Tasks",
6
+ description: "Manage your tasks",
7
+ };
8
+
9
+ // Force dynamic rendering to avoid FileList reference in server component
10
+ export const dynamic = "force-dynamic";
11
+
12
+ export default function Page() {
13
+ return <TasksPage />;
14
+ }