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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { User } from 'next-auth'
|
|
4
|
+
import { signOut } from 'next-auth/react'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { FC, HTMLAttributes } from 'react'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuSeparator,
|
|
13
|
+
DropdownMenuTrigger
|
|
14
|
+
} from '@/components/ui/dropdown-menu'
|
|
15
|
+
import { UserAvatar } from '@/components/user-avatar'
|
|
16
|
+
|
|
17
|
+
interface UserAccountNavProps extends HTMLAttributes<HTMLDivElement> {
|
|
18
|
+
user: Pick<User, 'name' | 'image' | 'email'>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const UserAccountNav: FC<UserAccountNavProps> = ({ user }) => {
|
|
22
|
+
return (
|
|
23
|
+
<DropdownMenu>
|
|
24
|
+
<DropdownMenuTrigger>
|
|
25
|
+
<UserAvatar
|
|
26
|
+
user={{ name: user.name || null, image: user.image || null }}
|
|
27
|
+
className="h-8 w-8"
|
|
28
|
+
/>
|
|
29
|
+
</DropdownMenuTrigger>
|
|
30
|
+
<DropdownMenuContent align="end">
|
|
31
|
+
<div className="flex items-center justify-start gap-2 p-2">
|
|
32
|
+
<div className="flex flex-col space-y-1 leading-none">
|
|
33
|
+
{user.name && <p className="font-medium">{user.name}</p>}
|
|
34
|
+
{user.email && (
|
|
35
|
+
<p className="w-[200px] truncate text-sm text-muted-foreground">
|
|
36
|
+
{user.email}
|
|
37
|
+
</p>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<DropdownMenuSeparator />
|
|
42
|
+
<DropdownMenuItem asChild>
|
|
43
|
+
<Link href="/settings">Settings</Link>
|
|
44
|
+
</DropdownMenuItem>
|
|
45
|
+
<DropdownMenuSeparator />
|
|
46
|
+
<DropdownMenuItem
|
|
47
|
+
className="cursor-pointer"
|
|
48
|
+
onSelect={event => {
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
signOut({
|
|
51
|
+
callbackUrl: `${window.location.origin}/login`
|
|
52
|
+
})
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
Sign out
|
|
56
|
+
</DropdownMenuItem>
|
|
57
|
+
</DropdownMenuContent>
|
|
58
|
+
</DropdownMenu>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { User } from '@prisma/client'
|
|
2
|
+
import { AvatarProps } from '@radix-ui/react-avatar'
|
|
3
|
+
import { FC } from 'react'
|
|
4
|
+
|
|
5
|
+
import { Icons } from '@/components/icons'
|
|
6
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
7
|
+
|
|
8
|
+
interface UserAvatarProps extends AvatarProps {
|
|
9
|
+
user: Pick<User, 'image' | 'name'>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const UserAvatar: FC<UserAvatarProps> = ({ user, ...props }) => {
|
|
13
|
+
return (
|
|
14
|
+
<Avatar {...props}>
|
|
15
|
+
{user.image ? (
|
|
16
|
+
<AvatarImage alt="Picture" src={user.image} />
|
|
17
|
+
) : (
|
|
18
|
+
<AvatarFallback>
|
|
19
|
+
<span className="sr-only">{user.name}</span>
|
|
20
|
+
<Icons.user className="h-4 w-4" />
|
|
21
|
+
</AvatarFallback>
|
|
22
|
+
)}
|
|
23
|
+
</Avatar>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createEnv } from '@t3-oss/env-nextjs'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const env = createEnv({
|
|
5
|
+
/**
|
|
6
|
+
* Specify your server-side environment variables schema here. This way you can ensure the app
|
|
7
|
+
* isn't built with invalid env vars.
|
|
8
|
+
*/
|
|
9
|
+
server: {
|
|
10
|
+
NODE_ENV: z.enum(['development', 'test', 'production']),
|
|
11
|
+
DATABASE_URL: z.string().url(),
|
|
12
|
+
NEXTAUTH_SECRET:
|
|
13
|
+
process.env.NODE_ENV === 'production'
|
|
14
|
+
? z.string().min(1)
|
|
15
|
+
: z.string().min(1).optional(),
|
|
16
|
+
NEXTAUTH_URL: z.preprocess(
|
|
17
|
+
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
|
|
18
|
+
// Since NextAuth.js automatically uses the VERCEL_URL if present.
|
|
19
|
+
str => process.env.VERCEL_URL ?? str,
|
|
20
|
+
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
|
|
21
|
+
process.env.VERCEL ? z.string().min(1) : z.string().url()
|
|
22
|
+
),
|
|
23
|
+
EMAIL_SERVER_HOST: z.string().min(1),
|
|
24
|
+
EMAIL_SERVER_PORT: z.number(),
|
|
25
|
+
EMAIL_SERVER_USER: z.string().min(1),
|
|
26
|
+
EMAIL_SERVER_PASSWORD: z.string().min(1),
|
|
27
|
+
EMAIL_FROM: z.string().email(),
|
|
28
|
+
GOOGLE_ID: z.string().min(1),
|
|
29
|
+
GOOGLE_SECRET: z.string().min(1),
|
|
30
|
+
GITHUB_ID: z.string().min(1),
|
|
31
|
+
GITHUB_SECRET: z.string().min(1)
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Specify your client-side environment variables schema here. This way you can ensure the app
|
|
36
|
+
* isn't built with invalid env vars. To expose them to the client, prefix them with
|
|
37
|
+
* `NEXT_PUBLIC_`.
|
|
38
|
+
*/
|
|
39
|
+
client: {
|
|
40
|
+
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
|
45
|
+
* middlewares) or client-side so we need to destruct manually.
|
|
46
|
+
*/
|
|
47
|
+
runtimeEnv: {
|
|
48
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
49
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
50
|
+
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
|
51
|
+
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
|
52
|
+
EMAIL_SERVER_HOST: process.env.EMAIL_SERVER_HOST,
|
|
53
|
+
EMAIL_SERVER_PORT: parseInt(process.env.EMAIL_SERVER_PORT || ''),
|
|
54
|
+
EMAIL_SERVER_USER: process.env.EMAIL_SERVER_USER,
|
|
55
|
+
EMAIL_SERVER_PASSWORD: process.env.EMAIL_SERVER_PASSWORD,
|
|
56
|
+
EMAIL_FROM: process.env.EMAIL_FROM,
|
|
57
|
+
GOOGLE_ID: process.env.GOOGLE_ID,
|
|
58
|
+
GOOGLE_SECRET: process.env.GOOGLE_SECRET,
|
|
59
|
+
GITHUB_ID: process.env.GITHUB_ID,
|
|
60
|
+
GITHUB_SECRET: process.env.GITHUB_SECRET
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
|
|
64
|
+
* This is especially useful for Docker builds.
|
|
65
|
+
*/
|
|
66
|
+
skipValidation: !!process.env.SKIP_ENV_VALIDATION
|
|
67
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
export function useDebounce<T>(value: T, delay?: number): T {
|
|
6
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
|
|
10
|
+
|
|
11
|
+
return () => {
|
|
12
|
+
clearTimeout(timer)
|
|
13
|
+
}
|
|
14
|
+
}, [value, delay])
|
|
15
|
+
|
|
16
|
+
return debouncedValue
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
// @see https://usehooks.com/useLockBodyScroll.
|
|
4
|
+
export function useLockBody() {
|
|
5
|
+
useLayoutEffect((): (() => void) => {
|
|
6
|
+
const originalStyle: string = window.getComputedStyle(
|
|
7
|
+
document.body
|
|
8
|
+
).overflow
|
|
9
|
+
document.body.style.overflow = 'hidden'
|
|
10
|
+
return () => (document.body.style.overflow = originalStyle)
|
|
11
|
+
}, [])
|
|
12
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
|
2
|
+
import { Permissions, UserType } from '@prisma/client'
|
|
3
|
+
import {
|
|
4
|
+
Session,
|
|
5
|
+
getServerSession,
|
|
6
|
+
type DefaultSession,
|
|
7
|
+
type NextAuthOptions
|
|
8
|
+
} from 'next-auth'
|
|
9
|
+
import EmailProvider from 'next-auth/providers/email'
|
|
10
|
+
import GitHubProvider from 'next-auth/providers/github'
|
|
11
|
+
import GoogleProvider from 'next-auth/providers/google'
|
|
12
|
+
|
|
13
|
+
import { env } from '@/env.mjs'
|
|
14
|
+
import { prisma } from '@/lib/db'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
|
|
18
|
+
* object and keep type safety.
|
|
19
|
+
*
|
|
20
|
+
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
|
|
21
|
+
*/
|
|
22
|
+
declare module 'next-auth' {
|
|
23
|
+
interface Session extends DefaultSession {
|
|
24
|
+
user: {
|
|
25
|
+
id: string
|
|
26
|
+
type: UserType
|
|
27
|
+
roleId?: string | null
|
|
28
|
+
} & DefaultSession['user']
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface User {
|
|
32
|
+
type: UserType
|
|
33
|
+
roleId?: string | null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
|
|
39
|
+
*
|
|
40
|
+
* @see https://next-auth.js.org/configuration/options
|
|
41
|
+
*/
|
|
42
|
+
export const authOptions: NextAuthOptions = {
|
|
43
|
+
pages: {
|
|
44
|
+
signIn: '/login'
|
|
45
|
+
},
|
|
46
|
+
callbacks: {
|
|
47
|
+
session({ session, user }) {
|
|
48
|
+
return {
|
|
49
|
+
...session,
|
|
50
|
+
user: {
|
|
51
|
+
...session.user,
|
|
52
|
+
id: user.id,
|
|
53
|
+
type: user.type,
|
|
54
|
+
roleId: user.roleId
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
adapter: PrismaAdapter(prisma),
|
|
60
|
+
providers: [
|
|
61
|
+
EmailProvider({
|
|
62
|
+
server: {
|
|
63
|
+
host: env.EMAIL_SERVER_HOST,
|
|
64
|
+
port: env.EMAIL_SERVER_PORT,
|
|
65
|
+
auth: {
|
|
66
|
+
user: env.EMAIL_SERVER_USER,
|
|
67
|
+
pass: env.EMAIL_SERVER_PASSWORD
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
from: env.EMAIL_FROM
|
|
71
|
+
}),
|
|
72
|
+
GoogleProvider({
|
|
73
|
+
clientId: env.GOOGLE_ID,
|
|
74
|
+
clientSecret: env.GOOGLE_SECRET
|
|
75
|
+
}),
|
|
76
|
+
GitHubProvider({
|
|
77
|
+
clientId: env.GITHUB_ID,
|
|
78
|
+
clientSecret: env.GITHUB_SECRET
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
|
|
85
|
+
*
|
|
86
|
+
* @see https://next-auth.js.org/configuration/nextjs
|
|
87
|
+
*/
|
|
88
|
+
export const getAuthSession = () => getServerSession(authOptions)
|
|
89
|
+
|
|
90
|
+
export const authGuard = async (
|
|
91
|
+
types?: UserType | UserType[],
|
|
92
|
+
permission?: Permissions,
|
|
93
|
+
session?: Session
|
|
94
|
+
) => {
|
|
95
|
+
if (!session) {
|
|
96
|
+
const tempSession = await getAuthSession()
|
|
97
|
+
if (!tempSession) return false
|
|
98
|
+
session = tempSession
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (session.user.type === 'Root') return session
|
|
102
|
+
|
|
103
|
+
if (types) {
|
|
104
|
+
if (Array.isArray(types)) {
|
|
105
|
+
if (!types.includes(session.user.type)) return false
|
|
106
|
+
} else {
|
|
107
|
+
if (types !== session.user.type) return false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!session.user.roleId || !permission) return false
|
|
112
|
+
|
|
113
|
+
const c = await prisma.rolePermission.count({
|
|
114
|
+
where: {
|
|
115
|
+
roleId: session.user.roleId,
|
|
116
|
+
permission
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
if (!c) return false
|
|
120
|
+
|
|
121
|
+
return session
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const checkForPermission = async (
|
|
125
|
+
permission: Permissions,
|
|
126
|
+
session?: Session
|
|
127
|
+
) => {
|
|
128
|
+
if (!session) {
|
|
129
|
+
const tempSession = await getAuthSession()
|
|
130
|
+
if (!tempSession) return false
|
|
131
|
+
session = tempSession
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (session.user.type === 'Root') return true
|
|
135
|
+
if (!session.user.roleId) return false
|
|
136
|
+
|
|
137
|
+
const c = await prisma.rolePermission.count({
|
|
138
|
+
where: {
|
|
139
|
+
roleId: session.user.roleId,
|
|
140
|
+
permission
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return !!c
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const getAuthSessionWithPermissions = async () => {
|
|
148
|
+
const session = await getAuthSession()
|
|
149
|
+
|
|
150
|
+
if (session && session.user) {
|
|
151
|
+
if (session.user.type === 'Root') {
|
|
152
|
+
return {
|
|
153
|
+
session,
|
|
154
|
+
permissions: ['*']
|
|
155
|
+
}
|
|
156
|
+
} else if (session.user.roleId) {
|
|
157
|
+
const permissions = await prisma.rolePermission.findMany({
|
|
158
|
+
where: {
|
|
159
|
+
roleId: session.user.roleId
|
|
160
|
+
},
|
|
161
|
+
select: {
|
|
162
|
+
permission: true
|
|
163
|
+
},
|
|
164
|
+
orderBy: {
|
|
165
|
+
permission: 'asc'
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
return {
|
|
169
|
+
session,
|
|
170
|
+
permissions: permissions.map(p => p.permission)
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
return {
|
|
174
|
+
session,
|
|
175
|
+
permissions: []
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
session
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client'
|
|
2
|
+
|
|
3
|
+
import { env } from '@/env.mjs'
|
|
4
|
+
|
|
5
|
+
const globalForPrisma = globalThis as unknown as {
|
|
6
|
+
prisma: PrismaClient | undefined
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const prisma =
|
|
10
|
+
globalForPrisma.prisma ||
|
|
11
|
+
new PrismaClient({
|
|
12
|
+
log:
|
|
13
|
+
env.NODE_ENV === 'development'
|
|
14
|
+
? [
|
|
15
|
+
// 'info', 'query', 'error', 'warn'
|
|
16
|
+
]
|
|
17
|
+
: ['error']
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
3
|
+
|
|
4
|
+
dayjs.extend(relativeTime)
|
|
5
|
+
|
|
6
|
+
export const formatDate = (date: Date) => dayjs(date).format('DD/MM/YYYY')
|
|
7
|
+
|
|
8
|
+
export const formatDateTime = (date: Date) =>
|
|
9
|
+
dayjs(date).format('DD/MM/YYYY hh:mm:ss A')
|
|
10
|
+
|
|
11
|
+
export const timesAgo = (date: Date) => dayjs(date).fromNow()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Config } from 'tailwindcss'
|
|
2
|
+
|
|
3
|
+
const tailwindConfig: Config = {
|
|
4
|
+
darkMode: ['class'],
|
|
5
|
+
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
|
6
|
+
theme: {
|
|
7
|
+
container: {
|
|
8
|
+
center: true,
|
|
9
|
+
padding: '2rem',
|
|
10
|
+
screens: {
|
|
11
|
+
'2xl': '1400px'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
extend: {
|
|
15
|
+
backgroundImage: {
|
|
16
|
+
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
|
17
|
+
'gradient-conic':
|
|
18
|
+
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
|
|
19
|
+
},
|
|
20
|
+
colors: {
|
|
21
|
+
border: 'hsl(var(--border))',
|
|
22
|
+
input: 'hsl(var(--input))',
|
|
23
|
+
ring: 'hsl(var(--ring))',
|
|
24
|
+
background: 'hsl(var(--background))',
|
|
25
|
+
foreground: 'hsl(var(--foreground))',
|
|
26
|
+
primary: {
|
|
27
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
28
|
+
foreground: 'hsl(var(--primary-foreground))'
|
|
29
|
+
},
|
|
30
|
+
secondary: {
|
|
31
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
32
|
+
foreground: 'hsl(var(--secondary-foreground))'
|
|
33
|
+
},
|
|
34
|
+
destructive: {
|
|
35
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
36
|
+
foreground: 'hsl(var(--destructive-foreground))'
|
|
37
|
+
},
|
|
38
|
+
muted: {
|
|
39
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
40
|
+
foreground: 'hsl(var(--muted-foreground))'
|
|
41
|
+
},
|
|
42
|
+
accent: {
|
|
43
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
44
|
+
foreground: 'hsl(var(--accent-foreground))'
|
|
45
|
+
},
|
|
46
|
+
popover: {
|
|
47
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
48
|
+
foreground: 'hsl(var(--popover-foreground))'
|
|
49
|
+
},
|
|
50
|
+
card: {
|
|
51
|
+
DEFAULT: 'hsl(var(--card))',
|
|
52
|
+
foreground: 'hsl(var(--card-foreground))'
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
borderRadius: {
|
|
56
|
+
lg: 'var(--radius)',
|
|
57
|
+
md: 'calc(var(--radius) - 2px)',
|
|
58
|
+
sm: 'calc(var(--radius) - 4px)'
|
|
59
|
+
},
|
|
60
|
+
keyframes: {
|
|
61
|
+
'accordion-down': {
|
|
62
|
+
from: { height: '0' },
|
|
63
|
+
to: { height: 'var(--radix-accordion-content-height)' }
|
|
64
|
+
},
|
|
65
|
+
'accordion-up': {
|
|
66
|
+
from: { height: 'var(--radix-accordion-content-height)' },
|
|
67
|
+
to: { height: '0' }
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
animation: {
|
|
71
|
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
72
|
+
'accordion-up': 'accordion-up 0.2s ease-out'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
plugins: [require('tailwindcss-animate')]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = tailwindConfig
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"checkJs": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"strictNullChecks": true,
|
|
11
|
+
"strictFunctionTypes": true,
|
|
12
|
+
"noImplicitThis": true,
|
|
13
|
+
"noUncheckedIndexedAccess": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noImplicitReturns": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"noEmit": true,
|
|
20
|
+
"esModuleInterop": true,
|
|
21
|
+
"module": "ESNext",
|
|
22
|
+
"moduleResolution": "Bundler",
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
"isolatedModules": true,
|
|
25
|
+
"jsx": "preserve",
|
|
26
|
+
"incremental": true,
|
|
27
|
+
"plugins": [
|
|
28
|
+
{
|
|
29
|
+
"name": "next"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"paths": {
|
|
33
|
+
"@/*": ["./src/*"]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"include": [
|
|
37
|
+
".eslintrc.cjs",
|
|
38
|
+
"next-env.d.ts",
|
|
39
|
+
"**/*.ts",
|
|
40
|
+
"**/*.tsx",
|
|
41
|
+
"**/*.cjs",
|
|
42
|
+
"**/*.mjs",
|
|
43
|
+
".next/types/**/*.ts"
|
|
44
|
+
],
|
|
45
|
+
"exclude": ["node_modules"]
|
|
46
|
+
}
|