create-ej-app 11.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 +14 -20
- package/template/base/src/app/not-found.tsx +1 -7
- 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,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
|
-
})
|