create-ej-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/cli.js +68 -0
  4. package/package.json +36 -0
  5. package/template/.env.example +12 -0
  6. package/template/.eslintrc.cjs +24 -0
  7. package/template/components.json +16 -0
  8. package/template/next.config.mjs +18 -0
  9. package/template/package.json +68 -0
  10. package/template/postcss.config.cjs +6 -0
  11. package/template/prettier.config.ts +11 -0
  12. package/template/prisma/schema.prisma +136 -0
  13. package/template/public/next.svg +1 -0
  14. package/template/src/app/(app)/dashboard/page.tsx +19 -0
  15. package/template/src/app/(app)/layout.tsx +117 -0
  16. package/template/src/app/(app)/roles/[id]/actions.ts +100 -0
  17. package/template/src/app/(app)/roles/[id]/page.tsx +36 -0
  18. package/template/src/app/(app)/roles/[id]/render.tsx +266 -0
  19. package/template/src/app/(app)/roles/actions.ts +67 -0
  20. package/template/src/app/(app)/roles/page.tsx +51 -0
  21. package/template/src/app/(app)/roles/render.tsx +70 -0
  22. package/template/src/app/(app)/settings/actions.ts +18 -0
  23. package/template/src/app/(app)/settings/loading.tsx +28 -0
  24. package/template/src/app/(app)/settings/page.tsx +28 -0
  25. package/template/src/app/(app)/settings/render.tsx +113 -0
  26. package/template/src/app/(app)/tasks/[id]/actions.ts +107 -0
  27. package/template/src/app/(app)/tasks/[id]/page.tsx +55 -0
  28. package/template/src/app/(app)/tasks/[id]/render.tsx +307 -0
  29. package/template/src/app/(app)/tasks/actions.ts +75 -0
  30. package/template/src/app/(app)/tasks/page.tsx +66 -0
  31. package/template/src/app/(app)/tasks/render.tsx +124 -0
  32. package/template/src/app/(app)/users/[id]/actions.ts +104 -0
  33. package/template/src/app/(app)/users/[id]/page.tsx +39 -0
  34. package/template/src/app/(app)/users/[id]/render.tsx +320 -0
  35. package/template/src/app/(app)/users/actions.ts +67 -0
  36. package/template/src/app/(app)/users/page.tsx +61 -0
  37. package/template/src/app/(app)/users/render.tsx +108 -0
  38. package/template/src/app/(auth)/layout.tsx +20 -0
  39. package/template/src/app/(auth)/login/page.tsx +49 -0
  40. package/template/src/app/(auth)/login/user-auth-form.tsx +152 -0
  41. package/template/src/app/(marketing)/layout.tsx +42 -0
  42. package/template/src/app/(marketing)/page.tsx +96 -0
  43. package/template/src/app/api/auth/[...nextauth]/route.ts +7 -0
  44. package/template/src/app/favicon.ico +0 -0
  45. package/template/src/app/globals.css +76 -0
  46. package/template/src/app/layout.tsx +32 -0
  47. package/template/src/components/app-nav-command.tsx +160 -0
  48. package/template/src/components/dark-mode-toggle.tsx +43 -0
  49. package/template/src/components/data-table/data-table-column-header.tsx +91 -0
  50. package/template/src/components/data-table/data-table-faceted-filter.tsx +149 -0
  51. package/template/src/components/data-table/data-table-loading.tsx +77 -0
  52. package/template/src/components/data-table/data-table-pagination.tsx +94 -0
  53. package/template/src/components/data-table/data-table-toolbar.tsx +78 -0
  54. package/template/src/components/data-table/data-table-types.ts +17 -0
  55. package/template/src/components/data-table/data-table-view-options.tsx +59 -0
  56. package/template/src/components/data-table/data-table.tsx +301 -0
  57. package/template/src/components/data-table/simple-data-table.tsx +109 -0
  58. package/template/src/components/empty-placeholder.tsx +78 -0
  59. package/template/src/components/heading.tsx +17 -0
  60. package/template/src/components/icons.tsx +83 -0
  61. package/template/src/components/main-nav.tsx +64 -0
  62. package/template/src/components/mobile-nav.tsx +50 -0
  63. package/template/src/components/shell.tsx +13 -0
  64. package/template/src/components/side-nav.tsx +51 -0
  65. package/template/src/components/site-footer.tsx +31 -0
  66. package/template/src/components/system-info.tsx +36 -0
  67. package/template/src/components/theme-provider.tsx +13 -0
  68. package/template/src/components/ui/alert-dialog.tsx +145 -0
  69. package/template/src/components/ui/avatar.tsx +50 -0
  70. package/template/src/components/ui/badge.tsx +36 -0
  71. package/template/src/components/ui/button.tsx +56 -0
  72. package/template/src/components/ui/card.tsx +79 -0
  73. package/template/src/components/ui/checkbox.tsx +30 -0
  74. package/template/src/components/ui/command.tsx +155 -0
  75. package/template/src/components/ui/dialog.tsx +123 -0
  76. package/template/src/components/ui/dropdown-menu.tsx +200 -0
  77. package/template/src/components/ui/form.tsx +176 -0
  78. package/template/src/components/ui/input.tsx +25 -0
  79. package/template/src/components/ui/label.tsx +26 -0
  80. package/template/src/components/ui/popover.tsx +31 -0
  81. package/template/src/components/ui/select.tsx +121 -0
  82. package/template/src/components/ui/separator.tsx +31 -0
  83. package/template/src/components/ui/skeleton.tsx +15 -0
  84. package/template/src/components/ui/table.tsx +114 -0
  85. package/template/src/components/ui/textarea.tsx +24 -0
  86. package/template/src/components/ui/toast.tsx +127 -0
  87. package/template/src/components/ui/toaster.tsx +35 -0
  88. package/template/src/components/ui/use-toast.ts +189 -0
  89. package/template/src/components/user-account-nav.tsx +60 -0
  90. package/template/src/components/user-avatar.tsx +25 -0
  91. package/template/src/env.mjs +67 -0
  92. package/template/src/hooks/use-debounce.ts +17 -0
  93. package/template/src/hooks/use-lock-body.tsx +12 -0
  94. package/template/src/lib/auth.ts +183 -0
  95. package/template/src/lib/cn.ts +4 -0
  96. package/template/src/lib/db.ts +20 -0
  97. package/template/src/lib/delay.ts +2 -0
  98. package/template/src/lib/formatDate.ts +11 -0
  99. package/template/src/lib/generateLabel.ts +5 -0
  100. package/template/src/lib/siteConfig.ts +4 -0
  101. package/template/src/types/UnwrapPromise.ts +1 -0
  102. package/template/tailwind.config.ts +79 -0
  103. package/template/tsconfig.json +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Ekansh Jain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # create-ej-app
