@lastbrain/module-auth 2.0.27 → 2.0.31

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 (179) hide show
  1. package/README.md +55 -7
  2. package/dist/api/admin/signup-stats.d.ts.map +1 -1
  3. package/dist/api/admin/signup-stats.js +2 -1
  4. package/dist/api/admin/storage/usage.d.ts +18 -0
  5. package/dist/api/admin/storage/usage.d.ts.map +1 -0
  6. package/dist/api/admin/storage/usage.js +100 -0
  7. package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
  8. package/dist/api/admin/users/[id]/notifications.js +3 -2
  9. package/dist/api/admin/users/[id].d.ts.map +1 -1
  10. package/dist/api/admin/users/[id].js +3 -2
  11. package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
  12. package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
  13. package/dist/api/admin/users/reactivate/[id].js +59 -0
  14. package/dist/api/admin/users/suspend/[id].d.ts +16 -0
  15. package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
  16. package/dist/api/admin/users/suspend/[id].js +59 -0
  17. package/dist/api/admin/users-by-source.d.ts.map +1 -1
  18. package/dist/api/admin/users-by-source.js +2 -1
  19. package/dist/api/admin/users.d.ts.map +1 -1
  20. package/dist/api/admin/users.js +53 -2
  21. package/dist/api/auth/account/email-change.d.ts +7 -0
  22. package/dist/api/auth/account/email-change.d.ts.map +1 -0
  23. package/dist/api/auth/account/email-change.js +39 -0
  24. package/dist/api/auth/account/reset-password.d.ts +7 -0
  25. package/dist/api/auth/account/reset-password.d.ts.map +1 -0
  26. package/dist/api/auth/account/reset-password.js +36 -0
  27. package/dist/api/auth/check-username.d.ts +9 -0
  28. package/dist/api/auth/check-username.d.ts.map +1 -0
  29. package/dist/api/auth/check-username.js +35 -0
  30. package/dist/api/auth/establish-session.d.ts +2 -0
  31. package/dist/api/auth/establish-session.d.ts.map +1 -0
  32. package/dist/api/auth/establish-session.js +23 -0
  33. package/dist/api/auth/me.d.ts +4 -4
  34. package/dist/api/auth/me.d.ts.map +1 -1
  35. package/dist/api/auth/me.js +28 -6
  36. package/dist/api/auth/profile.d.ts.map +1 -1
  37. package/dist/api/auth/profile.js +6 -3
  38. package/dist/api/auth/storage/recalculate.d.ts +15 -0
  39. package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
  40. package/dist/api/auth/storage/recalculate.js +68 -0
  41. package/dist/api/auth/storage/usage.d.ts +10 -0
  42. package/dist/api/auth/storage/usage.d.ts.map +1 -0
  43. package/dist/api/auth/storage/usage.js +86 -0
  44. package/dist/api/public/add-welcome-bonus.d.ts +16 -0
  45. package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
  46. package/dist/api/public/add-welcome-bonus.js +177 -0
  47. package/dist/api/public/callback.d.ts +3 -0
  48. package/dist/api/public/callback.d.ts.map +1 -0
  49. package/dist/api/public/callback.js +197 -0
  50. package/dist/api/public/reset-password.d.ts +3 -0
  51. package/dist/api/public/reset-password.d.ts.map +1 -0
  52. package/dist/api/public/reset-password.js +43 -0
  53. package/dist/api/public/set-session.d.ts +7 -0
  54. package/dist/api/public/set-session.d.ts.map +1 -0
  55. package/dist/api/public/set-session.js +55 -0
  56. package/dist/api/public/signin.d.ts.map +1 -1
  57. package/dist/api/public/signin.js +31 -0
  58. package/dist/api/public/signup.d.ts.map +1 -1
  59. package/dist/api/public/signup.js +38 -27
  60. package/dist/api/public/webhook/storage-addon.d.ts +9 -0
  61. package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
  62. package/dist/api/public/webhook/storage-addon.js +155 -0
  63. package/dist/api/storage.js +2 -2
  64. package/dist/auth.build.config.d.ts.map +1 -1
  65. package/dist/auth.build.config.js +126 -11
  66. package/dist/components/AccountButton.d.ts.map +1 -1
  67. package/dist/components/AccountButton.js +54 -28
  68. package/dist/components/Doc.d.ts.map +1 -1
  69. package/dist/components/Doc.js +1 -1
  70. package/dist/components/HasProfil.d.ts +4 -0
  71. package/dist/components/HasProfil.d.ts.map +1 -0
  72. package/dist/components/HasProfil.js +39 -0
  73. package/dist/components/auth/dashboard.d.ts +1 -1
  74. package/dist/components/auth/dashboard.d.ts.map +1 -1
  75. package/dist/components/auth/dashboard.js +34 -7
  76. package/dist/index.d.ts +2 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +2 -0
  79. package/dist/lib/app-branding-data.d.ts +22 -0
  80. package/dist/lib/app-branding-data.d.ts.map +1 -0
  81. package/dist/lib/app-branding-data.js +49 -0
  82. package/dist/lib/auth-email-service.d.ts +57 -0
  83. package/dist/lib/auth-email-service.d.ts.map +1 -0
  84. package/dist/lib/auth-email-service.js +382 -0
  85. package/dist/lib/auth-email-templates.d.ts +2 -0
  86. package/dist/lib/auth-email-templates.d.ts.map +1 -0
  87. package/dist/lib/auth-email-templates.js +1 -0
  88. package/dist/lib/site-url.d.ts +3 -0
  89. package/dist/lib/site-url.d.ts.map +1 -0
  90. package/dist/lib/site-url.js +11 -0
  91. package/dist/sitemap/manifest.d.ts +9 -0
  92. package/dist/sitemap/manifest.d.ts.map +1 -0
  93. package/dist/sitemap/manifest.js +14 -0
  94. package/dist/web/admin/signup-stats.js +3 -3
  95. package/dist/web/admin/user-detail.d.ts.map +1 -1
  96. package/dist/web/admin/user-detail.js +135 -14
  97. package/dist/web/admin/users-by-signup-source.js +2 -2
  98. package/dist/web/admin/users.d.ts.map +1 -1
  99. package/dist/web/admin/users.js +26 -7
  100. package/dist/web/auth/folder.d.ts.map +1 -1
  101. package/dist/web/auth/folder.js +4 -3
  102. package/dist/web/auth/profile.d.ts.map +1 -1
  103. package/dist/web/auth/profile.js +132 -13
  104. package/dist/web/auth/reglage.d.ts.map +1 -1
  105. package/dist/web/auth/reglage.js +15 -8
  106. package/dist/web/public/ResetPassword.d.ts.map +1 -1
  107. package/dist/web/public/ResetPassword.js +172 -2
  108. package/dist/web/public/SignInPage.d.ts.map +1 -1
  109. package/dist/web/public/SignInPage.js +39 -3
  110. package/dist/web/public/SignUpPage.d.ts.map +1 -1
  111. package/dist/web/public/SignUpPage.js +7 -2
  112. package/dist/web/public/auth-code-error.d.ts +2 -0
  113. package/dist/web/public/auth-code-error.d.ts.map +1 -0
  114. package/dist/web/public/auth-code-error.js +14 -0
  115. package/dist/web/public/confirm.d.ts +2 -0
  116. package/dist/web/public/confirm.d.ts.map +1 -0
  117. package/dist/web/public/confirm.js +157 -0
  118. package/package.json +10 -5
  119. package/src/api/admin/signup-stats.ts +2 -1
  120. package/src/api/admin/storage/usage.ts +141 -0
  121. package/src/api/admin/users/[id]/notifications.ts +3 -2
  122. package/src/api/admin/users/[id].ts +3 -2
  123. package/src/api/admin/users/reactivate/[id].ts +88 -0
  124. package/src/api/admin/users/suspend/[id].ts +85 -0
  125. package/src/api/admin/users-by-source.ts +2 -1
  126. package/src/api/admin/users.ts +59 -2
  127. package/src/api/auth/account/email-change.ts +54 -0
  128. package/src/api/auth/account/reset-password.ts +47 -0
  129. package/src/api/auth/check-username.ts +52 -0
  130. package/src/api/auth/establish-session.ts +32 -0
  131. package/src/api/auth/me.ts +29 -7
  132. package/src/api/auth/profile.ts +6 -2
  133. package/src/api/auth/storage/recalculate.ts +108 -0
  134. package/src/api/auth/storage/usage.ts +113 -0
  135. package/src/api/public/add-welcome-bonus.ts +229 -0
  136. package/src/api/public/callback.ts +307 -0
  137. package/src/api/public/reset-password.ts +52 -0
  138. package/src/api/public/set-session.ts +73 -0
  139. package/src/api/public/signin.ts +36 -0
  140. package/src/api/public/signup.ts +44 -37
  141. package/src/api/public/webhook/storage-addon.ts +267 -0
  142. package/src/api/storage.ts +2 -2
  143. package/src/auth.build.config.ts +126 -11
  144. package/src/components/AccountButton.tsx +114 -90
  145. package/src/components/Doc.tsx +47 -9
  146. package/src/components/HasProfil.tsx +63 -0
  147. package/src/components/auth/dashboard.tsx +54 -13
  148. package/src/i18n/en.json +76 -8
  149. package/src/i18n/es.json +330 -0
  150. package/src/i18n/fr.json +74 -8
  151. package/src/index.ts +2 -0
  152. package/src/lib/app-branding-data.ts +90 -0
  153. package/src/lib/auth-email-service.ts +508 -0
  154. package/src/lib/auth-email-templates.ts +5 -0
  155. package/src/lib/site-url.ts +17 -0
  156. package/src/sitemap/manifest.ts +26 -0
  157. package/src/web/admin/signup-stats.tsx +3 -3
  158. package/src/web/admin/user-detail.tsx +314 -15
  159. package/src/web/admin/users-by-signup-source.tsx +2 -2
  160. package/src/web/admin/users.tsx +50 -14
  161. package/src/web/auth/folder.tsx +23 -5
  162. package/src/web/auth/profile.tsx +227 -13
  163. package/src/web/auth/reglage.tsx +55 -24
  164. package/src/web/public/ResetPassword.tsx +301 -1
  165. package/src/web/public/SignInPage.tsx +43 -3
  166. package/src/web/public/SignUpPage.tsx +14 -5
  167. package/src/web/public/auth-code-error.tsx +49 -0
  168. package/src/web/public/confirm.tsx +195 -0
  169. package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
  170. package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
  171. package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
  172. package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
  173. package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
  174. package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
  175. package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
  176. package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
  177. package/dist/web/auth/dashboard.d.ts +0 -2
  178. package/dist/web/auth/dashboard.d.ts.map +0 -1
  179. package/dist/web/auth/dashboard.js +0 -48
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { Card, CardBody, CardHeader, Switch, Button, Spinner, Divider, Select, SelectItem, addToast, } from "@lastbrain/ui";
5
5
  import { Settings, Save } from "lucide-react";
