@digilogiclabs/create-saas-app 2.0.0 → 2.2.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 (348) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +975 -891
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/generators/template-generator.d.ts +11 -0
  5. package/dist/generators/template-generator.d.ts.map +1 -1
  6. package/dist/generators/template-generator.js +389 -18
  7. package/dist/generators/template-generator.js.map +1 -1
  8. package/dist/index.js +1837 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/templates/infrastructure/kubernetes/base/template/README.md +253 -0
  11. package/dist/templates/infrastructure/kubernetes/base/template/configmap.yaml +12 -0
  12. package/dist/templates/infrastructure/kubernetes/base/template/deployment.yaml +123 -0
  13. package/dist/templates/infrastructure/kubernetes/base/template/hpa.yaml +45 -0
  14. package/dist/templates/infrastructure/kubernetes/base/template/ingress.yaml +31 -0
  15. package/dist/templates/infrastructure/kubernetes/base/template/kustomization.yaml +25 -0
  16. package/dist/templates/infrastructure/kubernetes/base/template/namespace.yaml +8 -0
  17. package/dist/templates/infrastructure/kubernetes/base/template/networkpolicy.yaml +48 -0
  18. package/dist/templates/infrastructure/kubernetes/base/template/pdb.yaml +14 -0
  19. package/dist/templates/infrastructure/kubernetes/base/template/secret.yaml +17 -0
  20. package/dist/templates/infrastructure/kubernetes/base/template/service.yaml +19 -0
  21. package/dist/templates/infrastructure/kubernetes/base/template/serviceaccount.yaml +9 -0
  22. package/dist/templates/infrastructure/kubernetes/production/template/kustomization.yaml +92 -0
  23. package/dist/templates/infrastructure/terraform/aws/template/README.md +156 -0
  24. package/dist/templates/infrastructure/terraform/aws/template/main.tf +343 -0
  25. package/dist/templates/infrastructure/terraform/aws/template/outputs.tf +66 -0
  26. package/dist/templates/infrastructure/terraform/aws/template/terraform.tfvars.example +28 -0
  27. package/dist/templates/infrastructure/terraform/aws/template/variables.tf +110 -0
  28. package/dist/templates/infrastructure/terraform/gcp/template/README.md +165 -0
  29. package/dist/templates/infrastructure/terraform/gcp/template/main.tf +397 -0
  30. package/dist/templates/infrastructure/terraform/gcp/template/outputs.tf +51 -0
  31. package/dist/templates/infrastructure/terraform/gcp/template/terraform.tfvars.example +29 -0
  32. package/dist/templates/infrastructure/terraform/gcp/template/variables.tf +115 -0
  33. package/dist/templates/shared/admin/web/app/admin/layout.tsx +34 -0
  34. package/dist/templates/shared/admin/web/components/admin-nav.tsx +48 -0
  35. package/dist/templates/shared/audit/web/lib/audit.ts +24 -0
  36. package/dist/templates/shared/auth/keycloak/web/app/api/auth/federated-logout/route.ts +173 -0
  37. package/dist/templates/shared/auth/keycloak/web/auth.config.ts +84 -0
  38. package/dist/templates/shared/auth/keycloak/web/auth.ts +26 -0
  39. package/dist/templates/shared/beta/web/app/api/beta-settings/route.ts +25 -0
  40. package/dist/templates/shared/beta/web/app/api/validate-beta-code/route.ts +67 -0
  41. package/dist/templates/shared/beta/web/lib/beta/settings.ts +31 -0
  42. package/dist/templates/shared/cache/web/lib/cache.ts +44 -0
  43. package/dist/templates/shared/config/web/lib/config.ts +112 -0
  44. package/dist/templates/shared/config/web/next.config.mjs +62 -0
  45. package/dist/templates/shared/contact/web/app/api/contact/route.ts +113 -0
  46. package/dist/templates/shared/contact/web/app/contact/page.tsx +195 -0
  47. package/dist/templates/shared/cookie-consent/web/components/cookie-consent.tsx +54 -0
  48. package/dist/templates/shared/database/postgresql/web/drizzle.config.ts +16 -0
  49. package/dist/templates/shared/database/postgresql/web/lib/db/drizzle.ts +39 -0
  50. package/dist/templates/shared/database/postgresql/web/lib/db/schema.ts +33 -0
  51. package/dist/templates/shared/database/supabase/web/lib/supabase/client.ts +12 -0
  52. package/dist/templates/shared/database/supabase/web/lib/supabase/server.ts +31 -0
  53. package/dist/templates/shared/database/supabase/web/lib/supabase/service.ts +15 -0
  54. package/dist/templates/shared/email/web/lib/email/branding.ts +18 -0
  55. package/dist/templates/shared/email/web/lib/email/client.ts +96 -0
  56. package/dist/templates/shared/error-pages/web/app/error.tsx +70 -0
  57. package/dist/templates/shared/error-pages/web/app/global-error.tsx +102 -0
  58. package/dist/templates/shared/error-pages/web/app/not-found.tsx +39 -0
  59. package/dist/templates/shared/health/web/app/api/health/route.ts +68 -0
  60. package/dist/templates/shared/legal/web/app/(legal)/privacy/page.tsx +205 -0
  61. package/dist/templates/shared/legal/web/app/(legal)/terms/page.tsx +154 -0
  62. package/dist/templates/shared/legal/web/lib/legal-config.ts +50 -0
  63. package/dist/templates/shared/loading/web/app/loading.tsx +5 -0
  64. package/dist/templates/shared/loading/web/components/skeleton.tsx +95 -0
  65. package/dist/templates/shared/middleware/web/middleware.ts +68 -0
  66. package/dist/templates/shared/observability/web/lib/observability.ts +135 -0
  67. package/dist/templates/shared/payments/web/app/api/webhooks/stripe/route.ts +109 -0
  68. package/dist/templates/shared/platform/web/lib/platform.ts +37 -0
  69. package/dist/templates/shared/redis/web/lib/rate-limit-store.ts +18 -0
  70. package/dist/templates/shared/redis/web/lib/redis.ts +48 -0
  71. package/dist/templates/shared/security/web/lib/api-security.ts +318 -0
  72. package/dist/templates/shared/seo/web/app/api/og/route.tsx +97 -0
  73. package/dist/templates/shared/seo/web/app/robots.ts +53 -0
  74. package/dist/templates/shared/seo/web/app/sitemap.ts +53 -0
  75. package/dist/templates/shared/utils/web/lib/api-response.ts +71 -0
  76. package/dist/templates/shared/utils/web/lib/utils.ts +85 -0
  77. package/dist/templates/web/ai-platform/template/.env.example +16 -0
  78. package/dist/templates/web/ai-platform/template/README.md +84 -0
  79. package/dist/templates/web/ai-platform/template/middleware.ts +55 -0
  80. package/dist/templates/web/ai-platform/template/next.config.js +14 -0
  81. package/dist/templates/web/ai-platform/template/package.json +55 -0
  82. package/dist/templates/web/ai-platform/template/src/app/api/chat/route.ts +54 -0
  83. package/dist/templates/web/ai-platform/template/src/app/chat/page.tsx +235 -0
  84. package/dist/templates/web/ai-platform/template/src/app/dashboard/page.tsx +142 -0
  85. package/dist/templates/web/ai-platform/template/src/app/globals.css +34 -0
  86. package/dist/templates/web/ai-platform/template/src/app/layout.tsx +27 -0
  87. package/dist/templates/web/ai-platform/template/src/app/page.tsx +203 -0
  88. package/dist/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +27 -0
  89. package/dist/templates/web/ai-platform/template/src/lib/auth-server.ts +33 -0
  90. package/dist/templates/web/ai-platform/template/src/lib/supabase/client.ts +8 -0
  91. package/dist/templates/web/ai-platform/template/src/lib/supabase/server.ts +27 -0
  92. package/dist/templates/web/ai-platform/template/src/lib/utils.ts +6 -0
  93. package/dist/templates/web/ai-platform/template/tsconfig.json +27 -0
  94. package/dist/templates/web/iot-dashboard/template/.env.example +12 -0
  95. package/dist/templates/web/iot-dashboard/template/README.md +101 -0
  96. package/dist/templates/web/iot-dashboard/template/middleware.ts +56 -0
  97. package/dist/templates/web/iot-dashboard/template/next.config.js +14 -0
  98. package/dist/templates/web/iot-dashboard/template/package.json +49 -0
  99. package/dist/templates/web/iot-dashboard/template/src/app/dashboard/page.tsx +229 -0
  100. package/dist/templates/web/iot-dashboard/template/src/app/globals.css +20 -0
  101. package/dist/templates/web/iot-dashboard/template/src/app/layout.tsx +27 -0
  102. package/dist/templates/web/iot-dashboard/template/src/app/page.tsx +191 -0
  103. package/dist/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +24 -0
  104. package/dist/templates/web/iot-dashboard/template/src/lib/auth-server.ts +33 -0
  105. package/dist/templates/web/iot-dashboard/template/src/lib/supabase/client.ts +8 -0
  106. package/dist/templates/web/iot-dashboard/template/src/lib/supabase/server.ts +27 -0
  107. package/dist/templates/web/iot-dashboard/template/src/lib/utils.ts +25 -0
  108. package/dist/templates/web/iot-dashboard/template/tsconfig.json +27 -0
  109. package/dist/templates/web/marketplace/template/.env.example +12 -0
  110. package/dist/templates/web/marketplace/template/README.md +66 -0
  111. package/dist/templates/web/marketplace/template/middleware.ts +56 -0
  112. package/dist/templates/web/marketplace/template/next.config.js +14 -0
  113. package/dist/templates/web/marketplace/template/package.json +51 -0
  114. package/dist/templates/web/marketplace/template/src/app/cart/page.tsx +147 -0
  115. package/dist/templates/web/marketplace/template/src/app/dashboard/page.tsx +149 -0
  116. package/dist/templates/web/marketplace/template/src/app/globals.css +20 -0
  117. package/dist/templates/web/marketplace/template/src/app/layout.tsx +27 -0
  118. package/dist/templates/web/marketplace/template/src/app/page.tsx +167 -0
  119. package/dist/templates/web/marketplace/template/src/app/products/page.tsx +129 -0
  120. package/dist/templates/web/marketplace/template/src/components/providers/app-providers.tsx +27 -0
  121. package/dist/templates/web/marketplace/template/src/lib/auth-server.ts +33 -0
  122. package/dist/templates/web/marketplace/template/src/lib/supabase/client.ts +8 -0
  123. package/dist/templates/web/marketplace/template/src/lib/supabase/server.ts +27 -0
  124. package/dist/templates/web/marketplace/template/src/lib/utils.ts +19 -0
  125. package/dist/templates/web/marketplace/template/tsconfig.json +27 -0
  126. package/dist/templates/web/micro-saas/template/.env.example +10 -0
  127. package/dist/templates/web/micro-saas/template/README.md +63 -0
  128. package/dist/templates/web/micro-saas/template/middleware.ts +53 -0
  129. package/dist/templates/web/micro-saas/template/next.config.js +14 -0
  130. package/dist/templates/web/micro-saas/template/package.json +41 -0
  131. package/dist/templates/web/micro-saas/template/src/app/dashboard/page.tsx +117 -0
  132. package/dist/templates/web/micro-saas/template/src/app/globals.css +20 -0
  133. package/dist/templates/web/micro-saas/template/src/app/layout.tsx +27 -0
  134. package/dist/templates/web/micro-saas/template/src/app/login/page.tsx +87 -0
  135. package/dist/templates/web/micro-saas/template/src/app/page.tsx +137 -0
  136. package/dist/templates/web/micro-saas/template/src/app/signup/page.tsx +108 -0
  137. package/dist/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +24 -0
  138. package/dist/templates/web/micro-saas/template/src/lib/auth-server.ts +33 -0
  139. package/dist/templates/web/micro-saas/template/src/lib/supabase/client.ts +8 -0
  140. package/dist/templates/web/micro-saas/template/src/lib/supabase/server.ts +29 -0
  141. package/dist/templates/web/micro-saas/template/src/lib/utils.ts +6 -0
  142. package/dist/templates/web/micro-saas/template/tsconfig.json +27 -0
  143. package/package.json +5 -4
  144. package/src/templates/infrastructure/kubernetes/base/template/README.md +253 -0
  145. package/src/templates/infrastructure/kubernetes/base/template/configmap.yaml +12 -0
  146. package/src/templates/infrastructure/kubernetes/base/template/deployment.yaml +123 -0
  147. package/src/templates/infrastructure/kubernetes/base/template/hpa.yaml +45 -0
  148. package/src/templates/infrastructure/kubernetes/base/template/ingress.yaml +31 -0
  149. package/src/templates/infrastructure/kubernetes/base/template/kustomization.yaml +25 -0
  150. package/src/templates/infrastructure/kubernetes/base/template/namespace.yaml +8 -0
  151. package/src/templates/infrastructure/kubernetes/base/template/networkpolicy.yaml +48 -0
  152. package/src/templates/infrastructure/kubernetes/base/template/pdb.yaml +14 -0
  153. package/src/templates/infrastructure/kubernetes/base/template/secret.yaml +17 -0
  154. package/src/templates/infrastructure/kubernetes/base/template/service.yaml +19 -0
  155. package/src/templates/infrastructure/kubernetes/base/template/serviceaccount.yaml +9 -0
  156. package/src/templates/infrastructure/kubernetes/production/template/kustomization.yaml +92 -0
  157. package/src/templates/infrastructure/terraform/aws/template/README.md +156 -0
  158. package/src/templates/infrastructure/terraform/aws/template/main.tf +343 -0
  159. package/src/templates/infrastructure/terraform/aws/template/outputs.tf +66 -0
  160. package/src/templates/infrastructure/terraform/aws/template/terraform.tfvars.example +28 -0
  161. package/src/templates/infrastructure/terraform/aws/template/variables.tf +110 -0
  162. package/src/templates/infrastructure/terraform/gcp/template/README.md +165 -0
  163. package/src/templates/infrastructure/terraform/gcp/template/main.tf +397 -0
  164. package/src/templates/infrastructure/terraform/gcp/template/outputs.tf +51 -0
  165. package/src/templates/infrastructure/terraform/gcp/template/terraform.tfvars.example +29 -0
  166. package/src/templates/infrastructure/terraform/gcp/template/variables.tf +115 -0
  167. package/src/templates/shared/admin/web/app/admin/layout.tsx +34 -0
  168. package/src/templates/shared/admin/web/components/admin-nav.tsx +48 -0
  169. package/src/templates/shared/audit/web/lib/audit.ts +24 -0
  170. package/src/templates/shared/auth/keycloak/web/app/api/auth/federated-logout/route.ts +173 -0
  171. package/src/templates/shared/auth/keycloak/web/auth.config.ts +84 -0
  172. package/src/templates/shared/auth/keycloak/web/auth.ts +26 -0
  173. package/src/templates/shared/beta/web/app/api/beta-settings/route.ts +25 -0
  174. package/src/templates/shared/beta/web/app/api/validate-beta-code/route.ts +67 -0
  175. package/src/templates/shared/beta/web/lib/beta/settings.ts +31 -0
  176. package/src/templates/shared/cache/web/lib/cache.ts +44 -0
  177. package/src/templates/shared/config/web/lib/config.ts +112 -0
  178. package/src/templates/shared/config/web/next.config.mjs +62 -0
  179. package/src/templates/shared/contact/web/app/api/contact/route.ts +113 -0
  180. package/src/templates/shared/contact/web/app/contact/page.tsx +195 -0
  181. package/src/templates/shared/cookie-consent/web/components/cookie-consent.tsx +54 -0
  182. package/src/templates/shared/database/postgresql/web/drizzle.config.ts +16 -0
  183. package/src/templates/shared/database/postgresql/web/lib/db/drizzle.ts +39 -0
  184. package/src/templates/shared/database/postgresql/web/lib/db/schema.ts +33 -0
  185. package/src/templates/shared/database/supabase/web/lib/supabase/client.ts +12 -0
  186. package/src/templates/shared/database/supabase/web/lib/supabase/server.ts +31 -0
  187. package/src/templates/shared/database/supabase/web/lib/supabase/service.ts +15 -0
  188. package/src/templates/shared/email/web/lib/email/branding.ts +18 -0
  189. package/src/templates/shared/email/web/lib/email/client.ts +96 -0
  190. package/src/templates/shared/error-pages/web/app/error.tsx +70 -0
  191. package/src/templates/shared/error-pages/web/app/global-error.tsx +102 -0
  192. package/src/templates/shared/error-pages/web/app/not-found.tsx +39 -0
  193. package/src/templates/shared/health/web/app/api/health/route.ts +68 -0
  194. package/src/templates/shared/legal/web/app/(legal)/privacy/page.tsx +205 -0
  195. package/src/templates/shared/legal/web/app/(legal)/terms/page.tsx +154 -0
  196. package/src/templates/shared/legal/web/lib/legal-config.ts +50 -0
  197. package/src/templates/shared/loading/web/app/loading.tsx +5 -0
  198. package/src/templates/shared/loading/web/components/skeleton.tsx +95 -0
  199. package/src/templates/shared/middleware/web/middleware.ts +68 -0
  200. package/src/templates/shared/observability/web/lib/observability.ts +135 -0
  201. package/src/templates/shared/payments/web/app/api/webhooks/stripe/route.ts +109 -0
  202. package/src/templates/shared/platform/web/lib/platform.ts +37 -0
  203. package/src/templates/shared/redis/web/lib/rate-limit-store.ts +18 -0
  204. package/src/templates/shared/redis/web/lib/redis.ts +48 -0
  205. package/src/templates/shared/security/web/lib/api-security.ts +318 -0
  206. package/src/templates/shared/seo/web/app/api/og/route.tsx +97 -0
  207. package/src/templates/shared/seo/web/app/robots.ts +53 -0
  208. package/src/templates/shared/seo/web/app/sitemap.ts +53 -0
  209. package/src/templates/shared/utils/web/lib/api-response.ts +71 -0
  210. package/src/templates/shared/utils/web/lib/utils.ts +85 -0
  211. package/src/templates/web/ai-platform/template/.env.example +16 -0
  212. package/src/templates/web/ai-platform/template/README.md +84 -0
  213. package/src/templates/web/ai-platform/template/middleware.ts +55 -0
  214. package/src/templates/web/ai-platform/template/next.config.js +14 -0
  215. package/src/templates/web/ai-platform/template/package.json +55 -0
  216. package/src/templates/web/ai-platform/template/src/app/api/chat/route.ts +54 -0
  217. package/src/templates/web/ai-platform/template/src/app/chat/page.tsx +235 -0
  218. package/src/templates/web/ai-platform/template/src/app/dashboard/page.tsx +142 -0
  219. package/src/templates/web/ai-platform/template/src/app/globals.css +34 -0
  220. package/src/templates/web/ai-platform/template/src/app/layout.tsx +27 -0
  221. package/src/templates/web/ai-platform/template/src/app/page.tsx +203 -0
  222. package/src/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +27 -0
  223. package/src/templates/web/ai-platform/template/src/lib/auth-server.ts +33 -0
  224. package/src/templates/web/ai-platform/template/src/lib/supabase/client.ts +8 -0
  225. package/src/templates/web/ai-platform/template/src/lib/supabase/server.ts +27 -0
  226. package/src/templates/web/ai-platform/template/src/lib/utils.ts +6 -0
  227. package/src/templates/web/ai-platform/template/tsconfig.json +27 -0
  228. package/src/templates/web/iot-dashboard/template/.env.example +12 -0
  229. package/src/templates/web/iot-dashboard/template/README.md +101 -0
  230. package/src/templates/web/iot-dashboard/template/middleware.ts +56 -0
  231. package/src/templates/web/iot-dashboard/template/next.config.js +14 -0
  232. package/src/templates/web/iot-dashboard/template/package.json +49 -0
  233. package/src/templates/web/iot-dashboard/template/src/app/dashboard/page.tsx +229 -0
  234. package/src/templates/web/iot-dashboard/template/src/app/globals.css +20 -0
  235. package/src/templates/web/iot-dashboard/template/src/app/layout.tsx +27 -0
  236. package/src/templates/web/iot-dashboard/template/src/app/page.tsx +191 -0
  237. package/src/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +24 -0
  238. package/src/templates/web/iot-dashboard/template/src/lib/auth-server.ts +33 -0
  239. package/src/templates/web/iot-dashboard/template/src/lib/supabase/client.ts +8 -0
  240. package/src/templates/web/iot-dashboard/template/src/lib/supabase/server.ts +27 -0
  241. package/src/templates/web/iot-dashboard/template/src/lib/utils.ts +25 -0
  242. package/src/templates/web/iot-dashboard/template/tsconfig.json +27 -0
  243. package/src/templates/web/marketplace/template/.env.example +12 -0
  244. package/src/templates/web/marketplace/template/README.md +66 -0
  245. package/src/templates/web/marketplace/template/middleware.ts +56 -0
  246. package/src/templates/web/marketplace/template/next.config.js +14 -0
  247. package/src/templates/web/marketplace/template/package.json +51 -0
  248. package/src/templates/web/marketplace/template/src/app/cart/page.tsx +147 -0
  249. package/src/templates/web/marketplace/template/src/app/dashboard/page.tsx +149 -0
  250. package/src/templates/web/marketplace/template/src/app/globals.css +20 -0
  251. package/src/templates/web/marketplace/template/src/app/layout.tsx +27 -0
  252. package/src/templates/web/marketplace/template/src/app/page.tsx +167 -0
  253. package/src/templates/web/marketplace/template/src/app/products/page.tsx +129 -0
  254. package/src/templates/web/marketplace/template/src/components/providers/app-providers.tsx +27 -0
  255. package/src/templates/web/marketplace/template/src/lib/auth-server.ts +33 -0
  256. package/src/templates/web/marketplace/template/src/lib/supabase/client.ts +8 -0
  257. package/src/templates/web/marketplace/template/src/lib/supabase/server.ts +27 -0
  258. package/src/templates/web/marketplace/template/src/lib/utils.ts +19 -0
  259. package/src/templates/web/marketplace/template/tsconfig.json +27 -0
  260. package/src/templates/web/micro-saas/template/.env.example +10 -0
  261. package/src/templates/web/micro-saas/template/README.md +63 -0
  262. package/src/templates/web/micro-saas/template/middleware.ts +53 -0
  263. package/src/templates/web/micro-saas/template/next.config.js +14 -0
  264. package/src/templates/web/micro-saas/template/package.json +41 -0
  265. package/src/templates/web/micro-saas/template/src/app/dashboard/page.tsx +117 -0
  266. package/src/templates/web/micro-saas/template/src/app/globals.css +20 -0
  267. package/src/templates/web/micro-saas/template/src/app/layout.tsx +27 -0
  268. package/src/templates/web/micro-saas/template/src/app/login/page.tsx +87 -0
  269. package/src/templates/web/micro-saas/template/src/app/page.tsx +137 -0
  270. package/src/templates/web/micro-saas/template/src/app/signup/page.tsx +108 -0
  271. package/src/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +24 -0
  272. package/src/templates/web/micro-saas/template/src/lib/auth-server.ts +33 -0
  273. package/src/templates/web/micro-saas/template/src/lib/supabase/client.ts +8 -0
  274. package/src/templates/web/micro-saas/template/src/lib/supabase/server.ts +29 -0
  275. package/src/templates/web/micro-saas/template/src/lib/utils.ts +6 -0
  276. package/src/templates/web/micro-saas/template/tsconfig.json +27 -0
  277. package/dist/cli/commands/add.d.ts +0 -6
  278. package/dist/cli/commands/add.d.ts.map +0 -1
  279. package/dist/cli/commands/add.js +0 -39
  280. package/dist/cli/commands/add.js.map +0 -1
  281. package/dist/cli/commands/create.d.ts +0 -45
  282. package/dist/cli/commands/create.d.ts.map +0 -1
  283. package/dist/cli/commands/create.js +0 -175
  284. package/dist/cli/commands/create.js.map +0 -1
  285. package/dist/cli/commands/index.d.ts +0 -4
  286. package/dist/cli/commands/index.d.ts.map +0 -1
  287. package/dist/cli/commands/index.js +0 -20
  288. package/dist/cli/commands/index.js.map +0 -1
  289. package/dist/cli/commands/update.d.ts +0 -6
  290. package/dist/cli/commands/update.d.ts.map +0 -1
  291. package/dist/cli/commands/update.js +0 -68
  292. package/dist/cli/commands/update.js.map +0 -1
  293. package/dist/cli/index.d.ts +0 -4
  294. package/dist/cli/index.d.ts.map +0 -1
  295. package/dist/cli/index.js +0 -61
  296. package/dist/cli/index.js.map +0 -1
  297. package/dist/cli/prompts/index.d.ts +0 -2
  298. package/dist/cli/prompts/index.d.ts.map +0 -1
  299. package/dist/cli/prompts/index.js +0 -18
  300. package/dist/cli/prompts/index.js.map +0 -1
  301. package/dist/cli/prompts/project-setup.d.ts +0 -5
  302. package/dist/cli/prompts/project-setup.d.ts.map +0 -1
  303. package/dist/cli/prompts/project-setup.js +0 -316
  304. package/dist/cli/prompts/project-setup.js.map +0 -1
  305. package/dist/cli/utils/git.d.ts +0 -9
  306. package/dist/cli/utils/git.d.ts.map +0 -1
  307. package/dist/cli/utils/git.js +0 -77
  308. package/dist/cli/utils/git.js.map +0 -1
  309. package/dist/cli/utils/index.d.ts +0 -5
  310. package/dist/cli/utils/index.d.ts.map +0 -1
  311. package/dist/cli/utils/index.js +0 -21
  312. package/dist/cli/utils/index.js.map +0 -1
  313. package/dist/cli/utils/logger.d.ts +0 -16
  314. package/dist/cli/utils/logger.d.ts.map +0 -1
  315. package/dist/cli/utils/logger.js +0 -55
  316. package/dist/cli/utils/logger.js.map +0 -1
  317. package/dist/cli/utils/package-manager.d.ts +0 -8
  318. package/dist/cli/utils/package-manager.d.ts.map +0 -1
  319. package/dist/cli/utils/package-manager.js +0 -92
  320. package/dist/cli/utils/package-manager.js.map +0 -1
  321. package/dist/cli/utils/spinner.d.ts +0 -7
  322. package/dist/cli/utils/spinner.d.ts.map +0 -1
  323. package/dist/cli/utils/spinner.js +0 -48
  324. package/dist/cli/utils/spinner.js.map +0 -1
  325. package/dist/cli/validators/dependencies.d.ts +0 -15
  326. package/dist/cli/validators/dependencies.d.ts.map +0 -1
  327. package/dist/cli/validators/dependencies.js +0 -108
  328. package/dist/cli/validators/dependencies.js.map +0 -1
  329. package/dist/cli/validators/index.d.ts +0 -3
  330. package/dist/cli/validators/index.d.ts.map +0 -1
  331. package/dist/cli/validators/index.js +0 -19
  332. package/dist/cli/validators/index.js.map +0 -1
  333. package/dist/cli/validators/project-name.d.ts +0 -5
  334. package/dist/cli/validators/project-name.d.ts.map +0 -1
  335. package/dist/cli/validators/project-name.js +0 -151
  336. package/dist/cli/validators/project-name.js.map +0 -1
  337. package/dist/generators/file-processor.d.ts +0 -28
  338. package/dist/generators/file-processor.d.ts.map +0 -1
  339. package/dist/generators/file-processor.js +0 -224
  340. package/dist/generators/file-processor.js.map +0 -1
  341. package/dist/generators/index.d.ts +0 -4
  342. package/dist/generators/index.d.ts.map +0 -1
  343. package/dist/generators/index.js +0 -20
  344. package/dist/generators/index.js.map +0 -1
  345. package/dist/generators/package-installer.d.ts +0 -29
  346. package/dist/generators/package-installer.d.ts.map +0 -1
  347. package/dist/generators/package-installer.js +0 -177
  348. package/dist/generators/package-installer.js.map +0 -1
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Contact Form API
3
+ *
4
+ * POST — Submit a contact form message.
5
+ * Public endpoint with rate limiting. Sends email via Resend.
6
+ */
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import { z } from 'zod';
9
+ import { withPublicApi, escapeHtml } from '@/lib/api-security';
10
+ import { sendEmail } from '@/lib/email/client';
11
+
12
+ export const dynamic = 'force-dynamic';
13
+
14
+ const CATEGORY_LABELS: Record<string, string> = {
15
+ general: 'General Inquiry',
16
+ support: 'Technical Support',
17
+ business: 'Business Inquiry',
18
+ feedback: 'Feedback',
19
+ other: 'Other',
20
+ };
21
+
22
+ const ContactSchema = z.object({
23
+ name: z.string().min(2, 'Name must be at least 2 characters').max(100, 'Name is too long'),
24
+ email: z.string().email('Please enter a valid email address').max(255),
25
+ category: z.enum(['general', 'support', 'business', 'feedback', 'other'], {
26
+ errorMap: () => ({ message: 'Please select a category' }),
27
+ }),
28
+ message: z
29
+ .string()
30
+ .min(10, 'Message must be at least 10 characters')
31
+ .max(2000, 'Message is too long')
32
+ .refine((v) => !/<[^>]*>/.test(v), 'HTML tags are not allowed')
33
+ .refine(
34
+ (v) => !/(https?:\/\/|ftp:\/\/|www\.)\S+/i.test(v),
35
+ 'Links are not allowed for security reasons'
36
+ ),
37
+ });
38
+
39
+ export const POST = withPublicApi(
40
+ {
41
+ operation: 'contact/post',
42
+ rateLimit: { limit: 5, windowSeconds: 300 }, // 5 per 5 min — strict
43
+ },
44
+ async (request: NextRequest) => {
45
+ const body = await request.json();
46
+ const parsed = ContactSchema.safeParse(body);
47
+
48
+ if (!parsed.success) {
49
+ const errors: Record<string, string> = {};
50
+ for (const issue of parsed.error.issues) {
51
+ const field = String(issue.path[0] ?? 'input');
52
+ if (!errors[field]) errors[field] = issue.message;
53
+ }
54
+ return NextResponse.json({ error: 'Validation failed', errors }, { status: 400 });
55
+ }
56
+
57
+ const { name, email, category, message } = parsed.data;
58
+ const categoryLabel = CATEGORY_LABELS[category] ?? category;
59
+
60
+ // Build plain-text email
61
+ const text = [
62
+ `New Contact Form Submission`,
63
+ ``,
64
+ `Name: ${name}`,
65
+ `Email: ${email}`,
66
+ `Category: ${categoryLabel}`,
67
+ ``,
68
+ `Message:`,
69
+ message,
70
+ ].join('\n');
71
+
72
+ // Build HTML email (escape all user input)
73
+ const html = `
74
+ <div style="font-family:sans-serif;max-width:600px;margin:0 auto">
75
+ <h2 style="border-bottom:2px solid #2563eb;padding-bottom:8px">New Contact Form Submission</h2>
76
+ <table style="width:100%;border-collapse:collapse">
77
+ <tr><td style="padding:8px 0;font-weight:bold;width:100px">Name:</td><td style="padding:8px 0">${escapeHtml(name)}</td></tr>
78
+ <tr><td style="padding:8px 0;font-weight:bold">Email:</td><td style="padding:8px 0">${escapeHtml(email)}</td></tr>
79
+ <tr><td style="padding:8px 0;font-weight:bold">Category:</td><td style="padding:8px 0">${escapeHtml(categoryLabel)}</td></tr>
80
+ </table>
81
+ <div style="background:#f3f4f6;padding:16px;border-radius:8px;margin-top:16px">
82
+ <strong>Message:</strong>
83
+ <p style="white-space:pre-wrap;margin:8px 0 0">${escapeHtml(message)}</p>
84
+ </div>
85
+ <p style="font-size:12px;color:#6b7280;margin-top:24px">
86
+ Sent from the contact form. Reply directly to respond to ${escapeHtml(name)}.
87
+ </p>
88
+ </div>`;
89
+
90
+ const adminEmail = process.env.ADMIN_EMAIL || process.env.EMAIL_FROM;
91
+ if (!adminEmail) {
92
+ console.error('[Contact] Neither ADMIN_EMAIL nor EMAIL_FROM is configured');
93
+ return NextResponse.json({ error: 'Server configuration error' }, { status: 500 });
94
+ }
95
+
96
+ const sent = await sendEmail({
97
+ to: adminEmail,
98
+ subject: `Contact: ${categoryLabel} — ${name}`,
99
+ html,
100
+ text,
101
+ replyTo: email,
102
+ });
103
+
104
+ if (!sent) {
105
+ return NextResponse.json(
106
+ { error: 'Failed to send message. Please try again later.' },
107
+ { status: 502 }
108
+ );
109
+ }
110
+
111
+ return NextResponse.json({ success: true });
112
+ }
113
+ );
@@ -0,0 +1,195 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import type { FormEvent } from 'react';
5
+
6
+ const CATEGORIES = [
7
+ { value: 'general', label: 'General Inquiry' },
8
+ { value: 'support', label: 'Technical Support' },
9
+ { value: 'business', label: 'Business Inquiry' },
10
+ { value: 'feedback', label: 'Feedback' },
11
+ { value: 'other', label: 'Other' },
12
+ ] as const;
13
+
14
+ type Status = 'idle' | 'submitting' | 'success' | 'error';
15
+
16
+ export default function ContactPage() {
17
+ const [status, setStatus] = useState<Status>('idle');
18
+ const [errorMessage, setErrorMessage] = useState('');
19
+ const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
20
+
21
+ async function handleSubmit(e: FormEvent<HTMLFormElement>) {
22
+ e.preventDefault();
23
+ setStatus('submitting');
24
+ setErrorMessage('');
25
+ setFieldErrors({});
26
+
27
+ const formData = new FormData(e.currentTarget);
28
+ const body = {
29
+ name: formData.get('name') as string,
30
+ email: formData.get('email') as string,
31
+ category: formData.get('category') as string,
32
+ message: formData.get('message') as string,
33
+ };
34
+
35
+ try {
36
+ const res = await fetch('/api/contact', {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify(body),
40
+ });
41
+
42
+ const data = await res.json();
43
+
44
+ if (!res.ok) {
45
+ if (data.errors) {
46
+ setFieldErrors(data.errors);
47
+ }
48
+ setErrorMessage(data.error || 'Something went wrong. Please try again.');
49
+ setStatus('error');
50
+ return;
51
+ }
52
+
53
+ setStatus('success');
54
+ (e.target as HTMLFormElement).reset();
55
+ } catch {
56
+ setErrorMessage('Network error. Please check your connection and try again.');
57
+ setStatus('error');
58
+ }
59
+ }
60
+
61
+ if (status === 'success') {
62
+ return (
63
+ <div className="mx-auto max-w-2xl px-4 py-24 text-center">
64
+ <div className="rounded-2xl border border-green-200 bg-green-50 p-8 dark:border-green-800 dark:bg-green-950">
65
+ <h1 className="mb-4 text-2xl font-bold text-green-800 dark:text-green-200">
66
+ Message Sent
67
+ </h1>
68
+ <p className="text-green-700 dark:text-green-300">
69
+ Thank you for reaching out. We&apos;ll get back to you as soon as possible.
70
+ </p>
71
+ <button
72
+ onClick={() => setStatus('idle')}
73
+ className="mt-6 rounded-lg bg-green-600 px-6 py-2 text-sm font-medium text-white hover:bg-green-700"
74
+ >
75
+ Send Another Message
76
+ </button>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <div className="mx-auto max-w-2xl px-4 py-16 sm:py-24">
84
+ <div className="mb-10 text-center">
85
+ <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">Contact Us</h1>
86
+ <p className="mt-3 text-muted-foreground">
87
+ Have a question or need help? Fill out the form below and we&apos;ll get back to you.
88
+ </p>
89
+ </div>
90
+
91
+ <form onSubmit={handleSubmit} className="space-y-6">
92
+ {/* Name */}
93
+ <div>
94
+ <label htmlFor="name" className="mb-1.5 block text-sm font-medium">
95
+ Name
96
+ </label>
97
+ <input
98
+ id="name"
99
+ name="name"
100
+ type="text"
101
+ required
102
+ minLength={2}
103
+ maxLength={100}
104
+ className="w-full rounded-lg border border-input bg-background px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
105
+ placeholder="Your name"
106
+ />
107
+ {fieldErrors.name && (
108
+ <p className="mt-1 text-sm text-red-500">{fieldErrors.name}</p>
109
+ )}
110
+ </div>
111
+
112
+ {/* Email */}
113
+ <div>
114
+ <label htmlFor="email" className="mb-1.5 block text-sm font-medium">
115
+ Email
116
+ </label>
117
+ <input
118
+ id="email"
119
+ name="email"
120
+ type="email"
121
+ required
122
+ maxLength={255}
123
+ className="w-full rounded-lg border border-input bg-background px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
124
+ placeholder="you@example.com"
125
+ />
126
+ {fieldErrors.email && (
127
+ <p className="mt-1 text-sm text-red-500">{fieldErrors.email}</p>
128
+ )}
129
+ </div>
130
+
131
+ {/* Category */}
132
+ <div>
133
+ <label htmlFor="category" className="mb-1.5 block text-sm font-medium">
134
+ Category
135
+ </label>
136
+ <select
137
+ id="category"
138
+ name="category"
139
+ required
140
+ defaultValue=""
141
+ className="w-full rounded-lg border border-input bg-background px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
142
+ >
143
+ <option value="" disabled>
144
+ Select a category
145
+ </option>
146
+ {CATEGORIES.map((cat) => (
147
+ <option key={cat.value} value={cat.value}>
148
+ {cat.label}
149
+ </option>
150
+ ))}
151
+ </select>
152
+ {fieldErrors.category && (
153
+ <p className="mt-1 text-sm text-red-500">{fieldErrors.category}</p>
154
+ )}
155
+ </div>
156
+
157
+ {/* Message */}
158
+ <div>
159
+ <label htmlFor="message" className="mb-1.5 block text-sm font-medium">
160
+ Message
161
+ </label>
162
+ <textarea
163
+ id="message"
164
+ name="message"
165
+ required
166
+ minLength={10}
167
+ maxLength={2000}
168
+ rows={5}
169
+ className="w-full rounded-lg border border-input bg-background px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
170
+ placeholder="How can we help?"
171
+ />
172
+ {fieldErrors.message && (
173
+ <p className="mt-1 text-sm text-red-500">{fieldErrors.message}</p>
174
+ )}
175
+ </div>
176
+
177
+ {/* Error banner */}
178
+ {status === 'error' && errorMessage && (
179
+ <div className="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
180
+ {errorMessage}
181
+ </div>
182
+ )}
183
+
184
+ {/* Submit */}
185
+ <button
186
+ type="submit"
187
+ disabled={status === 'submitting'}
188
+ className="w-full rounded-lg bg-primary px-6 py-3 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
189
+ >
190
+ {status === 'submitting' ? 'Sending...' : 'Send Message'}
191
+ </button>
192
+ </form>
193
+ </div>
194
+ );
195
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import Link from 'next/link';
5
+
6
+ /**
7
+ * GDPR cookie consent banner.
8
+ *
9
+ * Stores consent status in localStorage. Shows a fixed bottom banner
10
+ * until the user accepts or declines. Customize the text and styling
11
+ * to match your app's branding.
12
+ */
13
+ export function CookieConsent() {
14
+ const [visible, setVisible] = useState(false);
15
+
16
+ useEffect(() => {
17
+ const consent = localStorage.getItem('cookie-consent');
18
+ if (!consent) setVisible(true);
19
+ }, []);
20
+
21
+ if (!visible) return null;
22
+
23
+ const handleChoice = (choice: 'accepted' | 'declined') => {
24
+ localStorage.setItem('cookie-consent', choice);
25
+ setVisible(false);
26
+ };
27
+
28
+ return (
29
+ <div className="fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg dark:border-gray-800 dark:bg-gray-950">
30
+ <div className="mx-auto flex max-w-5xl flex-col items-center gap-4 sm:flex-row sm:justify-between">
31
+ <p className="text-sm text-gray-600 dark:text-gray-400">
32
+ We use cookies to improve your experience.{' '}
33
+ <Link href="/privacy" className="underline hover:text-gray-900 dark:hover:text-gray-200">
34
+ Learn more
35
+ </Link>
36
+ </p>
37
+ <div className="flex gap-3">
38
+ <button
39
+ onClick={() => handleChoice('declined')}
40
+ className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
41
+ >
42
+ Decline
43
+ </button>
44
+ <button
45
+ onClick={() => handleChoice('accepted')}
46
+ className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
47
+ >
48
+ Accept
49
+ </button>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,16 @@
1
+ import type { Config } from 'drizzle-kit';
2
+ import 'dotenv/config';
3
+
4
+ if (!process.env.DATABASE_URL) {
5
+ throw new Error('DATABASE_URL is not set in .env file');
6
+ }
7
+
8
+ export default {
9
+ schema: './lib/db/schema.ts',
10
+ out: './lib/db/migrations',
11
+ dialect: 'postgresql',
12
+ dbCredentials: {
13
+ url: process.env.DATABASE_URL,
14
+ },
15
+ breakpoints: true,
16
+ } satisfies Config;
@@ -0,0 +1,39 @@
1
+ import { drizzle, PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
+ import postgres, { Sql } from 'postgres';
3
+ import * as schema from './schema';
4
+ import { config } from '@/lib/config';
5
+
6
+ let _client: Sql | null = null;
7
+ let _db: PostgresJsDatabase<typeof schema> | null = null;
8
+
9
+ export function isDatabaseConfigured(): boolean {
10
+ return !!process.env.DATABASE_URL;
11
+ }
12
+
13
+ function getClient(): Sql {
14
+ if (!_client) {
15
+ _client = postgres(config.db.url);
16
+ }
17
+ return _client;
18
+ }
19
+
20
+ function getDatabase(): PostgresJsDatabase<typeof schema> {
21
+ if (!_db) {
22
+ _db = drizzle(getClient(), { schema });
23
+ }
24
+ return _db;
25
+ }
26
+
27
+ /** Lazy-initialized postgres-js client (defers connection until first use). */
28
+ export const client = new Proxy({} as Sql, {
29
+ get(_target, prop) {
30
+ return (getClient() as any)[prop];
31
+ },
32
+ });
33
+
34
+ /** Lazy-initialized Drizzle ORM instance with typed schema. */
35
+ export const db = new Proxy({} as PostgresJsDatabase<typeof schema>, {
36
+ get(_target, prop) {
37
+ return (getDatabase() as any)[prop];
38
+ },
39
+ });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Drizzle ORM Schema
3
+ *
4
+ * Define your database tables here. Each app uses its own schema namespace
5
+ * for isolation when sharing a PostgreSQL instance.
6
+ *
7
+ * Usage:
8
+ * import { db } from '@/lib/db/drizzle';
9
+ * import { users } from '@/lib/db/schema';
10
+ * const allUsers = await db.select().from(users);
11
+ *
12
+ * Generate migrations:
13
+ * npx drizzle-kit generate
14
+ *
15
+ * Apply migrations:
16
+ * npx drizzle-kit push
17
+ */
18
+ import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core';
19
+ // import { pgSchema } from 'drizzle-orm/pg-core'; // Uncomment for schema isolation
20
+
21
+ // Use a schema namespace for isolation (recommended for multi-app databases)
22
+ // export const appSchema = pgSchema('myapp');
23
+ // export const users = appSchema.table('users', { ... });
24
+
25
+ // Or use public schema (simpler for single-app databases)
26
+ export const users = pgTable('users', {
27
+ id: text('id').primaryKey(),
28
+ email: text('email').notNull().unique(),
29
+ name: text('name'),
30
+ emailVerified: boolean('email_verified').default(false),
31
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
32
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
33
+ });
@@ -0,0 +1,12 @@
1
+ import { createBrowserClient } from '@supabase/ssr';
2
+
3
+ /**
4
+ * Supabase client for use in Client Components.
5
+ * Uses browser-side auth (reads cookies automatically).
6
+ */
7
+ export function createClient() {
8
+ return createBrowserClient(
9
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
10
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
11
+ );
12
+ }
@@ -0,0 +1,31 @@
1
+ import { createServerClient } from '@supabase/ssr';
2
+ import { cookies } from 'next/headers';
3
+
4
+ /**
5
+ * Supabase client for use in Server Components, Route Handlers, and Server Actions.
6
+ * Reads and writes auth cookies via Next.js cookies() API.
7
+ */
8
+ export async function createClient() {
9
+ const cookieStore = await cookies();
10
+
11
+ return createServerClient(
12
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
13
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
14
+ {
15
+ cookies: {
16
+ getAll() {
17
+ return cookieStore.getAll();
18
+ },
19
+ setAll(cookiesToSet) {
20
+ try {
21
+ cookiesToSet.forEach(({ name, value, options }) =>
22
+ cookieStore.set(name, value, options)
23
+ );
24
+ } catch {
25
+ // Called from a Server Component — cookies are read-only
26
+ }
27
+ },
28
+ },
29
+ }
30
+ );
31
+ }
@@ -0,0 +1,15 @@
1
+ import 'server-only';
2
+ import { createClient } from '@supabase/supabase-js';
3
+
4
+ /**
5
+ * Supabase admin client with service_role key.
6
+ * Bypasses Row Level Security — use only in trusted server-side code
7
+ * (webhooks, cron jobs, admin operations).
8
+ */
9
+ export function createServiceClient() {
10
+ return createClient(
11
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
12
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
13
+ { auth: { persistSession: false } }
14
+ );
15
+ }
@@ -0,0 +1,18 @@
1
+ import type { EmailBranding } from '@digilogiclabs/platform-core/email-templates';
2
+
3
+ /**
4
+ * App branding for shared email templates.
5
+ * Used by platform-core's email template functions (welcomeEmail, notificationEmail, etc.)
6
+ *
7
+ * Update these values to match your app's branding.
8
+ */
9
+ export const APP_BRANDING: EmailBranding = {
10
+ appName: 'My App',
11
+ primaryColor: '#3b82f6',
12
+ gradientFrom: '#3b82f6',
13
+ gradientTo: '#8b5cf6',
14
+ fromEmail: 'noreply@example.com',
15
+ supportEmail: 'support@example.com',
16
+ baseUrl: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
17
+ footerText: 'Built with Digi Logic Labs platform',
18
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Email client singleton.
3
+ *
4
+ * Lazy-initializes a Resend client when first needed.
5
+ * Returns null gracefully if RESEND_API_KEY is not configured.
6
+ *
7
+ * Usage:
8
+ * import { sendEmail } from '@/lib/email/client';
9
+ * await sendEmail({ to: 'user@example.com', subject: 'Hello', html: '<p>Hi</p>' });
10
+ */
11
+
12
+ let resendClient: Resend | null = null;
13
+ let initialized = false;
14
+
15
+ type Resend = {
16
+ emails: {
17
+ send: (params: {
18
+ from: string;
19
+ to: string | string[];
20
+ subject: string;
21
+ html: string;
22
+ text?: string;
23
+ replyTo?: string;
24
+ }) => Promise<{ data: { id: string } | null; error: { message: string } | null }>;
25
+ };
26
+ };
27
+
28
+ function getResend(): Resend | null {
29
+ if (initialized) return resendClient;
30
+ initialized = true;
31
+
32
+ const apiKey = process.env.RESEND_API_KEY;
33
+ if (!apiKey) {
34
+ console.warn('[Email] RESEND_API_KEY not configured — emails will be logged to console');
35
+ return null;
36
+ }
37
+
38
+ try {
39
+ // Dynamic import to avoid requiring resend in all environments
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ const { Resend } = require('resend');
42
+ resendClient = new Resend(apiKey);
43
+ return resendClient;
44
+ } catch {
45
+ console.warn('[Email] resend package not installed — emails will be logged to console');
46
+ return null;
47
+ }
48
+ }
49
+
50
+ const DEFAULT_FROM = process.env.EMAIL_FROM || 'noreply@example.com';
51
+
52
+ interface EmailParams {
53
+ to: string | string[];
54
+ subject: string;
55
+ html: string;
56
+ text?: string;
57
+ from?: string;
58
+ replyTo?: string;
59
+ }
60
+
61
+ /**
62
+ * Send an email via Resend. Returns true on success, false on failure.
63
+ * Falls back to console logging in development or when Resend is not configured.
64
+ */
65
+ export async function sendEmail(params: EmailParams): Promise<boolean> {
66
+ const from = params.from || DEFAULT_FROM;
67
+ const resend = getResend();
68
+
69
+ if (!resend) {
70
+ if (process.env.NODE_ENV === 'development') {
71
+ console.log('[Email] Would send:', { to: params.to, subject: params.subject, from });
72
+ }
73
+ return false;
74
+ }
75
+
76
+ try {
77
+ const { error } = await resend.emails.send({
78
+ from,
79
+ to: params.to,
80
+ subject: params.subject,
81
+ html: params.html,
82
+ text: params.text,
83
+ replyTo: params.replyTo,
84
+ });
85
+
86
+ if (error) {
87
+ console.error('[Email] Send failed:', error.message);
88
+ return false;
89
+ }
90
+
91
+ return true;
92
+ } catch (err) {
93
+ console.error('[Email] Send error:', err);
94
+ return false;
95
+ }
96
+ }