@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.
@@ -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,iBA6NtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
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: "Connexion",
124
- description: "Connectez-vous à votre compte",
125
- icon: "LogIn",
126
- path: "/signin",
127
- order: 1,
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: "Inscription",
131
- description: "Créez votre compte",
132
- icon: "UserPlus2",
133
- path: "/signup",
134
- order: 2,
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"}
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ThemeSwitcher } from "@lastbrain/ui";
4
+ export const ThemeSwitcherButton = (_props) => {
5
+ return _jsx(ThemeSwitcher, {});
6
+ };
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";
@@ -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;AAE5D,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"}
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";
@@ -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: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Photo de profil" }) }), _jsx(Divider, {}), _jsx(CardBody, { 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" })] })] }) })] }));
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.16",
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"
@@ -122,18 +122,34 @@ const authBuildConfig: ModuleBuildConfig = {
122
122
  menu: {
123
123
  public: [
124
124
  {
125
- title: "Connexion",
126
- description: "Connectez-vous à votre compte",
127
- icon: "LogIn",
128
- path: "/signin",
129
- order: 1,
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: "Inscription",
133
- description: "Créez votre compte",
134
- icon: "UserPlus2",
135
- path: "/signup",
136
- order: 2,
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";
@@ -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
- <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>
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 = '';