@lastbrain/module-auth 2.0.19 → 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.
- package/README.md +55 -7
- package/dist/api/admin/signup-stats.d.ts.map +1 -1
- package/dist/api/admin/signup-stats.js +2 -1
- package/dist/api/admin/storage/usage.d.ts +18 -0
- package/dist/api/admin/storage/usage.d.ts.map +1 -0
- package/dist/api/admin/storage/usage.js +100 -0
- package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
- package/dist/api/admin/users/[id]/notifications.js +3 -2
- package/dist/api/admin/users/[id].d.ts.map +1 -1
- package/dist/api/admin/users/[id].js +3 -2
- package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
- package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
- package/dist/api/admin/users/reactivate/[id].js +59 -0
- package/dist/api/admin/users/suspend/[id].d.ts +16 -0
- package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
- package/dist/api/admin/users/suspend/[id].js +59 -0
- package/dist/api/admin/users-by-source.d.ts.map +1 -1
- package/dist/api/admin/users-by-source.js +2 -1
- package/dist/api/admin/users.d.ts.map +1 -1
- package/dist/api/admin/users.js +53 -2
- package/dist/api/auth/account/email-change.d.ts +7 -0
- package/dist/api/auth/account/email-change.d.ts.map +1 -0
- package/dist/api/auth/account/email-change.js +39 -0
- package/dist/api/auth/account/reset-password.d.ts +7 -0
- package/dist/api/auth/account/reset-password.d.ts.map +1 -0
- package/dist/api/auth/account/reset-password.js +36 -0
- package/dist/api/auth/check-username.d.ts +9 -0
- package/dist/api/auth/check-username.d.ts.map +1 -0
- package/dist/api/auth/check-username.js +35 -0
- package/dist/api/auth/establish-session.d.ts +2 -0
- package/dist/api/auth/establish-session.d.ts.map +1 -0
- package/dist/api/auth/establish-session.js +23 -0
- package/dist/api/auth/me.d.ts +4 -4
- package/dist/api/auth/me.d.ts.map +1 -1
- package/dist/api/auth/me.js +28 -6
- package/dist/api/auth/profile.d.ts.map +1 -1
- package/dist/api/auth/profile.js +6 -3
- package/dist/api/auth/storage/recalculate.d.ts +15 -0
- package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
- package/dist/api/auth/storage/recalculate.js +68 -0
- package/dist/api/auth/storage/usage.d.ts +10 -0
- package/dist/api/auth/storage/usage.d.ts.map +1 -0
- package/dist/api/auth/storage/usage.js +86 -0
- package/dist/api/public/add-welcome-bonus.d.ts +16 -0
- package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
- package/dist/api/public/add-welcome-bonus.js +177 -0
- package/dist/api/public/callback.d.ts +3 -0
- package/dist/api/public/callback.d.ts.map +1 -0
- package/dist/api/public/callback.js +197 -0
- package/dist/api/public/reset-password.d.ts +3 -0
- package/dist/api/public/reset-password.d.ts.map +1 -0
- package/dist/api/public/reset-password.js +43 -0
- package/dist/api/public/set-session.d.ts +7 -0
- package/dist/api/public/set-session.d.ts.map +1 -0
- package/dist/api/public/set-session.js +55 -0
- package/dist/api/public/signin.d.ts.map +1 -1
- package/dist/api/public/signin.js +31 -0
- package/dist/api/public/signup.d.ts.map +1 -1
- package/dist/api/public/signup.js +38 -27
- package/dist/api/public/webhook/storage-addon.d.ts +9 -0
- package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
- package/dist/api/public/webhook/storage-addon.js +155 -0
- package/dist/api/storage.js +2 -2
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +134 -14
- package/dist/components/AccountButton.d.ts.map +1 -1
- package/dist/components/AccountButton.js +54 -28
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/HasProfil.d.ts +4 -0
- package/dist/components/HasProfil.d.ts.map +1 -0
- package/dist/components/HasProfil.js +39 -0
- package/dist/{web → components}/auth/dashboard.d.ts +1 -1
- package/dist/components/auth/dashboard.d.ts.map +1 -0
- package/dist/components/auth/dashboard.js +74 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib/app-branding-data.d.ts +22 -0
- package/dist/lib/app-branding-data.d.ts.map +1 -0
- package/dist/lib/app-branding-data.js +49 -0
- package/dist/lib/auth-email-service.d.ts +57 -0
- package/dist/lib/auth-email-service.d.ts.map +1 -0
- package/dist/lib/auth-email-service.js +382 -0
- package/dist/lib/auth-email-templates.d.ts +2 -0
- package/dist/lib/auth-email-templates.d.ts.map +1 -0
- package/dist/lib/auth-email-templates.js +1 -0
- package/dist/lib/site-url.d.ts +3 -0
- package/dist/lib/site-url.d.ts.map +1 -0
- package/dist/lib/site-url.js +11 -0
- package/dist/sitemap/manifest.d.ts +9 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +14 -0
- package/dist/web/admin/signup-stats.js +3 -3
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +135 -14
- package/dist/web/admin/users-by-signup-source.js +2 -2
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +26 -7
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +4 -3
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +132 -13
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +15 -8
- package/dist/web/public/ResetPassword.d.ts.map +1 -1
- package/dist/web/public/ResetPassword.js +172 -2
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +39 -3
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +7 -2
- package/dist/web/public/auth-code-error.d.ts +2 -0
- package/dist/web/public/auth-code-error.d.ts.map +1 -0
- package/dist/web/public/auth-code-error.js +14 -0
- package/dist/web/public/confirm.d.ts +2 -0
- package/dist/web/public/confirm.d.ts.map +1 -0
- package/dist/web/public/confirm.js +157 -0
- package/package.json +10 -5
- package/src/api/admin/signup-stats.ts +2 -1
- package/src/api/admin/storage/usage.ts +141 -0
- package/src/api/admin/users/[id]/notifications.ts +3 -2
- package/src/api/admin/users/[id].ts +3 -2
- package/src/api/admin/users/reactivate/[id].ts +88 -0
- package/src/api/admin/users/suspend/[id].ts +85 -0
- package/src/api/admin/users-by-source.ts +2 -1
- package/src/api/admin/users.ts +59 -2
- package/src/api/auth/account/email-change.ts +54 -0
- package/src/api/auth/account/reset-password.ts +47 -0
- package/src/api/auth/check-username.ts +52 -0
- package/src/api/auth/establish-session.ts +32 -0
- package/src/api/auth/me.ts +29 -7
- package/src/api/auth/profile.ts +6 -2
- package/src/api/auth/storage/recalculate.ts +108 -0
- package/src/api/auth/storage/usage.ts +113 -0
- package/src/api/public/add-welcome-bonus.ts +229 -0
- package/src/api/public/callback.ts +307 -0
- package/src/api/public/reset-password.ts +52 -0
- package/src/api/public/set-session.ts +73 -0
- package/src/api/public/signin.ts +36 -0
- package/src/api/public/signup.ts +44 -37
- package/src/api/public/webhook/storage-addon.ts +267 -0
- package/src/api/storage.ts +2 -2
- package/src/auth.build.config.ts +134 -14
- package/src/components/AccountButton.tsx +114 -90
- package/src/components/Doc.tsx +47 -9
- package/src/components/HasProfil.tsx +63 -0
- package/src/{web → components}/auth/dashboard.tsx +64 -20
- package/src/i18n/en.json +78 -8
- package/src/i18n/es.json +330 -0
- package/src/i18n/fr.json +75 -8
- package/src/index.ts +3 -1
- package/src/lib/app-branding-data.ts +90 -0
- package/src/lib/auth-email-service.ts +508 -0
- package/src/lib/auth-email-templates.ts +5 -0
- package/src/lib/site-url.ts +17 -0
- package/src/sitemap/manifest.ts +26 -0
- package/src/web/admin/signup-stats.tsx +3 -3
- package/src/web/admin/user-detail.tsx +314 -15
- package/src/web/admin/users-by-signup-source.tsx +2 -2
- package/src/web/admin/users.tsx +50 -14
- package/src/web/auth/folder.tsx +23 -5
- package/src/web/auth/profile.tsx +227 -13
- package/src/web/auth/reglage.tsx +55 -24
- package/src/web/public/ResetPassword.tsx +301 -1
- package/src/web/public/SignInPage.tsx +43 -3
- package/src/web/public/SignUpPage.tsx +14 -5
- package/src/web/public/auth-code-error.tsx +49 -0
- package/src/web/public/confirm.tsx +195 -0
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
- package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
- package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
- package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
- package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
- package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
- package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
- package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
- package/dist/web/auth/dashboard.d.ts.map +0 -1
- package/dist/web/auth/dashboard.js +0 -48
package/dist/web/auth/reglage.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
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: "
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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
|
|
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: "/
|
|
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":"
|
|
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: "
|
|
82
|
-
"Créez votre compte en quelques secondes" })] }), _jsx(Card, { className: "border border-default-200/60
|
|
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 @@
|
|
|
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 @@
|
|
|
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.
|
|
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.
|
|
36
|
-
"@lastbrain/core": "^2.0.
|
|
37
|
-
"@lastbrain/
|
|
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": "
|
|
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
|
-
|
|
52
|
+
logger.error("Error fetching signup stats:", error);
|
|
52
53
|
return NextResponse.json(
|
|
53
54
|
{ error: "Erreur interne du serveur" },
|
|
54
55
|
{ status: 500 }
|