@lastbrain/ai-ui-react 1.0.68 → 1.0.69

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 (60) 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 +21 -70
  4. package/dist/components/AiContextButton.d.ts +5 -1
  5. package/dist/components/AiContextButton.d.ts.map +1 -1
  6. package/dist/components/AiContextButton.js +67 -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 +58 -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 +198 -668
  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/examples/AiUiPremiumShowcase.d.ts +2 -0
  33. package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
  34. package/dist/examples/AiUiPremiumShowcase.js +15 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -0
  38. package/dist/styles/inline.d.ts +1 -0
  39. package/dist/styles/inline.d.ts.map +1 -1
  40. package/dist/styles/inline.js +25 -129
  41. package/dist/styles.css +1268 -369
  42. package/dist/types.d.ts +3 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +2 -2
  45. package/src/components/AiChipLabel.tsx +64 -101
  46. package/src/components/AiContextButton.tsx +138 -430
  47. package/src/components/AiImageButton.tsx +29 -190
  48. package/src/components/AiInput.tsx +49 -74
  49. package/src/components/AiPromptPanel.tsx +71 -254
  50. package/src/components/AiSelect.tsx +61 -69
  51. package/src/components/AiStatusButton.tsx +477 -1313
  52. package/src/components/AiTextarea.tsx +49 -64
  53. package/src/components/LBApiKeySelector.tsx +86 -274
  54. package/src/components/LBConnectButton.tsx +46 -334
  55. package/src/components/LBSigninModal.tsx +140 -481
  56. package/src/examples/AiUiPremiumShowcase.tsx +91 -0
  57. package/src/index.ts +3 -0
  58. package/src/styles/inline.ts +27 -148
  59. package/src/styles.css +1268 -369
  60. package/src/types.ts +3 -0
@@ -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,564 +12,222 @@ 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
21
-
22
- // Appeler useLB() inconditionnellement - hook doit toujours être appelé
23
- const lbContext = useLB();
24
-
25
- // Vérifier si le contexte est valide
26
- if (!lbContext || !isOpen) {
27
- return null;
28
- }
23
+ const [currentApiKeys, setCurrentApiKeys] = useState<any[]>([]);
29
24
 
30
- // Extraire les valeurs du contexte
31
25
  const {
32
26
  login,
33
27
  selectApiKeyWithToken,
34
28
  fetchApiKeys,
35
- apiKeys = [],
36
- status: lbStatus,
37
- } = lbContext;
29
+ } = lbContext || {};
30
+
31
+ const canRender = Boolean(isOpen && lbContext && login);
32
+
33
+ const panelTitle = useMemo(() => "Connexion LastBrain", []);
38
34
 
39
- if (!isOpen || !login) return null;
35
+ useEffect(() => {
36
+ setPortalRoot(document.body);
37
+ }, []);
38
+
39
+ if (!canRender || !portalRoot) {
40
+ return null;
41
+ }
40
42
 
