@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.
Files changed (105) hide show
  1. package/README.md +56 -321
  2. package/components/dashboard/admin/adminUsersTable.tsx +36 -2
  3. package/components/dashboard/admin/feedbackTestForm.tsx +55 -0
  4. package/components/dashboard/admin/systemSettingsForm.tsx +28 -0
  5. package/components/dashboard/appSidebar.tsx +4 -4
  6. package/components/dashboard/deleteAccountCard.tsx +137 -0
  7. package/components/dashboard/errorFeedback.tsx +65 -0
  8. package/components/dashboard/feedbackDialog.tsx +160 -0
  9. package/components/dashboard/installAppCard.tsx +187 -0
  10. package/components/site/cookie-banner.tsx +57 -0
  11. package/components/ui/button.tsx +1 -1
  12. package/components/ui/file-upload.tsx +178 -0
  13. package/components/ui/input.tsx +1 -1
  14. package/components/ui/separator.tsx +1 -1
  15. package/components/ui/sheet.tsx +1 -1
  16. package/components/ui/sidebar.tsx +8 -8
  17. package/components/ui/skeleton.tsx +1 -1
  18. package/components/ui/tooltip.tsx +1 -1
  19. package/dist/activity/logger.js +21 -1
  20. package/dist/activity/logger.js.map +1 -1
  21. package/dist/activity/logger.mjs +4 -4
  22. package/dist/activity/queries.d.mts +1 -1
  23. package/dist/activity/queries.d.ts +1 -1
  24. package/dist/activity/queries.js +21 -1
  25. package/dist/activity/queries.js.map +1 -1
  26. package/dist/activity/queries.mjs +3 -3
  27. package/dist/admin/queries.js +21 -1
  28. package/dist/admin/queries.js.map +1 -1
  29. package/dist/admin/queries.mjs +3 -3
  30. package/dist/api/feedback.d.mts +23 -0
  31. package/dist/api/feedback.d.ts +23 -0
  32. package/dist/api/feedback.js +1224 -0
  33. package/dist/api/feedback.js.map +1 -0
  34. package/dist/api/feedback.mjs +136 -0
  35. package/dist/api/feedback.mjs.map +1 -0
  36. package/dist/auth/helpers.d.mts +3 -3
  37. package/dist/auth/helpers.d.ts +3 -3
  38. package/dist/auth/helpers.js +45 -4
  39. package/dist/auth/helpers.js.map +1 -1
  40. package/dist/auth/helpers.mjs +7 -7
  41. package/dist/auth/helpers.mjs.map +1 -1
  42. package/dist/auth/index.d.mts +8 -2
  43. package/dist/auth/index.d.ts +8 -2
  44. package/dist/auth/index.js +44 -3
  45. package/dist/auth/index.js.map +1 -1
  46. package/dist/auth/index.mjs +6 -6
  47. package/dist/auth-client/index.d.mts +16 -16
  48. package/dist/auth-client/index.d.ts +16 -16
  49. package/dist/{chunk-IF3JZRSZ.mjs → chunk-3MGEAGHU.mjs} +6 -5
  50. package/dist/chunk-3MGEAGHU.mjs.map +1 -0
  51. package/dist/chunk-KAEA35OC.mjs +76 -0
  52. package/dist/chunk-KAEA35OC.mjs.map +1 -0
  53. package/dist/{chunk-GTPTVNY2.mjs → chunk-TMJYAOC2.mjs} +10 -2
  54. package/dist/chunk-TMJYAOC2.mjs.map +1 -0
  55. package/dist/{chunk-OHAUKE2Q.mjs → chunk-VQ5B4MA7.mjs} +4 -4
  56. package/dist/{chunk-OHAUKE2Q.mjs.map → chunk-VQ5B4MA7.mjs.map} +1 -1
  57. package/dist/{chunk-5PYDAD4P.mjs → chunk-WZDAMCUX.mjs} +4 -4
  58. package/dist/{chunk-5PYDAD4P.mjs.map → chunk-WZDAMCUX.mjs.map} +1 -1
  59. package/dist/{chunk-V72CFFU2.mjs → chunk-XWVAJXAG.mjs} +31 -11
  60. package/dist/chunk-XWVAJXAG.mjs.map +1 -0
  61. package/dist/{chunk-4B7MJOLH.mjs → chunk-ZQCALTKB.mjs} +17 -5
  62. package/dist/chunk-ZQCALTKB.mjs.map +1 -0
  63. package/dist/config/env.d.mts +3 -0
  64. package/dist/config/env.d.ts +3 -0
  65. package/dist/config/env.js +8 -0
  66. package/dist/config/env.js.map +1 -1
  67. package/dist/config/env.mjs +1 -1
  68. package/dist/db/index.d.mts +1 -1
  69. package/dist/db/index.d.ts +1 -1
  70. package/dist/db/index.js +21 -1
  71. package/dist/db/index.js.map +1 -1
  72. package/dist/db/index.mjs +3 -3
  73. package/dist/db/schema.d.mts +1 -1
  74. package/dist/db/schema.d.ts +1 -1
  75. package/dist/db/schema.js +13 -1
  76. package/dist/db/schema.js.map +1 -1
  77. package/dist/db/schema.mjs +1 -1
  78. package/dist/errors.d.mts +29 -0
  79. package/dist/errors.d.ts +29 -0
  80. package/dist/errors.js +365 -0
  81. package/dist/errors.js.map +1 -0
  82. package/dist/errors.mjs +7 -0
  83. package/dist/errors.mjs.map +1 -0
  84. package/dist/{schema-BghJ8N4X.d.mts → schema-BNSBWNjK.d.mts} +99 -6
  85. package/dist/{schema-BghJ8N4X.d.ts → schema-BNSBWNjK.d.ts} +99 -6
  86. package/dist/sessions/queries.js +21 -1
  87. package/dist/sessions/queries.js.map +1 -1
  88. package/dist/sessions/queries.mjs +3 -3
  89. package/dist/settings/queries.d.mts +2 -2
  90. package/dist/settings/queries.d.ts +2 -2
  91. package/dist/settings/queries.js +22 -1
  92. package/dist/settings/queries.js.map +1 -1
  93. package/dist/settings/queries.mjs +4 -4
  94. package/dist/users/queries.d.mts +3 -3
  95. package/dist/users/queries.d.ts +3 -3
  96. package/dist/users/queries.js +21 -1
  97. package/dist/users/queries.js.map +1 -1
  98. package/dist/users/queries.mjs +3 -3
  99. package/hooks/use-mobile.ts +19 -0
  100. package/lib/device-fingerprint.ts +2 -3
  101. package/package.json +35 -32
  102. package/dist/chunk-4B7MJOLH.mjs.map +0 -1
  103. package/dist/chunk-GTPTVNY2.mjs.map +0 -1
  104. package/dist/chunk-IF3JZRSZ.mjs.map +0 -1
  105. package/dist/chunk-V72CFFU2.mjs.map +0 -1
