deevoauth 1.4.5
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/README.md +222 -0
- package/app/api/internal/create-user/route.js +54 -0
- package/app/api/internal/developer/clients/route.js +122 -0
- package/app/api/internal/generate-code/route.js +41 -0
- package/app/api/oauth/token/route.js +115 -0
- package/app/api/oauth/userinfo/route.js +46 -0
- package/app/consent/page.jsx +202 -0
- package/app/dashboard/page.jsx +254 -0
- package/app/developers/page.jsx +287 -0
- package/app/globals.css +1041 -0
- package/app/layout.jsx +33 -0
- package/app/login/page.jsx +257 -0
- package/app/page.jsx +165 -0
- package/app/register/page.jsx +249 -0
- package/components/DeevoLogo.jsx +41 -0
- package/firebase.json +10 -0
- package/jsconfig.json +7 -0
- package/lib/auth-context.jsx +102 -0
- package/lib/firebase-admin.js +32 -0
- package/lib/firebase.js +18 -0
- package/next.config.mjs +9 -0
- package/package.json +20 -0
- package/public/deevo-logo.svg +3 -0
- package/sdk/README.md +216 -0
- package/sdk/build.js +30 -0
- package/sdk/deevo-oauth-1.4.5.tgz +0 -0
- package/sdk/dist/index.d.ts +69 -0
- package/sdk/dist/index.js +228 -0
- package/sdk/dist/index.mjs +222 -0
- package/sdk/package.json +39 -0
- package/sdk/src/index.d.ts +69 -0
- package/sdk/src/index.js +228 -0
package/app/layout.jsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import './globals.css';
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: 'Deevo Account',
|
|
5
|
+
description: 'One Account. All of Deevo. Sign in or create your Deevo Account to access the entire Deevo ecosystem.',
|
|
6
|
+
keywords: ['Deevo', 'OAuth', 'Account', 'Authentication', 'Sign In'],
|
|
7
|
+
authors: [{ name: 'Deevo' }],
|
|
8
|
+
openGraph: {
|
|
9
|
+
title: 'Deevo Account',
|
|
10
|
+
description: 'One Account. All of Deevo.',
|
|
11
|
+
url: 'https://deevo.tech',
|
|
12
|
+
siteName: 'Deevo Account',
|
|
13
|
+
type: 'website',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default function RootLayout({ children }) {
|
|
18
|
+
return (
|
|
19
|
+
<html lang="en">
|
|
20
|
+
<head>
|
|
21
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
22
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
23
|
+
<link
|
|
24
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
|
|
25
|
+
rel="stylesheet"
|
|
26
|
+
/>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
{children}
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, Suspense, useEffect } from 'react';
|
|
4
|
+
import { useSearchParams, useRouter } from 'next/navigation';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { AuthProvider, useAuth } from '@/lib/auth-context';
|
|
7
|
+
|
|
8
|
+
function LoginContent() {
|
|
9
|
+
const searchParams = useSearchParams();
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const { user, loading: authLoading, signInWithGoogle, signInWithEmail } = useAuth();
|
|
12
|
+
|
|
13
|
+
const [email, setEmail] = useState('');
|
|
14
|
+
const [password, setPassword] = useState('');
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [googleLoading, setGoogleLoading] = useState(false);
|
|
17
|
+
const [error, setError] = useState('');
|
|
18
|
+
const [initChecking, setInitChecking] = useState(true);
|
|
19
|
+
|
|
20
|
+
// OAuth params from URL (when app redirects user here)
|
|
21
|
+
const clientId = searchParams.get('client_id');
|
|
22
|
+
const redirectUri = searchParams.get('redirect_uri');
|
|
23
|
+
const isOAuthFlow = !!(clientId && redirectUri);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!authLoading) {
|
|
27
|
+
if (user) {
|
|
28
|
+
handleOAuthRedirect(user);
|
|
29
|
+
} else {
|
|
30
|
+
setInitChecking(false);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}, [user, authLoading]);
|
|
34
|
+
|
|
35
|
+
const handleOAuthRedirect = async (user) => {
|
|
36
|
+
if (!isOAuthFlow) {
|
|
37
|
+
router.push('/dashboard');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Exchange Firebase token for Deevo OAuth code
|
|
42
|
+
const idToken = await user.getIdToken();
|
|
43
|
+
const response = await fetch('/api/internal/generate-code', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ idToken, clientId, redirectUri }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const data = await response.json();
|
|
50
|
+
if (data.error) throw new Error(data.error);
|
|
51
|
+
|
|
52
|
+
// Redirect back to client app
|
|
53
|
+
window.location.href = `${redirectUri}?code=${data.code}`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleGoogleLogin = async () => {
|
|
57
|
+
setError('');
|
|
58
|
+
setGoogleLoading(true);
|
|
59
|
+
try {
|
|
60
|
+
const user = await signInWithGoogle();
|
|
61
|
+
|
|
62
|
+
// Sync user profile to Firestore
|
|
63
|
+
const idToken = await user.getIdToken();
|
|
64
|
+
await fetch('/api/internal/create-user', {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ idToken, fullName: user.displayName || '' }),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await handleOAuthRedirect(user);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(err);
|
|
73
|
+
if (err.code === 'auth/popup-closed-by-user') {
|
|
74
|
+
setError('Sign in was cancelled.');
|
|
75
|
+
} else {
|
|
76
|
+
setError('Authentication failed. Please try again.');
|
|
77
|
+
}
|
|
78
|
+
setGoogleLoading(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleEmailLogin = async (e) => {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
if (!email || !password) {
|
|
85
|
+
setError('Please enter your email and password.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setError('');
|
|
90
|
+
setLoading(true);
|
|
91
|
+
try {
|
|
92
|
+
const user = await signInWithEmail(email, password);
|
|
93
|
+
await handleOAuthRedirect(user);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error(err);
|
|
96
|
+
if (err.code === 'auth/user-not-found' || err.code === 'auth/wrong-password' || err.code === 'auth/invalid-credential') {
|
|
97
|
+
setError('Invalid email or password.');
|
|
98
|
+
} else if (err.code === 'auth/too-many-requests') {
|
|
99
|
+
setError('Too many attempts. Please try again later.');
|
|
100
|
+
} else {
|
|
101
|
+
setError('Sign in failed. Please try again.');
|
|
102
|
+
}
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Build OAuth query for register link
|
|
108
|
+
const registerHref = isOAuthFlow
|
|
109
|
+
? `/register?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}`
|
|
110
|
+
: '/register';
|
|
111
|
+
|
|
112
|
+
if (initChecking || authLoading) {
|
|
113
|
+
return (
|
|
114
|
+
<div className="loading-overlay">
|
|
115
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 32, width: 'auto' }} />
|
|
116
|
+
<span className="spinner" style={{ width: 24, height: 24, color: 'var(--primary)', marginTop: 'var(--space-4)' }} />
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<div className="bg-animated" />
|
|
124
|
+
<div className="page-center">
|
|
125
|
+
<div className="auth-container">
|
|
126
|
+
<div className="glass-card auth-card">
|
|
127
|
+
{/* Logo */}
|
|
128
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 28, width: 'auto', margin: '0 auto var(--space-6)', display: 'block' }} />
|
|
129
|
+
|
|
130
|
+
{/* Header */}
|
|
131
|
+
<div className="auth-header">
|
|
132
|
+
<h1>Sign in to Deevo</h1>
|
|
133
|
+
<p>
|
|
134
|
+
{isOAuthFlow
|
|
135
|
+
? 'Sign in to continue to the application'
|
|
136
|
+
: 'Access your Deevo Account'}
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{/* Error */}
|
|
141
|
+
{error && (
|
|
142
|
+
<div className="alert alert-error" style={{ marginBottom: 'var(--space-5)' }}>
|
|
143
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ flexShrink: 0, marginTop: '2px' }}>
|
|
144
|
+
<circle cx="12" cy="12" r="10" />
|
|
145
|
+
<line x1="15" y1="9" x2="9" y2="15" />
|
|
146
|
+
<line x1="9" y1="9" x2="15" y2="15" />
|
|
147
|
+
</svg>
|
|
148
|
+
<span>{error}</span>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{/* Email/Password Form */}
|
|
153
|
+
<form className="auth-form" onSubmit={handleEmailLogin}>
|
|
154
|
+
<div className="form-group">
|
|
155
|
+
<label className="form-label" htmlFor="login-email">Email</label>
|
|
156
|
+
<input
|
|
157
|
+
id="login-email"
|
|
158
|
+
className="form-input"
|
|
159
|
+
type="email"
|
|
160
|
+
placeholder="you@example.com"
|
|
161
|
+
value={email}
|
|
162
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
163
|
+
autoComplete="email"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="form-group">
|
|
168
|
+
<label className="form-label" htmlFor="login-password">Password</label>
|
|
169
|
+
<input
|
|
170
|
+
id="login-password"
|
|
171
|
+
className="form-input"
|
|
172
|
+
type="password"
|
|
173
|
+
placeholder="Enter your password"
|
|
174
|
+
value={password}
|
|
175
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
176
|
+
autoComplete="current-password"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<button
|
|
181
|
+
type="submit"
|
|
182
|
+
className="btn btn-primary btn-full"
|
|
183
|
+
disabled={loading || googleLoading}
|
|
184
|
+
>
|
|
185
|
+
{loading ? (
|
|
186
|
+
<>
|
|
187
|
+
<span className="spinner" />
|
|
188
|
+
Signing in...
|
|
189
|
+
</>
|
|
190
|
+
) : (
|
|
191
|
+
'Sign In'
|
|
192
|
+
)}
|
|
193
|
+
</button>
|
|
194
|
+
</form>
|
|
195
|
+
|
|
196
|
+
{/* Divider */}
|
|
197
|
+
<div className="divider" style={{ margin: 'var(--space-6) 0' }}>
|
|
198
|
+
<span>or</span>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{/* Google Sign In */}
|
|
202
|
+
<button
|
|
203
|
+
className="btn btn-social"
|
|
204
|
+
onClick={handleGoogleLogin}
|
|
205
|
+
disabled={loading || googleLoading}
|
|
206
|
+
>
|
|
207
|
+
{googleLoading ? (
|
|
208
|
+
<>
|
|
209
|
+
<span className="spinner" />
|
|
210
|
+
Connecting...
|
|
211
|
+
</>
|
|
212
|
+
) : (
|
|
213
|
+
<>
|
|
214
|
+
<svg width="20" height="20" viewBox="0 0 24 24">
|
|
215
|
+
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
|
216
|
+
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
217
|
+
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
|
218
|
+
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
|
219
|
+
</svg>
|
|
220
|
+
Continue with Google
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
</button>
|
|
224
|
+
|
|
225
|
+
{/* Footer */}
|
|
226
|
+
<div className="auth-footer">
|
|
227
|
+
Don't have an account?{' '}
|
|
228
|
+
<Link href={registerHref}>Create one</Link>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function LoginFallback() {
|
|
238
|
+
return (
|
|
239
|
+
<div className="page-center">
|
|
240
|
+
<div className="auth-container">
|
|
241
|
+
<div className="glass-card auth-card" style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-16)' }}>
|
|
242
|
+
<span className="spinner" style={{ width: 32, height: 32 }} />
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default function LoginPage() {
|
|
250
|
+
return (
|
|
251
|
+
<AuthProvider>
|
|
252
|
+
<Suspense fallback={<LoginFallback />}>
|
|
253
|
+
<LoginContent />
|
|
254
|
+
</Suspense>
|
|
255
|
+
</AuthProvider>
|
|
256
|
+
);
|
|
257
|
+
}
|
package/app/page.jsx
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { useAuth } from '@/lib/auth-context';
|
|
5
|
+
import { AuthProvider } from '@/lib/auth-context';
|
|
6
|
+
|
|
7
|
+
function HomeContent() {
|
|
8
|
+
const { user } = useAuth();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<div className="bg-animated" />
|
|
13
|
+
|
|
14
|
+
{/* Navbar */}
|
|
15
|
+
<nav className="navbar">
|
|
16
|
+
<Link href="/" className="navbar-brand">
|
|
17
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 22, width: 'auto' }} />
|
|
18
|
+
</Link>
|
|
19
|
+
<div className="navbar-actions">
|
|
20
|
+
<Link href="/developers" className="btn btn-ghost" style={{ marginRight: 'var(--space-2)' }}>
|
|
21
|
+
Developers
|
|
22
|
+
</Link>
|
|
23
|
+
{user ? (
|
|
24
|
+
<Link href="/dashboard" className="btn btn-primary" style={{ padding: '0.5rem 1.5rem' }}>
|
|
25
|
+
Dashboard
|
|
26
|
+
</Link>
|
|
27
|
+
) : (
|
|
28
|
+
<>
|
|
29
|
+
<Link href="/login" className="btn btn-ghost">Sign In</Link>
|
|
30
|
+
<Link href="/register" className="btn btn-primary" style={{ padding: '0.5rem 1.5rem' }}>
|
|
31
|
+
Create Account
|
|
32
|
+
</Link>
|
|
33
|
+
</>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
</nav>
|
|
37
|
+
|
|
38
|
+
{/* Hero Section */}
|
|
39
|
+
<main>
|
|
40
|
+
<section className="hero">
|
|
41
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 48, width: 'auto', marginBottom: 'var(--space-8)' }} />
|
|
42
|
+
<h1>One Account.<br />All of Deevo.</h1>
|
|
43
|
+
<p>
|
|
44
|
+
Your Deevo Account is the single key to the entire Deevo ecosystem.
|
|
45
|
+
Sign in once, access everything — securely, seamlessly, instantly.
|
|
46
|
+
</p>
|
|
47
|
+
<div className="hero-cta">
|
|
48
|
+
<Link href="/register" className="btn btn-primary" style={{ padding: '1rem 2.5rem', fontSize: '1rem' }}>
|
|
49
|
+
Get Started
|
|
50
|
+
</Link>
|
|
51
|
+
<Link href="/login" className="btn btn-secondary" style={{ padding: '1rem 2.5rem', fontSize: '1rem' }}>
|
|
52
|
+
Sign In
|
|
53
|
+
</Link>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
{/* Features */}
|
|
58
|
+
<section className="features">
|
|
59
|
+
<div className="features-grid">
|
|
60
|
+
<div className="glass-card feature-card">
|
|
61
|
+
<div className="feature-icon">
|
|
62
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--primary)' }}>
|
|
63
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
64
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
65
|
+
</svg>
|
|
66
|
+
</div>
|
|
67
|
+
<h3>Enterprise Security</h3>
|
|
68
|
+
<p>Built on Firebase Auth with Google's infrastructure. Your credentials are encrypted and protected by industry-leading security standards.</p>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="glass-card feature-card">
|
|
72
|
+
<div className="feature-icon">
|
|
73
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--success)' }}>
|
|
74
|
+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
|
75
|
+
</svg>
|
|
76
|
+
</div>
|
|
77
|
+
<h3>Lightning Fast</h3>
|
|
78
|
+
<p>One-click sign in with Google, or use email and password. No friction, no delays — get into your apps in under a second.</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="glass-card feature-card">
|
|
82
|
+
<div className="feature-icon">
|
|
83
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--tertiary)' }}>
|
|
84
|
+
<circle cx="12" cy="12" r="10" />
|
|
85
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
86
|
+
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
87
|
+
</svg>
|
|
88
|
+
</div>
|
|
89
|
+
<h3>Universal Access</h3>
|
|
90
|
+
<p>One account works across all Deevo products. No need to manage multiple logins — your identity travels with you.</p>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
{/* How It Works */}
|
|
96
|
+
<section style={{ padding: 'var(--space-16) var(--space-8)', maxWidth: '800px', margin: '0 auto', textAlign: 'center' }}>
|
|
97
|
+
<h2 style={{ fontSize: 'var(--font-size-3xl)', fontWeight: 700, letterSpacing: '-0.02em', marginBottom: 'var(--space-4)' }}>
|
|
98
|
+
How it works
|
|
99
|
+
</h2>
|
|
100
|
+
<p style={{ color: 'var(--on-surface-variant)', marginBottom: 'var(--space-12)', fontSize: 'var(--font-size-md)' }}>
|
|
101
|
+
Deevo products use OAuth 2.0 — the same protocol used by Google, GitHub, and Apple.
|
|
102
|
+
</p>
|
|
103
|
+
|
|
104
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)', textAlign: 'left' }}>
|
|
105
|
+
{[
|
|
106
|
+
{ step: '01', title: 'App redirects you here', desc: 'When a Deevo product needs to verify your identity, it sends you to this login screen.' },
|
|
107
|
+
{ step: '02', title: 'You sign in securely', desc: 'Use Google or your email/password. Your credentials never touch the requesting app.' },
|
|
108
|
+
{ step: '03', title: 'You\'re back in the app', desc: 'After authentication, you\'re redirected back with a secure token. The app knows who you are.' },
|
|
109
|
+
].map((item) => (
|
|
110
|
+
<div key={item.step} className="glass-card" style={{ padding: 'var(--space-6)', display: 'flex', gap: 'var(--space-5)', alignItems: 'flex-start' }}>
|
|
111
|
+
<span style={{ fontSize: 'var(--font-size-2xl)', fontWeight: 800, color: 'var(--primary-container)', opacity: 0.6, flexShrink: 0 }}>
|
|
112
|
+
{item.step}
|
|
113
|
+
</span>
|
|
114
|
+
<div>
|
|
115
|
+
<h3 style={{ fontWeight: 600, marginBottom: 'var(--space-1)' }}>{item.title}</h3>
|
|
116
|
+
<p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--on-surface-variant)', lineHeight: 1.7 }}>{item.desc}</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
</section>
|
|
122
|
+
|
|
123
|
+
{/* For Developers CTA */}
|
|
124
|
+
<section style={{ padding: 'var(--space-16) var(--space-8)', textAlign: 'center' }}>
|
|
125
|
+
<div className="glass-card" style={{ maxWidth: '700px', margin: '0 auto', padding: 'var(--space-12)' }}>
|
|
126
|
+
<h2 style={{ fontSize: 'var(--font-size-2xl)', fontWeight: 700, letterSpacing: '-0.02em', marginBottom: 'var(--space-3)' }}>
|
|
127
|
+
For Developers
|
|
128
|
+
</h2>
|
|
129
|
+
<p style={{ color: 'var(--on-surface-variant)', marginBottom: 'var(--space-8)', fontSize: 'var(--font-size-sm)', lineHeight: 1.7 }}>
|
|
130
|
+
Integrate Deevo Auth into your app in minutes. Install the SDK, configure your client credentials, and let users sign in with their Deevo Account.
|
|
131
|
+
</p>
|
|
132
|
+
<div style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-md)', padding: 'var(--space-4)', marginBottom: 'var(--space-6)', fontFamily: 'monospace', fontSize: 'var(--font-size-sm)', color: 'var(--primary)', textAlign: 'left' }}>
|
|
133
|
+
npm install deevoauth
|
|
134
|
+
</div>
|
|
135
|
+
<Link href="/developers" className="btn btn-primary" style={{ padding: '0.75rem 2rem' }}>
|
|
136
|
+
Developer Console →
|
|
137
|
+
</Link>
|
|
138
|
+
</div>
|
|
139
|
+
</section>
|
|
140
|
+
</main>
|
|
141
|
+
|
|
142
|
+
{/* Footer */}
|
|
143
|
+
<footer className="footer">
|
|
144
|
+
<div className="footer-brand">
|
|
145
|
+
<img src="/deevo-logo.svg" alt="Deevo" style={{ height: 16, width: 'auto' }} />
|
|
146
|
+
</div>
|
|
147
|
+
<div className="footer-links">
|
|
148
|
+
<a href="https://deevo.tech" target="_blank" rel="noopener noreferrer">Website</a>
|
|
149
|
+
<Link href="/login">Sign In</Link>
|
|
150
|
+
<Link href="/register">Create Account</Link>
|
|
151
|
+
<Link href="/developers">Developers</Link>
|
|
152
|
+
</div>
|
|
153
|
+
<p style={{ opacity: 0.5 }}>© {new Date().getFullYear()} Deevo. All rights reserved.</p>
|
|
154
|
+
</footer>
|
|
155
|
+
</>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default function HomePage() {
|
|
160
|
+
return (
|
|
161
|
+
<AuthProvider>
|
|
162
|
+
<HomeContent />
|
|
163
|
+
</AuthProvider>
|
|
164
|
+
);
|
|
165
|
+
}
|