@xk2800/nextjs-template 0.7.1-beta.6 → 0.7.1-beta.8

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 (86) hide show
  1. package/README.md +31 -29
  2. package/components/dashboard/admin/broadcastActions.tsx +148 -0
  3. package/components/dashboard/admin/confirmSendDialog.tsx +44 -0
  4. package/components/dashboard/admin/marketingEmailForm.tsx +115 -0
  5. package/components/dashboard/admin/marketingSection.tsx +67 -0
  6. package/components/dashboard/admin/subscribersCard.tsx +62 -0
  7. package/components/dashboard/marketingUpdatesCard.tsx +62 -0
  8. package/components/dashboard/marketingUpdatesSection.tsx +10 -0
  9. package/components/ui/select.tsx +189 -0
  10. package/dist/activity/logger.js +6 -0
  11. package/dist/activity/logger.js.map +1 -1
  12. package/dist/activity/logger.mjs +3 -3
  13. package/dist/activity/queries.js +6 -0
  14. package/dist/activity/queries.js.map +1 -1
  15. package/dist/activity/queries.mjs +2 -2
  16. package/dist/admin/queries.js +6 -0
  17. package/dist/admin/queries.js.map +1 -1
  18. package/dist/admin/queries.mjs +2 -2
  19. package/dist/api/feedback.js +7 -0
  20. package/dist/api/feedback.js.map +1 -1
  21. package/dist/api/feedback.mjs +7 -6
  22. package/dist/api/feedback.mjs.map +1 -1
  23. package/dist/api/marketing.d.mts +36 -0
  24. package/dist/api/marketing.d.ts +36 -0
  25. package/dist/api/marketing.js +1357 -0
  26. package/dist/api/marketing.js.map +1 -0
  27. package/dist/api/marketing.mjs +170 -0
  28. package/dist/api/marketing.mjs.map +1 -0
  29. package/dist/auth/helpers.d.mts +3 -3
  30. package/dist/auth/helpers.d.ts +3 -3
  31. package/dist/auth/helpers.js +7 -0
  32. package/dist/auth/helpers.js.map +1 -1
  33. package/dist/auth/helpers.mjs +8 -63
  34. package/dist/auth/helpers.mjs.map +1 -1
  35. package/dist/auth/index.js +7 -0
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/index.mjs +6 -5
  38. package/dist/auth-client/index.d.mts +16 -16
  39. package/dist/auth-client/index.d.ts +16 -16
  40. package/dist/{chunk-6KTSULA5.mjs → chunk-6MDQRT6U.mjs} +3 -3
  41. package/dist/{chunk-6KTSULA5.mjs.map → chunk-6MDQRT6U.mjs.map} +1 -1
  42. package/dist/chunk-6Z3WUSDJ.mjs +62 -0
  43. package/dist/chunk-6Z3WUSDJ.mjs.map +1 -0
  44. package/dist/{chunk-FVLMSLTX.mjs → chunk-7F2POZM6.mjs} +3 -3
  45. package/dist/{chunk-FVLMSLTX.mjs.map → chunk-7F2POZM6.mjs.map} +1 -1
  46. package/dist/{chunk-4GGLYS5R.mjs → chunk-7QVIOQ47.mjs} +8 -16
  47. package/dist/chunk-7QVIOQ47.mjs.map +1 -0
  48. package/dist/chunk-C6RGN6QA.mjs +60 -0
  49. package/dist/chunk-C6RGN6QA.mjs.map +1 -0
  50. package/dist/chunk-OVKVD2HH.mjs +25 -0
  51. package/dist/chunk-OVKVD2HH.mjs.map +1 -0
  52. package/dist/{chunk-F776DQAM.mjs → chunk-P2G23JMY.mjs} +4 -4
  53. package/dist/{chunk-F776DQAM.mjs.map → chunk-P2G23JMY.mjs.map} +1 -1
  54. package/dist/{chunk-TMJYAOC2.mjs → chunk-TKWNEKK6.mjs} +8 -2
  55. package/dist/chunk-TKWNEKK6.mjs.map +1 -0
  56. package/dist/{chunk-F7CG5CA3.mjs → chunk-ZJMFE5B2.mjs} +3 -3
  57. package/dist/{chunk-F7CG5CA3.mjs.map → chunk-ZJMFE5B2.mjs.map} +1 -1
  58. package/dist/config/env.d.mts +2 -0
  59. package/dist/config/env.d.ts +2 -0
  60. package/dist/config/env.js +6 -0
  61. package/dist/config/env.js.map +1 -1
  62. package/dist/config/env.mjs +1 -1
  63. package/dist/db/index.js +6 -0
  64. package/dist/db/index.js.map +1 -1
  65. package/dist/db/index.mjs +2 -2
  66. package/dist/errors.js +6 -0
  67. package/dist/errors.js.map +1 -1
  68. package/dist/errors.mjs +3 -3
  69. package/dist/marketing/index.d.mts +35 -0
  70. package/dist/marketing/index.d.ts +35 -0
  71. package/dist/marketing/index.js +125 -0
  72. package/dist/marketing/index.js.map +1 -0
  73. package/dist/marketing/index.mjs +6 -0
  74. package/dist/marketing/index.mjs.map +1 -0
  75. package/dist/sessions/queries.js +6 -0
  76. package/dist/sessions/queries.js.map +1 -1
  77. package/dist/sessions/queries.mjs +2 -2
  78. package/dist/settings/queries.js +6 -0
  79. package/dist/settings/queries.js.map +1 -1
  80. package/dist/settings/queries.mjs +3 -3
  81. package/dist/users/queries.js +6 -0
  82. package/dist/users/queries.js.map +1 -1
  83. package/dist/users/queries.mjs +2 -2
  84. package/package.json +23 -3
  85. package/dist/chunk-4GGLYS5R.mjs.map +0 -1
  86. package/dist/chunk-TMJYAOC2.mjs.map +0 -1
