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,255 @@
1
+ "use client";
2
+
3
+ import { ChevronDown, Menu } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useState } from "react";
6
+
7
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
8
+ import { Button } from "@/components/ui/button";
9
+ import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
10
+ import { mainNav, productsNav } from "@/config/nav";
11
+ import { LanguageSwitcher, ModeToggle } from "@/features/shared";
12
+ import { Link } from "@/i18n/routing";
13
+ import { useSession } from "@/lib/auth/client";
14
+
15
+ import { NavMenu } from "./nav-menu";
16
+
17
+ /**
18
+ * Products 下拉菜单翻译映射 key (移动端复用)
19
+ */
20
+ const productsTitleMap: Record<string, string> = {
21
+ Core: "productsMenu.core.title",
22
+ "DX Platform": "productsMenu.dx.title",
23
+ Infrastructure: "productsMenu.infra.title",
24
+ Authentication: "productsMenu.core.auth",
25
+ Payments: "productsMenu.core.payments",
26
+ Credits: "productsMenu.core.credits",
27
+ "Background Jobs": "productsMenu.dx.jobs",
28
+ Internationalization: "productsMenu.dx.i18n",
29
+ "AI Integration": "productsMenu.dx.ai",
30
+ "Admin Panel": "productsMenu.infra.admin",
31
+ "File Storage": "productsMenu.infra.storage",
32
+ Monitoring: "productsMenu.infra.monitoring",
33
+ };
34
+
35
+ /**
36
+ * Marketing 页面顶部导航栏
37
+ *
38
+ * 布局: [Logo + Nav 靠左] -------- [Actions 靠右]
39
+ */
40
+ export function Header() {
41
+ // 获取当前用户会话状态
42
+ const { data: session, isPending } = useSession();
43
+ const user = session?.user;
44
+ const t = useTranslations("Header");
45
+ const tNav = useTranslations("Navigation");
46
+ const [mobileOpen, setMobileOpen] = useState(false);
47
+ const [productsExpanded, setProductsExpanded] = useState(false);
48
+
49
+ /**
50
+ * 获取用户名首字母作为头像回退
51
+ */
52
+ const getInitials = (name: string) => {
53
+ return name
54
+ .split(" ")
55
+ .map((n) => n[0])
56
+ .join("")
57
+ .toUpperCase()
58
+ .slice(0, 2);
59
+ };
60
+
61
+ /**
62
+ * 导航项标题翻译映射
63
+ */
64
+ const navTitleMap: Record<string, string> = {
65
+ Products: tNav("products"),
66
+ Docs: tNav("docs"),
67
+ Pricing: tNav("pricing"),
68
+ Blog: tNav("blog"),
69
+ };
70
+
71
+ return (
72
+ <header className="sticky top-0 z-50 w-full border-b border-border/60 bg-background/80 backdrop-blur-xl supports-[backdrop-filter]:bg-background/70">
73
+ <div className="container flex h-16 items-center justify-between">
74
+ {/* 左侧 - Logo + 导航菜单 */}
75
+ <div className="flex items-center gap-8">
76
+ {/* Logo */}
77
+ <Link href="/" className="flex items-center gap-2">
78
+ {/* biome-ignore lint/a11y/noSvgWithoutTitle: decorative logo */}
79
+ <svg
80
+ className="h-6 w-6 text-primary"
81
+ viewBox="0 0 24 24"
82
+ fill="none"
83
+ stroke="currentColor"
84
+ strokeWidth={2.4}
85
+ strokeLinecap="round"
86
+ strokeLinejoin="round"
87
+ >
88
+ <path d="M3 17 L9 7 L13 14 L21 4" />
89
+ <circle cx="9" cy="7" r="1.4" fill="currentColor" stroke="none" />
90
+ <circle
91
+ cx="21"
92
+ cy="4"
93
+ r="1.4"
94
+ fill="currentColor"
95
+ stroke="none"
96
+ />
97
+ </svg>
98
+ <span className="text-xl font-bold tracking-tight">
99
+ NextDev<span className="text-muted-foreground">Tpl</span>
100
+ </span>
101
+ </Link>
102
+
103
+ {/* 导航菜单 (桌面端) */}
104
+ <div className="hidden md:flex">
105
+ <NavMenu />
106
+ </div>
107
+ </div>
108
+
109
+ {/* 右侧 - 操作区域 */}
110
+ <div className="flex items-center gap-2">
111
+ {/* 语言切换 */}
112
+ <LanguageSwitcher />
113
+
114
+ {/* 主题切换 */}
115
+ <ModeToggle />
116
+
117
+ {isPending ? (
118
+ // 加载状态 - 显示骨架
119
+ <div className="hidden h-9 w-24 animate-pulse rounded-md bg-muted md:block" />
120
+ ) : user ? (
121
+ // 已登录 - 显示 Dashboard 按钮和头像
122
+ <>
123
+ <Button
124
+ asChild
125
+ variant="ghost"
126
+ className="hidden text-muted-foreground md:inline-flex"
127
+ >
128
+ <Link href="/dashboard">{t("dashboard")}</Link>
129
+ </Button>
130
+ <Link href="/dashboard" className="hidden md:block">
131
+ <Avatar className="h-8 w-8">
132
+ <AvatarImage src={user.image || undefined} alt={user.name} />
133
+ <AvatarFallback className="bg-primary text-xs text-primary-foreground">
134
+ {getInitials(user.name)}
135
+ </AvatarFallback>
136
+ </Avatar>
137
+ </Link>
138
+ </>
139
+ ) : (
140
+ // 未登录 - 显示登录和注册按钮(桌面端)
141
+ <>
142
+ <Button
143
+ asChild
144
+ variant="ghost"
145
+ className="hidden text-muted-foreground hover:text-foreground md:inline-flex"
146
+ >
147
+ <Link href="/sign-in">{t("login")}</Link>
148
+ </Button>
149
+ <Button asChild className="hidden md:inline-flex">
150
+ <Link href="/sign-up">{t("getStarted")}</Link>
151
+ </Button>
152
+ </>
153
+ )}
154
+
155
+ {/* 移动端汉堡按钮 */}
156
+ <button
157
+ type="button"
158
+ onClick={() => setMobileOpen(true)}
159
+ className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors md:hidden"
160
+ >
161
+ <Menu className="h-5 w-5" />
162
+ </button>
163
+ </div>
164
+ </div>
165
+
166
+ {/* 移动端导航 Sheet */}
167
+ <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
168
+ <SheetContent side="right" className="w-72 p-0">
169
+ <SheetTitle className="sr-only">Navigation</SheetTitle>
170
+ <div className="flex h-full flex-col">
171
+ {/* 导航链接 */}
172
+ <nav className="flex-1 overflow-y-auto px-4 pt-12">
173
+ {/* Products 可折叠区域 */}
174
+ <div className="space-y-1">
175
+ <button
176
+ type="button"
177
+ onClick={() => setProductsExpanded(!productsExpanded)}
178
+ className="flex w-full items-center justify-between rounded-md px-3 py-2 text-sm font-medium text-foreground/70 hover:bg-accent hover:text-foreground transition-colors"
179
+ >
180
+ {navTitleMap.Products}
181
+ <ChevronDown
182
+ className={`h-4 w-4 transition-transform duration-200 ${productsExpanded ? "rotate-180" : ""}`}
183
+ />
184
+ </button>
185
+ {productsExpanded && (
186
+ <div className="ml-3 space-y-1 border-l border-border pl-3">
187
+ {productsNav.map((group) => (
188
+ <div key={group.title} className="py-1">
189
+ <div className="px-3 py-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
190
+ {tNav(productsTitleMap[group.title] || group.title)}
191
+ </div>
192
+ {group.items.map((item) => {
193
+ const Icon = item.icon;
194
+ return (
195
+ <Link
196
+ key={item.title}
197
+ href={item.href}
198
+ onClick={() => setMobileOpen(false)}
199
+ className="flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-foreground/70 hover:bg-accent hover:text-foreground transition-colors"
200
+ >
201
+ <Icon className="h-3.5 w-3.5 text-primary" />
202
+ {tNav(productsTitleMap[item.title] || item.title)}
203
+ </Link>
204
+ );
205
+ })}
206
+ </div>
207
+ ))}
208
+ </div>
209
+ )}
210
+ </div>
211
+
212
+ {/* 主导航链接 */}
213
+ <div className="space-y-1">
214
+ {mainNav.map((item) => (
215
+ <Link
216
+ key={item.href}
217
+ href={item.href}
218
+ onClick={() => setMobileOpen(false)}
219
+ className="flex items-center rounded-md px-3 py-2 text-sm font-medium text-foreground/70 hover:bg-accent hover:text-foreground transition-colors"
220
+ >
221
+ {navTitleMap[item.title] || item.title}
222
+ </Link>
223
+ ))}
224
+ </div>
225
+ </nav>
226
+
227
+ {/* 底部操作按钮 */}
228
+ <div className="border-t border-border p-4 space-y-2">
229
+ {user ? (
230
+ <Button asChild className="w-full">
231
+ <Link href="/dashboard" onClick={() => setMobileOpen(false)}>
232
+ {t("dashboard")}
233
+ </Link>
234
+ </Button>
235
+ ) : (
236
+ <>
237
+ <Button asChild variant="outline" className="w-full">
238
+ <Link href="/sign-in" onClick={() => setMobileOpen(false)}>
239
+ {t("login")}
240
+ </Link>
241
+ </Button>
242
+ <Button asChild className="w-full">
243
+ <Link href="/sign-up" onClick={() => setMobileOpen(false)}>
244
+ {t("getStarted")}
245
+ </Link>
246
+ </Button>
247
+ </>
248
+ )}
249
+ </div>
250
+ </div>
251
+ </SheetContent>
252
+ </Sheet>
253
+ </header>
254
+ );
255
+ }
@@ -0,0 +1,323 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import { ArrowRight } from "lucide-react";
5
+ import { useTranslations } from "next-intl";
6
+ import { useTheme } from "next-themes";
7
+ import type { ReactNode } from "react";
8
+ import { useEffect, useRef } from "react";
9
+
10
+ import { Button } from "@/components/ui/button";
11
+ import { Link } from "@/i18n/routing";
12
+
13
+ /**
14
+ * 缓动曲线(全站统一)+ 入场动画变体
15
+ */
16
+ const EASE: [number, number, number, number] = [0.22, 0.7, 0.16, 1];
17
+
18
+ const containerVariants = {
19
+ hidden: {},
20
+ show: {
21
+ transition: { staggerChildren: 0.08, delayChildren: 0.04 },
22
+ },
23
+ };
24
+
25
+ const itemVariants = {
26
+ hidden: { opacity: 0, y: 16 },
27
+ show: { opacity: 1, y: 0, transition: { duration: 0.6, ease: EASE } },
28
+ };
29
+
30
+ /**
31
+ * Hero Section —— 工程图纸风首页主视觉
32
+ *
33
+ * 左:eyebrow + 大标题(主色高亮下划线)+ 副标题 + CTA + 真实仓库指标
34
+ * 右:生产环境读数面板(指标 + 折线 + 状态药丸),展示 dashboard 级精致度
35
+ */
36
+ export function HeroSection() {
37
+ const t = useTranslations("Hero");
38
+
39
+ return (
40
+ <section className="relative overflow-hidden">
41
+ {/* 氛围层:双色光晕 + 极淡网格(顶部渐隐) */}
42
+ <div
43
+ aria-hidden
44
+ className="aura pointer-events-none absolute inset-0 -z-10"
45
+ />
46
+ <div
47
+ aria-hidden
48
+ className="bg-grid pointer-events-none absolute inset-0 -z-10 opacity-70 [mask-image:radial-gradient(72%_60%_at_50%_0%,black,transparent)]"
49
+ />
50
+
51
+ <div className="container grid items-center gap-14 py-20 md:py-28 lg:grid-cols-[1.05fr_0.95fr] lg:gap-16">
52
+ {/* 左列 */}
53
+ <motion.div
54
+ variants={containerVariants}
55
+ initial="hidden"
56
+ animate="show"
57
+ className="flex flex-col items-start"
58
+ >
59
+ <motion.span variants={itemVariants} className="eyebrow">
60
+ {t("eyebrow")}
61
+ </motion.span>
62
+
63
+ <motion.h1
64
+ variants={itemVariants}
65
+ className="mt-6 text-4xl font-extrabold tracking-tight text-balance text-foreground text-[clamp(2.5rem,6vw,4.5rem)] leading-[1.02]"
66
+ >
67
+ <span className="block">{t("title1")}</span>
68
+ <span className="block">
69
+ <span className="relative inline-block text-primary">
70
+ {t("titleHighlight")}
71
+ <span
72
+ aria-hidden
73
+ className="absolute inset-x-0 -bottom-1 h-[6px] rounded-sm bg-primary/20"
74
+ />
75
+ </span>
76
+ </span>
77
+ </motion.h1>
78
+
79
+ <motion.p
80
+ variants={itemVariants}
81
+ className="mt-6 max-w-xl text-base leading-relaxed text-muted-foreground sm:text-lg"
82
+ >
83
+ {t("subtitle")}
84
+ </motion.p>
85
+
86
+ <motion.div
87
+ variants={itemVariants}
88
+ className="mt-8 flex w-full flex-col gap-3 sm:w-auto sm:flex-row"
89
+ >
90
+ <Button size="lg" asChild className="group">
91
+ <Link href="/sign-up">
92
+ {t("getStarted")}
93
+ <ArrowRight className="transition-transform group-hover:translate-x-0.5" />
94
+ </Link>
95
+ </Button>
96
+ <Button size="lg" variant="outline" asChild>
97
+ <Link href="/#features">{t("seeDemo")}</Link>
98
+ </Button>
99
+ </motion.div>
100
+
101
+ <motion.div
102
+ variants={itemVariants}
103
+ className="mt-10 grid w-full max-w-lg grid-cols-2 gap-x-8 gap-y-5 border-t pt-7 sm:grid-cols-4"
104
+ >
105
+ <Stat k="18" v={t("meta.modules")} />
106
+ <Stat k="256" v={t("meta.tests")} />
107
+ <Stat k="0" v={t("meta.errors")} />
108
+ <Stat k="MIT" v={t("meta.license")} />
109
+ </motion.div>
110
+ </motion.div>
111
+
112
+ {/* 右列:生产环境读数面板 */}
113
+ <motion.div
114
+ initial={{ opacity: 0, y: 24 }}
115
+ animate={{ opacity: 1, y: 0 }}
116
+ transition={{ duration: 0.8, delay: 0.25, ease: EASE }}
117
+ >
118
+ <ProductionPanel />
119
+ </motion.div>
120
+ </div>
121
+ </section>
122
+ );
123
+ }
124
+
125
+ /** 指标小卡(左侧大数字 + 标签) */
126
+ function Stat({ k, v }: { k: string; v: string }) {
127
+ return (
128
+ <div>
129
+ <div className="mono-data text-2xl font-bold tracking-tight text-foreground">
130
+ {k}
131
+ </div>
132
+ <div className="mt-1 text-xs text-muted-foreground">{v}</div>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ /** 面板内的指标单元 */
138
+ function Metric({
139
+ delta,
140
+ label,
141
+ unit,
142
+ value,
143
+ }: {
144
+ delta: string;
145
+ label: string;
146
+ unit?: string;
147
+ value: string;
148
+ }) {
149
+ return (
150
+ <div className="p-3.5 [&:not(:first-child)]:border-l [&:not(:first-child)]:border-border">
151
+ <div className="font-mono text-[10px] font-semibold tracking-[0.14em] text-muted-foreground/80 uppercase">
152
+ {label}
153
+ </div>
154
+ <div className="mono-data mt-2 text-xl font-bold tracking-tight text-foreground">
155
+ {value}
156
+ {unit ? (
157
+ <span className="text-xs text-muted-foreground">{unit}</span>
158
+ ) : null}
159
+ </div>
160
+ <div className="mt-1 text-[11px] font-semibold text-primary">{delta}</div>
161
+ </div>
162
+ );
163
+ }
164
+
165
+ /** 状态药丸 */
166
+ function Pill({ children, ok = false }: { children: ReactNode; ok?: boolean }) {
167
+ return (
168
+ <span
169
+ className={`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 font-mono text-[10px] font-semibold tracking-[0.12em] uppercase ${
170
+ ok
171
+ ? "border-primary/30 text-foreground"
172
+ : "border-border text-muted-foreground"
173
+ }`}
174
+ >
175
+ <span
176
+ className={`h-1.5 w-1.5 rounded-full ${ok ? "bg-primary" : "bg-muted-foreground/40"}`}
177
+ />
178
+ {children}
179
+ </span>
180
+ );
181
+ }
182
+
183
+ /** Hero 右侧:生产环境读数面板(含 canvas 折线图) */
184
+ function ProductionPanel() {
185
+ const t = useTranslations("Hero.panel");
186
+ const { resolvedTheme } = useTheme();
187
+ const canvasRef = useRef<HTMLCanvasElement>(null);
188
+
189
+ // resolvedTheme 是有意依赖:主题切换后 CSS 变量(--primary 等)变化,
190
+ // 需要用新颜色重画 canvas。effect 内部通过 getComputedStyle 间接读取颜色,
191
+ // Biome 检测不到这层依赖,故显式保留并抑制告警。
192
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intentional theme-driven redraw
193
+ useEffect(() => {
194
+ const cv = canvasRef.current;
195
+ if (!cv) return;
196
+ const ctx = cv.getContext("2d");
197
+ if (!ctx) return;
198
+
199
+ const dpr = window.devicePixelRatio || 1;
200
+ const w = cv.clientWidth;
201
+ const h = 72;
202
+ cv.width = w * dpr;
203
+ cv.height = h * dpr;
204
+ ctx.scale(dpr, dpr);
205
+ ctx.clearRect(0, 0, w, h);
206
+
207
+ const data = [8, 12, 9, 14, 11, 16, 13, 18, 15, 20, 17, 22, 19, 28, 26, 32];
208
+ const max = Math.max(...data);
209
+ const min = Math.min(...data);
210
+ const pad = 8;
211
+ const X = (i: number) => (i / (data.length - 1)) * (w - pad * 2) + pad;
212
+ const Y = (v: number) =>
213
+ h - pad - ((v - min) / (max - min)) * (h - pad * 2);
214
+
215
+ const cs = getComputedStyle(document.documentElement);
216
+ const accent = cs.getPropertyValue("--primary").trim() || "#0F5C57";
217
+ const hair = cs.getPropertyValue("--border").trim() || "#ddd";
218
+
219
+ // 把任意 CSS 颜色(含 oklch)转成带透明度的合法颜色字符串。
220
+ // 注意:不能用 hex 拼接 `${accent}55`——主色是 oklch 时拼出来是非法颜色,
221
+ // 会让 canvas addColorStop 抛异常、整个组件白屏。color-mix 在所有现代浏览器
222
+ // 都支持,且能正确处理 oklch / hex / rgb。
223
+ const withAlpha = (color: string, pct: number) =>
224
+ `color-mix(in srgb, ${color} ${pct}%, transparent)`;
225
+
226
+ // 基线
227
+ ctx.strokeStyle = hair;
228
+ ctx.lineWidth = 1;
229
+ ctx.beginPath();
230
+ ctx.moveTo(0, h - pad + 0.5);
231
+ ctx.lineTo(w, h - pad + 0.5);
232
+ ctx.stroke();
233
+
234
+ // 区域填充
235
+ const grad = ctx.createLinearGradient(0, 0, 0, h);
236
+ grad.addColorStop(0, withAlpha(accent, 33));
237
+ grad.addColorStop(1, withAlpha(accent, 0));
238
+ ctx.beginPath();
239
+ ctx.moveTo(X(0), h - pad);
240
+ data.forEach((v, i) => {
241
+ ctx.lineTo(X(i), Y(v));
242
+ });
243
+ ctx.lineTo(X(data.length - 1), h - pad);
244
+ ctx.closePath();
245
+ ctx.fillStyle = grad;
246
+ ctx.fill();
247
+
248
+ // 折线
249
+ ctx.beginPath();
250
+ data.forEach((v, i) => {
251
+ if (i === 0) ctx.moveTo(X(i), Y(v));
252
+ else ctx.lineTo(X(i), Y(v));
253
+ });
254
+ ctx.strokeStyle = accent;
255
+ ctx.lineWidth = 1.8;
256
+ ctx.lineJoin = "round";
257
+ ctx.stroke();
258
+
259
+ // 强调终点
260
+ const last = data[data.length - 1] ?? 0;
261
+ const lx = X(data.length - 1);
262
+ const ly = Y(last);
263
+ ctx.beginPath();
264
+ ctx.arc(lx, ly, 7, 0, Math.PI * 2);
265
+ ctx.fillStyle = withAlpha(accent, 13);
266
+ ctx.fill();
267
+ ctx.beginPath();
268
+ ctx.arc(lx, ly, 3.2, 0, Math.PI * 2);
269
+ ctx.fillStyle = accent;
270
+ ctx.fill();
271
+ }, [resolvedTheme]);
272
+
273
+ return (
274
+ <div className="relative overflow-hidden rounded-xl border bg-card text-card-foreground shadow-lifted">
275
+ <div
276
+ aria-hidden
277
+ className="pointer-events-none absolute inset-0 bg-gradient-to-b from-primary/5 to-transparent"
278
+ />
279
+ {/* 面板头 */}
280
+ <div className="relative flex items-center gap-2.5 border-b px-5 py-3.5">
281
+ <span className="relative flex h-2 w-2">
282
+ <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary/60" />
283
+ <span className="relative inline-flex h-2 w-2 rounded-full bg-primary" />
284
+ </span>
285
+ <span className="font-mono text-[11px] font-semibold tracking-[0.14em] text-muted-foreground uppercase">
286
+ {t("title")}
287
+ </span>
288
+ <span className="ml-auto font-mono text-[11px] text-muted-foreground/70">
289
+ {t("region")}
290
+ </span>
291
+ </div>
292
+
293
+ {/* 指标行 */}
294
+ <div className="relative space-y-4 p-5">
295
+ <div className="grid grid-cols-3 overflow-hidden rounded-lg border bg-card">
296
+ <Metric delta="↓ 12" label={t("p95")} unit="ms" value="84" />
297
+ <Metric delta="30d" label={t("uptime")} unit="%" value="99.98" />
298
+ <Metric delta="↑ 8%" label={t("jobs")} value="1,420" />
299
+ </div>
300
+
301
+ {/* 折线 */}
302
+ <div className="rounded-lg border bg-muted/30 p-4">
303
+ <div className="flex items-baseline justify-between">
304
+ <span className="font-mono text-[10px] font-semibold tracking-[0.14em] text-muted-foreground uppercase">
305
+ {t("signups")}
306
+ </span>
307
+ <span className="mono-data text-lg font-bold text-foreground">
308
+ 3,284{" "}
309
+ <span className="text-xs font-semibold text-primary">+24%</span>
310
+ </span>
311
+ </div>
312
+ <canvas ref={canvasRef} className="mt-2 h-[72px] w-full" />
313
+ </div>
314
+ </div>
315
+
316
+ {/* 状态药丸 */}
317
+ <div className="relative flex flex-wrap gap-2 border-t bg-muted/30 px-5 py-3">
318
+ <Pill ok>{t("pillOk")}</Pill>
319
+ <Pill>{t("pillStack")}</Pill>
320
+ </div>
321
+ </div>
322
+ );
323
+ }
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ import { Check } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import type { ReactNode } from "react";
6
+
7
+ import { Reveal } from "@/components/motion/reveal";
8
+
9
+ /**
10
+ * HowItWorks —— 三步流程
11
+ *
12
+ * 用编号 01/02/03 是因为内容本身就是有序流程(克隆 → 配置 → 上线),
13
+ * 编码承载信息,所以保留;非装饰性编号。
14
+ */
15
+ const steps: {
16
+ key: "upload" | "generate" | "export";
17
+ n: string;
18
+ code: ReactNode;
19
+ }[] = [
20
+ {
21
+ key: "upload",
22
+ n: "01",
23
+ code: (
24
+ <code className="block">
25
+ <span className="text-muted-foreground/60">$</span> git clone
26
+ nextdev-tpl{"\n"}
27
+ <span className="text-muted-foreground/60">$</span> pnpm install
28
+ </code>
29
+ ),
30
+ },
31
+ {
32
+ key: "generate",
33
+ n: "02",
34
+ code: (
35
+ <code className="block">
36
+ <span className="text-muted-foreground/60">.env</span>
37
+ {"\n"}DATABASE_URL=
38
+ <span className="text-muted-foreground">postgres://…</span>
39
+ {"\n"}BETTER_AUTH_SECRET=
40
+ <span className="text-muted-foreground">…</span>
41
+ {"\n"}AI_PROVIDER=<span className="text-primary">"openai"</span>
42
+ </code>
43
+ ),
44
+ },
45
+ {
46
+ key: "export",
47
+ n: "03",
48
+ code: (
49
+ <code className="block">
50
+ <span className="text-muted-foreground/60">$</span> pnpm build{" "}
51
+ <span className="text-muted-foreground/60">&amp;&amp;</span> deploy
52
+ {"\n"}
53
+ <span className="text-muted-foreground/60">→</span> live in ~6 min
54
+ </code>
55
+ ),
56
+ },
57
+ ];
58
+
59
+ export function HowItWorks() {
60
+ const t = useTranslations("HowItWorks");
61
+
62
+ return (
63
+ <section id="how-it-works" className="border-t bg-muted/30 py-24">
64
+ <div className="container">
65
+ <Reveal>
66
+ <div className="mb-14 max-w-2xl">
67
+ <span className="eyebrow">{t("label")}</span>
68
+ <h2 className="mt-4 text-balance text-3xl font-extrabold tracking-tight text-foreground md:text-4xl">
69
+ {t("title")}
70
+ </h2>
71
+ <p className="mt-4 text-muted-foreground">{t("subtitle")}</p>
72
+ </div>
73
+ </Reveal>
74
+
75
+ <Reveal delay={0.08}>
76
+ <div className="grid gap-8 md:grid-cols-3">
77
+ {steps.map((s) => (
78
+ <div key={s.key}>
79
+ <div className="flex items-center gap-3">
80
+ <span className="font-mono text-sm font-bold text-primary">
81
+ {s.n}
82
+ </span>
83
+ <span className="h-px flex-1 bg-border" />
84
+ </div>
85
+ <h3 className="mt-5 text-xl font-bold tracking-tight text-foreground">
86
+ {t(`steps.${s.key}.title`)}
87
+ </h3>
88
+ <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
89
+ {t(`steps.${s.key}.description`)}
90
+ </p>
91
+ <pre className="mt-5 overflow-x-auto rounded-lg border bg-card p-4 font-mono text-xs leading-relaxed text-foreground">
92
+ {s.code}
93
+ </pre>
94
+ </div>
95
+ ))}
96
+ </div>
97
+ </Reveal>
98
+
99
+ <Reveal delay={0.16}>
100
+ <div className="mt-10 flex items-center gap-4 rounded-xl border bg-card p-6">
101
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-success/15 text-success">
102
+ <Check className="h-5 w-5" />
103
+ </div>
104
+ <div>
105
+ <p className="font-semibold">{t("completion.title")}</p>
106
+ <p className="text-sm text-muted-foreground">
107
+ {t("completion.description")}
108
+ </p>
109
+ </div>
110
+ </div>
111
+ </Reveal>
112
+ </div>
113
+ </section>
114
+ );
115
+ }