@cortexmemory/cli 0.27.1 → 0.27.3
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/commands/convex.js +1 -1
- package/dist/commands/convex.js.map +1 -1
- package/dist/commands/deploy.d.ts +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +771 -144
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +89 -26
- package/dist/commands/dev.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/app-template-sync.d.ts +95 -0
- package/dist/utils/app-template-sync.d.ts.map +1 -0
- package/dist/utils/app-template-sync.js +425 -0
- package/dist/utils/app-template-sync.js.map +1 -0
- package/dist/utils/deployment-selector.d.ts +21 -0
- package/dist/utils/deployment-selector.d.ts.map +1 -1
- package/dist/utils/deployment-selector.js +32 -0
- package/dist/utils/deployment-selector.js.map +1 -1
- package/dist/utils/init/graph-setup.d.ts.map +1 -1
- package/dist/utils/init/graph-setup.js +13 -2
- package/dist/utils/init/graph-setup.js.map +1 -1
- package/package.json +1 -1
- package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +30 -0
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +83 -0
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +94 -0
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +59 -0
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +83 -2
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +179 -0
- package/templates/vercel-ai-quickstart/app/globals.css +161 -0
- package/templates/vercel-ai-quickstart/app/page.tsx +93 -8
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +139 -0
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +283 -0
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +323 -0
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +113 -16
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +202 -0
- package/templates/vercel-ai-quickstart/jest.config.js +45 -0
- package/templates/vercel-ai-quickstart/lib/cortex.ts +27 -0
- package/templates/vercel-ai-quickstart/lib/password.ts +120 -0
- package/templates/vercel-ai-quickstart/next.config.js +20 -0
- package/templates/vercel-ai-quickstart/package.json +7 -2
- package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +263 -0
- package/templates/vercel-ai-quickstart/tests/helpers/setup.ts +48 -0
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +455 -0
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +461 -0
- package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +228 -0
- package/templates/vercel-ai-quickstart/tsconfig.json +1 -1
|
@@ -112,3 +112,164 @@
|
|
|
112
112
|
font-size: 0.875rem;
|
|
113
113
|
line-height: 1.5;
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
117
|
+
Auth Screen Animations
|
|
118
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
119
|
+
|
|
120
|
+
@keyframes fade-in-up {
|
|
121
|
+
from {
|
|
122
|
+
opacity: 0;
|
|
123
|
+
transform: translateY(20px);
|
|
124
|
+
}
|
|
125
|
+
to {
|
|
126
|
+
opacity: 1;
|
|
127
|
+
transform: translateY(0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@keyframes logo-pulse {
|
|
132
|
+
0%, 100% {
|
|
133
|
+
transform: scale(1);
|
|
134
|
+
box-shadow: 0 0 0 0 rgba(12, 140, 230, 0.4);
|
|
135
|
+
}
|
|
136
|
+
50% {
|
|
137
|
+
transform: scale(1.02);
|
|
138
|
+
box-shadow: 0 0 40px 10px rgba(12, 140, 230, 0.2);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.auth-screen {
|
|
143
|
+
animation: fade-in-up 0.5s ease-out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.auth-logo {
|
|
147
|
+
animation: logo-pulse 3s ease-in-out infinite;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
151
|
+
Sidebar Animations
|
|
152
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
153
|
+
|
|
154
|
+
@keyframes sidebar-slide-in {
|
|
155
|
+
from {
|
|
156
|
+
opacity: 0;
|
|
157
|
+
transform: translateX(-20px);
|
|
158
|
+
}
|
|
159
|
+
to {
|
|
160
|
+
opacity: 1;
|
|
161
|
+
transform: translateX(0);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@keyframes conversation-item-in {
|
|
166
|
+
from {
|
|
167
|
+
opacity: 0;
|
|
168
|
+
transform: translateX(-10px);
|
|
169
|
+
}
|
|
170
|
+
to {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
transform: translateX(0);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.sidebar-animate {
|
|
177
|
+
animation: sidebar-slide-in 0.3s ease-out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.conversation-item {
|
|
181
|
+
animation: conversation-item-in 0.2s ease-out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Staggered animation for conversation list */
|
|
185
|
+
.conversation-item:nth-child(1) { animation-delay: 0ms; }
|
|
186
|
+
.conversation-item:nth-child(2) { animation-delay: 30ms; }
|
|
187
|
+
.conversation-item:nth-child(3) { animation-delay: 60ms; }
|
|
188
|
+
.conversation-item:nth-child(4) { animation-delay: 90ms; }
|
|
189
|
+
.conversation-item:nth-child(5) { animation-delay: 120ms; }
|
|
190
|
+
.conversation-item:nth-child(n+6) { animation-delay: 150ms; }
|
|
191
|
+
|
|
192
|
+
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
193
|
+
Input Focus Animations
|
|
194
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
195
|
+
|
|
196
|
+
input:focus {
|
|
197
|
+
box-shadow: 0 0 0 2px rgba(12, 140, 230, 0.2);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
201
|
+
Button Hover Effects
|
|
202
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
203
|
+
|
|
204
|
+
.btn-primary {
|
|
205
|
+
position: relative;
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.btn-primary::after {
|
|
210
|
+
content: "";
|
|
211
|
+
position: absolute;
|
|
212
|
+
top: 50%;
|
|
213
|
+
left: 50%;
|
|
214
|
+
width: 0;
|
|
215
|
+
height: 0;
|
|
216
|
+
background: rgba(255, 255, 255, 0.1);
|
|
217
|
+
border-radius: 50%;
|
|
218
|
+
transform: translate(-50%, -50%);
|
|
219
|
+
transition: width 0.4s ease, height 0.4s ease;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.btn-primary:hover::after {
|
|
223
|
+
width: 300px;
|
|
224
|
+
height: 300px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
228
|
+
Loading States
|
|
229
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
230
|
+
|
|
231
|
+
@keyframes shimmer {
|
|
232
|
+
0% {
|
|
233
|
+
background-position: -200% 0;
|
|
234
|
+
}
|
|
235
|
+
100% {
|
|
236
|
+
background-position: 200% 0;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.skeleton {
|
|
241
|
+
background: linear-gradient(
|
|
242
|
+
90deg,
|
|
243
|
+
rgba(255, 255, 255, 0.05) 25%,
|
|
244
|
+
rgba(255, 255, 255, 0.1) 50%,
|
|
245
|
+
rgba(255, 255, 255, 0.05) 75%
|
|
246
|
+
);
|
|
247
|
+
background-size: 200% 100%;
|
|
248
|
+
animation: shimmer 1.5s infinite;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
252
|
+
Responsive Sidebar
|
|
253
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
|
254
|
+
|
|
255
|
+
@media (max-width: 1024px) {
|
|
256
|
+
/* On smaller screens, the layer flow visualization is hidden */
|
|
257
|
+
.layer-flow-panel {
|
|
258
|
+
display: none;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@media (max-width: 768px) {
|
|
263
|
+
/* On mobile, sidebar becomes collapsible */
|
|
264
|
+
.sidebar-mobile-hidden {
|
|
265
|
+
transform: translateX(-100%);
|
|
266
|
+
transition: transform 0.3s ease;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.sidebar-mobile-visible {
|
|
270
|
+
transform: translateX(0);
|
|
271
|
+
position: fixed;
|
|
272
|
+
z-index: 50;
|
|
273
|
+
height: 100vh;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import dynamic from "next/dynamic";
|
|
4
|
-
import { useState } from "react";
|
|
4
|
+
import { useState, useCallback } from "react";
|
|
5
5
|
import { useLayerTracking } from "@/lib/layer-tracking";
|
|
6
|
+
import { AuthProvider, useAuth } from "@/components/AuthProvider";
|
|
7
|
+
import { AdminSetup } from "@/components/AdminSetup";
|
|
8
|
+
import { LoginScreen } from "@/components/LoginScreen";
|
|
6
9
|
|
|
7
10
|
// Dynamic imports to avoid SSR issues with framer-motion
|
|
8
11
|
const ChatInterface = dynamic(
|
|
@@ -33,10 +36,22 @@ const HealthStatus = dynamic(
|
|
|
33
36
|
})),
|
|
34
37
|
{ ssr: false },
|
|
35
38
|
);
|
|
39
|
+
const ChatHistorySidebar = dynamic(
|
|
40
|
+
() =>
|
|
41
|
+
import("@/components/ChatHistorySidebar").then((m) => ({
|
|
42
|
+
default: m.ChatHistorySidebar,
|
|
43
|
+
})),
|
|
44
|
+
{ ssr: false },
|
|
45
|
+
);
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
48
|
+
// Main App Content (with auth)
|
|
49
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
50
|
+
|
|
51
|
+
function MainContent() {
|
|
52
|
+
const { isLoading, isAdminSetup, isAuthenticated, user } = useAuth();
|
|
38
53
|
const [memorySpaceId, setMemorySpaceId] = useState("quickstart-demo");
|
|
39
|
-
const [
|
|
54
|
+
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null);
|
|
40
55
|
const {
|
|
41
56
|
layers,
|
|
42
57
|
isOrchestrating,
|
|
@@ -45,11 +60,59 @@ export default function Home() {
|
|
|
45
60
|
resetLayers,
|
|
46
61
|
} = useLayerTracking();
|
|
47
62
|
|
|
63
|
+
// Handle new chat
|
|
64
|
+
const handleNewChat = useCallback(() => {
|
|
65
|
+
setCurrentConversationId(null);
|
|
66
|
+
resetLayers();
|
|
67
|
+
}, [resetLayers]);
|
|
68
|
+
|
|
69
|
+
// Handle conversation selection
|
|
70
|
+
const handleSelectConversation = useCallback((conversationId: string) => {
|
|
71
|
+
setCurrentConversationId(conversationId);
|
|
72
|
+
resetLayers();
|
|
73
|
+
}, [resetLayers]);
|
|
74
|
+
|
|
75
|
+
// Handle conversation update (e.g., title change after first message)
|
|
76
|
+
const handleConversationUpdate = useCallback((conversationId: string) => {
|
|
77
|
+
// Update current conversation ID if it was null (new chat created)
|
|
78
|
+
if (!currentConversationId) {
|
|
79
|
+
setCurrentConversationId(conversationId);
|
|
80
|
+
}
|
|
81
|
+
}, [currentConversationId]);
|
|
82
|
+
|
|
83
|
+
// Loading state
|
|
84
|
+
if (isLoading) {
|
|
85
|
+
return (
|
|
86
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
87
|
+
<div className="text-center">
|
|
88
|
+
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center animate-pulse">
|
|
89
|
+
<span className="text-3xl">🧠</span>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="text-gray-400">Loading Cortex...</p>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// First-run: Admin setup
|
|
98
|
+
if (isAdminSetup === false) {
|
|
99
|
+
return <AdminSetup />;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Not authenticated: Login/Register
|
|
103
|
+
if (!isAuthenticated) {
|
|
104
|
+
return <LoginScreen />;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Get userId from authenticated user
|
|
108
|
+
const userId = user?.id || "demo-user";
|
|
109
|
+
|
|
110
|
+
// Main authenticated interface
|
|
48
111
|
return (
|
|
49
112
|
<main className="min-h-screen flex flex-col">
|
|
50
113
|
{/* Header */}
|
|
51
114
|
<header className="border-b border-white/10 px-6 py-4">
|
|
52
|
-
<div className="
|
|
115
|
+
<div className="flex items-center justify-between">
|
|
53
116
|
<div className="flex items-center gap-3">
|
|
54
117
|
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center">
|
|
55
118
|
<span className="text-xl">🧠</span>
|
|
@@ -72,20 +135,30 @@ export default function Home() {
|
|
|
72
135
|
</div>
|
|
73
136
|
</header>
|
|
74
137
|
|
|
75
|
-
{/* Main Content */}
|
|
138
|
+
{/* Main Content - Three Column Layout */}
|
|
76
139
|
<div className="flex-1 flex overflow-hidden">
|
|
77
|
-
{/* Chat
|
|
140
|
+
{/* Left: Chat History Sidebar */}
|
|
141
|
+
<ChatHistorySidebar
|
|
142
|
+
memorySpaceId={memorySpaceId}
|
|
143
|
+
currentConversationId={currentConversationId}
|
|
144
|
+
onSelectConversation={handleSelectConversation}
|
|
145
|
+
onNewChat={handleNewChat}
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
{/* Center: Chat Section */}
|
|
78
149
|
<div className="flex-1 flex flex-col border-r border-white/10">
|
|
79
150
|
<ChatInterface
|
|
80
151
|
memorySpaceId={memorySpaceId}
|
|
81
152
|
userId={userId}
|
|
153
|
+
conversationId={currentConversationId}
|
|
82
154
|
onOrchestrationStart={startOrchestration}
|
|
83
155
|
onLayerUpdate={updateLayer}
|
|
84
156
|
onReset={resetLayers}
|
|
157
|
+
onConversationUpdate={handleConversationUpdate}
|
|
85
158
|
/>
|
|
86
159
|
</div>
|
|
87
160
|
|
|
88
|
-
{/* Layer Flow Visualization */}
|
|
161
|
+
{/* Right: Layer Flow Visualization */}
|
|
89
162
|
<div className="w-[480px] flex flex-col bg-black/20">
|
|
90
163
|
<div className="p-4 border-b border-white/10">
|
|
91
164
|
<h2 className="font-semibold flex items-center gap-2">
|
|
@@ -110,7 +183,7 @@ export default function Home() {
|
|
|
110
183
|
|
|
111
184
|
{/* Footer */}
|
|
112
185
|
<footer className="border-t border-white/10 px-6 py-3">
|
|
113
|
-
<div className="
|
|
186
|
+
<div className="flex items-center justify-between text-sm text-gray-500">
|
|
114
187
|
<div className="flex items-center gap-4">
|
|
115
188
|
<span>Cortex SDK v0.24.0</span>
|
|
116
189
|
<span>•</span>
|
|
@@ -129,3 +202,15 @@ export default function Home() {
|
|
|
129
202
|
</main>
|
|
130
203
|
);
|
|
131
204
|
}
|
|
205
|
+
|
|
206
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
207
|
+
// Page Component (wraps with AuthProvider)
|
|
208
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
209
|
+
|
|
210
|
+
export default function Home() {
|
|
211
|
+
return (
|
|
212
|
+
<AuthProvider>
|
|
213
|
+
<MainContent />
|
|
214
|
+
</AuthProvider>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useAuth } from "./AuthProvider";
|
|
5
|
+
|
|
6
|
+
export function AdminSetup() {
|
|
7
|
+
const { setupAdmin, error, clearError } = useAuth();
|
|
8
|
+
const [password, setPassword] = useState("");
|
|
9
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
10
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
11
|
+
const [localError, setLocalError] = useState<string | null>(null);
|
|
12
|
+
|
|
13
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
setLocalError(null);
|
|
16
|
+
clearError();
|
|
17
|
+
|
|
18
|
+
if (password.length < 4) {
|
|
19
|
+
setLocalError("Password must be at least 4 characters");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (password !== confirmPassword) {
|
|
24
|
+
setLocalError("Passwords do not match");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
await setupAdmin(password);
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const displayError = localError || error;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="min-h-screen flex items-center justify-center p-4">
|
|
37
|
+
<div className="w-full max-w-md">
|
|
38
|
+
{/* Logo and Title */}
|
|
39
|
+
<div className="text-center mb-8">
|
|
40
|
+
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-cortex-500 to-cortex-700 flex items-center justify-center shadow-lg shadow-cortex-500/20">
|
|
41
|
+
<span className="text-4xl">🧠</span>
|
|
42
|
+
</div>
|
|
43
|
+
<h1 className="text-3xl font-bold mb-2">Welcome to Cortex</h1>
|
|
44
|
+
<p className="text-gray-400">Set up your admin password to get started</p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{/* Setup Form */}
|
|
48
|
+
<div className="glass rounded-2xl p-8">
|
|
49
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
50
|
+
<div>
|
|
51
|
+
<label
|
|
52
|
+
htmlFor="password"
|
|
53
|
+
className="block text-sm font-medium text-gray-300 mb-2"
|
|
54
|
+
>
|
|
55
|
+
Admin Password
|
|
56
|
+
</label>
|
|
57
|
+
<input
|
|
58
|
+
id="password"
|
|
59
|
+
type="password"
|
|
60
|
+
value={password}
|
|
61
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
62
|
+
placeholder="Enter a secure password"
|
|
63
|
+
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:border-cortex-500 transition-colors"
|
|
64
|
+
disabled={isLoading}
|
|
65
|
+
autoFocus
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div>
|
|
70
|
+
<label
|
|
71
|
+
htmlFor="confirmPassword"
|
|
72
|
+
className="block text-sm font-medium text-gray-300 mb-2"
|
|
73
|
+
>
|
|
74
|
+
Confirm Password
|
|
75
|
+
</label>
|
|
76
|
+
<input
|
|
77
|
+
id="confirmPassword"
|
|
78
|
+
type="password"
|
|
79
|
+
value={confirmPassword}
|
|
80
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
81
|
+
placeholder="Confirm your password"
|
|
82
|
+
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:border-cortex-500 transition-colors"
|
|
83
|
+
disabled={isLoading}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{displayError && (
|
|
88
|
+
<div className="p-3 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400 text-sm">
|
|
89
|
+
{displayError}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<button
|
|
94
|
+
type="submit"
|
|
95
|
+
disabled={isLoading || !password || !confirmPassword}
|
|
96
|
+
className="w-full py-3 bg-cortex-600 hover:bg-cortex-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-xl font-medium transition-colors flex items-center justify-center gap-2"
|
|
97
|
+
>
|
|
98
|
+
{isLoading ? (
|
|
99
|
+
<>
|
|
100
|
+
<svg
|
|
101
|
+
className="animate-spin h-5 w-5"
|
|
102
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
103
|
+
fill="none"
|
|
104
|
+
viewBox="0 0 24 24"
|
|
105
|
+
>
|
|
106
|
+
<circle
|
|
107
|
+
className="opacity-25"
|
|
108
|
+
cx="12"
|
|
109
|
+
cy="12"
|
|
110
|
+
r="10"
|
|
111
|
+
stroke="currentColor"
|
|
112
|
+
strokeWidth="4"
|
|
113
|
+
/>
|
|
114
|
+
<path
|
|
115
|
+
className="opacity-75"
|
|
116
|
+
fill="currentColor"
|
|
117
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
118
|
+
/>
|
|
119
|
+
</svg>
|
|
120
|
+
Setting up...
|
|
121
|
+
</>
|
|
122
|
+
) : (
|
|
123
|
+
"Set Admin Password"
|
|
124
|
+
)}
|
|
125
|
+
</button>
|
|
126
|
+
</form>
|
|
127
|
+
|
|
128
|
+
<div className="mt-6 pt-6 border-t border-white/10">
|
|
129
|
+
<p className="text-xs text-gray-500 text-center">
|
|
130
|
+
This password will be used to manage the quickstart demo.
|
|
131
|
+
<br />
|
|
132
|
+
You can create additional user accounts after setup.
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|