@jerydam/lumina-sdk 0.1.0
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/BUTTON_FIXES.md +59 -0
- package/DOCS_INDEX.md +332 -0
- package/DOCUMENTATION.md +252 -0
- package/DOCUMENTATION_BUILD_SUMMARY.md +376 -0
- package/DOCUMENTATION_COMPLETE.md +311 -0
- package/FEATURES.md +333 -0
- package/Lumina-sdk/src/components/lumina-provider.tsx +46 -0
- package/Lumina-sdk/src/components/transaction-confirm.tsx +242 -0
- package/Lumina-sdk/src/components/wallet-display.tsx +157 -0
- package/Lumina-sdk/src/components/wallet-login.tsx +163 -0
- package/Lumina-sdk/src/hooks/use-mobile.ts +19 -0
- package/Lumina-sdk/src/hooks/use-toast.ts +191 -0
- package/Lumina-sdk/src/index.ts +0 -0
- package/Lumina-sdk/src/lib/api.ts +66 -0
- package/Lumina-sdk/src/lib/utils.ts +6 -0
- package/Lumina-sdk/src/package.json +42 -0
- package/Lumina-sdk/src/tsconfig.json +19 -0
- package/NEW_FILES_MANIFEST.txt +146 -0
- package/README.md +298 -0
- package/app/dashboard/analytics/page.tsx +218 -0
- package/app/dashboard/api-keys/page.tsx +260 -0
- package/app/dashboard/billing/page.tsx +412 -0
- package/app/dashboard/integration/page.tsx +185 -0
- package/app/dashboard/layout.tsx +18 -0
- package/app/dashboard/page.tsx +244 -0
- package/app/dashboard/settings/page.tsx +285 -0
- package/app/dashboard/users/page.tsx +148 -0
- package/app/docs/api/authentication/page.tsx +246 -0
- package/app/docs/api/endpoints/page.tsx +397 -0
- package/app/docs/api/errors/page.tsx +305 -0
- package/app/docs/api/overview/page.tsx +306 -0
- package/app/docs/examples/basic-setup/page.tsx +256 -0
- package/app/docs/examples/multi-chain/page.tsx +331 -0
- package/app/docs/examples/nextjs-full-stack/page.tsx +332 -0
- package/app/docs/getting-started/environment-setup/page.tsx +243 -0
- package/app/docs/getting-started/installation/page.tsx +187 -0
- package/app/docs/getting-started/introduction/page.tsx +178 -0
- package/app/docs/getting-started/quick-start/page.tsx +199 -0
- package/app/docs/guides/nextjs/page.tsx +358 -0
- package/app/docs/guides/react/page.tsx +230 -0
- package/app/docs/guides/security/page.tsx +284 -0
- package/app/docs/layout.tsx +32 -0
- package/app/docs/page.tsx +180 -0
- package/app/docs/sdk/lumina-provider/page.tsx +186 -0
- package/app/docs/sdk/transaction-confirm/page.tsx +331 -0
- package/app/docs/sdk/wallet-display/page.tsx +224 -0
- package/app/docs/sdk/wallet-login/page.tsx +207 -0
- package/app/docs/troubleshooting/common-issues/page.tsx +301 -0
- package/app/docs/troubleshooting/faq/page.tsx +105 -0
- package/app/globals.css +125 -0
- package/app/invite/[token]/page.tsx +78 -0
- package/app/layout.tsx +36 -0
- package/app/login/page.tsx +175 -0
- package/app/page.tsx +336 -0
- package/app/sdk-demo/page.tsx +239 -0
- package/components/dashboard-sidebar.tsx +113 -0
- package/components/docs/breadcrumb.tsx +51 -0
- package/components/docs/callout.tsx +53 -0
- package/components/docs/code-block.tsx +77 -0
- package/components/docs/docs-sidebar.tsx +214 -0
- package/components/docs/table-of-contents.tsx +83 -0
- package/components/sdk/lumina-provider.tsx +46 -0
- package/components/sdk/transaction-confirm.tsx +242 -0
- package/components/sdk/wallet-display.tsx +157 -0
- package/components/sdk/wallet-login.tsx +163 -0
- package/components/theme-provider.tsx +11 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +157 -0
- package/components/ui/alert.tsx +66 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/badge.tsx +46 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/calendar.tsx +213 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +351 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +184 -0
- package/components/ui/context-menu.tsx +252 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/drawer.tsx +135 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/empty.tsx +104 -0
- package/components/ui/field.tsx +244 -0
- package/components/ui/form.tsx +167 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/input-group.tsx +169 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/item.tsx +193 -0
- package/components/ui/kbd.tsx +28 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/navigation-menu.tsx +166 -0
- package/components/ui/pagination.tsx +127 -0
- package/components/ui/popover.tsx +48 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +45 -0
- package/components/ui/resizable.tsx +56 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/select.tsx +185 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +139 -0
- package/components/ui/sidebar.tsx +726 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/slider.tsx +59 -0
- package/components/ui/sonner.tsx +25 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +29 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +66 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/toast.tsx +129 -0
- package/components/ui/toaster.tsx +35 -0
- package/components/ui/toggle-group.tsx +73 -0
- package/components/ui/toggle.tsx +47 -0
- package/components/ui/tooltip.tsx +61 -0
- package/components/ui/use-mobile.tsx +19 -0
- package/components/ui/use-toast.ts +191 -0
- package/components.json +21 -0
- package/hooks/use-mobile.ts +19 -0
- package/hooks/use-toast.ts +191 -0
- package/lib/api.ts +66 -0
- package/lib/utils.ts +6 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +11 -0
- package/package.json +73 -0
- package/pnpm-workspace.yaml +5 -0
- package/postcss.config.mjs +8 -0
- package/public/apple-icon.png +0 -0
- package/public/fav.jpeg +0 -0
- package/public/fav.png +0 -0
- package/public/icon-dark-32x32.png +0 -0
- package/public/icon-light-32x32.png +0 -0
- package/public/icon.png +0 -0
- package/public/icon.svg +26 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/public/logo2.jpeg +0 -0
- package/public/logo2.png +0 -0
- package/public/placeholder-logo.png +0 -0
- package/public/placeholder-logo.svg +1 -0
- package/public/placeholder-user.jpg +0 -0
- package/public/placeholder.jpg +0 -0
- package/public/placeholder.svg +1 -0
- package/styles/globals.css +209 -0
- package/tailwind.config.ts +15 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Card } from '@/components/ui/card'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
6
|
+
import { Bell, Lock, Users, Shield, Trash2, Loader2, AlertTriangle, CheckCircle2, UserPlus, X } from 'lucide-react'
|
|
7
|
+
import { useState, useEffect } from 'react'
|
|
8
|
+
import { useRouter } from 'next/navigation'
|
|
9
|
+
import { apiClient } from '@/lib/api'
|
|
10
|
+
|
|
11
|
+
interface DeveloperProfile {
|
|
12
|
+
name: string
|
|
13
|
+
email: string
|
|
14
|
+
plan: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function SettingsPage() {
|
|
18
|
+
const router = useRouter()
|
|
19
|
+
const [profile, setProfile] = useState<DeveloperProfile | null>(null)
|
|
20
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
21
|
+
const [message, setMessage] = useState({ type: '', text: '' })
|
|
22
|
+
|
|
23
|
+
// Modals State
|
|
24
|
+
const [showPasswordModal, setShowPasswordModal] = useState(false)
|
|
25
|
+
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
|
26
|
+
|
|
27
|
+
// Forms State
|
|
28
|
+
const [inviteEmail, setInviteEmail] = useState('')
|
|
29
|
+
const [isInviting, setIsInviting] = useState(false)
|
|
30
|
+
|
|
31
|
+
const [passwordForm, setPasswordForm] = useState({ current: '', new: '', confirm: '' })
|
|
32
|
+
const [isChangingPassword, setIsChangingPassword] = useState(false)
|
|
33
|
+
|
|
34
|
+
const [deleteConfirmation, setDeleteConfirmation] = useState('')
|
|
35
|
+
const [isDeleting, setIsDeleting] = useState(false)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const fetchProfile = async () => {
|
|
39
|
+
try {
|
|
40
|
+
setIsLoading(true)
|
|
41
|
+
const data = await apiClient.dashboard('/v1/developers/me')
|
|
42
|
+
setProfile(data)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to load profile:', error)
|
|
45
|
+
} finally {
|
|
46
|
+
setIsLoading(false)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
fetchProfile()
|
|
50
|
+
}, [])
|
|
51
|
+
|
|
52
|
+
const handleInviteTeam = async (e: React.FormEvent) => {
|
|
53
|
+
e.preventDefault()
|
|
54
|
+
setIsInviting(true)
|
|
55
|
+
setMessage({ type: '', text: '' })
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await apiClient.dashboard('/v1/developers/team/invite', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({ email: inviteEmail })
|
|
62
|
+
})
|
|
63
|
+
setMessage({ type: 'success', text: `Invite sent to ${inviteEmail}` })
|
|
64
|
+
setInviteEmail('')
|
|
65
|
+
} catch (err: any) {
|
|
66
|
+
setMessage({ type: 'error', text: err.message || 'Failed to send invite.' })
|
|
67
|
+
} finally {
|
|
68
|
+
setIsInviting(false)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleChangePassword = async (e: React.FormEvent) => {
|
|
73
|
+
e.preventDefault()
|
|
74
|
+
if (passwordForm.new !== passwordForm.confirm) {
|
|
75
|
+
setMessage({ type: 'error', text: 'New passwords do not match.' })
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (passwordForm.new.length < 8) {
|
|
79
|
+
setMessage({ type: 'error', text: 'Password must be at least 8 characters.' })
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setIsChangingPassword(true)
|
|
84
|
+
setMessage({ type: '', text: '' })
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await apiClient.dashboard('/v1/developers/password', {
|
|
88
|
+
method: 'PUT',
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
current_password: passwordForm.current,
|
|
91
|
+
new_password: passwordForm.new
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
setMessage({ type: 'success', text: 'Password updated successfully.' })
|
|
95
|
+
setShowPasswordModal(false)
|
|
96
|
+
setPasswordForm({ current: '', new: '', confirm: '' })
|
|
97
|
+
} catch (err: any) {
|
|
98
|
+
setMessage({ type: 'error', text: err.message || 'Failed to update password.' })
|
|
99
|
+
} finally {
|
|
100
|
+
setIsChangingPassword(false)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const handleDeleteAccount = async () => {
|
|
105
|
+
if (deleteConfirmation !== 'DELETE') return
|
|
106
|
+
|
|
107
|
+
setIsDeleting(true)
|
|
108
|
+
try {
|
|
109
|
+
await apiClient.dashboard('/v1/developers/me', { method: 'DELETE' })
|
|
110
|
+
localStorage.removeItem('lumina_dev_token')
|
|
111
|
+
router.push('/login')
|
|
112
|
+
} catch (err: any) {
|
|
113
|
+
setMessage({ type: 'error', text: err.message || 'Failed to delete account.' })
|
|
114
|
+
setIsDeleting(false)
|
|
115
|
+
setShowDeleteModal(false)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (isLoading) {
|
|
120
|
+
return (
|
|
121
|
+
<div className="min-h-[60vh] flex items-center justify-center">
|
|
122
|
+
<Loader2 className="w-8 h-8 animate-spin text-emerald-500" />
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="space-y-8 relative">
|
|
129
|
+
<div>
|
|
130
|
+
<h1 className="text-4xl font-bold mb-2">Settings</h1>
|
|
131
|
+
<p className="text-foreground/60">Manage your developer profile and workspace preferences</p>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{message.text && (
|
|
135
|
+
<div className={`p-4 rounded-lg border flex items-center gap-3 ${
|
|
136
|
+
message.type === 'error' ? 'bg-red-500/10 border-red-500/50 text-red-400' : 'bg-emerald-500/10 border-emerald-500/50 text-emerald-400'
|
|
137
|
+
}`}>
|
|
138
|
+
{message.type === 'error' ? <AlertTriangle size={20} /> : <CheckCircle2 size={20} />}
|
|
139
|
+
{message.text}
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{/* Account Settings */}
|
|
144
|
+
<Card className="glassmorphism-dark p-6 border-border">
|
|
145
|
+
<h2 className="text-2xl font-semibold mb-6">Account Information</h2>
|
|
146
|
+
<div className="space-y-6">
|
|
147
|
+
<div>
|
|
148
|
+
<label className="block text-sm font-medium mb-2">Developer Name</label>
|
|
149
|
+
<Input defaultValue={profile?.name || ''} className="bg-white/5 border-border text-foreground/80" readOnly />
|
|
150
|
+
</div>
|
|
151
|
+
<div>
|
|
152
|
+
<label className="block text-sm font-medium mb-2">Email Address</label>
|
|
153
|
+
<Input type="email" defaultValue={profile?.email || ''} className="bg-white/5 border-border text-foreground/80" readOnly />
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</Card>
|
|
157
|
+
|
|
158
|
+
{/* Team Management UI */}
|
|
159
|
+
<Card className="glassmorphism-dark p-6 border-border">
|
|
160
|
+
<div className="flex items-center justify-between mb-6">
|
|
161
|
+
<h2 className="text-2xl font-semibold flex items-center gap-2">
|
|
162
|
+
<Users size={24} />
|
|
163
|
+
Team Members
|
|
164
|
+
</h2>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<form onSubmit={handleInviteTeam} className="flex gap-2 mb-6">
|
|
168
|
+
<Input
|
|
169
|
+
type="email"
|
|
170
|
+
placeholder="colleague@company.com"
|
|
171
|
+
value={inviteEmail}
|
|
172
|
+
onChange={(e) => setInviteEmail(e.target.value)}
|
|
173
|
+
className="bg-white/5 border-border"
|
|
174
|
+
required
|
|
175
|
+
/>
|
|
176
|
+
<Button type="submit" disabled={isInviting} className="bg-emerald-600 hover:bg-emerald-700 whitespace-nowrap gap-2">
|
|
177
|
+
{isInviting ? <Loader2 className="animate-spin" size={16} /> : <UserPlus size={16} />}
|
|
178
|
+
Invite Member
|
|
179
|
+
</Button>
|
|
180
|
+
</form>
|
|
181
|
+
|
|
182
|
+
<div className="space-y-4">
|
|
183
|
+
<div className="flex items-center justify-between p-4 bg-white/5 rounded-lg border border-border/50">
|
|
184
|
+
<div>
|
|
185
|
+
<p className="font-medium">{profile?.name || 'You'}</p>
|
|
186
|
+
<p className="text-sm text-foreground/60">{profile?.email}</p>
|
|
187
|
+
</div>
|
|
188
|
+
<span className="px-3 py-1 text-sm rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">Owner</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</Card>
|
|
192
|
+
|
|
193
|
+
{/* Security */}
|
|
194
|
+
<Card className="glassmorphism-dark p-6 border-border">
|
|
195
|
+
<h2 className="text-2xl font-semibold mb-6 flex items-center gap-2">
|
|
196
|
+
<Lock size={24} />
|
|
197
|
+
Security
|
|
198
|
+
</h2>
|
|
199
|
+
<div className="p-4 bg-white/5 rounded-lg border border-border/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
200
|
+
<div>
|
|
201
|
+
<p className="font-medium mb-1">Password</p>
|
|
202
|
+
<p className="text-sm text-foreground/60">Update your dashboard login password</p>
|
|
203
|
+
</div>
|
|
204
|
+
<Button onClick={() => setShowPasswordModal(true)} variant="outline">Change Password</Button>
|
|
205
|
+
</div>
|
|
206
|
+
</Card>
|
|
207
|
+
|
|
208
|
+
{/* Danger Zone */}
|
|
209
|
+
<Card className="glassmorphism-dark p-6 border-red-600/30 bg-red-600/5">
|
|
210
|
+
<h2 className="text-2xl font-semibold mb-6 text-red-500">Danger Zone</h2>
|
|
211
|
+
<div className="p-4 bg-red-600/10 rounded-lg border border-red-600/30">
|
|
212
|
+
<p className="font-medium mb-1">Delete Workspace</p>
|
|
213
|
+
<p className="text-sm text-foreground/60 mb-4">Permanently delete your account, API keys, and sever all active embedded wallets.</p>
|
|
214
|
+
<Button onClick={() => setShowDeleteModal(true)} variant="outline" className="border-red-600/30 text-red-500 hover:bg-red-600/10 gap-2">
|
|
215
|
+
<Trash2 size={18} />
|
|
216
|
+
Delete Account
|
|
217
|
+
</Button>
|
|
218
|
+
</div>
|
|
219
|
+
</Card>
|
|
220
|
+
|
|
221
|
+
{/* ── Modals ── */}
|
|
222
|
+
|
|
223
|
+
{/* Password Modal */}
|
|
224
|
+
{showPasswordModal && (
|
|
225
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm p-4">
|
|
226
|
+
<Card className="glassmorphism-dark w-full max-w-md p-6 border-border shadow-2xl relative">
|
|
227
|
+
<button onClick={() => setShowPasswordModal(false)} className="absolute top-4 right-4 text-foreground/50 hover:text-foreground">
|
|
228
|
+
<X size={20} />
|
|
229
|
+
</button>
|
|
230
|
+
<h2 className="text-xl font-bold mb-6">Change Password</h2>
|
|
231
|
+
<form onSubmit={handleChangePassword} className="space-y-4">
|
|
232
|
+
<div>
|
|
233
|
+
<label className="block text-sm font-medium mb-1">Current Password</label>
|
|
234
|
+
<Input type="password" required value={passwordForm.current} onChange={(e) => setPasswordForm({...passwordForm, current: e.target.value})} className="bg-white/5 border-border" />
|
|
235
|
+
</div>
|
|
236
|
+
<div>
|
|
237
|
+
<label className="block text-sm font-medium mb-1">New Password</label>
|
|
238
|
+
<Input type="password" required minLength={8} value={passwordForm.new} onChange={(e) => setPasswordForm({...passwordForm, new: e.target.value})} className="bg-white/5 border-border" />
|
|
239
|
+
</div>
|
|
240
|
+
<div>
|
|
241
|
+
<label className="block text-sm font-medium mb-1">Confirm New Password</label>
|
|
242
|
+
<Input type="password" required minLength={8} value={passwordForm.confirm} onChange={(e) => setPasswordForm({...passwordForm, confirm: e.target.value})} className="bg-white/5 border-border" />
|
|
243
|
+
</div>
|
|
244
|
+
<Button type="submit" disabled={isChangingPassword} className="w-full bg-emerald-600 hover:bg-emerald-700 mt-2">
|
|
245
|
+
{isChangingPassword ? <Loader2 className="animate-spin" size={18} /> : 'Update Password'}
|
|
246
|
+
</Button>
|
|
247
|
+
</form>
|
|
248
|
+
</Card>
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
|
|
252
|
+
{/* Delete Modal */}
|
|
253
|
+
{showDeleteModal && (
|
|
254
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm p-4">
|
|
255
|
+
<Card className="glassmorphism-dark w-full max-w-md p-6 border-red-600/50 shadow-2xl relative bg-red-950/20">
|
|
256
|
+
<button onClick={() => setShowDeleteModal(false)} className="absolute top-4 right-4 text-foreground/50 hover:text-foreground">
|
|
257
|
+
<X size={20} />
|
|
258
|
+
</button>
|
|
259
|
+
<div className="flex items-center gap-3 text-red-500 mb-4">
|
|
260
|
+
<AlertTriangle size={28} />
|
|
261
|
+
<h2 className="text-xl font-bold">Delete Workspace?</h2>
|
|
262
|
+
</div>
|
|
263
|
+
<p className="text-sm text-foreground/80 mb-6">
|
|
264
|
+
This action is irreversible. All your API keys will be revoked, gas balances lost, and embedded wallets disconnected.
|
|
265
|
+
To confirm, type <strong>DELETE</strong> below.
|
|
266
|
+
</p>
|
|
267
|
+
<Input
|
|
268
|
+
value={deleteConfirmation}
|
|
269
|
+
onChange={(e) => setDeleteConfirmation(e.target.value)}
|
|
270
|
+
placeholder="DELETE"
|
|
271
|
+
className="bg-white/5 border-red-600/30 mb-6 focus:border-red-500"
|
|
272
|
+
/>
|
|
273
|
+
<Button
|
|
274
|
+
onClick={handleDeleteAccount}
|
|
275
|
+
disabled={deleteConfirmation !== 'DELETE' || isDeleting}
|
|
276
|
+
className="w-full bg-red-600 hover:bg-red-700 text-white"
|
|
277
|
+
>
|
|
278
|
+
{isDeleting ? <Loader2 className="animate-spin" size={18} /> : 'Permanently Delete Account'}
|
|
279
|
+
</Button>
|
|
280
|
+
</Card>
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Search, Filter, Download, ExternalLink, Shield, Loader2 } from 'lucide-react'
|
|
4
|
+
import { Card } from '@/components/ui/card'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Input } from '@/components/ui/input'
|
|
7
|
+
import { useState, useEffect } from 'react'
|
|
8
|
+
import { apiClient } from '@/lib/api'
|
|
9
|
+
|
|
10
|
+
interface UserWallet {
|
|
11
|
+
id: string
|
|
12
|
+
signer_address: string
|
|
13
|
+
oauth_provider: string
|
|
14
|
+
created_at: string
|
|
15
|
+
account_count: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function UsersPage() {
|
|
19
|
+
const [wallets, setWallets] = useState<UserWallet[]>([])
|
|
20
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
21
|
+
const [page, setPage] = useState(0)
|
|
22
|
+
const limit = 10
|
|
23
|
+
|
|
24
|
+
const fetchUsers = async (pageIndex: number) => {
|
|
25
|
+
try {
|
|
26
|
+
setIsLoading(true)
|
|
27
|
+
const offset = pageIndex * limit
|
|
28
|
+
const data = await apiClient.dashboard(`/v1/wallets?limit=${limit}&offset=${offset}`)
|
|
29
|
+
setWallets(data.wallets || [])
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Failed to fetch users:', error)
|
|
32
|
+
} finally {
|
|
33
|
+
setIsLoading(false)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetchUsers(page)
|
|
39
|
+
}, [page])
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="space-y-8">
|
|
43
|
+
{/* Header */}
|
|
44
|
+
<div>
|
|
45
|
+
<h1 className="text-4xl font-bold mb-2">Users & Wallets</h1>
|
|
46
|
+
<p className="text-foreground/60">Manage your application's embedded wallets and signers</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Filters and Actions */}
|
|
50
|
+
<Card className="glassmorphism-dark p-6 border-border">
|
|
51
|
+
<div className="flex flex-col md:flex-row gap-4 items-end">
|
|
52
|
+
<div className="flex-1">
|
|
53
|
+
<label className="block text-sm font-medium mb-2">Search</label>
|
|
54
|
+
<div className="relative">
|
|
55
|
+
<Search className="absolute left-3 top-3 text-foreground/40" size={20} />
|
|
56
|
+
<Input
|
|
57
|
+
placeholder="Search by signer address..."
|
|
58
|
+
className="pl-10 bg-white/5 border-border"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<Button variant="outline" className="gap-2">
|
|
63
|
+
<Filter size={18} /> Filter
|
|
64
|
+
</Button>
|
|
65
|
+
<Button variant="outline" className="gap-2">
|
|
66
|
+
<Download size={18} /> Export
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
</Card>
|
|
70
|
+
|
|
71
|
+
{/* Users Table */}
|
|
72
|
+
<Card className="glassmorphism-dark border-border overflow-hidden">
|
|
73
|
+
<div className="overflow-x-auto">
|
|
74
|
+
<table className="w-full text-sm">
|
|
75
|
+
<thead>
|
|
76
|
+
<tr className="border-b border-border bg-white/5">
|
|
77
|
+
<th className="text-left py-4 px-6 text-foreground/60 font-medium">Signer Address</th>
|
|
78
|
+
<th className="text-left py-4 px-6 text-foreground/60 font-medium">Auth Provider</th>
|
|
79
|
+
<th className="text-left py-4 px-6 text-foreground/60 font-medium">Networks Deployed</th>
|
|
80
|
+
<th className="text-left py-4 px-6 text-foreground/60 font-medium">Created Date</th>
|
|
81
|
+
<th className="text-right py-4 px-6 text-foreground/60 font-medium">Actions</th>
|
|
82
|
+
</tr>
|
|
83
|
+
</thead>
|
|
84
|
+
<tbody>
|
|
85
|
+
{isLoading ? (
|
|
86
|
+
<tr>
|
|
87
|
+
<td colSpan={5} className="py-12 text-center">
|
|
88
|
+
<Loader2 className="w-6 h-6 animate-spin mx-auto text-emerald-500" />
|
|
89
|
+
</td>
|
|
90
|
+
</tr>
|
|
91
|
+
) : wallets.length === 0 ? (
|
|
92
|
+
<tr>
|
|
93
|
+
<td colSpan={5} className="py-12 text-center text-foreground/60">
|
|
94
|
+
No users have created wallets yet.
|
|
95
|
+
</td>
|
|
96
|
+
</tr>
|
|
97
|
+
) : (
|
|
98
|
+
wallets.map((wallet) => (
|
|
99
|
+
<tr key={wallet.id} className="border-b border-border/50 hover:bg-white/5 transition">
|
|
100
|
+
<td className="py-4 px-6 font-mono text-sm">{wallet.signer_address}</td>
|
|
101
|
+
<td className="py-4 px-6">
|
|
102
|
+
<div className="flex items-center gap-2">
|
|
103
|
+
<Shield size={16} className="text-emerald-500" />
|
|
104
|
+
<span className="capitalize">{wallet.oauth_provider}</span>
|
|
105
|
+
</div>
|
|
106
|
+
</td>
|
|
107
|
+
<td className="py-4 px-6 font-medium">{wallet.account_count}</td>
|
|
108
|
+
<td className="py-4 px-6 text-foreground/70">
|
|
109
|
+
{new Date(wallet.created_at).toLocaleDateString()}
|
|
110
|
+
</td>
|
|
111
|
+
<td className="py-4 px-6 text-right">
|
|
112
|
+
<button className="p-2 hover:bg-white/10 rounded-lg transition text-foreground/60 hover:text-emerald-400">
|
|
113
|
+
<ExternalLink size={18} />
|
|
114
|
+
</button>
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
))
|
|
118
|
+
)}
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Pagination */}
|
|
124
|
+
<div className="px-6 py-4 border-t border-border flex items-center justify-between">
|
|
125
|
+
<p className="text-sm text-foreground/60">Page {page + 1}</p>
|
|
126
|
+
<div className="flex gap-2">
|
|
127
|
+
<Button
|
|
128
|
+
variant="outline"
|
|
129
|
+
size="sm"
|
|
130
|
+
onClick={() => setPage(Math.max(0, page - 1))}
|
|
131
|
+
disabled={page === 0 || isLoading}
|
|
132
|
+
>
|
|
133
|
+
Previous
|
|
134
|
+
</Button>
|
|
135
|
+
<Button
|
|
136
|
+
variant="outline"
|
|
137
|
+
size="sm"
|
|
138
|
+
onClick={() => setPage(page + 1)}
|
|
139
|
+
disabled={wallets.length < limit || isLoading}
|
|
140
|
+
>
|
|
141
|
+
Next
|
|
142
|
+
</Button>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</Card>
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { Breadcrumb } from '@/components/docs/breadcrumb'
|
|
2
|
+
import { CodeBlock } from '@/components/docs/code-block'
|
|
3
|
+
import { Callout } from '@/components/docs/callout'
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: 'Authentication API - API Reference',
|
|
7
|
+
description: 'Authenticate users and manage sessions with the Lumina API.',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function AuthenticationPage() {
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<Breadcrumb />
|
|
14
|
+
|
|
15
|
+
<div className="space-y-8">
|
|
16
|
+
<div className="space-y-4">
|
|
17
|
+
<h1 className="text-4xl font-bold text-white">Authentication API</h1>
|
|
18
|
+
<p className="text-lg text-foreground/80">
|
|
19
|
+
Handle user authentication, session management, and credential verification via the Lumina REST API.
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div className="space-y-4">
|
|
24
|
+
<h2 className="text-2xl font-bold">Authentication Methods</h2>
|
|
25
|
+
<p className="text-foreground/80 mb-4">
|
|
26
|
+
Lumina supports multiple authentication methods:
|
|
27
|
+
</p>
|
|
28
|
+
<div className="space-y-3">
|
|
29
|
+
<div className="p-4 rounded-lg bg-white/5 border border-white/10">
|
|
30
|
+
<p className="font-semibold text-emerald-400 mb-2">Bearer Token (API Key)</p>
|
|
31
|
+
<p className="text-sm text-foreground/80">
|
|
32
|
+
Use your API key as a Bearer token in the Authorization header.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
<div className="p-4 rounded-lg bg-white/5 border border-white/10">
|
|
36
|
+
<p className="font-semibold text-emerald-400 mb-2">Session Tokens</p>
|
|
37
|
+
<p className="text-sm text-foreground/80">
|
|
38
|
+
JWT tokens issued after successful user authentication.
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="p-4 rounded-lg bg-white/5 border border-white/10">
|
|
42
|
+
<p className="font-semibold text-emerald-400 mb-2">OAuth 2.0</p>
|
|
43
|
+
<p className="text-sm text-foreground/80">
|
|
44
|
+
Standard OAuth 2.0 flow for delegated access.
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div className="space-y-4">
|
|
51
|
+
<h2 className="text-2xl font-bold">Login</h2>
|
|
52
|
+
<p className="text-foreground/80 mb-4">
|
|
53
|
+
Authenticate a user with email and password or email verification code.
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<CodeBlock
|
|
57
|
+
code="POST /auth/login"
|
|
58
|
+
language="http"
|
|
59
|
+
title="Endpoint"
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<CodeBlock
|
|
63
|
+
code={`{
|
|
64
|
+
"email": "user@example.com",
|
|
65
|
+
"password": "secure_password_or_code"
|
|
66
|
+
}`}
|
|
67
|
+
language="json"
|
|
68
|
+
title="Request Body"
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<CodeBlock
|
|
72
|
+
code={`{
|
|
73
|
+
"success": true,
|
|
74
|
+
"user": {
|
|
75
|
+
"id": "user_123",
|
|
76
|
+
"email": "user@example.com",
|
|
77
|
+
"walletAddress": "0x...",
|
|
78
|
+
"createdAt": "2024-01-15T10:30:00Z"
|
|
79
|
+
},
|
|
80
|
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
81
|
+
}`}
|
|
82
|
+
language="json"
|
|
83
|
+
title="Response"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="space-y-4">
|
|
88
|
+
<h2 className="text-2xl font-bold">Request Verification Email</h2>
|
|
89
|
+
<p className="text-foreground/80 mb-4">
|
|
90
|
+
Send a verification code to the user's email.
|
|
91
|
+
</p>
|
|
92
|
+
|
|
93
|
+
<CodeBlock
|
|
94
|
+
code="POST /auth/send-verification-email"
|
|
95
|
+
language="http"
|
|
96
|
+
title="Endpoint"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<CodeBlock
|
|
100
|
+
code={`{
|
|
101
|
+
"email": "user@example.com"
|
|
102
|
+
}`}
|
|
103
|
+
language="json"
|
|
104
|
+
title="Request Body"
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<CodeBlock
|
|
108
|
+
code={`{
|
|
109
|
+
"success": true,
|
|
110
|
+
"message": "Verification email sent"
|
|
111
|
+
}`}
|
|
112
|
+
language="json"
|
|
113
|
+
title="Response"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="space-y-4">
|
|
118
|
+
<h2 className="text-2xl font-bold">Register</h2>
|
|
119
|
+
<p className="text-foreground/80 mb-4">
|
|
120
|
+
Create a new user account.
|
|
121
|
+
</p>
|
|
122
|
+
|
|
123
|
+
<CodeBlock
|
|
124
|
+
code="POST /auth/register"
|
|
125
|
+
language="http"
|
|
126
|
+
title="Endpoint"
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
<CodeBlock
|
|
130
|
+
code={`{
|
|
131
|
+
"email": "newuser@example.com",
|
|
132
|
+
"password": "secure_password",
|
|
133
|
+
"authMethod": "email"
|
|
134
|
+
}`}
|
|
135
|
+
language="json"
|
|
136
|
+
title="Request Body"
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<CodeBlock
|
|
140
|
+
code={`{
|
|
141
|
+
"success": true,
|
|
142
|
+
"user": {
|
|
143
|
+
"id": "user_456",
|
|
144
|
+
"email": "newuser@example.com",
|
|
145
|
+
"walletAddress": "0x...",
|
|
146
|
+
"createdAt": "2024-01-15T10:35:00Z"
|
|
147
|
+
}
|
|
148
|
+
}`}
|
|
149
|
+
language="json"
|
|
150
|
+
title="Response"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div className="space-y-4">
|
|
155
|
+
<h2 className="text-2xl font-bold">Verify Token</h2>
|
|
156
|
+
<p className="text-foreground/80 mb-4">
|
|
157
|
+
Verify that a session token is valid and not expired.
|
|
158
|
+
</p>
|
|
159
|
+
|
|
160
|
+
<CodeBlock
|
|
161
|
+
code="GET /auth/verify"
|
|
162
|
+
language="http"
|
|
163
|
+
title="Endpoint"
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<CodeBlock
|
|
167
|
+
code={`Authorization: Bearer <token>`}
|
|
168
|
+
language="http"
|
|
169
|
+
title="Request Headers"
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<CodeBlock
|
|
173
|
+
code={`{
|
|
174
|
+
"valid": true,
|
|
175
|
+
"user": {
|
|
176
|
+
"id": "user_123",
|
|
177
|
+
"email": "user@example.com",
|
|
178
|
+
"walletAddress": "0x...",
|
|
179
|
+
"expiresAt": "2024-01-22T10:30:00Z"
|
|
180
|
+
}
|
|
181
|
+
}`}
|
|
182
|
+
language="json"
|
|
183
|
+
title="Response"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<Callout type="warning" title="Token Expiration">
|
|
188
|
+
Session tokens expire after 7 days. Use refresh tokens to obtain new session tokens without requiring re-authentication.
|
|
189
|
+
</Callout>
|
|
190
|
+
|
|
191
|
+
<div className="space-y-4">
|
|
192
|
+
<h2 className="text-2xl font-bold">Logout</h2>
|
|
193
|
+
<p className="text-foreground/80 mb-4">
|
|
194
|
+
Invalidate a session token.
|
|
195
|
+
</p>
|
|
196
|
+
|
|
197
|
+
<CodeBlock
|
|
198
|
+
code="POST /auth/logout"
|
|
199
|
+
language="http"
|
|
200
|
+
title="Endpoint"
|
|
201
|
+
/>
|
|
202
|
+
|
|
203
|
+
<CodeBlock
|
|
204
|
+
code={`Authorization: Bearer <token>`}
|
|
205
|
+
language="http"
|
|
206
|
+
title="Request Headers"
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
<CodeBlock
|
|
210
|
+
code={`{
|
|
211
|
+
"success": true,
|
|
212
|
+
"message": "Successfully logged out"
|
|
213
|
+
}`}
|
|
214
|
+
language="json"
|
|
215
|
+
title="Response"
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div className="space-y-4">
|
|
220
|
+
<h2 className="text-2xl font-bold">Error Responses</h2>
|
|
221
|
+
<CodeBlock
|
|
222
|
+
code={`{
|
|
223
|
+
"error": {
|
|
224
|
+
"code": "INVALID_CREDENTIALS",
|
|
225
|
+
"message": "Email or password is incorrect",
|
|
226
|
+
"status": 401
|
|
227
|
+
}
|
|
228
|
+
}`}
|
|
229
|
+
language="json"
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
<div className="space-y-2 text-sm">
|
|
233
|
+
<p className="text-foreground/80 font-semibold">Common error codes:</p>
|
|
234
|
+
<ul className="space-y-1 text-foreground/70 ml-4">
|
|
235
|
+
<li>• <code className="bg-black/40 px-2 py-1 rounded text-xs">INVALID_CREDENTIALS</code> - Wrong email/password</li>
|
|
236
|
+
<li>• <code className="bg-black/40 px-2 py-1 rounded text-xs">USER_NOT_FOUND</code> - Email not registered</li>
|
|
237
|
+
<li>• <code className="bg-black/40 px-2 py-1 rounded text-xs">EMAIL_ALREADY_EXISTS</code> - Account already exists</li>
|
|
238
|
+
<li>• <code className="bg-black/40 px-2 py-1 rounded text-xs">INVALID_TOKEN</code> - Token is expired or invalid</li>
|
|
239
|
+
<li>• <code className="bg-black/40 px-2 py-1 rounded text-xs">RATE_LIMITED</code> - Too many requests</li>
|
|
240
|
+
</ul>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</>
|
|
245
|
+
)
|
|
246
|
+
}
|