@lastbrain/module-auth 2.0.27 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +126 -11
- 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/components/auth/dashboard.d.ts +1 -1
- package/dist/components/auth/dashboard.d.ts.map +1 -1
- package/dist/components/auth/dashboard.js +34 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- 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 +126 -11
- package/src/components/AccountButton.tsx +114 -90
- package/src/components/Doc.tsx +47 -9
- package/src/components/HasProfil.tsx +63 -0
- package/src/components/auth/dashboard.tsx +54 -13
- package/src/i18n/en.json +76 -8
- package/src/i18n/es.json +330 -0
- package/src/i18n/fr.json +74 -8
- package/src/index.ts +2 -0
- 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 +0 -2
- package/dist/web/auth/dashboard.d.ts.map +0 -1
- package/dist/web/auth/dashboard.js +0 -48
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Charge les traductions de l'app depuis les fichiers i18n agrégés
|
|
5
|
+
*/
|
|
6
|
+
function loadAppTranslations(lang) {
|
|
7
|
+
try {
|
|
8
|
+
const possiblePaths = [
|
|
9
|
+
// Production (Vercel)
|
|
10
|
+
path.join("/app", "i18n", `${lang}.json`),
|
|
11
|
+
// Development - apps/lastbrain
|
|
12
|
+
path.join(process.cwd(), "i18n", `${lang}.json`),
|
|
13
|
+
// Development - depuis packages vers apps/lastbrain
|
|
14
|
+
path.join(process.cwd(), "..", "..", "apps", "lastbrain", "i18n", `${lang}.json`),
|
|
15
|
+
// Development - apps/recipe
|
|
16
|
+
path.join(process.cwd(), "..", "..", "apps", "recipe", "i18n", `${lang}.json`),
|
|
17
|
+
];
|
|
18
|
+
for (const filePath of possiblePaths) {
|
|
19
|
+
if (fs.existsSync(filePath)) {
|
|
20
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
21
|
+
return JSON.parse(content);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn(`[getAppBranding] Could not load translations for ${lang}:`, error);
|
|
27
|
+
}
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Récupère le branding depuis les clés i18n de l'app
|
|
32
|
+
* Utilise les clés : app.name, app.icon, app.description, app.tagline, etc.
|
|
33
|
+
*/
|
|
34
|
+
export function getAppBranding(locale = "en") {
|
|
35
|
+
const loc = locale === "fr" ? "fr" : "en";
|
|
36
|
+
const translations = loadAppTranslations(loc);
|
|
37
|
+
return {
|
|
38
|
+
appName: translations["app.name"] || "LastBrain",
|
|
39
|
+
icon: translations["app.icon"] || "Brain",
|
|
40
|
+
logo: translations["app.icon"] || "Brain",
|
|
41
|
+
description: translations["app.description"] || "",
|
|
42
|
+
tagline: translations["app.tagline"] || "",
|
|
43
|
+
twitterCreator: translations["app.twitter_creator"] || "",
|
|
44
|
+
twitterSite: translations["app.twitter_site"] || "",
|
|
45
|
+
supportEmail: translations["app.email.contact"] || "support@lastbrain.io",
|
|
46
|
+
primaryColor: translations["app.email.color.primary"] || "#3b82f6",
|
|
47
|
+
secondaryColor: translations["app.email.color.secondary"] || "#8b5cf6",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
interface BaseEmailParams {
|
|
2
|
+
email: string;
|
|
3
|
+
displayName?: string;
|
|
4
|
+
ownerId?: string;
|
|
5
|
+
redirectTo?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function sendSignupConfirmationEmail(params: {
|
|
8
|
+
email: string;
|
|
9
|
+
password: string;
|
|
10
|
+
fullName?: string;
|
|
11
|
+
signupSource?: string;
|
|
12
|
+
redirectTo?: string;
|
|
13
|
+
next?: string;
|
|
14
|
+
locale?: string;
|
|
15
|
+
}): Promise<import("@supabase/supabase-js").AuthUser>;
|
|
16
|
+
export declare function sendPasswordResetEmail(params: BaseEmailParams & {
|
|
17
|
+
locale?: string;
|
|
18
|
+
next?: string;
|
|
19
|
+
}): Promise<import("@supabase/supabase-js").AuthUser>;
|
|
20
|
+
export declare function sendMagicLinkEmail(params: BaseEmailParams & {
|
|
21
|
+
locale?: string;
|
|
22
|
+
}): Promise<import("@supabase/supabase-js").AuthUser>;
|
|
23
|
+
export declare function sendInviteEmail(params: BaseEmailParams & {
|
|
24
|
+
password?: string;
|
|
25
|
+
locale?: string;
|
|
26
|
+
}): Promise<import("@supabase/supabase-js").AuthUser>;
|
|
27
|
+
export declare function sendEmailChangeConfirmation(params: {
|
|
28
|
+
email: string;
|
|
29
|
+
newEmail: string;
|
|
30
|
+
displayName?: string;
|
|
31
|
+
ownerId?: string;
|
|
32
|
+
redirectTo?: string;
|
|
33
|
+
locale?: string;
|
|
34
|
+
}): Promise<import("@supabase/supabase-js").AuthUser>;
|
|
35
|
+
export declare function sendReauthenticationEmail(params: BaseEmailParams & {
|
|
36
|
+
locale?: string;
|
|
37
|
+
}): Promise<import("@supabase/supabase-js").AuthUser>;
|
|
38
|
+
export declare function fetchCurrentUserOwnerId(): Promise<string | null>;
|
|
39
|
+
export declare function sendWelcomeOnboardingEmail(params: {
|
|
40
|
+
email: string;
|
|
41
|
+
displayName?: string;
|
|
42
|
+
locale?: string;
|
|
43
|
+
ownerId?: string;
|
|
44
|
+
bonusAmountUsd?: number;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Send notification to admin when new user signs up
|
|
48
|
+
*/
|
|
49
|
+
export declare function sendNewUserNotificationToAdmin(params: {
|
|
50
|
+
userEmail: string;
|
|
51
|
+
displayName?: string;
|
|
52
|
+
userId: string;
|
|
53
|
+
locale?: string;
|
|
54
|
+
bonusAmountUsd?: number;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=auth-email-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-email-service.d.ts","sourceRoot":"","sources":["../../src/lib/auth-email-service.ts"],"names":[],"mappings":"AAqCA,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA0DD,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,qDA6CA;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,eAAe,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,qDAwF7D;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,eAAe,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,qDAqC9C;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,eAAe,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,qDAgCjE;AAED,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,qDAiBA;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,eAAe,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,qDA+B9C;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMtE;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,iBA6BA;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAAC,MAAM,EAAE;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,iBAwEA"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { getSupabaseServiceClient, getSupabaseServerClient, } from "@lastbrain/core/server";
|
|
2
|
+
import { sendEmail, getAuthEmailSubject, renderAuthEmailHtml, } from "@lastbrain-labs/module-contact-pro";
|
|
3
|
+
import { getAppBranding } from "./app-branding-data";
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
5
|
+
import { cookies } from "next/headers";
|
|
6
|
+
const FALLBACK_SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ||
|
|
7
|
+
process.env.SITE_URL ||
|
|
8
|
+
"http://localhost:3000";
|
|
9
|
+
const NORMALIZED_SITE_URL = FALLBACK_SITE_URL.replace(/\/$/, "");
|
|
10
|
+
const DEFAULT_LOCALE = "fr";
|
|
11
|
+
/**
|
|
12
|
+
* Récupère la langue depuis le cookie NEXT_LOCALE
|
|
13
|
+
*/
|
|
14
|
+
async function getLocaleFromCookie() {
|
|
15
|
+
try {
|
|
16
|
+
const cookieStore = await cookies();
|
|
17
|
+
const localeCookie = cookieStore.get("NEXT_LOCALE");
|
|
18
|
+
if (localeCookie?.value && /^[a-z]{2}$/.test(localeCookie.value)) {
|
|
19
|
+
return localeCookie.value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
logger.debug("[getLocaleFromCookie] Could not read cookie:", error);
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
async function generateLink(type, params) {
|
|
28
|
+
const supabase = await getSupabaseServiceClient();
|
|
29
|
+
return supabase.auth.admin.generateLink({ type, ...params });
|
|
30
|
+
}
|
|
31
|
+
async function sendAuthEmail(kind, to, actionLink, options) {
|
|
32
|
+
const branding = getAppBranding(options.locale);
|
|
33
|
+
logger.debug("📧 sendAuthEmail - branding:", {
|
|
34
|
+
locale: options.locale,
|
|
35
|
+
appName: branding.appName,
|
|
36
|
+
icon: branding.icon,
|
|
37
|
+
});
|
|
38
|
+
const html = renderAuthEmailHtml(kind, {
|
|
39
|
+
kind,
|
|
40
|
+
actionLink,
|
|
41
|
+
displayName: options.displayName,
|
|
42
|
+
locale: options.locale,
|
|
43
|
+
appName: branding.appName,
|
|
44
|
+
appIcon: branding.icon,
|
|
45
|
+
appTagline: branding.tagline,
|
|
46
|
+
supportEmail: branding.supportEmail,
|
|
47
|
+
primaryColor: branding.primaryColor,
|
|
48
|
+
secondaryColor: branding.secondaryColor,
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
const result = await sendEmail({
|
|
52
|
+
to,
|
|
53
|
+
subject: getAuthEmailSubject(kind, options.locale),
|
|
54
|
+
html,
|
|
55
|
+
emailType: "transactional",
|
|
56
|
+
ownerId: options.ownerId,
|
|
57
|
+
});
|
|
58
|
+
logger.debug("📧 sendEmail result", { ok: true, result });
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.error("📧 sendEmail error", err);
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function sendSignupConfirmationEmail(params) {
|
|
67
|
+
// Récupérer la langue depuis le cookie si non fournie
|
|
68
|
+
const locale = params.locale || (await getLocaleFromCookie()) || DEFAULT_LOCALE;
|
|
69
|
+
// Use magic link for signup so client receives tokens in URL fragment
|
|
70
|
+
// Inclure la locale dans l'URL si pas déjà présente
|
|
71
|
+
let redirectTo = params.redirectTo || `${NORMALIZED_SITE_URL}/${locale}/confirm`;
|
|
72
|
+
// Si un redirectTo personnalisé est fourni sans locale, l'ajouter
|
|
73
|
+
if (params.redirectTo && !params.redirectTo.match(/\/(fr|en|es|de)\//)) {
|
|
74
|
+
const url = new URL(params.redirectTo, NORMALIZED_SITE_URL);
|
|
75
|
+
url.pathname = `/${locale}${url.pathname}`;
|
|
76
|
+
redirectTo = url.toString();
|
|
77
|
+
}
|
|
78
|
+
if (params.next) {
|
|
79
|
+
const sep = redirectTo.includes("?") ? "&" : "?";
|
|
80
|
+
redirectTo = `${redirectTo}${sep}next=${encodeURIComponent(params.next)}`;
|
|
81
|
+
}
|
|
82
|
+
const { data, error } = await generateLink("magiclink", {
|
|
83
|
+
email: params.email,
|
|
84
|
+
options: {
|
|
85
|
+
redirectTo,
|
|
86
|
+
data: {
|
|
87
|
+
full_name: params.fullName,
|
|
88
|
+
signup_source: params.signupSource,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
if (error)
|
|
93
|
+
throw error;
|
|
94
|
+
const actionLink = data?.properties?.action_link || data?.action_link;
|
|
95
|
+
await sendAuthEmail("magic_link", params.email, actionLink, {
|
|
96
|
+
displayName: params.fullName,
|
|
97
|
+
ownerId: data.user?.id,
|
|
98
|
+
locale,
|
|
99
|
+
});
|
|
100
|
+
return data.user;
|
|
101
|
+
}
|
|
102
|
+
export async function sendPasswordResetEmail(params) {
|
|
103
|
+
// Récupérer la langue depuis le cookie si non fournie
|
|
104
|
+
const cookieLocale = await getLocaleFromCookie();
|
|
105
|
+
logger.debug("[sendPasswordResetEmail] locale detection", {
|
|
106
|
+
providedLocale: params.locale,
|
|
107
|
+
cookieLocale,
|
|
108
|
+
});
|
|
109
|
+
// Attempt to infer locale from redirectTo path or use provided locale
|
|
110
|
+
const inferredLocale = (() => {
|
|
111
|
+
// Priority: 1. provided locale, 2. cookie, 3. redirectTo path
|
|
112
|
+
if (params.locale && (params.locale === "fr" || params.locale === "en")) {
|
|
113
|
+
return params.locale;
|
|
114
|
+
}
|
|
115
|
+
if (cookieLocale) {
|
|
116
|
+
return cookieLocale;
|
|
117
|
+
}
|
|
118
|
+
// Otherwise, try to infer from redirectTo URL path
|
|
119
|
+
const url = params.redirectTo || `${FALLBACK_SITE_URL}/api/auth/callback`;
|
|
120
|
+
try {
|
|
121
|
+
const u = new URL(url);
|
|
122
|
+
const seg = u.pathname.split("/").filter(Boolean)[0];
|
|
123
|
+
return seg === "fr" || seg === "en" ? seg : undefined;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
// Default to the reset-password page so the client can handle tokens directly.
|
|
130
|
+
const targetLocale = inferredLocale || DEFAULT_LOCALE;
|
|
131
|
+
logger.debug("[sendPasswordResetEmail] final locale", {
|
|
132
|
+
inferredLocale,
|
|
133
|
+
targetLocale,
|
|
134
|
+
});
|
|
135
|
+
// Construire le redirectTo avec la locale
|
|
136
|
+
let redirectTo = params.redirectTo ||
|
|
137
|
+
`${NORMALIZED_SITE_URL}/${targetLocale}/reset-password`;
|
|
138
|
+
// Si un redirectTo personnalisé est fourni sans locale, l'ajouter
|
|
139
|
+
if (params.redirectTo && !params.redirectTo.match(/\/(fr|en|es|de)\//)) {
|
|
140
|
+
const url = new URL(params.redirectTo, NORMALIZED_SITE_URL);
|
|
141
|
+
url.pathname = `/${targetLocale}${url.pathname}`;
|
|
142
|
+
redirectTo = url.toString();
|
|
143
|
+
}
|
|
144
|
+
if (params.next) {
|
|
145
|
+
const next = params.next;
|
|
146
|
+
const sep = redirectTo.includes("?") ? "&" : "?";
|
|
147
|
+
redirectTo = `${redirectTo}${sep}next=${encodeURIComponent(next)}`;
|
|
148
|
+
}
|
|
149
|
+
logger.debug("[sendPasswordResetEmail] calling generateLink with redirectTo", {
|
|
150
|
+
redirectTo,
|
|
151
|
+
email: params.email,
|
|
152
|
+
});
|
|
153
|
+
const { data, error } = await generateLink("recovery", {
|
|
154
|
+
email: params.email,
|
|
155
|
+
options: { redirectTo },
|
|
156
|
+
});
|
|
157
|
+
logger.debug("[sendPasswordResetEmail] generateLink returned", {
|
|
158
|
+
action_link: data?.properties?.action_link || data?.action_link,
|
|
159
|
+
supabaseData: data,
|
|
160
|
+
error,
|
|
161
|
+
});
|
|
162
|
+
if (error)
|
|
163
|
+
throw error;
|
|
164
|
+
const actionLink = data?.properties?.action_link || data?.action_link;
|
|
165
|
+
await sendAuthEmail("magic_link", params.email, actionLink, {
|
|
166
|
+
displayName: params.displayName,
|
|
167
|
+
ownerId: params.ownerId,
|
|
168
|
+
locale: targetLocale,
|
|
169
|
+
});
|
|
170
|
+
return data.user;
|
|
171
|
+
}
|
|
172
|
+
export async function sendMagicLinkEmail(params) {
|
|
173
|
+
// Récupérer la langue depuis le cookie si non fournie
|
|
174
|
+
const cookieLocale = await getLocaleFromCookie();
|
|
175
|
+
const inferredLocale = (() => {
|
|
176
|
+
if (params.locale && (params.locale === "fr" || params.locale === "en")) {
|
|
177
|
+
return params.locale;
|
|
178
|
+
}
|
|
179
|
+
if (cookieLocale) {
|
|
180
|
+
return cookieLocale;
|
|
181
|
+
}
|
|
182
|
+
const url = params.redirectTo || `${FALLBACK_SITE_URL}/api/auth/callback`;
|
|
183
|
+
try {
|
|
184
|
+
const u = new URL(url);
|
|
185
|
+
const seg = u.pathname.split("/").filter(Boolean)[0];
|
|
186
|
+
return seg === "fr" || seg === "en" ? seg : undefined;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
192
|
+
const redirectTo = params.redirectTo || `${NORMALIZED_SITE_URL}/api/public/callback`;
|
|
193
|
+
const { data, error } = await generateLink("magiclink", {
|
|
194
|
+
email: params.email,
|
|
195
|
+
options: { redirectTo },
|
|
196
|
+
});
|
|
197
|
+
if (error)
|
|
198
|
+
throw error;
|
|
199
|
+
const actionLink = data?.properties?.action_link || data?.action_link;
|
|
200
|
+
await sendAuthEmail("magic_link", params.email, actionLink, {
|
|
201
|
+
displayName: params.displayName,
|
|
202
|
+
ownerId: params.ownerId,
|
|
203
|
+
locale: inferredLocale,
|
|
204
|
+
});
|
|
205
|
+
return data.user;
|
|
206
|
+
}
|
|
207
|
+
export async function sendInviteEmail(params) {
|
|
208
|
+
const inferredLocale = (() => {
|
|
209
|
+
if (params.locale && (params.locale === "fr" || params.locale === "en")) {
|
|
210
|
+
return params.locale;
|
|
211
|
+
}
|
|
212
|
+
const url = params.redirectTo || `${FALLBACK_SITE_URL}/api/auth/callback`;
|
|
213
|
+
try {
|
|
214
|
+
const u = new URL(url);
|
|
215
|
+
const seg = u.pathname.split("/").filter(Boolean)[0];
|
|
216
|
+
return seg === "fr" || seg === "en" ? seg : undefined;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
const redirectTo = params.redirectTo || `${NORMALIZED_SITE_URL}/api/public/callback`;
|
|
223
|
+
const { data, error } = await generateLink("invite", {
|
|
224
|
+
email: params.email,
|
|
225
|
+
password: params.password,
|
|
226
|
+
options: { redirectTo },
|
|
227
|
+
});
|
|
228
|
+
if (error)
|
|
229
|
+
throw error;
|
|
230
|
+
const actionLink = data?.properties?.action_link || data?.action_link;
|
|
231
|
+
await sendAuthEmail("invite", params.email, actionLink, {
|
|
232
|
+
displayName: params.displayName,
|
|
233
|
+
ownerId: params.ownerId ?? data.user?.id,
|
|
234
|
+
locale: inferredLocale,
|
|
235
|
+
});
|
|
236
|
+
return data.user;
|
|
237
|
+
}
|
|
238
|
+
export async function sendEmailChangeConfirmation(params) {
|
|
239
|
+
const redirectTo = params.redirectTo || `${NORMALIZED_SITE_URL}/api/auth/callback`;
|
|
240
|
+
const { data, error } = await generateLink("email_change", {
|
|
241
|
+
email: params.email,
|
|
242
|
+
newEmail: params.newEmail,
|
|
243
|
+
options: { redirectTo },
|
|
244
|
+
});
|
|
245
|
+
if (error)
|
|
246
|
+
throw error;
|
|
247
|
+
const actionLink = data?.properties?.action_link || data?.action_link;
|
|
248
|
+
await sendAuthEmail("confirm_email_change", params.newEmail, actionLink, {
|
|
249
|
+
displayName: params.displayName,
|
|
250
|
+
ownerId: params.ownerId,
|
|
251
|
+
locale: params.locale,
|
|
252
|
+
});
|
|
253
|
+
return data.user;
|
|
254
|
+
}
|
|
255
|
+
export async function sendReauthenticationEmail(params) {
|
|
256
|
+
const inferredLocale = (() => {
|
|
257
|
+
if (params.locale && (params.locale === "fr" || params.locale === "en")) {
|
|
258
|
+
return params.locale;
|
|
259
|
+
}
|
|
260
|
+
const url = params.redirectTo || `${FALLBACK_SITE_URL}/api/auth/callback`;
|
|
261
|
+
try {
|
|
262
|
+
const u = new URL(url);
|
|
263
|
+
const seg = u.pathname.split("/").filter(Boolean)[0];
|
|
264
|
+
return seg === "fr" || seg === "en" ? seg : undefined;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
})();
|
|
270
|
+
const redirectTo = params.redirectTo || `${NORMALIZED_SITE_URL}/api/auth/callback`;
|
|
271
|
+
const { data, error } = await generateLink("reauthentication", {
|
|
272
|
+
email: params.email,
|
|
273
|
+
options: { redirectTo },
|
|
274
|
+
});
|
|
275
|
+
if (error)
|
|
276
|
+
throw error;
|
|
277
|
+
const actionLink = data?.properties?.action_link || data?.action_link;
|
|
278
|
+
await sendAuthEmail("reauthentication", params.email, actionLink, {
|
|
279
|
+
displayName: params.displayName,
|
|
280
|
+
ownerId: params.ownerId,
|
|
281
|
+
locale: inferredLocale,
|
|
282
|
+
});
|
|
283
|
+
return data.user;
|
|
284
|
+
}
|
|
285
|
+
export async function fetchCurrentUserOwnerId() {
|
|
286
|
+
const supabase = await getSupabaseServerClient();
|
|
287
|
+
const { data: { user }, } = await supabase.auth.getUser();
|
|
288
|
+
return user?.id || null;
|
|
289
|
+
}
|
|
290
|
+
export async function sendWelcomeOnboardingEmail(params) {
|
|
291
|
+
const branding = getAppBranding(params.locale);
|
|
292
|
+
// Render the welcome onboarding email using the template from module-contact-pro
|
|
293
|
+
const html = renderAuthEmailHtml("welcome_onboarding", {
|
|
294
|
+
kind: "welcome_onboarding",
|
|
295
|
+
actionLink: `${FALLBACK_SITE_URL}/`,
|
|
296
|
+
displayName: params.displayName,
|
|
297
|
+
locale: params.locale,
|
|
298
|
+
appName: branding.appName,
|
|
299
|
+
appIcon: branding.icon,
|
|
300
|
+
appTagline: branding.tagline,
|
|
301
|
+
supportEmail: branding.supportEmail,
|
|
302
|
+
bonusAmountUsd: params.bonusAmountUsd,
|
|
303
|
+
});
|
|
304
|
+
await sendEmail({
|
|
305
|
+
to: params.email,
|
|
306
|
+
subject: getAuthEmailSubject("welcome_onboarding", params.locale, params.bonusAmountUsd),
|
|
307
|
+
html,
|
|
308
|
+
emailType: "transactional",
|
|
309
|
+
ownerId: params.ownerId,
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Send notification to admin when new user signs up
|
|
315
|
+
*/
|
|
316
|
+
export async function sendNewUserNotificationToAdmin(params) {
|
|
317
|
+
const ADMIN_EMAIL = "contact@lastbrain.io";
|
|
318
|
+
const branding = getAppBranding("fr");
|
|
319
|
+
const html = `
|
|
320
|
+
<!DOCTYPE html>
|
|
321
|
+
<html>
|
|
322
|
+
<head>
|
|
323
|
+
<meta charset="utf-8">
|
|
324
|
+
<style>
|
|
325
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
|
|
326
|
+
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
327
|
+
.header { background: #4F46E5; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
|
|
328
|
+
.content { background: #f9fafb; padding: 30px; border-radius: 0 0 8px 8px; }
|
|
329
|
+
.info-row { margin: 10px 0; padding: 10px; background: white; border-radius: 4px; }
|
|
330
|
+
.label { font-weight: 600; color: #6B7280; }
|
|
331
|
+
.value { color: #111827; }
|
|
332
|
+
</style>
|
|
333
|
+
</head>
|
|
334
|
+
<body>
|
|
335
|
+
<div class="container">
|
|
336
|
+
<div class="header">
|
|
337
|
+
<h2>🎉 Nouvelle inscription - ${branding.appName}</h2>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="content">
|
|
340
|
+
<p>Un nouvel utilisateur vient de s'inscrire sur la plateforme :</p>
|
|
341
|
+
|
|
342
|
+
<div class="info-row">
|
|
343
|
+
<span class="label">Email :</span>
|
|
344
|
+
<span class="value">${params.userEmail}</span>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
${params.displayName ? `<div class="info-row"><span class="label">Nom :</span><span class="value">${params.displayName}</span></div>` : ""}
|
|
348
|
+
|
|
349
|
+
<div class="info-row">
|
|
350
|
+
<span class="label">User ID :</span>
|
|
351
|
+
<span class="value">${params.userId}</span>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<div class="info-row">
|
|
355
|
+
<span class="label">Langue :</span>
|
|
356
|
+
<span class="value">${params.locale || "non définie"}</span>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
${params.bonusAmountUsd ? `<div class="info-row"><span class="label">Bonus accordé :</span><span class="value">$${params.bonusAmountUsd}</span></div>` : ""}
|
|
360
|
+
|
|
361
|
+
<div class="info-row">
|
|
362
|
+
<span class="label">Date :</span>
|
|
363
|
+
<span class="value">${new Date().toLocaleString("fr-FR")}</span>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
</body>
|
|
368
|
+
</html>
|
|
369
|
+
`;
|
|
370
|
+
try {
|
|
371
|
+
await sendEmail({
|
|
372
|
+
to: ADMIN_EMAIL,
|
|
373
|
+
subject: `[${branding.appName}] Nouvelle inscription : ${params.userEmail}`,
|
|
374
|
+
html,
|
|
375
|
+
emailType: "transactional",
|
|
376
|
+
});
|
|
377
|
+
logger.info(`[sendNewUserNotificationToAdmin] Notification sent to ${ADMIN_EMAIL} for user ${params.userId}`);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
logger.error("[sendNewUserNotificationToAdmin] Failed to send admin notification:", error);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-email-templates.d.ts","sourceRoot":"","sources":["../../src/lib/auth-email-templates.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getAuthEmailSubject, renderAuthEmailHtml, } from "@lastbrain-labs/module-contact-pro";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-url.d.ts","sourceRoot":"","sources":["../../src/lib/site-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO1C,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,UASlD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const FALLBACK_SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ||
|
|
2
|
+
process.env.SITE_URL ||
|
|
3
|
+
"http://localhost:3000";
|
|
4
|
+
export function resolveSiteUrl(request) {
|
|
5
|
+
const origin = process.env.NEXT_PUBLIC_SITE_URL ||
|
|
6
|
+
process.env.SITE_URL ||
|
|
7
|
+
request.headers.get("origin") ||
|
|
8
|
+
request.nextUrl.origin ||
|
|
9
|
+
FALLBACK_SITE_URL;
|
|
10
|
+
return origin.replace(/\/$/, "");
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest des sitemaps du module auth
|
|
3
|
+
* Déclare tous les sitemaps enfants à générer
|
|
4
|
+
* Auth module only has static pages (signin, signup, etc.)
|
|
5
|
+
*/
|
|
6
|
+
import type { SitemapManifest, SitemapChild, SitemapChildKind } from "@lastbrain/core";
|
|
7
|
+
export type { SitemapManifest, SitemapChild, SitemapChildKind };
|
|
8
|
+
export declare const sitemapManifest: SitemapManifest;
|
|
9
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/sitemap/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAChE,eAAO,MAAM,eAAe,EAAE,eAapB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const sitemapManifest = {
|
|
2
|
+
module: "auth",
|
|
3
|
+
enabled: true,
|
|
4
|
+
includePublicPagesFromBuildConfig: true,
|
|
5
|
+
children: [
|
|
6
|
+
// Pages statiques (générées auto depuis build.config)
|
|
7
|
+
// Includes: /signin, /signup, /reset-password, /confirm, etc.
|
|
8
|
+
{
|
|
9
|
+
id: "static",
|
|
10
|
+
path: ":lang/static.xml",
|
|
11
|
+
kind: "static",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
@@ -30,16 +30,16 @@ export function SignupStatsPage() {
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
if (loading) {
|
|
33
|
-
return (_jsx("div", { className: "flex justify-center items-center min-h-screen", children: _jsx(Spinner, { size: "lg", label: t("signup_stats.loading") || "Chargement des statistiques..." }) }));
|
|
33
|
+
return (_jsx("div", { className: "mt-16 flex justify-center items-center min-h-screen", children: _jsx(Spinner, { size: "lg", label: t("signup_stats.loading") || "Chargement des statistiques..." }) }));
|
|
34
34
|
}
|
|
35
35
|
if (error || !stats) {
|
|
36
|
-
return (_jsx("div", { className: "p-6", children: _jsx(Card, { className: "border border-danger-200 bg-danger-50/50", children: _jsx(CardBody, { children: _jsx("p", { className: "text-danger-600", children: error || "Erreur de chargement" }) }) }) }));
|
|
36
|
+
return (_jsx("div", { className: "mt-16 p-6", children: _jsx(Card, { className: "border border-danger-200 bg-danger-50/50", children: _jsx(CardBody, { children: _jsx("p", { className: "text-danger-600", children: error || "Erreur de chargement" }) }) }) }));
|
|
37
37
|
}
|
|
38
38
|
const lastbrainPercentage = ((stats.bySource.lastbrain / stats.total) *
|
|
39
39
|
100).toFixed(1);
|
|
40
40
|
const recipePercentage = ((stats.bySource.recipe / stats.total) *
|
|
41
41
|
100).toFixed(1);
|
|
42
|
-
return (_jsxs("div", { className: "space-y-6 px-2 md:p-6", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(BarChart3, { size: 28, className: "text-primary-600" }), _jsx("h1", { className: "text-3xl font-bold", children: t("signup_stats.title") || "Statistiques d'inscriptions" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-12", children: [_jsx(Card, { className: " ", children: _jsxs(CardBody, { className: "gap-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium text-primary-600 dark:text-primary-400", children: t("signup_stats.total") || "Total d'inscriptions" }), _jsx(TrendingUp, { size: 20, className: "text-primary-600" })] }), _jsx("p", { className: "text-4xl font-bold text-primary-700 dark:text-primary-300", children: stats.total })] }) }), _jsx(Card, { className: " ", children: _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("span", { className: "text-sm font-medium text-secondary-600 dark:text-secondary-400", children: "Inscriptions LastBrain" }) }), _jsxs("div", { className: "flex items-end justify-between gap-2", children: [_jsx("p", { className: "text-4xl font-bold text-secondary-700 dark:text-secondary-300", children: stats.bySource.lastbrain }), _jsxs(Chip, { size: "sm", color: "secondary", variant: "flat", children: [lastbrainPercentage, "%"] })] })] }) }), _jsx(Card, { className: " ", children: _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("span", { className: "text-sm font-medium text-success-600 dark:text-success-400", children: "Inscriptions Recipe" }) }), _jsxs("div", { className: "flex items-end justify-between gap-2", children: [_jsx("p", { className: "text-4xl font-bold text-success-700 dark:text-success-300", children: stats.bySource.recipe }), _jsxs(Chip, { size: "sm", color: "success", variant: "flat", children: [recipePercentage, "%"] })] })] }) })] }), _jsxs(Tabs, { "aria-label": "Vues des statistiques", color: "primary", variant: "bordered", children: [_jsx(Tab, { title: "Par Source", children: _jsxs(Card, { className: "mt-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "R\u00E9sum\u00E9 par source" }) }), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "p-4 rounded-lg border border-secondary-800 dark:border-secondary-800", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "font-medium text-secondary-700 dark:text-secondary-300", children: "LastBrain" }), _jsxs(Chip, { size: "sm", color: "secondary", variant: "flat", children: [stats.bySource.lastbrain, " inscriptions"] })] }), _jsx("div", { className: "w-full bg-default-200 rounded-full h-2 dark:bg-default-700", children: _jsx("div", { className: "bg-secondary-500 h-2 rounded-full", style: {
|
|
42
|
+
return (_jsxs("div", { className: "mt-16 space-y-6 px-2 md:p-6", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(BarChart3, { size: 28, className: "text-primary-600" }), _jsx("h1", { className: "text-3xl font-bold", children: t("signup_stats.title") || "Statistiques d'inscriptions" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-12", children: [_jsx(Card, { className: " ", children: _jsxs(CardBody, { className: "gap-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium text-primary-600 dark:text-primary-400", children: t("signup_stats.total") || "Total d'inscriptions" }), _jsx(TrendingUp, { size: 20, className: "text-primary-600" })] }), _jsx("p", { className: "text-4xl font-bold text-primary-700 dark:text-primary-300", children: stats.total })] }) }), _jsx(Card, { className: " ", children: _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("span", { className: "text-sm font-medium text-secondary-600 dark:text-secondary-400", children: "Inscriptions LastBrain" }) }), _jsxs("div", { className: "flex items-end justify-between gap-2", children: [_jsx("p", { className: "text-4xl font-bold text-secondary-700 dark:text-secondary-300", children: stats.bySource.lastbrain }), _jsxs(Chip, { size: "sm", color: "secondary", variant: "flat", children: [lastbrainPercentage, "%"] })] })] }) }), _jsx(Card, { className: " ", children: _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("span", { className: "text-sm font-medium text-success-600 dark:text-success-400", children: "Inscriptions Recipe" }) }), _jsxs("div", { className: "flex items-end justify-between gap-2", children: [_jsx("p", { className: "text-4xl font-bold text-success-700 dark:text-success-300", children: stats.bySource.recipe }), _jsxs(Chip, { size: "sm", color: "success", variant: "flat", children: [recipePercentage, "%"] })] })] }) })] }), _jsxs(Tabs, { "aria-label": "Vues des statistiques", color: "primary", variant: "bordered", children: [_jsx(Tab, { title: "Par Source", children: _jsxs(Card, { className: "mt-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "R\u00E9sum\u00E9 par source" }) }), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "p-4 rounded-lg border border-secondary-800 dark:border-secondary-800", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "font-medium text-secondary-700 dark:text-secondary-300", children: "LastBrain" }), _jsxs(Chip, { size: "sm", color: "secondary", variant: "flat", children: [stats.bySource.lastbrain, " inscriptions"] })] }), _jsx("div", { className: "w-full bg-default-200 rounded-full h-2 dark:bg-default-700", children: _jsx("div", { className: "bg-secondary-500 h-2 rounded-full", style: {
|
|
43
43
|
width: `${(stats.bySource.lastbrain / stats.total) * 100}%`,
|
|
44
44
|
} }) }), _jsxs("p", { className: "text-xs text-default-500 mt-2", children: [lastbrainPercentage, "% du total"] })] }), _jsxs("div", { className: "p-4 rounded-lg border border-success-200 dark:border-success-800", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "font-medium text-success-700 dark:text-success-300", children: "Recipe" }), _jsxs(Chip, { size: "sm", color: "success", variant: "flat", children: [stats.bySource.recipe, " inscriptions"] })] }), _jsx("div", { className: "w-full bg-default-200 rounded-full h-2 dark:bg-default-700", children: _jsx("div", { className: "bg-success-500 h-2 rounded-full", style: {
|
|
45
45
|
width: `${(stats.bySource.recipe / stats.total) * 100}%`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-detail.d.ts","sourceRoot":"","sources":["../../../src/web/admin/user-detail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"user-detail.d.ts","sourceRoot":"","sources":["../../../src/web/admin/user-detail.tsx"],"names":[],"mappings":"AAgCA,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAkCD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,cAAmB,GACpB,EAAE,mBAAmB,2CA23BrB"}
|