create-ej-app 12.0.0 → 15.0.0

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 (141) hide show
  1. package/cli.js +5 -4
  2. package/cli.ts +9 -4
  3. package/package.json +3 -3
  4. package/template/base/package.json +23 -29
  5. package/template/base/src/lib/auth.ts +15 -6
  6. package/template/base/terraform/artifact-registry.tf +5 -0
  7. package/template/base/terraform/backend-service.tf +13 -0
  8. package/template/base/terraform/cleanup.sh +9 -0
  9. package/template/base/terraform/cloudbuild.tf +70 -0
  10. package/template/base/terraform/cloudrun.tf +68 -0
  11. package/template/base/terraform/database.tf +60 -0
  12. package/template/base/terraform/forwarding-rule.tf +43 -0
  13. package/template/base/terraform/ip-address.tf +11 -0
  14. package/template/base/terraform/network-endpoint-group.tf +11 -0
  15. package/template/base/terraform/network.tf +21 -0
  16. package/template/base/terraform/pre.sh +3 -0
  17. package/template/base/terraform/providers.tf +18 -0
  18. package/template/base/terraform/secret.txt +11 -0
  19. package/template/base/terraform/setup.sh +25 -0
  20. package/template/base/terraform/ssl-certs.tf +32 -0
  21. package/template/base/terraform/target-https-proxy copy.tf +14 -0
  22. package/template/base/terraform/target-https-proxy.tf +14 -0
  23. package/template/base/terraform/terraform.tfvars +4 -0
  24. package/template/base/terraform/url-map.tf +23 -0
  25. package/template/base/terraform/variables.tf +21 -0
  26. package/template/old/.env.example +0 -17
  27. package/template/old/.eslintrc.cjs +0 -26
  28. package/template/old/components.json +0 -17
  29. package/template/old/drizzle.config.ts +0 -11
  30. package/template/old/next.config.mjs +0 -4
  31. package/template/old/package.json +0 -87
  32. package/template/old/postcss.config.mjs +0 -8
  33. package/template/old/prettier.config.mjs +0 -11
  34. package/template/old/public/next.svg +0 -1
  35. package/template/old/public/vercel.svg +0 -1
  36. package/template/old/src/app/(app)/dashboard/layout.tsx +0 -14
  37. package/template/old/src/app/(app)/dashboard/page.tsx +0 -12
  38. package/template/old/src/app/(app)/layout.tsx +0 -125
  39. package/template/old/src/app/(app)/roles/[id]/actions.ts +0 -59
  40. package/template/old/src/app/(app)/roles/[id]/page.tsx +0 -46
  41. package/template/old/src/app/(app)/roles/[id]/render.tsx +0 -264
  42. package/template/old/src/app/(app)/roles/[id]/validation.ts +0 -22
  43. package/template/old/src/app/(app)/roles/actions.ts +0 -33
  44. package/template/old/src/app/(app)/roles/page.tsx +0 -52
  45. package/template/old/src/app/(app)/roles/render.tsx +0 -80
  46. package/template/old/src/app/(app)/settings/actions.ts +0 -17
  47. package/template/old/src/app/(app)/settings/layout.tsx +0 -14
  48. package/template/old/src/app/(app)/settings/loading.tsx +0 -28
  49. package/template/old/src/app/(app)/settings/page.tsx +0 -19
  50. package/template/old/src/app/(app)/settings/render.tsx +0 -107
  51. package/template/old/src/app/(app)/settings/validation.ts +0 -5
  52. package/template/old/src/app/(app)/tasks/[id]/actions.ts +0 -92
  53. package/template/old/src/app/(app)/tasks/[id]/page.tsx +0 -59
  54. package/template/old/src/app/(app)/tasks/[id]/render.tsx +0 -482
  55. package/template/old/src/app/(app)/tasks/[id]/validation.ts +0 -18
  56. package/template/old/src/app/(app)/tasks/actions.ts +0 -39
  57. package/template/old/src/app/(app)/tasks/page.tsx +0 -59
  58. package/template/old/src/app/(app)/tasks/render.tsx +0 -129
  59. package/template/old/src/app/(app)/tasks/statusEnum.ts +0 -3
  60. package/template/old/src/app/(app)/users/[id]/actions.ts +0 -68
  61. package/template/old/src/app/(app)/users/[id]/page.tsx +0 -39
  62. package/template/old/src/app/(app)/users/[id]/render.tsx +0 -325
  63. package/template/old/src/app/(app)/users/[id]/validation.ts +0 -18
  64. package/template/old/src/app/(app)/users/actions.ts +0 -34
  65. package/template/old/src/app/(app)/users/page.tsx +0 -62
  66. package/template/old/src/app/(app)/users/render.tsx +0 -124
  67. package/template/old/src/app/(auth)/layout.tsx +0 -27
  68. package/template/old/src/app/(auth)/login/page.tsx +0 -49
  69. package/template/old/src/app/(auth)/login/render.tsx +0 -146
  70. package/template/old/src/app/(marketing)/layout.tsx +0 -42
  71. package/template/old/src/app/(marketing)/page.tsx +0 -97
  72. package/template/old/src/app/api/auth/[...nextauth]/route.ts +0 -7
  73. package/template/old/src/app/favicon.ico +0 -0
  74. package/template/old/src/app/globals.css +0 -76
  75. package/template/old/src/app/layout.tsx +0 -31
  76. package/template/old/src/components/app-nav-command.tsx +0 -178
  77. package/template/old/src/components/dark-mode-toggle.tsx +0 -43
  78. package/template/old/src/components/data-table/data-table-column-header.tsx +0 -91
  79. package/template/old/src/components/data-table/data-table-faceted-filter.tsx +0 -149
  80. package/template/old/src/components/data-table/data-table-loading.tsx +0 -77
  81. package/template/old/src/components/data-table/data-table-pagination.tsx +0 -94
  82. package/template/old/src/components/data-table/data-table-toolbar.tsx +0 -78
  83. package/template/old/src/components/data-table/data-table-types.ts +0 -17
  84. package/template/old/src/components/data-table/data-table-view-options.tsx +0 -59
  85. package/template/old/src/components/data-table/data-table.tsx +0 -301
  86. package/template/old/src/components/data-table/simple-data-table.tsx +0 -109
  87. package/template/old/src/components/empty-placeholder.tsx +0 -78
  88. package/template/old/src/components/heading.tsx +0 -17
  89. package/template/old/src/components/icons.tsx +0 -101
  90. package/template/old/src/components/main-nav.tsx +0 -64
  91. package/template/old/src/components/mobile-nav.tsx +0 -50
  92. package/template/old/src/components/shell.tsx +0 -13
  93. package/template/old/src/components/side-nav.tsx +0 -51
  94. package/template/old/src/components/site-footer.tsx +0 -31
  95. package/template/old/src/components/system-info.tsx +0 -36
  96. package/template/old/src/components/ui/alert-dialog.tsx +0 -141
  97. package/template/old/src/components/ui/avatar.tsx +0 -50
  98. package/template/old/src/components/ui/badge.tsx +0 -36
  99. package/template/old/src/components/ui/button.tsx +0 -56
  100. package/template/old/src/components/ui/card.tsx +0 -79
  101. package/template/old/src/components/ui/checkbox.tsx +0 -30
  102. package/template/old/src/components/ui/command.tsx +0 -155
  103. package/template/old/src/components/ui/dialog.tsx +0 -122
  104. package/template/old/src/components/ui/dropdown-menu.tsx +0 -200
  105. package/template/old/src/components/ui/form.tsx +0 -176
  106. package/template/old/src/components/ui/input.tsx +0 -25
  107. package/template/old/src/components/ui/label.tsx +0 -26
  108. package/template/old/src/components/ui/popover.tsx +0 -31
  109. package/template/old/src/components/ui/select.tsx +0 -160
  110. package/template/old/src/components/ui/separator.tsx +0 -31
  111. package/template/old/src/components/ui/skeleton.tsx +0 -15
  112. package/template/old/src/components/ui/sonner.tsx +0 -31
  113. package/template/old/src/components/ui/table.tsx +0 -117
  114. package/template/old/src/components/user-account-nav.tsx +0 -60
  115. package/template/old/src/components/user-avatar.tsx +0 -27
  116. package/template/old/src/data-access/resource.ts +0 -85
  117. package/template/old/src/data-access/role-permission.ts +0 -45
  118. package/template/old/src/data-access/role.ts +0 -105
  119. package/template/old/src/data-access/task.ts +0 -141
  120. package/template/old/src/data-access/user.ts +0 -141
  121. package/template/old/src/db/index.ts +0 -14
  122. package/template/old/src/db/schema.ts +0 -287
  123. package/template/old/src/env.mjs +0 -58
  124. package/template/old/src/hooks/use-debounce.tsx +0 -17
  125. package/template/old/src/hooks/use-lock-body.tsx +0 -13
  126. package/template/old/src/lib/auth.ts +0 -89
  127. package/template/old/src/lib/cn.ts +0 -4
  128. package/template/old/src/lib/currentUser.ts +0 -8
  129. package/template/old/src/lib/delay.ts +0 -2
  130. package/template/old/src/lib/formatDate.ts +0 -11
  131. package/template/old/src/lib/generateLabel.ts +0 -5
  132. package/template/old/src/lib/s3Client.ts +0 -156
  133. package/template/old/src/lib/safe-action.ts +0 -19
  134. package/template/old/src/lib/siteConfig.ts +0 -4
  135. package/template/old/src/lib/sortOrderEnum.ts +0 -2
  136. package/template/old/src/use-case/role-permission.ts +0 -19
  137. package/template/old/src/use-case/role.ts +0 -110
  138. package/template/old/src/use-case/task.ts +0 -166
  139. package/template/old/src/use-case/user.ts +0 -121
  140. package/template/old/tailwind.config.ts +0 -81
  141. package/template/old/tsconfig.json +0 -47
