@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { AppLink, Button } from "@lastbrain/ui";
|
|
3
|
+
import { AppLink, Button, ThemeSwitcher } from "@lastbrain/ui";
|
|
4
4
|
import { Avatar } from "@lastbrain/ui";
|
|
5
5
|
import {
|
|
6
6
|
Dropdown,
|
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
} from "@lastbrain/ui";
|
|
11
11
|
import * as LucideIcons from "lucide-react";
|
|
12
12
|
import type { User } from "@supabase/supabase-js";
|
|
13
|
-
import { useModuleTranslation
|
|
13
|
+
import { useModuleTranslation } from "@lastbrain/app";
|
|
14
14
|
import { useLanguage } from "@lastbrain/app";
|
|
15
|
+
import { useLocalizedRouter, supabaseBrowserClient } from "@lastbrain/core";
|
|
15
16
|
|
|
16
17
|
interface AccountButtonProps {
|
|
17
18
|
item: {
|
|
@@ -42,49 +43,46 @@ export const AccountButton = ({
|
|
|
42
43
|
}: AccountButtonProps) => {
|
|
43
44
|
const { lang } = useLanguage();
|
|
44
45
|
const t = useModuleTranslation("auth");
|
|
46
|
+
const router = useLocalizedRouter();
|
|
45
47
|
|
|
46
48
|
if (!user)
|
|
47
49
|
return (
|
|
48
50
|
<>
|
|
49
|
-
<div className="block
|
|
51
|
+
<div className="block sm:hidden flex flex-inline gap-2 item-center">
|
|
50
52
|
<Button
|
|
51
53
|
as={AppLink}
|
|
52
54
|
href="/signin"
|
|
53
55
|
radius="full"
|
|
54
56
|
isIconOnly
|
|
55
|
-
variant="light"
|
|
56
|
-
color="primary"
|
|
57
|
-
>
|
|
58
|
-
<LucideIcons.LogIn size={16} />
|
|
59
|
-
</Button>
|
|
60
|
-
<Button
|
|
61
|
-
as={AppLink}
|
|
62
|
-
href="/signup"
|
|
63
|
-
radius="full"
|
|
64
|
-
isIconOnly
|
|
65
57
|
variant="flat"
|
|
66
|
-
|
|
67
|
-
color="secondary"
|
|
58
|
+
color="primary"
|
|
68
59
|
>
|
|
69
60
|
<LucideIcons.UserPlus2 size={16} />
|
|
70
61
|
</Button>
|
|
62
|
+
<div className="ms-2">
|
|
63
|
+
<ThemeSwitcher />
|
|
64
|
+
</div>
|
|
71
65
|
</div>
|
|
72
|
-
<div className="hidden
|
|
66
|
+
<div className="hidden sm:block flex flex-inline gap-2 item-center">
|
|
73
67
|
<Button
|
|
74
68
|
as={AppLink}
|
|
75
69
|
href="/signin"
|
|
76
70
|
startContent={<LucideIcons.LogIn size={16} />}
|
|
77
71
|
variant="light"
|
|
78
72
|
color="primary"
|
|
73
|
+
radius="full"
|
|
74
|
+
size="sm"
|
|
79
75
|
>
|
|
80
76
|
{t("signin")}
|
|
81
77
|
</Button>
|
|
82
78
|
<Button
|
|
83
79
|
as={AppLink}
|
|
84
80
|
href="/signup"
|
|
85
|
-
variant="
|
|
81
|
+
variant="bordered"
|
|
86
82
|
className="ml-2"
|
|
87
83
|
color="secondary"
|
|
84
|
+
radius="full"
|
|
85
|
+
size="sm"
|
|
88
86
|
startContent={<LucideIcons.UserPlus2 size={16} />}
|
|
89
87
|
>
|
|
90
88
|
{t("signup")}
|
|
@@ -94,80 +92,106 @@ export const AccountButton = ({
|
|
|
94
92
|
);
|
|
95
93
|
|
|
96
94
|
return (
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
<div className="flex flex-inline gap-2">
|
|
96
|
+
<Dropdown>
|
|
97
|
+
<DropdownTrigger>
|
|
98
|
+
{user?.user_metadata.avatar ? (
|
|
99
|
+
<Avatar
|
|
100
|
+
size="sm"
|
|
101
|
+
src={
|
|
102
|
+
user?.user_metadata.avatar
|
|
103
|
+
? `/api/storage/${user?.user_metadata.avatar}`
|
|
104
|
+
: undefined
|
|
105
|
+
}
|
|
106
|
+
title={user.email}
|
|
107
|
+
fallback={<LucideIcons.User2 size={18} />}
|
|
108
|
+
classNames={{
|
|
109
|
+
base: "cursor-pointer bg-white/0",
|
|
110
|
+
icon: "text-default-700",
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
) : (
|
|
114
|
+
<Button size="sm" variant="flat" radius="full" isIconOnly>
|
|
115
|
+
<LucideIcons.User2 size={16} />
|
|
116
|
+
</Button>
|
|
117
|
+
)}
|
|
118
|
+
</DropdownTrigger>
|
|
119
|
+
|
|
120
|
+
<DropdownMenu
|
|
121
|
+
className="relative"
|
|
122
|
+
items={[
|
|
123
|
+
{
|
|
124
|
+
key: "hello",
|
|
125
|
+
label: `${t("hello")} ${
|
|
126
|
+
user?.user_metadata?.full_name || user.email
|
|
127
|
+
}`,
|
|
128
|
+
isReadOnly: true,
|
|
129
|
+
},
|
|
130
|
+
...accountMenu.map((item) => ({
|
|
131
|
+
key: item.path,
|
|
132
|
+
label: item.title,
|
|
133
|
+
description: item.description,
|
|
134
|
+
icon: item.icon,
|
|
135
|
+
isLogout:
|
|
136
|
+
item.path.includes("signout") || item.path.includes("logout"),
|
|
137
|
+
href:
|
|
138
|
+
item.path.includes("signout") || item.path.includes("logout")
|
|
139
|
+
? undefined
|
|
140
|
+
: item.path,
|
|
141
|
+
})),
|
|
142
|
+
]}
|
|
143
|
+
>
|
|
144
|
+
{(item: {
|
|
145
|
+
key: string;
|
|
146
|
+
label: string;
|
|
147
|
+
description?: string;
|
|
148
|
+
icon?: string;
|
|
149
|
+
isLogout?: boolean;
|
|
150
|
+
href?: string;
|
|
151
|
+
isReadOnly?: boolean;
|
|
152
|
+
}) => {
|
|
153
|
+
const Icon = item.icon ? getIcon(item.icon) : null;
|
|
120
154
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
})),
|
|
142
|
-
]}
|
|
143
|
-
>
|
|
144
|
-
{(item: {
|
|
145
|
-
key: string;
|
|
146
|
-
label: string;
|
|
147
|
-
description?: string;
|
|
148
|
-
icon?: string;
|
|
149
|
-
isLogout?: boolean;
|
|
150
|
-
href?: string;
|
|
151
|
-
isReadOnly?: boolean;
|
|
152
|
-
}) => {
|
|
153
|
-
const Icon = item.icon ? getIcon(item.icon) : null;
|
|
155
|
+
const handleLogout = async () => {
|
|
156
|
+
try {
|
|
157
|
+
if (onLogout) {
|
|
158
|
+
await onLogout();
|
|
159
|
+
} else {
|
|
160
|
+
// default fallback: sign out via supabase client
|
|
161
|
+
try {
|
|
162
|
+
await supabaseBrowserClient.auth.signOut();
|
|
163
|
+
} catch (_e) {
|
|
164
|
+
// ignore
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} finally {
|
|
168
|
+
try {
|
|
169
|
+
router.push("/");
|
|
170
|
+
} catch (_e) {
|
|
171
|
+
// ignore routing errors
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
154
175
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
return (
|
|
177
|
+
<DropdownItem
|
|
178
|
+
as={AppLink}
|
|
179
|
+
key={item.key}
|
|
180
|
+
href={item.href}
|
|
181
|
+
onPress={item.isLogout ? handleLogout : undefined}
|
|
182
|
+
color={item.isLogout ? "danger" : "default"}
|
|
183
|
+
description={!item.isLogout && item.description}
|
|
184
|
+
startContent={Icon && <Icon size={16} />}
|
|
185
|
+
isDisabled={item.isReadOnly}
|
|
186
|
+
isReadOnly={item.isReadOnly}
|
|
187
|
+
>
|
|
188
|
+
{item.label}
|
|
189
|
+
</DropdownItem>
|
|
190
|
+
);
|
|
191
|
+
}}
|
|
192
|
+
</DropdownMenu>
|
|
193
|
+
</Dropdown>
|
|
194
|
+
<ThemeSwitcher />
|
|
195
|
+
</div>
|
|
172
196
|
);
|
|
173
197
|
};
|
package/src/components/Doc.tsx
CHANGED
|
@@ -93,6 +93,24 @@ export function Doc() {
|
|
|
93
93
|
- ResetPassword
|
|
94
94
|
</span>
|
|
95
95
|
</div>
|
|
96
|
+
<div className="flex items-start gap-2">
|
|
97
|
+
<Chip size="sm" color="success" variant="flat">
|
|
98
|
+
GET
|
|
99
|
+
</Chip>
|
|
100
|
+
<code className="text-sm">/confirm</code>
|
|
101
|
+
<span className="text-sm text-slate-600 dark:text-slate-400">
|
|
102
|
+
- ConfirmPage
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
<div className="flex items-start gap-2">
|
|
106
|
+
<Chip size="sm" color="success" variant="flat">
|
|
107
|
+
GET
|
|
108
|
+
</Chip>
|
|
109
|
+
<code className="text-sm">/auth-code-error</code>
|
|
110
|
+
<span className="text-sm text-slate-600 dark:text-slate-400">
|
|
111
|
+
- AuthCodeErrorPage
|
|
112
|
+
</span>
|
|
113
|
+
</div>
|
|
96
114
|
</div>
|
|
97
115
|
</div>
|
|
98
116
|
<div>
|
|
@@ -100,15 +118,6 @@ export function Doc() {
|
|
|
100
118
|
Pages Protégées (Auth)
|
|
101
119
|
</h3>
|
|
102
120
|
<div className="space-y-2">
|
|
103
|
-
<div className="flex items-start gap-2">
|
|
104
|
-
<Chip size="sm" color="primary" variant="flat">
|
|
105
|
-
GET
|
|
106
|
-
</Chip>
|
|
107
|
-
<code className="text-sm">/dashboard</code>
|
|
108
|
-
<span className="text-sm text-slate-600 dark:text-slate-400">
|
|
109
|
-
- DashboardPage
|
|
110
|
-
</span>
|
|
111
|
-
</div>
|
|
112
121
|
<div className="flex items-start gap-2">
|
|
113
122
|
<Chip size="sm" color="primary" variant="flat">
|
|
114
123
|
GET
|
|
@@ -201,6 +210,25 @@ export function Doc() {
|
|
|
201
210
|
</Chip>
|
|
202
211
|
</div>
|
|
203
212
|
</div>
|
|
213
|
+
<div>
|
|
214
|
+
<h3 className="text-lg font-semibold mb-2">
|
|
215
|
+
<code>/api/public/callback</code>
|
|
216
|
+
</h3>
|
|
217
|
+
<div className="flex gap-2">
|
|
218
|
+
<Chip size="sm" color="success" variant="flat">
|
|
219
|
+
GET
|
|
220
|
+
</Chip>
|
|
221
|
+
<Chip size="sm" color="primary" variant="flat">
|
|
222
|
+
POST
|
|
223
|
+
</Chip>
|
|
224
|
+
<Chip size="sm" color="warning" variant="flat">
|
|
225
|
+
PUT
|
|
226
|
+
</Chip>
|
|
227
|
+
<Chip size="sm" color="danger" variant="flat">
|
|
228
|
+
DELETE
|
|
229
|
+
</Chip>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
204
232
|
<div>
|
|
205
233
|
<h3 className="text-lg font-semibold mb-2">
|
|
206
234
|
<code>/api/auth/profile</code>
|
|
@@ -284,6 +312,16 @@ export function Doc() {
|
|
|
284
312
|
title="user_notifications"
|
|
285
313
|
description="Table user_notifications du module auth"
|
|
286
314
|
/>
|
|
315
|
+
<TableStructure
|
|
316
|
+
tableName="global_addons"
|
|
317
|
+
title="global_addons"
|
|
318
|
+
description="Table global_addons du module auth"
|
|
319
|
+
/>
|
|
320
|
+
<TableStructure
|
|
321
|
+
tableName="user_global_addons"
|
|
322
|
+
title="user_global_addons"
|
|
323
|
+
description="Table user_global_addons du module auth"
|
|
324
|
+
/>
|
|
287
325
|
</CardBody>
|
|
288
326
|
</Card>
|
|
289
327
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { logger, useModuleTranslation } from "@lastbrain/core";
|
|
2
|
+
import { Alert, AppLink, Skeleton } from "@lastbrain/ui";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export const HasProfil = ({
|
|
6
|
+
hasUpdate,
|
|
7
|
+
}: {
|
|
8
|
+
hasUpdate?: (value: boolean | null) => void;
|
|
9
|
+
}) => {
|
|
10
|
+
const [profil, setProfil] = useState<any>(null);
|
|
11
|
+
const t = useModuleTranslation("auth");
|
|
12
|
+
const [loading, setLoading] = useState(true);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const fetchStats = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const responseProfil = await fetch("/api/auth/profile");
|
|
18
|
+
if (!responseProfil.ok) {
|
|
19
|
+
throw new Error("Failed to load stats");
|
|
20
|
+
}
|
|
21
|
+
const jsonProfil = await responseProfil.json();
|
|
22
|
+
|
|
23
|
+
setProfil(jsonProfil.data);
|
|
24
|
+
if (jsonProfil.data && jsonProfil.data.first_name) {
|
|
25
|
+
hasUpdate?.(true);
|
|
26
|
+
} else {
|
|
27
|
+
hasUpdate?.(false);
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error("[HasProfil] stats error", error);
|
|
31
|
+
} finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
fetchStats();
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
if (loading) {
|
|
40
|
+
return (
|
|
41
|
+
<div className="mb-6 mt-6">
|
|
42
|
+
<Skeleton className="w-full h-12 w-full rounded-md" />
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return (
|
|
47
|
+
<div className="mb-6 mt-6">
|
|
48
|
+
{!profil ||
|
|
49
|
+
(!profil.first_name ? (
|
|
50
|
+
<Alert
|
|
51
|
+
color="danger"
|
|
52
|
+
endContent={
|
|
53
|
+
<AppLink className="underline" href="/auth/profile">
|
|
54
|
+
Go
|
|
55
|
+
</AppLink>
|
|
56
|
+
}
|
|
57
|
+
>
|
|
58
|
+
{t("please_complete_your_profile")}
|
|
59
|
+
</Alert>
|
|
60
|
+
) : null)}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -9,10 +9,13 @@ import {
|
|
|
9
9
|
Chip,
|
|
10
10
|
Divider,
|
|
11
11
|
Avatar,
|
|
12
|
+
Skeleton,
|
|
12
13
|
} from "@lastbrain/ui";
|
|
13
14
|
|
|
14
|
-
import { User, Mail, Calendar, Shield } from "lucide-react";
|
|
15
|
+
import { User, Mail, Calendar, Shield, LayoutDashboard } from "lucide-react";
|
|
15
16
|
import { useModuleTranslation } from "@lastbrain/core";
|
|
17
|
+
import { useRouter } from "next/navigation";
|
|
18
|
+
import { HasProfil } from "../HasProfil";
|
|
16
19
|
|
|
17
20
|
interface UserData {
|
|
18
21
|
id: string;
|
|
@@ -33,6 +36,8 @@ export function DashboardPage() {
|
|
|
33
36
|
const [userData, setUserData] = useState<UserData | null>(null);
|
|
34
37
|
const [isLoading, setIsLoading] = useState(true);
|
|
35
38
|
const [error, setError] = useState<string | null>(null);
|
|
39
|
+
const [hasUpdate, setHasUpdate] = useState<boolean | null>(null);
|
|
40
|
+
const router = useRouter();
|
|
36
41
|
|
|
37
42
|
useEffect(() => {
|
|
38
43
|
fetchUserData();
|
|
@@ -41,13 +46,36 @@ export function DashboardPage() {
|
|
|
41
46
|
const fetchUserData = async () => {
|
|
42
47
|
try {
|
|
43
48
|
setIsLoading(true);
|
|
44
|
-
const response = await fetch("/api/auth/me");
|
|
49
|
+
const response = await fetch("/api/auth/me", { credentials: "include" });
|
|
45
50
|
|
|
46
51
|
if (!response.ok) {
|
|
47
52
|
throw new Error("Failed to fetch user data");
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
const result = await response.json();
|
|
56
|
+
|
|
57
|
+
// Si le serveur a synchronisé la locale côté cookie, rediriger
|
|
58
|
+
// le client vers le même chemin préfixé par la langue du profil.
|
|
59
|
+
if (result?.localeUpdated && result?.profileLang) {
|
|
60
|
+
try {
|
|
61
|
+
const profileLang = result.profileLang as string;
|
|
62
|
+
const pathname = window.location.pathname;
|
|
63
|
+
const search = window.location.search || "";
|
|
64
|
+
// Retirer un préfixe langue existant
|
|
65
|
+
const pathWithoutLang =
|
|
66
|
+
pathname.replace(/^\/[a-z]{2}(?=\/|$)/, "") || "/";
|
|
67
|
+
// Éviter de rediriger vers des routes API
|
|
68
|
+
const safePath = pathWithoutLang.startsWith("/api")
|
|
69
|
+
? "/"
|
|
70
|
+
: pathWithoutLang;
|
|
71
|
+
const target = `${profileLang.startsWith("/") ? profileLang : "/" + profileLang}${safePath}${search}`;
|
|
72
|
+
router.replace(target);
|
|
73
|
+
return;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.debug("dashboard: locale redirect failed", e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
51
79
|
setUserData(result.data);
|
|
52
80
|
} catch (err) {
|
|
53
81
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
@@ -84,21 +112,33 @@ export function DashboardPage() {
|
|
|
84
112
|
}
|
|
85
113
|
|
|
86
114
|
if (!userData) {
|
|
87
|
-
return
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
<HasProfil />
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
88
120
|
}
|
|
89
121
|
|
|
90
122
|
const fullName =
|
|
91
|
-
userData
|
|
123
|
+
userData?.profile?.first_name && userData.profile?.last_name
|
|
92
124
|
? `${userData.profile.first_name} ${userData.profile.last_name}`
|
|
93
125
|
: "User";
|
|
94
126
|
|
|
95
127
|
return (
|
|
96
|
-
<div className="
|
|
97
|
-
<
|
|
98
|
-
{
|
|
99
|
-
|
|
128
|
+
<div className="max-w-6xl mx-auto">
|
|
129
|
+
<HasProfil
|
|
130
|
+
hasUpdate={(v) => {
|
|
131
|
+
setHasUpdate(v);
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
<div className="flex flex-inline items-center gap-2 mb-4">
|
|
135
|
+
<LayoutDashboard size={24} />
|
|
136
|
+
<h1 className="text-3xl font-bold ">
|
|
137
|
+
{t("dashboard.title") || "Dashboard"}
|
|
138
|
+
</h1>
|
|
139
|
+
</div>
|
|
100
140
|
|
|
101
|
-
<div className="grid gap-6 md:grid-cols-2">
|
|
141
|
+
<div className="mx-0 px-0 grid gap-6 md:grid-cols-2">
|
|
102
142
|
{/* Profile Summary Card */}
|
|
103
143
|
<Card className="col-span-full md:col-span-1">
|
|
104
144
|
<CardHeader className="flex gap-3">
|
|
@@ -159,15 +199,19 @@ export function DashboardPage() {
|
|
|
159
199
|
<span className="text-small">
|
|
160
200
|
{t("dashboard.profile") || "Profile"}
|
|
161
201
|
</span>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
{hasUpdate === null ? (
|
|
203
|
+
<Skeleton className="w-16 h-6 rounded-full" />
|
|
204
|
+
) : (
|
|
205
|
+
<Chip
|
|
206
|
+
color={hasUpdate ? "success" : "warning"}
|
|
207
|
+
size="sm"
|
|
208
|
+
variant="flat"
|
|
209
|
+
>
|
|
210
|
+
{hasUpdate
|
|
211
|
+
? t("dashboard.complete") || "Complete"
|
|
212
|
+
: t("dashboard.incomplete") || "Incomplete"}
|
|
213
|
+
</Chip>
|
|
214
|
+
)}
|
|
171
215
|
</div>
|
|
172
216
|
</div>
|
|
173
217
|
</CardBody>
|
|
@@ -188,7 +232,7 @@ export function DashboardPage() {
|
|
|
188
232
|
</Card>
|
|
189
233
|
)}
|
|
190
234
|
|
|
191
|
-
{/* Quick Stats
|
|
235
|
+
{/* Quick Stats
|
|
192
236
|
<Card className="col-span-full">
|
|
193
237
|
<CardHeader>
|
|
194
238
|
<h3 className="text-lg font-semibold">Quick Stats</h3>
|
|
@@ -219,7 +263,7 @@ export function DashboardPage() {
|
|
|
219
263
|
</div>
|
|
220
264
|
</div>
|
|
221
265
|
</CardBody>
|
|
222
|
-
</Card>
|
|
266
|
+
</Card> */}
|
|
223
267
|
</div>
|
|
224
268
|
</div>
|
|
225
269
|
);
|