@agent-relay/dashboard 2.0.81 → 2.0.82

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 (244) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
  3. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
  4. package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
  5. package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
  6. package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
  7. package/out/about.html +2 -2
  8. package/out/about.txt +1 -1
  9. package/out/app/onboarding.html +1 -1
  10. package/out/app/onboarding.txt +1 -1
  11. package/out/app.html +1 -1
  12. package/out/app.txt +2 -2
  13. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  14. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  15. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  16. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  17. package/out/blog.html +2 -2
  18. package/out/blog.txt +1 -1
  19. package/out/careers.html +2 -2
  20. package/out/careers.txt +1 -1
  21. package/out/changelog.html +2 -2
  22. package/out/changelog.txt +1 -1
  23. package/out/cloud/link.html +1 -1
  24. package/out/cloud/link.txt +2 -2
  25. package/out/complete-profile.html +2 -2
  26. package/out/complete-profile.txt +1 -1
  27. package/out/connect-repos.html +1 -1
  28. package/out/connect-repos.txt +1 -1
  29. package/out/contact.html +2 -2
  30. package/out/contact.txt +1 -1
  31. package/out/docs.html +2 -2
  32. package/out/docs.txt +1 -1
  33. package/out/history.html +1 -1
  34. package/out/history.txt +2 -2
  35. package/out/index.html +1 -1
  36. package/out/index.txt +2 -2
  37. package/out/login.html +2 -2
  38. package/out/login.txt +1 -1
  39. package/out/metrics.html +1 -1
  40. package/out/metrics.txt +2 -2
  41. package/out/pricing.html +2 -2
  42. package/out/pricing.txt +1 -1
  43. package/out/privacy.html +2 -2
  44. package/out/privacy.txt +1 -1
  45. package/out/providers/setup/claude.html +1 -1
  46. package/out/providers/setup/claude.txt +1 -1
  47. package/out/providers/setup/codex.html +1 -1
  48. package/out/providers/setup/codex.txt +1 -1
  49. package/out/providers/setup/cursor.html +1 -1
  50. package/out/providers/setup/cursor.txt +1 -1
  51. package/out/providers.html +1 -1
  52. package/out/providers.txt +1 -1
  53. package/out/security.html +2 -2
  54. package/out/security.txt +1 -1
  55. package/out/signup.html +2 -2
  56. package/out/signup.txt +1 -1
  57. package/out/terms.html +2 -2
  58. package/out/terms.txt +1 -1
  59. package/package.json +7 -1
  60. package/src/app/about/page.tsx +7 -0
  61. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
  62. package/src/app/app/[[...slug]]/page.tsx +23 -0
  63. package/src/app/app/onboarding/page.tsx +394 -0
  64. package/src/app/apple-icon.png +0 -0
  65. package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
  66. package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
  67. package/src/app/blog/page.tsx +15 -0
  68. package/src/app/careers/page.tsx +7 -0
  69. package/src/app/changelog/page.tsx +7 -0
  70. package/src/app/cloud/link/page.tsx +464 -0
  71. package/src/app/complete-profile/page.tsx +204 -0
  72. package/src/app/connect-repos/page.tsx +410 -0
  73. package/src/app/contact/page.tsx +7 -0
  74. package/src/app/docs/page.tsx +7 -0
  75. package/src/app/favicon.png +0 -0
  76. package/src/app/globals.css +200 -0
  77. package/src/app/history/page.tsx +658 -0
  78. package/src/app/layout.tsx +25 -0
  79. package/src/app/login/page.tsx +424 -0
  80. package/src/app/metrics/page.tsx +781 -0
  81. package/src/app/page.tsx +59 -0
  82. package/src/app/pricing/page.tsx +7 -0
  83. package/src/app/privacy/page.tsx +7 -0
  84. package/src/app/providers/page.tsx +193 -0
  85. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
  86. package/src/app/providers/setup/[provider]/constants.ts +35 -0
  87. package/src/app/providers/setup/[provider]/page.tsx +42 -0
  88. package/src/app/security/page.tsx +7 -0
  89. package/src/app/signup/page.tsx +533 -0
  90. package/src/app/terms/page.tsx +7 -0
  91. package/src/components/ActivityFeed.tsx +216 -0
  92. package/src/components/AddWorkspaceModal.tsx +170 -0
  93. package/src/components/AgentCard.test.tsx +134 -0
  94. package/src/components/AgentCard.tsx +585 -0
  95. package/src/components/AgentList.test.tsx +147 -0
  96. package/src/components/AgentList.tsx +419 -0
  97. package/src/components/AgentLogPreview.tsx +173 -0
  98. package/src/components/AgentProfilePanel.tsx +569 -0
  99. package/src/components/App.tsx +3424 -0
  100. package/src/components/BillingPanel.tsx +922 -0
  101. package/src/components/BillingResult.tsx +447 -0
  102. package/src/components/BroadcastComposer.tsx +690 -0
  103. package/src/components/ChannelAdminPanel.tsx +773 -0
  104. package/src/components/ChannelBrowser.tsx +385 -0
  105. package/src/components/ChannelChat.tsx +261 -0
  106. package/src/components/ChannelSidebar.tsx +399 -0
  107. package/src/components/CloudSessionProvider.tsx +130 -0
  108. package/src/components/CommandPalette.tsx +815 -0
  109. package/src/components/ConfirmationDialog.tsx +133 -0
  110. package/src/components/ConversationHistory.tsx +518 -0
  111. package/src/components/CoordinatorPanel.tsx +956 -0
  112. package/src/components/DecisionQueue.tsx +717 -0
  113. package/src/components/DirectMessageView.tsx +164 -0
  114. package/src/components/FileAutocomplete.tsx +368 -0
  115. package/src/components/FleetOverview.tsx +278 -0
  116. package/src/components/LogViewer.tsx +310 -0
  117. package/src/components/LogViewerPanel.tsx +482 -0
  118. package/src/components/Logo.tsx +284 -0
  119. package/src/components/MentionAutocomplete.tsx +384 -0
  120. package/src/components/MessageComposer.tsx +473 -0
  121. package/src/components/MessageList.tsx +725 -0
  122. package/src/components/MessageSenderName.tsx +91 -0
  123. package/src/components/MessageStatusIndicator.tsx +142 -0
  124. package/src/components/NewConversationModal.tsx +400 -0
  125. package/src/components/NotificationToast.tsx +488 -0
  126. package/src/components/OnlineUsersIndicator.tsx +164 -0
  127. package/src/components/Pagination.tsx +124 -0
  128. package/src/components/PricingPlans.tsx +386 -0
  129. package/src/components/ProjectList.tsx +711 -0
  130. package/src/components/ProviderAuthFlow.tsx +343 -0
  131. package/src/components/ProviderConnectionList.tsx +375 -0
  132. package/src/components/ProvisioningProgress.tsx +730 -0
  133. package/src/components/ReactionChips.tsx +70 -0
  134. package/src/components/ReactionPicker.tsx +121 -0
  135. package/src/components/RepoAccessPanel.tsx +787 -0
  136. package/src/components/RepositoriesPanel.tsx +901 -0
  137. package/src/components/ServerCard.tsx +202 -0
  138. package/src/components/SessionExpiredModal.tsx +128 -0
  139. package/src/components/SpawnModal.test.tsx +190 -0
  140. package/src/components/SpawnModal.tsx +1001 -0
  141. package/src/components/TaskAssignmentUI.tsx +375 -0
  142. package/src/components/TerminalProviderSetup.tsx +517 -0
  143. package/src/components/ThemeProvider.tsx +159 -0
  144. package/src/components/ThinkingIndicator.tsx +231 -0
  145. package/src/components/ThreadList.tsx +198 -0
  146. package/src/components/ThreadPanel.tsx +405 -0
  147. package/src/components/TrajectoryViewer.tsx +698 -0
  148. package/src/components/TypingIndicator.tsx +69 -0
  149. package/src/components/UsageBanner.tsx +231 -0
  150. package/src/components/UserProfilePanel.tsx +233 -0
  151. package/src/components/WorkspaceContext.tsx +95 -0
  152. package/src/components/WorkspaceSelector.tsx +234 -0
  153. package/src/components/WorkspaceStatusIndicator.tsx +396 -0
  154. package/src/components/XTermInteractive.tsx +516 -0
  155. package/src/components/XTermLogViewer.tsx +719 -0
  156. package/src/components/channels/ChannelDialogs.tsx +1411 -0
  157. package/src/components/channels/ChannelHeader.tsx +317 -0
  158. package/src/components/channels/ChannelMessageList.tsx +463 -0
  159. package/src/components/channels/ChannelViewV1.tsx +146 -0
  160. package/src/components/channels/MessageInput.tsx +302 -0
  161. package/src/components/channels/SearchInput.tsx +172 -0
  162. package/src/components/channels/SearchResults.tsx +336 -0
  163. package/src/components/channels/api.test.ts +1527 -0
  164. package/src/components/channels/api.ts +703 -0
  165. package/src/components/channels/index.ts +76 -0
  166. package/src/components/channels/mockApi.ts +344 -0
  167. package/src/components/channels/types.ts +566 -0
  168. package/src/components/hooks/index.ts +58 -0
  169. package/src/components/hooks/useAgentLogs.ts +504 -0
  170. package/src/components/hooks/useAgents.ts +127 -0
  171. package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
  172. package/src/components/hooks/useBroadcastDedup.ts +86 -0
  173. package/src/components/hooks/useChannelAdmin.ts +329 -0
  174. package/src/components/hooks/useChannelBrowser.ts +239 -0
  175. package/src/components/hooks/useChannelCommands.ts +138 -0
  176. package/src/components/hooks/useChannels.ts +367 -0
  177. package/src/components/hooks/useDebounce.ts +29 -0
  178. package/src/components/hooks/useDirectMessage.test.ts +952 -0
  179. package/src/components/hooks/useDirectMessage.ts +141 -0
  180. package/src/components/hooks/useMessages.ts +310 -0
  181. package/src/components/hooks/useOrchestrator.test.ts +165 -0
  182. package/src/components/hooks/useOrchestrator.ts +424 -0
  183. package/src/components/hooks/usePinnedAgents.test.ts +356 -0
  184. package/src/components/hooks/usePinnedAgents.ts +140 -0
  185. package/src/components/hooks/usePresence.test.ts +245 -0
  186. package/src/components/hooks/usePresence.ts +377 -0
  187. package/src/components/hooks/useRecentRepos.ts +130 -0
  188. package/src/components/hooks/useSession.ts +209 -0
  189. package/src/components/hooks/useThread.ts +138 -0
  190. package/src/components/hooks/useTrajectory.ts +265 -0
  191. package/src/components/hooks/useWebSocket.ts +290 -0
  192. package/src/components/hooks/useWorkspaceMembers.ts +132 -0
  193. package/src/components/hooks/useWorkspaceRepos.ts +73 -0
  194. package/src/components/hooks/useWorkspaceStatus.ts +237 -0
  195. package/src/components/index.ts +81 -0
  196. package/src/components/layout/Header.tsx +311 -0
  197. package/src/components/layout/RepoContextHeader.tsx +361 -0
  198. package/src/components/layout/Sidebar.archive.test.tsx +126 -0
  199. package/src/components/layout/Sidebar.test.tsx +691 -0
  200. package/src/components/layout/Sidebar.tsx +900 -0
  201. package/src/components/layout/index.ts +7 -0
  202. package/src/components/settings/BillingSettingsPanel.tsx +564 -0
  203. package/src/components/settings/SettingsPage.tsx +683 -0
  204. package/src/components/settings/TeamSettingsPanel.tsx +560 -0
  205. package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
  206. package/src/components/settings/index.ts +11 -0
  207. package/src/components/settings/types.ts +79 -0
  208. package/src/components/utils/messageFormatting.test.tsx +331 -0
  209. package/src/components/utils/messageFormatting.tsx +597 -0
  210. package/src/index.ts +63 -0
  211. package/src/landing/AboutPage.tsx +77 -0
  212. package/src/landing/BlogContent.tsx +187 -0
  213. package/src/landing/BlogPage.tsx +47 -0
  214. package/src/landing/CareersPage.tsx +53 -0
  215. package/src/landing/ChangelogPage.tsx +33 -0
  216. package/src/landing/ContactPage.tsx +41 -0
  217. package/src/landing/DocsPage.tsx +43 -0
  218. package/src/landing/LandingPage.tsx +702 -0
  219. package/src/landing/PricingPage.tsx +549 -0
  220. package/src/landing/PrivacyPage.tsx +117 -0
  221. package/src/landing/SecurityPage.tsx +42 -0
  222. package/src/landing/StaticPage.tsx +165 -0
  223. package/src/landing/TermsPage.tsx +125 -0
  224. package/src/landing/blogData.ts +312 -0
  225. package/src/landing/index.ts +18 -0
  226. package/src/landing/styles.css +3673 -0
  227. package/src/lib/agent-merge.test.ts +43 -0
  228. package/src/lib/agent-merge.ts +35 -0
  229. package/src/lib/api.ts +1294 -0
  230. package/src/lib/cloudApi.ts +893 -0
  231. package/src/lib/colors.test.ts +175 -0
  232. package/src/lib/colors.ts +218 -0
  233. package/src/lib/config.ts +109 -0
  234. package/src/lib/hierarchy.ts +242 -0
  235. package/src/lib/stuckDetection.ts +142 -0
  236. package/src/lib/useUrlRouting.ts +190 -0
  237. package/src/types/index.ts +317 -0
  238. package/src/types/threading.ts +7 -0
  239. package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
  240. package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
  241. /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
  243. /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
  244. /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Login Page - GitHub OAuth via Nango or Email/Password
