@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
package/app/globals.css
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@import 'tw-animate-css';
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--background: oklch(1 0 0);
|
|
8
|
+
--foreground: oklch(0.145 0 0);
|
|
9
|
+
--card: oklch(1 0 0);
|
|
10
|
+
--card-foreground: oklch(0.145 0 0);
|
|
11
|
+
--popover: oklch(1 0 0);
|
|
12
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
13
|
+
--primary: oklch(0.205 0 0);
|
|
14
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
15
|
+
--secondary: oklch(0.97 0 0);
|
|
16
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
17
|
+
--muted: oklch(0.97 0 0);
|
|
18
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
19
|
+
--accent: oklch(0.97 0 0);
|
|
20
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
21
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
22
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
23
|
+
--border: oklch(0.922 0 0);
|
|
24
|
+
--input: oklch(0.922 0 0);
|
|
25
|
+
--ring: oklch(0.708 0 0);
|
|
26
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
27
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
28
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
29
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
30
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
31
|
+
--radius: 0.625rem;
|
|
32
|
+
--sidebar: oklch(0.985 0 0);
|
|
33
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
34
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
35
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
36
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
37
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
38
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
39
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dark {
|
|
43
|
+
--background: oklch(0.145 0 0);
|
|
44
|
+
--foreground: oklch(0.985 0 0);
|
|
45
|
+
--card: oklch(0.145 0 0);
|
|
46
|
+
--card-foreground: oklch(0.985 0 0);
|
|
47
|
+
--popover: oklch(0.145 0 0);
|
|
48
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
49
|
+
--primary: oklch(0.985 0 0);
|
|
50
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
51
|
+
--secondary: oklch(0.269 0 0);
|
|
52
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
53
|
+
--muted: oklch(0.269 0 0);
|
|
54
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
55
|
+
--accent: oklch(0.269 0 0);
|
|
56
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
57
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
58
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
59
|
+
--border: oklch(0.269 0 0);
|
|
60
|
+
--input: oklch(0.269 0 0);
|
|
61
|
+
--ring: oklch(0.439 0 0);
|
|
62
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
63
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
64
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
65
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
66
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
67
|
+
--sidebar: oklch(0.205 0 0);
|
|
68
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
69
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
70
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
71
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
72
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
73
|
+
--sidebar-border: oklch(0.269 0 0);
|
|
74
|
+
--sidebar-ring: oklch(0.439 0 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@theme inline {
|
|
78
|
+
--font-sans: 'Geist', 'Geist Fallback';
|
|
79
|
+
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
|
|
80
|
+
--color-background: var(--background);
|
|
81
|
+
--color-foreground: var(--foreground);
|
|
82
|
+
--color-card: var(--card);
|
|
83
|
+
--color-card-foreground: var(--card-foreground);
|
|
84
|
+
--color-popover: var(--popover);
|
|
85
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
86
|
+
--color-primary: var(--primary);
|
|
87
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
88
|
+
--color-secondary: var(--secondary);
|
|
89
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
90
|
+
--color-muted: var(--muted);
|
|
91
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
92
|
+
--color-accent: var(--accent);
|
|
93
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
94
|
+
--color-destructive: var(--destructive);
|
|
95
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
96
|
+
--color-border: var(--border);
|
|
97
|
+
--color-input: var(--input);
|
|
98
|
+
--color-ring: var(--ring);
|
|
99
|
+
--color-chart-1: var(--chart-1);
|
|
100
|
+
--color-chart-2: var(--chart-2);
|
|
101
|
+
--color-chart-3: var(--chart-3);
|
|
102
|
+
--color-chart-4: var(--chart-4);
|
|
103
|
+
--color-chart-5: var(--chart-5);
|
|
104
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
105
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
106
|
+
--radius-lg: var(--radius);
|
|
107
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
108
|
+
--color-sidebar: var(--sidebar);
|
|
109
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
110
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
111
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
112
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
113
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
114
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
115
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@layer base {
|
|
119
|
+
* {
|
|
120
|
+
@apply border-border outline-ring/50;
|
|
121
|
+
}
|
|
122
|
+
body {
|
|
123
|
+
@apply bg-background text-foreground;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { use, useEffect, useState } from 'react'
|
|
4
|
+
import { useRouter } from 'next/navigation'
|
|
5
|
+
import { Card } from '@/components/ui/card'
|
|
6
|
+
import { Button } from '@/components/ui/button'
|
|
7
|
+
import { Loader2, CheckCircle2, AlertTriangle } from 'lucide-react'
|
|
8
|
+
import { apiClient } from '@/lib/api'
|
|
9
|
+
|
|
10
|
+
export default function AcceptInvitePage({ params }: { params: Promise<{ token: string }> }) {
|
|
11
|
+
const { token } = use(params) // ← unwrap the Promise synchronously
|
|
12
|
+
const router = useRouter()
|
|
13
|
+
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
|
|
14
|
+
const [errorMsg, setErrorMsg] = useState('')
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const acceptInvite = async () => {
|
|
18
|
+
if (!token) {
|
|
19
|
+
setStatus('error')
|
|
20
|
+
setErrorMsg('No invitation token found in URL.')
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sessionToken = localStorage.getItem('lumina_dev_token')
|
|
25
|
+
if (!sessionToken) {
|
|
26
|
+
router.push(`/login?redirect=/invite/${token}`)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await apiClient.dashboard('/v1/developers/team/accept', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
body: JSON.stringify({ token })
|
|
35
|
+
})
|
|
36
|
+
setStatus('success')
|
|
37
|
+
setTimeout(() => router.push('/dashboard'), 2000)
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
setStatus('error')
|
|
40
|
+
setErrorMsg(err.message || 'Failed to accept invitation.')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
acceptInvite()
|
|
45
|
+
}, [token, router])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="min-h-screen flex items-center justify-center p-4">
|
|
49
|
+
<Card className="glassmorphism-dark p-8 max-w-md w-full text-center border-border">
|
|
50
|
+
{status === 'loading' && (
|
|
51
|
+
<>
|
|
52
|
+
<Loader2 className="w-12 h-12 animate-spin mx-auto text-emerald-500 mb-4" />
|
|
53
|
+
<h1 className="text-xl font-bold">Verifying Invitation...</h1>
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{status === 'success' && (
|
|
58
|
+
<>
|
|
59
|
+
<CheckCircle2 className="w-16 h-16 mx-auto text-emerald-500 mb-4" />
|
|
60
|
+
<h1 className="text-2xl font-bold mb-2">Invitation Accepted!</h1>
|
|
61
|
+
<p className="text-foreground/60">You have been added to the workspace. Redirecting to dashboard...</p>
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{status === 'error' && (
|
|
66
|
+
<>
|
|
67
|
+
<AlertTriangle className="w-16 h-16 mx-auto text-red-500 mb-4" />
|
|
68
|
+
<h1 className="text-2xl font-bold mb-2">Invalid Invitation</h1>
|
|
69
|
+
<p className="text-foreground/60 mb-6">{errorMsg}</p>
|
|
70
|
+
<Button onClick={() => router.push('/dashboard')} className="w-full">
|
|
71
|
+
Go to Dashboard
|
|
72
|
+
</Button>
|
|
73
|
+
</>
|
|
74
|
+
)}
|
|
75
|
+
</Card>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import { Analytics } from "@vercel/analytics/next";
|
|
4
|
+
import "./globals.css";
|
|
5
|
+
|
|
6
|
+
const geist = Geist({ subsets: ["latin"] });
|
|
7
|
+
const geistMono = Geist_Mono({ subsets: ["latin"] });
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: "Lumina - Embedded Wallet for Web3 Apps",
|
|
11
|
+
description:
|
|
12
|
+
"Secure, scalable embedded wallet infrastructure for Web3 applications",
|
|
13
|
+
|
|
14
|
+
icons: {
|
|
15
|
+
icon: "/icon.png",
|
|
16
|
+
shortcut: "/icon.png",
|
|
17
|
+
apple: "/apple-icon.png",
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
themeColor: "#10B981",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({
|
|
24
|
+
children,
|
|
25
|
+
}: Readonly<{
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
}>) {
|
|
28
|
+
return (
|
|
29
|
+
<html lang="en" className="dark">
|
|
30
|
+
<body className={`${geist.className} antialiased`}>
|
|
31
|
+
{children}
|
|
32
|
+
{process.env.NODE_ENV === "production" && <Analytics />}
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useRouter } from 'next/navigation'
|
|
5
|
+
import { Loader2, Lock, Mail, User, ArrowRight, Zap } from 'lucide-react'
|
|
6
|
+
import { Button } from '@/components/ui/button'
|
|
7
|
+
import { Input } from '@/components/ui/input'
|
|
8
|
+
import { Card } from '@/components/ui/card'
|
|
9
|
+
import { apiClient } from '@/lib/api'
|
|
10
|
+
|
|
11
|
+
export default function DeveloperAuthPage() {
|
|
12
|
+
const router = useRouter()
|
|
13
|
+
const [isLogin, setIsLogin] = useState(true)
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
15
|
+
const [error, setError] = useState('')
|
|
16
|
+
|
|
17
|
+
const [formData, setFormData] = useState({
|
|
18
|
+
name: '',
|
|
19
|
+
email: '',
|
|
20
|
+
password: ''
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
24
|
+
e.preventDefault()
|
|
25
|
+
setIsLoading(true)
|
|
26
|
+
setError('')
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
if (!isLogin) {
|
|
30
|
+
// 1. Register the new developer
|
|
31
|
+
await apiClient.dashboard('/v1/developers/register', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
name: formData.name,
|
|
35
|
+
email: formData.email,
|
|
36
|
+
password: formData.password
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Log in to get the JWT access token (runs immediately after registration too)
|
|
42
|
+
const data = await apiClient.dashboard('/v1/developers/login', {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
email: formData.email,
|
|
46
|
+
password: formData.password
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// 3. Securely store the JWT for future dashboard API calls
|
|
51
|
+
if (typeof window !== 'undefined') {
|
|
52
|
+
localStorage.setItem('lumina_dev_token', data.access_token)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 4. Redirect to the main dashboard
|
|
56
|
+
router.push('/dashboard')
|
|
57
|
+
|
|
58
|
+
} catch (err: any) {
|
|
59
|
+
setError(err.message || 'Authentication failed. Please check your credentials.')
|
|
60
|
+
} finally {
|
|
61
|
+
setIsLoading(false)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="min-h-screen flex items-center justify-center p-4 bg-background relative overflow-hidden">
|
|
67
|
+
|
|
68
|
+
{/* Background ambient glow */}
|
|
69
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-150 h-150 bg-emerald-600/20 rounded-full blur-[120px] pointer-events-none" />
|
|
70
|
+
|
|
71
|
+
<Card className="glassmorphism-dark p-8 border-border w-full max-w-md relative z-10 shadow-2xl">
|
|
72
|
+
<div className="mb-8 text-center">
|
|
73
|
+
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-emerald-500/10 text-emerald-500 mb-4 border border-emerald-500/20">
|
|
74
|
+
<Zap size={24} />
|
|
75
|
+
</div>
|
|
76
|
+
<h1 className="text-3xl font-bold mb-2 tracking-tight">Lumina</h1>
|
|
77
|
+
<p className="text-foreground/60 text-sm">
|
|
78
|
+
{isLogin ? 'Sign in to manage your embedded wallets' : 'Create your workspace and get your API keys'}
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{error && (
|
|
83
|
+
<div className="mb-6 p-3 bg-red-500/10 border border-red-500/50 rounded-lg text-red-400 text-sm text-center">
|
|
84
|
+
{error}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
89
|
+
{!isLogin && (
|
|
90
|
+
<div className="space-y-2">
|
|
91
|
+
<label className="text-sm font-medium text-foreground/80">Full Name</label>
|
|
92
|
+
<div className="relative">
|
|
93
|
+
<User className="absolute left-3 top-2.5 text-foreground/40" size={18} />
|
|
94
|
+
<Input
|
|
95
|
+
type="text"
|
|
96
|
+
placeholder="Jeremiah"
|
|
97
|
+
value={formData.name}
|
|
98
|
+
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
99
|
+
required={!isLogin}
|
|
100
|
+
className="pl-10 bg-white/5 border-border focus:border-emerald-500/50 transition-colors"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
<div className="space-y-2">
|
|
107
|
+
<label className="text-sm font-medium text-foreground/80">Email Address</label>
|
|
108
|
+
<div className="relative">
|
|
109
|
+
<Mail className="absolute left-3 top-2.5 text-foreground/40" size={18} />
|
|
110
|
+
<Input
|
|
111
|
+
type="email"
|
|
112
|
+
placeholder="developer@company.com"
|
|
113
|
+
value={formData.email}
|
|
114
|
+
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
115
|
+
required
|
|
116
|
+
className="pl-10 bg-white/5 border-border focus:border-emerald-500/50 transition-colors"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div className="space-y-2">
|
|
122
|
+
<label className="text-sm font-medium text-foreground/80">Password</label>
|
|
123
|
+
<div className="relative">
|
|
124
|
+
<Lock className="absolute left-3 top-2.5 text-foreground/40" size={18} />
|
|
125
|
+
<Input
|
|
126
|
+
type="password"
|
|
127
|
+
placeholder="••••••••"
|
|
128
|
+
value={formData.password}
|
|
129
|
+
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
|
130
|
+
required
|
|
131
|
+
minLength={8} // Changed from 12 to 8
|
|
132
|
+
className="pl-10 bg-white/5 border-border focus:border-emerald-500/50 transition-colors"
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
{!isLogin && (
|
|
136
|
+
<p className="text-xs text-foreground/50 mt-1 pl-1">
|
|
137
|
+
Must be at least 8 characters.
|
|
138
|
+
</p>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<Button
|
|
143
|
+
type="submit"
|
|
144
|
+
disabled={isLoading}
|
|
145
|
+
className="w-full bg-emerald-600 hover:bg-emerald-700 mt-6 h-11 text-base gap-2 group"
|
|
146
|
+
>
|
|
147
|
+
{isLoading ? (
|
|
148
|
+
<Loader2 className="animate-spin" size={18} />
|
|
149
|
+
) : (
|
|
150
|
+
<>
|
|
151
|
+
{isLogin ? 'Sign In' : 'Create Workspace'}
|
|
152
|
+
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
|
|
153
|
+
</>
|
|
154
|
+
)}
|
|
155
|
+
</Button>
|
|
156
|
+
</form>
|
|
157
|
+
|
|
158
|
+
<div className="mt-8 text-center text-sm text-foreground/60 border-t border-border/50 pt-6">
|
|
159
|
+
{isLogin ? "Don't have an account yet?" : "Already have a workspace?"}{' '}
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={() => {
|
|
163
|
+
setIsLogin(!isLogin)
|
|
164
|
+
setError('')
|
|
165
|
+
setFormData({ name: '', email: '', password: '' })
|
|
166
|
+
}}
|
|
167
|
+
className="text-emerald-400 hover:text-emerald-300 font-medium transition-colors"
|
|
168
|
+
>
|
|
169
|
+
{isLogin ? 'Create one now' : 'Sign in here'}
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
</Card>
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
175
|
+
}
|