@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.
- package/dist/components/AiChipLabel.d.ts +8 -3
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +23 -70
- package/dist/components/AiContextButton.d.ts +10 -2
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +73 -291
- package/dist/components/AiImageButton.d.ts +5 -1
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +6 -142
- package/dist/components/AiInput.d.ts +5 -3
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +13 -25
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +64 -212
- package/dist/components/AiSelect.d.ts +5 -3
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +21 -30
- package/dist/components/AiStatusButton.d.ts +4 -1
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +211 -676
- package/dist/components/AiTextarea.d.ts +4 -2
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +14 -26
- package/dist/components/LBApiKeySelector.d.ts.map +1 -1
- package/dist/components/LBApiKeySelector.js +5 -166
- package/dist/components/LBConnectButton.d.ts +4 -7
- package/dist/components/LBConnectButton.d.ts.map +1 -1
- package/dist/components/LBConnectButton.js +17 -86
- package/dist/components/LBSigninModal.d.ts +1 -1
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/LBSigninModal.js +42 -320
- package/dist/context/LBAuthProvider.d.ts +35 -3
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +2 -0
- package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
- package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
- package/dist/examples/AiUiPremiumShowcase.js +15 -0
- package/dist/hooks/useAiModels.d.ts.map +1 -1
- package/dist/hooks/useModelManagement.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles/inline.d.ts +1 -0
- package/dist/styles/inline.d.ts.map +1 -1
- package/dist/styles/inline.js +25 -129
- package/dist/styles.css +1268 -369
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/errorHandler.d.ts +2 -2
- package/dist/utils/errorHandler.d.ts.map +1 -1
- package/dist/utils/errorHandler.js +8 -1
- package/dist/utils/modelManagement.d.ts +13 -10
- package/dist/utils/modelManagement.d.ts.map +1 -1
- package/dist/utils/modelManagement.js +19 -2
- package/package.json +2 -2
- package/src/components/AiChipLabel.tsx +68 -101
- package/src/components/AiContextButton.tsx +142 -413
- package/src/components/AiImageButton.tsx +29 -190
- package/src/components/AiInput.tsx +49 -74
- package/src/components/AiPromptPanel.tsx +81 -260
- package/src/components/AiSelect.tsx +61 -69
- package/src/components/AiStatusButton.tsx +496 -1327
- package/src/components/AiTextarea.tsx +50 -63
- package/src/components/LBApiKeySelector.tsx +93 -271
- package/src/components/LBConnectButton.tsx +39 -336
- package/src/components/LBSigninModal.tsx +141 -472
- package/src/context/LBAuthProvider.tsx +45 -6
- package/src/examples/AiUiPremiumShowcase.tsx +94 -0
- package/src/hooks/useAiModels.ts +2 -1
- package/src/hooks/useModelManagement.ts +2 -1
- package/src/index.ts +3 -0
- package/src/styles/inline.ts +27 -148
- package/src/styles.css +1268 -369
- package/src/types.ts +3 -0
- package/src/utils/errorHandler.ts +16 -3
- package/src/utils/modelManagement.ts +53 -15
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Bouton de connexion LastBrain
|
|
5
|
-
* Ouvre la modal d'authentification
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
3
|
import React from "react";
|
|
4
|
+
import { Loader2, LogIn, LogOut } from "lucide-react";
|
|
9
5
|
import { useLB } from "../context/LBAuthProvider";
|
|
10
|
-
import
|
|
6
|
+
import { LBSigninModal } from "./LBSigninModal";
|
|
11
7
|
|
|
12
8
|
interface LBConnectButtonProps {
|
|
13
|
-
/** Texte du bouton */
|
|
14
9
|
label?: string;
|
|
15
|
-
/** Classe CSS personnalisée */
|
|
16
10
|
className?: string;
|
|
17
|
-
/** Callback après connexion réussie */
|
|
18
11
|
onConnected?: () => void;
|
|
19
|
-
/** Callback à l'ouverture de la modal */
|
|
20
12
|
onOpenModal?: () => void;
|
|
21
13
|
}
|
|
22
14
|
|
|
23
15
|
export function LBConnectButton({
|
|
24
|
-
label = "Se connecter
|
|
16
|
+
label = "Se connecter",
|
|
25
17
|
className = "",
|
|
26
18
|
onConnected,
|
|
27
19
|
onOpenModal,
|
|
@@ -29,353 +21,64 @@ export function LBConnectButton({
|
|
|
29
21
|
const { status, user, logout } = useLB();
|
|
30
22
|
const [showModal, setShowModal] = React.useState(false);
|
|
31
23
|
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
if (status === "ready" && user && showModal) {
|
|
26
|
+
setShowModal(false);
|
|
27
|
+
onConnected?.();
|
|
28
|
+
}
|
|
29
|
+
}, [status, user, showModal, onConnected]);
|
|
30
|
+
|
|
32
31
|
const handleClick = () => {
|
|
33
32
|
if (status === "ready" && user) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Redirection propre après logout
|
|
37
|
-
if (typeof window !== "undefined") {
|
|
38
|
-
// Garder la langue actuelle de l'URL
|
|
39
|
-
const currentPath = window.location.pathname;
|
|
40
|
-
const langMatch = currentPath.match(/^\/([a-z]{2})\//);
|
|
41
|
-
const currentLang = langMatch ? langMatch[1] : "en";
|
|
42
|
-
|
|
43
|
-
// Rediriger vers la page d'accueil dans la langue actuelle
|
|
44
|
-
window.location.href = `/${currentLang}`;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
} else {
|
|
48
|
-
// Pas connecté, ouvrir la modal
|
|
49
|
-
setShowModal(true);
|
|
50
|
-
onOpenModal?.();
|
|
33
|
+
void logout();
|
|
34
|
+
return;
|
|
51
35
|
}
|
|
52
|
-
};
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (success) {
|
|
57
|
-
onConnected?.();
|
|
58
|
-
}
|
|
37
|
+
setShowModal(true);
|
|
38
|
+
onOpenModal?.();
|
|
59
39
|
};
|
|
60
40
|
|
|
41
|
+
const buttonLabel = status === "ready" && user ? "Déconnexion" : label;
|
|
42
|
+
|
|
61
43
|
return (
|
|
62
44
|
<>
|
|
63
45
|
<button
|
|
46
|
+
type="button"
|
|
64
47
|
onClick={handleClick}
|
|
65
|
-
className={
|
|
66
|
-
className ||
|
|
67
|
-
(status === "ready" && user
|
|
68
|
-
? "px-4 py-2 bg-gradient-to-r from-emerald-500 to-teal-600 text-white rounded-lg hover:from-emerald-600 hover:to-teal-700 transition-all duration-200 shadow-md hover:shadow-lg"
|
|
69
|
-
: "px-4 py-2 bg-gradient-to-r from-violet-500 to-purple-600 text-white rounded-lg hover:from-violet-600 hover:to-purple-700 transition-all duration-200 shadow-md hover:shadow-lg")
|
|
70
|
-
}
|
|
48
|
+
className={className || "ai-btn ai-btn--auth"}
|
|
71
49
|
disabled={status === "loading"}
|
|
72
50
|
>
|
|
73
|
-
{status === "loading"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
51
|
+
{status === "loading" ? (
|
|
52
|
+
<>
|
|
53
|
+
<Loader2 size={16} className="animate-spin" />
|
|
54
|
+
Chargement...
|
|
55
|
+
</>
|
|
56
|
+
) : status === "ready" && user ? (
|
|
57
|
+
<>
|
|
58
|
+
<LogOut size={16} />
|
|
59
|
+
{buttonLabel}
|
|
60
|
+
</>
|
|
61
|
+
) : (
|
|
62
|
+
<>
|
|
63
|
+
<LogIn size={16} />
|
|
64
|
+
{buttonLabel}
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
78
67
|
</button>
|
|
79
68
|
|
|
80
|
-
{showModal
|
|
69
|
+
<LBSigninModal isOpen={showModal} onClose={() => setShowModal(false)} />
|
|
81
70
|
</>
|
|
82
71
|
);
|
|
83
72
|
}
|
|
84
73
|
|
|
85
74
|
/**
|
|
86
|
-
*
|
|
75
|
+
* Compat export (ancien API): on garde un wrapper minimal
|
|
76
|
+
* pour éviter de casser les imports existants.
|
|
87
77
|
*/
|
|
88
78
|
interface LBAuthModalProps {
|
|
89
79
|
onClose: (success: boolean) => void;
|
|
90
80
|
}
|
|
91
81
|
|
|
92
|
-
function LBAuthModal({ onClose }: LBAuthModalProps) {
|
|
93
|
-
|
|
94
|
-
const [step, setStep] = React.useState<"login" | "select-key">("login");
|
|
95
|
-
const [email, setEmail] = React.useState("");
|
|
96
|
-
const [password, setPassword] = React.useState("");
|
|
97
|
-
const [error, setError] = React.useState("");
|
|
98
|
-
const [loading, setLoading] = React.useState(false);
|
|
99
|
-
const [accessToken, setAccessToken] = React.useState("");
|
|
100
|
-
const [apiKeys, setApiKeys] = React.useState<LBApiKey[]>([]);
|
|
101
|
-
|
|
102
|
-
const handleLogin = async (e: React.FormEvent) => {
|
|
103
|
-
e.preventDefault();
|
|
104
|
-
setError("");
|
|
105
|
-
setLoading(true);
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const result = await login(email, password);
|
|
109
|
-
if (!result.success) {
|
|
110
|
-
setError(result.error || "Échec de la connexion");
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Note: avec le nouveau système, login gère automatiquement
|
|
115
|
-
// la récupération et sélection de clé. Ce modal n'est plus nécessaire.
|
|
116
|
-
onClose(true);
|
|
117
|
-
} catch (err) {
|
|
118
|
-
setError(err instanceof Error ? err.message : "Échec de la connexion");
|
|
119
|
-
} finally {
|
|
120
|
-
setLoading(false);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const handleSelectKey = async (apiKeyId: string) => {
|
|
125
|
-
setError("");
|
|
126
|
-
setLoading(true);
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
await selectApiKey(accessToken, apiKeyId);
|
|
130
|
-
onClose(true); // Succès
|
|
131
|
-
} catch (err) {
|
|
132
|
-
setError(err instanceof Error ? err.message : "Échec de la sélection");
|
|
133
|
-
} finally {
|
|
134
|
-
setLoading(false);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<div className="fixed inset-0 bg-black/60 dark:bg-black/80 backdrop-blur-md flex items-center justify-center z-50 p-4 animate-in fade-in duration-200">
|
|
140
|
-
<div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-xl rounded-3xl shadow-2xl max-w-md w-full border border-slate-200/50 dark:border-slate-700/50 overflow-hidden animate-in zoom-in-95 duration-200">
|
|
141
|
-
{/* Header avec gradient */}
|
|
142
|
-
<div className="relative bg-gradient-to-r from-violet-600 via-purple-600 to-fuchsia-600 dark:from-violet-500 dark:via-purple-500 dark:to-fuchsia-500 px-6 py-8 text-white overflow-hidden">
|
|
143
|
-
{/* Pattern background */}
|
|
144
|
-
<div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PHBhdGggZD0iTTM2IDE0YzAtMTEuMDUgOC45NS0yMCAyMC0yMHMyMCA4Ljk1IDIwIDIwLTguOTUgMjAtMjAgMjAtMjAtOC45NS0yMC0yMHoiLz48L2c+PC9nPjwvc3ZnPg==')] opacity-30"></div>
|
|
145
|
-
|
|
146
|
-
<div className="relative flex justify-between items-start">
|
|
147
|
-
<div className="flex-1">
|
|
148
|
-
<div className="flex items-center gap-3 mb-2">
|
|
149
|
-
<div className="w-10 h-10 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center">
|
|
150
|
-
{step === "login" ? "🔐" : "🔑"}
|
|
151
|
-
</div>
|
|
152
|
-
<h2 className="text-2xl font-bold tracking-tight">
|
|
153
|
-
{step === "login" ? "Connexion" : "Sélection clé"}
|
|
154
|
-
</h2>
|
|
155
|
-
</div>
|
|
156
|
-
<p className="text-white/90 text-sm font-medium mt-1 ml-13">
|
|
157
|
-
{step === "login"
|
|
158
|
-
? "Accédez à vos outils IA"
|
|
159
|
-
: "Session sécurisée 72h"}
|
|
160
|
-
</p>
|
|
161
|
-
</div>
|
|
162
|
-
<button
|
|
163
|
-
onClick={() => onClose(false)}
|
|
164
|
-
className="text-white/80 hover:text-white hover:bg-white/20 rounded-lg p-2 transition-all duration-200 backdrop-blur-sm"
|
|
165
|
-
>
|
|
166
|
-
<svg
|
|
167
|
-
className="w-5 h-5"
|
|
168
|
-
fill="none"
|
|
169
|
-
stroke="currentColor"
|
|
170
|
-
viewBox="0 0 24 24"
|
|
171
|
-
>
|
|
172
|
-
<path
|
|
173
|
-
strokeLinecap="round"
|
|
174
|
-
strokeLinejoin="round"
|
|
175
|
-
strokeWidth={2.5}
|
|
176
|
-
d="M6 18L18 6M6 6l12 12"
|
|
177
|
-
/>
|
|
178
|
-
</svg>
|
|
179
|
-
</button>
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<div className="p-8">
|
|
184
|
-
{step === "login" ? (
|
|
185
|
-
<form onSubmit={handleLogin} className="space-y-5">
|
|
186
|
-
<div className="space-y-2">
|
|
187
|
-
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-200 mb-2 tracking-wide">
|
|
188
|
-
📧 Adresse email
|
|
189
|
-
</label>
|
|
190
|
-
<input
|
|
191
|
-
type="email"
|
|
192
|
-
value={email}
|
|
193
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
194
|
-
className="w-full px-4 py-3.5 text-base border-2 border-slate-200 dark:border-slate-600 rounded-xl bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:border-violet-500 dark:focus:border-violet-400 focus:ring-4 focus:ring-violet-100 dark:focus:ring-violet-900/30 transition-all outline-none font-medium tracking-wide"
|
|
195
|
-
placeholder="votre@email.com"
|
|
196
|
-
required
|
|
197
|
-
autoFocus
|
|
198
|
-
autoComplete="email"
|
|
199
|
-
style={{ letterSpacing: "0.01em" }}
|
|
200
|
-
/>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
<div className="space-y-2">
|
|
204
|
-
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-200 mb-2 tracking-wide">
|
|
205
|
-
🔒 Mot de passe
|
|
206
|
-
</label>
|
|
207
|
-
<input
|
|
208
|
-
type="password"
|
|
209
|
-
value={password}
|
|
210
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
211
|
-
className="w-full px-4 py-3.5 text-base border-2 border-slate-200 dark:border-slate-600 rounded-xl bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:border-violet-500 dark:focus:border-violet-400 focus:ring-4 focus:ring-violet-100 dark:focus:ring-violet-900/30 transition-all outline-none font-medium tracking-wide"
|
|
212
|
-
placeholder="••••••••"
|
|
213
|
-
required
|
|
214
|
-
autoComplete="current-password"
|
|
215
|
-
style={{ letterSpacing: "0.15em" }}
|
|
216
|
-
/>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
{error && (
|
|
220
|
-
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 dark:border-red-400 px-4 py-3.5 rounded-lg animate-in slide-in-from-top-2 duration-200">
|
|
221
|
-
<div className="flex items-start gap-3">
|
|
222
|
-
<span className="text-red-500 dark:text-red-400 text-xl flex-shrink-0 mt-0.5">
|
|
223
|
-
⚠️
|
|
224
|
-
</span>
|
|
225
|
-
<p className="text-red-700 dark:text-red-300 text-sm font-medium leading-relaxed">
|
|
226
|
-
{error}
|
|
227
|
-
</p>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
)}
|
|
231
|
-
|
|
232
|
-
<button
|
|
233
|
-
type="submit"
|
|
234
|
-
disabled={loading}
|
|
235
|
-
className="w-full mt-6 px-6 py-4 bg-gradient-to-r from-violet-600 to-purple-600 hover:from-violet-700 hover:to-purple-700 disabled:from-slate-400 disabled:to-slate-500 text-white font-bold text-base rounded-xl disabled:cursor-not-allowed transition-all duration-200 shadow-lg shadow-violet-500/30 hover:shadow-xl hover:shadow-violet-500/40 disabled:shadow-none transform hover:translate-y-[-2px] active:translate-y-0 disabled:translate-y-0 tracking-wide"
|
|
236
|
-
>
|
|
237
|
-
{loading ? (
|
|
238
|
-
<span className="flex items-center justify-center gap-3">
|
|
239
|
-
<svg
|
|
240
|
-
className="animate-spin h-5 w-5"
|
|
241
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
242
|
-
fill="none"
|
|
243
|
-
viewBox="0 0 24 24"
|
|
244
|
-
>
|
|
245
|
-
<circle
|
|
246
|
-
className="opacity-25"
|
|
247
|
-
cx="12"
|
|
248
|
-
cy="12"
|
|
249
|
-
r="10"
|
|
250
|
-
stroke="currentColor"
|
|
251
|
-
strokeWidth="4"
|
|
252
|
-
></circle>
|
|
253
|
-
<path
|
|
254
|
-
className="opacity-75"
|
|
255
|
-
fill="currentColor"
|
|
256
|
-
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
257
|
-
></path>
|
|
258
|
-
</svg>
|
|
259
|
-
<span className="font-semibold">Connexion en cours...</span>
|
|
260
|
-
</span>
|
|
261
|
-
) : (
|
|
262
|
-
<span className="flex items-center justify-center gap-2">
|
|
263
|
-
🚀 <span>Se connecter</span>
|
|
264
|
-
</span>
|
|
265
|
-
)}
|
|
266
|
-
</button>
|
|
267
|
-
|
|
268
|
-
<div className="relative my-8">
|
|
269
|
-
<div className="absolute inset-0 flex items-center">
|
|
270
|
-
<div className="w-full border-t border-slate-200 dark:border-slate-700"></div>
|
|
271
|
-
</div>
|
|
272
|
-
<div className="relative flex justify-center text-sm">
|
|
273
|
-
<span className="px-4 bg-white dark:bg-slate-900 text-slate-500 dark:text-slate-400 font-medium">
|
|
274
|
-
ou
|
|
275
|
-
</span>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
|
|
279
|
-
<div className="bg-gradient-to-br from-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20 rounded-xl p-5 border border-violet-200 dark:border-violet-800/50 backdrop-blur-sm">
|
|
280
|
-
<p className="text-sm text-slate-700 dark:text-slate-300 text-center font-medium mb-3">
|
|
281
|
-
Pas encore de compte ?
|
|
282
|
-
</p>
|
|
283
|
-
<a
|
|
284
|
-
href="https://prompt.lastbrain.io/signup"
|
|
285
|
-
target="_blank"
|
|
286
|
-
rel="noopener noreferrer"
|
|
287
|
-
className="block text-center px-4 py-3 bg-white dark:bg-slate-800 text-violet-600 dark:text-violet-400 hover:text-violet-700 dark:hover:text-violet-300 font-bold text-sm rounded-lg hover:shadow-md transition-all duration-200 border-2 border-violet-200 dark:border-violet-700"
|
|
288
|
-
>
|
|
289
|
-
✨ Créer un compte gratuitement
|
|
290
|
-
</a>
|
|
291
|
-
</div>
|
|
292
|
-
</form>
|
|
293
|
-
) : (
|
|
294
|
-
<div className="space-y-4">
|
|
295
|
-
<p className="text-sm text-slate-600 dark:text-slate-400 bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
296
|
-
ℹ️ Sélectionnez une clé API pour créer une session sécurisée de
|
|
297
|
-
72h
|
|
298
|
-
</p>
|
|
299
|
-
|
|
300
|
-
<div className="space-y-3 max-h-96 overflow-y-auto">
|
|
301
|
-
{apiKeys.map((key) => (
|
|
302
|
-
<button
|
|
303
|
-
key={key.id}
|
|
304
|
-
onClick={() => handleSelectKey(key.id)}
|
|
305
|
-
disabled={!key.isActive || loading}
|
|
306
|
-
className={`w-full text-left px-5 py-4 border-2 rounded-lg transition-all transform hover:scale-[1.02] ${
|
|
307
|
-
key.isActive
|
|
308
|
-
? "border-slate-200 dark:border-slate-600 hover:border-violet-400 dark:hover:border-violet-500 hover:bg-gradient-to-r hover:from-violet-50 hover:to-purple-50 dark:hover:from-violet-900/20 dark:hover:to-purple-900/20"
|
|
309
|
-
: "border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 opacity-50 cursor-not-allowed"
|
|
310
|
-
}`}
|
|
311
|
-
>
|
|
312
|
-
<div className="flex items-start justify-between gap-2">
|
|
313
|
-
<div className="flex-1">
|
|
314
|
-
<div className="font-semibold text-slate-900 dark:text-white flex items-center gap-2">
|
|
315
|
-
{key.isActive ? "🔑" : "🔒"}
|
|
316
|
-
{key.name}
|
|
317
|
-
</div>
|
|
318
|
-
<div className="text-sm text-slate-500 dark:text-slate-400 mt-1 font-mono">
|
|
319
|
-
{key.keyPrefix}...
|
|
320
|
-
</div>
|
|
321
|
-
{key.scopes && (
|
|
322
|
-
<div className="flex gap-1 mt-2 flex-wrap">
|
|
323
|
-
{key.scopes.slice(0, 3).map((scope: string) => (
|
|
324
|
-
<span
|
|
325
|
-
key={scope}
|
|
326
|
-
className="text-xs px-2 py-1 bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 rounded"
|
|
327
|
-
>
|
|
328
|
-
{scope}
|
|
329
|
-
</span>
|
|
330
|
-
))}
|
|
331
|
-
{key.scopes.length > 3 && (
|
|
332
|
-
<span className="text-xs px-2 py-1 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-400 rounded">
|
|
333
|
-
+{key.scopes.length - 3}
|
|
334
|
-
</span>
|
|
335
|
-
)}
|
|
336
|
-
</div>
|
|
337
|
-
)}
|
|
338
|
-
</div>
|
|
339
|
-
{key.isActive ? (
|
|
340
|
-
<svg
|
|
341
|
-
className="w-5 h-5 text-violet-500 flex-shrink-0 mt-1"
|
|
342
|
-
fill="none"
|
|
343
|
-
stroke="currentColor"
|
|
344
|
-
viewBox="0 0 24 24"
|
|
345
|
-
>
|
|
346
|
-
<path
|
|
347
|
-
strokeLinecap="round"
|
|
348
|
-
strokeLinejoin="round"
|
|
349
|
-
strokeWidth={2}
|
|
350
|
-
d="M9 5l7 7-7 7"
|
|
351
|
-
/>
|
|
352
|
-
</svg>
|
|
353
|
-
) : (
|
|
354
|
-
<span className="text-xs text-red-600 dark:text-red-400 font-semibold bg-red-100 dark:bg-red-900/30 px-2 py-1 rounded">
|
|
355
|
-
Inactive
|
|
356
|
-
</span>
|
|
357
|
-
)}
|
|
358
|
-
</div>
|
|
359
|
-
</button>
|
|
360
|
-
))}
|
|
361
|
-
</div>
|
|
362
|
-
|
|
363
|
-
{error && (
|
|
364
|
-
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 rounded">
|
|
365
|
-
<div className="flex items-start gap-2">
|
|
366
|
-
<span className="text-red-500 text-xl">⚠️</span>
|
|
367
|
-
<p className="text-red-700 dark:text-red-400 text-sm font-medium">
|
|
368
|
-
{error}
|
|
369
|
-
</p>
|
|
370
|
-
</div>
|
|
371
|
-
</div>
|
|
372
|
-
)}
|
|
373
|
-
</div>
|
|
374
|
-
)}
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
);
|
|
82
|
+
export function LBAuthModal({ onClose }: LBAuthModalProps) {
|
|
83
|
+
return <LBSigninModal isOpen onClose={() => onClose(false)} />;
|
|
379
84
|
}
|
|
380
|
-
|
|
381
|
-
export { LBAuthModal };
|