2
+
3
+ ```
4
+ npx create-ej-app
5
+ ```
package/cli.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander')
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const { prompt } = require('enquirer')
7
+ const copydir = require('copy-dir')
8
+
9
+ program
10
+ .version('1.0.0')
11
+ .description('CLI tool to bootstrap a Next.js App')
12
+ .action(async () => {
13
+ const response = await prompt([
14
+ {
15
+ type: 'input',
16
+ name: 'projectName',
17
+ message: 'Enter the project name:',
18
+ initial: 'my-app'
19
+ },
20
+ {
21
+ type: 'input',
22
+ name: 'description',
23
+ message: 'Enter a description for the project:',
24
+ initial: 'A Legendary Next.js App'
25
+ }
26
+ ])
27
+
28
+ const { projectName, description } = response
29
+
30
+ // Create a directory with the project name
31
+ const projectDir = path.join(process.cwd(), projectName)
32
+
33
+ if (fs.existsSync(projectDir)) {
34
+ console.error(`Error: Directory '${projectName}' already exists.`)
35
+ process.exit(1)
36
+ }
37
+
38
+ fs.mkdirSync(projectDir)
39
+
40
+ // Copy all files and subdirectories from your template folder to the new project directory
41
+ const templateDir = path.join(__dirname, 'template')
42
+ copydir.sync(templateDir, projectDir)
43
+
44
+ // Modify package.json in the new project directory to set the project name and description
45
+ const packageJsonFile = path.join(projectDir, 'package.json')
46
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'))
47
+ packageJson.name = projectName
48
+ packageJson.description = description
49
+ fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2))
50
+
51
+ // Modify src/lib/siteConfig.ts in the new project directory to set the project name and description
52
+ const siteConfigFile = path.join(projectDir, 'src', 'lib', 'siteConfig.ts')
53
+ let siteConfig = fs.readFileSync(siteConfigFile, 'utf-8')
54
+ siteConfig = siteConfig.replace('Project Name', projectName)
55
+ siteConfig = siteConfig.replace('Project Description', description)
56
+ fs.writeFileSync(siteConfigFile, siteConfig)
57
+
58
+ console.log(`Created a new Next.js app called '${projectName}'
59
+
60
+
61
+ To get started, run the following commands:
62
+
63
+ cd ${projectName}
64
+ npm install
65
+ npm run dev`)
66
+ })
67
+
68
+ program.parse(process.argv)
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "create-ej-app",
3
+ "version": "1.0.0",
4
+ "description": "Bootstrap a next.js app with my preferred setup",
5
+ "main": "cli.js",
6
+ "scripts": {
7
+ "start": "node ."
8
+ },
9
+ "bin": {
10
+ "create-ej-app": "cli.js"
11
+ },
12
+ "keywords": [
13
+ "create-ej-app",
14
+ "nextjs",
15
+ "typescript",
16
+ "nextauth",
17
+ "prisma",
18
+ "tailwind",
19
+ "shadcn"
20
+ ],
21
+ "author": "Ekansh Jain",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/ejekanshjain/create-ej-app.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/ejekanshjain/create-ej-app/issues"
29
+ },
30
+ "homepage": "https://github.com/ejekanshjain/create-ej-app#readme",
31
+ "dependencies": {
32
+ "commander": "^11.0.0",
33
+ "copy-dir": "^1.3.0",
34
+ "enquirer": "^2.4.1"
35
+ }
36
+ }
@@ -0,0 +1,12 @@
1
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres?schema=public"
2
+ NEXTAUTH_SECRET="secret"
3
+ NEXTAUTH_URL="http://localhost:3000"
4
+ EMAIL_SERVER_HOST="example.com"
5
+ EMAIL_SERVER_PORT="123"
6
+ EMAIL_SERVER_USER="example@example.com"
7
+ EMAIL_SERVER_PASSWORD="password"
8
+ EMAIL_FROM="example@example.com"
9
+ GOOGLE_ID=""
10
+ GOOGLE_SECRET=""
11
+ GITHUB_ID=""
12
+ GITHUB_SECRET=""
@@ -0,0 +1,24 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
2
+ const path = require('path')
3
+
4
+ /** @type {import("eslint").Linter.Config} */
5
+ const eslintConfig = {
6
+ overrides: [
7
+ {
8
+ extends: [],
9
+ files: ['*.ts', '*.tsx'],
10
+ parserOptions: {
11
+ project: path.join(__dirname, 'tsconfig.json')
12
+ }
13
+ }
14
+ ],
15
+ parser: '@typescript-eslint/parser',
16
+ parserOptions: {
17
+ project: path.join(__dirname, 'tsconfig.json')
18
+ },
19
+ plugins: ['@typescript-eslint'],
20
+ extends: ['next/core-web-vitals'],
21
+ rules: {}
22
+ }
23
+
24
+ module.exports = eslintConfig
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.cjs",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/cn"
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
3
+ * for Docker builds.
4
+ */
5
+ await import('./src/env.mjs')
6
+
7
+ /** @type {import('next').NextConfig} */
8
+ const nextConfig = {
9
+ experimental: {
10
+ serverActions: true
11
+ },
12
+ i18n: {
13
+ locales: ['en'],
14
+ defaultLocale: 'en'
15
+ }
16
+ }
17
+
18
+ export default nextConfig
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "my-app",
3
+ "version": "1.0.0",
4
+ "description": "A Legendary Next.js App",
5
+ "private": true,
6
+ "scripts": {
7
+ "postinstall": "prisma generate",
8
+ "lint": "next lint",
9
+ "dev": "next dev",
10
+ "build": "next build",
11
+ "start": "next start",
12
+ "prisma:push": "prisma db push",
13
+ "prisma:gen": "prisma generate",
14
+ "prisma:studio": "prisma studio"
15
+ },
16
+ "dependencies": {
17
+ "@hookform/resolvers": "^3.3.1",
18
+ "@next-auth/prisma-adapter": "^1.0.7",
19
+ "@prisma/client": "^5.3.1",
20
+ "@radix-ui/react-alert-dialog": "^1.0.4",
21
+ "@radix-ui/react-avatar": "^1.0.3",
22
+ "@radix-ui/react-checkbox": "^1.0.4",
23
+ "@radix-ui/react-dialog": "^1.0.4",
24
+ "@radix-ui/react-dropdown-menu": "^2.0.5",
25
+ "@radix-ui/react-label": "^2.0.2",
26
+ "@radix-ui/react-popover": "^1.0.6",
27
+ "@radix-ui/react-select": "^1.2.2",
28
+ "@radix-ui/react-separator": "^1.0.3",
29
+ "@radix-ui/react-slot": "^1.0.2",
30
+ "@radix-ui/react-toast": "^1.1.4",
31
+ "@t3-oss/env-nextjs": "^0.6.1",
32
+ "@tanstack/react-table": "^8.10.1",
33
+ "@types/eslint": "^8.44.2",
34
+ "@types/lodash": "^4.14.199",
35
+ "@types/node": "^20.6.3",
36
+ "@types/nodemailer": "^6.4.10",
37
+ "@types/react": "^18.2.22",
38
+ "@types/react-dom": "^18.2.7",
39
+ "@typescript-eslint/eslint-plugin": "^6.7.2",
40
+ "@typescript-eslint/parser": "^6.7.2",
41
+ "@vercel/analytics": "^1.0.2",
42
+ "autoprefixer": "^10.4.16",
43
+ "class-variance-authority": "^0.7.0",
44
+ "clsx": "^2.0.0",
45
+ "cmdk": "^0.2.0",
46
+ "dayjs": "^1.11.10",
47
+ "eslint": "^8.50.0",
48
+ "eslint-config-next": "^13.5.2",
49
+ "lodash": "^4.17.21",
50
+ "lucide-react": "^0.274.0",
51
+ "next": "^13.5.2",
52
+ "next-auth": "^4.23.1",
53
+ "next-themes": "^0.2.1",
54
+ "nodemailer": "^6.9.5",
55
+ "postcss": "^8.4.30",
56
+ "prettier": "^3.0.3",
57
+ "prettier-plugin-tailwindcss": "^0.5.4",
58
+ "prisma": "^5.3.1",
59
+ "react": "^18.2.0",
60
+ "react-dom": "^18.2.0",
61
+ "react-hook-form": "^7.46.2",
62
+ "tailwind-merge": "^1.14.0",
63
+ "tailwindcss": "^3.3.3",
64
+ "tailwindcss-animate": "^1.0.7",
65
+ "typescript": "^5.2.2",
66
+ "zod": "^3.22.2"
67
+ }
68
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {}
5
+ }
6
+ }
@@ -0,0 +1,11 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ const prettierConfig: Config = {
4
+ plugins: [require.resolve('prettier-plugin-tailwindcss')],
5
+ arrowParens: 'avoid',
6
+ trailingComma: 'none',
7
+ semi: false,
8
+ singleQuote: true
9
+ }
10
+
11
+ module.exports = prettierConfig
@@ -0,0 +1,136 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model Account {
11
+ id String @id @default(cuid())
12
+ userId String
13
+ type String
14
+ provider String
15
+ providerAccountId String
16
+ refresh_token String? @db.Text
17
+ access_token String? @db.Text
18
+ expires_at Int?
19
+ token_type String?
20
+ scope String?
21
+ id_token String? @db.Text
22
+ session_state String?
23
+
24
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
25
+
26
+ createdAt DateTime @default(now()) @db.Timestamptz()
27
+ updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
28
+
29
+ @@unique([provider, providerAccountId])
30
+ @@map("Accounts")
31
+ }
32
+
33
+ model Session {
34
+ id String @id @default(cuid())
35
+ sessionToken String @unique
36
+ userId String
37
+ expires DateTime @db.Timestamptz()
38
+
39
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
40
+
41
+ createdAt DateTime @default(now()) @db.Timestamptz()
42
+ updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
43
+
44
+ @@map("Sessions")
45
+ }
46
+
47
+ model Role {
48
+ id String @id @default(cuid())
49
+ name String @unique
50
+
51
+ createdAt DateTime @default(now()) @db.Timestamptz()
52
+ updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
53
+
54
+ permissions RolePermission[] @relation("rolePermissions")
55
+ users User[] @relation("userRole")
56
+
57
+ @@map("Roles")
58
+ }
59
+
60
+ enum Permissions {
61
+ TaskList
62
+ TaskView
63
+ TaskCreate
64
+ TaskUpdate
65
+ TaskDelete
66
+ }
67
+
68
+ model RolePermission {
69
+ id String @id @default(cuid())
70
+ roleId String
71
+ permission Permissions
72
+
73
+ role Role @relation("rolePermissions", fields: [roleId], references: [id], onDelete: Cascade)
74
+
75
+ @@map("RolePermissions")
76
+ }
77
+
78
+ enum UserType {
79
+ Root
80
+ Normal
81
+ }
82
+
83
+ model User {
84
+ id String @id @default(cuid())
85
+ name String?
86
+ email String? @unique
87
+ emailVerified DateTime? @db.Timestamptz()
88
+ image String?
89
+ type UserType @default(Normal)
90
+ roleId String?
91
+
92
+ createdAt DateTime @default(now()) @db.Timestamptz()
93
+ updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
94
+
95
+ accounts Account[]
96
+ sessions Session[]
97
+ role Role? @relation("userRole", fields: [roleId], references: [id])
98
+
99
+ createdTasks Task[] @relation("createdTasks")
100
+ updatedTasks Task[] @relation("updatedTasks")
101
+
102
+ @@map("Users")
103
+ }
104
+
105
+ model VerificationToken {
106
+ identifier String
107
+ token String @unique
108
+ expires DateTime @db.Timestamptz()
109
+
110
+ @@unique([identifier, token])
111
+ @@map("VerificationTokens")
112
+ }
113
+
114
+ enum TaskStatus {
115
+ Backlog
116
+ Todo
117
+ In_Progress
118
+ Done
119
+ Cancelled
120
+ }
121
+
122
+ model Task {
123
+ id String @id @default(cuid())
124
+ title String
125
+ status TaskStatus
126
+ description String?
127
+
128
+ createdAt DateTime @default(now()) @db.Timestamptz()
129
+ updatedAt DateTime @default(now()) @updatedAt @db.Timestamptz()
130
+ createdById String
131
+ createdBy User @relation("createdTasks", fields: [createdById], references: [id], onDelete: Cascade)
132
+ updatedById String
133
+ updatedBy User @relation("updatedTasks", fields: [updatedById], references: [id], onDelete: Cascade)
134
+
135
+ @@map("Tasks")
136
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1,19 @@
1
+ import { Metadata } from 'next'
2
+
3
+ import { Heading } from '@/components/heading'
4
+ import { Shell } from '@/components/shell'
5
+ import { siteConfig } from '@/lib/siteConfig'
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'Dashboard' + ' | ' + siteConfig.name
9
+ }
10
+
11
+ const DashboardPage = () => {
12
+ return (
13
+ <Shell>
14
+ <Heading heading="Dashboard" />
15
+ </Shell>
16
+ )
17
+ }
18
+
19
+ export default DashboardPage
@@ -0,0 +1,117 @@
1
+ import { notFound } from 'next/navigation'
2
+ import { ReactNode } from 'react'
3
+
4
+ import { AppNavCommand } from '@/components/app-nav-command'
5
+ import { MainNav } from '@/components/main-nav'
6
+ import { SideNav } from '@/components/side-nav'
7
+ import { SiteFooter } from '@/components/site-footer'
8
+ import { UserAccountNav } from '@/components/user-account-nav'
9
+ import { checkForPermission, getAuthSession } from '@/lib/auth'
10
+
11
+ const Layout = async ({ children }: { children: ReactNode }) => {
12
+ const session = await getAuthSession()
13
+
14
+ if (!session?.user) return notFound()
15
+
16
+ const isRoot = session.user.type === 'Root'
17
+ const [isTask] = await Promise.all([checkForPermission('TaskList', session)])
18
+
19
+ return (
20
+ <div className="flex min-h-screen flex-col space-y-6">
21
+ <header className="sticky top-0 z-40 border-b bg-background">
22
+ <div className="container flex h-16 items-center justify-between py-4">
23
+ <MainNav
24
+ logoLink="/dashboard"
25
+ items={[
26
+ {
27
+ title: 'Dashboard',
28
+ href: '/dashboard'
29
+ },
30
+ ...(isRoot
31
+ ? [
32
+ {
33
+ title: 'Users',
34
+ href: '/users'
35
+ },
36
+ {
37
+ title: 'Roles',
38
+ href: '/roles'
39
+ }
40
+ ]
41
+ : []),
42
+ ...(isTask
43
+ ? [
44
+ {
45
+ title: 'Tasks',
46
+ href: '/tasks'
47
+ }
48
+ ]
49
+ : [])
50
+ ]}
51
+ />
52
+ <div className="flex items-center justify-center space-x-4">
53
+ <AppNavCommand
54
+ modules={{
55
+ task: true
56
+ }}
57
+ />
58
+ <UserAccountNav
59
+ user={{
60
+ name: session.user.name,
61
+ image: session.user.image,
62
+ email: session.user.email
63
+ }}
64
+ />
65
+ </div>
66
+ </div>
67
+ </header>
68
+ <div className="container grid flex-1 gap-12 md:grid-cols-[200px_1fr]">
69
+ <aside className="hidden w-[200px] flex-col md:flex">
70
+ <SideNav
71
+ items={[
72
+ {
73
+ title: 'Dashboard',
74
+ href: '/dashboard',
75
+ icon: 'dashboard'
76
+ },
77
+ ...(isRoot
78
+ ? [
79
+ {
80
+ title: 'Users',
81
+ href: '/users',
82
+ icon: 'user' as const
83
+ },
84
+ {
85
+ title: 'Roles',
86
+ href: '/roles',
87
+ icon: 'mixer' as const
88
+ }
89
+ ]
90
+ : []),
91
+ ...(isTask
92
+ ? [
93
+ {
94
+ title: 'Tasks',
95
+ href: '/tasks',
96
+ icon: 'task' as const
97
+ }
98
+ ]
99
+ : []),
100
+ {
101
+ title: 'Settings',
102
+ href: '/settings',
103
+ icon: 'settings'
104
+ }
105
+ ]}
106
+ />
107
+ </aside>
108
+ <main className="flex w-full flex-1 flex-col overflow-hidden">
109
+ {children}
110
+ </main>
111
+ </div>
112
+ <SiteFooter className="border-t" />
113
+ </div>
114
+ )
115
+ }
116
+
117
+ export default Layout
@@ -0,0 +1,100 @@
1
+ 'use server'
2
+
3
+ import { Permissions } from '@prisma/client'
4
+ import { revalidatePath } from 'next/cache'
5
+
6
+ import { authGuard } from '@/lib/auth'
7
+ import { prisma } from '@/lib/db'
8
+ import { UnwrapPromise } from '@/types/UnwrapPromise'
9
+
10
+ export const getRole = async (id: string) => {
11
+ const session = await authGuard(['Root'])
12
+ if (!session) throw new Error('Unauthorized')
13
+
14
+ return await prisma.role.findUnique({
15
+ where: {
16
+ id
17
+ },
18
+ include: {
19
+ permissions: true
20
+ }
21
+ })
22
+ }
23
+
24
+ export type GetRoleFnDataType = UnwrapPromise<ReturnType<typeof getRole>>
25
+
26
+ export const createRole = async ({
27
+ name,
28
+ permissions
29
+ }: {
30
+ name: string
31
+ permissions: Permissions[]
32
+ }) => {
33
+ const session = await authGuard(['Root'])
34
+ if (!session) throw new Error('Unauthorized')
35
+
36
+ const { id } = await prisma.role.create({
37
+ data: {
38
+ name,
39
+ permissions: {
40
+ createMany: {
41
+ data: permissions.map(permission => ({
42
+ permission
43
+ }))
44
+ }
45
+ }
46
+ }
47
+ })
48
+
49
+ revalidatePath('/roles')
50
+ revalidatePath(`/roles/${id}`)
51
+
52
+ return id
53
+ }
54
+
55
+ export const updateRole = async ({
56
+ id,
57
+ name,
58
+ permissions
59
+ }: {
60
+ id: string
61
+ name: string
62
+ permissions: Permissions[]
63
+ }) => {
64
+ const session = await authGuard(['Root'])
65
+ if (!session) throw new Error('Unauthorized')
66
+
67
+ await prisma.role.update({
68
+ where: {
69
+ id
70
+ },
71
+ data: {
72
+ name
73
+ }
74
+ })
75
+ await prisma.rolePermission.deleteMany({
76
+ where: {
77
+ roleId: id
78
+ }
79
+ })
80
+ await prisma.rolePermission.createMany({
81
+ data: permissions.map(permission => ({ permission, roleId: id }))
82
+ })
83
+
84
+ revalidatePath('/roles')
85
+ revalidatePath(`/roles/${id}`)
86
+ }
87
+
88
+ export const deleteRole = async (id: string) => {
89
+ const session = await authGuard(['Root'])
90
+ if (!session) throw new Error('Unauthorized')
91
+
92
+ await prisma.role.delete({
93
+ where: {
94
+ id
95
+ }
96
+ })
97
+
98
+ revalidatePath('/roles')
99
+ revalidatePath(`/roles/${id}`)
100
+ }