create-ej-app 1.0.0 → 2.1.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 +22 -4
- package/package.json +1 -1
- package/template/{.env.example → advanced/.env.example} +4 -0
- package/template/{components.json → advanced/components.json} +1 -1
- package/template/{package.json → advanced/package.json} +11 -0
- package/template/{prisma → advanced/prisma}/schema.prisma +18 -1
- package/template/{src → advanced/src}/app/(app)/tasks/[id]/actions.ts +46 -4
- package/template/{src → advanced/src}/app/(app)/tasks/[id]/render.tsx +136 -25
- package/template/{src → advanced/src}/app/(app)/tasks/actions.ts +0 -6
- package/template/{src → advanced/src}/app/(app)/tasks/page.tsx +0 -3
- package/template/{src → advanced/src}/app/(app)/tasks/render.tsx +0 -10
- package/template/advanced/src/app/api/upload/route.ts +45 -0
- package/template/{src → advanced/src}/app/layout.tsx +1 -1
- package/template/{src → advanced/src}/env.mjs +10 -2
- package/template/advanced/src/styles/editor.css +61 -0
- package/template/{tailwind.config.ts → advanced/tailwind.config.ts} +1 -1
- package/template/base/.env.example +16 -0
- package/template/base/.eslintrc.cjs +24 -0
- package/template/base/components.json +16 -0
- package/template/base/next.config.mjs +18 -0
- package/template/base/package.json +79 -0
- package/template/base/postcss.config.cjs +6 -0
- package/template/base/prettier.config.ts +11 -0
- package/template/base/prisma/schema.prisma +115 -0
- package/template/base/public/next.svg +1 -0
- package/template/base/src/app/(app)/dashboard/page.tsx +19 -0
- package/template/base/src/app/(app)/layout.tsx +90 -0
- package/template/base/src/app/(app)/settings/actions.ts +18 -0
- package/template/base/src/app/(app)/settings/loading.tsx +28 -0
- package/template/base/src/app/(app)/settings/page.tsx +28 -0
- package/template/base/src/app/(app)/settings/render.tsx +113 -0
- package/template/base/src/app/(app)/tasks/[id]/actions.ts +149 -0
- package/template/base/src/app/(app)/tasks/[id]/page.tsx +25 -0
- package/template/base/src/app/(app)/tasks/[id]/render.tsx +413 -0
- package/template/base/src/app/(app)/tasks/actions.ts +69 -0
- package/template/base/src/app/(app)/tasks/page.tsx +53 -0
- package/template/base/src/app/(app)/tasks/render.tsx +107 -0
- package/template/base/src/app/(app)/users/[id]/actions.ts +83 -0
- package/template/base/src/app/(app)/users/[id]/page.tsx +32 -0
- package/template/base/src/app/(app)/users/[id]/render.tsx +240 -0
- package/template/base/src/app/(app)/users/actions.ts +55 -0
- package/template/base/src/app/(app)/users/page.tsx +49 -0
- package/template/base/src/app/(app)/users/render.tsx +80 -0
- package/template/base/src/app/(auth)/layout.tsx +20 -0
- package/template/base/src/app/(auth)/login/page.tsx +49 -0
- package/template/base/src/app/(auth)/login/user-auth-form.tsx +152 -0
- package/template/base/src/app/(marketing)/layout.tsx +42 -0
- package/template/base/src/app/(marketing)/page.tsx +96 -0
- package/template/base/src/app/api/auth/[...nextauth]/route.ts +7 -0
- package/template/base/src/app/api/upload/route.ts +45 -0
- package/template/base/src/app/favicon.ico +0 -0
- package/template/base/src/app/layout.tsx +32 -0
- package/template/base/src/components/app-nav-command.tsx +160 -0
- package/template/base/src/components/dark-mode-toggle.tsx +43 -0
- package/template/base/src/components/data-table/data-table-column-header.tsx +91 -0
- package/template/base/src/components/data-table/data-table-faceted-filter.tsx +149 -0
- package/template/base/src/components/data-table/data-table-loading.tsx +77 -0
- package/template/base/src/components/data-table/data-table-pagination.tsx +94 -0
- package/template/base/src/components/data-table/data-table-toolbar.tsx +78 -0
- package/template/base/src/components/data-table/data-table-types.ts +17 -0
- package/template/base/src/components/data-table/data-table-view-options.tsx +59 -0
- package/template/base/src/components/data-table/data-table.tsx +301 -0
- package/template/base/src/components/data-table/simple-data-table.tsx +109 -0
- package/template/base/src/components/empty-placeholder.tsx +78 -0
- package/template/base/src/components/heading.tsx +17 -0
- package/template/base/src/components/icons.tsx +83 -0
- package/template/base/src/components/main-nav.tsx +64 -0
- package/template/base/src/components/mobile-nav.tsx +50 -0
- package/template/base/src/components/shell.tsx +13 -0
- package/template/base/src/components/side-nav.tsx +51 -0
- package/template/base/src/components/site-footer.tsx +31 -0
- package/template/base/src/components/system-info.tsx +36 -0
- package/template/base/src/components/theme-provider.tsx +13 -0
- package/template/base/src/components/ui/alert-dialog.tsx +145 -0
- package/template/base/src/components/ui/avatar.tsx +50 -0
- package/template/base/src/components/ui/badge.tsx +36 -0
- package/template/base/src/components/ui/button.tsx +56 -0
- package/template/base/src/components/ui/card.tsx +79 -0
- package/template/base/src/components/ui/checkbox.tsx +30 -0
- package/template/base/src/components/ui/command.tsx +155 -0
- package/template/base/src/components/ui/dialog.tsx +123 -0
- package/template/base/src/components/ui/dropdown-menu.tsx +200 -0
- package/template/base/src/components/ui/form.tsx +176 -0
- package/template/base/src/components/ui/input.tsx +25 -0
- package/template/base/src/components/ui/label.tsx +26 -0
- package/template/base/src/components/ui/popover.tsx +31 -0
- package/template/base/src/components/ui/select.tsx +121 -0
- package/template/base/src/components/ui/separator.tsx +31 -0
- package/template/base/src/components/ui/skeleton.tsx +15 -0
- package/template/base/src/components/ui/table.tsx +114 -0
- package/template/base/src/components/ui/textarea.tsx +24 -0
- package/template/base/src/components/ui/toast.tsx +127 -0
- package/template/base/src/components/ui/toaster.tsx +35 -0
- package/template/base/src/components/ui/use-toast.ts +189 -0
- package/template/base/src/components/user-account-nav.tsx +60 -0
- package/template/base/src/components/user-avatar.tsx +25 -0
- package/template/base/src/env.mjs +75 -0
- package/template/base/src/hooks/use-debounce.ts +17 -0
- package/template/base/src/hooks/use-lock-body.tsx +12 -0
- package/template/base/src/lib/auth.ts +79 -0
- package/template/base/src/lib/cn.ts +4 -0
- package/template/base/src/lib/db.ts +20 -0
- package/template/base/src/lib/delay.ts +2 -0
- package/template/base/src/lib/formatDate.ts +11 -0
- package/template/base/src/lib/generateLabel.ts +5 -0
- package/template/base/src/lib/siteConfig.ts +4 -0
- package/template/base/src/styles/editor.css +61 -0
- package/template/base/src/styles/globals.css +76 -0
- package/template/base/src/types/UnwrapPromise.ts +1 -0
- package/template/base/tailwind.config.ts +79 -0
- package/template/base/tsconfig.json +46 -0
- /package/template/{.eslintrc.cjs → advanced/.eslintrc.cjs} +0 -0
- /package/template/{next.config.mjs → advanced/next.config.mjs} +0 -0
- /package/template/{postcss.config.cjs → advanced/postcss.config.cjs} +0 -0
- /package/template/{prettier.config.ts → advanced/prettier.config.ts} +0 -0
- /package/template/{public → advanced/public}/next.svg +0 -0
- /package/template/{src → advanced/src}/app/(app)/dashboard/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/layout.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/roles/[id]/actions.ts +0 -0
- /package/template/{src → advanced/src}/app/(app)/roles/[id]/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/roles/[id]/render.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/roles/actions.ts +0 -0
- /package/template/{src → advanced/src}/app/(app)/roles/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/roles/render.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/settings/actions.ts +0 -0
- /package/template/{src → advanced/src}/app/(app)/settings/loading.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/settings/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/settings/render.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/tasks/[id]/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/users/[id]/actions.ts +0 -0
- /package/template/{src → advanced/src}/app/(app)/users/[id]/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/users/[id]/render.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/users/actions.ts +0 -0
- /package/template/{src → advanced/src}/app/(app)/users/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(app)/users/render.tsx +0 -0
- /package/template/{src → advanced/src}/app/(auth)/layout.tsx +0 -0
- /package/template/{src → advanced/src}/app/(auth)/login/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/(auth)/login/user-auth-form.tsx +0 -0
- /package/template/{src → advanced/src}/app/(marketing)/layout.tsx +0 -0
- /package/template/{src → advanced/src}/app/(marketing)/page.tsx +0 -0
- /package/template/{src → advanced/src}/app/api/auth/[...nextauth]/route.ts +0 -0
- /package/template/{src → advanced/src}/app/favicon.ico +0 -0
- /package/template/{src → advanced/src}/components/app-nav-command.tsx +0 -0
- /package/template/{src → advanced/src}/components/dark-mode-toggle.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-column-header.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-faceted-filter.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-loading.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-pagination.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-toolbar.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-types.ts +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table-view-options.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/data-table.tsx +0 -0
- /package/template/{src → advanced/src}/components/data-table/simple-data-table.tsx +0 -0
- /package/template/{src → advanced/src}/components/empty-placeholder.tsx +0 -0
- /package/template/{src → advanced/src}/components/heading.tsx +0 -0
- /package/template/{src → advanced/src}/components/icons.tsx +0 -0
- /package/template/{src → advanced/src}/components/main-nav.tsx +0 -0
- /package/template/{src → advanced/src}/components/mobile-nav.tsx +0 -0
- /package/template/{src → advanced/src}/components/shell.tsx +0 -0
- /package/template/{src → advanced/src}/components/side-nav.tsx +0 -0
- /package/template/{src → advanced/src}/components/site-footer.tsx +0 -0
- /package/template/{src → advanced/src}/components/system-info.tsx +0 -0
- /package/template/{src → advanced/src}/components/theme-provider.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/alert-dialog.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/avatar.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/badge.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/button.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/card.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/checkbox.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/command.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/dialog.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/dropdown-menu.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/form.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/input.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/label.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/popover.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/select.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/separator.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/skeleton.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/table.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/textarea.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/toast.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/toaster.tsx +0 -0
- /package/template/{src → advanced/src}/components/ui/use-toast.ts +0 -0
- /package/template/{src → advanced/src}/components/user-account-nav.tsx +0 -0
- /package/template/{src → advanced/src}/components/user-avatar.tsx +0 -0
- /package/template/{src → advanced/src}/hooks/use-debounce.ts +0 -0
- /package/template/{src → advanced/src}/hooks/use-lock-body.tsx +0 -0
- /package/template/{src → advanced/src}/lib/auth.ts +0 -0
- /package/template/{src → advanced/src}/lib/cn.ts +0 -0
- /package/template/{src → advanced/src}/lib/db.ts +0 -0
- /package/template/{src → advanced/src}/lib/delay.ts +0 -0
- /package/template/{src → advanced/src}/lib/formatDate.ts +0 -0
- /package/template/{src → advanced/src}/lib/generateLabel.ts +0 -0
- /package/template/{src → advanced/src}/lib/siteConfig.ts +0 -0
- /package/template/{src/app → advanced/src/styles}/globals.css +0 -0
- /package/template/{src → advanced/src}/types/UnwrapPromise.ts +0 -0
- /package/template/{tsconfig.json → advanced/tsconfig.json} +0 -0
package/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ const { prompt } = require('enquirer')
|
|
|
7
7
|
const copydir = require('copy-dir')
|
|
8
8
|
|
|
9
9
|
program
|
|
10
|
-
.version('1.0
|
|
10
|
+
.version('2.1.0')
|
|
11
11
|
.description('CLI tool to bootstrap a Next.js App')
|
|
12
12
|
.action(async () => {
|
|
13
13
|
const response = await prompt([
|
|
@@ -15,17 +15,30 @@ program
|
|
|
15
15
|
type: 'input',
|
|
16
16
|
name: 'projectName',
|
|
17
17
|
message: 'Enter the project name:',
|
|
18
|
-
initial: '
|
|
18
|
+
initial: 'ej-app'
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
type: 'input',
|
|
22
22
|
name: 'description',
|
|
23
23
|
message: 'Enter a description for the project:',
|
|
24
24
|
initial: 'A Legendary Next.js App'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'select',
|
|
28
|
+
name: 'template',
|
|
29
|
+
message: 'Select a template:',
|
|
30
|
+
choices: [
|
|
31
|
+
{ name: 'Base', value: 'base' },
|
|
32
|
+
{
|
|
33
|
+
name: 'With User Types and ACLs (roles & permissions)',
|
|
34
|
+
value: 'advanced'
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
initial: 0
|
|
25
38
|
}
|
|
26
39
|
])
|
|
27
40
|
|
|
28
|
-
const { projectName, description } = response
|
|
41
|
+
const { projectName, description, template } = response
|
|
29
42
|
|
|
30
43
|
// Create a directory with the project name
|
|
31
44
|
const projectDir = path.join(process.cwd(), projectName)
|
|
@@ -38,7 +51,7 @@ program
|
|
|
38
51
|
fs.mkdirSync(projectDir)
|
|
39
52
|
|
|
40
53
|
// Copy all files and subdirectories from your template folder to the new project directory
|
|
41
|
-
const templateDir = path.join(__dirname, 'template')
|
|
54
|
+
const templateDir = path.join(__dirname, 'template', template)
|
|
42
55
|
copydir.sync(templateDir, projectDir)
|
|
43
56
|
|
|
44
57
|
// Modify package.json in the new project directory to set the project name and description
|
|
@@ -55,6 +68,11 @@ program
|
|
|
55
68
|
siteConfig = siteConfig.replace('Project Description', description)
|
|
56
69
|
fs.writeFileSync(siteConfigFile, siteConfig)
|
|
57
70
|
|
|
71
|
+
// Copy .env.example to .env
|
|
72
|
+
const envExampleFile = path.join(projectDir, '.env.example')
|
|
73
|
+
const envExample = fs.readFileSync(envExampleFile, 'utf-8')
|
|
74
|
+
fs.writeFileSync(path.join(projectDir, '.env'), envExample)
|
|
75
|
+
|
|
58
76
|
console.log(`Created a new Next.js app called '${projectName}'
|
|
59
77
|
|
|
60
78
|
|
package/package.json
CHANGED
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
"prisma:studio": "prisma studio"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@editorjs/checklist": "^1.5.0",
|
|
18
|
+
"@editorjs/delimiter": "^1.3.0",
|
|
19
|
+
"@editorjs/editorjs": "^2.28.0",
|
|
20
|
+
"@editorjs/header": "^2.7.0",
|
|
21
|
+
"@editorjs/image": "^2.8.1",
|
|
22
|
+
"@editorjs/list": "^1.8.0",
|
|
23
|
+
"@editorjs/quote": "^2.5.0",
|
|
24
|
+
"@editorjs/table": "^2.2.2",
|
|
25
|
+
"@ejekanshjain/cloud-storage": "^3.0.0",
|
|
17
26
|
"@hookform/resolvers": "^3.3.1",
|
|
18
27
|
"@next-auth/prisma-adapter": "^1.0.7",
|
|
19
28
|
"@prisma/client": "^5.3.1",
|
|
@@ -29,6 +38,7 @@
|
|
|
29
38
|
"@radix-ui/react-slot": "^1.0.2",
|
|
30
39
|
"@radix-ui/react-toast": "^1.1.4",
|
|
31
40
|
"@t3-oss/env-nextjs": "^0.6.1",
|
|
41
|
+
"@tailwindcss/typography": "^0.5.10",
|
|
32
42
|
"@tanstack/react-table": "^8.10.1",
|
|
33
43
|
"@types/eslint": "^8.44.2",
|
|
34
44
|
"@types/lodash": "^4.14.199",
|
|
@@ -44,6 +54,7 @@
|
|
|
44
54
|
"clsx": "^2.0.0",
|
|
45
55
|
"cmdk": "^0.2.0",
|
|
46
56
|
"dayjs": "^1.11.10",
|
|
57
|
+
"editorjs-html": "^3.4.3",
|
|
47
58
|
"eslint": "^8.50.0",
|
|
48
59
|
"eslint-config-next": "^13.5.2",
|
|
49
60
|
"lodash": "^4.17.21",
|
|
@@ -123,7 +123,9 @@ model Task {
|
|
|
123
123
|
id String @id @default(cuid())
|
|
124
124
|
title String
|
|
125
125
|
status TaskStatus
|
|
126
|
-
description
|
|
126
|
+
description Json?
|
|
127
|
+
|
|
128
|
+
images Resource[] @relation("taskImages")
|
|
127
129
|
|
|
128
130
|
createdAt DateTime @default(now()) @db.Timestamptz()
|
|
129
131
|
updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
|
|
@@ -134,3 +136,18 @@ model Task {
|
|
|
134
136
|
|
|
135
137
|
@@map("Tasks")
|
|
136
138
|
}
|
|
139
|
+
|
|
140
|
+
model Resource {
|
|
141
|
+
id String @id @default(cuid())
|
|
142
|
+
originalFilename String
|
|
143
|
+
newFilename String @unique
|
|
144
|
+
url String @unique
|
|
145
|
+
|
|
146
|
+
taskId String?
|
|
147
|
+
task Task? @relation("taskImages", fields: [taskId], references: [id])
|
|
148
|
+
|
|
149
|
+
createdAt DateTime @default(now()) @db.Timestamptz()
|
|
150
|
+
updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
|
|
151
|
+
|
|
152
|
+
@@map("Resources")
|
|
153
|
+
}
|
|
@@ -37,11 +37,13 @@ export type GetTaskFnDataType = UnwrapPromise<ReturnType<typeof getTask>>
|
|
|
37
37
|
export const createTask = async ({
|
|
38
38
|
title,
|
|
39
39
|
status,
|
|
40
|
-
description
|
|
40
|
+
description,
|
|
41
|
+
imageIds
|
|
41
42
|
}: {
|
|
42
43
|
title: string
|
|
43
44
|
status: TaskStatus
|
|
44
|
-
description?:
|
|
45
|
+
description?: any
|
|
46
|
+
imageIds: string[]
|
|
45
47
|
}) => {
|
|
46
48
|
const session = await authGuard(['Root', 'Normal'], 'TaskCreate')
|
|
47
49
|
if (!session) throw new Error('Unauthorized')
|
|
@@ -55,6 +57,25 @@ export const createTask = async ({
|
|
|
55
57
|
updatedById: session.user.id
|
|
56
58
|
}
|
|
57
59
|
})
|
|
60
|
+
await prisma.resource.updateMany({
|
|
61
|
+
where: {
|
|
62
|
+
taskId: id
|
|
63
|
+
},
|
|
64
|
+
data: {
|
|
65
|
+
taskId: null
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
if (imageIds.length)
|
|
69
|
+
await prisma.resource.updateMany({
|
|
70
|
+
where: {
|
|
71
|
+
id: {
|
|
72
|
+
in: imageIds
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
data: {
|
|
76
|
+
taskId: id
|
|
77
|
+
}
|
|
78
|
+
})
|
|
58
79
|
|
|
59
80
|
revalidatePath('/tasks')
|
|
60
81
|
revalidatePath(`/tasks/${id}`)
|
|
@@ -66,12 +87,14 @@ export const updateTask = async ({
|
|
|
66
87
|
id,
|
|
67
88
|
title,
|
|
68
89
|
status,
|
|
69
|
-
description
|
|
90
|
+
description,
|
|
91
|
+
imageIds
|
|
70
92
|
}: {
|
|
71
93
|
id: string
|
|
72
94
|
title: string
|
|
73
95
|
status: TaskStatus
|
|
74
|
-
description?:
|
|
96
|
+
description?: any
|
|
97
|
+
imageIds: string[]
|
|
75
98
|
}) => {
|
|
76
99
|
const session = await authGuard(['Root', 'Normal'], 'TaskUpdate')
|
|
77
100
|
if (!session) throw new Error('Unauthorized')
|
|
@@ -87,6 +110,25 @@ export const updateTask = async ({
|
|
|
87
110
|
updatedById: session.user.id
|
|
88
111
|
}
|
|
89
112
|
})
|
|
113
|
+
await prisma.resource.updateMany({
|
|
114
|
+
where: {
|
|
115
|
+
taskId: id
|
|
116
|
+
},
|
|
117
|
+
data: {
|
|
118
|
+
taskId: null
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
if (imageIds.length)
|
|
122
|
+
await prisma.resource.updateMany({
|
|
123
|
+
where: {
|
|
124
|
+
id: {
|
|
125
|
+
in: imageIds
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
data: {
|
|
129
|
+
taskId: id
|
|
130
|
+
}
|
|
131
|
+
})
|
|
90
132
|
|
|
91
133
|
revalidatePath('/tasks')
|
|
92
134
|
revalidatePath(`/tasks/${id}`)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import EditorJS from '@editorjs/editorjs'
|
|
3
4
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
4
5
|
import { TaskStatus } from '@prisma/client'
|
|
6
|
+
import edjsHTML from 'editorjs-html'
|
|
5
7
|
import Link from 'next/link'
|
|
6
8
|
import { useRouter } from 'next/navigation'
|
|
7
|
-
import { FC, useState } from 'react'
|
|
9
|
+
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
8
10
|
import { useForm } from 'react-hook-form'
|
|
9
11
|
import { z } from 'zod'
|
|
10
12
|
|
|
@@ -40,10 +42,10 @@ import {
|
|
|
40
42
|
SelectTrigger,
|
|
41
43
|
SelectValue
|
|
42
44
|
} from '@/components/ui/select'
|
|
43
|
-
import { Textarea } from '@/components/ui/textarea'
|
|
44
45
|
import { toast } from '@/components/ui/use-toast'
|
|
45
46
|
import { formatDateTime, timesAgo } from '@/lib/formatDate'
|
|
46
47
|
import { generateLabel } from '@/lib/generateLabel'
|
|
48
|
+
import '@/styles/editor.css'
|
|
47
49
|
import {
|
|
48
50
|
GetTaskFnDataType,
|
|
49
51
|
createTask,
|
|
@@ -53,8 +55,7 @@ import {
|
|
|
53
55
|
|
|
54
56
|
const TaskSchema = z.object({
|
|
55
57
|
title: z.string().nonempty(),
|
|
56
|
-
status: z.nativeEnum(TaskStatus)
|
|
57
|
-
description: z.string().optional()
|
|
58
|
+
status: z.nativeEnum(TaskStatus)
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
type FormData = z.infer<typeof TaskSchema>
|
|
@@ -64,6 +65,8 @@ const statuses = Object.values(TaskStatus).map(x => ({
|
|
|
64
65
|
value: x
|
|
65
66
|
}))
|
|
66
67
|
|
|
68
|
+
const edjsParser = edjsHTML()
|
|
69
|
+
|
|
67
70
|
export const Render: FC<{
|
|
68
71
|
task: GetTaskFnDataType | undefined
|
|
69
72
|
canUpdate: boolean
|
|
@@ -74,24 +77,134 @@ export const Render: FC<{
|
|
|
74
77
|
resolver: zodResolver(TaskSchema),
|
|
75
78
|
defaultValues: {
|
|
76
79
|
title: task?.title ?? '',
|
|
77
|
-
status: task?.status || 'Todo'
|
|
78
|
-
description: task?.description ?? ''
|
|
80
|
+
status: task?.status || 'Todo'
|
|
79
81
|
}
|
|
80
82
|
})
|
|
81
83
|
const router = useRouter()
|
|
82
84
|
const [isSaving, setIsSaving] = useState(false)
|
|
83
85
|
const [isDeleting, setIsDeleting] = useState(false)
|
|
86
|
+
const [isEditorMounted, setIsMounted] = useState<boolean>(false)
|
|
87
|
+
|
|
88
|
+
const editorRef = useRef<EditorJS>()
|
|
89
|
+
|
|
90
|
+
const initializeEditor = useCallback(async () => {
|
|
91
|
+
const EditorJS = (await import('@editorjs/editorjs')).default
|
|
92
|
+
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
const Table = (await import('@editorjs/table')).default
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
const List = (await import('@editorjs/list')).default
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
const Image = (await import('@editorjs/image')).default
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
const Header = (await import('@editorjs/header')).default
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
const Quote = (await import('@editorjs/quote')).default
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
const CheckList = (await import('@editorjs/checklist')).default
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
const Delimiter = (await import('@editorjs/delimiter')).default
|
|
107
|
+
|
|
108
|
+
if (!editorRef.current) {
|
|
109
|
+
const editor = new EditorJS({
|
|
110
|
+
holder: 'editor',
|
|
111
|
+
onReady() {
|
|
112
|
+
editorRef.current = editor
|
|
113
|
+
},
|
|
114
|
+
placeholder: 'Type here to write...',
|
|
115
|
+
inlineToolbar: true,
|
|
116
|
+
data: task?.description as any,
|
|
117
|
+
tools: {
|
|
118
|
+
header: Header,
|
|
119
|
+
list: List,
|
|
120
|
+
image: {
|
|
121
|
+
class: Image,
|
|
122
|
+
config: {
|
|
123
|
+
uploader: {
|
|
124
|
+
async uploadByFile(file: any) {
|
|
125
|
+
const formData = new FormData()
|
|
126
|
+
formData.append('file', file)
|
|
127
|
+
const res = await fetch('/api/upload', {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
body: formData
|
|
130
|
+
})
|
|
131
|
+
const json = await res.json()
|
|
132
|
+
if (json.success && json.id && json.url) {
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
file: {
|
|
136
|
+
id: json.id,
|
|
137
|
+
url: json.url
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
return {
|
|
142
|
+
success: false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
table: Table,
|
|
150
|
+
checklist: CheckList,
|
|
151
|
+
quote: Quote,
|
|
152
|
+
delimiter: Delimiter
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}, [task?.description])
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (typeof window !== 'undefined') setIsMounted(true)
|
|
160
|
+
}, [])
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!isEditorMounted) return
|
|
164
|
+
|
|
165
|
+
initializeEditor()
|
|
166
|
+
|
|
167
|
+
return () => {
|
|
168
|
+
editorRef.current?.destroy()
|
|
169
|
+
editorRef.current = undefined
|
|
170
|
+
}
|
|
171
|
+
}, [isEditorMounted, initializeEditor])
|
|
172
|
+
|
|
173
|
+
const editorHTML = useMemo(() => {
|
|
174
|
+
if (!task?.description) return ''
|
|
175
|
+
return edjsParser.parse(task.description as any).join('')
|
|
176
|
+
}, [task?.description])
|
|
177
|
+
console.log(editorHTML)
|
|
84
178
|
|
|
85
179
|
async function onSubmit(data: FormData) {
|
|
86
180
|
setIsSaving(true)
|
|
87
181
|
try {
|
|
182
|
+
const editorData = await editorRef.current?.save()
|
|
183
|
+
const imageIds: string[] = []
|
|
184
|
+
if (editorData?.blocks)
|
|
185
|
+
for (const b of editorData.blocks)
|
|
186
|
+
if (
|
|
187
|
+
b.type === 'image' &&
|
|
188
|
+
b.data &&
|
|
189
|
+
b.data.file &&
|
|
190
|
+
b.data.file.id &&
|
|
191
|
+
b.data.file.url
|
|
192
|
+
)
|
|
193
|
+
imageIds.push(b.data.file.id)
|
|
194
|
+
|
|
88
195
|
if (!task) {
|
|
89
|
-
const newId = await createTask(
|
|
196
|
+
const newId = await createTask({
|
|
197
|
+
...data,
|
|
198
|
+
description: editorData,
|
|
199
|
+
imageIds
|
|
200
|
+
})
|
|
90
201
|
router.replace(`/tasks/${newId}`)
|
|
91
202
|
} else {
|
|
92
203
|
await updateTask({
|
|
93
204
|
id: task.id,
|
|
94
|
-
...data
|
|
205
|
+
...data,
|
|
206
|
+
description: editorData,
|
|
207
|
+
imageIds
|
|
95
208
|
})
|
|
96
209
|
}
|
|
97
210
|
toast({
|
|
@@ -243,23 +356,21 @@ export const Render: FC<{
|
|
|
243
356
|
)}
|
|
244
357
|
/>
|
|
245
358
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
)}
|
|
262
|
-
/>
|
|
359
|
+
{isEditorMounted ? (
|
|
360
|
+
<div className="col-span-1 md:col-span-2 w-full mt-5 prose prose-neutral mx-auto dark:prose-invert">
|
|
361
|
+
<h3 className="w-full bg-transparent text-3xl font-bold">
|
|
362
|
+
Description
|
|
363
|
+
</h3>
|
|
364
|
+
<div id="editor" className="min-h-[360px] w-full" />
|
|
365
|
+
<p className="text-sm text-gray-500">
|
|
366
|
+
Use{' '}
|
|
367
|
+
<kbd className="rounded-md border bg-muted px-1 text-xs uppercase">
|
|
368
|
+
Tab
|
|
369
|
+
</kbd>{' '}
|
|
370
|
+
to open the command menu.
|
|
371
|
+
</p>
|
|
372
|
+
</div>
|
|
373
|
+
) : undefined}
|
|
263
374
|
</form>
|
|
264
375
|
</Form>
|
|
265
376
|
{task ? (
|
|
@@ -12,7 +12,6 @@ export const getTasks = async ({
|
|
|
12
12
|
sortBy,
|
|
13
13
|
sortOrder,
|
|
14
14
|
title,
|
|
15
|
-
description,
|
|
16
15
|
status
|
|
17
16
|
}: {
|
|
18
17
|
page: number
|
|
@@ -20,7 +19,6 @@ export const getTasks = async ({
|
|
|
20
19
|
sortBy?: keyof Task
|
|
21
20
|
sortOrder?: 'asc' | 'desc'
|
|
22
21
|
title?: string
|
|
23
|
-
description?: string
|
|
24
22
|
status?: TaskStatus[]
|
|
25
23
|
}) => {
|
|
26
24
|
const session = await authGuard(['Root', 'Normal'], 'TaskList')
|
|
@@ -30,9 +28,6 @@ export const getTasks = async ({
|
|
|
30
28
|
title: title
|
|
31
29
|
? { contains: title, mode: 'insensitive' as const }
|
|
32
30
|
: undefined,
|
|
33
|
-
description: description
|
|
34
|
-
? { contains: description, mode: 'insensitive' as const }
|
|
35
|
-
: undefined,
|
|
36
31
|
status: status?.length ? { in: status } : undefined
|
|
37
32
|
}
|
|
38
33
|
const [tasks, total] = await Promise.all([
|
|
@@ -41,7 +36,6 @@ export const getTasks = async ({
|
|
|
41
36
|
select: {
|
|
42
37
|
id: true,
|
|
43
38
|
title: true,
|
|
44
|
-
description: true,
|
|
45
39
|
status: true,
|
|
46
40
|
createdAt: true,
|
|
47
41
|
updatedAt: true,
|
|
@@ -19,7 +19,6 @@ const TasksPage = async ({
|
|
|
19
19
|
limit?: string
|
|
20
20
|
sort?: string
|
|
21
21
|
title?: string
|
|
22
|
-
description?: string
|
|
23
22
|
status?: string
|
|
24
23
|
}
|
|
25
24
|
}) => {
|
|
@@ -38,7 +37,6 @@ const TasksPage = async ({
|
|
|
38
37
|
: []
|
|
39
38
|
|
|
40
39
|
const title = searchParams.title
|
|
41
|
-
const description = searchParams.description
|
|
42
40
|
|
|
43
41
|
const status =
|
|
44
42
|
typeof searchParams.status === 'string'
|
|
@@ -56,7 +54,6 @@ const TasksPage = async ({
|
|
|
56
54
|
sortBy,
|
|
57
55
|
sortOrder,
|
|
58
56
|
title,
|
|
59
|
-
description,
|
|
60
57
|
status
|
|
61
58
|
})
|
|
62
59
|
|
|
@@ -55,12 +55,6 @@ export const Render: FC<{
|
|
|
55
55
|
row.original.title
|
|
56
56
|
)
|
|
57
57
|
},
|
|
58
|
-
{
|
|
59
|
-
accessorKey: 'description',
|
|
60
|
-
header: ({ column }) => (
|
|
61
|
-
<DataTableColumnHeader column={column} title="Description" />
|
|
62
|
-
)
|
|
63
|
-
},
|
|
64
58
|
{
|
|
65
59
|
accessorKey: 'status',
|
|
66
60
|
header: ({ column }) => (
|
|
@@ -105,10 +99,6 @@ export const Render: FC<{
|
|
|
105
99
|
{
|
|
106
100
|
id: 'title',
|
|
107
101
|
title: 'Title'
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
id: 'description',
|
|
111
|
-
title: 'Description'
|
|
112
102
|
}
|
|
113
103
|
]}
|
|
114
104
|
filterableColumns={[
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GCPStorageClient } from '@ejekanshjain/cloud-storage'
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
3
|
+
|
|
4
|
+
import { env } from '@/env.mjs'
|
|
5
|
+
import { getAuthSession } from '@/lib/auth'
|
|
6
|
+
import { prisma } from '@/lib/db'
|
|
7
|
+
|
|
8
|
+
const storageClient = GCPStorageClient({
|
|
9
|
+
projectId: env.FIREBASE_PROJECT_ID!,
|
|
10
|
+
privateKey: env.FIREBASE_PRIVATE_KEY!,
|
|
11
|
+
clientEmail: env.FIREBASE_CLIENT_EMAIL!,
|
|
12
|
+
bucket: env.FIREBASE_STORAGE_BUCKET!,
|
|
13
|
+
defaultMediaPublic: true
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export async function POST(request: NextRequest) {
|
|
17
|
+
const session = await getAuthSession()
|
|
18
|
+
if (!session)
|
|
19
|
+
return NextResponse.json({
|
|
20
|
+
success: false
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const data = await request.formData()
|
|
24
|
+
const file: File | null = data.get('file') as unknown as File
|
|
25
|
+
|
|
26
|
+
if (!file) return NextResponse.json({ success: false })
|
|
27
|
+
|
|
28
|
+
const bytes = await file.arrayBuffer()
|
|
29
|
+
const buffer = Buffer.from(bytes)
|
|
30
|
+
|
|
31
|
+
const filename = `${Date.now()}-${Math.random()}-${file.name}`
|
|
32
|
+
const url = await storageClient.addFile({
|
|
33
|
+
filename,
|
|
34
|
+
data: buffer
|
|
35
|
+
})
|
|
36
|
+
const { id } = await prisma.resource.create({
|
|
37
|
+
data: {
|
|
38
|
+
newFilename: filename,
|
|
39
|
+
originalFilename: file.name,
|
|
40
|
+
url
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return NextResponse.json({ success: true, id, url })
|
|
45
|
+
}
|
|
@@ -6,7 +6,7 @@ import { ReactNode } from 'react'
|
|
|
6
6
|
import { ThemeProvider } from '@/components/theme-provider'
|
|
7
7
|
import { Toaster } from '@/components/ui/toaster'
|
|
8
8
|
import { siteConfig } from '@/lib/siteConfig'
|
|
9
|
-
import '
|
|
9
|
+
import '@/styles/globals.css'
|
|
10
10
|
|
|
11
11
|
const inter = Inter({ subsets: ['latin'] })
|
|
12
12
|
|
|
@@ -28,7 +28,11 @@ export const env = createEnv({
|
|
|
28
28
|
GOOGLE_ID: z.string().min(1),
|
|
29
29
|
GOOGLE_SECRET: z.string().min(1),
|
|
30
30
|
GITHUB_ID: z.string().min(1),
|
|
31
|
-
GITHUB_SECRET: z.string().min(1)
|
|
31
|
+
GITHUB_SECRET: z.string().min(1),
|
|
32
|
+
FIREBASE_PROJECT_ID: z.string().min(1),
|
|
33
|
+
FIREBASE_PRIVATE_KEY: z.string().min(1),
|
|
34
|
+
FIREBASE_CLIENT_EMAIL: z.string().min(1),
|
|
35
|
+
FIREBASE_STORAGE_BUCKET: z.string().min(1)
|
|
32
36
|
},
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -57,7 +61,11 @@ export const env = createEnv({
|
|
|
57
61
|
GOOGLE_ID: process.env.GOOGLE_ID,
|
|
58
62
|
GOOGLE_SECRET: process.env.GOOGLE_SECRET,
|
|
59
63
|
GITHUB_ID: process.env.GITHUB_ID,
|
|
60
|
-
GITHUB_SECRET: process.env.GITHUB_SECRET
|
|
64
|
+
GITHUB_SECRET: process.env.GITHUB_SECRET,
|
|
65
|
+
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
|
|
66
|
+
FIREBASE_PRIVATE_KEY: process.env.FIREBASE_PRIVATE_KEY,
|
|
67
|
+
FIREBASE_CLIENT_EMAIL: process.env.FIREBASE_CLIENT_EMAIL,
|
|
68
|
+
FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET
|
|
61
69
|
},
|
|
62
70
|
/**
|
|
63
71
|
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.dark .ce-block--selected .ce-block__content,
|
|
2
|
+
.dark .ce-inline-toolbar,
|
|
3
|
+
.dark .codex-editor--narrow .ce-toolbox,
|
|
4
|
+
.dark .ce-conversion-toolbar,
|
|
5
|
+
.dark .ce-settings,
|
|
6
|
+
.dark .ce-settings__button,
|
|
7
|
+
.dark .ce-toolbar__settings-btn,
|
|
8
|
+
.dark .cdx-button,
|
|
9
|
+
.dark .ce-popover,
|
|
10
|
+
.dark .ce-toolbar__plus:hover {
|
|
11
|
+
background: theme('colors.popover.DEFAULT');
|
|
12
|
+
color: inherit;
|
|
13
|
+
border-color: theme('colors.border');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dark .ce-inline-tool,
|
|
17
|
+
.dark .ce-conversion-toolbar__label,
|
|
18
|
+
.dark .ce-toolbox__button,
|
|
19
|
+
.dark .cdx-settings-button,
|
|
20
|
+
.dark .ce-toolbar__plus {
|
|
21
|
+
color: inherit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.dark .ce-popover__item-icon,
|
|
25
|
+
.dark .ce-conversion-tool__icon {
|
|
26
|
+
background-color: theme('colors.muted.DEFAULT');
|
|
27
|
+
box-shadow: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dark .cdx-search-field {
|
|
31
|
+
border-color: theme('colors.border');
|
|
32
|
+
background: theme('colors.input');
|
|
33
|
+
color: inherit;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.dark ::selection {
|
|
37
|
+
background: theme('colors.accent.DEFAULT');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dark .cdx-settings-button:hover,
|
|
41
|
+
.dark .ce-settings__button:hover,
|
|
42
|
+
.dark .ce-toolbox__button--active,
|
|
43
|
+
.dark .ce-toolbox__button:hover,
|
|
44
|
+
.dark .cdx-button:hover,
|
|
45
|
+
.dark .ce-inline-toolbar__dropdown:hover,
|
|
46
|
+
.dark .ce-inline-tool:hover,
|
|
47
|
+
.dark .ce-popover__item:hover,
|
|
48
|
+
.dark .ce-conversion-tool:hover,
|
|
49
|
+
.dark .ce-toolbar__settings-btn:hover {
|
|
50
|
+
background-color: theme('colors.accent.DEFAULT');
|
|
51
|
+
color: theme('colors.accent.foreground');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.dark .cdx-notify--error {
|
|
55
|
+
background: theme('colors.destructive.DEFAULT') !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dark .cdx-notify__cross::after,
|
|
59
|
+
.dark .cdx-notify__cross::before {
|
|
60
|
+
background: white;
|
|
61
|
+
}
|