6
- import { useModuleTranslation } from "@lastbrain/core";
6
+ import { logger, useModuleTranslation } from "@lastbrain/core";
7
7
  export function ReglagePage() {
8
8
  const t = useModuleTranslation("auth");
9
9
  const [preferences, setPreferences] = useState({
@@ -22,7 +22,9 @@ export function ReglagePage() {
22
22
  const fetchSettings = async () => {
23
23
  try {
24
24
  setIsLoading(true);
25
- const response = await fetch("/api/auth/profile");
25
+ const response = await fetch("/api/auth/profile", {
26
+ credentials: "include",
27
+ });
26
28
  if (!response.ok) {
27
29
  throw new Error("Failed to fetch settings");
28
30
  }
@@ -38,10 +40,10 @@ export function ReglagePage() {
38
40
  }
39
41
  }
40
42
  catch (err) {
41
- console.error("Error loading settings:", err);
43
+ logger.error("Error loading settings:", err);
42
44
  addToast({
43
- title: "Error",
44
- description: "Failed to load settings",
45
+ title: t("settings.load_error_title") || "Error",
46
+ description: t("settings.load_error_description") || "Failed to load settings",
45
47
  color: "danger",
46
48
  });
47
49
  }
@@ -57,6 +59,7 @@ export function ReglagePage() {
57
59
  headers: {
58
60
  "Content-Type": "application/json",
59
61
  },
62
+ credentials: "include",
60
63
  body: JSON.stringify({
61
64
  language: preferences.language,
62
65
  timezone: preferences.timezone,
@@ -76,9 +79,11 @@ export function ReglagePage() {
76
79
  description: t("settings.updated") || "Settings updated successfully",
77
80
  color: "success",
78
81
  });
82
+ // Si la langue a été modifiée, attendre la confirmation du serveur
83
+ // (localeUpdated) avant de forcer le reload complet.
79
84
  }
80
85
  catch (err) {
81
- console.error("Error updating settings:", err);
86
+ logger.error("Error updating settings:", err);
82
87
  addToast({
83
88
  title: t("settings.error") || "Error",
84
89
  description: t("settings.update_failed") || "Failed to update settings",
@@ -98,8 +103,10 @@ export function ReglagePage() {
98
103
  if (isLoading) {
99
104
  return (_jsx("div", { className: "flex justify-center items-center min-h-[400px]", children: _jsx(Spinner, { size: "lg", label: t("settings.loading") || "Loading settings..." }) }));
100
105
  }
101
- return (_jsxs("div", { className: "pt-12 pb-12 max-w-4xl mx-auto px-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(Settings, { className: "w-8 h-8" }), _jsx("h1", { className: "text-3xl font-bold", children: t("settings.account") || "Account Settings" })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("settings.notifications") || "Notifications" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: t("settings.email_notifications") || "Email Notifications" }), _jsx("p", { className: "text-small text-default-500", children: t("settings.email_notifications_desc") ||
106
+ return (_jsxs("div", { className: "md:pt-12 pb-12 max-w-2xl mx-auto md:px-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(Settings, { className: "w-8 h-8" }), _jsx("h1", { className: "text-3xl font-bold", children: t("settings.account") || "Account Settings" })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("settings.notifications") || "Notifications" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: t("settings.email_notifications") || "Email Notifications" }), _jsx("p", { className: "text-small text-default-500", children: t("settings.email_notifications_desc") ||
102
107
  "Receive email notifications for important updates" })] }), _jsx(Switch, { isSelected: preferences.email_notifications, onValueChange: (value) => handleToggle("email_notifications", value) })] }), _jsx(Divider, {}), _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: t("settings.push_notifications") || "Push Notifications" }), _jsx("p", { className: "text-small text-default-500", children: t("settings.push_notifications_desc") ||
103
108
  "Receive push notifications in your browser" })] }), _jsx(Switch, { isSelected: preferences.push_notifications, onValueChange: (value) => handleToggle("push_notifications", value) })] }), _jsx(Divider, {}), _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: t("settings.marketing_emails") || "Marketing Emails" }), _jsx("p", { className: "text-small text-default-500", children: t("settings.marketing_emails_desc") ||
104
- "Receive emails about new features and updates" })] }), _jsx(Switch, { isSelected: preferences.marketing_emails, onValueChange: (value) => handleToggle("marketing_emails", value) })] })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("settings.appearance") || "Appearance" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs(Select, { label: t("settings.theme") || "Theme", placeholder: t("settings.select_theme") || "Select a theme", selectedKeys: preferences.theme ? [preferences.theme] : [], onChange: (e) => handleSelect("theme", e.target.value), children: [_jsx(SelectItem, { children: t("settings.light") || "Light" }, "light"), _jsx(SelectItem, { children: t("settings.dark") || "Dark" }, "dark"), _jsx(SelectItem, { children: t("settings.system") || "System" }, "system")] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Language & Region" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs(Select, { label: "Language", placeholder: "Select a language", selectedKeys: preferences.language ? [preferences.language] : [], onChange: (e) => handleSelect("language", e.target.value), children: [_jsx(SelectItem, { children: "English" }, "en"), _jsx(SelectItem, { children: "Fran\u00E7ais" }, "fr"), _jsx(SelectItem, { children: "Espa\u00F1ol" }, "es"), _jsx(SelectItem, { children: "Deutsch" }, "de")] }), _jsxs(Select, { label: "Timezone", placeholder: "Select a timezone", selectedKeys: preferences.timezone ? [preferences.timezone] : [], onChange: (e) => handleSelect("timezone", e.target.value), children: [_jsx(SelectItem, { children: "UTC" }, "UTC"), _jsx(SelectItem, { children: "Europe/Paris" }, "Europe/Paris"), _jsx(SelectItem, { children: "America/New_York" }, "America/New_York"), _jsx(SelectItem, { children: "America/Los_Angeles" }, "America/Los_Angeles"), _jsx(SelectItem, { children: "Asia/Tokyo" }, "Asia/Tokyo")] })] }) })] }), _jsxs("div", { className: "flex justify-end gap-3", children: [_jsx(Button, { type: "button", variant: "flat", onPress: () => fetchSettings(), isDisabled: isSaving, children: "Reset" }), _jsx(Button, { type: "button", color: "primary", isLoading: isSaving, onPress: handleSave, startContent: !isSaving && _jsx(Save, { className: "w-4 h-4" }), children: isSaving ? "Saving..." : "Save Settings" })] })] })] }));
109
+ "Receive emails about new features and updates" })] }), _jsx(Switch, { isSelected: preferences.marketing_emails, onValueChange: (value) => handleToggle("marketing_emails", value) })] })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("settings.appearance") || "Appearance" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs(Select, { label: t("settings.theme") || "Theme", placeholder: t("settings.select_theme") || "Select a theme", selectedKeys: preferences.theme ? [preferences.theme] : [], onChange: (e) => handleSelect("theme", e.target.value), children: [_jsx(SelectItem, { children: t("settings.light") || "Light" }, "light"), _jsx(SelectItem, { children: t("settings.dark") || "Dark" }, "dark"), _jsx(SelectItem, { children: t("settings.system") || "System" }, "system")] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("settings.language_region") || "Language & Region" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs(Select, { label: t("settings.language") || "Language", placeholder: t("settings.language_placeholder") || "Select a language", selectedKeys: preferences.language ? [preferences.language] : [], onChange: (e) => handleSelect("language", e.target.value), children: [_jsx(SelectItem, { children: t("settings.lang_en") || "English" }, "en"), _jsx(SelectItem, { children: t("settings.lang_fr") || "Français" }, "fr"), _jsx(SelectItem, { children: t("settings.lang_es") || "Español" }, "es"), _jsx(SelectItem, { children: t("settings.lang_de") || "Deutsch" }, "de")] }), _jsxs(Select, { label: t("settings.timezone") || "Timezone", placeholder: t("settings.timezone_placeholder") || "Select a timezone", selectedKeys: preferences.timezone ? [preferences.timezone] : [], onChange: (e) => handleSelect("timezone", e.target.value), children: [_jsx(SelectItem, { children: t("settings.tz_utc") || "UTC" }, "UTC"), _jsx(SelectItem, { children: t("settings.tz_paris") || "Europe/Paris" }, "Europe/Paris"), _jsx(SelectItem, { children: t("settings.tz_ny") || "America/New_York" }, "America/New_York"), _jsx(SelectItem, { children: t("settings.tz_la") || "America/Los_Angeles" }, "America/Los_Angeles"), _jsx(SelectItem, { children: t("settings.tz_tokyo") || "Asia/Tokyo" }, "Asia/Tokyo")] })] }) })] }), _jsxs("div", { className: "flex justify-center gap-3", children: [_jsx(Button, { type: "button", variant: "flat", onPress: () => fetchSettings(), isDisabled: isSaving, children: t("settings.reset") || "Reset" }), _jsx(Button, { type: "button", color: "primary", isLoading: isSaving, onPress: handleSave, startContent: !isSaving && _jsx(Save, { className: "w-4 h-4" }), children: isSaving
110
+ ? t("settings.saving") || "Saving..."
111
+ : t("settings.save_button") || "Save Settings" })] })] })] }));
105
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ResetPassword.d.ts","sourceRoot":"","sources":["../../../src/web/public/ResetPassword.tsx"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,4CAE5B"}
1
+ {"version":3,"file":"ResetPassword.d.ts","sourceRoot":"","sources":["../../../src/web/public/ResetPassword.tsx"],"names":[],"mappings":"AAaA,wBAAgB,aAAa,4CAiS5B"}
@@ -1,4 +1,174 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { Alert, Button, Card, CardBody, Input, addToast } from "@lastbrain/ui";
5
+ import { Mail, ArrowRight, Lock } from "lucide-react";
6
+ import { useModuleTranslation, useLocalizedRouter, useLanguage, logger, supabaseBrowserClient, } from "@lastbrain/core";
2
7
  export function ResetPassword() {
3
- return _jsx("div", { children: "Reset Password Page" });
8
+ const t = useModuleTranslation("auth");
9
+ const router = useLocalizedRouter();
10
+ const { lang } = useLanguage();
11
+ const [email, setEmail] = useState("");
12
+ const [loading, setLoading] = useState(false);
13
+ const [success, setSuccess] = useState(null);
14
+ const [hasToken, setHasToken] = useState(false);
15
+ const [newPassword, setNewPassword] = useState("");
16
+ const [confirmPassword, setConfirmPassword] = useState("");
17
+ const [expiredMessage, setExpiredMessage] = useState(null);
18
+ // Detect access_token in hash (Supabase recovery link) and set session
19
+ useEffect(() => {
20
+ logger.debug("[ResetPassword] checking for tokens in URL hash");
21
+ const hash = window.location.hash.replace(/^#/, "");
22
+ if (!hash)
23
+ return;
24
+ const hasLangPrefix = /^\/[a-z]{2}(?:\/|$)/.test(window.location.pathname);
25
+ if (!hasLangPrefix) {
26
+ const preferredLang = lang || "fr";
27
+ const pathWithLang = window.location.pathname.startsWith("/")
28
+ ? window.location.pathname
29
+ : `/${window.location.pathname}`;
30
+ const target = `/${preferredLang}${pathWithLang}${window.location.search || ""}${window.location.hash || ""}`;
31
+ window.location.replace(target);
32
+ return;
33
+ }
34
+ const params = new URLSearchParams(hash);
35
+ const error = params.get("error");
36
+ const errorDescription = params.get("error_description");
37
+ if (error) {
38
+ const message = errorDescription ||
39
+ t("reset_password.link_expired") ||
40
+ "Lien invalide ou expiré, demandez un nouveau lien.";
41
+ setExpiredMessage(message);
42
+ logger.warn("Reset password link error", {
43
+ error,
44
+ description: errorDescription,
45
+ });
46
+ console.warn("Reset password magic link error", {
47
+ error,
48
+ errorDescription,
49
+ });
50
+ addToast({
51
+ title: t("reset_password.error") || "Erreur",
52
+ description: message,
53
+ color: "warning",
54
+ });
55
+ window.history.replaceState({}, document.title, window.location.pathname + window.location.search);
56
+ return;
57
+ }
58
+ const access_token = params.get("access_token");
59
+ const refresh_token = params.get("refresh_token");
60
+ if (access_token && refresh_token) {
61
+ supabaseBrowserClient.auth
62
+ .setSession({ access_token, refresh_token })
63
+ .then(async ({ error }) => {
64
+ if (error) {
65
+ addToast({
66
+ title: t("reset_password.error") || "Erreur",
67
+ description: error.message,
68
+ color: "danger",
69
+ });
70
+ return;
71
+ }
72
+ try {
73
+ const setSessionRes = await fetch("/api/public/set-session", {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify({ access_token, refresh_token }),
77
+ });
78
+ if (!setSessionRes.ok) {
79
+ const setSessionBody = await setSessionRes
80
+ .json()
81
+ .catch(() => null);
82
+ logger.info("Failed to persist session cookies", setSessionBody);
83
+ }
84
+ }
85
+ catch (err) {
86
+ logger.info("set-session call failed", err);
87
+ }
88
+ setHasToken(true);
89
+ // Clear hash to avoid leaking tokens
90
+ window.history.replaceState({}, document.title, window.location.pathname + window.location.search);
91
+ });
92
+ }
93
+ }, [t, lang]);
94
+ const handleRequestLink = async (e) => {
95
+ e.preventDefault();
96
+ setSuccess(null);
97
+ setLoading(true);
98
+ try {
99
+ const response = await fetch("/api/public/reset-password", {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ },
104
+ body: JSON.stringify({ email }),
105
+ });
106
+ const data = await response.json();
107
+ if (!response.ok) {
108
+ throw new Error(data.error || "Erreur lors de l'envoi du lien");
109
+ }
110
+ setSuccess(t("reset_password.success") ||
111
+ "Vérifiez votre email pour le lien de réinitialisation");
112
+ }
113
+ catch (error) {
114
+ addToast({
115
+ title: t("reset_password.error") || "Erreur",
116
+ description: error?.message ||
117
+ t("reset_password.error") ||
118
+ "Impossible d'envoyer le lien de réinitialisation",
119
+ color: "danger",
120
+ });
121
+ }
122
+ finally {
123
+ setLoading(false);
124
+ }
125
+ };
126
+ const handleUpdatePassword = async (e) => {
127
+ e.preventDefault();
128
+ if (newPassword.length < 6) {
129
+ addToast({
130
+ title: t("reset_password.error") || "Erreur",
131
+ description: t("signup.password_too_short") || "Mot de passe trop court",
132
+ color: "warning",
133
+ });
134
+ return;
135
+ }
136
+ if (newPassword !== confirmPassword) {
137
+ addToast({
138
+ title: t("reset_password.error") || "Erreur",
139
+ description: t("signup.password_mismatch") ||
140
+ "Les mots de passe ne correspondent pas.",
141
+ color: "warning",
142
+ });
143
+ return;
144
+ }
145
+ setLoading(true);
146
+ try {
147
+ const { error } = await supabaseBrowserClient.auth.updateUser({
148
+ password: newPassword,
149
+ });
150
+ if (error)
151
+ throw error;
152
+ addToast({
153
+ title: t("reset_password.success") || "Succès",
154
+ description: t("profile.security_reset_desc") || "Mot de passe mis à jour",
155
+ color: "success",
156
+ });
157
+ setTimeout(() => router.push("/auth/dashboard"), 1200);
158
+ }
159
+ catch (err) {
160
+ addToast({
161
+ title: t("reset_password.error") || "Erreur",
162
+ description: err?.message || "Impossible de mettre à jour le mot de passe",
163
+ color: "danger",
164
+ });
165
+ }
166
+ finally {
167
+ setLoading(false);
168
+ }
169
+ };
170
+ return (_jsx("div", { className: "min-h-[40vh] flex items-center justify-center px-4 py-16 mt-16 ", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "text-center mb-8", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("reset_password.title") || "Réinitialiser le mot de passe" }), _jsx("p", { className: "text-default-600 dark:text-default-500", children: t("reset_password.subtitle") ||
171
+ "Entrez votre email pour recevoir un lien" })] }), _jsx(Card, { className: "", children: _jsxs(CardBody, { className: "p-6 space-y-6", children: [expiredMessage && (_jsx(Alert, { color: "warning", title: t("reset_password.link_expired_title") || "Lien expiré", className: "text-sm font-semibold", children: expiredMessage })), hasToken ? (_jsxs("form", { onSubmit: handleUpdatePassword, className: "space-y-4", children: [_jsx(Input, { label: t("signup.password") || "Nouveau mot de passe", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), startContent: _jsx(Lock, { className: "w-4 h-4 text-default-400" }), isRequired: true }), _jsx(Input, { label: t("signup.confirm_password") || "Confirmer", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), startContent: _jsx(Lock, { className: "w-4 h-4 text-default-400" }), isRequired: true }), _jsx(Button, { type: "submit", color: "primary", isLoading: loading, endContent: !loading && _jsx(ArrowRight, { className: "w-4 h-4" }), className: "w-full", children: t("reset_password.submit") ||
172
+ "Mettre à jour le mot de passe" })] })) : success ? (_jsx("div", { className: "text-center text-success font-semibold", children: success })) : (_jsxs("form", { onSubmit: handleRequestLink, className: "space-y-6", children: [_jsx(Input, { label: t("reset_password.title") || "Réinitialiser le mot de passe", placeholder: t("reset_password.email_placeholder") || "votre@email.com", type: "email", value: email, onChange: (e) => setEmail(e.target.value), startContent: _jsx(Mail, { className: "w-4 h-4 text-default-400" }), isRequired: true }), _jsx(Button, { type: "submit", color: "primary", isLoading: loading, endContent: !loading && _jsx(ArrowRight, { className: "w-4 h-4" }), className: "w-full", children: t("reset_password.submit") || "Envoyer le lien" }), _jsx(Button, { type: "button", variant: "flat", onPress: () => router.push("/signin"), className: "w-full", children: t("reset_password.back_to_signin") ||
173
+ "Retour à la connexion" })] }))] }) })] }) }));
4
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SignInPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignInPage.tsx"],"names":[],"mappings":"AAiNA,wBAAgB,UAAU,4CAMzB"}
1
+ {"version":3,"file":"SignInPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignInPage.tsx"],"names":[],"mappings":"AAyPA,wBAAgB,UAAU,4CAMzB"}
@@ -34,6 +34,42 @@ function SignInForm() {
34
34
  return;
35
35
  }