41
43
  const handleSubmit = async (e: React.FormEvent) => {
42
44
  e.preventDefault();
45
+ if (!login) {
46
+ return;
47
+ }
48
+
43
49
  setError("");
44
50
  setLoading(true);
45
51
 
46
52
  try {
47
53
  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 {
54
+ if (!result.success) {
69
55
  setError(result.error || "Échec de la connexion");
56
+ return;
57
+ }
58
+
59
+ if (!result.needsKeySelection) {
60
+ onClose();
61
+ return;
62
+ }
63
+
64
+ if (!fetchApiKeys || !result.accessToken) {
65
+ setError("Token d'accès non disponible");
66
+ return;
67
+ }
68
+
69
+ try {
70
+ const keys = await fetchApiKeys(result.accessToken);
71
+ setCurrentApiKeys(keys || []);
72
+ setShowKeySelector(true);
73
+ } catch (keyError) {
74
+ console.error("Failed to fetch API keys:", keyError);
75
+ setError("Erreur lors de la récupération des clés API");
70
76
  }
71
77
  } catch (err) {
72
- setError(
73
- err instanceof Error ? err.message : "Une erreur s'est produite"
74
- );
78
+ setError(err instanceof Error ? err.message : "Une erreur s'est produite");
75
79
  } finally {
76
80
  setLoading(false);
77
81
  }
78
82
  };
79
83
 
80
84
  const handleKeySelect = async (apiKeyId: string) => {
81
- if (!selectApiKeyWithToken) return;
85
+ if (!selectApiKeyWithToken) {
86
+ return;
87
+ }
82
88
 
83
89
  try {
84
90
  await selectApiKeyWithToken(apiKeyId);
85
91
  setShowKeySelector(false);
86
92
  onClose();
87
93
  } catch (err) {
88
- setError(
89
- err instanceof Error ? err.message : "Erreur lors de la sélection"
90
- );
94
+ setError(err instanceof Error ? err.message : "Erreur lors de la sélection");
91
95
  setShowKeySelector(false);
92
96
  }
93
97
  };
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>
280
- <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
- }}
291
- >
292
- <Mail size={16} strokeWidth={2.5} />
151
+ <div className="ai-signin-content">
152
+ <form onSubmit={handleSubmit}>
153
+ <div className="ai-input-row">
154
+ <label htmlFor="lb-signin-email" className="ai-input-label ai-row">
155
+ <Mail size={14} className="ai-inline-icon" />
293
156
  Email
294
157
  </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
- />
158
+ <div className="ai-control-group ai-glow">
159
+ <div className="ai-shell ai-size-md ai-radius-full">
160
+ <input
161
+ id="lb-signin-email"
162
+ className="ai-control ai-control-input ai-size-md ai-radius-full"
163
+ type="email"
164
+ value={email}
165
+ onChange={(e) => setEmail(e.target.value)}
166
+ required
167
+ autoFocus
168
+ autoComplete="email"
169
+ placeholder="votre@email.com"
170
+ />
171
+ </div>
332
172
  </div>
333
173
  </div>
334
174
 
335
- {/* Password Input */}
336
- <div>
337
- <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
- }}
348
- >
349
- <Lock size={16} strokeWidth={2.5} />
175
+ <div className="ai-input-row">
176
+ <label htmlFor="lb-signin-password" className="ai-input-label ai-row">
177
+ <Lock size={14} className="ai-inline-icon" />
350
178
  Mot de passe
351
179
  </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
- />
180
+ <div className="ai-control-group ai-glow">
181
+ <div className="ai-shell ai-size-md ai-radius-full">
182
+ <input
183
+ id="lb-signin-password"
184
+ className="ai-control ai-control-input ai-size-md ai-radius-full"
185
+ type="password"
186
+ value={password}
187
+ onChange={(e) => setPassword(e.target.value)}
188
+ required
189
+ autoComplete="current-password"
190
+ placeholder="••••••••"
191
+ />
192
+ </div>
389
193
  </div>
390
194
  </div>
391
195
 
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>
196
+ {error ? (
197
+ <div className="ai-signin-error" role="alert">
198
+ <AlertCircle size={16} />
199
+ <span>{error}</span>
416
200
  </div>
417
- )}
418
-
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>
201
+ ) : null}
202
+
203
+ <div className="ai-signin-actions">
204
+ <button type="submit" className="ai-btn ai-btn--auth" disabled={loading}>
205
+ {loading ? (
206
+ <>
207
+ <Loader2 size={16} className="ai-spinner" />
208
+ Connexion...
209
+ </>
210
+ ) : (
211
+ <>
212
+ <Sparkles size={16} />
213
+ Se connecter
214
+ </>
215
+ )}
216
+ </button>
475
217
 
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
- }}
494
- >
495
- Pas encore de compte ?
496
- </p>
497
218
  <a
498
219
  href="https://prompt.lastbrain.io/signup"
499
220
  target="_blank"
500
221
  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
- }}
222
+ className="ai-btn ai-btn--ghost"
524
223
  >
525
- <Sparkles size={16} strokeWidth={2.5} />
526
- Créer un compte gratuitement
224
+ Créer un compte
527
225
  </a>
528
226
  </div>
529
227
  </form>
530
228
  </div>
531
229
  </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>
230
+ </div>,
231
+ portalRoot
573
232
  );
574
233
  }