create-z3 0.0.47 → 0.0.49
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/dist/index.js +39 -38
- package/package.json +11 -11
- package/templates/nextjs/convex/auth/adapter/index.ts +7 -22
- package/templates/nextjs/convex/auth/index.ts +1 -1
- package/templates/nextjs/convex/auth/plugins/index.ts +6 -2
- package/templates/nextjs/convex/auth/sessions.ts +1 -1
- package/templates/nextjs/convex/auth.config.ts +7 -0
- package/templates/nextjs/convex/schema.ts +30 -2
- package/templates/nextjs/package.json +3 -2
- package/templates/nextjs/src/app/(frontend)/layout.tsx +21 -27
- package/templates/nextjs/src/auth/client.tsx +2 -4
- package/templates/nextjs/src/auth/permissions.ts +2 -2
- package/templates/nextjs/src/components/component-example.tsx +44 -492
- package/templates/nextjs/src/db/constants/index.ts +1 -0
- package/templates/tanstack-start/convex/auth/adapter/index.ts +7 -22
- package/templates/tanstack-start/convex/auth/index.ts +1 -1
- package/templates/tanstack-start/convex/auth/plugins/index.ts +5 -1
- package/templates/tanstack-start/convex/auth/sessions.ts +1 -1
- package/templates/tanstack-start/convex/auth.config.ts +7 -0
- package/templates/tanstack-start/convex/schema.ts +37 -2
- package/templates/tanstack-start/package.json +3 -2
- package/templates/tanstack-start/src/components/component-example.tsx +41 -460
- package/templates/tanstack-start/src/db/constants/index.ts +1 -0
- package/templates/tanstack-start/src/lib/auth/client.ts +2 -1
- package/templates/tanstack-start/src/lib/auth/permissions.ts +2 -2
- package/templates/tanstack-start/src/routes/auth/$authView.tsx +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Isaiah
|
|
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/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ async function isDirectoryEmpty(dirPath) {
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
function resolveProjectName(input2,
|
|
40
|
+
function resolveProjectName(input2, _cwd) {
|
|
41
41
|
return input2;
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -1730,26 +1730,28 @@ function getAdditionalProviders() {
|
|
|
1730
1730
|
|
|
1731
1731
|
// src/installers/string-utils.ts
|
|
1732
1732
|
import fs3 from "fs-extra";
|
|
1733
|
-
var DEFAULT_THEME =
|
|
1734
|
-
--
|
|
1735
|
-
--
|
|
1736
|
-
--card
|
|
1737
|
-
--
|
|
1738
|
-
--popover
|
|
1739
|
-
--
|
|
1740
|
-
--primary
|
|
1741
|
-
--
|
|
1742
|
-
--secondary
|
|
1743
|
-
--
|
|
1744
|
-
--muted
|
|
1745
|
-
--
|
|
1746
|
-
--accent
|
|
1747
|
-
--
|
|
1748
|
-
--destructive
|
|
1749
|
-
--
|
|
1750
|
-
--
|
|
1751
|
-
--
|
|
1752
|
-
--
|
|
1733
|
+
var DEFAULT_THEME = `:root {
|
|
1734
|
+
--background: 100% 0.000 0;
|
|
1735
|
+
--foreground: 3.9% 0.006 240;
|
|
1736
|
+
--card: 100% 0.000 0;
|
|
1737
|
+
--card-foreground: 3.9% 0.006 240;
|
|
1738
|
+
--popover: 100% 0.000 0;
|
|
1739
|
+
--popover-foreground: 3.9% 0.006 240;
|
|
1740
|
+
--primary: 10% 0.003 240;
|
|
1741
|
+
--primary-foreground: 98% 0.000 0;
|
|
1742
|
+
--secondary: 95.9% 0.002 240;
|
|
1743
|
+
--secondary-foreground: 10% 0.003 240;
|
|
1744
|
+
--muted: 95.9% 0.002 240;
|
|
1745
|
+
--muted-foreground: 46.1% 0.002 240;
|
|
1746
|
+
--accent: 95.9% 0.002 240;
|
|
1747
|
+
--accent-foreground: 10% 0.003 240;
|
|
1748
|
+
--destructive: 60.2% 0.168 0;
|
|
1749
|
+
--destructive-foreground: 98% 0.000 0;
|
|
1750
|
+
--border: 90% 0.003 240;
|
|
1751
|
+
--input: 90% 0.003 240;
|
|
1752
|
+
--ring: 10% 0.003 240;
|
|
1753
|
+
--radius: 0.5rem;
|
|
1754
|
+
}`;
|
|
1753
1755
|
function detectIndentation(line) {
|
|
1754
1756
|
const match = line.match(/^(\s*)/);
|
|
1755
1757
|
return match ? match[1] : "";
|
|
@@ -1798,9 +1800,11 @@ function generateCredentialsValue(enabled) {
|
|
|
1798
1800
|
}
|
|
1799
1801
|
function generateAuthProvidersBlock(oauthProviders, emailPasswordEnabled) {
|
|
1800
1802
|
const parts = [];
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
+
if (emailPasswordEnabled) {
|
|
1804
|
+
parts.push(`emailAndPassword: {
|
|
1805
|
+
enabled: true
|
|
1803
1806
|
},`);
|
|
1807
|
+
}
|
|
1804
1808
|
if (oauthProviders.length > 0) {
|
|
1805
1809
|
const providersObject = oauthProviders.map((providerId) => {
|
|
1806
1810
|
const provider = getProvider(providerId);
|
|
@@ -2300,13 +2304,12 @@ var TanStackInstaller = class extends FrameworkInstaller {
|
|
|
2300
2304
|
async updateEnvExample(selectedProviders) {
|
|
2301
2305
|
const envFilePath = join2(this.targetPath, ".env.example");
|
|
2302
2306
|
const envVarsBlock = generateEnvVarsBlock(selectedProviders, "tanstack");
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
}
|
|
2307
|
+
await replacePlaceholder(
|
|
2308
|
+
envFilePath,
|
|
2309
|
+
"# {{ENV_OAUTH_VARS}}",
|
|
2310
|
+
envVarsBlock,
|
|
2311
|
+
{ graceful: true }
|
|
2312
|
+
);
|
|
2310
2313
|
}
|
|
2311
2314
|
/**
|
|
2312
2315
|
* Update README with OAuth provider setup guides
|
|
@@ -2319,14 +2322,12 @@ var TanStackInstaller = class extends FrameworkInstaller {
|
|
|
2319
2322
|
async updateReadme(selectedProviders) {
|
|
2320
2323
|
const readmeFilePath = join2(this.targetPath, "README.md");
|
|
2321
2324
|
const readmeSection = generateReadmeSection(selectedProviders);
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
);
|
|
2329
|
-
}
|
|
2325
|
+
await replacePlaceholder(
|
|
2326
|
+
readmeFilePath,
|
|
2327
|
+
"<!-- {{OAUTH_SETUP_GUIDE}} -->",
|
|
2328
|
+
readmeSection,
|
|
2329
|
+
{ graceful: true }
|
|
2330
|
+
);
|
|
2330
2331
|
}
|
|
2331
2332
|
/**
|
|
2332
2333
|
* Apply TweakCN theme to global CSS file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-z3",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for scaffolding Z3 Stack applications (TanStack/Next.js + Convex + Better Auth)",
|
|
6
6
|
"bin": {
|
|
@@ -10,13 +10,6 @@
|
|
|
10
10
|
"dist",
|
|
11
11
|
"templates"
|
|
12
12
|
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
15
|
-
"dev": "tsup src/index.ts --format esm --watch",
|
|
16
|
-
"typecheck": "tsc --noEmit",
|
|
17
|
-
"test": "vitest run",
|
|
18
|
-
"test:watch": "vitest"
|
|
19
|
-
},
|
|
20
13
|
"dependencies": {
|
|
21
14
|
"commander": "^12.0.0",
|
|
22
15
|
"@inquirer/prompts": "^7.2.0",
|
|
@@ -55,7 +48,14 @@
|
|
|
55
48
|
],
|
|
56
49
|
"repository": {
|
|
57
50
|
"type": "git",
|
|
58
|
-
"url": "https://github.com/
|
|
51
|
+
"url": "https://github.com/ianyimi/create-z3-app"
|
|
59
52
|
},
|
|
60
|
-
"license": "MIT"
|
|
61
|
-
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
56
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -50,6 +50,12 @@ export const convexAdapter = <DataModel extends GenericDataModel>(
|
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
52
|
id: "convex",
|
|
53
|
+
// Tell the convex() plugin this context supports mutations. It checks
|
|
54
|
+
// ctx.context.adapter.options?.isRunMutationCtx — if falsy it replaces all
|
|
55
|
+
// adapter writes with silent no-ops. HTTP actions always have runMutation.
|
|
56
|
+
options: {
|
|
57
|
+
isRunMutationCtx: "runMutation" in ctx,
|
|
58
|
+
},
|
|
53
59
|
|
|
54
60
|
create: async ({ data, model, select }): Promise<any> => {
|
|
55
61
|
return await ctx.runMutation(internal.auth.db.dbCreate, {
|
|
@@ -189,34 +195,12 @@ export const convexAdapter = <DataModel extends GenericDataModel>(
|
|
|
189
195
|
if (data && fieldAttributes.type === "date") {
|
|
190
196
|
return new Date(data).getTime();
|
|
191
197
|
}
|
|
192
|
-
// Handle array fields - Better Auth may send single values or arrays
|
|
193
|
-
if ((fieldAttributes.type as string)?.endsWith("[]")) {
|
|
194
|
-
// If already an array, return as-is
|
|
195
|
-
if (Array.isArray(data)) {
|
|
196
|
-
return data
|
|
197
|
-
}
|
|
198
|
-
// If it's a string that looks like JSON array, parse it
|
|
199
|
-
if (typeof data === "string") {
|
|
200
|
-
try {
|
|
201
|
-
const parsed = JSON.parse(data)
|
|
202
|
-
return Array.isArray(parsed) ? parsed : [data]
|
|
203
|
-
} catch {
|
|
204
|
-
// If parsing fails, wrap the string in an array
|
|
205
|
-
return [data]
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// For any other value type, wrap in array
|
|
209
|
-
if (data !== null && data !== undefined) {
|
|
210
|
-
return [data]
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
198
|
return data;
|
|
214
199
|
},
|
|
215
200
|
customTransformOutput: ({ data, fieldAttributes }) => {
|
|
216
201
|
if (data && fieldAttributes.type === "date") {
|
|
217
202
|
return new Date(data).getTime();
|
|
218
203
|
}
|
|
219
|
-
// Arrays are stored natively in Convex, no transformation needed for output
|
|
220
204
|
return data;
|
|
221
205
|
},
|
|
222
206
|
debugLogs: config.debugLogs ?? false,
|
|
@@ -230,6 +214,7 @@ export const convexAdapter = <DataModel extends GenericDataModel>(
|
|
|
230
214
|
supportsDates: false,
|
|
231
215
|
supportsJSON: true,
|
|
232
216
|
supportsNumericIds: false,
|
|
217
|
+
supportsArrays: true,
|
|
233
218
|
transaction: false,
|
|
234
219
|
usePlural: false,
|
|
235
220
|
},
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { nextCookies } from "better-auth/next-js"
|
|
2
|
-
import { admin
|
|
2
|
+
import { admin } from "better-auth/plugins"
|
|
3
|
+
import { apiKey } from "@better-auth/api-key"
|
|
4
|
+
import { convex } from "@convex-dev/better-auth/plugins"
|
|
3
5
|
import { USER_ROLES } from "~/db/constants"
|
|
6
|
+
import authConfig from "@convex/auth.config"
|
|
4
7
|
|
|
5
8
|
const plugins = [
|
|
6
9
|
admin({
|
|
7
10
|
adminRoles: [USER_ROLES.admin],
|
|
8
|
-
defaultRole: USER_ROLES.user
|
|
11
|
+
defaultRole: USER_ROLES.user,
|
|
9
12
|
}),
|
|
10
13
|
apiKey(),
|
|
11
14
|
nextCookies(),
|
|
15
|
+
convex({ authConfig }),
|
|
12
16
|
]
|
|
13
17
|
|
|
14
18
|
export default plugins
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineSchema, defineTable } from "convex/server";
|
|
2
2
|
import { v } from "convex/values"
|
|
3
3
|
|
|
4
|
-
import { TABLE_SLUG_ACCOUNTS, TABLE_SLUG_JWKS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS, TABLE_SLUG_VERIFICATIONS } from "~/db/constants";
|
|
4
|
+
import { TABLE_SLUG_ACCOUNTS, TABLE_SLUG_API_KEYS, TABLE_SLUG_JWKS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS, TABLE_SLUG_VERIFICATIONS } from "~/db/constants";
|
|
5
5
|
|
|
6
6
|
export default defineSchema({
|
|
7
7
|
// Better Auth component tables (type definitions only - actual tables are in component)
|
|
@@ -19,7 +19,8 @@ export default defineSchema({
|
|
|
19
19
|
isAnonymous: v.optional(v.union(v.null(), v.boolean())),
|
|
20
20
|
phoneNumber: v.optional(v.union(v.null(), v.string())),
|
|
21
21
|
phoneNumberVerified: v.optional(v.union(v.null(), v.boolean())),
|
|
22
|
-
role: v.
|
|
22
|
+
role: v.optional(v.string()), // admin plugin — single string in BA 1.5
|
|
23
|
+
roles: v.array(v.string()), // our multi-role field via additionalFields
|
|
23
24
|
twoFactorEnabled: v.optional(v.union(v.null(), v.boolean())),
|
|
24
25
|
updatedAt: v.number(),
|
|
25
26
|
userId: v.optional(v.union(v.null(), v.string()))
|
|
@@ -70,4 +71,31 @@ export default defineSchema({
|
|
|
70
71
|
privateKey: v.optional(v.string()),
|
|
71
72
|
publicKey: v.string(),
|
|
72
73
|
}),
|
|
74
|
+
|
|
75
|
+
// Better Auth 1.5 — apiKey plugin (@better-auth/api-key)
|
|
76
|
+
[TABLE_SLUG_API_KEYS]: defineTable({
|
|
77
|
+
configId: v.string(),
|
|
78
|
+
name: v.optional(v.string()),
|
|
79
|
+
start: v.optional(v.string()),
|
|
80
|
+
referenceId: v.string(),
|
|
81
|
+
prefix: v.optional(v.string()),
|
|
82
|
+
key: v.string(),
|
|
83
|
+
refillInterval: v.optional(v.number()),
|
|
84
|
+
refillAmount: v.optional(v.number()),
|
|
85
|
+
lastRefillAt: v.optional(v.number()),
|
|
86
|
+
enabled: v.optional(v.boolean()),
|
|
87
|
+
rateLimitEnabled: v.optional(v.boolean()),
|
|
88
|
+
rateLimitTimeWindow: v.optional(v.number()),
|
|
89
|
+
rateLimitMax: v.optional(v.number()),
|
|
90
|
+
requestCount: v.optional(v.number()),
|
|
91
|
+
remaining: v.optional(v.number()),
|
|
92
|
+
lastRequest: v.optional(v.number()),
|
|
93
|
+
expiresAt: v.optional(v.number()),
|
|
94
|
+
createdAt: v.number(),
|
|
95
|
+
updatedAt: v.number(),
|
|
96
|
+
permissions: v.optional(v.string()),
|
|
97
|
+
metadata: v.optional(v.string()),
|
|
98
|
+
})
|
|
99
|
+
.index("by_referenceId", ["referenceId"])
|
|
100
|
+
.index("by_key", ["key"]),
|
|
73
101
|
})
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@base-ui/react": "^1.1.0",
|
|
16
|
-
"@
|
|
16
|
+
"@better-auth/api-key": "^1.0.0",
|
|
17
|
+
"@convex-dev/better-auth": "^0.11.0",
|
|
17
18
|
"@convex-dev/react-query": "^0.1.0",
|
|
18
19
|
"@daveyplate/better-auth-ui": "^3.3.15",
|
|
19
20
|
"@t3-oss/env-nextjs": "^0.13.10",
|
|
20
21
|
"@tanstack/react-form": "^1.27.7",
|
|
21
22
|
"@tanstack/react-query": "^5.90.17",
|
|
22
|
-
"better-auth": "^1.
|
|
23
|
+
"better-auth": "^1.5.0",
|
|
23
24
|
"class-variance-authority": "^0.7.1",
|
|
24
25
|
"clsx": "^2.1.1",
|
|
25
26
|
"convex": "^1.31.5",
|
|
@@ -1,46 +1,40 @@
|
|
|
1
|
-
import type { Metadata } from "next"
|
|
2
|
-
import { Geist, Geist_Mono, Inter } from "next/font/google";
|
|
3
|
-
import "./globals.css";
|
|
4
|
-
import ServerProviders from "~/components/providers/server";
|
|
5
|
-
import ClientProviders from "~/components/providers/client";
|
|
6
|
-
import Head from "next/head";
|
|
1
|
+
import type { Metadata } from "next"
|
|
7
2
|
|
|
8
|
-
|
|
3
|
+
import { Geist, Geist_Mono, Inter } from "next/font/google"
|
|
4
|
+
|
|
5
|
+
import "./globals.css"
|
|
6
|
+
|
|
7
|
+
import ClientProviders from "~/components/providers/client"
|
|
8
|
+
import ServerProviders from "~/components/providers/server"
|
|
9
|
+
|
|
10
|
+
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" })
|
|
9
11
|
|
|
10
12
|
const geistSans = Geist({
|
|
11
|
-
variable: "--font-geist-sans",
|
|
12
13
|
subsets: ["latin"],
|
|
13
|
-
|
|
14
|
+
variable: "--font-geist-sans",
|
|
15
|
+
})
|
|
14
16
|
|
|
15
17
|
const geistMono = Geist_Mono({
|
|
16
|
-
variable: "--font-geist-mono",
|
|
17
18
|
subsets: ["latin"],
|
|
18
|
-
|
|
19
|
+
variable: "--font-geist-mono",
|
|
20
|
+
})
|
|
19
21
|
|
|
20
22
|
export const metadata: Metadata = {
|
|
21
|
-
title: "Create Next App",
|
|
22
23
|
description: "Generated by create next app",
|
|
23
|
-
}
|
|
24
|
+
icons: { icon: "/favicons/favicon.ico" },
|
|
25
|
+
title: "Create Next App",
|
|
26
|
+
}
|
|
24
27
|
|
|
25
28
|
export default function RootLayout({
|
|
26
29
|
auth,
|
|
27
30
|
children,
|
|
28
31
|
}: Readonly<{
|
|
29
|
-
auth: React.ReactNode
|
|
30
|
-
children: React.ReactNode
|
|
32
|
+
auth: React.ReactNode
|
|
33
|
+
children: React.ReactNode
|
|
31
34
|
}>) {
|
|
32
35
|
return (
|
|
33
|
-
<html
|
|
34
|
-
<
|
|
35
|
-
<link
|
|
36
|
-
href="/favicons/favicon.ico"
|
|
37
|
-
rel="shortcut icon"
|
|
38
|
-
type="image/x-icon"
|
|
39
|
-
/>
|
|
40
|
-
</Head>
|
|
41
|
-
<body
|
|
42
|
-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
43
|
-
>
|
|
36
|
+
<html className={inter.variable} lang="en" suppressHydrationWarning>
|
|
37
|
+
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
|
44
38
|
<ServerProviders>
|
|
45
39
|
<ClientProviders>
|
|
46
40
|
{children}
|
|
@@ -49,5 +43,5 @@ export default function RootLayout({
|
|
|
49
43
|
</ServerProviders>
|
|
50
44
|
</body>
|
|
51
45
|
</html>
|
|
52
|
-
)
|
|
46
|
+
)
|
|
53
47
|
}
|
|
@@ -2,10 +2,8 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { convexClient } from "@convex-dev/better-auth/client/plugins";
|
|
4
4
|
import { AuthUIProvider } from "@daveyplate/better-auth-ui";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
apiKeyClient,
|
|
8
|
-
} from "better-auth/client/plugins";
|
|
5
|
+
import { apiKeyClient } from "@better-auth/api-key/client";
|
|
6
|
+
import { adminClient } from "better-auth/client/plugins";
|
|
9
7
|
import { createAuthClient } from 'better-auth/react'
|
|
10
8
|
import Link from "next/link";
|
|
11
9
|
import { useRouter } from "next/navigation";
|
|
@@ -80,9 +80,9 @@ export function hasPermission<Resource extends keyof Permissions>({
|
|
|
80
80
|
resource: Resource;
|
|
81
81
|
user?: null | User;
|
|
82
82
|
}): boolean {
|
|
83
|
-
if (!user?.
|
|
83
|
+
if (!user?.roles) { return false; }
|
|
84
84
|
|
|
85
|
-
return user.
|
|
85
|
+
return user.roles.some((role) => {
|
|
86
86
|
const permission = (ROLES as RolesWithPermissions)[role as UserRole][resource]?.[action]
|
|
87
87
|
|
|
88
88
|
if (!permission) { return false }
|