create-nextdevtpl 2.6.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 (425) hide show
  1. package/README.md +29 -0
  2. package/catalog.json +385 -0
  3. package/dist/catalog.d.ts +3 -0
  4. package/dist/catalog.js +112 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.js +115 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/filesystem.d.ts +10 -0
  10. package/dist/filesystem.js +86 -0
  11. package/dist/filesystem.js.map +1 -0
  12. package/dist/generate.d.ts +5 -0
  13. package/dist/generate.js +758 -0
  14. package/dist/generate.js.map +1 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.js +4 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/selection.d.ts +2 -0
  19. package/dist/selection.js +111 -0
  20. package/dist/selection.js.map +1 -0
  21. package/dist/service-templates.d.ts +4 -0
  22. package/dist/service-templates.js +180 -0
  23. package/dist/service-templates.js.map +1 -0
  24. package/dist/types.d.ts +65 -0
  25. package/dist/types.js +2 -0
  26. package/dist/types.js.map +1 -0
  27. package/package.json +54 -0
  28. package/template/.dockerignore +14 -0
  29. package/template/.env.example +282 -0
  30. package/template/.gitattributes +17 -0
  31. package/template/.node-version +1 -0
  32. package/template/Dockerfile +45 -0
  33. package/template/LICENSE +21 -0
  34. package/template/README.md +267 -0
  35. package/template/biome.json +68 -0
  36. package/template/cloudflare/templates/logger.ts +162 -0
  37. package/template/cloudflare/templates/mail/server.ts +6 -0
  38. package/template/cloudflare/templates/mail/templates.ts +87 -0
  39. package/template/cloudflare/templates/mail/utils.ts +69 -0
  40. package/template/cloudflare/worker.mjs +25 -0
  41. package/template/components.json +22 -0
  42. package/template/compose.yaml +74 -0
  43. package/template/deploy/check-health.mjs +25 -0
  44. package/template/deploy/docker/README.md +14 -0
  45. package/template/deploy/server/README.md +24 -0
  46. package/template/deploy/server/build.sh +24 -0
  47. package/template/deploy/server/nextdevtpl.service +19 -0
  48. package/template/deploy/server/start.sh +19 -0
  49. package/template/deploy/vercel/README.md +16 -0
  50. package/template/deploy/verify-production-health.mjs +86 -0
  51. package/template/drizzle.config.ts +47 -0
  52. package/template/gitignore +62 -0
  53. package/template/manifest.json +35 -0
  54. package/template/messages/en.json +766 -0
  55. package/template/messages/zh.json +762 -0
  56. package/template/next.config.mjs +28 -0
  57. package/template/open-next.config.ts +3 -0
  58. package/template/package.json +125 -0
  59. package/template/pnpm-lock.yaml +15901 -0
  60. package/template/postcss.config.mjs +7 -0
  61. package/template/public/file.svg +1 -0
  62. package/template/public/globe.svg +1 -0
  63. package/template/public/logo.svg +6 -0
  64. package/template/public/next.svg +1 -0
  65. package/template/public/vercel.svg +1 -0
  66. package/template/public/window.svg +1 -0
  67. package/template/sentry.client.config.ts +10 -0
  68. package/template/sentry.edge.config.ts +10 -0
  69. package/template/sentry.server.config.ts +10 -0
  70. package/template/source.config.ts +59 -0
  71. package/template/src/adapters/ai/anthropic.ts +113 -0
  72. package/template/src/adapters/ai/index.ts +6 -0
  73. package/template/src/adapters/ai/openai-compatible.ts +88 -0
  74. package/template/src/adapters/ai/workers-ai.ts +63 -0
  75. package/template/src/adapters/index.ts +10 -0
  76. package/template/src/adapters/jobs/cloudflare-workflows.ts +46 -0
  77. package/template/src/adapters/jobs/index.ts +5 -0
  78. package/template/src/adapters/jobs/inngest/adapter.ts +29 -0
  79. package/template/src/adapters/jobs/inngest/client.ts +8 -0
  80. package/template/src/adapters/jobs/inngest/functions.ts +17 -0
  81. package/template/src/adapters/jobs/inngest/handler.ts +6 -0
  82. package/template/src/adapters/jobs/inngest/index.ts +4 -0
  83. package/template/src/adapters/mail/cloudflare-email.ts +50 -0
  84. package/template/src/adapters/mail/disabled.ts +17 -0
  85. package/template/src/adapters/mail/index.ts +7 -0
  86. package/template/src/adapters/mail/resend.ts +76 -0
  87. package/template/src/adapters/mail/smtp.ts +82 -0
  88. package/template/src/adapters/payment/creem.ts +269 -0
  89. package/template/src/adapters/payment/index.ts +2 -0
  90. package/template/src/adapters/payment/stripe.ts +302 -0
  91. package/template/src/adapters/rate-limit/cloudflare.ts +42 -0
  92. package/template/src/adapters/rate-limit/index.ts +6 -0
  93. package/template/src/adapters/rate-limit/noop.ts +27 -0
  94. package/template/src/adapters/rate-limit/upstash.ts +113 -0
  95. package/template/src/adapters/registry.ts +228 -0
  96. package/template/src/adapters/storage/index.ts +3 -0
  97. package/template/src/adapters/storage/r2-binding.ts +105 -0
  98. package/template/src/adapters/storage/s3-compatible.ts +149 -0
  99. package/template/src/app/[locale]/(admin)/admin/layout.tsx +36 -0
  100. package/template/src/app/[locale]/(admin)/admin/page.tsx +345 -0
  101. package/template/src/app/[locale]/(admin)/admin/tickets/[id]/page.tsx +261 -0
  102. package/template/src/app/[locale]/(admin)/admin/tickets/page.tsx +236 -0
  103. package/template/src/app/[locale]/(admin)/admin/users/page.tsx +557 -0
  104. package/template/src/app/[locale]/(auth)/forgot-password/page.tsx +9 -0
  105. package/template/src/app/[locale]/(auth)/layout.tsx +28 -0
  106. package/template/src/app/[locale]/(auth)/sign-in/page.tsx +9 -0
  107. package/template/src/app/[locale]/(auth)/sign-up/page.tsx +9 -0
  108. package/template/src/app/[locale]/(dashboard)/dashboard/credits/buy/buy-credits-view.tsx +170 -0
  109. package/template/src/app/[locale]/(dashboard)/dashboard/credits/buy/page.tsx +12 -0
  110. package/template/src/app/[locale]/(dashboard)/dashboard/credits/page.tsx +5 -0
  111. package/template/src/app/[locale]/(dashboard)/dashboard/loading.tsx +41 -0
  112. package/template/src/app/[locale]/(dashboard)/dashboard/page.tsx +299 -0
  113. package/template/src/app/[locale]/(dashboard)/dashboard/settings/page.tsx +38 -0
  114. package/template/src/app/[locale]/(dashboard)/dashboard/support/[id]/page.tsx +220 -0
  115. package/template/src/app/[locale]/(dashboard)/dashboard/support/new/page.tsx +183 -0
  116. package/template/src/app/[locale]/(dashboard)/dashboard/support/page.tsx +145 -0
  117. package/template/src/app/[locale]/(dashboard)/layout.tsx +35 -0
  118. package/template/src/app/[locale]/(marketing)/blog/[slug]/page.tsx +178 -0
  119. package/template/src/app/[locale]/(marketing)/blog/blog-post-card.tsx +95 -0
  120. package/template/src/app/[locale]/(marketing)/blog/page.tsx +127 -0
  121. package/template/src/app/[locale]/(marketing)/demo/plan-badges/page.tsx +36 -0
  122. package/template/src/app/[locale]/(marketing)/layout.tsx +15 -0
  123. package/template/src/app/[locale]/(marketing)/legal/[slug]/page.tsx +130 -0
  124. package/template/src/app/[locale]/(marketing)/page.tsx +94 -0
  125. package/template/src/app/[locale]/(marketing)/pseo/[slug]/page.tsx +68 -0
  126. package/template/src/app/[locale]/(marketing)/pseo/page.tsx +137 -0
  127. package/template/src/app/[locale]/docs/[[...slug]]/page.tsx +63 -0
  128. package/template/src/app/[locale]/docs/layout.tsx +39 -0
  129. package/template/src/app/[locale]/layout.tsx +80 -0
  130. package/template/src/app/[locale]/opengraph-image.tsx +20 -0
  131. package/template/src/app/[locale]/twitter-image.tsx +20 -0
  132. package/template/src/app/api/auth/[...all]/route.ts +20 -0
  133. package/template/src/app/api/health/route.ts +45 -0
  134. package/template/src/app/api/inngest/route.ts +12 -0
  135. package/template/src/app/api/jobs/credits/expire/route.ts +102 -0
  136. package/template/src/app/api/search/route.ts +17 -0
  137. package/template/src/app/api/upload/presigned/route.ts +98 -0
  138. package/template/src/app/api/webhooks/payment/route.ts +363 -0
  139. package/template/src/app/globals.css +406 -0
  140. package/template/src/app/icon.svg +6 -0
  141. package/template/src/app/image-proxy/[...path]/route.ts +137 -0
  142. package/template/src/app/layout.tsx +73 -0
  143. package/template/src/app/robots.ts +29 -0
  144. package/template/src/app/sitemap.ts +68 -0
  145. package/template/src/components/motion/reveal.tsx +49 -0
  146. package/template/src/components/seo/json-ld.tsx +87 -0
  147. package/template/src/components/seo/og-image-template.tsx +118 -0
  148. package/template/src/components/ui/accordion.tsx +66 -0
  149. package/template/src/components/ui/alert-dialog.tsx +195 -0
  150. package/template/src/components/ui/avatar.tsx +53 -0
  151. package/template/src/components/ui/badge.tsx +46 -0
  152. package/template/src/components/ui/button.tsx +65 -0
  153. package/template/src/components/ui/card.tsx +92 -0
  154. package/template/src/components/ui/checkbox.tsx +32 -0
  155. package/template/src/components/ui/collapsible.tsx +33 -0
  156. package/template/src/components/ui/dialog.tsx +124 -0
  157. package/template/src/components/ui/dropdown-menu.tsx +257 -0
  158. package/template/src/components/ui/form.tsx +233 -0
  159. package/template/src/components/ui/index.ts +2 -0
  160. package/template/src/components/ui/input.tsx +21 -0
  161. package/template/src/components/ui/label.tsx +24 -0
  162. package/template/src/components/ui/navigation-menu.tsx +167 -0
  163. package/template/src/components/ui/pagination.tsx +125 -0
  164. package/template/src/components/ui/popover.tsx +48 -0
  165. package/template/src/components/ui/progress.tsx +27 -0
  166. package/template/src/components/ui/radio-group.tsx +45 -0
  167. package/template/src/components/ui/select.tsx +190 -0
  168. package/template/src/components/ui/separator.tsx +28 -0
  169. package/template/src/components/ui/sheet.tsx +140 -0
  170. package/template/src/components/ui/switch.tsx +31 -0
  171. package/template/src/components/ui/tabs.tsx +75 -0
  172. package/template/src/components/ui/textarea.tsx +18 -0
  173. package/template/src/config/index.ts +31 -0
  174. package/template/src/config/nav.ts +238 -0
  175. package/template/src/config/payment.ts +282 -0
  176. package/template/src/config/site.ts +53 -0
  177. package/template/src/config/subscription-plan.ts +164 -0
  178. package/template/src/content/blog/en/hello-world.mdx +48 -0
  179. package/template/src/content/blog/zh/hello-world.mdx +48 -0
  180. package/template/src/content/blog/zh/nextdevtpl-linuxdo-intro.mdx +98 -0
  181. package/template/src/content/docs/en/admin-and-support.mdx +65 -0
  182. package/template/src/content/docs/en/authentication.mdx +71 -0
  183. package/template/src/content/docs/en/background-jobs.mdx +53 -0
  184. package/template/src/content/docs/en/configuration.mdx +71 -0
  185. package/template/src/content/docs/en/credits.mdx +69 -0
  186. package/template/src/content/docs/en/customization.mdx +95 -0
  187. package/template/src/content/docs/en/database.mdx +66 -0
  188. package/template/src/content/docs/en/deployment.mdx +62 -0
  189. package/template/src/content/docs/en/email.mdx +48 -0
  190. package/template/src/content/docs/en/index.mdx +48 -0
  191. package/template/src/content/docs/en/internationalization.mdx +78 -0
  192. package/template/src/content/docs/en/meta.json +22 -0
  193. package/template/src/content/docs/en/observability.mdx +69 -0
  194. package/template/src/content/docs/en/payments.mdx +64 -0
  195. package/template/src/content/docs/en/project-structure.mdx +82 -0
  196. package/template/src/content/docs/en/quick-start.mdx +113 -0
  197. package/template/src/content/docs/en/storage.mdx +51 -0
  198. package/template/src/content/docs/zh/admin-and-support.mdx +65 -0
  199. package/template/src/content/docs/zh/authentication.mdx +71 -0
  200. package/template/src/content/docs/zh/background-jobs.mdx +53 -0
  201. package/template/src/content/docs/zh/configuration.mdx +70 -0
  202. package/template/src/content/docs/zh/credits.mdx +69 -0
  203. package/template/src/content/docs/zh/customization.mdx +95 -0
  204. package/template/src/content/docs/zh/database.mdx +66 -0
  205. package/template/src/content/docs/zh/deployment.mdx +61 -0
  206. package/template/src/content/docs/zh/email.mdx +47 -0
  207. package/template/src/content/docs/zh/index.mdx +48 -0
  208. package/template/src/content/docs/zh/internationalization.mdx +78 -0
  209. package/template/src/content/docs/zh/meta.json +22 -0
  210. package/template/src/content/docs/zh/observability.mdx +68 -0
  211. package/template/src/content/docs/zh/payments.mdx +64 -0
  212. package/template/src/content/docs/zh/project-structure.mdx +82 -0
  213. package/template/src/content/docs/zh/quick-start.mdx +110 -0
  214. package/template/src/content/docs/zh/storage.mdx +51 -0
  215. package/template/src/content/legal/en/cookie-policy.mdx +78 -0
  216. package/template/src/content/legal/en/privacy.mdx +131 -0
  217. package/template/src/content/legal/en/terms.mdx +93 -0
  218. package/template/src/content/legal/zh/cookie-policy.mdx +78 -0
  219. package/template/src/content/legal/zh/privacy.mdx +131 -0
  220. package/template/src/content/legal/zh/terms.mdx +92 -0
  221. package/template/src/core/modules/contract.ts +225 -0
  222. package/template/src/core/modules/index.ts +1 -0
  223. package/template/src/core/services/ai.ts +31 -0
  224. package/template/src/core/services/common.ts +112 -0
  225. package/template/src/core/services/index.ts +8 -0
  226. package/template/src/core/services/jobs.ts +24 -0
  227. package/template/src/core/services/mail.ts +35 -0
  228. package/template/src/core/services/manifest.ts +51 -0
  229. package/template/src/core/services/payment.ts +92 -0
  230. package/template/src/core/services/rate-limit.ts +47 -0
  231. package/template/src/core/services/storage.ts +42 -0
  232. package/template/src/db/index.ts +71 -0
  233. package/template/src/db/schema/auth.ts +67 -0
  234. package/template/src/db/schema/credits.ts +94 -0
  235. package/template/src/db/schema/index.ts +5 -0
  236. package/template/src/db/schema/mail.ts +14 -0
  237. package/template/src/db/schema/subscription.ts +21 -0
  238. package/template/src/db/schema/support.ts +56 -0
  239. package/template/src/db/schema.ts +2 -0
  240. package/template/src/features/admin/components/admin-sidebar.tsx +213 -0
  241. package/template/src/features/admin/components/index.ts +2 -0
  242. package/template/src/features/admin/index.ts +3 -0
  243. package/template/src/features/admin/manifest.ts +41 -0
  244. package/template/src/features/analytics/components/analytics.tsx +62 -0
  245. package/template/src/features/analytics/components/index.ts +2 -0
  246. package/template/src/features/analytics/index.ts +3 -0
  247. package/template/src/features/analytics/manifest.ts +13 -0
  248. package/template/src/features/auth/components/auth-brand-panel.tsx +82 -0
  249. package/template/src/features/auth/components/auth-error-alert.tsx +28 -0
  250. package/template/src/features/auth/components/auth-footer.tsx +52 -0
  251. package/template/src/features/auth/components/auth-logo.tsx +30 -0
  252. package/template/src/features/auth/components/forgot-password-form.tsx +146 -0
  253. package/template/src/features/auth/components/index.ts +9 -0
  254. package/template/src/features/auth/components/sign-in-form.tsx +241 -0
  255. package/template/src/features/auth/components/sign-up-form.tsx +307 -0
  256. package/template/src/features/auth/index.ts +3 -0
  257. package/template/src/features/auth/manifest.ts +30 -0
  258. package/template/src/features/blog/components/blog-post-item.tsx +72 -0
  259. package/template/src/features/blog/components/index.ts +1 -0
  260. package/template/src/features/blog/data/mock-posts.ts +62 -0
  261. package/template/src/features/blog/index.ts +3 -0
  262. package/template/src/features/blog/manifest.ts +22 -0
  263. package/template/src/features/credits/account.ts +76 -0
  264. package/template/src/features/credits/actions.ts +427 -0
  265. package/template/src/features/credits/components/credit-balance-badge.tsx +54 -0
  266. package/template/src/features/credits/components/credit-usage-section.tsx +160 -0
  267. package/template/src/features/credits/components/index.ts +7 -0
  268. package/template/src/features/credits/components/transaction-history.tsx +346 -0
  269. package/template/src/features/credits/config.ts +70 -0
  270. package/template/src/features/credits/consume.ts +140 -0
  271. package/template/src/features/credits/core.ts +23 -0
  272. package/template/src/features/credits/errors.ts +26 -0
  273. package/template/src/features/credits/expire.ts +112 -0
  274. package/template/src/features/credits/grant.ts +168 -0
  275. package/template/src/features/credits/index.ts +6 -0
  276. package/template/src/features/credits/manifest.ts +36 -0
  277. package/template/src/features/credits/query.ts +65 -0
  278. package/template/src/features/credits/types.ts +48 -0
  279. package/template/src/features/dashboard/components/index.ts +3 -0
  280. package/template/src/features/dashboard/components/main-wrapper.tsx +100 -0
  281. package/template/src/features/dashboard/components/sidebar.tsx +341 -0
  282. package/template/src/features/dashboard/context/index.ts +2 -0
  283. package/template/src/features/dashboard/context/sidebar-context.tsx +72 -0
  284. package/template/src/features/dashboard/index.ts +4 -0
  285. package/template/src/features/dashboard/manifest.ts +28 -0
  286. package/template/src/features/mail/actions.ts +175 -0
  287. package/template/src/features/mail/index.ts +5 -0
  288. package/template/src/features/mail/manifest.ts +13 -0
  289. package/template/src/features/mail/server.ts +8 -0
  290. package/template/src/features/mail/templates/index.ts +11 -0
  291. package/template/src/features/mail/templates/primary-action-email.tsx +224 -0
  292. package/template/src/features/mail/templates/welcome-email.tsx +121 -0
  293. package/template/src/features/mail/utils.ts +190 -0
  294. package/template/src/features/marketing/components/animated-price.tsx +51 -0
  295. package/template/src/features/marketing/components/cookie-consent.tsx +255 -0
  296. package/template/src/features/marketing/components/cta-section.tsx +50 -0
  297. package/template/src/features/marketing/components/faq-section.tsx +59 -0
  298. package/template/src/features/marketing/components/feature-grid.tsx +80 -0
  299. package/template/src/features/marketing/components/footer.tsx +128 -0
  300. package/template/src/features/marketing/components/header.tsx +255 -0
  301. package/template/src/features/marketing/components/hero-section.tsx +323 -0
  302. package/template/src/features/marketing/components/how-it-works.tsx +115 -0
  303. package/template/src/features/marketing/components/index.ts +14 -0
  304. package/template/src/features/marketing/components/nav-menu.tsx +236 -0
  305. package/template/src/features/marketing/components/pricing-section.tsx +446 -0
  306. package/template/src/features/marketing/components/testimonials.tsx +59 -0
  307. package/template/src/features/marketing/components/use-cases-section.tsx +78 -0
  308. package/template/src/features/marketing/index.ts +3 -0
  309. package/template/src/features/marketing/manifest.ts +35 -0
  310. package/template/src/features/payment/actions.ts +182 -0
  311. package/template/src/features/payment/index.ts +4 -0
  312. package/template/src/features/payment/manifest.ts +18 -0
  313. package/template/src/features/payment/server.ts +7 -0
  314. package/template/src/features/payment/types.ts +202 -0
  315. package/template/src/features/pseo/components/index.ts +6 -0
  316. package/template/src/features/pseo/components/pseo-cta.tsx +51 -0
  317. package/template/src/features/pseo/components/pseo-faq.tsx +40 -0
  318. package/template/src/features/pseo/components/pseo-feature-grid.tsx +74 -0
  319. package/template/src/features/pseo/components/pseo-hero.tsx +106 -0
  320. package/template/src/features/pseo/components/pseo-related.tsx +62 -0
  321. package/template/src/features/pseo/components/pseo-use-cases.tsx +50 -0
  322. package/template/src/features/pseo/data/pseo-pages.json +534 -0
  323. package/template/src/features/pseo/index.ts +4 -0
  324. package/template/src/features/pseo/lib/pseo-data.ts +140 -0
  325. package/template/src/features/pseo/manifest.ts +22 -0
  326. package/template/src/features/settings/actions/delete-account.ts +50 -0
  327. package/template/src/features/settings/actions/index.ts +3 -0
  328. package/template/src/features/settings/actions/update-profile.ts +49 -0
  329. package/template/src/features/settings/components/billing-section.tsx +283 -0
  330. package/template/src/features/settings/components/index.ts +3 -0
  331. package/template/src/features/settings/components/profile-form.tsx +154 -0
  332. package/template/src/features/settings/components/security-section.tsx +158 -0
  333. package/template/src/features/settings/components/settings-profile-view.tsx +506 -0
  334. package/template/src/features/settings/index.ts +5 -0
  335. package/template/src/features/settings/manifest.ts +28 -0
  336. package/template/src/features/settings/schemas/index.ts +2 -0
  337. package/template/src/features/settings/schemas/update-profile.ts +21 -0
  338. package/template/src/features/shared/components/cookie-settings-dialog.tsx +282 -0
  339. package/template/src/features/shared/components/index.ts +5 -0
  340. package/template/src/features/shared/components/language-switcher.tsx +82 -0
  341. package/template/src/features/shared/components/max-width-wrapper.tsx +49 -0
  342. package/template/src/features/shared/components/mode-toggle.tsx +119 -0
  343. package/template/src/features/shared/icons/google-icon.tsx +39 -0
  344. package/template/src/features/shared/icons/index.ts +7 -0
  345. package/template/src/features/shared/index.ts +5 -0
  346. package/template/src/features/shared/manifest.ts +13 -0
  347. package/template/src/features/shared/providers.tsx +59 -0
  348. package/template/src/features/storage/actions.ts +107 -0
  349. package/template/src/features/storage/index.ts +6 -0
  350. package/template/src/features/storage/manifest.ts +22 -0
  351. package/template/src/features/storage/types.ts +143 -0
  352. package/template/src/features/storage/utils.ts +85 -0
  353. package/template/src/features/storage/validation.ts +151 -0
  354. package/template/src/features/subscription/actions/get-user-plan.ts +26 -0
  355. package/template/src/features/subscription/actions/index.ts +5 -0
  356. package/template/src/features/subscription/components/current-plan-badge.tsx +18 -0
  357. package/template/src/features/subscription/components/index.ts +6 -0
  358. package/template/src/features/subscription/components/plan-badge.tsx +98 -0
  359. package/template/src/features/subscription/index.ts +25 -0
  360. package/template/src/features/subscription/manifest.ts +13 -0
  361. package/template/src/features/subscription/services/user-plan.ts +181 -0
  362. package/template/src/features/support/actions/admin-ticket.ts +172 -0
  363. package/template/src/features/support/actions/admin-users.ts +295 -0
  364. package/template/src/features/support/actions/index.ts +22 -0
  365. package/template/src/features/support/actions/ticket.ts +12 -0
  366. package/template/src/features/support/actions/user-ticket.ts +153 -0
  367. package/template/src/features/support/components/admin-ticket-reply-form.tsx +109 -0
  368. package/template/src/features/support/components/admin-ticket-status-select.tsx +119 -0
  369. package/template/src/features/support/components/index.ts +6 -0
  370. package/template/src/features/support/components/ticket-message-form.tsx +99 -0
  371. package/template/src/features/support/components/user-role-select.tsx +107 -0
  372. package/template/src/features/support/index.ts +5 -0
  373. package/template/src/features/support/manifest.ts +53 -0
  374. package/template/src/features/support/schemas/index.ts +15 -0
  375. package/template/src/features/support/schemas/ticket.ts +81 -0
  376. package/template/src/hooks/index.ts +2 -0
  377. package/template/src/i18n/index.ts +12 -0
  378. package/template/src/i18n/request.ts +24 -0
  379. package/template/src/i18n/routing.ts +23 -0
  380. package/template/src/instrumentation.ts +19 -0
  381. package/template/src/lib/ai/index.ts +16 -0
  382. package/template/src/lib/api-logger.ts +24 -0
  383. package/template/src/lib/auth/admin.ts +54 -0
  384. package/template/src/lib/auth/api.ts +17 -0
  385. package/template/src/lib/auth/client.ts +227 -0
  386. package/template/src/lib/auth/edge.ts +84 -0
  387. package/template/src/lib/auth/errors.ts +99 -0
  388. package/template/src/lib/auth/index.ts +159 -0
  389. package/template/src/lib/auth/server.ts +46 -0
  390. package/template/src/lib/auth/session-context.tsx +96 -0
  391. package/template/src/lib/cloudflare/bindings.ts +31 -0
  392. package/template/src/lib/cookie-consent.ts +33 -0
  393. package/template/src/lib/file-utils.ts +45 -0
  394. package/template/src/lib/fingerprint/index.ts +73 -0
  395. package/template/src/lib/health/checks.ts +6 -0
  396. package/template/src/lib/health/core.ts +86 -0
  397. package/template/src/lib/health/index.ts +11 -0
  398. package/template/src/lib/health/types.ts +13 -0
  399. package/template/src/lib/index.ts +2 -0
  400. package/template/src/lib/logger/index.ts +258 -0
  401. package/template/src/lib/monitoring/index.ts +384 -0
  402. package/template/src/lib/rate-limit/anonymous.ts +85 -0
  403. package/template/src/lib/rate-limit/index.ts +111 -0
  404. package/template/src/lib/safe-action.ts +127 -0
  405. package/template/src/lib/seo/json-ld.ts +188 -0
  406. package/template/src/lib/source.ts +122 -0
  407. package/template/src/lib/utils.ts +6 -0
  408. package/template/src/middleware.ts +162 -0
  409. package/template/src/modules/index.ts +10 -0
  410. package/template/src/modules/presets.ts +8 -0
  411. package/template/src/modules/registry.ts +34 -0
  412. package/template/src/modules/selection.ts +45 -0
  413. package/template/src/services/ai.ts +49 -0
  414. package/template/src/services/index.ts +6 -0
  415. package/template/src/services/jobs.ts +3 -0
  416. package/template/src/services/mail.ts +7 -0
  417. package/template/src/services/payment.ts +3 -0
  418. package/template/src/services/rate-limit.ts +12 -0
  419. package/template/src/services/storage.ts +3 -0
  420. package/template/src/types/fumadocs-source.d.ts +14 -0
  421. package/template/src/types/index.ts +2 -0
  422. package/template/tsconfig.json +53 -0
  423. package/template/vercel.json +11 -0
  424. package/template/vitest.config.ts +51 -0
  425. package/template/wrangler.jsonc +30 -0
