@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.
Files changed (152) hide show
  1. package/BUTTON_FIXES.md +59 -0
  2. package/DOCS_INDEX.md +332 -0
  3. package/DOCUMENTATION.md +252 -0
  4. package/DOCUMENTATION_BUILD_SUMMARY.md +376 -0
  5. package/DOCUMENTATION_COMPLETE.md +311 -0
  6. package/FEATURES.md +333 -0
  7. package/Lumina-sdk/src/components/lumina-provider.tsx +46 -0
  8. package/Lumina-sdk/src/components/transaction-confirm.tsx +242 -0
  9. package/Lumina-sdk/src/components/wallet-display.tsx +157 -0
  10. package/Lumina-sdk/src/components/wallet-login.tsx +163 -0
  11. package/Lumina-sdk/src/hooks/use-mobile.ts +19 -0
  12. package/Lumina-sdk/src/hooks/use-toast.ts +191 -0
  13. package/Lumina-sdk/src/index.ts +0 -0
  14. package/Lumina-sdk/src/lib/api.ts +66 -0
  15. package/Lumina-sdk/src/lib/utils.ts +6 -0
  16. package/Lumina-sdk/src/package.json +42 -0
  17. package/Lumina-sdk/src/tsconfig.json +19 -0
  18. package/NEW_FILES_MANIFEST.txt +146 -0
  19. package/README.md +298 -0
  20. package/app/dashboard/analytics/page.tsx +218 -0
  21. package/app/dashboard/api-keys/page.tsx +260 -0
  22. package/app/dashboard/billing/page.tsx +412 -0
  23. package/app/dashboard/integration/page.tsx +185 -0
  24. package/app/dashboard/layout.tsx +18 -0
  25. package/app/dashboard/page.tsx +244 -0
  26. package/app/dashboard/settings/page.tsx +285 -0
  27. package/app/dashboard/users/page.tsx +148 -0
  28. package/app/docs/api/authentication/page.tsx +246 -0
  29. package/app/docs/api/endpoints/page.tsx +397 -0
  30. package/app/docs/api/errors/page.tsx +305 -0
  31. package/app/docs/api/overview/page.tsx +306 -0
  32. package/app/docs/examples/basic-setup/page.tsx +256 -0
  33. package/app/docs/examples/multi-chain/page.tsx +331 -0
  34. package/app/docs/examples/nextjs-full-stack/page.tsx +332 -0
  35. package/app/docs/getting-started/environment-setup/page.tsx +243 -0
  36. package/app/docs/getting-started/installation/page.tsx +187 -0
  37. package/app/docs/getting-started/introduction/page.tsx +178 -0
  38. package/app/docs/getting-started/quick-start/page.tsx +199 -0
  39. package/app/docs/guides/nextjs/page.tsx +358 -0
  40. package/app/docs/guides/react/page.tsx +230 -0
  41. package/app/docs/guides/security/page.tsx +284 -0
  42. package/app/docs/layout.tsx +32 -0
  43. package/app/docs/page.tsx +180 -0
  44. package/app/docs/sdk/lumina-provider/page.tsx +186 -0
  45. package/app/docs/sdk/transaction-confirm/page.tsx +331 -0
  46. package/app/docs/sdk/wallet-display/page.tsx +224 -0
  47. package/app/docs/sdk/wallet-login/page.tsx +207 -0
  48. package/app/docs/troubleshooting/common-issues/page.tsx +301 -0
  49. package/app/docs/troubleshooting/faq/page.tsx +105 -0
  50. package/app/globals.css +125 -0
  51. package/app/invite/[token]/page.tsx +78 -0
  52. package/app/layout.tsx +36 -0
  53. package/app/login/page.tsx +175 -0
  54. package/app/page.tsx +336 -0
  55. package/app/sdk-demo/page.tsx +239 -0
  56. package/components/dashboard-sidebar.tsx +113 -0
  57. package/components/docs/breadcrumb.tsx +51 -0
  58. package/components/docs/callout.tsx +53 -0
  59. package/components/docs/code-block.tsx +77 -0
  60. package/components/docs/docs-sidebar.tsx +214 -0
  61. package/components/docs/table-of-contents.tsx +83 -0
  62. package/components/sdk/lumina-provider.tsx +46 -0
  63. package/components/sdk/transaction-confirm.tsx +242 -0
  64. package/components/sdk/wallet-display.tsx +157 -0
  65. package/components/sdk/wallet-login.tsx +163 -0
  66. package/components/theme-provider.tsx +11 -0
  67. package/components/ui/accordion.tsx +66 -0
  68. package/components/ui/alert-dialog.tsx +157 -0
  69. package/components/ui/alert.tsx +66 -0
  70. package/components/ui/aspect-ratio.tsx +11 -0
  71. package/components/ui/avatar.tsx +53 -0
  72. package/components/ui/badge.tsx +46 -0
  73. package/components/ui/breadcrumb.tsx +109 -0
  74. package/components/ui/button-group.tsx +83 -0
  75. package/components/ui/button.tsx +60 -0
  76. package/components/ui/calendar.tsx +213 -0
  77. package/components/ui/card.tsx +92 -0
  78. package/components/ui/carousel.tsx +241 -0
  79. package/components/ui/chart.tsx +351 -0
  80. package/components/ui/checkbox.tsx +32 -0
  81. package/components/ui/collapsible.tsx +33 -0
  82. package/components/ui/command.tsx +184 -0
  83. package/components/ui/context-menu.tsx +252 -0
  84. package/components/ui/dialog.tsx +143 -0
  85. package/components/ui/drawer.tsx +135 -0
  86. package/components/ui/dropdown-menu.tsx +257 -0
  87. package/components/ui/empty.tsx +104 -0
  88. package/components/ui/field.tsx +244 -0
  89. package/components/ui/form.tsx +167 -0
  90. package/components/ui/hover-card.tsx +44 -0
  91. package/components/ui/input-group.tsx +169 -0
  92. package/components/ui/input-otp.tsx +77 -0
  93. package/components/ui/input.tsx +21 -0
  94. package/components/ui/item.tsx +193 -0
  95. package/components/ui/kbd.tsx +28 -0
  96. package/components/ui/label.tsx +24 -0
  97. package/components/ui/menubar.tsx +276 -0
  98. package/components/ui/navigation-menu.tsx +166 -0
  99. package/components/ui/pagination.tsx +127 -0
  100. package/components/ui/popover.tsx +48 -0
  101. package/components/ui/progress.tsx +31 -0
  102. package/components/ui/radio-group.tsx +45 -0
  103. package/components/ui/resizable.tsx +56 -0
  104. package/components/ui/scroll-area.tsx +58 -0
  105. package/components/ui/select.tsx +185 -0
  106. package/components/ui/separator.tsx +28 -0
  107. package/components/ui/sheet.tsx +139 -0
  108. package/components/ui/sidebar.tsx +726 -0
  109. package/components/ui/skeleton.tsx +13 -0
  110. package/components/ui/slider.tsx +59 -0
  111. package/components/ui/sonner.tsx +25 -0
  112. package/components/ui/spinner.tsx +16 -0
  113. package/components/ui/switch.tsx +29 -0
  114. package/components/ui/table.tsx +116 -0
  115. package/components/ui/tabs.tsx +66 -0
  116. package/components/ui/textarea.tsx +18 -0
  117. package/components/ui/toast.tsx +129 -0
  118. package/components/ui/toaster.tsx +35 -0
  119. package/components/ui/toggle-group.tsx +73 -0
  120. package/components/ui/toggle.tsx +47 -0
  121. package/components/ui/tooltip.tsx +61 -0
  122. package/components/ui/use-mobile.tsx +19 -0
  123. package/components/ui/use-toast.ts +191 -0
  124. package/components.json +21 -0
  125. package/hooks/use-mobile.ts +19 -0
  126. package/hooks/use-toast.ts +191 -0
  127. package/lib/api.ts +66 -0
  128. package/lib/utils.ts +6 -0
  129. package/next-env.d.ts +6 -0
  130. package/next.config.mjs +11 -0
  131. package/package.json +73 -0
  132. package/pnpm-workspace.yaml +5 -0
  133. package/postcss.config.mjs +8 -0
  134. package/public/apple-icon.png +0 -0
  135. package/public/fav.jpeg +0 -0
  136. package/public/fav.png +0 -0
  137. package/public/icon-dark-32x32.png +0 -0
  138. package/public/icon-light-32x32.png +0 -0
  139. package/public/icon.png +0 -0
  140. package/public/icon.svg +26 -0
  141. package/public/logo.jpeg +0 -0
  142. package/public/logo.png +0 -0
  143. package/public/logo2.jpeg +0 -0
  144. package/public/logo2.png +0 -0
  145. package/public/placeholder-logo.png +0 -0
  146. package/public/placeholder-logo.svg +1 -0
  147. package/public/placeholder-user.jpg +0 -0
  148. package/public/placeholder.jpg +0 -0
  149. package/public/placeholder.svg +1 -0
  150. package/styles/globals.css +209 -0
  151. package/tailwind.config.ts +15 -0
  152. package/tsconfig.json +41 -0
@@ -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
+ }