@lastbrain/module-ai 2.0.12 → 2.0.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.
@@ -126,8 +126,8 @@ const buildConfig = {
126
126
  menu: {
127
127
  auth: [
128
128
  {
129
- title: "Mes Tokens",
130
- description: "Historique et balance des tokens IA",
129
+ title: "module-ai.menu.my_tokens",
130
+ description: "module-ai.menu.my_tokens_desc",
131
131
  icon: "Coins",
132
132
  path: "/auth/ai/token",
133
133
  order: 100,
@@ -137,8 +137,8 @@ const buildConfig = {
137
137
  ],
138
138
  admin: [
139
139
  {
140
- title: "Gestion Tokens",
141
- description: "Gestion des tokens utilisateurs",
140
+ title: "module-ai.menu.tokens_admin",
141
+ description: "module-ai.menu.tokens_admin_desc",
142
142
  icon: "Coins",
143
143
  path: "/admin/ai/user-token",
144
144
  order: 100,
@@ -146,8 +146,8 @@ const buildConfig = {
146
146
  shortcutDisplay: "⌘⇧G",
147
147
  },
148
148
  {
149
- title: "Packs de Tokens",
150
- description: "Gestion des packs de tokens",
149
+ title: "module-ai.menu.token_packs",
150
+ description: "module-ai.menu.token_packs_desc",
151
151
  icon: "Package",
152
152
  path: "/admin/ai/token-packs",
153
153
  order: 101,
@@ -157,7 +157,7 @@ const buildConfig = {
157
157
  userTabs: [
158
158
  {
159
159
  key: "tokens",
160
- title: "Tokens IA",
160
+ title: "module-ai.user_tabs.tokens",
161
161
  icon: "Coins",
162
162
  componentExport: "UserTokenTab",
163
163
  entryPoint: "components/admin/UserTokenTab",
@@ -4,8 +4,8 @@ import { NextRequest, NextResponse } from "next/server";
4
4
  * Create a Stripe checkout session for token purchase
5
5
  */
6
6
  export declare function POST(request: NextRequest): Promise<NextResponse<{
7
- checkout_url: any;
8
- session_id: any;
7
+ checkout_url: string;
8
+ session_id: string;
9
9
  }> | NextResponse<{
10
10
  error: any;
11
11
  }>>;
@@ -1 +1 @@
1
- {"version":3,"file":"token-checkout.d.ts","sourceRoot":"","sources":["../../../src/api/auth/token-checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMxD;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAgG9C"}
1
+ {"version":3,"file":"token-checkout.d.ts","sourceRoot":"","sources":["../../../src/api/auth/token-checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAiE9C"}
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
- import { getSupabaseServerClient, fetchInternalApi, } from "@lastbrain/core/server";
2
+ import { getSupabaseServerClient, getApiBaseUrl } from "@lastbrain/core/server";
3
+ import { createOneTimeCheckout } from "@lastbrain-labs/module-core-payment-pro/server";
3
4
  /**
4
5
  * POST /api/ai/auth/token-checkout
5
6
  * Create a Stripe checkout session for token purchase
@@ -27,49 +28,26 @@ export async function POST(request) {
27
28
  if (packError || !pack) {
28
29
  return NextResponse.json({ error: "Pack non trouvé" }, { status: 404 });
29
30
  }
30
- const baseUrl = process.env.APP_PRINCIPAL_DOMAIN ||
31
- process.env.NEXT_PUBLIC_APP_URL ||
32
- "http://localhost:3000";
33
- const successUrl = `${baseUrl}${"/cart/success"}`;
34
- const cancelUrl = `${baseUrl}${"/cart/cancel"}`;
35
- // Create checkout session via payment API
36
- const checkoutResponse = await fetchInternalApi("/api/auth/payment/checkout", {
37
- method: "POST",
38
- headers: {
39
- "Content-Type": "application/json",
40
- },
41
- body: JSON.stringify({
42
- items: [
43
- {
44
- name: pack.name,
45
- description: pack.description || `${pack.tokens} tokens`,
46
- price_cents: pack.price_cents,
47
- quantity: 1,
48
- },
49
- ],
50
- currency: pack.currency,
51
- mode: "one_time",
52
- success_url: successUrl,
53
- cancel_url: cancelUrl,
54
- metadata: {
55
- purpose: "token_purchase",
56
- module: "@lastbrain-labs/module-ai",
57
- token_pack_id: pack.id,
58
- tokens_amount: pack.tokens.toString(),
59
- user_id: user.id,
60
- },
61
- }),
62
- }, request);
63
- if (!checkoutResponse.ok) {
64
- const error = await checkoutResponse.json();
65
- throw new Error(error.error || "Erreur lors de la création du checkout");
66
- }
67
- const checkoutData = await checkoutResponse.json();
68
- const url = checkoutData.url || checkoutData.checkoutUrl || checkoutData.checkout_url;
69
- const sessionId = checkoutData.session_id || checkoutData.sessionId;
31
+ // Build absolute URLs with a fully-qualified base (works on Vercel, custom domains, local)
32
+ const baseUrl = getApiBaseUrl();
33
+ const successUrl = `${baseUrl.replace(/\/$/, "")}${"/cart/success"}`;
34
+ const cancelUrl = `${baseUrl.replace(/\/$/, "")}${"/cart/cancel"}`;
35
+ // Use central payment service directly instead of fetching internally
36
+ const checkoutResult = await createOneTimeCheckout({
37
+ purpose: "token",
38
+ module: "module-ai",
39
+ mode: "one_time",
40
+ owner_id: user.id,
41
+ user_id: user.id,
42
+ resourceType: "token_packs",
43
+ resourceId: pack.id,
44
+ successPath: "/cart/success",
45
+ cancelPath: "/cart/cancel",
46
+ description: `${pack.tokens} tokens IA`,
47
+ });
70
48
  return NextResponse.json({
71
- checkout_url: url,
72
- session_id: sessionId,
49
+ checkout_url: checkoutResult.url,
50
+ session_id: checkoutResult.sessionId,
73
51
  }, { status: 200 });
74
52
  }
75
53
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"UserTokenTab.d.ts","sourceRoot":"","sources":["../../../src/components/admin/UserTokenTab.tsx"],"names":[],"mappings":"AAeA,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA6OzD"}
1
+ {"version":3,"file":"UserTokenTab.d.ts","sourceRoot":"","sources":["../../../src/components/admin/UserTokenTab.tsx"],"names":[],"mappings":"AAgBA,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA0RzD"}
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
4
  import { Card, CardBody, CardHeader, Button, Input, Spinner, addToast, Chip, } from "@lastbrain/ui";
5
5
  import { Plus, Minus, History, Coins } from "lucide-react";
6
+ import { useModuleTranslation } from "@lastbrain/core";
6
7
  export function UserTokenTab({ userId }) {
8
+ const t = useModuleTranslation("ai");
7
9
  const [loading, setLoading] = useState(true);
8
10
  const [balance, setBalance] = useState(0);
9
11
  const [ledger, setLedger] = useState([]);
@@ -16,7 +18,8 @@ export function UserTokenTab({ userId }) {
16
18
  setLoading(true);
17
19
  const res = await fetch(`/api/ai/admin/user-token/${userId}`);
18
20
  if (!res.ok)
19
- throw new Error("Échec chargement détails tokens");
21
+ throw new Error(t("admin.user_token.error.loading") ||
22
+ "Échec chargement détails tokens");
20
23
  const data = await res.json();
21
24
  const currentBalance = data.balance || 0;
22
25
  setBalance(currentBalance);
@@ -33,16 +36,19 @@ export function UserTokenTab({ userId }) {
33
36
  display_description: entry.meta?.reason ||
34
37
  entry.description ||
35
38
  entry.type ||
39
+ t("admin.user_token.transaction_label") ||
36
40
  "Transaction",
37
41
  };
38
42
  });
39
43
  setLedger(computed);
40
44
  }
41
45
  catch (error) {
42
- console.error("Erreur lors du chargement des tokens:", error);
46
+ console.error(t("admin.user_token.error.loading_tokens") ||
47
+ "Erreur lors du chargement des tokens:", error);
43
48
  addToast({
44
49
  color: "danger",
45
- title: "Erreur lors du chargement des données",
50
+ title: t("admin.user_token.error.loading_data") ||
51
+ "Erreur lors du chargement des données",
46
52
  });
47
53
  }
48
54
  finally {
@@ -58,14 +64,15 @@ export function UserTokenTab({ userId }) {
58
64
  if (isNaN(amount) || amount <= 0) {
59
65
  addToast({
60
66
  color: "danger",
61
- title: "Montant invalide",
67
+ title: t("admin.user_token.error.invalid_amount") || "Montant invalide",
62
68
  });
63
69
  return;
64
70
  }
65
71
  if (!adjustmentDescription.trim()) {
66
72
  addToast({
67
73
  color: "danger",
68
- title: "Description requise",
74
+ title: t("admin.user_token.error.description_required") ||
75
+ "Description requise",
69
76
  });
70
77
  return;
71
78
  }
@@ -82,11 +89,14 @@ export function UserTokenTab({ userId }) {
82
89
  }),
83
90
  });
84
91
  if (!response.ok) {
85
- throw new Error("Échec de l'ajustement");
92
+ throw new Error(t("admin.user_token.error.adjustment_failed") ||
93
+ "Échec de l'ajustement");
86
94
  }
87
95
  addToast({
88
96
  color: "success",
89
- title: type === "credit" ? "Tokens crédités" : "Tokens débités",
97
+ title: type === "credit"
98
+ ? t("admin.user_token.success.credited") || "Tokens crédités"
99
+ : t("admin.user_token.success.debited") || "Tokens débités",
90
100
  });
91
101
  // Reset
92
102
  setAdjustmentAmount("");
@@ -95,10 +105,11 @@ export function UserTokenTab({ userId }) {
95
105
  await fetchTokenData();
96
106
  }
97
107
  catch (error) {
98
- console.error("Erreur:", error);
108
+ console.error(t("admin.user_token.error.adjustment") || "Erreur:", error);
99
109
  addToast({
100
110
  color: "danger",
101
- title: "Erreur lors de l'ajustement",
111
+ title: t("admin.user_token.error.adjustment_error") ||
112
+ "Erreur lors de l'ajustement",
102
113
  });
103
114
  }
104
115
  finally {
@@ -108,5 +119,9 @@ export function UserTokenTab({ userId }) {
108
119
  if (loading) {
109
120
  return (_jsx("div", { className: "flex justify-center items-center min-h-64", children: _jsx(Spinner, { size: "lg" }) }));
110
121
  }
111
- return (_jsxs("div", { className: "w-full space-y-6 mt-4", children: [_jsxs("div", { className: "w-full flex flex-col md:flex-row gap-6", children: [_jsxs(Card, { className: "min-w-72", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Coins, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: "Solde de tokens" })] }) }), _jsx(CardBody, { className: "flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-4xl font-bold text-primary", children: balance.toLocaleString() }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children: "tokens disponibles" })] }) })] }), _jsxs(Card, { className: "flex-1 ", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Ajuster le solde" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx(Input, { type: "number", label: "Montant", placeholder: "1000", value: adjustmentAmount, onChange: (e) => setAdjustmentAmount(e.target.value), min: "1", endContent: _jsx("span", { className: "text-sm text-gray-500", children: "tokens" }) }), _jsx(Input, { label: "Description", placeholder: "Raison de l'ajustement...", value: adjustmentDescription, onChange: (e) => setAdjustmentDescription(e.target.value), maxLength: 200 }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { color: "success", startContent: _jsx(Plus, { size: 16 }), onPress: () => handleAdjustment("credit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: "Cr\u00E9diter" }), _jsx(Button, { color: "danger", variant: "bordered", startContent: _jsx(Minus, { size: 16 }), onPress: () => handleAdjustment("debit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: "D\u00E9biter" })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(History, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: "Historique des transactions" })] }) }), _jsx(CardBody, { children: ledger.length === 0 ? (_jsx("p", { className: "text-center text-gray-500 py-4", children: "Aucune transaction" })) : (_jsx("div", { className: "space-y-2", children: ledger.map((entry) => (_jsxs("div", { className: "flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "text-sm font-medium", children: entry.display_description }), _jsx("p", { className: "text-xs text-gray-500", children: new Date(entry.created_at).toLocaleString() })] }), _jsxs("div", { className: "text-right", children: [_jsxs(Chip, { size: "sm", color: entry.display_amount > 0 ? "success" : "danger", variant: "flat", children: [entry.display_amount > 0 ? "+" : "", entry.display_amount.toLocaleString()] }), _jsxs("p", { className: "text-xs text-gray-500 mt-1", children: ["Solde: ", entry.balance_after.toLocaleString()] })] })] }, entry.id))) })) })] })] }));
122
+ return (_jsxs("div", { className: "w-full space-y-6 mt-4", children: [_jsxs("div", { className: "w-full flex flex-col md:flex-row gap-6", children: [_jsxs(Card, { className: "min-w-72", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Coins, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("admin.user_token.balance.title") || "Solde de tokens" })] }) }), _jsx(CardBody, { className: "flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-4xl font-bold text-primary", children: balance.toLocaleString() }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children: t("admin.user_token.balance.available") ||
123
+ "tokens disponibles" })] }) })] }), _jsxs(Card, { className: "flex-1 ", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("admin.user_token.adjustment.title") || "Ajuster le solde" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx(Input, { type: "number", label: t("admin.user_token.adjustment.amount") || "Montant", placeholder: t("admin.user_token.adjustment.amount_placeholder") || "1000", value: adjustmentAmount, onChange: (e) => setAdjustmentAmount(e.target.value), min: "1", endContent: _jsx("span", { className: "text-sm text-gray-500", children: t("chip.tokens") || "tokens" }) }), _jsx(Input, { label: t("admin.user_token.adjustment.description") || "Description", placeholder: t("admin.user_token.adjustment.description_placeholder") ||
124
+ "Raison de l'ajustement...", value: adjustmentDescription, onChange: (e) => setAdjustmentDescription(e.target.value), maxLength: 200 }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { color: "success", startContent: _jsx(Plus, { size: 16 }), onPress: () => handleAdjustment("credit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: t("admin.user_token.adjustment.credit_button") || "Créditer" }), _jsx(Button, { color: "danger", variant: "bordered", startContent: _jsx(Minus, { size: 16 }), onPress: () => handleAdjustment("debit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: t("admin.user_token.adjustment.debit_button") || "Débiter" })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(History, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("admin.user_token.history.title") ||
125
+ "Historique des transactions" })] }) }), _jsx(CardBody, { children: ledger.length === 0 ? (_jsx("p", { className: "text-center text-gray-500 py-4", children: t("admin.user_token.history.no_transactions") ||
126
+ "Aucune transaction" })) : (_jsx("div", { className: "space-y-2", children: ledger.map((entry) => (_jsxs("div", { className: "flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "text-sm font-medium", children: entry.display_description }), _jsx("p", { className: "text-xs text-gray-500", children: new Date(entry.created_at).toLocaleString() })] }), _jsxs("div", { className: "text-right", children: [_jsxs(Chip, { size: "sm", color: entry.display_amount > 0 ? "success" : "danger", variant: "flat", children: [entry.display_amount > 0 ? "+" : "", entry.display_amount.toLocaleString()] }), _jsxs("p", { className: "text-xs text-gray-500 mt-1", children: [t("admin.user_token.history.balance_label") || "Solde", ":", " ", entry.balance_after.toLocaleString()] })] })] }, entry.id))) })) })] })] }));
112
127
  }
package/dist/server.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface TokenLedgerEntry {
17
17
  amount: number;
18
18
  model?: string;
19
19
  prompt?: string;
20
- meta: Record<string, any>;
20
+ meta?: Record<string, any>;
21
21
  created_by?: string;
22
22
  created_at: string;
23
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,UAAU,GAAG,MAAM,GAAG,QAAiB,EAC7C,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC,CA+B/B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAoD/B;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBrE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiB7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM;;;;;;GA4CjD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU;;;KAiBnB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,UAAU,GAAG,MAAM,GAAG,QAAiB,EAC7C,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC,CA+B/B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAoD/B;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBrE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiB7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM;;;;;;GA4CjD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU;;;KAiBnB"}
package/dist/server.js CHANGED
@@ -124,7 +124,7 @@ export async function getTokenHistory(userId, limit = 50, offset = 0) {
124
124
  try {
125
125
  const { data, error } = await supabase
126
126
  .from("user_token_ledger")
127
- .select("*")
127
+ .select("amount,created_at,created_by,id,model,owner_id,prompt,ts,type")
128
128
  .eq("owner_id", userId)
129
129
  .order("ts", { ascending: false })
130
130
  .range(offset, offset + limit - 1);
@@ -1 +1 @@
1
- {"version":3,"file":"AdminTokenPacksPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/AdminTokenPacksPage.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,mBAAmB,4CAyTlC"}
1
+ {"version":3,"file":"AdminTokenPacksPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/AdminTokenPacksPage.tsx"],"names":[],"mappings":"AA0CA,wBAAgB,mBAAmB,4CAkXlC"}
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
4
  import { Card, CardBody, Button, Input, Textarea, Switch, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Spinner, addToast, Chip, } from "@lastbrain/ui";
5
5
  import { Plus, Edit, Trash2 } from "lucide-react";
6
+ import { useModuleTranslation } from "@lastbrain/core";
6
7
  export function AdminTokenPacksPage() {
8
+ const t = useModuleTranslation("ai");
7
9
  const [packs, setPacks] = useState([]);
8
10
  const [loading, setLoading] = useState(true);
9
11
  const [isModalOpen, setIsModalOpen] = useState(false);
@@ -26,7 +28,7 @@ export function AdminTokenPacksPage() {
26
28
  setLoading(true);
27
29
  const response = await fetch("/api/ai/admin/token-packs");
28
30
  if (!response.ok)
29
- throw new Error("Erreur lors du chargement");
31
+ throw new Error(t("admin.packs.error.loading") || "Erreur lors du chargement");
30
32
  const result = await response.json();
31
33
  setPacks(result.data || []);
32
34
  }
@@ -77,10 +79,12 @@ export function AdminTokenPacksPage() {
77
79
  body: JSON.stringify(formData),
78
80
  });
79
81
  if (!response.ok)
80
- throw new Error("Erreur lors de la sauvegarde");
82
+ throw new Error(t("admin.packs.error.saving") || "Erreur lors de la sauvegarde");
81
83
  addToast({
82
84
  color: "success",
83
- title: editingPack ? "Pack mis à jour" : "Pack créé",
85
+ title: editingPack
86
+ ? t("admin.packs.success.updated") || "Pack mis à jour"
87
+ : t("admin.packs.success.created") || "Pack créé",
84
88
  });
85
89
  setIsModalOpen(false);
86
90
  fetchPacks();
@@ -90,15 +94,19 @@ export function AdminTokenPacksPage() {
90
94
  }
91
95
  };
92
96
  const handleDelete = async (id) => {
93
- if (!confirm("Êtes-vous sûr de vouloir supprimer ce pack ?"))
97
+ if (!confirm(t("admin.packs.confirm.delete") ||
98
+ "Êtes-vous sûr de vouloir supprimer ce pack ?"))
94
99
  return;
95
100
  try {
96
101
  const response = await fetch(`/api/ai/admin/token-packs/${id}`, {
97
102
  method: "DELETE",
98
103
  });
99
104
  if (!response.ok)
100
- throw new Error("Erreur lors de la suppression");
101
- addToast({ color: "success", title: "Pack supprimé" });
105
+ throw new Error(t("admin.packs.error.deleting") || "Erreur lors de la suppression");
106
+ addToast({
107
+ color: "success",
108
+ title: t("admin.packs.success.deleted") || "Pack supprimé",
109
+ });
102
110
  fetchPacks();
103
111
  }
104
112
  catch (error) {
@@ -114,14 +122,23 @@ export function AdminTokenPacksPage() {
114
122
  if (loading) {
115
123
  return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
116
124
  }
117
- return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "flex justify-between items-center mb-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: "Token Packs" }), _jsx("p", { className: "text-gray-500", children: "Gestion des packs de tokens disponibles \u00E0 l'achat" })] }), _jsx(Button, { color: "primary", onPress: handleCreate, startContent: _jsx(Plus, { size: 20 }), children: "Nouveau pack" })] }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs(Table, { "aria-label": "Token packs", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "NOM" }), _jsx(TableColumn, { children: "TOKENS" }), _jsx(TableColumn, { children: "PRIX" }), _jsx(TableColumn, { children: "STATUT" }), _jsx(TableColumn, { children: "ORDRE" }), _jsx(TableColumn, { children: "ACTIONS" })] }), _jsx(TableBody, { children: packs.map((pack) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500", children: pack.description }))] }) }), _jsx(TableCell, { children: _jsxs(Chip, { size: "sm", variant: "flat", color: "primary", children: [pack.tokens.toLocaleString(), " tokens"] }) }), _jsx(TableCell, { children: _jsx("span", { className: "font-semibold", children: formatPrice(pack.price_cents, pack.currency) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: pack.is_active ? "success" : "default", children: pack.is_active ? "Actif" : "Inactif" }) }), _jsx(TableCell, { children: pack.sort_order }), _jsx(TableCell, { children: _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "light", onPress: () => handleEdit(pack), children: _jsx(Edit, { size: 16 }) }), _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", color: "danger", onPress: () => handleDelete(pack.id), children: _jsx(Trash2, { size: 16 }) })] }) })] }, pack.id))) })] }) }) }), _jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: editingPack ? "Modifier le pack" : "Nouveau pack" }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: "Nom", placeholder: "Pack Starter", value: formData.name, onChange: (e) => setFormData({ ...formData, name: e.target.value }) }), _jsx(Textarea, { label: "Description", placeholder: "Description du pack", value: formData.description, onChange: (e) => setFormData({ ...formData, description: e.target.value }) }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Nombre de tokens", type: "number", value: formData.tokens.toString(), onChange: (e) => setFormData({
125
+ return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "flex justify-between items-center mb-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("admin.packs.page.title") || "Token Packs" }), _jsx("p", { className: "text-gray-500", children: t("admin.packs.page.description") ||
126
+ "Gestion des packs de tokens disponibles à l'achat" })] }), _jsx(Button, { color: "primary", onPress: handleCreate, startContent: _jsx(Plus, { size: 20 }), children: t("admin.packs.button.new") || "Nouveau pack" })] }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs(Table, { "aria-label": t("admin.packs.table.aria_label") || "Token packs", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: t("admin.packs.table.column_name") || "NOM" }), _jsx(TableColumn, { children: t("admin.packs.table.column_tokens") || "TOKENS" }), _jsx(TableColumn, { children: t("admin.packs.table.column_price") || "PRIX" }), _jsx(TableColumn, { children: t("admin.packs.table.column_status") || "STATUT" }), _jsx(TableColumn, { children: t("admin.packs.table.column_order") || "ORDRE" }), _jsx(TableColumn, { children: t("admin.packs.table.column_actions") || "ACTIONS" })] }), _jsx(TableBody, { children: packs.map((pack) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500", children: pack.description }))] }) }), _jsx(TableCell, { children: _jsxs(Chip, { size: "sm", variant: "flat", color: "primary", children: [pack.tokens.toLocaleString(), " ", t("chip.tokens") || "tokens"] }) }), _jsx(TableCell, { children: _jsx("span", { className: "font-semibold", children: formatPrice(pack.price_cents, pack.currency) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: pack.is_active ? "success" : "default", children: pack.is_active
127
+ ? t("admin.packs.status.active") || "Actif"
128
+ : t("admin.packs.status.inactive") || "Inactif" }) }), _jsx(TableCell, { children: pack.sort_order }), _jsx(TableCell, { children: _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "light", onPress: () => handleEdit(pack), children: _jsx(Edit, { size: 16 }) }), _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", color: "danger", onPress: () => handleDelete(pack.id), children: _jsx(Trash2, { size: 16 }) })] }) })] }, pack.id))) })] }) }) }), _jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: editingPack
129
+ ? t("admin.packs.modal.edit_title") || "Modifier le pack"
130
+ : t("admin.packs.modal.new_title") || "Nouveau pack" }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: t("admin.packs.form.name") || "Nom", placeholder: t("admin.packs.form.name_placeholder") || "Pack Starter", value: formData.name, onChange: (e) => setFormData({ ...formData, name: e.target.value }) }), _jsx(Textarea, { label: t("admin.packs.form.description") || "Description", placeholder: t("admin.packs.form.description_placeholder") ||
131
+ "Description du pack", value: formData.description, onChange: (e) => setFormData({ ...formData, description: e.target.value }) }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: t("admin.packs.form.tokens") || "Nombre de tokens", type: "number", value: formData.tokens.toString(), onChange: (e) => setFormData({
118
132
  ...formData,
119
133
  tokens: parseInt(e.target.value) || 0,
120
- }) }), _jsx(Input, { label: "Prix (centimes)", type: "number", value: formData.price_cents.toString(), onChange: (e) => setFormData({
134
+ }) }), _jsx(Input, { label: t("admin.packs.form.price") || "Prix (centimes)", type: "number", value: formData.price_cents.toString(), onChange: (e) => setFormData({
121
135
  ...formData,
122
136
  price_cents: parseInt(e.target.value) || 0,
123
- }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Devise", value: formData.currency, onChange: (e) => setFormData({ ...formData, currency: e.target.value }) }), _jsx(Input, { label: "Ordre d'affichage", type: "number", value: formData.sort_order.toString(), onChange: (e) => setFormData({
137
+ }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: t("admin.packs.form.currency") || "Devise", value: formData.currency, onChange: (e) => setFormData({ ...formData, currency: e.target.value }) }), _jsx(Input, { label: t("admin.packs.form.sort_order") || "Ordre d'affichage", type: "number", value: formData.sort_order.toString(), onChange: (e) => setFormData({
124
138
  ...formData,
125
139
  sort_order: parseInt(e.target.value) || 0,
126
- }) })] }), _jsx(Input, { label: "Stripe Price ID (optionnel)", placeholder: "price_...", value: formData.stripe_price_id, onChange: (e) => setFormData({ ...formData, stripe_price_id: e.target.value }) }), _jsx(Switch, { isSelected: formData.is_active, onValueChange: (checked) => setFormData({ ...formData, is_active: checked }), children: "Pack actif" })] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onPress: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onPress: handleSave, children: editingPack ? "Mettre à jour" : "Créer" })] })] }) })] }));
140
+ }) })] }), _jsx(Input, { label: t("admin.packs.form.stripe_id") ||
141
+ "Stripe Price ID (optionnel)", placeholder: t("admin.packs.form.stripe_id_placeholder") || "price_...", value: formData.stripe_price_id, onChange: (e) => setFormData({ ...formData, stripe_price_id: e.target.value }) }), _jsx(Switch, { isSelected: formData.is_active, onValueChange: (checked) => setFormData({ ...formData, is_active: checked }), children: t("admin.packs.form.active") || "Pack actif" })] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onPress: () => setIsModalOpen(false), children: t("admin.packs.button.cancel") || "Annuler" }), _jsx(Button, { color: "primary", onPress: handleSave, children: editingPack
142
+ ? t("admin.packs.button.update") || "Mettre à jour"
143
+ : t("admin.packs.button.create") || "Créer" })] })] }) })] }));
127
144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AA+DA,wBAAgB,aAAa,4CAuY5B"}
1
+ {"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,aAAa,4CAwc5B"}
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback } from "react";
4
4
  import { Card, CardBody, CardHeader, Spinner, Chip, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Avatar, Input, addToast, } from "@lastbrain/ui";
5
5
  import { Coins, TrendingUp, TrendingDown, Calendar, Search, Users, AlertCircle, } from "lucide-react";
6
+ import { useModuleTranslation } from "@lastbrain/core";
6
7
  export function UserTokenPage() {
8
+ const t = useModuleTranslation("ai");
7
9
  const [loading, setLoading] = useState(true);
8
10
  const [users, setUsers] = useState([]);
9
11
  const [transactions, setTransactions] = useState([]);
@@ -22,7 +24,8 @@ export function UserTokenPage() {
22
24
  // Récupérer toutes les données tokens de tous les utilisateurs
23
25
  const response = await fetch("/api/ai/admin/user-token");
24
26
  if (!response.ok) {
25
- throw new Error("Erreur lors du chargement des données");
27
+ throw new Error(t("admin.tokens.error.loading") ||
28
+ "Erreur lors du chargement des données");
26
29
  }
27
30
  const data = await response.json();
28
31
  // Traiter les données utilisateurs
@@ -50,10 +53,11 @@ export function UserTokenPage() {
50
53
  });
51
54
  }
52
55
  catch (error) {
53
- console.error("Erreur:", error);
56
+ console.error(t("admin.tokens.error.loading") || "Erreur:", error);
54
57
  addToast({
55
58
  color: "danger",
56
- title: "Erreur lors du chargement des données",
59
+ title: t("admin.tokens.error.loading") ||
60
+ "Erreur lors du chargement des données",
57
61
  });
58
62
  }
59
63
  finally {
@@ -74,10 +78,10 @@ export function UserTokenPage() {
74
78
  };
75
79
  const getTypeLabel = (type) => {
76
80
  const labels = {
77
- purchase: "Achat",
78
- gift: "Cadeau",
79
- use: "Utilisation",
80
- adjust: "Ajustement",
81
+ purchase: t("tokens.type.purchase") || "Achat",
82
+ gift: t("tokens.type.gift") || "Cadeau",
83
+ use: t("tokens.type.use") || "Utilisation",
84
+ adjust: t("tokens.type.adjust") || "Ajustement",
81
85
  };
82
86
  return labels[type] || type;
83
87
  };
@@ -96,12 +100,19 @@ export function UserTokenPage() {
96
100
  if (loading) {
97
101
  return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
98
102
  }
99
- return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: "Gestion des Tokens IA" }), _jsx("p", { className: "text-gray-500", children: "Vue d'ensemble de la consommation des tokens par utilisateur" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 mb-6", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: "Solde total" })] }), _jsx("p", { className: "text-3xl font-bold text-primary", children: monthlyStats?.totalBalance.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "tokens en circulation" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: "Cr\u00E9dits du mois" })] }), _jsx("p", { className: "text-3xl font-bold text-success", children: monthlyStats?.totalCredit.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "tokens ajout\u00E9s" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: "D\u00E9bits du mois" })] }), _jsx("p", { className: "text-3xl font-bold text-danger", children: monthlyStats?.totalDebit.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "tokens consomm\u00E9s" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Users, { size: 24, className: "text-warning" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: "Utilisateurs actifs" })] }), _jsx("p", { className: "text-3xl font-bold text-warning", children: monthlyStats?.activeUsers }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "ce mois-ci" })] }) })] }), _jsx(Card, { className: "mb-6", children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: "P\u00E9riode d'analyse" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": "S\u00E9lectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
103
+ return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("admin.tokens.page.title") || "Gestion des Tokens IA" }), _jsx("p", { className: "text-gray-500", children: t("admin.tokens.page.description") ||
104
+ "Vue d'ensemble de la consommation des tokens par utilisateur" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 mb-6", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.total_balance") || "Solde total" })] }), _jsx("p", { className: "text-3xl font-bold text-primary", children: monthlyStats?.totalBalance.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.in_circulation") ||
105
+ "tokens en circulation" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.monthly_credits") || "Crédits du mois" })] }), _jsx("p", { className: "text-3xl font-bold text-success", children: monthlyStats?.totalCredit.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.tokens_added") || "tokens ajoutés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.monthly_debits") || "Débits du mois" })] }), _jsx("p", { className: "text-3xl font-bold text-danger", children: monthlyStats?.totalDebit.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.tokens_consumed") || "tokens consommés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Users, { size: 24, className: "text-warning" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.active_users") || "Utilisateurs actifs" })] }), _jsx("p", { className: "text-3xl font-bold text-warning", children: monthlyStats?.activeUsers }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.this_month") || "ce mois-ci" })] }) })] }), _jsx(Card, { className: "mb-6", children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("admin.tokens.period.title") || "Période d'analyse" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": t("admin.tokens.period.select") || "Sélectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
100
106
  month: "long",
101
107
  year: "numeric",
102
- }) }, month))) })] }) }) }), _jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsx("h3", { className: "text-lg font-semibold", children: "Soldes par utilisateur" }), _jsx(Input, { size: "sm", placeholder: "Rechercher un utilisateur...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), startContent: _jsx(Search, { size: 16 }), className: "w-64" })] }) }), _jsx(CardBody, { children: filteredUsers.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Users, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: "Aucun utilisateur trouv\u00E9" })] })) : (_jsxs(Table, { "aria-label": "Soldes par utilisateur", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "UTILISATEUR" }), _jsx(TableColumn, { children: "SOLDE ACTUEL" }), _jsx(TableColumn, { children: "TOTAL AJOUT\u00C9" }), _jsx(TableColumn, { children: "TOTAL UTILIS\u00C9" }), _jsx(TableColumn, { children: "DERNI\u00C8RE ACTIVIT\u00C9" })] }), _jsx(TableBody, { children: filteredUsers.map((user) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { src: user.avatarUrl, name: user.fullName || user.email, size: "sm" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: user.fullName || user.email }), user.fullName && (_jsx("p", { className: "text-xs text-gray-500", children: user.email }))] })] }) }), _jsx(TableCell, { children: _jsx("span", { className: "font-semibold text-primary", children: user.balance.toLocaleString() }) }), _jsx(TableCell, { children: _jsxs("span", { className: "text-success", children: ["+", user.totalAdded.toLocaleString()] }) }), _jsx(TableCell, { children: _jsxs("span", { className: "text-danger", children: ["-", user.totalUsed.toLocaleString()] }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: user.lastActivity
108
+ }) }, month))) })] }) }) }), _jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsx("h3", { className: "text-lg font-semibold", children: t("admin.tokens.users.title") || "Soldes par utilisateur" }), _jsx(Input, { size: "sm", placeholder: t("admin.tokens.users.search") || "Rechercher un utilisateur...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), startContent: _jsx(Search, { size: 16 }), className: "w-64" })] }) }), _jsx(CardBody, { children: filteredUsers.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Users, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("admin.tokens.users.no_users") || "Aucun utilisateur trouvé" })] })) : (_jsxs(Table, { "aria-label": t("admin.tokens.users.aria_label") || "Soldes par utilisateur", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: t("admin.tokens.users.column_user") || "UTILISATEUR" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_balance") || "SOLDE ACTUEL" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_added") || "TOTAL AJOUTÉ" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_used") || "TOTAL UTILISÉ" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_activity") ||
109
+ "DERNIÈRE ACTIVITÉ" })] }), _jsx(TableBody, { children: filteredUsers.map((user) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { src: user.avatarUrl, name: user.fullName || user.email, size: "sm" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: user.fullName || user.email }), user.fullName && (_jsx("p", { className: "text-xs text-gray-500", children: user.email }))] })] }) }), _jsx(TableCell, { children: _jsx("span", { className: "font-semibold text-primary", children: user.balance.toLocaleString() }) }), _jsx(TableCell, { children: _jsxs("span", { className: "text-success", children: ["+", user.totalAdded.toLocaleString()] }) }), _jsx(TableCell, { children: _jsxs("span", { className: "text-danger", children: ["-", user.totalUsed.toLocaleString()] }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: user.lastActivity
103
110
  ? formatDate(user.lastActivity)
104
- : "-" }) })] }, user.userId))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Transactions du mois s\u00E9lectionn\u00E9" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(AlertCircle, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: "Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "DATE" }), _jsx(TableColumn, { children: "UTILISATEUR" }), _jsx(TableColumn, { children: "TYPE" }), _jsx(TableColumn, { children: "DESCRIPTION" }), _jsx(TableColumn, { children: "MONTANT" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Avatar, { src: transaction.avatarUrl, name: transaction.fullName || transaction.email, size: "sm" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: transaction.fullName || transaction.email }), transaction.fullName && (_jsx("p", { className: "text-xs text-gray-500", children: transaction.email }))] })] }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type), children: getTypeLabel(transaction.type) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.amount > 0
111
+ : "-" }) })] }, user.userId))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("admin.tokens.transactions.title") ||
112
+ "Transactions du mois sélectionné" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(AlertCircle, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("admin.tokens.transactions.no_transactions") ||
113
+ "Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": t("admin.tokens.transactions.aria_label") ||
114
+ "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: t("admin.tokens.transactions.column_date") || "DATE" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_user") || "UTILISATEUR" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_type") || "TYPE" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_description") ||
115
+ "DESCRIPTION" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_amount") || "MONTANT" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Avatar, { src: transaction.avatarUrl, name: transaction.fullName || transaction.email, size: "sm" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: transaction.fullName || transaction.email }), transaction.fullName && (_jsx("p", { className: "text-xs text-gray-500", children: transaction.email }))] })] }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type), children: getTypeLabel(transaction.type) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.amount > 0
105
116
  ? "text-success"
106
117
  : "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", transaction.amount.toLocaleString()] }) })] }, transaction.id))) })] })) })] })] }));
107
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,SAAS,4CAobxB"}
1
+ {"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,SAAS,4CAkhBxB"}