36
36
  setIsLoading(false);
37
+ // Si le compte est suspendu, afficher un toast et déconnecter l'utilisateur
38
+ const isSuspended = data?.user?.app_metadata?.suspended ??
39
+ data?.user?.raw_app_meta_data?.suspended ??
40
+ false;
41
+ if (isSuspended) {
42
+ addToast({
43
+ color: "danger",
44
+ title: t("signin.suspended_title") || "Compte suspendu",
45
+ description: t("signin.suspended_message") ||
46
+ "Votre compte a été suspendu. Contactez l'administrateur.",
47
+ });
48
+ try {
49
+ await supabaseBrowserClient.auth.signOut();
50
+ }
51
+ catch (e) {
52
+ // ignore
53
+ }
54
+ return;
55
+ }
56
+ // Appeler l'endpoint serveur pour synchroniser le cookie `NEXT_LOCALE`.
57
+ // L'endpoint mettra à jour le cookie seulement s'il existe côté client
58
+ // (ex: cookie HttpOnly envoyé avec la requête).
59
+ // try {
60
+ // const res = await fetch("/api/auth/sync-locale", {
61
+ // method: "POST",
62
+ // credentials: "include",
63
+ // headers: { "Content-Type": "application/json" },
64
+ // body: JSON.stringify({ userId: data.user?.id }),
65
+ // });
66
+ // // Wait for body to be consumed so the Set-Cookie is processed by the browser
67
+ // if (res && res.body) {
68
+ // await res.text().catch(() => null);
69
+ // }
70
+ // } catch (e) {
71
+ // console.debug("signin: sync-locale request failed", e);
72
+ // }
37
73
  addToast({
38
74
  color: "success",
39
75
  title: t("signin.success_title") || "Connecté avec succès !",
@@ -50,14 +86,14 @@ function SignInForm() {
50
86
  });
51
87
  }