@@ -1,67 +1,12 @@
1
- import { auth } from '../chunk-4GGLYS5R.mjs';
2
- import '../chunk-FVLMSLTX.mjs';
1
+ export { getCurrentSession, hasRole, normalizeCallbackUrl, requireAuth, requireRole } from '../chunk-6Z3WUSDJ.mjs';
2
+ import '../chunk-7QVIOQ47.mjs';
3
+ import '../chunk-7F2POZM6.mjs';
3
4
  import '../chunk-6OAHCEDJ.mjs';
4
- import '../chunk-F776DQAM.mjs';
5
- import { db } from '../chunk-F7CG5CA3.mjs';
6
- import { users } from '../chunk-TR5XFBTD.mjs';
7
- import '../chunk-TMJYAOC2.mjs';
5
+ import '../chunk-P2G23JMY.mjs';
6
+ import '../chunk-OVKVD2HH.mjs';
7
+ import '../chunk-ZJMFE5B2.mjs';
8
+ import '../chunk-TR5XFBTD.mjs';
9
+ import '../chunk-TKWNEKK6.mjs';
8
10
  import '../chunk-3465SI5N.mjs';
9
- import 'server-only';
10
- import { cache } from 'react';
11
- import { headers } from 'next/headers';
12
- import { redirect } from 'next/navigation';
13
- import { eq } from 'drizzle-orm';
14
-
15
- var getCurrentSession = cache(async () => {
16
- const session = await auth.api.getSession({
17
- headers: await headers()
18
- });
19
- return session;
20
- });
21
- var requireAuth = cache(async () => {
22
- var _a;
23
- const session = await getCurrentSession();
24
- if (!(session == null ? void 0 : session.user)) {
25
- redirect("/login");
26
- }
27
- const user = await db.select().from(users).where(eq(users.id, session.user.id)).limit(1);
28
- if ((_a = user[0]) == null ? void 0 : _a.banned) {
29
- await auth.api.signOut({
30
- headers: await headers()
31
- });
32
- redirect("/login?error=account_banned");
33
- }
34
- return session;
35
- });
36
- function hasRole(userRole, requiredRole) {
37
- if (!userRole) return false;
38
- if (requiredRole === "admin") {
39
- return userRole === "admin";
40
- }
41
- return userRole === "user" || userRole === "admin" || userRole === "tester";
42
- }
43
- async function requireRole(requiredRole) {
44
- const session = await requireAuth();
45
- if (!hasRole(session.user.role, requiredRole)) {
46
- redirect("/dashboard");
47
- }
48
- return session;
49
- }
50
- function normalizeCallbackUrl(value) {
51
- if (typeof value !== "string" || !value.trim()) return "/dashboard";
52
- const candidate = value.trim();
53
- try {
54
- const appOrigin = new URL(process.env.NEXT_PUBLIC_APP_URL || process.env.BETTER_AUTH_URL || "http://localhost:3000").origin;
55
- const callbackUrl = new URL(candidate, appOrigin);
56
- if (callbackUrl.origin !== appOrigin) {
57
- return "/dashboard";
58
- }
59
- return `${callbackUrl.pathname}${callbackUrl.search}${callbackUrl.hash}` || "/dashboard";
60
- } catch (e) {
61
- return "/dashboard";
62
- }
63
- }
64
-
65
- export { getCurrentSession, hasRole, normalizeCallbackUrl, requireAuth, requireRole };
66
11
  //# sourceMappingURL=helpers.mjs.map
