create-ej-app 12.0.0 → 14.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.
- package/cli.js +5 -4
- package/cli.ts +9 -4
- package/package.json +1 -1
- package/template/base/package.json +9 -15
- package/template/base/terraform/artifact-registry.tf +5 -0
- package/template/base/terraform/backend-service.tf +13 -0
- package/template/base/terraform/cleanup.sh +9 -0
- package/template/base/terraform/cloudbuild.tf +70 -0
- package/template/base/terraform/cloudrun.tf +68 -0
- package/template/base/terraform/database.tf +60 -0
- package/template/base/terraform/forwarding-rule.tf +43 -0
- package/template/base/terraform/ip-address.tf +11 -0
- package/template/base/terraform/network-endpoint-group.tf +11 -0
- package/template/base/terraform/network.tf +21 -0
- package/template/base/terraform/pre.sh +3 -0
- package/template/base/terraform/providers.tf +18 -0
- package/template/base/terraform/secret.txt +11 -0
- package/template/base/terraform/setup.sh +25 -0
- package/template/base/terraform/ssl-certs.tf +32 -0
- package/template/base/terraform/target-https-proxy copy.tf +14 -0
- package/template/base/terraform/target-https-proxy.tf +14 -0
- package/template/base/terraform/terraform.tfvars +4 -0
- package/template/base/terraform/url-map.tf +23 -0
- package/template/base/terraform/variables.tf +21 -0
- package/template/old/.env.example +0 -17
- package/template/old/.eslintrc.cjs +0 -26
- package/template/old/components.json +0 -17
- package/template/old/drizzle.config.ts +0 -11
- package/template/old/next.config.mjs +0 -4
- package/template/old/package.json +0 -87
- package/template/old/postcss.config.mjs +0 -8
- package/template/old/prettier.config.mjs +0 -11
- package/template/old/public/next.svg +0 -1
- package/template/old/public/vercel.svg +0 -1
- package/template/old/src/app/(app)/dashboard/layout.tsx +0 -14
- package/template/old/src/app/(app)/dashboard/page.tsx +0 -12
- package/template/old/src/app/(app)/layout.tsx +0 -125
- package/template/old/src/app/(app)/roles/[id]/actions.ts +0 -59
- package/template/old/src/app/(app)/roles/[id]/page.tsx +0 -46
- package/template/old/src/app/(app)/roles/[id]/render.tsx +0 -264
- package/template/old/src/app/(app)/roles/[id]/validation.ts +0 -22
- package/template/old/src/app/(app)/roles/actions.ts +0 -33
- package/template/old/src/app/(app)/roles/page.tsx +0 -52
- package/template/old/src/app/(app)/roles/render.tsx +0 -80
- package/template/old/src/app/(app)/settings/actions.ts +0 -17
- package/template/old/src/app/(app)/settings/layout.tsx +0 -14
- package/template/old/src/app/(app)/settings/loading.tsx +0 -28
- package/template/old/src/app/(app)/settings/page.tsx +0 -19
- package/template/old/src/app/(app)/settings/render.tsx +0 -107
- package/template/old/src/app/(app)/settings/validation.ts +0 -5
- package/template/old/src/app/(app)/tasks/[id]/actions.ts +0 -92
- package/template/old/src/app/(app)/tasks/[id]/page.tsx +0 -59
- package/template/old/src/app/(app)/tasks/[id]/render.tsx +0 -482
- package/template/old/src/app/(app)/tasks/[id]/validation.ts +0 -18
- package/template/old/src/app/(app)/tasks/actions.ts +0 -39
- package/template/old/src/app/(app)/tasks/page.tsx +0 -59
- package/template/old/src/app/(app)/tasks/render.tsx +0 -129
- package/template/old/src/app/(app)/tasks/statusEnum.ts +0 -3
- package/template/old/src/app/(app)/users/[id]/actions.ts +0 -68
- package/template/old/src/app/(app)/users/[id]/page.tsx +0 -39
- package/template/old/src/app/(app)/users/[id]/render.tsx +0 -325
- package/template/old/src/app/(app)/users/[id]/validation.ts +0 -18
- package/template/old/src/app/(app)/users/actions.ts +0 -34
- package/template/old/src/app/(app)/users/page.tsx +0 -62
- package/template/old/src/app/(app)/users/render.tsx +0 -124
- package/template/old/src/app/(auth)/layout.tsx +0 -27
- package/template/old/src/app/(auth)/login/page.tsx +0 -49
- package/template/old/src/app/(auth)/login/render.tsx +0 -146
- package/template/old/src/app/(marketing)/layout.tsx +0 -42
- package/template/old/src/app/(marketing)/page.tsx +0 -97
- package/template/old/src/app/api/auth/[...nextauth]/route.ts +0 -7
- package/template/old/src/app/favicon.ico +0 -0
- package/template/old/src/app/globals.css +0 -76
- package/template/old/src/app/layout.tsx +0 -31
- package/template/old/src/components/app-nav-command.tsx +0 -178
- package/template/old/src/components/dark-mode-toggle.tsx +0 -43
- package/template/old/src/components/data-table/data-table-column-header.tsx +0 -91
- package/template/old/src/components/data-table/data-table-faceted-filter.tsx +0 -149
- package/template/old/src/components/data-table/data-table-loading.tsx +0 -77
- package/template/old/src/components/data-table/data-table-pagination.tsx +0 -94
- package/template/old/src/components/data-table/data-table-toolbar.tsx +0 -78
- package/template/old/src/components/data-table/data-table-types.ts +0 -17
- package/template/old/src/components/data-table/data-table-view-options.tsx +0 -59
- package/template/old/src/components/data-table/data-table.tsx +0 -301
- package/template/old/src/components/data-table/simple-data-table.tsx +0 -109
- package/template/old/src/components/empty-placeholder.tsx +0 -78
- package/template/old/src/components/heading.tsx +0 -17
- package/template/old/src/components/icons.tsx +0 -101
- package/template/old/src/components/main-nav.tsx +0 -64
- package/template/old/src/components/mobile-nav.tsx +0 -50
- package/template/old/src/components/shell.tsx +0 -13
- package/template/old/src/components/side-nav.tsx +0 -51
- package/template/old/src/components/site-footer.tsx +0 -31
- package/template/old/src/components/system-info.tsx +0 -36
- package/template/old/src/components/ui/alert-dialog.tsx +0 -141
- package/template/old/src/components/ui/avatar.tsx +0 -50
- package/template/old/src/components/ui/badge.tsx +0 -36
- package/template/old/src/components/ui/button.tsx +0 -56
- package/template/old/src/components/ui/card.tsx +0 -79
- package/template/old/src/components/ui/checkbox.tsx +0 -30
- package/template/old/src/components/ui/command.tsx +0 -155
- package/template/old/src/components/ui/dialog.tsx +0 -122
- package/template/old/src/components/ui/dropdown-menu.tsx +0 -200
- package/template/old/src/components/ui/form.tsx +0 -176
- package/template/old/src/components/ui/input.tsx +0 -25
- package/template/old/src/components/ui/label.tsx +0 -26
- package/template/old/src/components/ui/popover.tsx +0 -31
- package/template/old/src/components/ui/select.tsx +0 -160
- package/template/old/src/components/ui/separator.tsx +0 -31
- package/template/old/src/components/ui/skeleton.tsx +0 -15
- package/template/old/src/components/ui/sonner.tsx +0 -31
- package/template/old/src/components/ui/table.tsx +0 -117
- package/template/old/src/components/user-account-nav.tsx +0 -60
- package/template/old/src/components/user-avatar.tsx +0 -27
- package/template/old/src/data-access/resource.ts +0 -85
- package/template/old/src/data-access/role-permission.ts +0 -45
- package/template/old/src/data-access/role.ts +0 -105
- package/template/old/src/data-access/task.ts +0 -141
- package/template/old/src/data-access/user.ts +0 -141
- package/template/old/src/db/index.ts +0 -14
- package/template/old/src/db/schema.ts +0 -287
- package/template/old/src/env.mjs +0 -58
- package/template/old/src/hooks/use-debounce.tsx +0 -17
- package/template/old/src/hooks/use-lock-body.tsx +0 -13
- package/template/old/src/lib/auth.ts +0 -89
- package/template/old/src/lib/cn.ts +0 -4
- package/template/old/src/lib/currentUser.ts +0 -8
- package/template/old/src/lib/delay.ts +0 -2
- package/template/old/src/lib/formatDate.ts +0 -11
- package/template/old/src/lib/generateLabel.ts +0 -5
- package/template/old/src/lib/s3Client.ts +0 -156
- package/template/old/src/lib/safe-action.ts +0 -19
- package/template/old/src/lib/siteConfig.ts +0 -4
- package/template/old/src/lib/sortOrderEnum.ts +0 -2
- package/template/old/src/use-case/role-permission.ts +0 -19
- package/template/old/src/use-case/role.ts +0 -110
- package/template/old/src/use-case/task.ts +0 -166
- package/template/old/src/use-case/user.ts +0 -121
- package/template/old/tailwind.config.ts +0 -81
- package/template/old/tsconfig.json +0 -47
|
@@ -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
|
-
})
|
|
@@ -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 { getTasksAction } from './actions'
|
|
7
|
-
import { Render } from './render'
|
|
8
|
-
|
|
9
|
-
export const metadata: Metadata = {
|
|
10
|
-
title: 'Tasks' + ' | ' + siteConfig.name
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const TasksPage = async ({
|
|
14
|
-
searchParams
|
|
15
|
-
}: {
|
|
16
|
-
searchParams: {
|
|
17
|
-
page?: string
|
|
18
|
-
limit?: string
|
|
19
|
-
sort?: string
|
|
20
|
-
title?: string
|
|
21
|
-
status?: string
|
|
22
|
-
}
|
|
23
|
-
}) => {
|
|
24
|
-
const session = await getAuthSession()
|
|
25
|
-
if (!session) return notFound()
|
|
26
|
-
|
|
27
|
-
const g = await authGuard(session, 'TaskList')
|
|
28
|
-
if (!g) return notFound()
|
|
29
|
-
|
|
30
|
-
const page = searchParams.page ? parseInt(searchParams.page) : 1
|
|
31
|
-
const limit = searchParams.limit ? parseInt(searchParams.limit) : 10
|
|
32
|
-
|
|
33
|
-
const [sortBy, sortOrder] =
|
|
34
|
-
typeof searchParams.sort === 'string'
|
|
35
|
-
? (searchParams.sort.split('.') as [any, 'asc' | 'desc' | undefined])
|
|
36
|
-
: []
|
|
37
|
-
|
|
38
|
-
const title = searchParams.title
|
|
39
|
-
|
|
40
|
-
const status =
|
|
41
|
-
typeof searchParams.status === 'string'
|
|
42
|
-
? (searchParams.status.split('.') as any[])
|
|
43
|
-
: undefined
|
|
44
|
-
|
|
45
|
-
const tasks = await getTasksAction({
|
|
46
|
-
page,
|
|
47
|
-
limit,
|
|
48
|
-
sortBy,
|
|
49
|
-
sortOrder,
|
|
50
|
-
title,
|
|
51
|
-
status
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
if (!tasks?.data) throw new Error('Tasks data not found')
|
|
55
|
-
|
|
56
|
-
return <Render data={tasks.data} />
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export default TasksPage
|