@@ -211,6 +211,7 @@ __export(schema_exports, {
211
211
  activityLogs: () => activityLogs,
212
212
  authThrottle: () => authThrottle,
213
213
  deviceFingerprints: () => deviceFingerprints,
214
+ errorRefs: () => errorRefs,
214
215
  passkeys: () => passkeys,
215
216
  sessions: () => sessions,
216
217
  systemSettings: () => systemSettings,
@@ -230,7 +231,7 @@ var DEFAULT_MAINTENANCE_EXEMPT_PATHS = [
230
231
  ];
231
232
 
232
233
  // server/db/schema.ts
233
- var RoleEnum = pgCore.pgEnum("roles", ["user", "admin"]);
234
+ var RoleEnum = pgCore.pgEnum("roles", ["user", "admin", "tester"]);
234
235
  var ActivityActionEnum = pgCore.pgEnum("activity_actions", [
235
236
  "login",
236
237
  "logout",
@@ -393,6 +394,9 @@ var systemSettings = pgCore.pgTable("system_settings", {
393
394
  authEnableEmailPassword: pgCore.boolean("authEnableEmailPassword").notNull().default(true),
394
395
  authEnableOneTap: pgCore.boolean("authEnableOneTap").notNull().default(false),
395
396
  enableSessionRevocation: pgCore.boolean("enableSessionRevocation").notNull().default(true),
397
+ // Attach the error a user hit to feedback they send from it (see
398
+ // lib/feedback-route.ts). Off = only what the user typed is sent.
399
+ feedbackAttachErrors: pgCore.boolean("feedbackAttachErrors").notNull().default(true),
396
400
  updatedAt: pgCore.timestamp("updatedAt").notNull().defaultNow(),
397
401
  // Who last changed a setting. This row doesn't "belong" to that admin the
398
402
  // way sessions/activityLogs belong to their user, so deleting that admin's
@@ -401,15 +405,31 @@ var systemSettings = pgCore.pgTable("system_settings", {
401
405
  }, (table) => [
402
406
  pgCore.check("system_settings_singleton", drizzleOrm.sql`${table.id} = 'default'`)
403
407
  ]);
408
+ var errorRefs = pgCore.pgTable("error_refs", {
409
+ ref: pgCore.text("ref").primaryKey(),
410
+ message: pgCore.text("message").notNull(),
411
+ detail: pgCore.text("detail").notNull(),
412
+ createdAt: pgCore.timestamp("createdAt").notNull().defaultNow()
413
+ }, (table) => [
414
+ pgCore.index("error_refs_created_at_idx").on(table.createdAt)
415
+ ]);
404
416
  var envSchema = zod.z.object({
405
417
  NODE_ENV: zod.z.enum(["development", "production", "test"]).default("development"),
406
418
  PORT: zod.z.string().default("3000").transform(Number),
407
419
  DATABASE_URL: zod.z.string().default(""),
408
420
  RESEND_API_KEY: zod.z.string().default(""),
421
+ // Sender for every transactional email. Must be on a domain verified in
422
+ // your Resend account.
423
+ EMAIL_FROM: zod.z.string().default("Acme <onboarding@xavierkhew.xyz>"),
409
424
  // Display name for this deployment — shown as the authenticator-app label
410
425
  // (2FA issuer) and the passkey relying-party name. Consuming projects set
411
426
  // this in their own .env; defaults to the template name.
412
427
  APP_NAME: zod.z.string().default("Next.js Template"),
428
+ // Where the dashboard feedback form delivers (app/api/feedback). Set either
429
+ // or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes
430
+ // a comma-separated list and sends through Resend (needs RESEND_API_KEY).
431
+ FEEDBACK_DISCORD_WEBHOOK_URL: zod.z.string().default(""),
432
+ FEEDBACK_EMAIL: zod.z.string().default(""),
413
433
  // Seed-only: the live value lives in the system_settings DB row (see
414
434
  // lib/settings-queries.ts) once it's been seeded on first read, and is
415
435
  // changed from the admin System Settings page, not by editing this var.
@@ -595,6 +615,7 @@ function fallbackSettings() {
595
615
  authEnableEmailPassword: config.AUTH_ENABLE_EMAIL_PASSWORD,
596
616
  authEnableOneTap: config.AUTH_ENABLE_ONE_TAP,
597
617
  enableSessionRevocation: config.ENABLE_SESSION_REVOCATION,
618
+ feedbackAttachErrors: true,
598
619
  updatedAt: /* @__PURE__ */ new Date(0),
599
620
  updatedBy: null
600
621
  };
@@ -635,7 +656,7 @@ async function getEffectiveAuthFlags() {
635
656
  oneTap: config.AUTH_ENABLE_ONE_TAP && settings.authEnableOneTap
636
657
  };
637
658
  }
638
- var EMAIL_FROM = "Acme <onboarding@xavierkhew.xyz>";
659
+ var EMAIL_FROM = config.EMAIL_FROM;
639
660
  var resend;
640
661
  async function getResend() {
641
662
  if (!resend) {
@@ -787,7 +808,13 @@ function createAuth(overrides = {}) {
787
808
  input: false
788
809
  // Prevent users from setting their own role
789
810
  }
790
- }
811
+ },
812
+ // Self-service "Delete account" (dashboard Settings → Danger zone).
813
+ // Credential users confirm with their password; OAuth-only users have
814
+ // no password, so better-auth instead requires a session younger than
815
+ // `freshAge` (default 1 day). Every user-owned table cascades on delete.
816
+ // Impersonation and last-admin guards live in hooks.before below.
817
+ deleteUser: { enabled: true }
791
818
  },
792
819
  // Advanced options
793
820
  advanced: {
@@ -891,6 +918,20 @@ function createAuth(overrides = {}) {
891
918
  }
892
919
  return;
893
920
  }
921
+ if (ctx.path === "/delete-user") {
922
+ const current2 = await api.getSessionFromCtx(ctx, { disableCookieCache: true });
923
+ if (!current2) return;
924
+ if (current2.session.impersonatedBy) {
925
+ throw new api.APIError("FORBIDDEN", { message: "Stop impersonating before deleting an account." });
926
+ }
927
+ if (current2.user.role === "admin") {
928
+ const [{ count }] = await db.select({ count: drizzleOrm.sql`count(*)` }).from(users).where(drizzleOrm.eq(users.role, "admin"));
929
+ if (Number(count) <= 1) {
930
+ throw new api.APIError("FORBIDDEN", { message: "You're the only admin. Promote another user to admin first." });
931
+ }
932
+ }
933
+ return;
934
+ }
894
935
  if (ctx.path !== "/admin/stop-impersonating") return;
895
936
  const current = await api.getSessionFromCtx(ctx, { disableCookieCache: true });
896
937
  if (current == null ? void 0 : current.session.impersonatedBy) {
@@ -1038,7 +1079,7 @@ function hasRole(userRole, requiredRole) {
1038
1079
  if (requiredRole === "admin") {
1039
1080
  return userRole === "admin";
1040
1081
  }
1041
- return userRole === "user" || userRole === "admin";
1082
+ return userRole === "user" || userRole === "admin" || userRole === "tester";
1042
1083
  }
1043
1084
  async function requireRole(requiredRole) {
1044
1085
  const session = await requireAuth();