@@ -1,92 +0,0 @@
1
- 'use server'
2
-
3
- import { revalidatePath } from 'next/cache'
4
- import { z } from 'zod'
5
-
6
- import { getPresignedUrl, markFileUploaded } from '@/lib/s3Client'
7
- import { authActionClient } from '@/lib/safe-action'
8
- import {
9
- createTaskUseCase,
10
- deleteTaskUseCase,
11
- getTaskByIdWithUserUseCase,
12
- updateTaskUseCase
13
- } from '@/use-case/task'
14
- import { TaskCreateUpdateSchema, TaskUpdateServerSchema } from './validation'
15
-
16
- export const getTaskAction = authActionClient
17
- .schema(z.string())
18
- .action(async ({ parsedInput, ctx }) => {
19
- return await getTaskByIdWithUserUseCase(ctx.user, parsedInput)
20
- })
21
-
22
- export const createTaskAction = authActionClient
23
- .schema(TaskCreateUpdateSchema)
24
- .action(async ({ parsedInput, ctx }) => {
25
- const id = await createTaskUseCase(ctx.user, parsedInput)
26
-
27
- revalidatePath('/tasks')
28
- revalidatePath(`/tasks/${id}`)
29
-
30
- return {
31
- id,
32
- success: true
33
- }
34
- })
35
-
36
- export const updateTaskAction = authActionClient
37
- .schema(TaskUpdateServerSchema)
38
- .action(async ({ parsedInput: id, ctx }) => {
39
- await updateTaskUseCase(ctx.user, id)
40
-
41
- revalidatePath('/tasks')
42
- revalidatePath(`/tasks/${id}`)
43
-
44
- return {
45
- success: true
46
- }
47
- })
48
-
49
- export const deleteTaskAction = authActionClient
50
- .schema(z.string())
51
- .action(async ({ parsedInput: id, ctx }) => {
52
- await deleteTaskUseCase(ctx.user, id)
53
-
54
- revalidatePath('/tasks')
55
- revalidatePath(`/tasks/${id}`)
56
-
57
- return {
58
- success: true
59
- }
60
- })
61
-
62
- export const getTaskImageUploadPresignedUrlAction = authActionClient
63
- .schema(
64
- z.object({
65
- filename: z.string().min(1),
66
- contentType: z.string().min(1)
67
- })
68
- )
69
- .action(async ({ ctx, parsedInput }) => {
70
- return await getPresignedUrl({
71
- // contentTypeStartsWith: 'image/',
72
- isPublic: true,
73
- filename: parsedInput.filename,
74
- contentType: parsedInput.contentType,
75
- createdById: ctx.user.id
76
- })
77
- })
78
-
79
- export const markTaskImageUploadedAction = authActionClient
80
- .schema(z.string())
81
- .action(async ({ parsedInput: id, ctx }) => {
82
- try {
83
- await markFileUploaded(ctx.user, id)
84
- return {
85
- success: true
86
- }
87
- } catch (err) {
88
- return {
89
- success: false
90
- }
91
- }
92
- })
@@ -1,59 +0,0 @@
1
- import { Metadata } from 'next'
2
- import { notFound } from 'next/navigation'
3
-
4
- import { authGuard, getAuthSession } from '@/lib/auth'
5
- import { siteConfig } from '@/lib/siteConfig'
6
- import { getTaskAction } from './actions'
7
- import { Render } from './render'
8
-
9
- export const generateMetadata = async (props: {
10
- params: { id: string }
11
- }): Promise<Metadata> => ({
12
- title: 'Task - ' + props.params.id + ' | ' + siteConfig.name
13
- })
14
-
15
- const TaskPage = async ({
16
- params: { id }
17
- }: {
18
- params: {
19
- id: string
20
- }
21
- }) => {
22
- const session = await getAuthSession()
23
- if (!session) return notFound()
24
-
25
- const g = await authGuard(session, 'TaskView')
26
- if (!g) return notFound()
27
-
28
- const [canCreate, canUpdate, canDelete] = await Promise.all([
29
- authGuard(session, 'TaskCreate'),
30
- authGuard(session, 'TaskUpdate'),
31
- authGuard(session, 'TaskDelete')
32
- ])
33
-
34
- if (id === 'new') {
35
- if (!canCreate) return notFound()
36
-
37
- return (
38
- <Render
39
- canCreate={canCreate}
40
- canUpdate={canUpdate}
41
- canDelete={canDelete}
42
- />
43
- )
44
- }
45
-
46
- const task = await getTaskAction(id)
47
- if (!task?.data) return notFound()
48
-
49
- return (
50
- <Render
51
- task={task.data}
52
- canCreate={canCreate}
53
- canUpdate={canUpdate}
54
- canDelete={canDelete}
55
- />
56
- )
57
- }
58
-
59
- export default TaskPage
@@ -1,482 +0,0 @@
1
- 'use client'
2
-
3
- import EditorJS from '@editorjs/editorjs'
4
- import { zodResolver } from '@hookform/resolvers/zod'
5
- import { InferSelectModel } from 'drizzle-orm'
6
- import edjsHTML from 'editorjs-html'
7
- import Link from 'next/link'
8
- import { useRouter } from 'next/navigation'
9
- import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
10
- import { useForm } from 'react-hook-form'
11
- import { toast } from 'sonner'
12
- import { z } from 'zod'
13
-
14
- import { Heading } from '@/components/heading'
15
- import { Icons } from '@/components/icons'
16
- import { Shell } from '@/components/shell'
17
- import { SystemInfo } from '@/components/system-info'
18
- import {
19
- AlertDialog,
20
- AlertDialogAction,
21
- AlertDialogCancel,
22
- AlertDialogContent,
23
- AlertDialogDescription,
24
- AlertDialogFooter,
25
- AlertDialogHeader,
26
- AlertDialogTitle,
27
- AlertDialogTrigger
28
- } from '@/components/ui/alert-dialog'
29
- import { Button } from '@/components/ui/button'
30
- import {
31
- Form,
32
- FormControl,
33
- FormField,
34
- FormItem,
35
- FormLabel,
36
- FormMessage
37
- } from '@/components/ui/form'
38
- import { Input } from '@/components/ui/input'
39
- import {
40
- Select,
41
- SelectContent,
42
- SelectItem,
43
- SelectTrigger,
44
- SelectValue
45
- } from '@/components/ui/select'
46
- import { Task } from '@/db/schema'
47
- import { formatDateTime, timesAgo } from '@/lib/formatDate'
48
- import { generateLabel } from '@/lib/generateLabel'
49
- import {
50
- createTaskAction,
51
- deleteTaskAction,
52
- getTaskImageUploadPresignedUrlAction,
53
- markTaskImageUploadedAction,
54
- updateTaskAction
55
- } from './actions'
56
- import { TaskCreateUpdateSchema, TaskStatusEnumArr } from './validation'
57
-
58
- type FormData = z.infer<typeof TaskCreateUpdateSchema>
59
-
60
- const statuses = TaskStatusEnumArr.map(x => ({
61
- label: generateLabel(x),
62
- value: x
63
- }))
64
-
65
- const edjsParser = edjsHTML()
66
-
67
- export const Render: FC<{
68
- task?: {
69
- id: string
70
- title: string
71
- description?: string | null
72
- status: InferSelectModel<typeof Task>['status']
73
- createdAt: Date
74
- updatedAt?: Date | null
75
- createdBy: {
76
- id: string
77
- name?: string | null
78
- }
79
- updatedBy?: {
80
- id: string
81
- name?: string | null
82
- } | null
83
- } | null
84
- canCreate: boolean
85
- canUpdate: boolean
86
- canDelete: boolean
87
- }> = ({ task, canCreate, canUpdate, canDelete }) => {
88
- const form = useForm<FormData>({
89
- resolver: zodResolver(TaskCreateUpdateSchema),
90
- defaultValues: {
91
- title: task?.title || '',
92
- status: task?.status
93
- }
94
- })
95
- const router = useRouter()
96
-
97
- const [isSaving, setIsSaving] = useState(false)
98
- const [isDeleting, setIsDeleting] = useState(false)
99
- const [isEditorMounted, setIsMounted] = useState<boolean>(false)
100
-
101
- const editorRef = useRef<EditorJS>()
102
-
103
- const initializeEditor = useCallback(async () => {
104
- const EditorJS = (await import('@editorjs/editorjs')).default
105
-
106
- // @ts-ignore
107
- const Header = (await import('@editorjs/header')).default
108
-
109
- // @ts-ignore
110
- const List = (await import('@editorjs/list')).default
111
-
112
- // @ts-ignore
113
- const NestedList = (await import('@editorjs/nested-list')).default
114
-
115
- // @ts-ignore
116
- const CheckList = (await import('@editorjs/checklist')).default
117
-
118
- // @ts-ignore
119
- const Table = (await import('@editorjs/table')).default
120
-
121
- // @ts-ignore
122
- const Quote = (await import('@editorjs/quote')).default
123
-
124
- // @ts-ignore
125
- const Delimiter = (await import('@editorjs/delimiter')).default
126
-
127
- // @ts-ignore
128
- const InlineCode = (await import('@editorjs/inline-code')).default
129
-
130
- // @ts-ignore
131
- const Code = (await import('@editorjs/code')).default
132
-
133
- // @ts-ignore
134
- const Image = (await import('@editorjs/image')).default
135
-
136
- if (!editorRef.current) {
137
- const editor = new EditorJS({
138
- holder: 'editor',
139
- onReady() {
140
- editorRef.current = editor
141
- },
142
- placeholder: 'Type here to write...',
143
- inlineToolbar: true,
144
- data: task?.description ? JSON.parse(task.description) : undefined,
145
- tools: {
146
- header: Header,
147
- list: List,
148
- nestedList: NestedList,
149
- checklist: CheckList,
150
- table: Table,
151
- quote: Quote,
152
- delimiter: Delimiter,
153
- inlineCode: InlineCode,
154
- code: Code,
155
- image: {
156
- class: Image,
157
- config: {
158
- uploader: {
159
- async uploadByFile(file: File) {
160
- try {
161
- const res = await getTaskImageUploadPresignedUrlAction({
162
- filename: file.name,
163
- contentType: file.type
164
- })
165
- if (res?.data) {
166
- const formData = new FormData()
167
-
168
- for (const [key, value] of Object.entries(
169
- res.data.presigned.fields
170
- )) {
171
- formData.append(key, value)
172
- }
173
-
174
- formData.append('file', file)
175
-
176
- const response = await fetch(res.data.presigned.url, {
177
- method: 'POST',
178
- body: formData
179
- })
180
-
181
- if (!response.ok)
182
- throw new Error('Failed to upload image to s3')
183
-
184
- const res2 = await markTaskImageUploadedAction(
185
- res.data.id
186
- )
187
-
188
- if (!res2?.data?.success)
189
- throw new Error('Failed to mark image uploaded')
190
-
191
- return {
192
- success: true,
193
- file: {
194
- id: res.data.id,
195
- url: res.data.url
196
- }
197
- }
198
- }
199
- throw new Error('No success returned from server')
200
- } catch (err) {
201
- return {
202
- success: false
203
- }
204
- }
205
- }
206
- }
207
- }
208
- }
209
- }
210
- })
211
- }
212
- }, [task?.description])
213
-
214
- useEffect(() => {
215
- if (typeof window !== 'undefined') setIsMounted(true)
216
- }, [])
217
-
218
- useEffect(() => {
219
- if (!isEditorMounted) return
220
-
221
- initializeEditor()
222
-
223
- return () => {
224
- editorRef.current?.destroy()
225
- editorRef.current = undefined
226
- }
227
- }, [isEditorMounted, initializeEditor])
228
-
229
- const editorHTML = useMemo(() => {
230
- if (!task?.description) return ''
231
- return edjsParser.parse(JSON.parse(task.description)).join('')
232
- }, [task?.description])
233
- console.log(editorHTML)
234
-
235
- async function onSubmit(data: FormData) {
236
- setIsSaving(true)
237
- try {
238
- const editorData = await editorRef.current?.save()
239
- const imageIds: string[] = []
240
- if (editorData?.blocks)
241
- for (const b of editorData.blocks)
242
- if (
243
- b.type === 'image' &&
244
- b.data &&
245
- b.data.file &&
246
- b.data.file.id &&
247
- b.data.file.url
248
- )
249
- imageIds.push(b.data.file.id)
250
-
251
- if (!task) {
252
- const res = await createTaskAction({
253
- ...data,
254
- description: editorData ? JSON.stringify(editorData) : null,
255
- imageIds
256
- })
257
- if (!res?.data?.success)
258
- throw new Error(res?.serverError || 'No success returned from server')
259
- if (!res?.data?.id) throw new Error('No id returned from server')
260
- router.replace(`/tasks/${res.data.id}`)
261
- } else {
262
- const res = await updateTaskAction({
263
- id: task.id,
264
- ...data,
265
- description: editorData ? JSON.stringify(editorData) : null,
266
- imageIds
267
- })
268
- if (!res?.data?.success)
269
- throw new Error(res?.serverError || 'No success returned from server')
270
- }
271
- toast('User saved')
272
- } catch (err) {
273
- toast('Error saving user', {
274
- description: (err as any).message || undefined
275
- })
276
- }
277
- setIsSaving(false)
278
- }
279
-
280
- return (
281
- <Shell>
282
- <Heading heading={task ? task.title : 'New Task'} />
283
- <Form {...form}>
284
- <form
285
- className="grid grid-cols-1 gap-3 md:grid-cols-2"
286
- onSubmit={form.handleSubmit(onSubmit)}
287
- >
288
- <div className="col-span-1 mb-1 grid grid-cols-2 gap-2 sm:flex md:col-span-2">
289
- <Button type="button" onClick={() => router.back()} variant="ghost">
290
- <Icons.chevronLeft className="mr-2 h-4 w-4" />
291
- Back
292
- </Button>
293
- {(task ? canUpdate : canCreate) ? (
294
- <Button type="submit" disabled={isSaving || isDeleting}>
295
- {isSaving ? (
296
- <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
297
- ) : (
298
- <Icons.save className="mr-2 h-4 w-4" />
299
- )}
300
- <span>Save</span>
301
- </Button>
302
- ) : undefined}
303
- {task && canDelete ? (
304
- <AlertDialog>
305
- <AlertDialogTrigger asChild>
306
- <Button
307
- type="button"
308
- variant="destructive"
309
- disabled={isSaving || isDeleting}
310
- >
311
- {isDeleting ? (
312
- <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
313
- ) : (
314
- <Icons.delete className="mr-2 h-4 w-4" />
315
- )}
316
- Delete
317
- </Button>
318
- </AlertDialogTrigger>
319
- <AlertDialogContent>
320
- <AlertDialogHeader>
321
- <AlertDialogTitle>
322
- Are you absolutely sure?
323
- </AlertDialogTitle>
324
- <AlertDialogDescription>
325
- This action cannot be undone.
326
- </AlertDialogDescription>
327
- </AlertDialogHeader>
328
- <AlertDialogFooter>
329
- <AlertDialogCancel>Cancel</AlertDialogCancel>
330
- <AlertDialogAction
331
- onClick={async () => {
332
- setIsDeleting(true)
333
- try {
334
- const res = await deleteTaskAction(task.id)
335
- if (!res?.data?.success)
336
- throw new Error(
337
- res?.serverError ||
338
- 'No success returned from server'
339
- )
340
- toast('Task deleted')
341
- router.replace('/tasks')
342
- } catch (err) {
343
- toast('Error deleting task', {
344
- description:
345
- (err as any).message || 'Something went wrong'
346
- })
347
- }
348
- setIsDeleting(false)
349
- }}
350
- >
351
- Continue
352
- </AlertDialogAction>
353
- </AlertDialogFooter>
354
- </AlertDialogContent>
355
- </AlertDialog>
356
- ) : undefined}
357
- {task && canCreate ? (
358
- <Link href="/tasks/new">
359
- <Button
360
- type="button"
361
- disabled={isSaving || isDeleting}
362
- variant="secondary"
363
- className="w-full"
364
- >
365
- <Icons.add className="mr-2 h-4 w-4" />
366
- New
367
- </Button>
368
- </Link>
369
- ) : undefined}
370
- </div>
371
-
372
- <FormField
373
- control={form.control}
374
- name="title"
375
- render={({ field }) => (
376
- <FormItem>
377
- <FormLabel>Title</FormLabel>
378
- <FormControl>
379
- <Input
380
- type="text"
381
- placeholder="Enter a title"
382
- disabled={isSaving}
383
- {...field}
384
- />
385
- </FormControl>
386
- <FormMessage />
387
- </FormItem>
388
- )}
389
- />
390
-
391
- <FormField
392
- control={form.control}
393
- name="status"
394
- render={({ field }) => (
395
- <FormItem>
396
- <FormLabel>Status</FormLabel>
397
- <Select
398
- onValueChange={field.onChange}
399
- defaultValue={field.value}
400
- disabled={isSaving}
401
- >
402
- <FormControl>
403
- <SelectTrigger>
404
- <SelectValue placeholder="Select status" />
405
- </SelectTrigger>
406
- </FormControl>
407
- <SelectContent>
408
- {statuses.map(({ label, value }) => (
409
- <SelectItem key={value} value={value}>
410
- {label}
411
- </SelectItem>
412
- ))}
413
- </SelectContent>
414
- </Select>
415
- <FormMessage />
416
- </FormItem>
417
- )}
418
- />
419
- </form>
420
- </Form>
421
-
422
- {isEditorMounted ? (
423
- <div className="prose prose-neutral mx-auto mt-5 w-full dark:prose-invert">
424
- <h3 className="w-full bg-transparent text-3xl font-bold">
425
- Description
426
- </h3>
427
- <div id="editor" className="min-h-[360px] w-full" />
428
- <p className="text-sm text-gray-500">
429
- Use{' '}
430
- <kbd className="rounded-md border bg-muted px-1 text-xs uppercase">
431
- /
432
- </kbd>{' '}
433
- to open the command menu.
434
- </p>
435
- </div>
436
- ) : undefined}
437
-
438
- {task ? (
439
- <SystemInfo
440
- items={[
441
- {
442
- label: 'Id',
443
- value: task.id
444
- },
445
- {
446
- label: 'Created At',
447
- value: formatDateTime(task.createdAt)
448
- },
449
- {
450
- label: 'Updated At',
451
- value: task.updatedAt ? timesAgo(task.updatedAt) : '-'
452
- },
453
- {
454
- label: 'Created By',
455
- value: (
456
- <Link
457
- href={`/users/${task.createdBy.id}`}
458
- className="underline underline-offset-4 transition-all hover:text-foreground"
459
- >
460
- {task.createdBy.name ?? 'Unknown'}
461
- </Link>
462
- )
463
- },
464
- {
465
- label: 'Updated By',
466
- value: task.updatedBy ? (
467
- <Link
468
- href={`/users/${task.updatedBy.id}`}
469
- className="underline underline-offset-4 transition-all hover:text-foreground"
470
- >
471
- {task.updatedBy.name ?? 'Unknown'}
472
- </Link>
473
- ) : (
474
- '-'
475
- )
476
- }
477
- ]}
478
- />
479
- ) : undefined}
480
- </Shell>
481
- )
482
- }
@@ -1,18 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- import { Task } from '@/db/schema'
4
-
5
- export const TaskStatusEnumArr = Task.status.enumValues
6
-
7
- export const TaskCreateUpdateSchema = z.object({
8
- title: z.string().min(1).trim(),
9
- description: z.string().optional().nullable(),
10
- status: z.enum(TaskStatusEnumArr),
11
- imageIds: z.array(z.string()).optional().nullable()
12
- })
13
-
14
- export const TaskUpdateServerSchema = TaskCreateUpdateSchema.merge(
15
- z.object({
16
- id: z.string().min(1)
17
- })
18
- )
@@ -1,39 +0,0 @@
1
- 'use server'
2
-
3
- import { z } from 'zod'
4
-
5
- import { authActionClient } from '@/lib/safe-action'
6
- import { SortOrderEnum } from '@/lib/sortOrderEnum'
7
- import { getTasksWithUserUseCase } from '@/use-case/task'
8
- import { TaskStatusEnumArr } from './statusEnum'
9
-
10
- export const getTasksAction = authActionClient
11
- .schema(
12
- z.object({
13
- page: z.number().gte(1),
14
- limit: z.number().gte(1).lte(1000),
15
- sortBy: z
16
- .enum(['id', 'title', 'status', 'createdAt', 'updatedAt'])
17
- .optional(),
18
- sortOrder: z.enum(SortOrderEnum).optional(),
19
- title: z.string().optional(),
20
- status: z.array(z.enum(TaskStatusEnumArr)).optional()
21
- })
22
- )
23
- .action(async ({ parsedInput, ctx }) => {
24
- return await getTasksWithUserUseCase(ctx.user, {
25
- page: parsedInput.page,
26
- limit: parsedInput.limit,
27
- sortBy: parsedInput.sortBy,
28
- sortOrder: parsedInput.sortOrder,
29
- filters:
30
- parsedInput.title || parsedInput.status?.length
31
- ? {
32
- title: parsedInput.title || undefined,
33
- status: parsedInput.status?.length
34
- ? parsedInput.status
35
- : undefined
36
- }
37
- : undefined
38
- })
39
- })