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,557 @@
1
+ "use client";
2
+
3
+ import { Ban, Coins, Loader2, Search, UserCheck } from "lucide-react";
4
+ import { useCallback, useEffect, useState } from "react";
5
+ import { toast } from "sonner";
6
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
7
+ import { Badge } from "@/components/ui/badge";
8
+ import { Button } from "@/components/ui/button";
9
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogDescription,
14
+ DialogFooter,
15
+ DialogHeader,
16
+ DialogTitle,
17
+ } from "@/components/ui/dialog";
18
+ import { Input } from "@/components/ui/input";
19
+ import { Label } from "@/components/ui/label";
20
+ import { Textarea } from "@/components/ui/textarea";
21
+ import {
22
+ adminGrantCreditsAction,
23
+ banUserAction,
24
+ getAllUsersAction,
25
+ UserRoleSelect,
26
+ } from "@/features/support";
27
+
28
+ /**
29
+ * 用户类型定义
30
+ */
31
+ interface UserWithDetails {
32
+ id: string;
33
+ name: string;
34
+ email: string;
35
+ image: string | null;
36
+ role: "user" | "admin";
37
+ banned: boolean;
38
+ bannedReason: string | null;
39
+ emailVerified: boolean;
40
+ createdAt: Date;
41
+ credits: {
42
+ balance: number;
43
+ totalEarned: number;
44
+ totalSpent: number;
45
+ status: "active" | "frozen";
46
+ } | null;
47
+ subscription: {
48
+ status: string;
49
+ priceId: string;
50
+ currentPeriodEnd: Date | null;
51
+ } | null;
52
+ }
53
+
54
+ /**
55
+ * 管理员 - 用户管理页面 (客户端组件)
56
+ *
57
+ * 功能:
58
+ * - 搜索用户 (邮箱/名称)
59
+ * - 查看积分余额
60
+ * - 查看订阅状态
61
+ * - 修改用户角色
62
+ * - 封禁/解封用户
63
+ * - 手动充值积分
64
+ */
65
+ export default function AdminUsersPage() {
66
+ const [users, setUsers] = useState<UserWithDetails[]>([]);
67
+ const [isLoading, setIsLoading] = useState(true);
68
+ const [searchQuery, setSearchQuery] = useState("");
69
+ const [searchInput, setSearchInput] = useState("");
70
+
71
+ // 封禁对话框状态
72
+ const [banDialogOpen, setBanDialogOpen] = useState(false);
73
+ const [selectedUser, setSelectedUser] = useState<UserWithDetails | null>(
74
+ null
75
+ );
76
+ const [banReason, setBanReason] = useState("");
77
+ const [isBanning, setIsBanning] = useState(false);
78
+
79
+ // 充值对话框状态
80
+ const [grantDialogOpen, setGrantDialogOpen] = useState(false);
81
+ const [grantAmount, setGrantAmount] = useState("");
82
+ const [grantReason, setGrantReason] = useState("");
83
+ const [isGranting, setIsGranting] = useState(false);
84
+
85
+ /**
86
+ * 加载用户列表
87
+ */
88
+ const loadUsers = useCallback(async (query?: string) => {
89
+ setIsLoading(true);
90
+ try {
91
+ const result = await getAllUsersAction(query ? { query } : undefined);
92
+ if (result?.data?.users) {
93
+ setUsers(result.data.users as UserWithDetails[]);
94
+ }
95
+ } catch (error) {
96
+ toast.error("加载用户列表失败");
97
+ console.error(error);
98
+ } finally {
99
+ setIsLoading(false);
100
+ }
101
+ }, []);
102
+
103
+ // 初始加载
104
+ useEffect(() => {
105
+ loadUsers();
106
+ }, [loadUsers]);
107
+
108
+ /**
109
+ * 处理搜索
110
+ */
111
+ const handleSearch = (e: React.FormEvent) => {
112
+ e.preventDefault();
113
+ setSearchQuery(searchInput);
114
+ loadUsers(searchInput);
115
+ };
116
+
117
+ /**
118
+ * 打开封禁对话框
119
+ */
120
+ const openBanDialog = (user: UserWithDetails) => {
121
+ setSelectedUser(user);
122
+ setBanReason("");
123
+ setBanDialogOpen(true);
124
+ };
125
+
126
+ /**
127
+ * 处理封禁/解封
128
+ */
129
+ const handleBan = async () => {
130
+ if (!selectedUser) return;
131
+
132
+ setIsBanning(true);
133
+ try {
134
+ const result = await banUserAction({
135
+ userId: selectedUser.id,
136
+ banned: !selectedUser.banned,
137
+ reason: banReason || undefined,
138
+ });
139
+
140
+ if (result?.data) {
141
+ toast.success(result.data.message);
142
+ setBanDialogOpen(false);
143
+ loadUsers(searchQuery);
144
+ } else if (result?.serverError) {
145
+ toast.error(result.serverError);
146
+ }
147
+ } catch (error) {
148
+ toast.error("操作失败");
149
+ console.error(error);
150
+ } finally {
151
+ setIsBanning(false);
152
+ }
153
+ };
154
+
155
+ /**
156
+ * 打开充值对话框
157
+ */
158
+ const openGrantDialog = (user: UserWithDetails) => {
159
+ setSelectedUser(user);
160
+ setGrantAmount("");
161
+ setGrantReason("");
162
+ setGrantDialogOpen(true);
163
+ };
164
+
165
+ /**
166
+ * 处理充值
167
+ */
168
+ const handleGrant = async () => {
169
+ if (!selectedUser) return;
170
+
171
+ const amount = parseInt(grantAmount, 10);
172
+ if (Number.isNaN(amount) || amount <= 0) {
173
+ toast.error("请输入有效的积分数量");
174
+ return;
175
+ }
176
+
177
+ if (!grantReason.trim()) {
178
+ toast.error("请填写充值原因");
179
+ return;
180
+ }
181
+
182
+ setIsGranting(true);
183
+ try {
184
+ const result = await adminGrantCreditsAction({
185
+ userId: selectedUser.id,
186
+ amount,
187
+ reason: grantReason.trim(),
188
+ });
189
+
190
+ if (result?.data) {
191
+ toast.success(result.data.message);
192
+ setGrantDialogOpen(false);
193
+ loadUsers(searchQuery);
194
+ } else if (result?.serverError) {
195
+ toast.error(result.serverError);
196
+ }
197
+ } catch (error) {
198
+ toast.error("充值失败");
199
+ console.error(error);
200
+ } finally {
201
+ setIsGranting(false);
202
+ }
203
+ };
204
+
205
+ /**
206
+ * 获取用户名首字母
207
+ */
208
+ const getInitials = (name: string) => {
209
+ return name
210
+ .split(" ")
211
+ .map((n) => n[0])
212
+ .join("")
213
+ .toUpperCase()
214
+ .slice(0, 2);
215
+ };
216
+
217
+ /**
218
+ * 获取订阅状态显示
219
+ */
220
+ const getSubscriptionBadge = (sub: UserWithDetails["subscription"]) => {
221
+ if (!sub) {
222
+ return (
223
+ <Badge variant="secondary" className="bg-muted text-muted-foreground">
224
+ 无订阅
225
+ </Badge>
226
+ );
227
+ }
228
+
229
+ const statusMap: Record<string, { label: string; color: string }> = {
230
+ active: { label: "订阅中", color: "bg-success/15 text-success" },
231
+ canceled: { label: "已取消", color: "bg-warning/15 text-warning" },
232
+ past_due: { label: "逾期", color: "bg-destructive/15 text-destructive" },
233
+ incomplete: { label: "未完成", color: "bg-muted text-muted-foreground" },
234
+ };
235
+
236
+ // 获取配置,使用默认值避免 undefined
237
+ const defaultConfig = {
238
+ label: "未完成",
239
+ color: "bg-muted text-muted-foreground",
240
+ };
241
+ const config = statusMap[sub.status] ?? defaultConfig;
242
+ return (
243
+ <Badge variant="secondary" className={config.color}>
244
+ {config.label}
245
+ </Badge>
246
+ );
247
+ };
248
+
249
+ // 统计数据
250
+ const totalUsers = users.length;
251
+ const adminCount = users.filter((u) => u.role === "admin").length;
252
+ const bannedCount = users.filter((u) => u.banned).length;
253
+ const activeSubscriptions = users.filter(
254
+ (u) => u.subscription?.status === "active"
255
+ ).length;
256
+
257
+ return (
258
+ <div className="space-y-6">
259
+ {/* 页面标题 */}
260
+ <div>
261
+ <h2 className="text-2xl font-bold tracking-tight">用户管理</h2>
262
+ <p className="text-muted-foreground">查看和管理系统中的所有用户</p>
263
+ </div>
264
+
265
+ {/* 统计信息 */}
266
+ <div className="grid gap-4 md:grid-cols-4">
267
+ <Card>
268
+ <CardHeader className="pb-2">
269
+ <CardTitle className="text-sm font-medium">总用户数</CardTitle>
270
+ </CardHeader>
271
+ <CardContent>
272
+ <div className="text-2xl font-bold">{totalUsers}</div>
273
+ </CardContent>
274
+ </Card>
275
+ <Card>
276
+ <CardHeader className="pb-2">
277
+ <CardTitle className="text-sm font-medium">管理员</CardTitle>
278
+ </CardHeader>
279
+ <CardContent>
280
+ <div className="text-2xl font-bold text-primary">{adminCount}</div>
281
+ </CardContent>
282
+ </Card>
283
+ <Card>
284
+ <CardHeader className="pb-2">
285
+ <CardTitle className="text-sm font-medium">订阅用户</CardTitle>
286
+ </CardHeader>
287
+ <CardContent>
288
+ <div className="text-2xl font-bold text-success">
289
+ {activeSubscriptions}
290
+ </div>
291
+ </CardContent>
292
+ </Card>
293
+ <Card>
294
+ <CardHeader className="pb-2">
295
+ <CardTitle className="text-sm font-medium">已封禁</CardTitle>
296
+ </CardHeader>
297
+ <CardContent>
298
+ <div className="text-2xl font-bold text-destructive">
299
+ {bannedCount}
300
+ </div>
301
+ </CardContent>
302
+ </Card>
303
+ </div>
304
+
305
+ {/* 搜索栏 */}
306
+ <Card>
307
+ <CardContent className="pt-6">
308
+ <form onSubmit={handleSearch} className="flex gap-4">
309
+ <div className="relative flex-1">
310
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
311
+ <Input
312
+ placeholder="搜索邮箱或用户名..."
313
+ value={searchInput}
314
+ onChange={(e) => setSearchInput(e.target.value)}
315
+ className="pl-10"
316
+ />
317
+ </div>
318
+ <Button type="submit" disabled={isLoading}>
319
+ {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
320
+ 搜索
321
+ </Button>
322
+ {searchQuery && (
323
+ <Button
324
+ type="button"
325
+ variant="outline"
326
+ onClick={() => {
327
+ setSearchInput("");
328
+ setSearchQuery("");
329
+ loadUsers();
330
+ }}
331
+ >
332
+ 清除
333
+ </Button>
334
+ )}
335
+ </form>
336
+ </CardContent>
337
+ </Card>
338
+
339
+ {/* 用户列表 */}
340
+ <Card>
341
+ <CardHeader>
342
+ <CardTitle>
343
+ 用户列表
344
+ {searchQuery && (
345
+ <span className="ml-2 text-sm font-normal text-muted-foreground">
346
+ 搜索: "{searchQuery}"
347
+ </span>
348
+ )}
349
+ </CardTitle>
350
+ </CardHeader>
351
+ <CardContent>
352
+ {isLoading ? (
353
+ <div className="flex items-center justify-center py-8">
354
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
355
+ </div>
356
+ ) : users.length === 0 ? (
357
+ <div className="text-center py-8 text-muted-foreground">
358
+ {searchQuery ? "没有找到匹配的用户" : "暂无用户"}
359
+ </div>
360
+ ) : (
361
+ <div className="relative overflow-x-auto">
362
+ <table className="w-full text-sm text-left">
363
+ <thead className="bg-muted/40 font-mono text-[10px] tracking-[0.14em] text-muted-foreground uppercase">
364
+ <tr>
365
+ <th className="px-4 py-3">用户</th>
366
+ <th className="px-4 py-3">状态</th>
367
+ <th className="px-4 py-3">积分</th>
368
+ <th className="px-4 py-3">订阅</th>
369
+ <th className="px-4 py-3">角色</th>
370
+ <th className="px-4 py-3">操作</th>
371
+ </tr>
372
+ </thead>
373
+ <tbody>
374
+ {users.map((u) => (
375
+ <tr key={u.id} className="border-b">
376
+ <td className="px-4 py-3">
377
+ <div className="flex items-center gap-3">
378
+ <Avatar className="h-8 w-8">
379
+ <AvatarImage
380
+ src={u.image || undefined}
381
+ alt={u.name}
382
+ />
383
+ <AvatarFallback className="bg-primary text-primary-foreground text-xs">
384
+ {getInitials(u.name)}
385
+ </AvatarFallback>
386
+ </Avatar>
387
+ <div>
388
+ <span className="font-medium">{u.name}</span>
389
+ <p className="text-xs text-muted-foreground">
390
+ {u.email}
391
+ </p>
392
+ </div>
393
+ </div>
394
+ </td>
395
+ <td className="px-4 py-3">
396
+ <div className="flex flex-col gap-1">
397
+ {u.banned ? (
398
+ <Badge variant="destructive">已封禁</Badge>
399
+ ) : u.emailVerified ? (
400
+ <Badge
401
+ variant="secondary"
402
+ className="bg-success/15 text-success"
403
+ >
404
+ 已验证
405
+ </Badge>
406
+ ) : (
407
+ <Badge
408
+ variant="secondary"
409
+ className="bg-warning/15 text-warning"
410
+ >
411
+ 未验证
412
+ </Badge>
413
+ )}
414
+ </div>
415
+ </td>
416
+ <td className="px-4 py-3">
417
+ <div className="flex items-center gap-2">
418
+ <Coins className="h-4 w-4 text-primary" />
419
+ <span className="font-medium">
420
+ {u.credits?.balance ?? 0}
421
+ </span>
422
+ </div>
423
+ </td>
424
+ <td className="px-4 py-3">
425
+ {getSubscriptionBadge(u.subscription)}
426
+ </td>
427
+ <td className="px-4 py-3">
428
+ <UserRoleSelect userId={u.id} currentRole={u.role} />
429
+ </td>
430
+ <td className="px-4 py-3">
431
+ <div className="flex items-center gap-2">
432
+ <Button
433
+ size="sm"
434
+ variant="outline"
435
+ onClick={() => openGrantDialog(u)}
436
+ title="充值积分"
437
+ >
438
+ <Coins className="h-4 w-4" />
439
+ </Button>
440
+ <Button
441
+ size="sm"
442
+ variant={u.banned ? "default" : "destructive"}
443
+ onClick={() => openBanDialog(u)}
444
+ title={u.banned ? "解封" : "封禁"}
445
+ >
446
+ {u.banned ? (
447
+ <UserCheck className="h-4 w-4" />
448
+ ) : (
449
+ <Ban className="h-4 w-4" />
450
+ )}
451
+ </Button>
452
+ </div>
453
+ </td>
454
+ </tr>
455
+ ))}
456
+ </tbody>
457
+ </table>
458
+ </div>
459
+ )}
460
+ </CardContent>
461
+ </Card>
462
+
463
+ {/* 封禁对话框 */}
464
+ <Dialog open={banDialogOpen} onOpenChange={setBanDialogOpen}>
465
+ <DialogContent>
466
+ <DialogHeader>
467
+ <DialogTitle>
468
+ {selectedUser?.banned ? "解除封禁" : "封禁用户"}
469
+ </DialogTitle>
470
+ <DialogDescription>
471
+ {selectedUser?.banned
472
+ ? `确定要解除用户 ${selectedUser?.name} 的封禁吗?`
473
+ : `确定要封禁用户 ${selectedUser?.name} 吗?封禁后该用户将无法登录。`}
474
+ </DialogDescription>
475
+ </DialogHeader>
476
+ {!selectedUser?.banned && (
477
+ <div className="space-y-2">
478
+ <Label htmlFor="banReason">封禁原因 (可选)</Label>
479
+ <Textarea
480
+ id="banReason"
481
+ placeholder="请输入封禁原因..."
482
+ value={banReason}
483
+ onChange={(e) => setBanReason(e.target.value)}
484
+ />
485
+ </div>
486
+ )}
487
+ <DialogFooter>
488
+ <Button
489
+ variant="outline"
490
+ onClick={() => setBanDialogOpen(false)}
491
+ disabled={isBanning}
492
+ >
493
+ 取消
494
+ </Button>
495
+ <Button
496
+ variant={selectedUser?.banned ? "default" : "destructive"}
497
+ onClick={handleBan}
498
+ disabled={isBanning}
499
+ >
500
+ {isBanning && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
501
+ {selectedUser?.banned ? "解除封禁" : "确认封禁"}
502
+ </Button>
503
+ </DialogFooter>
504
+ </DialogContent>
505
+ </Dialog>
506
+
507
+ {/* 充值对话框 */}
508
+ <Dialog open={grantDialogOpen} onOpenChange={setGrantDialogOpen}>
509
+ <DialogContent>
510
+ <DialogHeader>
511
+ <DialogTitle>手动充值积分</DialogTitle>
512
+ <DialogDescription>
513
+ 为用户 {selectedUser?.name} 充值积分
514
+ </DialogDescription>
515
+ </DialogHeader>
516
+ <div className="space-y-4">
517
+ <div className="space-y-2">
518
+ <Label htmlFor="grantAmount">积分数量 *</Label>
519
+ <Input
520
+ id="grantAmount"
521
+ type="number"
522
+ placeholder="请输入积分数量"
523
+ value={grantAmount}
524
+ onChange={(e) => setGrantAmount(e.target.value)}
525
+ min={1}
526
+ max={100000}
527
+ />
528
+ </div>
529
+ <div className="space-y-2">
530
+ <Label htmlFor="grantReason">充值原因 *</Label>
531
+ <Textarea
532
+ id="grantReason"
533
+ placeholder="请输入充值原因 (如:客服补偿、活动奖励等)"
534
+ value={grantReason}
535
+ onChange={(e) => setGrantReason(e.target.value)}
536
+ maxLength={200}
537
+ />
538
+ </div>
539
+ </div>
540
+ <DialogFooter>
541
+ <Button
542
+ variant="outline"
543
+ onClick={() => setGrantDialogOpen(false)}
544
+ disabled={isGranting}
545
+ >
546
+ 取消
547
+ </Button>
548
+ <Button onClick={handleGrant} disabled={isGranting}>
549
+ {isGranting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
550
+ 确认充值
551
+ </Button>
552
+ </DialogFooter>
553
+ </DialogContent>
554
+ </Dialog>
555
+ </div>
556
+ );
557
+ }
@@ -0,0 +1,9 @@
1
+ import { ForgotPasswordForm } from "@/features/auth";
2
+
3
+ /**
4
+ * 忘记密码页面
5
+ * 路由: /forgot-password
6
+ */
7
+ export default function ForgotPasswordPage() {
8
+ return <ForgotPasswordForm />;
9
+ }
@@ -0,0 +1,28 @@
1
+ import { AuthBrandPanel, AuthFooter } from "@/features/auth";
2
+
3
+ /**
4
+ * Auth 路由组布局
5
+ *
6
+ * 品牌分屏:左 = 深色品牌面板(lg+ 显示),右 = 跟随主题的表单区 + 页脚。
7
+ * 移动端品牌面板隐藏,只剩表单 + 页脚。
8
+ */
9
+ export default function AuthLayout({
10
+ children,
11
+ }: {
12
+ children: React.ReactNode;
13
+ }) {
14
+ return (
15
+ <div className="grid min-h-screen lg:grid-cols-[1.05fr_1fr]">
16
+ <AuthBrandPanel />
17
+ <div className="flex flex-col bg-background">
18
+ {/* 主内容区域 */}
19
+ <main className="flex flex-1 items-center justify-center px-4 py-12">
20
+ {children}
21
+ </main>
22
+
23
+ {/* 底部版权和法律链接 */}
24
+ <AuthFooter />
25
+ </div>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,9 @@
1
+ import { SignInForm } from "@/features/auth";
2
+
3
+ /**
4
+ * 登录页面
5
+ * 路由: /sign-in
6
+ */
7
+ export default function SignInPage() {
8
+ return <SignInForm />;
9
+ }
@@ -0,0 +1,9 @@
1
+ import { SignUpForm } from "@/features/auth";
2
+
3
+ /**
4
+ * 注册页面
5
+ * 路由: /sign-up
6
+ */
7
+ export default function SignUpPage() {
8
+ return <SignUpForm />;
9
+ }