@lastbrain/ai-ui-react 1.0.68 → 1.0.70

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.
Files changed (76) hide show
  1. package/dist/components/AiChipLabel.d.ts +8 -3
  2. package/dist/components/AiChipLabel.d.ts.map +1 -1
  3. package/dist/components/AiChipLabel.js +23 -70
  4. package/dist/components/AiContextButton.d.ts +10 -2
  5. package/dist/components/AiContextButton.d.ts.map +1 -1
  6. package/dist/components/AiContextButton.js +73 -291
  7. package/dist/components/AiImageButton.d.ts +5 -1
  8. package/dist/components/AiImageButton.d.ts.map +1 -1
  9. package/dist/components/AiImageButton.js +6 -142
  10. package/dist/components/AiInput.d.ts +5 -3
  11. package/dist/components/AiInput.d.ts.map +1 -1
  12. package/dist/components/AiInput.js +13 -25
  13. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  14. package/dist/components/AiPromptPanel.js +64 -212
  15. package/dist/components/AiSelect.d.ts +5 -3
  16. package/dist/components/AiSelect.d.ts.map +1 -1
  17. package/dist/components/AiSelect.js +21 -30
  18. package/dist/components/AiStatusButton.d.ts +4 -1
  19. package/dist/components/AiStatusButton.d.ts.map +1 -1
  20. package/dist/components/AiStatusButton.js +211 -676
  21. package/dist/components/AiTextarea.d.ts +4 -2
  22. package/dist/components/AiTextarea.d.ts.map +1 -1
  23. package/dist/components/AiTextarea.js +14 -26
  24. package/dist/components/LBApiKeySelector.d.ts.map +1 -1
  25. package/dist/components/LBApiKeySelector.js +5 -166
  26. package/dist/components/LBConnectButton.d.ts +4 -7
  27. package/dist/components/LBConnectButton.d.ts.map +1 -1
  28. package/dist/components/LBConnectButton.js +17 -86
  29. package/dist/components/LBSigninModal.d.ts +1 -1
  30. package/dist/components/LBSigninModal.d.ts.map +1 -1
  31. package/dist/components/LBSigninModal.js +42 -320
  32. package/dist/context/LBAuthProvider.d.ts +35 -3
  33. package/dist/context/LBAuthProvider.d.ts.map +1 -1
  34. package/dist/context/LBAuthProvider.js +2 -0
  35. package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
  36. package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
  37. package/dist/examples/AiUiPremiumShowcase.js +15 -0
  38. package/dist/hooks/useAiModels.d.ts.map +1 -1
  39. package/dist/hooks/useModelManagement.d.ts.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +2 -0
  43. package/dist/styles/inline.d.ts +1 -0
  44. package/dist/styles/inline.d.ts.map +1 -1
  45. package/dist/styles/inline.js +25 -129
  46. package/dist/styles.css +1268 -369
  47. package/dist/types.d.ts +3 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/utils/errorHandler.d.ts +2 -2
  50. package/dist/utils/errorHandler.d.ts.map +1 -1
  51. package/dist/utils/errorHandler.js +8 -1
  52. package/dist/utils/modelManagement.d.ts +13 -10
  53. package/dist/utils/modelManagement.d.ts.map +1 -1
  54. package/dist/utils/modelManagement.js +19 -2
  55. package/package.json +2 -2
  56. package/src/components/AiChipLabel.tsx +68 -101
  57. package/src/components/AiContextButton.tsx +142 -413
  58. package/src/components/AiImageButton.tsx +29 -190
  59. package/src/components/AiInput.tsx +49 -74
  60. package/src/components/AiPromptPanel.tsx +81 -260
  61. package/src/components/AiSelect.tsx +61 -69
  62. package/src/components/AiStatusButton.tsx +496 -1327
  63. package/src/components/AiTextarea.tsx +50 -63
  64. package/src/components/LBApiKeySelector.tsx +93 -271
  65. package/src/components/LBConnectButton.tsx +39 -336
  66. package/src/components/LBSigninModal.tsx +141 -472
  67. package/src/context/LBAuthProvider.tsx +45 -6
  68. package/src/examples/AiUiPremiumShowcase.tsx +94 -0
  69. package/src/hooks/useAiModels.ts +2 -1
  70. package/src/hooks/useModelManagement.ts +2 -1
  71. package/src/index.ts +3 -0
  72. package/src/styles/inline.ts +27 -148
  73. package/src/styles.css +1268 -369
  74. package/src/types.ts +3 -0
  75. package/src/utils/errorHandler.ts +16 -3
  76. package/src/utils/modelManagement.ts +53 -15
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { AlertCircle, Loader2, Lock, Mail, Sparkles, X } from "lucide-react";
4
6
  import { useLB } from "../context/LBAuthProvider";
