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.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/cli.js +68 -0
- package/package.json +36 -0
- package/template/.env.example +12 -0
- package/template/.eslintrc.cjs +24 -0
- package/template/components.json +16 -0
- package/template/next.config.mjs +18 -0
- package/template/package.json +68 -0
- package/template/postcss.config.cjs +6 -0
- package/template/prettier.config.ts +11 -0
- package/template/prisma/schema.prisma +136 -0
- package/template/public/next.svg +1 -0
- package/template/src/app/(app)/dashboard/page.tsx +19 -0
- package/template/src/app/(app)/layout.tsx +117 -0
- package/template/src/app/(app)/roles/[id]/actions.ts +100 -0
- package/template/src/app/(app)/roles/[id]/page.tsx +36 -0
- package/template/src/app/(app)/roles/[id]/render.tsx +266 -0
- package/template/src/app/(app)/roles/actions.ts +67 -0
- package/template/src/app/(app)/roles/page.tsx +51 -0
- package/template/src/app/(app)/roles/render.tsx +70 -0
- package/template/src/app/(app)/settings/actions.ts +18 -0
- package/template/src/app/(app)/settings/loading.tsx +28 -0
- package/template/src/app/(app)/settings/page.tsx +28 -0
- package/template/src/app/(app)/settings/render.tsx +113 -0
- package/template/src/app/(app)/tasks/[id]/actions.ts +107 -0
- package/template/src/app/(app)/tasks/[id]/page.tsx +55 -0
- package/template/src/app/(app)/tasks/[id]/render.tsx +307 -0
- package/template/src/app/(app)/tasks/actions.ts +75 -0
- package/template/src/app/(app)/tasks/page.tsx +66 -0
- package/template/src/app/(app)/tasks/render.tsx +124 -0
- package/template/src/app/(app)/users/[id]/actions.ts +104 -0
- package/template/src/app/(app)/users/[id]/page.tsx +39 -0
- package/template/src/app/(app)/users/[id]/render.tsx +320 -0
- package/template/src/app/(app)/users/actions.ts +67 -0
- package/template/src/app/(app)/users/page.tsx +61 -0
- package/template/src/app/(app)/users/render.tsx +108 -0
- package/template/src/app/(auth)/layout.tsx +20 -0
- package/template/src/app/(auth)/login/page.tsx +49 -0
- package/template/src/app/(auth)/login/user-auth-form.tsx +152 -0
- package/template/src/app/(marketing)/layout.tsx +42 -0
- package/template/src/app/(marketing)/page.tsx +96 -0
- package/template/src/app/api/auth/[...nextauth]/route.ts +7 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +76 -0
- package/template/src/app/layout.tsx +32 -0
- package/template/src/components/app-nav-command.tsx +160 -0
- package/template/src/components/dark-mode-toggle.tsx +43 -0
- package/template/src/components/data-table/data-table-column-header.tsx +91 -0
- package/template/src/components/data-table/data-table-faceted-filter.tsx +149 -0
- package/template/src/components/data-table/data-table-loading.tsx +77 -0
- package/template/src/components/data-table/data-table-pagination.tsx +94 -0
- package/template/src/components/data-table/data-table-toolbar.tsx +78 -0
- package/template/src/components/data-table/data-table-types.ts +17 -0
- package/template/src/components/data-table/data-table-view-options.tsx +59 -0
- package/template/src/components/data-table/data-table.tsx +301 -0
- package/template/src/components/data-table/simple-data-table.tsx +109 -0
- package/template/src/components/empty-placeholder.tsx +78 -0
- package/template/src/components/heading.tsx +17 -0
- package/template/src/components/icons.tsx +83 -0
- package/template/src/components/main-nav.tsx +64 -0
- package/template/src/components/mobile-nav.tsx +50 -0
- package/template/src/components/shell.tsx +13 -0
- package/template/src/components/side-nav.tsx +51 -0
- package/template/src/components/site-footer.tsx +31 -0
- package/template/src/components/system-info.tsx +36 -0
- package/template/src/components/theme-provider.tsx +13 -0
- package/template/src/components/ui/alert-dialog.tsx +145 -0
- package/template/src/components/ui/avatar.tsx +50 -0
- package/template/src/components/ui/badge.tsx +36 -0
- package/template/src/components/ui/button.tsx +56 -0
- package/template/src/components/ui/card.tsx +79 -0
- package/template/src/components/ui/checkbox.tsx +30 -0
- package/template/src/components/ui/command.tsx +155 -0
- package/template/src/components/ui/dialog.tsx +123 -0
- package/template/src/components/ui/dropdown-menu.tsx +200 -0
- package/template/src/components/ui/form.tsx +176 -0
- package/template/src/components/ui/input.tsx +25 -0
- package/template/src/components/ui/label.tsx +26 -0
- package/template/src/components/ui/popover.tsx +31 -0
- package/template/src/components/ui/select.tsx +121 -0
- package/template/src/components/ui/separator.tsx +31 -0
- package/template/src/components/ui/skeleton.tsx +15 -0
- package/template/src/components/ui/table.tsx +114 -0
- package/template/src/components/ui/textarea.tsx +24 -0
- package/template/src/components/ui/toast.tsx +127 -0
- package/template/src/components/ui/toaster.tsx +35 -0
- package/template/src/components/ui/use-toast.ts +189 -0
- package/template/src/components/user-account-nav.tsx +60 -0
- package/template/src/components/user-avatar.tsx +25 -0
- package/template/src/env.mjs +67 -0
- package/template/src/hooks/use-debounce.ts +17 -0
- package/template/src/hooks/use-lock-body.tsx +12 -0
- package/template/src/lib/auth.ts +183 -0
- package/template/src/lib/cn.ts +4 -0
- package/template/src/lib/db.ts +20 -0
- package/template/src/lib/delay.ts +2 -0
- package/template/src/lib/formatDate.ts +11 -0
- package/template/src/lib/generateLabel.ts +5 -0
- package/template/src/lib/siteConfig.ts +4 -0
- package/template/src/types/UnwrapPromise.ts +1 -0
- package/template/tailwind.config.ts +79 -0
- 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
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,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
|
+
}
|