52
88
  };
53
- return (_jsxs("div", { className: "relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-primary-50/30 to-secondary-50/30 dark:from-primary-950/50 dark:to-secondary-950/50", children: [_jsx("div", { className: "absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" }), _jsx("div", { className: "relative flex min-h-[60vh] items-center justify-center px-4 py-12", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "mb-8 text-center", children: [_jsx(Chip, { color: "primary", variant: "flat", startContent: _jsx(Sparkles, { className: "w-4 h-4" }), className: "mb-4 p-2", children: t("signin.chip_label") || "Espace membre" }), _jsx("h1", { className: "mb-3 text-4xl font-bold", children: _jsx("span", { className: "bg-gradient-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent", children: t("signin.title") || "Bon retour" }) }), _jsx("p", { className: "text-default-600 dark:text-default-700", children: t("signin.subtitle") ||
54
- "Connectez-vous pour accéder à votre espace" })] }), _jsx(Card, { className: "border border-default-200/60 bg-background/60 backdrop-blur-md backdrop-saturate-150 shadow-xl", children: _jsxs(CardBody, { className: "gap-6 p-8", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [_jsx(Input, { label: t("email") || "Email", type: "email", placeholder: t("signin.email_placeholder") || "votre@email.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, startContent: _jsx(Mail, { className: "h-4 w-4 text-default-400" }), classNames: {
89
+ return (_jsxs("div", { className: "pt-16 relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-primary-50/30 to-secondary-50/30 dark:from-primary-950/50 dark:to-secondary-950/50", children: [_jsx("div", { className: "absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" }), _jsx("div", { className: "relative flex min-h-[60vh] items-center justify-center px-4 py-12", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "mb-8 text-center", children: [_jsx(Chip, { color: "primary", variant: "flat", startContent: _jsx(Sparkles, { className: "w-4 h-4" }), className: "mb-4 p-2", children: t("signin.chip_label") || "Espace membre" }), _jsx("h1", { className: "mb-3 text-4xl font-bold", children: _jsx("span", { className: "bg-gradient-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent", children: t("signin.title") || "Bon retour" }) }), _jsx("p", { className: "text-default-600 dark:text-default-700", children: t("signin.subtitle") ||
90
+ "Connectez-vous pour accéder à votre espace" })] }), _jsx(Card, { className: "border border-default-200/60 backdrop-blur-md backdrop-saturate-150 shadow-xl", children: _jsxs(CardBody, { className: "gap-6 p-8", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [_jsx(Input, { label: t("email") || "Email", type: "email", placeholder: t("signin.email_placeholder") || "votre@email.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, startContent: _jsx(Mail, { className: "h-4 w-4 text-default-400" }), classNames: {
55
91
  input: "text-base",
56
92
  inputWrapper: "border border-default-200/60 hover:border-primary-400 focus-within:border-primary-500",
57
93
  } }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Input, { label: t("password") || "Mot de passe", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, startContent: _jsx(Lock, { className: "h-4 w-4 text-default-400" }), classNames: {
58
94
  input: "text-base",
59
95
  inputWrapper: "border border-default-200/60 hover:border-primary-400 focus-within:border-primary-500",
60
- } }), _jsx(Link, { href: "/forgot-password", className: "text-xs text-default-500 hover:text-primary-500 self-end", children: t("signin.forgot_password") || "Mot de passe oublié ?" })] }), error && (_jsx("div", { className: "rounded-lg border border-danger-200 bg-danger-50/50 px-4 py-3 dark:border-danger-800 dark:bg-danger-950/50", children: _jsx("p", { className: "text-sm text-danger-600 dark:text-danger-400", children: error }) })), _jsx(Button, { type: "submit", color: "primary", size: "lg", className: "w-full font-semibold", endContent: isLoading ? null : _jsx(ArrowRight, { className: "h-5 w-5" }), isLoading: isLoading, children: t("signin.submit") || "Se connecter" })] }), _jsxs("div", { className: "relative flex items-center gap-4 py-2", children: [_jsx("div", { className: "h-px flex-1 bg-default-200" }), _jsx("span", { className: "text-xs text-default-400", children: t("or") || "OU" }), _jsx("div", { className: "h-px flex-1 bg-default-200" })] }), _jsx("div", { className: "text-center", children: _jsxs("p", { className: "text-sm text-default-600", children: [t("signin.no_account") || "Pas encore de compte ?", " ", _jsx(Link, { href: redirectUrl
96
+ } }), _jsx(Link, { href: "/reset-password", className: "text-xs text-default-500 hover:text-primary-500 self-end", children: t("signin.forgot_password") || "Mot de passe oublié ?" })] }), error && (_jsx("div", { className: "rounded-lg border border-danger-200 bg-danger-50/50 px-4 py-3 dark:border-danger-800 dark:bg-danger-950/50", children: _jsx("p", { className: "text-sm text-danger-600 dark:text-danger-400", children: error }) })), _jsx(Button, { type: "submit", color: "primary", size: "lg", className: "w-full font-semibold", endContent: isLoading ? null : _jsx(ArrowRight, { className: "h-5 w-5" }), isLoading: isLoading, children: t("signin.submit") || "Se connecter" })] }), _jsxs("div", { className: "relative flex items-center gap-4 py-2", children: [_jsx("div", { className: "h-px flex-1 bg-default-200" }), _jsx("span", { className: "text-xs text-default-400", children: t("or") || "OU" }), _jsx("div", { className: "h-px flex-1 bg-default-200" })] }), _jsx("div", { className: "text-center", children: _jsxs("p", { className: "text-sm text-default-600", children: [t("signin.no_account") || "Pas encore de compte ?", " ", _jsx(Link, { href: redirectUrl
61
97
  ? `/signup?redirect=${encodeURIComponent(redirectUrl)}`
62
98
  : "/signup", className: "font-semibold text-primary-600 hover:text-primary-700", children: t("signin.sign_up") || "Créer un compte" })] }) })] }) }), _jsxs("p", { className: "mt-8 text-center text-sm text-default-700", children: [t("signin.accept_terms"), " ", _jsx(Link, { href: "/legal/terms", className: "underline hover:text-primary-500", children: t("signin.term") })] })] }) })] }));