@@ -0,0 +1,159 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
+
4
+ import { db } from "@/db";
5
+ import * as schema from "@/db/schema/auth";
6
+ import {
7
+ ResetPasswordEmail,
8
+ VerifyEmailEmail,
9
+ sendEmail,
10
+ } from "@/features/mail/server";
11
+ import { isMailServiceConfigured } from "@/services/mail";
12
+
13
+ const isEmailConfigured = isMailServiceConfigured();
14
+
15
+ /**
16
+ * Better Auth 服务端配置
17
+ *
18
+ * 此文件配置 Better Auth 的核心功能:
19
+ * - 数据库适配器 (Drizzle + PostgreSQL)
20
+ * - OAuth 提供商 (GitHub, Google)
21
+ * - 会话配置
22
+ * - 用户自定义字段
23
+ */
24
+ export const auth = betterAuth({
25
+ /**
26
+ * 基础 URL 配置
27
+ * 用于 OAuth 回调和邮件链接
28
+ */
29
+ baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
30
+
31
+ /**
32
+ * 信任的来源
33
+ * 允许从这些来源发起认证请求
34
+ */
35
+ trustedOrigins: [process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"],
36
+
37
+ /**
38
+ * 数据库配置
39
+ * 使用 Drizzle 适配器连接 PostgreSQL
40
+ */
41
+ database: drizzleAdapter(db, {
42
+ provider: "pg",
43
+ schema: {
44
+ user: schema.user,
45
+ session: schema.session,
46
+ account: schema.account,
47
+ verification: schema.verification,
48
+ },
49
+ }),
50
+
51
+ /**
52
+ * 用户自定义字段配置
53
+ * 将 role, banned, bannedReason 字段包含在会话用户中
54
+ */
55
+ user: {
56
+ additionalFields: {
57
+ role: {
58
+ type: "string",
59
+ required: false,
60
+ defaultValue: "user",
61
+ input: false, // 用户不能通过注册/更新设置此字段
62
+ },
63
+ banned: {
64
+ type: "boolean",
65
+ required: false,
66
+ defaultValue: false,
67
+ input: false, // 用户不能通过注册/更新设置此字段
68
+ },
69
+ bannedReason: {
70
+ type: "string",
71
+ required: false,
72
+ input: false, // 用户不能通过注册/更新设置此字段
73
+ },
74
+ },
75
+ },
76
+
77
+ /**
78
+ * 邮箱密码认证配置
79
+ */
80
+ emailAndPassword: {
81
+ enabled: true,
82
+ requireEmailVerification: isEmailConfigured,
83
+ sendResetPassword: async ({ user, url }) => {
84
+ await sendEmail({
85
+ to: user.email,
86
+ subject: "Reset your password - NextDevTpl",
87
+ react: ResetPasswordEmail({
88
+ resetUrl: url,
89
+ name: user.name || "there",
90
+ }),
91
+ });
92
+ },
93
+ },
94
+
95
+ /**
96
+ * 邮箱验证配置
97
+ */
98
+ ...(isEmailConfigured
99
+ ? {
100
+ emailVerification: {
101
+ sendOnSignUp: true,
102
+ sendVerificationEmail: async ({ user, url }) => {
103
+ await sendEmail({
104
+ to: user.email,
105
+ subject: "Verify your email - NextDevTpl",
106
+ react: VerifyEmailEmail({
107
+ verifyUrl: url,
108
+ name: user.name || "there",
109
+ }),
110
+ });
111
+ },
112
+ },
113
+ }
114
+ : {}),
115
+
116
+ /**
117
+ * OAuth 社交登录提供商配置
118
+ * 需要在 .env 中配置相应的 Client ID 和 Secret
119
+ */
120
+ socialProviders: {
121
+ /**
122
+ * GitHub OAuth 配置
123
+ * 获取凭证: https://github.com/settings/developers
124
+ */
125
+ github: {
126
+ clientId: process.env.GITHUB_CLIENT_ID || "",
127
+ clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
128
+ },
129
+
130
+ /**
131
+ * Google OAuth 配置
132
+ * 获取凭证: https://console.cloud.google.com/apis/credentials
133
+ */
134
+ google: {
135
+ clientId: process.env.GOOGLE_CLIENT_ID || "",
136
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
137
+ },
138
+ },
139
+
140
+ /**
141
+ * 会话配置
142
+ */
143
+ session: {
144
+ // 会话过期时间: 7 天
145
+ expiresIn: 60 * 60 * 24 * 7,
146
+ // 刷新阈值: 1 天 (会话剩余不足 1 天时自动刷新)
147
+ updateAge: 60 * 60 * 24,
148
+ // 使用 Cookie 存储会话
149
+ cookieCache: {
150
+ enabled: true,
151
+ maxAge: 60 * 5, // 5 分钟缓存
152
+ },
153
+ },
154
+ });
155
+
156
+ /**
157
+ * 导出类型以供其他模块使用
158
+ */
159
+ export type Auth = typeof auth;
@@ -0,0 +1,46 @@
1
+ import { headers } from "next/headers";
2
+
3
+ import { auth } from "./index";
4
+
5
+ /**
6
+ * 服务器端获取当前用户会话
7
+ *
8
+ * 用于 Server Components 和 Server Actions 中获取用户信息
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * // 在 Server Component 中使用
13
+ * export default async function Page() {
14
+ * const session = await getServerSession();
15
+ * if (!session) {
16
+ * redirect("/sign-in");
17
+ * }
18
+ * return <div>Welcome, {session.user.name}</div>;
19
+ * }
20
+ * ```
21
+ */
22
+ export async function getServerSession() {
23
+ return auth.api.getSession({
24
+ headers: await headers(),
25
+ });
26
+ }
27
+
28
+ /**
29
+ * 获取当前用户
30
+ *
31
+ * 便捷方法,直接返回用户对象或 null
32
+ */
33
+ export async function getCurrentUser() {
34
+ const session = await getServerSession();
35
+ return session?.user ?? null;
36
+ }
37
+
38
+ /**
39
+ * 检查用户是否已认证
40
+ *
41
+ * @returns boolean - 用户是否已登录
42
+ */
43
+ export async function isAuthenticated() {
44
+ const session = await getServerSession();
45
+ return !!session?.user;
46
+ }
@@ -0,0 +1,96 @@
1
+ "use client";
2
+
3
+ import { createContext, type ReactNode, useContext } from "react";
4
+
5
+ import { useSession } from "./client";
6
+
7
+ /**
8
+ * 会话用户类型
9
+ */
10
+ interface SessionUser {
11
+ id: string;
12
+ name: string;
13
+ email: string;
14
+ image?: string | null | undefined;
15
+ emailVerified: boolean;
16
+ }
17
+
18
+ /**
19
+ * 会话上下文类型
20
+ */
21
+ interface SessionContextType {
22
+ /** 当前用户,未登录时为 null */
23
+ user: SessionUser | null;
24
+ /** 是否正在加载会话 */
25
+ isLoading: boolean;
26
+ /** 是否已认证 */
27
+ isAuthenticated: boolean;
28
+ }
29
+
30
+ /**
31
+ * 会话上下文
32
+ */
33
+ const SessionContext = createContext<SessionContextType | undefined>(undefined);
34
+
35
+ /**
36
+ * 会话提供者组件
37
+ *
38
+ * 包裹应用根组件,提供全局会话状态
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * // app/layout.tsx
43
+ * import { SessionProvider } from "@/lib/auth/session-context";
44
+ *
45
+ * export default function RootLayout({ children }) {
46
+ * return (
47
+ * <html>
48
+ * <body>
49
+ * <SessionProvider>{children}</SessionProvider>
50
+ * </body>
51
+ * </html>
52
+ * );
53
+ * }
54
+ * ```
55
+ */
56
+ export function SessionProvider({ children }: { children: ReactNode }) {
57
+ const { data: session, isPending } = useSession();
58
+
59
+ const value: SessionContextType = {
60
+ user: session?.user ?? null,
61
+ isLoading: isPending,
62
+ isAuthenticated: !!session?.user,
63
+ };
64
+
65
+ return (
66
+ <SessionContext.Provider value={value}>{children}</SessionContext.Provider>
67
+ );
68
+ }
69
+
70
+ /**
71
+ * 使用会话上下文的 Hook
72
+ *
73
+ * @returns 会话上下文值
74
+ * @throws 如果在 SessionProvider 外部使用
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * function UserProfile() {
79
+ * const { user, isLoading, isAuthenticated } = useSessionContext();
80
+ *
81
+ * if (isLoading) return <Skeleton />;
82
+ * if (!isAuthenticated) return <LoginButton />;
83
+ *
84
+ * return <div>Welcome, {user.name}</div>;
85
+ * }
86
+ * ```
87
+ */
88
+ export function useSessionContext() {
89
+ const context = useContext(SessionContext);
90
+
91
+ if (context === undefined) {
92
+ throw new Error("useSessionContext 必须在 SessionProvider 内部使用");
93
+ }
94
+
95
+ return context;
96
+ }
@@ -0,0 +1,31 @@
1
+ import { getCloudflareContext } from "@opennextjs/cloudflare";
2
+
3
+ function getBinding<T extends object>(bindingName: string): T {
4
+ const env = getCloudflareContext().env;
5
+ const binding: unknown = Reflect.get(env, bindingName);
6
+ if (typeof binding !== "object" || binding === null) {
7
+ throw new Error(`Cloudflare binding is not configured: ${bindingName}`);
8
+ }
9
+ return binding as T;
10
+ }
11
+
12
+ export function createLazyCloudflareBinding<T extends object>(
13
+ bindingName: string
14
+ ): T {
15
+ return new Proxy({} as T, {
16
+ get(_target, property) {
17
+ const binding = getBinding<T>(bindingName);
18
+ const value = Reflect.get(binding, property, binding) as unknown;
19
+ return typeof value === "function" ? value.bind(binding) : value;
20
+ },
21
+ });
22
+ }
23
+
24
+ export function createCloudflareBindingMap<T extends object>(
25
+ bindingName: string
26
+ ): Readonly<Record<string, T>> {
27
+ const binding = createLazyCloudflareBinding<T>(bindingName);
28
+ return new Proxy(Object.create(null) as Record<string, T>, {
29
+ get: () => binding,
30
+ });
31
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Cookie 同意相关常量
3
+ *
4
+ * 集中管理 Cookie 同意功能的常量,确保类型安全
5
+ */
6
+
7
+ /**
8
+ * Cookie 同意类型
9
+ */
10
+ export type CookieConsentType = "all" | "essential" | null;
11
+
12
+ /**
13
+ * Cookie 偏好设置
14
+ */
15
+ export interface CookiePreferences {
16
+ analytics: boolean;
17
+ marketing: boolean;
18
+ }
19
+
20
+ /**
21
+ * localStorage 存储键名
22
+ */
23
+ export const COOKIE_CONSENT_KEY = "cookie-consent";
24
+
25
+ /**
26
+ * Cookie 偏好存储键名
27
+ */
28
+ export const COOKIE_PREFERENCES_KEY = "cookie-preferences";
29
+
30
+ /**
31
+ * Cookie 同意变更事件名
32
+ */
33
+ export const COOKIE_CONSENT_CHANGE_EVENT = "cookie-consent-change";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 支持的文件类型
3
+ */
4
+ export type SupportedFileType = "pdf" | "docx" | "md" | "txt";
5
+
6
+ /**
7
+ * 文件类型 MIME 映射
8
+ */
9
+ export const FILE_MIME_TYPES: Record<string, SupportedFileType> = {
10
+ "application/pdf": "pdf",
11
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
12
+ "docx",
13
+ "text/markdown": "md",
14
+ "text/plain": "txt",
15
+ };
16
+
17
+ /**
18
+ * 文件扩展名映射
19
+ */
20
+ export const FILE_EXTENSIONS: Record<string, SupportedFileType> = {
21
+ ".pdf": "pdf",
22
+ ".docx": "docx",
23
+ ".md": "md",
24
+ ".txt": "txt",
25
+ };
26
+
27
+ /**
28
+ * 从文件名获取文件类型
29
+ */
30
+ export function getFileTypeFromName(
31
+ filename: string
32
+ ): SupportedFileType | null {
33
+ const ext = filename.toLowerCase().match(/\.[^.]+$/)?.[0];
34
+ if (!ext) return null;
35
+ return FILE_EXTENSIONS[ext] || null;
36
+ }
37
+
38
+ /**
39
+ * 从 MIME 类型获取文件类型
40
+ */
41
+ export function getFileTypeFromMime(
42
+ mimeType: string
43
+ ): SupportedFileType | null {
44
+ return FILE_MIME_TYPES[mimeType] || null;
45
+ }
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ import FingerprintJS, { type GetResult } from "@fingerprintjs/fingerprintjs";
4
+
5
+ // Singleton promise for the fingerprint agent
6
+ let fpPromise: Promise<GetResult> | null = null;
7
+
8
+ /**
9
+ * Initialize and get the device fingerprint
10
+ *
11
+ * Uses FingerprintJS open source library to generate a visitor ID
12
+ * based on browser characteristics.
13
+ *
14
+ * @returns Promise resolving to the visitor ID string
15
+ */
16
+ export async function getDeviceFingerprint(): Promise<string> {
17
+ if (typeof window === "undefined") {
18
+ return "";
19
+ }
20
+
21
+ try {
22
+ // Initialize the agent at application startup
23
+ if (!fpPromise) {
24
+ fpPromise = FingerprintJS.load().then((fp) => fp.get());
25
+ }
26
+
27
+ const result = await fpPromise;
28
+ return result.visitorId;
29
+ } catch (error) {
30
+ console.error("Failed to get device fingerprint:", error);
31
+ return "";
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get fingerprint and add it to request headers
37
+ *
38
+ * @param headers - Existing headers object to extend
39
+ * @returns Headers object with X-Device-Fingerprint added
40
+ */
41
+ export async function addFingerprintHeader(
42
+ headers: Record<string, string> = {}
43
+ ): Promise<Record<string, string>> {
44
+ const fingerprint = await getDeviceFingerprint();
45
+ if (fingerprint) {
46
+ return {
47
+ ...headers,
48
+ "X-Device-Fingerprint": fingerprint,
49
+ };
50
+ }
51
+ return headers;
52
+ }
53
+
54
+ /**
55
+ * React hook to get the device fingerprint
56
+ *
57
+ * Usage:
58
+ * ```tsx
59
+ * const fingerprint = useDeviceFingerprint();
60
+ * ```
61
+ */
62
+ export function useDeviceFingerprint(): string | null {
63
+ const [fingerprint, setFingerprint] = useState<string | null>(null);
64
+
65
+ useEffect(() => {
66
+ getDeviceFingerprint().then(setFingerprint);
67
+ }, []);
68
+
69
+ return fingerprint;
70
+ }
71
+
72
+ // Need to import useState and useEffect for the hook
73
+ import { useEffect, useState } from "react";
@@ -0,0 +1,6 @@
1
+ import { sql } from "drizzle-orm";
2
+
3
+ export async function probeDatabase(): Promise<void> {
4
+ const { db } = await import("@/db");
5
+ await db.execute(sql`SELECT 1`);
6
+ }
@@ -0,0 +1,86 @@
1
+ import type { HealthCheckResult, HealthReport } from "./types";
2
+
3
+ const REQUIRED_ENVIRONMENT_KEYS = [
4
+ "DATABASE_URL",
5
+ "BETTER_AUTH_SECRET",
6
+ "BETTER_AUTH_URL",
7
+ "NEXT_PUBLIC_APP_URL",
8
+ ] as const;
9
+
10
+ function isHttpUrl(value: string): boolean {
11
+ try {
12
+ const protocol = new URL(value).protocol;
13
+ return protocol === "http:" || protocol === "https:";
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ function isPostgresUrl(value: string): boolean {
20
+ try {
21
+ const protocol = new URL(value).protocol;
22
+ return protocol === "postgres:" || protocol === "postgresql:";
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ export function validateDeploymentEnvironment(
29
+ environment: Readonly<Record<string, string | undefined>>
30
+ ): readonly string[] {
31
+ const invalid = REQUIRED_ENVIRONMENT_KEYS.filter(
32
+ (key) => !environment[key]?.trim()
33
+ );
34
+ const databaseUrl = environment.DATABASE_URL?.trim();
35
+ const authUrl = environment.BETTER_AUTH_URL?.trim();
36
+ const appUrl = environment.NEXT_PUBLIC_APP_URL?.trim();
37
+
38
+ if (databaseUrl && !isPostgresUrl(databaseUrl)) invalid.push("DATABASE_URL");
39
+ if (authUrl && !isHttpUrl(authUrl)) invalid.push("BETTER_AUTH_URL");
40
+ if (appUrl && !isHttpUrl(appUrl)) invalid.push("NEXT_PUBLIC_APP_URL");
41
+
42
+ return [...new Set(invalid)];
43
+ }
44
+
45
+ export function createHealthReport(input: {
46
+ checks: Readonly<Record<string, HealthCheckResult>>;
47
+ now?: Date;
48
+ version: string;
49
+ }): HealthReport {
50
+ const healthy = Object.values(input.checks).every(
51
+ (check) => check.status === "pass"
52
+ );
53
+
54
+ return {
55
+ status: healthy ? "healthy" : "unhealthy",
56
+ version: input.version,
57
+ timestamp: (input.now ?? new Date()).toISOString(),
58
+ checks: input.checks,
59
+ };
60
+ }
61
+
62
+ export async function runHealthProbe(
63
+ probe: () => Promise<void>,
64
+ options: { now?: () => number; timeoutMs: number }
65
+ ): Promise<HealthCheckResult> {
66
+ const now = options.now ?? Date.now;
67
+ const startedAt = now();
68
+ let timer: ReturnType<typeof setTimeout> | undefined;
69
+
70
+ try {
71
+ await Promise.race([
72
+ probe(),
73
+ new Promise<never>((_, reject) => {
74
+ timer = setTimeout(
75
+ () => reject(new Error("Health probe timed out")),
76
+ options.timeoutMs
77
+ );
78
+ }),
79
+ ]);
80
+ return { status: "pass", durationMs: Math.max(0, now() - startedAt) };
81
+ } catch {
82
+ return { status: "fail", durationMs: Math.max(0, now() - startedAt) };
83
+ } finally {
84
+ if (timer) clearTimeout(timer);
85
+ }
86
+ }
@@ -0,0 +1,11 @@
1
+ export { probeDatabase } from "./checks";
2
+ export {
3
+ createHealthReport,
4
+ runHealthProbe,
5
+ validateDeploymentEnvironment,
6
+ } from "./core";
7
+ export type {
8
+ HealthCheckResult,
9
+ HealthCheckStatus,
10
+ HealthReport,
11
+ } from "./types";
@@ -0,0 +1,13 @@
1
+ export type HealthCheckStatus = "fail" | "pass";
2
+
3
+ export interface HealthCheckResult {
4
+ durationMs: number;
5
+ status: HealthCheckStatus;
6
+ }
7
+
8
+ export interface HealthReport {
9
+ checks: Readonly<Record<string, HealthCheckResult>>;
10
+ status: "healthy" | "unhealthy";
11
+ timestamp: string;
12
+ version: string;
13
+ }
@@ -0,0 +1,2 @@
1
+ // Utility functions and configurations
2
+ export {};