3
+ *
4
+ * Key: Initialize Nango on page load, not on click.
5
+ * This avoids popup blockers by ensuring openConnectUI is synchronous.
6
+ * See: https://arveknudsen.com/posts/avoiding-popup-blocking-when-authing-with-google/
7
+ */
8
+
9
+ 'use client';
10
+
11
+ import React, { useState, useEffect, useRef, Suspense } from 'react';
12
+ import { useSearchParams } from 'next/navigation';
13
+ import Nango from '@nangohq/frontend';
14
+ import { LogoIcon } from '../../components/Logo';
15
+
16
+ // Loading fallback for Suspense
17
+ function LoginLoading() {
18
+ return (
19
+ <div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex flex-col items-center justify-center p-4">
20
+ <div className="relative z-10 w-full max-w-md">
21
+ <div className="flex flex-col items-center mb-8">
22
+ <LogoIcon size={48} withGlow={true} />
23
+ <h1 className="mt-4 text-2xl font-bold text-white">Agent Relay</h1>
24
+ <p className="mt-2 text-text-muted">Loading...</p>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
30
+
31
+ type AuthMethod = 'github' | 'email';
32
+
33
+ // Main login content that uses useSearchParams
34
+ function LoginContent() {
35
+ const searchParams = useSearchParams();
36
+ const [authMethod, setAuthMethod] = useState<AuthMethod>('github');
37
+ const [isReady, setIsReady] = useState(false);
38
+ const [isAuthenticating, setIsAuthenticating] = useState(false);
39
+ const [authStatus, setAuthStatus] = useState<string>('');
40
+ const [error, setError] = useState('');
41
+
42
+ // Email form state
43
+ const [email, setEmail] = useState('');
44
+ const [password, setPassword] = useState('');
45
+
46
+ // Get return URL from query params (used by cloud link flow)
47
+ const returnUrl = searchParams.get('return');
48
+
49
+ // Store Nango instance and session token - initialized on mount
50
+ const nangoRef = useRef<InstanceType<typeof Nango> | null>(null);
51
+
52
+ // Initialize Nango with session token on page load
53
+ useEffect(() => {
54
+ let mounted = true;
55
+
56
+ const init = async () => {
57
+ try {
58
+ const response = await fetch('/api/auth/nango/login-session', {
59
+ credentials: 'include',
60
+ });
61
+ const data = await response.json();
62
+
63
+ if (!mounted) return;
64
+
65
+ if (!response.ok || !data.sessionToken) {
66
+ // Don't set error - email login doesn't need Nango
67
+ setIsReady(true);
68
+ return;
69
+ }
70
+
71
+ // Create Nango instance NOW, not on click
72
+ nangoRef.current = new Nango({ connectSessionToken: data.sessionToken });
73
+ setIsReady(true);
74
+ } catch (err) {
75
+ if (mounted) {
76
+ console.error('Init error:', err);
77
+ // Still allow email login even if Nango fails
78
+ setIsReady(true);
79
+ }
80
+ }
81
+ };
82
+
83
+ init();
84
+ return () => { mounted = false; };
85
+ }, []);
86
+
87
+ const checkAuthStatus = async (connectionId: string): Promise<{ ready: boolean; hasRepos?: boolean; needsEmail?: boolean }> => {
88
+ const response = await fetch(`/api/auth/nango/login-status/${connectionId}`, {
89
+ credentials: 'include',
90
+ });
91
+ if (!response.ok) {
92
+ throw new Error('Auth status not ready');
93
+ }
94
+ return response.json();
95
+ };
96
+
97
+ const handleAuthSuccess = async (connectionId: string) => {
98
+ try {
99
+ setAuthStatus('Completing authentication...');
100
+
101
+ const pollStartTime = Date.now();
102
+ const maxPollTime = 30000;
103
+ const pollInterval = 1000;
104
+
105
+ const pollForAuth = async (): Promise<void> => {
106
+ const elapsed = Date.now() - pollStartTime;
107
+
108
+ if (elapsed > maxPollTime) {
109
+ throw new Error('Authentication timed out. Please try again.');
110
+ }
111
+
112
+ try {
113
+ const result = await checkAuthStatus(connectionId);
114
+ if (result && result.ready) {
115
+ // If user needs to provide email, redirect to complete-profile
116
+ if (result.needsEmail) {
117
+ window.location.href = '/complete-profile';
118
+ return;
119
+ }
120
+ // Redirect to return URL if provided (e.g., cloud link flow),
121
+ // otherwise to connect-repos if no repos, or to app
122
+ if (returnUrl) {
123
+ window.location.href = returnUrl;
124
+ } else {
125
+ window.location.href = result.hasRepos ? '/app' : '/connect-repos';
126
+ }
127
+ return;
128
+ }
129
+
130
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
131
+ return pollForAuth();
132
+ } catch {
133
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
134
+ return pollForAuth();
135
+ }
136
+ };
137
+
138
+ await pollForAuth();
139
+ } catch (err) {
140
+ console.error('[AUTH] Authentication error:', err);
141
+ setError(err instanceof Error ? err.message : 'Authentication failed');
142
+ setIsAuthenticating(false);
143
+ setAuthStatus('');
144
+ }
145
+ };
146
+
147
+ // Use nango.auth() instead of openConnectUI to avoid popup blocker issues
148
+ const handleGitHubAuth = async () => {
149
+ if (!nangoRef.current) {
150
+ setError('GitHub login not available. Please use email login or refresh the page.');
151
+ return;
152
+ }
153
+
154
+ setIsAuthenticating(true);
155
+ setError('');
156
+ setAuthStatus('Connecting to GitHub...');
157
+
158
+ try {
159
+ const result = await nangoRef.current.auth('github');
160
+ if (result && 'connectionId' in result) {
161
+ await handleAuthSuccess(result.connectionId);
162
+ } else {
163
+ throw new Error('No connection ID returned');
164
+ }
165
+ } catch (err: unknown) {
166
+ const error = err as Error & { type?: string };
167
+ console.error('GitHub auth error:', error);
168
+
169
+ // Don't show error for user-cancelled auth
170
+ if (error.type === 'user_cancelled' || error.message?.includes('closed')) {
171
+ setIsAuthenticating(false);
172
+ setAuthStatus('');
173
+ // Re-initialize for next attempt
174
+ fetch('/api/auth/nango/login-session', { credentials: 'include' })
175
+ .then(res => res.json())
176
+ .then(data => {
177
+ if (data.sessionToken) {
178
+ nangoRef.current = new Nango({ connectSessionToken: data.sessionToken });
179
+ setIsReady(true);
180
+ }
181
+ });
182
+ return;
183
+ }
184
+
185
+ setError(error.message || 'Authentication failed');
186
+ setIsAuthenticating(false);
187
+ setAuthStatus('');
188
+ }
189
+ };
190
+
191
+ // Handle email login
192
+ const handleEmailLogin = async (e: React.FormEvent) => {
193
+ e.preventDefault();
194
+ setError('');
195
+ setIsAuthenticating(true);
196
+ setAuthStatus('Signing in...');
197
+
198
+ try {
199
+ // Get CSRF token first
200
+ const csrfResponse = await fetch('/api/auth/session', { credentials: 'include' });
201
+ const csrfToken = csrfResponse.headers.get('x-csrf-token');
202
+
203
+ const response = await fetch('/api/auth/email/login', {
204
+ method: 'POST',
205
+ headers: {
206
+ 'Content-Type': 'application/json',
207
+ ...(csrfToken && { 'x-csrf-token': csrfToken }),
208
+ },
209
+ credentials: 'include',
210
+ body: JSON.stringify({ email, password }),
211
+ });
212
+
213
+ const data = await response.json();
214
+
215
+ if (!response.ok) {
216
+ // If user has GitHub account, suggest that
217
+ if (data.code === 'GITHUB_ACCOUNT') {
218
+ setError(data.error);
219
+ setAuthMethod('github');
220
+ } else {
221
+ setError(data.error || 'Login failed');
222
+ }
223
+ setIsAuthenticating(false);
224
+ setAuthStatus('');
225
+ return;
226
+ }
227
+
228
+ // Success - redirect
229
+ setAuthStatus('Login successful! Redirecting...');
230
+ if (returnUrl) {
231
+ window.location.href = returnUrl;
232
+ } else {
233
+ window.location.href = '/app';
234
+ }
235
+ } catch (err) {
236
+ console.error('Email login error:', err);
237
+ setError('Failed to connect. Please try again.');
238
+ setIsAuthenticating(false);
239
+ setAuthStatus('');
240
+ }
241
+ };
242
+
243
+ const isLoading = !isReady || isAuthenticating;
244
+
245
+ return (
246
+ <div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex flex-col items-center justify-center p-4">
247
+ {/* Background grid */}
248
+ <div className="fixed inset-0 opacity-10">
249
+ <div
250
+ className="absolute inset-0"
251
+ style={{
252
+ backgroundImage: `linear-gradient(rgba(0, 217, 255, 0.1) 1px, transparent 1px),
253
+ linear-gradient(90deg, rgba(0, 217, 255, 0.1) 1px, transparent 1px)`,
254
+ backgroundSize: '50px 50px',
255
+ }}
256
+ />
257
+ </div>
258
+
259
+ {/* Content */}
260
+ <div className="relative z-10 w-full max-w-md">
261
+ {/* Logo */}
262
+ <div className="flex flex-col items-center mb-8">
263
+ <LogoIcon size={48} withGlow={true} />
264
+ <h1 className="mt-4 text-2xl font-bold text-white">Agent Relay</h1>
265
+ <p className="mt-2 text-text-muted">Sign in to continue</p>
266
+ </div>
267
+
268
+ {/* Login Card */}
269
+ <div className="bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-2xl p-8 shadow-xl">
270
+ {/* Auth method tabs */}
271
+ <div className="flex mb-6 bg-bg-secondary/50 rounded-lg p-1">
272
+ <button
273
+ type="button"
274
+ onClick={() => setAuthMethod('github')}
275
+ className={`flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors ${
276
+ authMethod === 'github'
277
+ ? 'bg-bg-primary text-white shadow-sm'
278
+ : 'text-text-muted hover:text-white'
279
+ }`}
280
+ >
281
+ GitHub
282
+ </button>
283
+ <button
284
+ type="button"
285
+ onClick={() => setAuthMethod('email')}
286
+ className={`flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors ${
287
+ authMethod === 'email'
288
+ ? 'bg-bg-primary text-white shadow-sm'
289
+ : 'text-text-muted hover:text-white'
290
+ }`}
291
+ >
292
+ Email
293
+ </button>
294
+ </div>
295
+
296
+ <div>
297
+ {error && (
298
+ <div className="mb-4 p-3 bg-error/10 border border-error/20 rounded-lg">
299
+ <p className="text-error text-sm">{error}</p>
300
+ </div>
301
+ )}
302
+
303
+ {authMethod === 'github' ? (
304
+ <button
305
+ type="button"
306
+ onClick={handleGitHubAuth}
307
+ disabled={isLoading}
308
+ className="w-full py-4 px-6 bg-[#24292e] hover:bg-[#2f363d] border border-[#444d56] rounded-xl text-white font-medium flex items-center justify-center gap-3 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
309
+ >
310
+ {!isReady ? (
311
+ <>
312
+ <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
313
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
314
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
315
+ </svg>
316
+ <span>Loading...</span>
317
+ </>
318
+ ) : isAuthenticating ? (
319
+ <>
320
+ <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
321
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
322
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
323
+ </svg>
324
+ <span>{authStatus || 'Connecting...'}</span>
325
+ </>
326
+ ) : (
327
+ <>
328
+ <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
329
+ <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
330
+ </svg>
331
+ <span>Continue with GitHub</span>
332
+ </>
333
+ )}
334
+ </button>
335
+ ) : (
336
+ <form onSubmit={handleEmailLogin} className="space-y-4">
337
+ <div>
338
+ <label htmlFor="email" className="block text-sm font-medium text-text-secondary mb-2">
339
+ Email
340
+ </label>
341
+ <input
342
+ type="email"
343
+ id="email"
344
+ value={email}
345
+ onChange={(e) => setEmail(e.target.value)}
346
+ required
347
+ disabled={isAuthenticating}
348
+ className="w-full px-4 py-3 bg-bg-secondary border border-border-subtle rounded-xl text-white placeholder-text-muted focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 focus:border-accent-cyan disabled:opacity-50"
349
+ placeholder="you@example.com"
350
+ />
351
+ </div>
352
+ <div>
353
+ <label htmlFor="password" className="block text-sm font-medium text-text-secondary mb-2">
354
+ Password
355
+ </label>
356
+ <input
357
+ type="password"
358
+ id="password"
359
+ value={password}
360
+ onChange={(e) => setPassword(e.target.value)}
361
+ required
362
+ disabled={isAuthenticating}
363
+ className="w-full px-4 py-3 bg-bg-secondary border border-border-subtle rounded-xl text-white placeholder-text-muted focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 focus:border-accent-cyan disabled:opacity-50"
364
+ placeholder="Enter your password"
365
+ />
366
+ </div>
367
+ <button
368
+ type="submit"
369
+ disabled={isLoading || !email || !password}
370
+ className="w-full py-4 px-6 bg-accent-cyan hover:bg-accent-cyan/90 rounded-xl text-black font-medium flex items-center justify-center gap-3 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
371
+ >
372
+ {isAuthenticating ? (
373
+ <>
374
+ <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
375
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
376
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
377
+ </svg>
378
+ <span>{authStatus || 'Signing in...'}</span>
379
+ </>
380
+ ) : (
381
+ <span>Sign in with Email</span>
382
+ )}
383
+ </button>
384
+ </form>
385
+ )}
386
+
387
+ <p className="mt-6 text-center text-text-muted text-sm">
388
+ By signing in, you agree to our{' '}
389
+ <a href="/terms" className="text-accent-cyan hover:underline">Terms of Service</a>
390
+ {' '}and{' '}
391
+ <a href="/privacy" className="text-accent-cyan hover:underline">Privacy Policy</a>
392
+ </p>
393
+ </div>
394
+ </div>
395
+
396
+ {/* Sign up link */}
397
+ <div className="mt-6 text-center">
398
+ <p className="text-text-muted">
399
+ Don't have an account?{' '}
400
+ <a href="/signup" className="text-accent-cyan hover:underline font-medium">
401
+ Sign up
402
+ </a>
403
+ </p>
404
+ </div>
405
+
406
+ {/* Back to home */}
407
+ <div className="mt-4 text-center">
408
+ <a href="/" className="text-text-muted hover:text-white transition-colors text-sm">
409
+ Back to home
410
+ </a>
411
+ </div>
412
+ </div>
413
+ </div>
414
+ );
415
+ }
416
+
417
+ // Export page wrapped in Suspense for static generation
418
+ export default function LoginPage() {
419
+ return (
420
+ <Suspense fallback={<LoginLoading />}>
421
+ <LoginContent />
422
+ </Suspense>
423
+ );
424
+ }