63
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SignUpPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignUpPage.tsx"],"names":[],"mappings":"AA2SA,wBAAgB,UAAU,4CAMzB"}
1
+ {"version":3,"file":"SignUpPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignUpPage.tsx"],"names":[],"mappings":"AAoTA,wBAAgB,UAAU,4CAMzB"}
@@ -55,6 +55,11 @@ function SignUpForm() {
55
55
  // Si la confirmation par email est requise
56
56
  if (result.data.user && !result.data.session) {
57
57
  setSuccess("Compte créé avec succès ! Veuillez vérifier votre email pour confirmer votre compte.");
58
+ // Reset le formulaire après succès
59
+ setFullName("");
60
+ setEmail("");
61
+ setPassword("");
62
+ setConfirmPassword("");
58
63
  return;
59
64
  }
60
65
  setSuccess("Compte créé. Vous pouvez désormais vous connecter.");
@@ -78,8 +83,8 @@ function SignUpForm() {
78
83
  setLoading(false);
79
84
  }
80
85
  };
81
- return (_jsxs("div", { className: " relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-secondary-50/30 to-primary-50/30 dark:from-secondary-950/50 dark:to-primary-950/50", children: [_jsx("div", { className: "absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" }), _jsx("div", { className: "relative flex min-h-screen items-center justify-center px-4 py-12", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "mb-8 text-center", children: [_jsx(Chip, { color: "secondary", variant: "flat", startContent: _jsx(Sparkles, { className: "w-4 h-4" }), className: "mb-4 p-2", children: t("signup.chip_label") || "Rejoignez-nous" }), _jsx("h1", { className: "mb-3 text-4xl font-bold", children: _jsx("span", { className: "bg-gradient-to-r from-secondary-600 to-primary-600 bg-clip-text text-transparent", children: t("signup.title") || "Commencer gratuitement" }) }), _jsx("p", { className: "text-default-600 dark:text-default-700", children: t("signup.subtitle") ||
82
- "Créez votre compte en quelques secondes" })] }), _jsx(Card, { className: "border border-default-200/60 bg-background/60 backdrop-blur-md backdrop-saturate-150 shadow-xl", children: _jsxs(CardBody, { className: "gap-6 p-8", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [_jsx(Input, { label: t("signup.full_name") || "Nom complet", type: "text", placeholder: t("signup.full_name_placeholder") || "Jean Dupont", value: fullName, onChange: (e) => setFullName(e.target.value), startContent: _jsx(User, { className: "h-4 w-4 text-default-400" }), classNames: {
86
+ return (_jsxs("div", { className: "pt-16 relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-secondary-50/30 to-primary-50/30 dark:from-secondary-950/50 dark:to-primary-950/50", children: [_jsx("div", { className: "absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" }), _jsx("div", { className: "relative flex min-h-screen items-center justify-center px-4 py-12", children: _jsxs("div", { className: "w-full max-w-md", children: [_jsxs("div", { className: "mb-8 text-center", children: [_jsx(Chip, { color: "secondary", variant: "flat", startContent: _jsx(Sparkles, { className: "w-4 h-4" }), className: "mb-4 p-2", children: t("signup.chip_label") || "Rejoignez-nous" }), _jsx("h1", { className: "mb-3 text-4xl font-bold", children: _jsx("span", { className: "bg-gradient-to-r from-secondary-600 to-primary-600 bg-clip-text text-transparent", children: t("signup.title") || "Commencer gratuitement" }) }), _jsx("p", { className: "text-default-600 dark:text-default-700", children: t("signup.subtitle") ||
87
+ "Créez votre compte en quelques secondes" })] }), _jsx(Card, { className: "border border-default-200/60 backdrop-blur-md backdrop-saturate-150 shadow-xl", children: _jsxs(CardBody, { className: "gap-6 p-8", children: [_jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-6", children: [_jsx(Input, { label: t("signup.full_name") || "Nom complet", type: "text", placeholder: t("signup.full_name_placeholder") || "Jean Dupont", value: fullName, onChange: (e) => setFullName(e.target.value), startContent: _jsx(User, { className: "h-4 w-4 text-default-400" }), classNames: {
83
88
  input: "text-base",
84
89
  inputWrapper: "border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
85
90
  } }), _jsx(Input, { label: t("email") || "Email", type: "email", placeholder: t("signin.email_placeholder") || "votre@email.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, startContent: _jsx(Mail, { className: "h-4 w-4 text-default-400" }), classNames: {
@@ -0,0 +1,2 @@
1
+ export default function AuthCodeErrorPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=auth-code-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-code-error.d.ts","sourceRoot":"","sources":["../../../src/web/public/auth-code-error.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,iBAAiB,4CAwCxC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Auth error page - shown when email confirmation fails
3
+ */
4
+ "use client";
5
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
+ import { useSearchParams } from "next/navigation";
7
+ import Link from "next/link";
8
+ export default function AuthCodeErrorPage() {
9
+ const searchParams = useSearchParams();
10
+ const error = searchParams.get("error");
11
+ const description = searchParams.get("description");
12
+ return (_jsx("div", { className: "flex items-center justify-center min-h-screen bg-gray-50", children: _jsx("div", { className: "w-full max-w-md p-8 bg-white rounded-lg shadow-md", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "text-red-500 text-4xl mb-4", children: "\u274C" }), _jsx("h1", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Confirmation Failed" }), _jsx("p", { className: "text-gray-600 mb-4", children: description ||
13
+ "We couldn't confirm your email. The link may have expired." }), error && (_jsxs("p", { className: "text-sm text-gray-500 bg-gray-100 p-2 rounded mb-6", children: ["Error: ", error] })), _jsxs("div", { className: "space-y-3", children: [_jsx(Link, { href: "/auth/signin", className: "block w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition", children: "Try signing in" }), _jsx(Link, { href: "/auth/signup", className: "block w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition", children: "Create new account" })] })] }) }) }));
14
+ }
@@ -0,0 +1,2 @@
1
+ export default function ConfirmPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../../src/web/public/confirm.tsx"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,UAAU,WAAW,4CAoLlC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Email confirmation handler
3
+ * Handles hash-based tokens (direct from email with #access_token=XXX)
4
+ *
5
+ * Note: Code-based flow is now handled server-side in /api/auth/callback
6
+ * This page is only reached for hash-based tokens or as a fallback loading screen
7
+ */
8
+ "use client";
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ import { useEffect, useState } from "react";
11
+ import { useSearchParams } from "next/navigation";
12
+ import { addToast, Card, Spinner } from "@lastbrain/ui";
13
+ import { logger, useLocalizedRouter } from "@lastbrain/core";
14
+ export default function ConfirmPage() {
15
+ const router = useLocalizedRouter();
16
+ const searchParams = useSearchParams();
17
+ const [message, setMessage] = useState("Confirming your email...");
18
+ const [isProcessing, setIsProcessing] = useState(false);
19
+ const [debug, setDebug] = useState(null);
20
+ useEffect(() => {
21
+ // Éviter les appels multiples
22
+ if (isProcessing)
23
+ return;
24
+ const confirmEmail = async () => {
25
+ setIsProcessing(true);
26
+ const next = searchParams.get("next") || "/";
27
+ // Check for token in hash (direct from email links that use #access_token)
28
+ // This handles the legacy hash-based flow from Supabase
29
+ if (typeof window !== "undefined") {
30
+ const hash = window.location.hash;
31
+ if (hash && hash.length > 1) {
32
+ const params = new URLSearchParams(hash.substring(1));
33
+ const accessToken = params.get("access_token");
34
+ const refreshToken = params.get("refresh_token");
35
+ if (accessToken && refreshToken) {
36
+ try {
37
+ setMessage("Setting up your session...");
38
+ // Debug: capture full href & hash and tokens for troubleshooting
39
+ setDebug({
40
+ href: window.location.href,
41
+ hash,
42
+ access_token: accessToken,
43
+ refresh_token: refreshToken,
44
+ });
45
+ // Sync tokens to server-side cookies
46
+ console.debug("[ConfirmPage] Posting tokens to /api/public/set-session", {
47
+ accessToken: String(accessToken).slice(0, 8) + "...",
48
+ refreshToken: String(refreshToken).slice(0, 8) + "...",
49
+ });
50
+ const response = await fetch("/api/public/set-session", {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify({
54
+ access_token: accessToken,
55
+ refresh_token: refreshToken,
56
+ }),
57
+ });
58
+ const respBody = await response.json().catch(() => null);
59
+ console.debug("[ConfirmPage] /api/public/set-session response", {
60
+ ok: response.ok,
61
+ status: response.status,
62
+ body: respBody,
63
+ });
64
+ // Update debug panel with response
65
+ setDebug((d) => ({
66
+ ...(d || {}),
67
+ responseStatus: response.status,
68
+ responseBody: respBody,
69
+ }));
70
+ logger.debug("[ConfirmPage] start", {
71
+ url: typeof window !== "undefined" ? window.location.href : null,
72
+ searchParams: String(searchParams),
73
+ next,
74
+ });
75
+ if (!response.ok) {
76
+ const errorData = await response.json();
77
+ throw new Error(errorData.error || "Failed to set session");
78
+ }
79
+ // Add welcome bonus asynchronously (don't block redirect)
80
+ try {
81
+ const res = await fetch("/api/public/add-welcome-bonus", {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ });
85
+ if (!res.ok) {
86
+ const err = await res.json().catch(() => ({}));
87
+ logger.warn("add-welcome-bonus failed:", err);
88
+ }
89
+ else {
90
+ logger.debug("add-welcome-bonus succeeded for user");
91
+ }
92
+ }
93
+ catch (bonusError) {
94
+ logger.warn("Failed to add welcome bonus:", bonusError);
95
+ // Don't block the redirect if bonus fails
96
+ }
97
+ addToast({
98
+ title: "Email confirmed!",
99
+ description: "Your email has been verified successfully.",
100
+ color: "success",
101
+ });
102
+ // Redirect to destination
103
+ // Use hard reload to ensure cookies are applied
104
+ setMessage("Redirecting...");
105
+ setTimeout(() => {
106
+ window.location.href = next;
107
+ }, 500);
108
+ return;
109
+ }
110
+ catch (err) {
111
+ logger.error("❌ Error setting session:", err);
112
+ addToast({
113
+ title: "Email confirmation failed",
114
+ description: err instanceof Error
115
+ ? err.message
116
+ : "Could not create session",
117
+ color: "danger",
118
+ });
119
+ router.push("/auth-code-error");
120
+ return;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ // If we reach here, either:
126
+ // 1. Code was already handled by callback (just show loading and redirect)
127
+ // 2. No valid token/code found (redirect to error)
128
+ const code = searchParams.get("code");
129
+ if (code) {
130
+ // Code should have been handled by callback, but we're here
131
+ // This means callback already set cookies and redirected us here as fallback
132
+ // Just redirect to next
133
+ setMessage("Completing sign-in...");
134
+ setTimeout(() => {
135
+ window.location.href = next;
136
+ }, 500);
137
+ return;
138
+ }
139
+ // No code or token found - assume the user was redirected here after
140
+ // a verification step (Supabase verify) which does not provide a code.
141
+ // Consider this a successful confirmation and redirect to `next`.
142
+ logger.debug("No confirmation code or token found - assuming success and redirecting");
143
+ addToast({
144
+ title: "Email confirmé",
145
+ description: "Votre email a été vérifié.",
146
+ color: "success",
147
+ });
148
+ setMessage("Redirecting...");
149
+ setTimeout(() => {
150
+ window.location.href = next;
151
+ }, 500);
152
+ };
153
+ confirmEmail();
154
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
155
+ // On ne met pas searchParams/router dans les deps pour éviter les boucles
156
+ return (_jsx("div", { className: "flex items-center justify-center min-h-screen ", children: _jsx(Card, { className: "w-full max-w-md", children: _jsxs("div", { className: "text-center py-8", children: [_jsx(Spinner, { size: "lg", className: "mx-auto" }), _jsx("p", { className: "mt-4 text-gray-600", children: message })] }) }) }));
157
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/module-auth",
3
- "version": "2.0.27",
3
+ "version": "2.0.31",
4
4
  "description": "Module d'authentification complet pour LastBrain avec Supabase",
5
5
  "private": false,
6
6
  "type": "module",
@@ -32,9 +32,10 @@
32
32
  "supabase"
33
33
  ],
34
34
  "dependencies": {
35
- "@lastbrain/app": "^2.0.31",
36
- "@lastbrain/core": "^2.0.27",
37
- "@lastbrain/ui": "^2.0.27",
35
+ "@lastbrain/app": "^2.0.35",
36
+ "@lastbrain/core": "^2.0.31",
37
+ "@lastbrain-labs/module-contact-pro": "^0.1.3",
38
+ "@lastbrain/ui": "^2.0.31",
38
39
  "@supabase/supabase-js": "^2.86.0",
39
40
  "lucide-react": "^0.554.0",
40
41
  "react": "^19.2.1",
@@ -44,7 +45,7 @@
44
45
  "next": ">=15.0.0"
45
46
  },
46
47
  "devDependencies": {
47
- "next": "^16.0.7",
48
+ "next": "16.0.10",
48
49
  "typescript": "^5.4.0"
49
50
  },
50
51
  "exports": {
@@ -63,6 +64,10 @@
63
64
  "./api/*": {
64
65
  "types": "./dist/api/*.d.ts",
65
66
  "default": "./dist/api/*.js"
67
+ },
68
+ "./sitemap/*": {
69
+ "types": "./dist/sitemap/*.d.ts",
70
+ "default": "./dist/sitemap/*.js"
66
71
  }
67
72
  },
68
73
  "sideEffects": false,
@@ -1,3 +1,4 @@
1
+ import { logger } from "@lastbrain/core";
1
2
  import { getSupabaseServiceClient } from "@lastbrain/core/server";
2
3
  import { NextRequest, NextResponse } from "next/server";
3
4
 
@@ -48,7 +49,7 @@ export async function GET(_request: NextRequest) {
48
49
 
49
50
  return NextResponse.json({ data: stats }, { status: 200 });
50
51
  } catch (error) {
51
- console.error("Error fetching signup stats:", error);
52
+ logger.error("Error fetching signup stats:", error);
52
53
  return NextResponse.json(
53
54
  { error: "Erreur interne du serveur" },
54
55
  { status: 500 }