67
12
  //# sourceMappingURL=helpers.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../lib/auth-helpers.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAgBO,IAAM,iBAAA,GAAoB,MAAM,YAAY;AACjD,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,GAAA,CAAI,UAAA,CAAW;AAAA,IACxC,OAAA,EAAS,MAAM,OAAA;AAAQ,GACxB,CAAA;AAED,EAAA,OAAO,OAAA;AACT,CAAC;AASM,IAAM,WAAA,GAAc,MAAM,YAAY;AA/B7C,EAAA,IAAA,EAAA;AAgCE,EAAA,MAAM,OAAA,GAAU,MAAM,iBAAA,EAAkB;AAExC,EAAA,IAAI,EAAC,mCAAS,IAAA,CAAA,EAAM;AAClB,IAAA,QAAA,CAAS,QAAQ,CAAA;AAAA,EACnB;AAGA,EAAA,MAAM,OAAO,MAAM,EAAA,CAChB,QAAO,CACP,IAAA,CAAK,KAAK,CAAA,CACV,KAAA,CAAM,EAAA,CAAG,KAAA,CAAM,IAAI,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAC,CAAA,CACnC,MAAM,CAAC,CAAA;AAEV,EAAA,IAAA,CAAI,EAAA,GAAA,IAAA,CAAK,CAAC,CAAA,KAAN,IAAA,GAAA,MAAA,GAAA,EAAA,CAAS,MAAA,EAAQ;AAEnB,IAAA,MAAM,IAAA,CAAK,IAAI,OAAA,CAAQ;AAAA,MACrB,OAAA,EAAS,MAAM,OAAA;AAAQ,KACxB,CAAA;AACD,IAAA,QAAA,CAAS,6BAA6B,CAAA;AAAA,EACxC;AAEA,EAAA,OAAO,OAAA;AACT,CAAC;AAKM,SAAS,OAAA,CAAQ,UAAqC,YAAA,EAAyC;AACpG,EAAA,IAAI,CAAC,UAAU,OAAO,KAAA;AAEtB,EAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,IAAA,OAAO,QAAA,KAAa,OAAA;AAAA,EACtB;AAGA,EAAA,OAAO,QAAA,KAAa,MAAA,IAAU,QAAA,KAAa,OAAA,IAAW,QAAA,KAAa,QAAA;AACrE;AAKA,eAAsB,YAAY,YAAA,EAAgC;AAChE,EAAA,MAAM,OAAA,GAAU,MAAM,WAAA,EAAY;AAElC,EAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,IAAA,EAAM,YAAY,CAAA,EAAG;AAC7C,IAAA,QAAA,CAAS,YAAY,CAAA;AAAA,EACvB;AAEA,EAAA,OAAO,OAAA;AACT;AAQO,SAAS,qBAAqB,KAAA,EAAmC;AACtE,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,IAAA,IAAQ,OAAO,YAAA;AAEvD,EAAA,MAAM,SAAA,GAAY,MAAM,IAAA,EAAK;AAE7B,EAAA,IAAI;AACF,IAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,uBAAuB,OAAA,CAAQ,GAAA,CAAI,eAAA,IAAmB,uBAAuB,CAAA,CAAE,MAAA;AACrH,IAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,SAAA,EAAW,SAAS,CAAA;AAEhD,IAAA,IAAI,WAAA,CAAY,WAAW,SAAA,EAAW;AACpC,MAAA,OAAO,YAAA;AAAA,IACT;AAEA,IAAA,OAAO,CAAA,EAAG,YAAY,QAAQ,CAAA,EAAG,YAAY,MAAM,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA,CAAA,IAAM,YAAA;AAAA,EAC9E,CAAA,CAAA,OAAQ,CAAA,EAAA;AACN,IAAA,OAAO,YAAA;AAAA,EACT;AACF","file":"helpers.mjs","sourcesContent":["import \"server-only\"\nimport { cache } from \"react\"\nimport { auth } from \"../server/auth\"\nimport { headers } from \"next/headers\"\nimport { redirect } from \"next/navigation\"\nimport { db } from \"../server/db\"\nimport { users } from \"../server/db/schema\"\nimport { eq } from \"drizzle-orm\"\n\n/**\n * Get current session (server-side only)\n * Returns session data or null if not authenticated\n *\n * Wrapped in React's cache() so the layout and page (both of which call\n * requireAuth per request) share one auth.api.getSession call instead of two.\n */\nexport const getCurrentSession = cache(async () => {\n const session = await auth.api.getSession({\n headers: await headers()\n })\n\n return session\n})\n\n/**\n * Require authentication - redirect to login if not authenticated\n * Use in server components/layouts that need protection\n *\n * Wrapped in cache() so the layout, page, and any admin layout that all call\n * requireAuth per request share one banned-status DB lookup instead of several.\n */\nexport const requireAuth = cache(async () => {\n const session = await getCurrentSession()\n\n if (!session?.user) {\n redirect('/login')\n }\n\n // Check if user is banned\n const user = await db\n .select()\n .from(users)\n .where(eq(users.id, session.user.id))\n .limit(1)\n\n if (user[0]?.banned) {\n // Sign out the user\n await auth.api.signOut({\n headers: await headers()\n })\n redirect('/login?error=account_banned')\n }\n\n return session\n})\n\n/**\n * Check if user has specific role\n */\nexport function hasRole(userRole: string | null | undefined, requiredRole: 'user' | 'admin'): boolean {\n if (!userRole) return false\n\n if (requiredRole === 'admin') {\n return userRole === 'admin'\n }\n\n // 'user' role check - every role ('user', 'admin', 'tester') has access\n return userRole === 'user' || userRole === 'admin' || userRole === 'tester'\n}\n\n/**\n * Require specific role - throw error if user doesn't have it\n */\nexport async function requireRole(requiredRole: 'user' | 'admin') {\n const session = await requireAuth()\n\n if (!hasRole(session.user.role, requiredRole)) {\n redirect('/dashboard')\n }\n\n return session\n}\n\n/**\n * Normalize a callbackUrl query param to a same-origin path before it's ever\n * passed to redirect() or an auth form's callbackURL. Guards against open\n * redirects (e.g. /login?callbackUrl=https://evil.com) — anything that\n * doesn't resolve to this app's own origin falls back to /dashboard.\n */\nexport function normalizeCallbackUrl(value?: string | string[]): string {\n if (typeof value !== 'string' || !value.trim()) return '/dashboard'\n\n const candidate = value.trim()\n\n try {\n const appOrigin = new URL(process.env.NEXT_PUBLIC_APP_URL || process.env.BETTER_AUTH_URL || 'http://localhost:3000').origin\n const callbackUrl = new URL(candidate, appOrigin)\n\n if (callbackUrl.origin !== appOrigin) {\n return '/dashboard'\n }\n\n return `${callbackUrl.pathname}${callbackUrl.search}${callbackUrl.hash}` || '/dashboard'\n } catch {\n return '/dashboard'\n }\n}\n\n"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"helpers.mjs"}
@@ -445,6 +445,12 @@ var envSchema = zod.z.object({
445
445
  PORT: zod.z.string().default("3000").transform(Number),
446
446
  DATABASE_URL: zod.z.string().default(""),
447
447
  RESEND_API_KEY: zod.z.string().default(""),
448
+ // Full access key for marketing broadcasts (admin Marketing email page).
449
+ // Blank = use RESEND_API_KEY.
450
+ RESEND_BROADCAST_API_KEY: zod.z.string().default(""),
451
+ // Resend segment users join from the dashboard "Email updates" toggle.
452
+ // Blank = toggle hidden.
453
+ RESEND_MARKETING_SEGMENT_ID: zod.z.string().default(""),
448
454
  // Sender for every transactional email. Must be on a domain verified in
449
455
  // your Resend account.
450
456
  EMAIL_FROM: zod.z.string().default("Acme <onboarding@xavierkhew.xyz>"),
@@ -706,6 +712,7 @@ async function getResend() {
706
712
  }
707
713
  return resend;
708
714
  }
715
+ config.RESEND_BROADCAST_API_KEY || config.RESEND_API_KEY;
709
716
 
710
717
  // server/auth.ts
711
718
  async function logFailedLogin(ctx) {