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.
@@ -0,0 +1,287 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import Link from 'next/link';
6
+ import { AuthProvider, useAuth } from '@/lib/auth-context';
7
+
8
+ function DeveloperContent() {
9
+ const router = useRouter();
10
+ const { user, loading } = useAuth();
11
+ const [clients, setClients] = useState([]);
12
+ const [loadingClients, setLoadingClients] = useState(true);
13
+ const [showCreate, setShowCreate] = useState(false);
14
+ const [newApp, setNewApp] = useState({ name: '', redirectUri: '' });
15
+ const [creating, setCreating] = useState(false);
16
+ const [createdApp, setCreatedApp] = useState(null);
17
+
18
+ useEffect(() => {
19
+ if (!loading && !user) {
20
+ router.push('/login');
21
+ }
22
+ }, [user, loading, router]);
23
+
24
+ useEffect(() => {
25
+ if (user) {
26
+ fetchClients();
27
+ }
28
+ }, [user]);
29
+
30
+ const fetchClients = async () => {
31
+ try {
32
+ const idToken = await user.getIdToken();
33
+ const res = await fetch('/api/internal/developer/clients', {
34
+ headers: { Authorization: `Bearer ${idToken}` },
35
+ });
36
+ const data = await res.json();
37
+ setClients(data.clients || []);
38
+ } catch (err) {
39
+ console.error('Failed to load clients:', err);
40
+ }
41
+ setLoadingClients(false);
42
+ };
43
+
44
+ const handleCreateApp = async (e) => {
45
+ e.preventDefault();
46
+ if (!newApp.name || !newApp.redirectUri) return;
47
+
48
+ setCreating(true);
49
+ try {
50
+ const idToken = await user.getIdToken();
51
+ const res = await fetch('/api/internal/developer/clients', {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ Authorization: `Bearer ${idToken}`,
56
+ },
57
+ body: JSON.stringify(newApp),
58
+ });
59
+ const data = await res.json();
60
+ if (data.error) {
61
+ alert('Server Error: ' + data.error);
62
+ } else {
63
+ setCreatedApp(data);
64
+ setNewApp({ name: '', redirectUri: '' });
65
+ setShowCreate(false);
66
+ fetchClients();
67
+ }
68
+ } catch (err) {
69
+ console.error('Failed to create app:', err);
70
+ }
71
+ setCreating(false);
72
+ };
73
+
74
+ const handleDeleteClient = async (clientId) => {
75
+ if (!confirm('Are you sure? This will break any app using this client.')) return;
76
+
77
+ try {
78
+ const idToken = await user.getIdToken();
79
+ await fetch(`/api/internal/developer/clients?clientId=${clientId}`, {
80
+ method: 'DELETE',
81
+ headers: { Authorization: `Bearer ${idToken}` },
82
+ });
83
+ fetchClients();
84
+ } catch (err) {
85
+ console.error('Failed to delete client:', err);
86
+ }
87
+ };
88
+
89
+ if (loading || !user) {
90
+ return (
91
+ <div className="loading-overlay">
92
+ <img src="/deevo-logo.svg" alt="Deevo" style={{ height: 32, width: 'auto' }} />
93
+ <span className="spinner" style={{ width: 24, height: 24, color: 'var(--primary)', marginTop: 'var(--space-4)' }} />
94
+ </div>
95
+ );
96
+ }
97
+
98
+ return (
99
+ <>
100
+ <div className="bg-animated" />
101
+
102
+ {/* Navbar */}
103
+ <nav className="navbar">
104
+ <Link href="/" className="navbar-brand">
105
+ <img src="/deevo-logo.svg" alt="Deevo" style={{ height: 22, width: 'auto' }} />
106
+ </Link>
107
+ <div className="navbar-actions">
108
+ <Link href="/dashboard" className="btn btn-ghost" style={{ fontSize: 'var(--font-size-sm)' }}>
109
+ My Account
110
+ </Link>
111
+ </div>
112
+ </nav>
113
+
114
+ <div className="dashboard-grid" style={{ paddingTop: 'var(--space-8)', maxWidth: '900px' }}>
115
+ {/* Header */}
116
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
117
+ <div>
118
+ <h1 style={{ fontSize: 'var(--font-size-2xl)', fontWeight: 700, letterSpacing: '-0.02em' }}>
119
+ Developer Console
120
+ </h1>
121
+ <p style={{ color: 'var(--on-surface-variant)', fontSize: 'var(--font-size-sm)', marginTop: 'var(--space-1)' }}>
122
+ Manage your OAuth applications
123
+ </p>
124
+ </div>
125
+ <button className="btn btn-primary" onClick={() => setShowCreate(true)} style={{ padding: '0.6rem 1.5rem' }}>
126
+ + New App
127
+ </button>
128
+ </div>
129
+
130
+ {/* Created App Credentials (shown once after creation) */}
131
+ {createdApp && (
132
+ <div className="alert alert-success" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 'var(--space-4)' }}>
133
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
134
+ <strong>✓ App Created Successfully!</strong>
135
+ <button className="btn btn-ghost" onClick={() => setCreatedApp(null)} style={{ padding: '0.25rem 0.5rem', fontSize: 'var(--font-size-xs)' }}>
136
+ Dismiss
137
+ </button>
138
+ </div>
139
+ <p style={{ fontSize: 'var(--font-size-xs)', opacity: 0.8 }}>
140
+ Save these credentials now — the Client Secret will NOT be shown again.
141
+ </p>
142
+ <div style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-3)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', wordBreak: 'break-all' }}>
143
+ <div><span style={{ color: 'var(--on-surface-variant)' }}>Client ID:</span> {createdApp.clientId}</div>
144
+ <div style={{ marginTop: 'var(--space-2)' }}><span style={{ color: 'var(--on-surface-variant)' }}>Client Secret:</span> {createdApp.clientSecret}</div>
145
+ </div>
146
+ </div>
147
+ )}
148
+
149
+ {/* Create App Form */}
150
+ {showCreate && (
151
+ <div className="glass-card section-card">
152
+ <h2 className="section-title">Register New Application</h2>
153
+ <form onSubmit={handleCreateApp} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}>
154
+ <div className="form-group">
155
+ <label className="form-label" htmlFor="app-name">Application Name</label>
156
+ <input
157
+ id="app-name"
158
+ className="form-input"
159
+ placeholder="My Deevo App"
160
+ value={newApp.name}
161
+ onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
162
+ required
163
+ />
164
+ </div>
165
+ <div className="form-group">
166
+ <label className="form-label" htmlFor="app-redirect">Redirect URI</label>
167
+ <input
168
+ id="app-redirect"
169
+ className="form-input"
170
+ placeholder="https://myapp.com/auth/callback"
171
+ value={newApp.redirectUri}
172
+ onChange={(e) => setNewApp({ ...newApp, redirectUri: e.target.value })}
173
+ required
174
+ />
175
+ <span className="form-error" style={{ color: 'var(--on-surface-variant)', fontSize: 'var(--font-size-xs)' }}>
176
+ The URL where users will be redirected after authentication
177
+ </span>
178
+ </div>
179
+ <div style={{ display: 'flex', gap: 'var(--space-3)' }}>
180
+ <button type="submit" className="btn btn-primary" disabled={creating}>
181
+ {creating ? 'Creating...' : 'Create Application'}
182
+ </button>
183
+ <button type="button" className="btn btn-ghost" onClick={() => setShowCreate(false)}>Cancel</button>
184
+ </div>
185
+ </form>
186
+ </div>
187
+ )}
188
+
189
+ {/* Apps List */}
190
+ <div className="glass-card section-card">
191
+ <h2 className="section-title">Your Applications</h2>
192
+ {loadingClients ? (
193
+ <div style={{ textAlign: 'center', padding: 'var(--space-8)' }}>
194
+ <span className="spinner" style={{ width: 24, height: 24, color: 'var(--primary)', margin: '0 auto' }} />
195
+ </div>
196
+ ) : clients.length === 0 ? (
197
+ <div style={{ textAlign: 'center', padding: 'var(--space-8) 0', color: 'var(--on-surface-variant)' }}>
198
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ margin: '0 auto var(--space-4)', opacity: 0.3 }}>
199
+ <path d="M12 2L2 7l10 5 10-5-10-5z" />
200
+ <path d="M2 17l10 5 10-5" />
201
+ <path d="M2 12l10 5 10-5" />
202
+ </svg>
203
+ <p style={{ fontSize: 'var(--font-size-sm)' }}>No applications registered yet</p>
204
+ <p style={{ fontSize: 'var(--font-size-xs)', marginTop: 'var(--space-1)', opacity: 0.6 }}>
205
+ Create your first app to start integrating Deevo Auth
206
+ </p>
207
+ </div>
208
+ ) : (
209
+ <div className="app-list">
210
+ {clients.map((client) => (
211
+ <div key={client.id} className="app-item" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 'var(--space-3)' }}>
212
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
213
+ <span className="app-item-name">{client.name}</span>
214
+ <button
215
+ className="btn btn-ghost"
216
+ style={{ fontSize: 'var(--font-size-xs)', color: 'var(--error)', padding: '0.25rem 0.5rem' }}
217
+ onClick={() => handleDeleteClient(client.id)}
218
+ >
219
+ Delete
220
+ </button>
221
+ </div>
222
+ <div style={{ fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface-variant)', wordBreak: 'break-all' }}>
223
+ <div>Client ID: <span style={{ color: 'var(--primary)' }}>{client.id}</span></div>
224
+ <div style={{ marginTop: '2px' }}>Redirect: <span style={{ color: 'var(--on-surface)' }}>{client.redirectUri}</span></div>
225
+ </div>
226
+ </div>
227
+ ))}
228
+ </div>
229
+ )}
230
+ </div>
231
+
232
+ {/* Integration Guide */}
233
+ <div className="glass-card section-card">
234
+ <h2 className="section-title">Quick Integration Guide</h2>
235
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)' }}>
236
+ <div>
237
+ <h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 600, marginBottom: 'var(--space-2)', color: 'var(--primary)' }}>
238
+ 1. Install the SDK
239
+ </h3>
240
+ <div style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-3)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface)' }}>
241
+ npm install deevoauth
242
+ </div>
243
+ </div>
244
+
245
+ <div>
246
+ <h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 600, marginBottom: 'var(--space-2)', color: 'var(--primary)' }}>
247
+ 2. Configure in your app
248
+ </h3>
249
+ <pre style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-4)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface)', overflow: 'auto', whiteSpace: 'pre-wrap' }}>
250
+ {`import { DeevoAuth } from 'deevoauth';
251
+
252
+ const deevo = new DeevoAuth({
253
+ clientId: 'YOUR_CLIENT_ID',
254
+ clientSecret: 'YOUR_CLIENT_SECRET',
255
+ redirectUri: 'https://yourapp.com/auth/callback',
256
+ });`}
257
+ </pre>
258
+ </div>
259
+
260
+ <div>
261
+ <h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 600, marginBottom: 'var(--space-2)', color: 'var(--primary)' }}>
262
+ 3. Redirect users to sign in
263
+ </h3>
264
+ <pre style={{ background: 'var(--surface-container-lowest)', borderRadius: 'var(--radius-sm)', padding: 'var(--space-4)', fontFamily: 'monospace', fontSize: 'var(--font-size-xs)', color: 'var(--on-surface)', overflow: 'auto', whiteSpace: 'pre-wrap' }}>
265
+ {`// Redirect to Deevo login
266
+ const loginUrl = deevo.getAuthUrl();
267
+ window.location.href = loginUrl;
268
+
269
+ // In your callback route:
270
+ const { accessToken, user } = await deevo.handleCallback(code);
271
+ console.log(user.email, user.name);`}
272
+ </pre>
273
+ </div>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ </>
278
+ );
279
+ }
280
+
281
+ export default function DeveloperPage() {
282
+ return (
283
+ <AuthProvider>
284
+ <DeveloperContent />
285
+ </AuthProvider>
286
+ );
287
+ }