agent-relay 2.0.16 → 2.0.18
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/deploy/workspace/entrypoint.sh +35 -19
- package/deploy/workspace/git-credential-relay +82 -7
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/320-402ffc8646b31da1.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/83-26d2bde54616ee90.js +1 -0
- package/{packages/dashboard/ui-dist/_next/static/chunks/app/app/page-9d6bc8729b429956.js → dist/dashboard/out/_next/static/chunks/app/app/page-366fb7c078d4e9e0.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/complete-profile/page-dd64bbdf66b639cd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-435eceb0073be027.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-487fa38f041815c1.js → page-8119d4246743574e.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-c7a0a28341365ae0.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/{main-5a40a5ae29646e1b.js → main-311c3db74dcfadb7.js} +1 -1
- package/dist/dashboard/out/_next/static/css/{605dd4e30c91986f.css → 45361ce86b2847c4.css} +1 -1
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +1 -1
- package/dist/dashboard/out/complete-profile.html +5 -0
- package/dist/dashboard/out/complete-profile.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +1 -1
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -2
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +1 -1
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +2 -2
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +2 -2
- package/dist/src/cli/index.js +3 -1
- package/package.json +22 -21
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/package.json +8 -8
- package/packages/cloud/dist/api/auth.js +2 -0
- package/packages/cloud/dist/api/billing.js +4 -4
- package/packages/cloud/dist/api/email-auth.d.ts +11 -0
- package/packages/cloud/dist/api/email-auth.js +347 -0
- package/packages/cloud/dist/api/nango-auth.js +72 -5
- package/packages/cloud/dist/db/drizzle.d.ts +35 -1
- package/packages/cloud/dist/db/drizzle.js +136 -0
- package/packages/cloud/dist/db/index.d.ts +5 -4
- package/packages/cloud/dist/db/index.js +5 -3
- package/packages/cloud/dist/db/schema.d.ts +246 -2
- package/packages/cloud/dist/db/schema.js +39 -3
- package/packages/cloud/dist/provisioner/index.js +5 -1
- package/packages/cloud/dist/server.js +134 -24
- package/packages/cloud/dist/services/nango.d.ts +18 -0
- package/packages/cloud/dist/services/nango.js +32 -0
- package/packages/cloud/package.json +6 -6
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/dashboard/dist/server.js +36 -7
- package/packages/dashboard/package.json +13 -13
- package/packages/dashboard/ui/app/complete-profile/page.tsx +204 -0
- package/packages/dashboard/ui/app/login/page.tsx +182 -38
- package/packages/dashboard/ui/app/signup/page.tsx +244 -54
- package/packages/dashboard/ui/lib/cloudApi.ts +1 -0
- package/packages/dashboard/ui/react-components/App.tsx +1 -1
- package/packages/dashboard/ui/react-components/ProviderAuthFlow.tsx +10 -0
- package/packages/dashboard/ui/react-components/RepoAccessPanel.tsx +160 -3
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-402ffc8646b31da1.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/83-26d2bde54616ee90.js +1 -0
- package/{dist/dashboard/out/_next/static/chunks/app/app/page-9d6bc8729b429956.js → packages/dashboard/ui-dist/_next/static/chunks/app/app/page-366fb7c078d4e9e0.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/complete-profile/page-dd64bbdf66b639cd.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/app/login/page-435eceb0073be027.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/app/{page-487fa38f041815c1.js → page-8119d4246743574e.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-c7a0a28341365ae0.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/{main-5a40a5ae29646e1b.js → main-311c3db74dcfadb7.js} +1 -1
- package/packages/dashboard/ui-dist/_next/static/css/{605dd4e30c91986f.css → 45361ce86b2847c4.css} +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +2 -2
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
- package/packages/dashboard/ui-dist/complete-profile.html +5 -0
- package/packages/dashboard/ui-dist/complete-profile.txt +7 -0
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +1 -1
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +2 -2
- package/packages/dashboard/ui-dist/login.html +2 -2
- package/packages/dashboard/ui-dist/login.txt +2 -2
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +1 -1
- package/packages/dashboard/ui-dist/pricing.html +2 -2
- package/packages/dashboard/ui-dist/pricing.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +2 -2
- package/packages/dashboard/ui-dist/signup.html +2 -2
- package/packages/dashboard/ui-dist/signup.txt +2 -2
- package/packages/dashboard-server/dist/server.js +36 -7
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.js +17 -3
- package/packages/wrapper/package.json +6 -6
- package/relay-snippets/agent-policy-snippet.md +40 -0
- package/relay-snippets/agent-relay-protocol.md +101 -0
- package/relay-snippets/agent-relay-snippet.md +177 -0
- package/SESSION_HANDOFF.md +0 -67
- package/dist/dashboard/out/_next/static/chunks/320-23e5ffe6aa7eb934.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/83-4f08122d4e7e79a6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-a0ca6f7ca6a100b8.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-1ede2205b58649ca.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-23e5ffe6aa7eb934.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/83-4f08122d4e7e79a6.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/login/page-a0ca6f7ca6a100b8.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-1ede2205b58649ca.js +0 -1
- package/test-push.txt +0 -1
- /package/dist/dashboard/out/_next/static/{itBGQ1M8yMA_hC42DKCqv → JIjqkuDKNeoSg7KaMMuhx}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{itBGQ1M8yMA_hC42DKCqv → JIjqkuDKNeoSg7KaMMuhx}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{ML6Zby1B5OtZvl0Pa1zSZ → JIjqkuDKNeoSg7KaMMuhx}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{ML6Zby1B5OtZvl0Pa1zSZ → JIjqkuDKNeoSg7KaMMuhx}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{Ni5Di0TB0PDcrvEYBFRKd → nmkOi7bqeDmLMoWBih8lz}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{Ni5Di0TB0PDcrvEYBFRKd → nmkOi7bqeDmLMoWBih8lz}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{itBGQ1M8yMA_hC42DKCqv → wk_gKRNSPpWE-ZhGL6UMl}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{itBGQ1M8yMA_hC42DKCqv → wk_gKRNSPpWE-ZhGL6UMl}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complete Profile Page
|
|
3
|
+
*
|
|
4
|
+
* Prompts users to provide missing information after GitHub OAuth signup.
|
|
5
|
+
* Currently used to collect email address for GitHub users whose email
|
|
6
|
+
* is not publicly available.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use client';
|
|
10
|
+
|
|
11
|
+
import React, { useState, useEffect } from 'react';
|
|
12
|
+
import { LogoIcon } from '../../react-components/Logo';
|
|
13
|
+
|
|
14
|
+
export default function CompleteProfilePage() {
|
|
15
|
+
const [email, setEmail] = useState('');
|
|
16
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
17
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
18
|
+
const [error, setError] = useState('');
|
|
19
|
+
const [user, setUser] = useState<{
|
|
20
|
+
githubUsername?: string;
|
|
21
|
+
avatarUrl?: string;
|
|
22
|
+
email?: string;
|
|
23
|
+
} | null>(null);
|
|
24
|
+
|
|
25
|
+
// Check if user is logged in and needs email
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const checkSession = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch('/api/auth/me', { credentials: 'include' });
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
// Not logged in - redirect to login
|
|
32
|
+
window.location.href = '/login';
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
|
|
38
|
+
// If user already has email, redirect to app
|
|
39
|
+
if (data.user?.email) {
|
|
40
|
+
window.location.href = '/app';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setUser(data.user);
|
|
45
|
+
setIsLoading(false);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('Error checking session:', err);
|
|
48
|
+
window.location.href = '/login';
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
checkSession();
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
setError('');
|
|
58
|
+
setIsSubmitting(true);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Get CSRF token first
|
|
62
|
+
const csrfResponse = await fetch('/api/auth/session', { credentials: 'include' });
|
|
63
|
+
const csrfToken = csrfResponse.headers.get('x-csrf-token');
|
|
64
|
+
|
|
65
|
+
const response = await fetch('/api/auth/email/set-email', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
...(csrfToken && { 'x-csrf-token': csrfToken }),
|
|
70
|
+
},
|
|
71
|
+
credentials: 'include',
|
|
72
|
+
body: JSON.stringify({ email }),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
setError(data.error || 'Failed to set email');
|
|
79
|
+
setIsSubmitting(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Success - redirect to app
|
|
84
|
+
window.location.href = '/app';
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error('Error setting email:', err);
|
|
87
|
+
setError('Failed to connect. Please try again.');
|
|
88
|
+
setIsSubmitting(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (isLoading) {
|
|
93
|
+
return (
|
|
94
|
+
<div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex flex-col items-center justify-center p-4">
|
|
95
|
+
<div className="relative z-10 w-full max-w-md">
|
|
96
|
+
<div className="flex flex-col items-center mb-8">
|
|
97
|
+
<LogoIcon size={48} withGlow={true} />
|
|
98
|
+
<h1 className="mt-4 text-2xl font-bold text-white">Agent Relay</h1>
|
|
99
|
+
<p className="mt-2 text-text-muted">Loading...</p>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex flex-col items-center justify-center p-4">
|
|
108
|
+
{/* Background grid */}
|
|
109
|
+
<div className="fixed inset-0 opacity-10">
|
|
110
|
+
<div
|
|
111
|
+
className="absolute inset-0"
|
|
112
|
+
style={{
|
|
113
|
+
backgroundImage: `linear-gradient(rgba(0, 217, 255, 0.1) 1px, transparent 1px),
|
|
114
|
+
linear-gradient(90deg, rgba(0, 217, 255, 0.1) 1px, transparent 1px)`,
|
|
115
|
+
backgroundSize: '50px 50px',
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Content */}
|
|
121
|
+
<div className="relative z-10 w-full max-w-md">
|
|
122
|
+
{/* Logo */}
|
|
123
|
+
<div className="flex flex-col items-center mb-8">
|
|
124
|
+
<LogoIcon size={48} withGlow={true} />
|
|
125
|
+
<h1 className="mt-4 text-2xl font-bold text-white">Almost there!</h1>
|
|
126
|
+
<p className="mt-2 text-text-muted text-center">
|
|
127
|
+
Please provide your email to complete your account setup
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Card */}
|
|
132
|
+
<div className="bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-2xl p-8 shadow-xl">
|
|
133
|
+
{/* User info */}
|
|
134
|
+
{user && (
|
|
135
|
+
<div className="flex items-center gap-4 mb-6 pb-6 border-b border-border-subtle">
|
|
136
|
+
{user.avatarUrl ? (
|
|
137
|
+
<img
|
|
138
|
+
src={user.avatarUrl}
|
|
139
|
+
alt={user.githubUsername || 'User'}
|
|
140
|
+
className="w-12 h-12 rounded-full"
|
|
141
|
+
/>
|
|
142
|
+
) : (
|
|
143
|
+
<div className="w-12 h-12 rounded-full bg-bg-secondary flex items-center justify-center">
|
|
144
|
+
<svg className="w-6 h-6 text-text-muted" fill="currentColor" viewBox="0 0 24 24">
|
|
145
|
+
<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" />
|
|
146
|
+
</svg>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
<div>
|
|
150
|
+
<p className="font-medium text-white">{user.githubUsername}</p>
|
|
151
|
+
<p className="text-sm text-text-muted">Connected via GitHub</p>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
{error && (
|
|
157
|
+
<div className="mb-4 p-3 bg-error/10 border border-error/20 rounded-lg">
|
|
158
|
+
<p className="text-error text-sm">{error}</p>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
163
|
+
<div>
|
|
164
|
+
<label htmlFor="email" className="block text-sm font-medium text-text-secondary mb-2">
|
|
165
|
+
Email Address
|
|
166
|
+
</label>
|
|
167
|
+
<input
|
|
168
|
+
type="email"
|
|
169
|
+
id="email"
|
|
170
|
+
value={email}
|
|
171
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
172
|
+
required
|
|
173
|
+
disabled={isSubmitting}
|
|
174
|
+
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"
|
|
175
|
+
placeholder="you@example.com"
|
|
176
|
+
/>
|
|
177
|
+
<p className="mt-2 text-xs text-text-muted">
|
|
178
|
+
We'll use this email to notify you about important updates and account activity.
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<button
|
|
183
|
+
type="submit"
|
|
184
|
+
disabled={isSubmitting || !email}
|
|
185
|
+
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"
|
|
186
|
+
>
|
|
187
|
+
{isSubmitting ? (
|
|
188
|
+
<>
|
|
189
|
+
<svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
190
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
191
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
192
|
+
</svg>
|
|
193
|
+
<span>Saving...</span>
|
|
194
|
+
</>
|
|
195
|
+
) : (
|
|
196
|
+
<span>Continue</span>
|
|
197
|
+
)}
|
|
198
|
+
</button>
|
|
199
|
+
</form>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Login Page - GitHub OAuth via Nango
|
|
2
|
+
* Login Page - GitHub OAuth via Nango or Email/Password
|
|
3
3
|
*
|
|
4
4
|
* Key: Initialize Nango on page load, not on click.
|
|
5
5
|
* This avoids popup blockers by ensuring openConnectUI is synchronous.
|
|
@@ -28,14 +28,21 @@ function LoginLoading() {
|
|
|
28
28
|
);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
type AuthMethod = 'github' | 'email';
|
|
32
|
+
|
|
31
33
|
// Main login content that uses useSearchParams
|
|
32
34
|
function LoginContent() {
|
|
33
35
|
const searchParams = useSearchParams();
|
|
36
|
+
const [authMethod, setAuthMethod] = useState<AuthMethod>('github');
|
|
34
37
|
const [isReady, setIsReady] = useState(false);
|
|
35
38
|
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
|
36
39
|
const [authStatus, setAuthStatus] = useState<string>('');
|
|
37
40
|
const [error, setError] = useState('');
|
|
38
|
-
|
|
41
|
+
|
|
42
|
+
// Email form state
|
|
43
|
+
const [email, setEmail] = useState('');
|
|
44
|
+
const [password, setPassword] = useState('');
|
|
45
|
+
|
|
39
46
|
// Get return URL from query params (used by cloud link flow)
|
|
40
47
|
const returnUrl = searchParams.get('return');
|
|
41
48
|
|
|
@@ -56,7 +63,8 @@ function LoginContent() {
|
|
|
56
63
|
if (!mounted) return;
|
|
57
64
|
|
|
58
65
|
if (!response.ok || !data.sessionToken) {
|
|
59
|
-
|
|
66
|
+
// Don't set error - email login doesn't need Nango
|
|
67
|
+
setIsReady(true);
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
62
70
|
|
|
@@ -66,7 +74,8 @@ function LoginContent() {
|
|
|
66
74
|
} catch (err) {
|
|
67
75
|
if (mounted) {
|
|
68
76
|
console.error('Init error:', err);
|
|
69
|
-
|
|
77
|
+
// Still allow email login even if Nango fails
|
|
78
|
+
setIsReady(true);
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
};
|
|
@@ -75,7 +84,7 @@ function LoginContent() {
|
|
|
75
84
|
return () => { mounted = false; };
|
|
76
85
|
}, []);
|
|
77
86
|
|
|
78
|
-
const checkAuthStatus = async (connectionId: string): Promise<{ ready: boolean; hasRepos?: boolean }> => {
|
|
87
|
+
const checkAuthStatus = async (connectionId: string): Promise<{ ready: boolean; hasRepos?: boolean; needsEmail?: boolean }> => {
|
|
79
88
|
const response = await fetch(`/api/auth/nango/login-status/${connectionId}`, {
|
|
80
89
|
credentials: 'include',
|
|
81
90
|
});
|
|
@@ -103,7 +112,12 @@ function LoginContent() {
|
|
|
103
112
|
try {
|
|
104
113
|
const result = await checkAuthStatus(connectionId);
|
|
105
114
|
if (result && result.ready) {
|
|
106
|
-
//
|
|
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),
|
|
107
121
|
// otherwise to connect-repos if no repos, or to app
|
|
108
122
|
if (returnUrl) {
|
|
109
123
|
window.location.href = returnUrl;
|
|
@@ -133,7 +147,7 @@ function LoginContent() {
|
|
|
133
147
|
// Use nango.auth() instead of openConnectUI to avoid popup blocker issues
|
|
134
148
|
const handleGitHubAuth = async () => {
|
|
135
149
|
if (!nangoRef.current) {
|
|
136
|
-
setError('
|
|
150
|
+
setError('GitHub login not available. Please use email login or refresh the page.');
|
|
137
151
|
return;
|
|
138
152
|
}
|
|
139
153
|
|
|
@@ -174,6 +188,58 @@ function LoginContent() {
|
|
|
174
188
|
}
|
|
175
189
|
};
|
|
176
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
|
+
|
|
177
243
|
const isLoading = !isReady || isAuthenticating;
|
|
178
244
|
|
|
179
245
|
return (
|
|
@@ -201,6 +267,32 @@ function LoginContent() {
|
|
|
201
267
|
|
|
202
268
|
{/* Login Card */}
|
|
203
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
|
+
|
|
204
296
|
<div>
|
|
205
297
|
{error && (
|
|
206
298
|
<div className="mb-4 p-3 bg-error/10 border border-error/20 rounded-lg">
|
|
@@ -208,37 +300,89 @@ function LoginContent() {
|
|
|
208
300
|
</div>
|
|
209
301
|
)}
|
|
210
302
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
)}
|
|
242
386
|
|
|
243
387
|
<p className="mt-6 text-center text-text-muted text-sm">
|
|
244
388
|
By signing in, you agree to our{' '}
|