@lastbrain/module-auth 0.1.16 → 0.1.18
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/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +114 -10
- package/dist/components/AccountButton.d.ts +18 -0
- package/dist/components/AccountButton.d.ts.map +1 -0
- package/dist/components/AccountButton.js +40 -0
- package/dist/components/NotificationButton.d.ts +18 -0
- package/dist/components/NotificationButton.d.ts.map +1 -0
- package/dist/components/NotificationButton.js +8 -0
- package/dist/components/ThemeSwitcherButton.d.ts +9 -0
- package/dist/components/ThemeSwitcherButton.d.ts.map +1 -0
- package/dist/components/ThemeSwitcherButton.js +6 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/web/auth/profile.js +17 -17
- package/package.json +2 -1
- package/src/auth.build.config.ts +114 -10
- package/src/components/AccountButton.tsx +157 -0
- package/src/components/NotificationButton.tsx +41 -0
- package/src/components/ThemeSwitcherButton.tsx +14 -0
- package/src/index.ts +6 -0
- package/src/web/auth/profile.tsx +34 -34
- package/supabase/migrations/20251127100000_rename_body_to_message.sql +9 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.build.config.d.ts","sourceRoot":"","sources":["../src/auth.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.build.config.d.ts","sourceRoot":"","sources":["../src/auth.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,eAAe,EAAE,iBAqUtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -120,18 +120,34 @@ const authBuildConfig = {
|
|
|
120
120
|
menu: {
|
|
121
121
|
public: [
|
|
122
122
|
{
|
|
123
|
-
title: "
|
|
124
|
-
description: "
|
|
125
|
-
icon: "
|
|
126
|
-
path: "
|
|
127
|
-
order:
|
|
123
|
+
title: "Notifications",
|
|
124
|
+
description: "Vos notifications",
|
|
125
|
+
icon: "Bell",
|
|
126
|
+
path: "#",
|
|
127
|
+
order: 998,
|
|
128
|
+
type: "icon",
|
|
129
|
+
position: "end",
|
|
130
|
+
componentExport: "NotificationButton",
|
|
128
131
|
},
|
|
129
132
|
{
|
|
130
|
-
title: "
|
|
131
|
-
description: "
|
|
132
|
-
icon: "
|
|
133
|
-
path: "
|
|
134
|
-
order:
|
|
133
|
+
title: "Compte",
|
|
134
|
+
description: "Gérer votre compte",
|
|
135
|
+
icon: "User",
|
|
136
|
+
path: "#",
|
|
137
|
+
order: 999,
|
|
138
|
+
type: "icon",
|
|
139
|
+
position: "end",
|
|
140
|
+
componentExport: "AccountButton",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
title: "Theme",
|
|
144
|
+
description: "Changer le thème",
|
|
145
|
+
icon: "Palette",
|
|
146
|
+
path: "#",
|
|
147
|
+
order: 9999,
|
|
148
|
+
type: "icon",
|
|
149
|
+
position: "end",
|
|
150
|
+
componentExport: "ThemeSwitcherButton",
|
|
135
151
|
},
|
|
136
152
|
],
|
|
137
153
|
admin: [
|
|
@@ -144,6 +160,36 @@ const authBuildConfig = {
|
|
|
144
160
|
shortcut: "cmd+shift+u",
|
|
145
161
|
shortcutDisplay: "⌘⇧U",
|
|
146
162
|
},
|
|
163
|
+
{
|
|
164
|
+
title: "Notifications",
|
|
165
|
+
description: "Vos notifications",
|
|
166
|
+
icon: "Bell",
|
|
167
|
+
path: "#",
|
|
168
|
+
order: 998,
|
|
169
|
+
type: "icon",
|
|
170
|
+
position: "end",
|
|
171
|
+
componentExport: "NotificationButton",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
title: "Compte",
|
|
175
|
+
description: "Gérer votre compte",
|
|
176
|
+
icon: "User",
|
|
177
|
+
path: "#",
|
|
178
|
+
order: 999,
|
|
179
|
+
type: "icon",
|
|
180
|
+
position: "end",
|
|
181
|
+
componentExport: "AccountButton",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
title: "Theme",
|
|
185
|
+
description: "Changer le thème",
|
|
186
|
+
icon: "Palette",
|
|
187
|
+
path: "#",
|
|
188
|
+
order: 9999,
|
|
189
|
+
type: "icon",
|
|
190
|
+
position: "end",
|
|
191
|
+
componentExport: "ThemeSwitcherButton",
|
|
192
|
+
},
|
|
147
193
|
],
|
|
148
194
|
auth: [
|
|
149
195
|
{
|
|
@@ -164,6 +210,36 @@ const authBuildConfig = {
|
|
|
164
210
|
shortcut: "cmd+shift+f",
|
|
165
211
|
shortcutDisplay: "⌘⇧F",
|
|
166
212
|
},
|
|
213
|
+
{
|
|
214
|
+
title: "Notifications",
|
|
215
|
+
description: "Vos notifications",
|
|
216
|
+
icon: "Bell",
|
|
217
|
+
path: "#",
|
|
218
|
+
order: 998,
|
|
219
|
+
type: "icon",
|
|
220
|
+
position: "end",
|
|
221
|
+
componentExport: "NotificationButton",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
title: "Compte",
|
|
225
|
+
description: "Gérer votre compte",
|
|
226
|
+
icon: "User",
|
|
227
|
+
path: "#",
|
|
228
|
+
order: 999,
|
|
229
|
+
type: "icon",
|
|
230
|
+
position: "end",
|
|
231
|
+
componentExport: "AccountButton",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
title: "Theme",
|
|
235
|
+
description: "Changer le thème",
|
|
236
|
+
icon: "Palette",
|
|
237
|
+
path: "#",
|
|
238
|
+
order: 9999,
|
|
239
|
+
type: "icon",
|
|
240
|
+
position: "end",
|
|
241
|
+
componentExport: "ThemeSwitcherButton",
|
|
242
|
+
},
|
|
167
243
|
],
|
|
168
244
|
account: [
|
|
169
245
|
{
|
|
@@ -219,5 +295,33 @@ const authBuildConfig = {
|
|
|
219
295
|
},
|
|
220
296
|
],
|
|
221
297
|
},
|
|
298
|
+
storage: {
|
|
299
|
+
buckets: [
|
|
300
|
+
{
|
|
301
|
+
name: "app",
|
|
302
|
+
public: false,
|
|
303
|
+
description: "Private user files and documents /{userId}/...",
|
|
304
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB
|
|
305
|
+
customAccessControl: (userId, filePath) => {
|
|
306
|
+
// Users can only access files in their own folder (app/{userId}/...)
|
|
307
|
+
return filePath.startsWith(`${userId}/`);
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "avatar",
|
|
312
|
+
public: true,
|
|
313
|
+
description: "Public user avatar images",
|
|
314
|
+
allowedMimeTypes: [
|
|
315
|
+
"image/jpeg",
|
|
316
|
+
"image/jpg",
|
|
317
|
+
"image/png",
|
|
318
|
+
"image/webp",
|
|
319
|
+
"image/gif",
|
|
320
|
+
],
|
|
321
|
+
maxFileSize: 5242880, // 5MB
|
|
322
|
+
fileSizeLimit: "5MB",
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
222
326
|
};
|
|
223
327
|
export default authBuildConfig;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { User } from "@supabase/supabase-js";
|
|
2
|
+
interface AccountButtonProps {
|
|
3
|
+
item: {
|
|
4
|
+
path: string;
|
|
5
|
+
title: string;
|
|
6
|
+
};
|
|
7
|
+
user?: User | null;
|
|
8
|
+
accountMenu?: Array<{
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}>;
|
|
14
|
+
onLogout?: () => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare const AccountButton: ({ user, accountMenu, onLogout, }: AccountButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=AccountButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccountButton.d.ts","sourceRoot":"","sources":["../../src/components/AccountButton.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAGlD,UAAU,kBAAkB;IAC1B,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AASD,eAAO,MAAM,aAAa,GAAI,kCAI3B,kBAAkB,4CAoHpB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Button, Link } from "@lastbrain/ui";
|
|
4
|
+
import { Avatar } from "@lastbrain/ui";
|
|
5
|
+
import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, } from "@lastbrain/ui";
|
|
6
|
+
import * as LucideIcons from "lucide-react";
|
|
7
|
+
// Fonction pour récupérer l'icône Lucide
|
|
8
|
+
const getIcon = (iconName) => {
|
|
9
|
+
if (!iconName)
|
|
10
|
+
return null;
|
|
11
|
+
const Icon = LucideIcons[iconName];
|
|
12
|
+
return Icon ? Icon : null;
|
|
13
|
+
};
|
|
14
|
+
export const AccountButton = ({ user, accountMenu = [], onLogout, }) => {
|
|
15
|
+
if (!user)
|
|
16
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "block md:hidden", children: [_jsx(Button, { as: Link, href: "/signin", radius: "full", isIconOnly: true, variant: "light", color: "primary", children: _jsx(LucideIcons.LogIn, { size: 16 }) }), _jsx(Button, { as: Link, href: "/signup", radius: "full", isIconOnly: true, variant: "flat", className: "ml-2", color: "secondary", children: _jsx(LucideIcons.UserPlus2, { size: 16 }) })] }), _jsxs("div", { className: "hidden md:block", children: [_jsx(Button, { as: Link, href: "/signin", startContent: _jsx(LucideIcons.LogIn, { size: 16 }), variant: "light", color: "primary", children: "Se connecter" }), _jsx(Button, { as: Link, href: "/signup", variant: "flat", className: "ml-2", color: "secondary", startContent: _jsx(LucideIcons.UserPlus2, { size: 16 }), children: "S'inscrire" })] })] }));
|
|
17
|
+
return (_jsxs(Dropdown, { children: [_jsx(DropdownTrigger, { children: _jsx(Avatar, { size: "sm", src: `/api/storage/${user?.user_metadata.avatar}`, title: user.email, fallback: _jsx(LucideIcons.User2, { size: 18 }), classNames: {
|
|
18
|
+
base: "bg-white/0",
|
|
19
|
+
icon: "text-default-700",
|
|
20
|
+
} }) }), _jsx(DropdownMenu, { items: [
|
|
21
|
+
{
|
|
22
|
+
key: "hello",
|
|
23
|
+
label: `Bonjour ${user?.user_metadata?.full_name || user.email}`,
|
|
24
|
+
isReadOnly: true,
|
|
25
|
+
},
|
|
26
|
+
...accountMenu.map((item) => ({
|
|
27
|
+
key: item.path,
|
|
28
|
+
label: item.title,
|
|
29
|
+
description: item.description,
|
|
30
|
+
icon: item.icon,
|
|
31
|
+
isLogout: item.path.includes("signout") || item.path.includes("logout"),
|
|
32
|
+
href: item.path.includes("signout") || item.path.includes("logout")
|
|
33
|
+
? undefined
|
|
34
|
+
: item.path,
|
|
35
|
+
})),
|
|
36
|
+
], children: (item) => {
|
|
37
|
+
const Icon = item.icon ? getIcon(item.icon) : null;
|
|
38
|
+
return (_jsx(DropdownItem, { href: item.href, onPress: item.isLogout ? () => onLogout?.() : undefined, color: item.isLogout ? "danger" : "default", description: item.description, startContent: Icon && _jsx(Icon, { size: 16 }), isDisabled: item.isReadOnly, isReadOnly: item.isReadOnly, children: item.label }, item.key));
|
|
39
|
+
} })] }));
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type UserNotification } from "@lastbrain/ui";
|
|
2
|
+
import type { User } from "@supabase/supabase-js";
|
|
3
|
+
interface NotificationButtonProps {
|
|
4
|
+
item: {
|
|
5
|
+
path: string;
|
|
6
|
+
title: string;
|
|
7
|
+
};
|
|
8
|
+
user?: User | null;
|
|
9
|
+
notifications?: UserNotification[];
|
|
10
|
+
unreadCount?: number;
|
|
11
|
+
notificationsLoading?: boolean;
|
|
12
|
+
onMarkAsRead?: (id: string) => void;
|
|
13
|
+
onMarkAllAsRead?: () => void;
|
|
14
|
+
onDeleteNotification?: (id: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const NotificationButton: ({ user, notifications, unreadCount, notificationsLoading, onMarkAsRead, onMarkAllAsRead, onDeleteNotification, }: NotificationButtonProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=NotificationButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationButton.d.ts","sourceRoot":"","sources":["../../src/components/NotificationButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAElD,UAAU,uBAAuB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,aAAa,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,oBAAoB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED,eAAO,MAAM,kBAAkB,GAAI,kHAQhC,uBAAuB,mDAazB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Notification } from "@lastbrain/ui";
|
|
4
|
+
export const NotificationButton = ({ user, notifications = [], unreadCount = 0, notificationsLoading = false, onMarkAsRead = () => { }, onMarkAllAsRead = () => { }, onDeleteNotification = () => { }, }) => {
|
|
5
|
+
if (!user)
|
|
6
|
+
return null;
|
|
7
|
+
return (_jsx(Notification, { notifications: notifications, unreadCount: unreadCount, loading: notificationsLoading, onMarkAsRead: onMarkAsRead, onMarkAllAsRead: onMarkAllAsRead, onDelete: onDeleteNotification }));
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface ThemeSwitcherButtonProps {
|
|
2
|
+
item: {
|
|
3
|
+
path: string;
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export declare const ThemeSwitcherButton: (_props: ThemeSwitcherButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ThemeSwitcherButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeSwitcherButton.d.ts","sourceRoot":"","sources":["../../src/components/ThemeSwitcherButton.tsx"],"names":[],"mappings":"AAIA,UAAU,wBAAwB;IAChC,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,eAAO,MAAM,mBAAmB,GAAI,QAAQ,wBAAwB,4CAEnE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export { ReglagePage } from "./web/auth/reglage.js";
|
|
|
8
8
|
export { AdminUsersPage } from "./web/admin/users.js";
|
|
9
9
|
export { default as UserPage } from "./web/admin/users/[id].js";
|
|
10
10
|
export { UserDetailPage } from "./web/admin/user-detail.js";
|
|
11
|
+
export { AccountButton } from "./components/AccountButton.js";
|
|
12
|
+
export { NotificationButton } from "./components/NotificationButton.js";
|
|
13
|
+
export { ThemeSwitcherButton } from "./components/ThemeSwitcherButton.js";
|
|
11
14
|
export { Doc } from "./components/Doc.js";
|
|
12
15
|
export { Doc as AuthModuleDoc } from "./components/Doc.js";
|
|
13
16
|
export { default as authBuildConfig } from "./auth.build.config.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAG1E,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,10 @@ export { ReglagePage } from "./web/auth/reglage.js";
|
|
|
9
9
|
export { AdminUsersPage } from "./web/admin/users.js";
|
|
10
10
|
export { default as UserPage } from "./web/admin/users/[id].js";
|
|
11
11
|
export { UserDetailPage } from "./web/admin/user-detail.js";
|
|
12
|
+
// Header Components
|
|
13
|
+
export { AccountButton } from "./components/AccountButton.js";
|
|
14
|
+
export { NotificationButton } from "./components/NotificationButton.js";
|
|
15
|
+
export { ThemeSwitcherButton } from "./components/ThemeSwitcherButton.js";
|
|
12
16
|
// Documentation
|
|
13
17
|
export { Doc } from "./components/Doc.js";
|
|
14
18
|
export { Doc as AuthModuleDoc } from "./components/Doc.js";
|
package/dist/web/auth/profile.js
CHANGED
|
@@ -174,21 +174,21 @@ export function ProfilePage() {
|
|
|
174
174
|
if (isLoading) {
|
|
175
175
|
return (_jsx("div", { className: "flex justify-center items-center min-h-[400px]", children: _jsx(Spinner, { size: "lg", label: "Loading profile..." }) }));
|
|
176
176
|
}
|
|
177
|
-
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(User, { className: "w-8 h-8" }), _jsx("h1", { className: "text-3xl font-bold", children: "Edit Profile" })] }), _jsx("form", { onSubmit: handleSubmit, children: _jsxs("div", { className: "space-y-6", children: [
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
177
|
+
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(User, { className: "w-8 h-8" }), _jsx("h1", { className: "text-3xl font-bold", children: "Edit Profile" })] }), _jsx("form", { onSubmit: handleSubmit, children: _jsxs("div", { className: "space-y-6", children: [_jsx("div", { className: "flex justify-center", children: _jsx(AvatarUploader, { userId: currentUser?.id, bucket: "avatar", shape: "circle", onUpload: handleAvatarUpload, onDelete: handleAvatarDelete, initialAvatarPath: currentUser?.user_metadata?.avatar ||
|
|
178
|
+
profile.avatar_url ||
|
|
179
|
+
null, initialAvatarSizes: (() => {
|
|
180
|
+
const sizes = currentUser?.user_metadata
|
|
181
|
+
?.avatar_sizes;
|
|
182
|
+
if (!sizes)
|
|
183
|
+
return null;
|
|
184
|
+
return {
|
|
185
|
+
small: sizes.small ?? null,
|
|
186
|
+
medium: sizes.medium ?? null,
|
|
187
|
+
large: sizes.large ?? null,
|
|
188
|
+
};
|
|
189
|
+
})(), onUploaded: (urls) => {
|
|
190
|
+
setProfile((prev) => ({ ...prev, avatar_url: urls.large }));
|
|
191
|
+
}, onDeleted: () => {
|
|
192
|
+
setProfile((prev) => ({ ...prev, avatar_url: "" }));
|
|
193
|
+
} }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Personal Information" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: "First Name", placeholder: "Enter your first name", value: profile.first_name || "", onChange: (e) => handleChange("first_name", e.target.value) }), _jsx(Input, { label: "Last Name", placeholder: "Enter your last name", value: profile.last_name || "", onChange: (e) => handleChange("last_name", e.target.value) }), _jsx(Input, { label: "Phone", placeholder: "Enter your phone number", type: "tel", value: profile.phone || "", onChange: (e) => handleChange("phone", e.target.value), className: "md:col-span-2" }), _jsx(Textarea, { label: "Bio", placeholder: "Tell us about yourself", value: profile.bio || "", onChange: (e) => handleChange("bio", e.target.value), minRows: 3, className: "md:col-span-2" })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Professional Information" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: "Company", placeholder: "Enter your company name", value: profile.company || "", onChange: (e) => handleChange("company", e.target.value) }), _jsx(Input, { label: "Website", placeholder: "https://example.com", type: "url", value: profile.website || "", onChange: (e) => handleChange("website", e.target.value) }), _jsx(Input, { label: "Location", placeholder: "City, Country", value: profile.location || "", onChange: (e) => handleChange("location", e.target.value), className: "md:col-span-2" })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Preferences" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: "Language", placeholder: "en, fr, es...", value: profile.language || "", onChange: (e) => handleChange("language", e.target.value) }), _jsx(Input, { label: "Timezone", placeholder: "Europe/Paris, America/New_York...", value: profile.timezone || "", onChange: (e) => handleChange("timezone", e.target.value) })] }) })] }), _jsxs("div", { className: "flex justify-end gap-3", children: [_jsx(Button, { type: "button", variant: "flat", onPress: () => fetchProfile(), isDisabled: isSaving, children: "Cancel" }), _jsx(Button, { type: "submit", color: "primary", isLoading: isSaving, startContent: !isSaving && _jsx(Save, { className: "w-4 h-4" }), children: isSaving ? "Saving..." : "Save Changes" })] })] }) })] }));
|
|
194
194
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/module-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Module d'authentification complet pour LastBrain avec Supabase",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@lastbrain/core": "^0.1.0",
|
|
33
33
|
"@lastbrain/ui": "^0.1.4",
|
|
34
|
+
"@supabase/supabase-js": "^2.86.0",
|
|
34
35
|
"lucide-react": "^0.554.0",
|
|
35
36
|
"react": "^19.0.0",
|
|
36
37
|
"react-dom": "^19.0.0"
|
package/src/auth.build.config.ts
CHANGED
|
@@ -122,18 +122,34 @@ const authBuildConfig: ModuleBuildConfig = {
|
|
|
122
122
|
menu: {
|
|
123
123
|
public: [
|
|
124
124
|
{
|
|
125
|
-
title: "
|
|
126
|
-
description: "
|
|
127
|
-
icon: "
|
|
128
|
-
path: "
|
|
129
|
-
order:
|
|
125
|
+
title: "Notifications",
|
|
126
|
+
description: "Vos notifications",
|
|
127
|
+
icon: "Bell",
|
|
128
|
+
path: "#",
|
|
129
|
+
order: 998,
|
|
130
|
+
type: "icon",
|
|
131
|
+
position: "end",
|
|
132
|
+
componentExport: "NotificationButton",
|
|
130
133
|
},
|
|
131
134
|
{
|
|
132
|
-
title: "
|
|
133
|
-
description: "
|
|
134
|
-
icon: "
|
|
135
|
-
path: "
|
|
136
|
-
order:
|
|
135
|
+
title: "Compte",
|
|
136
|
+
description: "Gérer votre compte",
|
|
137
|
+
icon: "User",
|
|
138
|
+
path: "#",
|
|
139
|
+
order: 999,
|
|
140
|
+
type: "icon",
|
|
141
|
+
position: "end",
|
|
142
|
+
componentExport: "AccountButton",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
title: "Theme",
|
|
146
|
+
description: "Changer le thème",
|
|
147
|
+
icon: "Palette",
|
|
148
|
+
path: "#",
|
|
149
|
+
order: 9999,
|
|
150
|
+
type: "icon",
|
|
151
|
+
position: "end",
|
|
152
|
+
componentExport: "ThemeSwitcherButton",
|
|
137
153
|
},
|
|
138
154
|
],
|
|
139
155
|
admin: [
|
|
@@ -146,6 +162,36 @@ const authBuildConfig: ModuleBuildConfig = {
|
|
|
146
162
|
shortcut: "cmd+shift+u",
|
|
147
163
|
shortcutDisplay: "⌘⇧U",
|
|
148
164
|
},
|
|
165
|
+
{
|
|
166
|
+
title: "Notifications",
|
|
167
|
+
description: "Vos notifications",
|
|
168
|
+
icon: "Bell",
|
|
169
|
+
path: "#",
|
|
170
|
+
order: 998,
|
|
171
|
+
type: "icon",
|
|
172
|
+
position: "end",
|
|
173
|
+
componentExport: "NotificationButton",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
title: "Compte",
|
|
177
|
+
description: "Gérer votre compte",
|
|
178
|
+
icon: "User",
|
|
179
|
+
path: "#",
|
|
180
|
+
order: 999,
|
|
181
|
+
type: "icon",
|
|
182
|
+
position: "end",
|
|
183
|
+
componentExport: "AccountButton",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
title: "Theme",
|
|
187
|
+
description: "Changer le thème",
|
|
188
|
+
icon: "Palette",
|
|
189
|
+
path: "#",
|
|
190
|
+
order: 9999,
|
|
191
|
+
type: "icon",
|
|
192
|
+
position: "end",
|
|
193
|
+
componentExport: "ThemeSwitcherButton",
|
|
194
|
+
},
|
|
149
195
|
],
|
|
150
196
|
auth: [
|
|
151
197
|
{
|
|
@@ -166,6 +212,36 @@ const authBuildConfig: ModuleBuildConfig = {
|
|
|
166
212
|
shortcut: "cmd+shift+f",
|
|
167
213
|
shortcutDisplay: "⌘⇧F",
|
|
168
214
|
},
|
|
215
|
+
{
|
|
216
|
+
title: "Notifications",
|
|
217
|
+
description: "Vos notifications",
|
|
218
|
+
icon: "Bell",
|
|
219
|
+
path: "#",
|
|
220
|
+
order: 998,
|
|
221
|
+
type: "icon",
|
|
222
|
+
position: "end",
|
|
223
|
+
componentExport: "NotificationButton",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
title: "Compte",
|
|
227
|
+
description: "Gérer votre compte",
|
|
228
|
+
icon: "User",
|
|
229
|
+
path: "#",
|
|
230
|
+
order: 999,
|
|
231
|
+
type: "icon",
|
|
232
|
+
position: "end",
|
|
233
|
+
componentExport: "AccountButton",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
title: "Theme",
|
|
237
|
+
description: "Changer le thème",
|
|
238
|
+
icon: "Palette",
|
|
239
|
+
path: "#",
|
|
240
|
+
order: 9999,
|
|
241
|
+
type: "icon",
|
|
242
|
+
position: "end",
|
|
243
|
+
componentExport: "ThemeSwitcherButton",
|
|
244
|
+
},
|
|
169
245
|
],
|
|
170
246
|
account: [
|
|
171
247
|
{
|
|
@@ -221,6 +297,34 @@ const authBuildConfig: ModuleBuildConfig = {
|
|
|
221
297
|
},
|
|
222
298
|
],
|
|
223
299
|
},
|
|
300
|
+
storage: {
|
|
301
|
+
buckets: [
|
|
302
|
+
{
|
|
303
|
+
name: "app",
|
|
304
|
+
public: false,
|
|
305
|
+
description: "Private user files and documents /{userId}/...",
|
|
306
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB
|
|
307
|
+
customAccessControl: (userId: string, filePath: string) => {
|
|
308
|
+
// Users can only access files in their own folder (app/{userId}/...)
|
|
309
|
+
return filePath.startsWith(`${userId}/`);
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "avatar",
|
|
314
|
+
public: true,
|
|
315
|
+
description: "Public user avatar images",
|
|
316
|
+
allowedMimeTypes: [
|
|
317
|
+
"image/jpeg",
|
|
318
|
+
"image/jpg",
|
|
319
|
+
"image/png",
|
|
320
|
+
"image/webp",
|
|
321
|
+
"image/gif",
|
|
322
|
+
],
|
|
323
|
+
maxFileSize: 5242880, // 5MB
|
|
324
|
+
fileSizeLimit: "5MB",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
224
328
|
};
|
|
225
329
|
|
|
226
330
|
export default authBuildConfig;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button, Link } from "@lastbrain/ui";
|
|
4
|
+
import { Avatar } from "@lastbrain/ui";
|
|
5
|
+
import {
|
|
6
|
+
Dropdown,
|
|
7
|
+
DropdownItem,
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownTrigger,
|
|
10
|
+
} from "@lastbrain/ui";
|
|
11
|
+
import * as LucideIcons from "lucide-react";
|
|
12
|
+
import type { User } from "@supabase/supabase-js";
|
|
13
|
+
import { use } from "react";
|
|
14
|
+
|
|
15
|
+
interface AccountButtonProps {
|
|
16
|
+
item: {
|
|
17
|
+
path: string;
|
|
18
|
+
title: string;
|
|
19
|
+
};
|
|
20
|
+
user?: User | null;
|
|
21
|
+
accountMenu?: Array<{
|
|
22
|
+
title: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
icon?: string;
|
|
25
|
+
path: string;
|
|
26
|
+
}>;
|
|
27
|
+
onLogout?: () => void | Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fonction pour récupérer l'icône Lucide
|
|
31
|
+
const getIcon = (iconName?: string) => {
|
|
32
|
+
if (!iconName) return null;
|
|
33
|
+
const Icon = (LucideIcons as any)[iconName];
|
|
34
|
+
return Icon ? Icon : null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const AccountButton = ({
|
|
38
|
+
user,
|
|
39
|
+
accountMenu = [],
|
|
40
|
+
onLogout,
|
|
41
|
+
}: AccountButtonProps) => {
|
|
42
|
+
if (!user)
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<div className="block md:hidden">
|
|
46
|
+
<Button
|
|
47
|
+
as={Link}
|
|
48
|
+
href="/signin"
|
|
49
|
+
radius="full"
|
|
50
|
+
isIconOnly
|
|
51
|
+
variant="light"
|
|
52
|
+
color="primary"
|
|
53
|
+
>
|
|
54
|
+
<LucideIcons.LogIn size={16} />
|
|
55
|
+
</Button>
|
|
56
|
+
<Button
|
|
57
|
+
as={Link}
|
|
58
|
+
href="/signup"
|
|
59
|
+
radius="full"
|
|
60
|
+
isIconOnly
|
|
61
|
+
variant="flat"
|
|
62
|
+
className="ml-2"
|
|
63
|
+
color="secondary"
|
|
64
|
+
>
|
|
65
|
+
<LucideIcons.UserPlus2 size={16} />
|
|
66
|
+
</Button>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="hidden md:block">
|
|
69
|
+
<Button
|
|
70
|
+
as={Link}
|
|
71
|
+
href="/signin"
|
|
72
|
+
startContent={<LucideIcons.LogIn size={16} />}
|
|
73
|
+
variant="light"
|
|
74
|
+
color="primary"
|
|
75
|
+
>
|
|
76
|
+
Se connecter
|
|
77
|
+
</Button>
|
|
78
|
+
<Button
|
|
79
|
+
as={Link}
|
|
80
|
+
href="/signup"
|
|
81
|
+
variant="flat"
|
|
82
|
+
className="ml-2"
|
|
83
|
+
color="secondary"
|
|
84
|
+
startContent={<LucideIcons.UserPlus2 size={16} />}
|
|
85
|
+
>
|
|
86
|
+
S'inscrire
|
|
87
|
+
</Button>
|
|
88
|
+
</div>
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Dropdown>
|
|
94
|
+
<DropdownTrigger>
|
|
95
|
+
<Avatar
|
|
96
|
+
size="sm"
|
|
97
|
+
src={`/api/storage/${user?.user_metadata.avatar}`}
|
|
98
|
+
title={user.email}
|
|
99
|
+
fallback={<LucideIcons.User2 size={18} />}
|
|
100
|
+
classNames={{
|
|
101
|
+
base: "bg-white/0",
|
|
102
|
+
icon: "text-default-700",
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</DropdownTrigger>
|
|
106
|
+
|
|
107
|
+
<DropdownMenu
|
|
108
|
+
items={[
|
|
109
|
+
{
|
|
110
|
+
key: "hello",
|
|
111
|
+
label: `Bonjour ${user?.user_metadata?.full_name || user.email}`,
|
|
112
|
+
isReadOnly: true,
|
|
113
|
+
},
|
|
114
|
+
...accountMenu.map((item) => ({
|
|
115
|
+
key: item.path,
|
|
116
|
+
label: item.title,
|
|
117
|
+
description: item.description,
|
|
118
|
+
icon: item.icon,
|
|
119
|
+
isLogout:
|
|
120
|
+
item.path.includes("signout") || item.path.includes("logout"),
|
|
121
|
+
href:
|
|
122
|
+
item.path.includes("signout") || item.path.includes("logout")
|
|
123
|
+
? undefined
|
|
124
|
+
: item.path,
|
|
125
|
+
})),
|
|
126
|
+
]}
|
|
127
|
+
>
|
|
128
|
+
{(item: {
|
|
129
|
+
key: string;
|
|
130
|
+
label: string;
|
|
131
|
+
description?: string;
|
|
132
|
+
icon?: string;
|
|
133
|
+
isLogout?: boolean;
|
|
134
|
+
href?: string;
|
|
135
|
+
isReadOnly?: boolean;
|
|
136
|
+
}) => {
|
|
137
|
+
const Icon = item.icon ? getIcon(item.icon) : null;
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<DropdownItem
|
|
141
|
+
key={item.key}
|
|
142
|
+
href={item.href}
|
|
143
|
+
onPress={item.isLogout ? () => onLogout?.() : undefined}
|
|
144
|
+
color={item.isLogout ? "danger" : "default"}
|
|
145
|
+
description={item.description}
|
|
146
|
+
startContent={Icon && <Icon size={16} />}
|
|
147
|
+
isDisabled={item.isReadOnly}
|
|
148
|
+
isReadOnly={item.isReadOnly}
|
|
149
|
+
>
|
|
150
|
+
{item.label}
|
|
151
|
+
</DropdownItem>
|
|
152
|
+
);
|
|
153
|
+
}}
|
|
154
|
+
</DropdownMenu>
|
|
155
|
+
</Dropdown>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Notification, type UserNotification } from "@lastbrain/ui";
|
|
4
|
+
import type { User } from "@supabase/supabase-js";
|
|
5
|
+
|
|
6
|
+
interface NotificationButtonProps {
|
|
7
|
+
item: {
|
|
8
|
+
path: string;
|
|
9
|
+
title: string;
|
|
10
|
+
};
|
|
11
|
+
user?: User | null;
|
|
12
|
+
notifications?: UserNotification[];
|
|
13
|
+
unreadCount?: number;
|
|
14
|
+
notificationsLoading?: boolean;
|
|
15
|
+
onMarkAsRead?: (id: string) => void;
|
|
16
|
+
onMarkAllAsRead?: () => void;
|
|
17
|
+
onDeleteNotification?: (id: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const NotificationButton = ({
|
|
21
|
+
user,
|
|
22
|
+
notifications = [],
|
|
23
|
+
unreadCount = 0,
|
|
24
|
+
notificationsLoading = false,
|
|
25
|
+
onMarkAsRead = () => {},
|
|
26
|
+
onMarkAllAsRead = () => {},
|
|
27
|
+
onDeleteNotification = () => {},
|
|
28
|
+
}: NotificationButtonProps) => {
|
|
29
|
+
if (!user) return null;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Notification
|
|
33
|
+
notifications={notifications}
|
|
34
|
+
unreadCount={unreadCount}
|
|
35
|
+
loading={notificationsLoading}
|
|
36
|
+
onMarkAsRead={onMarkAsRead}
|
|
37
|
+
onMarkAllAsRead={onMarkAllAsRead}
|
|
38
|
+
onDelete={onDeleteNotification}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ThemeSwitcher } from "@lastbrain/ui";
|
|
4
|
+
|
|
5
|
+
interface ThemeSwitcherButtonProps {
|
|
6
|
+
item: {
|
|
7
|
+
path: string;
|
|
8
|
+
title: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ThemeSwitcherButton = (_props: ThemeSwitcherButtonProps) => {
|
|
13
|
+
return <ThemeSwitcher />;
|
|
14
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,12 @@ export { ReglagePage } from "./web/auth/reglage.js";
|
|
|
9
9
|
export { AdminUsersPage } from "./web/admin/users.js";
|
|
10
10
|
export { default as UserPage } from "./web/admin/users/[id].js";
|
|
11
11
|
export { UserDetailPage } from "./web/admin/user-detail.js";
|
|
12
|
+
|
|
13
|
+
// Header Components
|
|
14
|
+
export { AccountButton } from "./components/AccountButton.js";
|
|
15
|
+
export { NotificationButton } from "./components/NotificationButton.js";
|
|
16
|
+
export { ThemeSwitcherButton } from "./components/ThemeSwitcherButton.js";
|
|
17
|
+
|
|
12
18
|
// Documentation
|
|
13
19
|
export { Doc } from "./components/Doc.js";
|
|
14
20
|
export { Doc as AuthModuleDoc } from "./components/Doc.js";
|
package/src/web/auth/profile.tsx
CHANGED
|
@@ -260,44 +260,44 @@ export function ProfilePage() {
|
|
|
260
260
|
<form onSubmit={handleSubmit}>
|
|
261
261
|
<div className="space-y-6">
|
|
262
262
|
{/* Avatar Section */}
|
|
263
|
-
<Card>
|
|
263
|
+
{/* <Card className="max-w-sm mx-auto">
|
|
264
264
|
<CardHeader>
|
|
265
265
|
<h3 className="text-lg font-semibold">Photo de profil</h3>
|
|
266
266
|
</CardHeader>
|
|
267
267
|
<Divider />
|
|
268
|
-
<CardBody>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
</Card>
|
|
268
|
+
<CardBody> */}
|
|
269
|
+
<div className="flex justify-center">
|
|
270
|
+
<AvatarUploader
|
|
271
|
+
userId={currentUser?.id}
|
|
272
|
+
bucket="avatar"
|
|
273
|
+
shape="circle"
|
|
274
|
+
onUpload={handleAvatarUpload}
|
|
275
|
+
onDelete={handleAvatarDelete}
|
|
276
|
+
initialAvatarPath={
|
|
277
|
+
(currentUser?.user_metadata as UserMetadata)?.avatar ||
|
|
278
|
+
profile.avatar_url ||
|
|
279
|
+
null
|
|
280
|
+
}
|
|
281
|
+
initialAvatarSizes={(() => {
|
|
282
|
+
const sizes = (currentUser?.user_metadata as UserMetadata)
|
|
283
|
+
?.avatar_sizes;
|
|
284
|
+
if (!sizes) return null;
|
|
285
|
+
return {
|
|
286
|
+
small: sizes.small ?? null,
|
|
287
|
+
medium: sizes.medium ?? null,
|
|
288
|
+
large: sizes.large ?? null,
|
|
289
|
+
};
|
|
290
|
+
})()}
|
|
291
|
+
onUploaded={(urls) => {
|
|
292
|
+
setProfile((prev) => ({ ...prev, avatar_url: urls.large }));
|
|
293
|
+
}}
|
|
294
|
+
onDeleted={() => {
|
|
295
|
+
setProfile((prev) => ({ ...prev, avatar_url: "" }));
|
|
296
|
+
}}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
{/* </CardBody>
|
|
300
|
+
</Card> */}
|
|
301
301
|
|
|
302
302
|
{/* Personal Information */}
|
|
303
303
|
<Card>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Migration: Add message column to user_notifications table
|
|
2
|
+
-- message: short text summary (required)
|
|
3
|
+
-- body: rich HTML content (optional)
|
|
4
|
+
|
|
5
|
+
ALTER TABLE public.user_notifications
|
|
6
|
+
ADD COLUMN IF NOT EXISTS message TEXT NOT NULL DEFAULT '';
|
|
7
|
+
|
|
8
|
+
-- If you have existing data, you might want to copy body to message:
|
|
9
|
+
-- UPDATE public.user_notifications SET message = COALESCE(body, title) WHERE message = '';
|