@xk2800/nextjs-template 0.6.1-beta.3 → 0.7.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/README.md +56 -321
- package/components/dashboard/admin/adminUsersTable.tsx +36 -2
- package/components/dashboard/admin/feedbackTestForm.tsx +55 -0
- package/components/dashboard/admin/systemSettingsForm.tsx +28 -0
- package/components/dashboard/appSidebar.tsx +4 -4
- package/components/dashboard/deleteAccountCard.tsx +137 -0
- package/components/dashboard/errorFeedback.tsx +65 -0
- package/components/dashboard/feedbackDialog.tsx +160 -0
- package/components/dashboard/installAppCard.tsx +187 -0
- package/components/site/cookie-banner.tsx +57 -0
- package/components/ui/button.tsx +1 -1
- package/components/ui/file-upload.tsx +178 -0
- package/components/ui/input.tsx +1 -1
- package/components/ui/separator.tsx +1 -1
- package/components/ui/sheet.tsx +1 -1
- package/components/ui/sidebar.tsx +8 -8
- package/components/ui/skeleton.tsx +1 -1
- package/components/ui/tooltip.tsx +1 -1
- package/dist/activity/logger.js +21 -1
- package/dist/activity/logger.js.map +1 -1
- package/dist/activity/logger.mjs +4 -4
- package/dist/activity/queries.d.mts +1 -1
- package/dist/activity/queries.d.ts +1 -1
- package/dist/activity/queries.js +21 -1
- package/dist/activity/queries.js.map +1 -1
- package/dist/activity/queries.mjs +3 -3
- package/dist/admin/queries.js +21 -1
- package/dist/admin/queries.js.map +1 -1
- package/dist/admin/queries.mjs +3 -3
- package/dist/api/feedback.d.mts +23 -0
- package/dist/api/feedback.d.ts +23 -0
- package/dist/api/feedback.js +1224 -0
- package/dist/api/feedback.js.map +1 -0
- package/dist/api/feedback.mjs +136 -0
- package/dist/api/feedback.mjs.map +1 -0
- package/dist/auth/helpers.d.mts +3 -3
- package/dist/auth/helpers.d.ts +3 -3
- package/dist/auth/helpers.js +45 -4
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/helpers.mjs +7 -7
- package/dist/auth/helpers.mjs.map +1 -1
- package/dist/auth/index.d.mts +8 -2
- package/dist/auth/index.d.ts +8 -2
- package/dist/auth/index.js +44 -3
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +6 -6
- package/dist/auth-client/index.d.mts +16 -16
- package/dist/auth-client/index.d.ts +16 -16
- package/dist/{chunk-IF3JZRSZ.mjs → chunk-3MGEAGHU.mjs} +6 -5
- package/dist/chunk-3MGEAGHU.mjs.map +1 -0
- package/dist/chunk-KAEA35OC.mjs +76 -0
- package/dist/chunk-KAEA35OC.mjs.map +1 -0
- package/dist/{chunk-GTPTVNY2.mjs → chunk-TMJYAOC2.mjs} +10 -2
- package/dist/chunk-TMJYAOC2.mjs.map +1 -0
- package/dist/{chunk-OHAUKE2Q.mjs → chunk-VQ5B4MA7.mjs} +4 -4
- package/dist/{chunk-OHAUKE2Q.mjs.map → chunk-VQ5B4MA7.mjs.map} +1 -1
- package/dist/{chunk-5PYDAD4P.mjs → chunk-WZDAMCUX.mjs} +4 -4
- package/dist/{chunk-5PYDAD4P.mjs.map → chunk-WZDAMCUX.mjs.map} +1 -1
- package/dist/{chunk-V72CFFU2.mjs → chunk-XWVAJXAG.mjs} +31 -11
- package/dist/chunk-XWVAJXAG.mjs.map +1 -0
- package/dist/{chunk-4B7MJOLH.mjs → chunk-ZQCALTKB.mjs} +17 -5
- package/dist/chunk-ZQCALTKB.mjs.map +1 -0
- package/dist/config/env.d.mts +3 -0
- package/dist/config/env.d.ts +3 -0
- package/dist/config/env.js +8 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/env.mjs +1 -1
- package/dist/db/index.d.mts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +21 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/index.mjs +3 -3
- package/dist/db/schema.d.mts +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.js +13 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/db/schema.mjs +1 -1
- package/dist/errors.d.mts +29 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +365 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.mjs +7 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/{schema-BghJ8N4X.d.mts → schema-BNSBWNjK.d.mts} +99 -6
- package/dist/{schema-BghJ8N4X.d.ts → schema-BNSBWNjK.d.ts} +99 -6
- package/dist/sessions/queries.js +21 -1
- package/dist/sessions/queries.js.map +1 -1
- package/dist/sessions/queries.mjs +3 -3
- package/dist/settings/queries.d.mts +2 -2
- package/dist/settings/queries.d.ts +2 -2
- package/dist/settings/queries.js +22 -1
- package/dist/settings/queries.js.map +1 -1
- package/dist/settings/queries.mjs +4 -4
- package/dist/users/queries.d.mts +3 -3
- package/dist/users/queries.d.ts +3 -3
- package/dist/users/queries.js +21 -1
- package/dist/users/queries.js.map +1 -1
- package/dist/users/queries.mjs +3 -3
- package/hooks/use-mobile.ts +19 -0
- package/lib/device-fingerprint.ts +2 -3
- package/package.json +35 -32
- package/dist/chunk-4B7MJOLH.mjs.map +0 -1
- package/dist/chunk-GTPTVNY2.mjs.map +0 -1
- package/dist/chunk-IF3JZRSZ.mjs.map +0 -1
- package/dist/chunk-V72CFFU2.mjs.map +0 -1
package/dist/auth/index.d.mts
CHANGED
|
@@ -47,6 +47,9 @@ declare function createAuth(overrides?: AuthOverrides): better_auth.Auth<{
|
|
|
47
47
|
input: false;
|
|
48
48
|
};
|
|
49
49
|
};
|
|
50
|
+
deleteUser: {
|
|
51
|
+
enabled: true;
|
|
52
|
+
};
|
|
50
53
|
};
|
|
51
54
|
advanced: {
|
|
52
55
|
database: {
|
|
@@ -888,7 +891,7 @@ declare function createAuth(overrides?: AuthOverrides): better_auth.Auth<{
|
|
|
888
891
|
$Infer: {
|
|
889
892
|
body: {
|
|
890
893
|
permissions: {
|
|
891
|
-
readonly user?: ("
|
|
894
|
+
readonly user?: ("set-role" | "delete" | "update" | "list" | "create" | "ban" | "impersonate" | "impersonate-admins" | "set-password" | "set-email" | "get")[] | undefined;
|
|
892
895
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
893
896
|
};
|
|
894
897
|
} & {
|
|
@@ -2328,6 +2331,9 @@ declare const auth: better_auth.Auth<{
|
|
|
2328
2331
|
input: false;
|
|
2329
2332
|
};
|
|
2330
2333
|
};
|
|
2334
|
+
deleteUser: {
|
|
2335
|
+
enabled: true;
|
|
2336
|
+
};
|
|
2331
2337
|
};
|
|
2332
2338
|
advanced: {
|
|
2333
2339
|
database: {
|
|
@@ -3169,7 +3175,7 @@ declare const auth: better_auth.Auth<{
|
|
|
3169
3175
|
$Infer: {
|
|
3170
3176
|
body: {
|
|
3171
3177
|
permissions: {
|
|
3172
|
-
readonly user?: ("
|
|
3178
|
+
readonly user?: ("set-role" | "delete" | "update" | "list" | "create" | "ban" | "impersonate" | "impersonate-admins" | "set-password" | "set-email" | "get")[] | undefined;
|
|
3173
3179
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
3174
3180
|
};
|
|
3175
3181
|
} & {
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ declare function createAuth(overrides?: AuthOverrides): better_auth.Auth<{
|
|
|
47
47
|
input: false;
|
|
48
48
|
};
|
|
49
49
|
};
|
|
50
|
+
deleteUser: {
|
|
51
|
+
enabled: true;
|
|
52
|
+
};
|
|
50
53
|
};
|
|
51
54
|
advanced: {
|
|
52
55
|
database: {
|
|
@@ -888,7 +891,7 @@ declare function createAuth(overrides?: AuthOverrides): better_auth.Auth<{
|
|
|
888
891
|
$Infer: {
|
|
889
892
|
body: {
|
|
890
893
|
permissions: {
|
|
891
|
-
readonly user?: ("
|
|
894
|
+
readonly user?: ("set-role" | "delete" | "update" | "list" | "create" | "ban" | "impersonate" | "impersonate-admins" | "set-password" | "set-email" | "get")[] | undefined;
|
|
892
895
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
893
896
|
};
|
|
894
897
|
} & {
|
|
@@ -2328,6 +2331,9 @@ declare const auth: better_auth.Auth<{
|
|
|
2328
2331
|
input: false;
|
|
2329
2332
|
};
|
|
2330
2333
|
};
|
|
2334
|
+
deleteUser: {
|
|
2335
|
+
enabled: true;
|
|
2336
|
+
};
|
|
2331
2337
|
};
|
|
2332
2338
|
advanced: {
|
|
2333
2339
|
database: {
|
|
@@ -3169,7 +3175,7 @@ declare const auth: better_auth.Auth<{
|
|
|
3169
3175
|
$Infer: {
|
|
3170
3176
|
body: {
|
|
3171
3177
|
permissions: {
|
|
3172
|
-
readonly user?: ("
|
|
3178
|
+
readonly user?: ("set-role" | "delete" | "update" | "list" | "create" | "ban" | "impersonate" | "impersonate-admins" | "set-password" | "set-email" | "get")[] | undefined;
|
|
3173
3179
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
3174
3180
|
};
|
|
3175
3181
|
} & {
|
package/dist/auth/index.js
CHANGED
|
@@ -208,6 +208,7 @@ __export(schema_exports, {
|
|
|
208
208
|
activityLogs: () => activityLogs,
|
|
209
209
|
authThrottle: () => authThrottle,
|
|
210
210
|
deviceFingerprints: () => deviceFingerprints,
|
|
211
|
+
errorRefs: () => errorRefs,
|
|
211
212
|
passkeys: () => passkeys,
|
|
212
213
|
sessions: () => sessions,
|
|
213
214
|
systemSettings: () => systemSettings,
|
|
@@ -227,7 +228,7 @@ var DEFAULT_MAINTENANCE_EXEMPT_PATHS = [
|
|
|
227
228
|
];
|
|
228
229
|
|
|
229
230
|
// server/db/schema.ts
|
|
230
|
-
var RoleEnum = pgCore.pgEnum("roles", ["user", "admin"]);
|
|
231
|
+
var RoleEnum = pgCore.pgEnum("roles", ["user", "admin", "tester"]);
|
|
231
232
|
var ActivityActionEnum = pgCore.pgEnum("activity_actions", [
|
|
232
233
|
"login",
|
|
233
234
|
"logout",
|
|
@@ -390,6 +391,9 @@ var systemSettings = pgCore.pgTable("system_settings", {
|
|
|
390
391
|
authEnableEmailPassword: pgCore.boolean("authEnableEmailPassword").notNull().default(true),
|
|
391
392
|
authEnableOneTap: pgCore.boolean("authEnableOneTap").notNull().default(false),
|
|
392
393
|
enableSessionRevocation: pgCore.boolean("enableSessionRevocation").notNull().default(true),
|
|
394
|
+
// Attach the error a user hit to feedback they send from it (see
|
|
395
|
+
// lib/feedback-route.ts). Off = only what the user typed is sent.
|
|
396
|
+
feedbackAttachErrors: pgCore.boolean("feedbackAttachErrors").notNull().default(true),
|
|
393
397
|
updatedAt: pgCore.timestamp("updatedAt").notNull().defaultNow(),
|
|
394
398
|
// Who last changed a setting. This row doesn't "belong" to that admin the
|
|
395
399
|
// way sessions/activityLogs belong to their user, so deleting that admin's
|
|
@@ -398,15 +402,31 @@ var systemSettings = pgCore.pgTable("system_settings", {
|
|
|
398
402
|
}, (table) => [
|
|
399
403
|
pgCore.check("system_settings_singleton", drizzleOrm.sql`${table.id} = 'default'`)
|
|
400
404
|
]);
|
|
405
|
+
var errorRefs = pgCore.pgTable("error_refs", {
|
|
406
|
+
ref: pgCore.text("ref").primaryKey(),
|
|
407
|
+
message: pgCore.text("message").notNull(),
|
|
408
|
+
detail: pgCore.text("detail").notNull(),
|
|
409
|
+
createdAt: pgCore.timestamp("createdAt").notNull().defaultNow()
|
|
410
|
+
}, (table) => [
|
|
411
|
+
pgCore.index("error_refs_created_at_idx").on(table.createdAt)
|
|
412
|
+
]);
|
|
401
413
|
var envSchema = zod.z.object({
|
|
402
414
|
NODE_ENV: zod.z.enum(["development", "production", "test"]).default("development"),
|
|
403
415
|
PORT: zod.z.string().default("3000").transform(Number),
|
|
404
416
|
DATABASE_URL: zod.z.string().default(""),
|
|
405
417
|
RESEND_API_KEY: zod.z.string().default(""),
|
|
418
|
+
// Sender for every transactional email. Must be on a domain verified in
|
|
419
|
+
// your Resend account.
|
|
420
|
+
EMAIL_FROM: zod.z.string().default("Acme <onboarding@xavierkhew.xyz>"),
|
|
406
421
|
// Display name for this deployment — shown as the authenticator-app label
|
|
407
422
|
// (2FA issuer) and the passkey relying-party name. Consuming projects set
|
|
408
423
|
// this in their own .env; defaults to the template name.
|
|
409
424
|
APP_NAME: zod.z.string().default("Next.js Template"),
|
|
425
|
+
// Where the dashboard feedback form delivers (app/api/feedback). Set either
|
|
426
|
+
// or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes
|
|
427
|
+
// a comma-separated list and sends through Resend (needs RESEND_API_KEY).
|
|
428
|
+
FEEDBACK_DISCORD_WEBHOOK_URL: zod.z.string().default(""),
|
|
429
|
+
FEEDBACK_EMAIL: zod.z.string().default(""),
|
|
410
430
|
// Seed-only: the live value lives in the system_settings DB row (see
|
|
411
431
|
// lib/settings-queries.ts) once it's been seeded on first read, and is
|
|
412
432
|
// changed from the admin System Settings page, not by editing this var.
|
|
@@ -592,6 +612,7 @@ function fallbackSettings() {
|
|
|
592
612
|
authEnableEmailPassword: config.AUTH_ENABLE_EMAIL_PASSWORD,
|
|
593
613
|
authEnableOneTap: config.AUTH_ENABLE_ONE_TAP,
|
|
594
614
|
enableSessionRevocation: config.ENABLE_SESSION_REVOCATION,
|
|
615
|
+
feedbackAttachErrors: true,
|
|
595
616
|
updatedAt: /* @__PURE__ */ new Date(0),
|
|
596
617
|
updatedBy: null
|
|
597
618
|
};
|
|
@@ -632,7 +653,7 @@ async function getEffectiveAuthFlags() {
|
|
|
632
653
|
oneTap: config.AUTH_ENABLE_ONE_TAP && settings.authEnableOneTap
|
|
633
654
|
};
|
|
634
655
|
}
|
|
635
|
-
var EMAIL_FROM =
|
|
656
|
+
var EMAIL_FROM = config.EMAIL_FROM;
|
|
636
657
|
var resend;
|
|
637
658
|
async function getResend() {
|
|
638
659
|
if (!resend) {
|
|
@@ -784,7 +805,13 @@ function createAuth(overrides = {}) {
|
|
|
784
805
|
input: false
|
|
785
806
|
// Prevent users from setting their own role
|
|
786
807
|
}
|
|
787
|
-
}
|
|
808
|
+
},
|
|
809
|
+
// Self-service "Delete account" (dashboard Settings → Danger zone).
|
|
810
|
+
// Credential users confirm with their password; OAuth-only users have
|
|
811
|
+
// no password, so better-auth instead requires a session younger than
|
|
812
|
+
// `freshAge` (default 1 day). Every user-owned table cascades on delete.
|
|
813
|
+
// Impersonation and last-admin guards live in hooks.before below.
|
|
814
|
+
deleteUser: { enabled: true }
|
|
788
815
|
},
|
|
789
816
|
// Advanced options
|
|
790
817
|
advanced: {
|
|
@@ -888,6 +915,20 @@ function createAuth(overrides = {}) {
|
|
|
888
915
|
}
|
|
889
916
|
return;
|
|
890
917
|
}
|
|
918
|
+
if (ctx.path === "/delete-user") {
|
|
919
|
+
const current2 = await api.getSessionFromCtx(ctx, { disableCookieCache: true });
|
|
920
|
+
if (!current2) return;
|
|
921
|
+
if (current2.session.impersonatedBy) {
|
|
922
|
+
throw new api.APIError("FORBIDDEN", { message: "Stop impersonating before deleting an account." });
|
|
923
|
+
}
|
|
924
|
+
if (current2.user.role === "admin") {
|
|
925
|
+
const [{ count }] = await db.select({ count: drizzleOrm.sql`count(*)` }).from(users).where(drizzleOrm.eq(users.role, "admin"));
|
|
926
|
+
if (Number(count) <= 1) {
|
|
927
|
+
throw new api.APIError("FORBIDDEN", { message: "You're the only admin. Promote another user to admin first." });
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
891
932
|
if (ctx.path !== "/admin/stop-impersonating") return;
|
|
892
933
|
const current = await api.getSessionFromCtx(ctx, { disableCookieCache: true });
|
|
893
934
|
if (current == null ? void 0 : current.session.impersonatedBy) {
|