5
7
  import { LBApiKeySelector } from "./LBApiKeySelector";
6
- import { Mail, Lock, Sparkles, X, Loader2, AlertCircle } from "lucide-react";
7
8
 
8
9
  export interface LBSigninModalProps {
9
10
  isOpen: boolean;
@@ -11,62 +12,63 @@ export interface LBSigninModalProps {
11
12
  }
12
13
 
13
14
  export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
14
- // React Hooks doivent être appelés avant toute condition ou return
15
+ const lbContext = useLB();
16
+ const [portalRoot, setPortalRoot] = useState<HTMLElement | null>(null);
17
+
15
18
  const [email, setEmail] = useState("");
16
19
  const [password, setPassword] = useState("");
17
20
  const [loading, setLoading] = useState(false);
18
21
  const [error, setError] = useState("");
19
22
  const [showKeySelector, setShowKeySelector] = useState(false);
20
- const [currentApiKeys, setCurrentApiKeys] = useState<any[]>([]); // Stocker les clés API localement
23
+ const [currentApiKeys, setCurrentApiKeys] = useState<any[]>([]);
21
24
 
22
- // Appeler useLB() inconditionnellement - hook doit toujours être appelé
23
- const lbContext = useLB();
25
+ const { login, selectApiKeyWithToken, fetchApiKeys } = lbContext || {};
24
26
 
25
- // Vérifier si le contexte est valide
26
- if (!lbContext || !isOpen) {
27
- return null;
28
- }
27
+ const canRender = Boolean(isOpen && lbContext && login);
29
28
 
30
- // Extraire les valeurs du contexte
31
- const {
32
- login,
33
- selectApiKeyWithToken,
34
- fetchApiKeys,
35
- apiKeys = [],
36
- status: lbStatus,
37
- } = lbContext;
29
+ const panelTitle = useMemo(() => "Connexion LastBrain", []);
38
30
 
39
- if (!isOpen || !login) return null;
31
+ useEffect(() => {
32
+ setPortalRoot(document.body);
33
+ }, []);
34
+
35
+ if (!canRender || !portalRoot) {
36
+ return null;
37
+ }
40
38
 
41
39
  const handleSubmit = async (e: React.FormEvent) => {
42
40
  e.preventDefault();
41
+ if (!login) {
42
+ return;
43
+ }
44
+
43
45
  setError("");
44
46
  setLoading(true);
45
47
 
46
48
  try {
47
49
  const result = await login(email, password);
48
- if (result.success) {
49
- if (result.needsKeySelection) {
50
- // L'utilisateur doit choisir une clé API
51
- // Utiliser l'access token retourné directement par login
52
- if (fetchApiKeys && result.accessToken) {
53
- try {
54
- const keys = await fetchApiKeys(result.accessToken);
55
- setCurrentApiKeys(keys);
56
- setShowKeySelector(true);
57
- } catch (keyError) {
58
- setError("Erreur lors de la récupération des clés API");
59
- console.error("Failed to fetch API keys:", keyError);
60
- }
61
- } else {
62
- setError("Token d'accès non disponible");
63
- }
64
- } else {
65
- // Connexion réussie, fermer le modal
66
- onClose();
67
- }
68
- } else {
50
+ if (!result.success) {
69
51
  setError(result.error || "Échec de la connexion");
52
+ return;
53
+ }
54
+
55
+ if (!result.needsKeySelection) {
56
+ onClose();
57
+ return;
58
+ }
59
+
60
+ if (!fetchApiKeys || !result.accessToken) {
61
+ setError("Token d'accès non disponible");
62
+ return;
63
+ }
64
+
65
+ try {
66
+ const keys = await fetchApiKeys(result.accessToken);
67
+ setCurrentApiKeys(keys || []);
68
+ setShowKeySelector(true);
69
+ } catch (keyError) {
70
+ console.error("Failed to fetch API keys:", keyError);
71
+ setError("Erreur lors de la récupération des clés API");
70
72
  }
71
73
  } catch (err) {
72
74
  setError(
@@ -78,7 +80,9 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
78
80
  };
79
81
 
80
82
  const handleKeySelect = async (apiKeyId: string) => {
81
- if (!selectApiKeyWithToken) return;
83
+ if (!selectApiKeyWithToken) {
84
+ return;
85
+ }
82
86
 
83
87
  try {
84
88
  await selectApiKeyWithToken(apiKeyId);
@@ -94,481 +98,146 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
94
98
 
95
99
  const handleCancelKeySelection = () => {
96
100
  setShowKeySelector(false);
97
- // Fermer complètement le modal au lieu de rester dans l'état de sélection
98
101
  onClose();
99
102
  };
100
103
 
101
- // Si on doit afficher le sélecteur de clés
102
104
  if (showKeySelector && currentApiKeys.length > 0) {
103
- return (
105
+ return createPortal(
104
106
  <LBApiKeySelector
105
107
  apiKeys={currentApiKeys}
106
108
  onSelect={handleKeySelect}
107
109
  onCancel={handleCancelKeySelection}
108
- isOpen={true}
109
- />
110
+ isOpen
111
+ />,
112
+ portalRoot
110
113
  );
111
114
  }
112
115
 
113
- const handleKeyDown = (e: React.KeyboardEvent) => {
114
- if (e.key === "Escape") {
115
- onClose();
116
- }
117
- };
118
-
119
- return (
116
+ return createPortal(
120
117
  <div
121
- style={{
122
- position: "fixed",
123
- top: 0,
124
- left: 0,
125
- right: 0,
126
- bottom: 0,
127
- width: "100vw",
128
- height: "100vh",
129
- backgroundColor: "rgba(0, 0, 0, 0.75)",
130
- backdropFilter: "blur(12px)",
131
- WebkitBackdropFilter: "blur(12px)",
132
- display: "flex",
133
- alignItems: "center",
134
- justifyContent: "center",
135
- zIndex: 9999,
136
- padding: "16px",
137
- animation: "fadeIn 0.2s ease-out",
138
- overflow: "auto",
139
- }}
118
+ className="ai-signin-overlay"
140
119
  onClick={onClose}
141
- onKeyDown={handleKeyDown}
120
+ onKeyDown={(e) => {
121
+ if (e.key === "Escape") {
122
+ onClose();
123
+ }
124
+ }}
125
+ role="dialog"
126
+ aria-modal="true"
127
+ aria-label={panelTitle}
142
128
  >
143
- <div
144
- style={{
145
- backgroundColor: "light-dark(#ffffff, #1e293b)",
146
- borderRadius: "20px",
147
- boxShadow:
148
- "0 0 0 1px rgba(139, 92, 246, 0.2), 0 20px 70px rgba(139, 92, 246, 0.25), 0 4px 6px rgba(0, 0, 0, 0.1)",
149
- maxWidth: "440px",
150
- width: "100%",
151
- border:
152
- "1px solid light-dark(rgba(226, 232, 240, 0.8), rgba(139, 92, 246, 0.3))",
153
- display: "flex",
154
- flexDirection: "column",
155
- maxHeight: "90vh",
156
- overflow: "hidden",
157
- position: "relative",
158
- animation: "slideUp 0.3s ease-out",
159
- }}
160
- onClick={(e) => e.stopPropagation()}
161
- >
162
- {/* Gradient Background Effect */}
163
- <div
164
- style={{
165
- position: "absolute",
166
- top: 0,
167
- left: 0,
168
- right: 0,
169
- height: "200px",
170
- background:
171
- "radial-gradient(ellipse at top, rgba(139, 92, 246, 0.15), transparent 70%)",
172
- pointerEvents: "none",
173
- zIndex: 0,
174
- }}
175
- />
176
-
177
- {/* Header */}
178
- <div
179
- style={{
180
- padding: "32px 32px 24px",
181
- display: "flex",
182
- flexDirection: "column",
183
- alignItems: "center",
184
- gap: "16px",
185
- position: "relative",
186
- zIndex: 1,
187
- }}
129
+ <div className="ai-signin-panel" onClick={(e) => e.stopPropagation()}>
130
+ <button
131
+ type="button"
132
+ className="ai-icon-btn ai-signin-close"
133
+ onClick={onClose}
134
+ aria-label="Fermer"
188
135
  >
189
- {/* Logo/Icon */}
190
- <div
191
- style={{
192
- width: "64px",
193
- height: "64px",
194
- borderRadius: "16px",
195
- background: "linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)",
196
- display: "flex",
197
- alignItems: "center",
198
- justifyContent: "center",
199
- boxShadow: "0 8px 24px rgba(139, 92, 246, 0.3)",
200
- animation: "pulse 2s ease-in-out infinite",
201
- }}
202
- >
203
- <Sparkles size={32} color="#ffffff" strokeWidth={2.5} />
204
- </div>
205
-
206
- {/* Title */}
207
- <div style={{ textAlign: "center" }}>
208
- <h2
209
- style={{
210
- margin: "0 0 8px 0",
211
- fontSize: "24px",
212
- fontWeight: 700,
213
- color: "light-dark(#1e293b, #f8fafc)",
214
- letterSpacing: "-0.02em",
215
- }}
216
- >
217
- Connexion LastBrain
218
- </h2>
219
- <p
220
- style={{
221
- margin: 0,
222
- fontSize: "14px",
223
- color: "light-dark(#64748b, #94a3b8)",
224
- fontWeight: 400,
225
- }}
226
- >
227
- Accédez à vos outils d'intelligence artificielle
228
- </p>
136
+ <X size={16} />
137
+ </button>
138
+
139
+ <div className="ai-signin-header">
140
+ <div className="ai-center mb-3">
141
+ <span className="ai-icon-badge">
142
+ <Sparkles size={22} />
143
+ </span>
229
144
  </div>
230
-
231
- {/* Close Button */}
232
- <button
233
- onClick={onClose}
234
- style={{
235
- position: "absolute",
236
- top: "24px",
237
- right: "24px",
238
- background: "transparent",
239
- border: "none",
240
- color: "light-dark(#64748b, #94a3b8)",
241
- cursor: "pointer",
242
- padding: "8px",
243
- borderRadius: "10px",
244
- transition: "all 0.2s ease",
245
- display: "flex",
246
- alignItems: "center",
247
- justifyContent: "center",
248
- }}
249
- onMouseEnter={(e) => {
250
- e.currentTarget.style.background =
251
- "light-dark(rgba(100, 116, 139, 0.1), rgba(148, 163, 184, 0.15))";
252
- e.currentTarget.style.color = "light-dark(#1e293b, #f8fafc)";
253
- }}
254
- onMouseLeave={(e) => {
255
- e.currentTarget.style.background = "transparent";
256
- e.currentTarget.style.color = "light-dark(#64748b, #94a3b8)";
257
- }}
258
- aria-label="Close"
259
- >
260
- <X size={20} strokeWidth={2} />
261
- </button>
145
+ <h2 className="ai-signin-title">{panelTitle}</h2>
146
+ <p className="ai-signin-subtitle">
147
+ Connectez-vous pour activer les composants IA dans votre app.
148
+ </p>
262
149
  </div>
263
150
 
264
- {/* Body */}
265
- <div
266
- style={{
267
- padding: "0 32px 32px",
268
- overflow: "auto",
269
- flex: 1,
270
- position: "relative",
271
- zIndex: 1,
272
- }}
273
- >
274
- <form
275
- onSubmit={handleSubmit}
276
- style={{ display: "flex", flexDirection: "column", gap: "20px" }}
277
- >
278
- {/* Email Input */}
279
- <div>
151
+ <div className="ai-signin-content">
152
+ <form onSubmit={handleSubmit}>
153
+ <div className="ai-input-row">
280
154
  <label
281
- style={{
282
- display: "flex",
283
- alignItems: "center",
284
- gap: "8px",
285
- fontSize: "13px",
286
- fontWeight: 600,
287
- color: "light-dark(#1e293b, #f8fafc)",
288
- marginBottom: "10px",
289
- letterSpacing: "0.01em",
290
- }}
155
+ htmlFor="lb-signin-email"
156
+ className="ai-input-label ai-row"
291
157
  >
292
- <Mail size={16} strokeWidth={2.5} />
158
+ <Mail size={14} className="ai-inline-icon" />
293
159
  Email
294
160
  </label>
295
- <div style={{ position: "relative" }}>
296
- <input
297
- type="email"
298
- value={email}
299
- onChange={(e) => setEmail(e.target.value)}
300
- required
301
- autoFocus
302
- autoComplete="email"
303
- placeholder="votre@email.com"
304
- style={{
305
- width: "100%",
306
- padding: "14px 16px",
307
- fontSize: "15px",
308
- border: "2px solid light-dark(#e2e8f0, #334155)",
309
- borderRadius: "12px",
310
- background: "light-dark(#f8fafc, #0f172a)",
311
- color: "light-dark(#1e293b, #f8fafc)",
312
- outline: "none",
313
- transition: "all 0.2s ease",
314
- fontWeight: 500,
315
- }}
316
- onFocus={(e) => {
317
- e.currentTarget.style.borderColor =
318
- "rgba(139, 92, 246, 0.6)";
319
- e.currentTarget.style.boxShadow =
320
- "0 0 0 4px rgba(139, 92, 246, 0.15)";
321
- e.currentTarget.style.background =
322
- "light-dark(#ffffff, #1e293b)";
323
- }}
324
- onBlur={(e) => {
325
- e.currentTarget.style.borderColor =
326
- "light-dark(#e2e8f0, #334155)";
327
- e.currentTarget.style.boxShadow = "none";
328
- e.currentTarget.style.background =
329
- "light-dark(#f8fafc, #0f172a)";
330
- }}
331
- />
161
+ <div className="ai-control-group ai-glow">
162
+ <div className="ai-shell ai-size-md ai-radius-full">
163
+ <input
164
+ id="lb-signin-email"
165
+ className="ai-control ai-control-input ai-size-md ai-radius-full"
166
+ type="email"
167
+ value={email}
168
+ onChange={(e) => setEmail(e.target.value)}
169
+ required
170
+ autoFocus
171
+ autoComplete="email"
172
+ placeholder="votre@email.com"
173
+ />
174
+ </div>
332
175
  </div>
333
176
  </div>
334
177
 
335
- {/* Password Input */}
336
- <div>
178
+ <div className="ai-input-row">
337
179
  <label
338
- style={{
339
- display: "flex",
340
- alignItems: "center",
341
- gap: "8px",
342
- fontSize: "13px",
343
- fontWeight: 600,
344
- color: "light-dark(#1e293b, #f8fafc)",
345
- marginBottom: "10px",
346
- letterSpacing: "0.01em",
347
- }}
180
+ htmlFor="lb-signin-password"
181
+ className="ai-input-label ai-row"
348
182
  >
349
- <Lock size={16} strokeWidth={2.5} />
183
+ <Lock size={14} className="ai-inline-icon" />
350
184
  Mot de passe
351
185
  </label>
352
- <div style={{ position: "relative" }}>
353
- <input
354
- type="password"
355
- value={password}
356
- onChange={(e) => setPassword(e.target.value)}
357
- required
358
- autoComplete="current-password"
359
- placeholder="••••••••"
360
- style={{
361
- width: "100%",
362
- padding: "14px 16px",
363
- fontSize: "15px",
364
- border: "2px solid light-dark(#e2e8f0, #334155)",
365
- borderRadius: "12px",
366
- background: "light-dark(#f8fafc, #0f172a)",
367
- color: "light-dark(#1e293b, #f8fafc)",
368
- outline: "none",
369
- transition: "all 0.2s ease",
370
- letterSpacing: "0.15em",
371
- fontWeight: 500,
372
- }}
373
- onFocus={(e) => {
374
- e.currentTarget.style.borderColor =
375
- "rgba(139, 92, 246, 0.6)";
376
- e.currentTarget.style.boxShadow =
377
- "0 0 0 4px rgba(139, 92, 246, 0.15)";
378
- e.currentTarget.style.background =
379
- "light-dark(#ffffff, #1e293b)";
380
- }}
381
- onBlur={(e) => {
382
- e.currentTarget.style.borderColor =
383
- "light-dark(#e2e8f0, #334155)";
384
- e.currentTarget.style.boxShadow = "none";
385
- e.currentTarget.style.background =
386
- "light-dark(#f8fafc, #0f172a)";
387
- }}
388
- />
186
+ <div className="ai-control-group ai-glow">
187
+ <div className="ai-shell ai-size-md ai-radius-full">
188
+ <input
189
+ id="lb-signin-password"
190
+ className="ai-control ai-control-input ai-size-md ai-radius-full"
191
+ type="password"
192
+ value={password}
193
+ onChange={(e) => setPassword(e.target.value)}
194
+ required
195
+ autoComplete="current-password"
196
+ placeholder="••••••••"
197
+ />
198
+ </div>
389
199
  </div>
390
200
  </div>
391
201
 
392
- {/* Error Message */}
393
- {error && (
394
- <div
395
- style={{
396
- padding: "14px 16px",
397
- background: "rgba(239, 68, 68, 0.1)",
398
- border: "2px solid rgba(239, 68, 68, 0.35)",
399
- borderRadius: "12px",
400
- color: "light-dark(#dc2626, #fca5a5)",
401
- fontSize: "14px",
402
- display: "flex",
403
- alignItems: "start",
404
- gap: "12px",
405
- animation: "shake 0.4s ease",
406
- }}
407
- >
408
- <AlertCircle
409
- size={20}
410
- style={{ flexShrink: 0, marginTop: "2px" }}
411
- strokeWidth={2.5}
412
- />
413
- <span style={{ flex: 1, lineHeight: "1.5", fontWeight: 500 }}>
414
- {error}
415
- </span>
202
+ {error ? (
203
+ <div className="ai-signin-error" role="alert">
204
+ <AlertCircle size={16} />
205
+ <span>{error}</span>
416
206
  </div>
417
- )}
207
+ ) : null}
418
208
 
419
- {/* Submit Button */}
420
- <button
421
- type="submit"
422
- disabled={loading}
423
- style={{
424
- width: "100%",
425
- padding: "16px",
426
- fontSize: "15px",
427
- fontWeight: 700,
428
- color: "#ffffff",
429
- background: loading
430
- ? "light-dark(#cbd5e1, #475569)"
431
- : "linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)",
432
- border: "none",
433
- borderRadius: "12px",
434
- cursor: loading ? "not-allowed" : "pointer",
435
- transition: "all 0.2s ease",
436
- display: "flex",
437
- alignItems: "center",
438
- justifyContent: "center",
439
- gap: "10px",
440
- letterSpacing: "0.01em",
441
- opacity: loading ? 0.6 : 1,
442
- boxShadow: loading
443
- ? "none"
444
- : "0 4px 14px rgba(139, 92, 246, 0.4)",
445
- }}
446
- onMouseEnter={(e) => {
447
- if (!loading) {
448
- e.currentTarget.style.transform = "translateY(-2px)";
449
- e.currentTarget.style.boxShadow =
450
- "0 8px 24px rgba(139, 92, 246, 0.6)";
451
- }
452
- }}
453
- onMouseLeave={(e) => {
454
- e.currentTarget.style.transform = "translateY(0)";
455
- e.currentTarget.style.boxShadow =
456
- "0 4px 14px rgba(139, 92, 246, 0.4)";
457
- }}
458
- >
459
- {loading ? (
460
- <>
461
- <Loader2
462
- size={20}
463
- strokeWidth={2.5}
464
- style={{ animation: "spin 1s linear infinite" }}
465
- />
466
- Connexion en cours...
467
- </>
468
- ) : (
469
- <>
470
- <Sparkles size={20} strokeWidth={2.5} />
471
- Se connecter
472
- </>
473
- )}
474
- </button>
475
-
476
- {/* Signup Link */}
477
- <div
478
- style={{
479
- marginTop: "8px",
480
- padding: "20px",
481
- background: "rgba(139, 92, 246, 0.05)",
482
- border: "1px solid rgba(139, 92, 246, 0.2)",
483
- borderRadius: "16px",
484
- textAlign: "center",
485
- }}
486
- >
487
- <p
488
- style={{
489
- margin: "0 0 14px 0",
490
- fontSize: "14px",
491
- color: "light-dark(#64748b, #94a3b8)",
492
- fontWeight: 500,
493
- }}
209
+ <div className="ai-signin-actions">
210
+ <button
211
+ type="submit"
212
+ className="ai-btn ai-btn--auth"
213
+ disabled={loading}
494
214
  >
495
- Pas encore de compte ?
496
- </p>
215
+ {loading ? (
216
+ <>
217
+ <Loader2 size={16} className="ai-spinner" />
218
+ Connexion...
219
+ </>
220
+ ) : (
221
+ <>
222
+ <Sparkles size={16} />
223
+ Se connecter
224
+ </>
225
+ )}
226
+ </button>
227
+
497
228
  <a
498
229
  href="https://prompt.lastbrain.io/signup"
499
230
  target="_blank"
500
231
  rel="noopener noreferrer"
501
- style={{
502
- display: "inline-flex",
503
- alignItems: "center",
504
- gap: "8px",
505
- padding: "10px 20px",
506
- fontSize: "14px",
507
- fontWeight: 600,
508
- color: "#8b5cf6",
509
- textDecoration: "none",
510
- border: "2px solid rgba(139, 92, 246, 0.3)",
511
- borderRadius: "10px",
512
- transition: "all 0.2s ease",
513
- }}
514
- onMouseEnter={(e) => {
515
- e.currentTarget.style.background = "rgba(139, 92, 246, 0.1)";
516
- e.currentTarget.style.borderColor = "rgba(139, 92, 246, 0.5)";
517
- e.currentTarget.style.transform = "translateY(-1px)";
518
- }}
519
- onMouseLeave={(e) => {
520
- e.currentTarget.style.background = "transparent";
521
- e.currentTarget.style.borderColor = "rgba(139, 92, 246, 0.3)";
522
- e.currentTarget.style.transform = "translateY(0)";
523
- }}
232
+ className="ai-btn ai-btn--ghost"
524
233
  >
525
- <Sparkles size={16} strokeWidth={2.5} />
526
- Créer un compte gratuitement
234
+ Créer un compte
527
235
  </a>
528
236
  </div>
529
237
  </form>
530
238
  </div>
531
239
  </div>
532
-
533
- <style>
534
- {`
535
- @keyframes fadeIn {
536
- from { opacity: 0; }
537
- to { opacity: 1; }
538
- }
539
-
540
- @keyframes slideUp {
541
- from {
542
- opacity: 0;
543
- transform: translateY(20px) scale(0.95);
544
- }
545
- to {
546
- opacity: 1;
547
- transform: translateY(0) scale(1);
548
- }
549
- }
550
-
551
- @keyframes spin {
552
- from { transform: rotate(0deg); }
553
- to { transform: rotate(360deg); }
554
- }
555
-
556
- @keyframes pulse {
557
- 0%, 100% {
558
- box-shadow: 0 8px 24px rgba(139, 92, 246, 0.3);
559
- }
560
- 50% {
561
- box-shadow: 0 8px 32px rgba(139, 92, 246, 0.5);
562
- }
563
- }
564
-
565
- @keyframes shake {
566
- 0%, 100% { transform: translateX(0); }
567
- 25% { transform: translateX(-8px); }
568
- 75% { transform: translateX(8px); }
569
- }
570
- `}
571
- </style>
572
- </div>
240
+ </div>,
241
+ portalRoot
573
242
  );
574
243
  }