@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,242 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { Loader2, CheckCircle, AlertCircle, ExternalLink, Shield } from 'lucide-react'
5
+ import { Button } from '@/components/ui/button'
6
+ import { Card } from '@/components/ui/card'
7
+ import { useLumina } from './lumina-provider'
8
+ import { encodeFunctionData, parseEther } from 'viem'
9
+
10
+ interface TransactionDetails {
11
+ from: string // The smart account address
12
+ signerAddress: string // The underlying Turnkey embedded wallet EOA
13
+ to: string
14
+ amount: string
15
+ token?: string
16
+ network?: string
17
+ chainId?: string
18
+ gasFee?: string
19
+ estimatedTime?: string
20
+ callData?: string // Raw hex data for contract interactions
21
+ isDeployed?: boolean // Tells us if we need to append initCode
22
+ factoryAddress?: string // Required if !isDeployed
23
+ }
24
+
25
+ interface TransactionConfirmProps {
26
+ transaction: TransactionDetails
27
+ onConfirm?: () => Promise<void>
28
+ onCancel?: () => void
29
+ }
30
+
31
+ type TransactionStatus = 'pending' | 'sponsoring' | 'signing' | 'submitting' | 'confirmed' | 'failed'
32
+
33
+ export function TransactionConfirm({
34
+ transaction,
35
+ onConfirm,
36
+ onCancel,
37
+ }: TransactionConfirmProps) {
38
+ const { fetchApi } = useLumina()
39
+ const [status, setStatus] = useState<TransactionStatus>('pending')
40
+ const [txHash, setTxHash] = useState<string>('')
41
+ const [errorMessage, setErrorMessage] = useState<string>('')
42
+
43
+ const handleConfirm = async () => {
44
+ setStatus('sponsoring')
45
+ setErrorMessage('')
46
+
47
+ try {
48
+ if (onConfirm) await onConfirm()
49
+
50
+ const chainId = transaction.chainId || '1'
51
+
52
+ // ── 1. Handle Counterfactual Deployment (initCode) ──────────────────────
53
+ let finalInitCode = '0x'
54
+
55
+ if (!transaction.isDeployed && transaction.factoryAddress && transaction.signerAddress) {
56
+ const factoryABI = [{
57
+ name: 'createAccount',
58
+ type: 'function',
59
+ inputs: [
60
+ { name: 'owner', type: 'address' },
61
+ { name: 'salt', type: 'uint256' }
62
+ ]
63
+ }]
64
+
65
+ const createAccountData = encodeFunctionData({
66
+ abi: factoryABI,
67
+ functionName: 'createAccount',
68
+ args: [transaction.signerAddress as `0x${string}`, 0n] // salt = 0 matches auth.py
69
+ })
70
+
71
+ // initCode is Factory Address + calldata
72
+ finalInitCode = `${transaction.factoryAddress}${createAccountData.replace('0x', '')}`
73
+ }
74
+
75
+ // ── 2. Encode SmartAccount execution (callData) ─────────────────────────
76
+ const smartAccountABI = [{
77
+ name: 'execute',
78
+ type: 'function',
79
+ inputs: [
80
+ { name: 'target', type: 'address' },
81
+ { name: 'value', type: 'uint256' },
82
+ { name: 'data', type: 'bytes' }
83
+ ]
84
+ }]
85
+
86
+ const finalCallData = encodeFunctionData({
87
+ abi: smartAccountABI,
88
+ functionName: 'execute',
89
+ args: [
90
+ transaction.to as `0x${string}`,
91
+ transaction.amount ? parseEther(transaction.amount) : 0n,
92
+ (transaction.callData as `0x${string}`) || '0x'
93
+ ]
94
+ })
95
+
96
+ // ── 3. Build the UserOperation ──────────────────────────────────────────
97
+ const partialUserOp = {
98
+ sender: transaction.from,
99
+ nonce: 0, // In production, fetch this via eth_call to the EntryPoint
100
+ init_code: finalInitCode,
101
+ call_data: finalCallData,
102
+ call_gas_limit: 50000,
103
+ verification_gas_limit: 150000,
104
+ pre_verification_gas: 21000,
105
+ max_fee_per_gas: 2000000000,
106
+ max_priority_fee_per_gas: 1000000000,
107
+ chain_id: chainId
108
+ }
109
+
110
+ // ── 4. Get Paymaster Sponsorship ────────────────────────────────────────
111
+ const signRes = await fetchApi('/v1/paymaster/sign', {
112
+ method: 'POST',
113
+ body: JSON.stringify(partialUserOp)
114
+ })
115
+
116
+ setStatus('signing')
117
+
118
+ // ── 5. Local User Signature (Turnkey integration goes here) ─────────────
119
+ // Simulating a Passkey/Biometric signing delay
120
+ await new Promise(resolve => setTimeout(resolve, 1500))
121
+ const mockUserSignature = "0xabcd1234..." // Replace with Turnkey sign output
122
+
123
+ const fullySignedOp = {
124
+ ...partialUserOp,
125
+ paymasterAndData: signRes.paymaster_and_data,
126
+ signature: mockUserSignature
127
+ }
128
+
129
+ setStatus('submitting')
130
+
131
+ // ── 6. Submit to the Bundler ────────────────────────────────────────────
132
+ const submitRes = await fetchApi('/v1/paymaster/submit', {
133
+ method: 'POST',
134
+ body: JSON.stringify({
135
+ user_op_id: signRes.user_op_id,
136
+ signed_user_op: fullySignedOp,
137
+ chain_id: chainId
138
+ })
139
+ })
140
+
141
+ setTxHash(submitRes.user_op_hash)
142
+ setStatus('confirmed')
143
+
144
+ } catch (error: any) {
145
+ console.error("Transaction failed:", error)
146
+ setStatus('failed')
147
+ setErrorMessage(error.message || "Failed to process transaction")
148
+ }
149
+ }
150
+
151
+ return (
152
+ <Card className="glassmorphism-dark p-8 border-border w-full max-w-lg mx-auto">
153
+ {status === 'pending' && (
154
+ <>
155
+ <div className="mb-6">
156
+ <h2 className="text-2xl font-bold mb-2">Confirm Transaction</h2>
157
+ <p className="text-foreground/60 text-sm">Review the details before confirming</p>
158
+ </div>
159
+
160
+ <div className="space-y-4 mb-6 p-4 bg-white/5 rounded-lg border border-border/50">
161
+ <div className="flex justify-between items-start">
162
+ <span className="text-foreground/60">To Address</span>
163
+ <span className="font-mono text-sm">{transaction.to.slice(0, 6)}...{transaction.to.slice(-4)}</span>
164
+ </div>
165
+ <div className="flex justify-between items-start border-t border-border/30 pt-4">
166
+ <span className="text-foreground/60">Amount</span>
167
+ <span className="font-bold text-lg">{transaction.amount} {transaction.token || 'ETH'}</span>
168
+ </div>
169
+ {transaction.gasFee && (
170
+ <div className="flex justify-between items-start border-t border-border/30 pt-4">
171
+ <span className="text-foreground/60">Gas Fee</span>
172
+ <span className="font-medium text-emerald-400">Sponsored by Paymaster</span>
173
+ </div>
174
+ )}
175
+ </div>
176
+
177
+ <div className="mb-6 p-4 bg-emerald-600/10 rounded-lg border border-emerald-600/30 flex gap-3">
178
+ <Shield className="text-emerald-500 shrink-0 mt-0.5" size={20} />
179
+ <div className="text-sm">
180
+ <p className="font-medium mb-1">Gasless Transaction</p>
181
+ <p className="text-foreground/70">The network fees for this action are being covered by the application.</p>
182
+ </div>
183
+ </div>
184
+
185
+ <div className="flex gap-3">
186
+ <Button onClick={onCancel} variant="outline" className="flex-1">
187
+ Cancel
188
+ </Button>
189
+ <Button onClick={handleConfirm} className="flex-1 bg-emerald-600 hover:bg-emerald-700">
190
+ Confirm Transaction
191
+ </Button>
192
+ </div>
193
+ </>
194
+ )}
195
+
196
+ {(status === 'sponsoring' || status === 'signing' || status === 'submitting') && (
197
+ <div className="text-center py-8">
198
+ <Loader2 className="animate-spin mx-auto mb-4 text-emerald-500" size={48} />
199
+ <h3 className="text-xl font-bold mb-2">
200
+ {status === 'sponsoring' && "Sponsoring Gas..."}
201
+ {status === 'signing' && "Awaiting Signature..."}
202
+ {status === 'submitting' && "Submitting to Network..."}
203
+ </h3>
204
+ <p className="text-foreground/60 text-sm">Please do not close this window.</p>
205
+ </div>
206
+ )}
207
+
208
+ {status === 'confirmed' && (
209
+ <div className="text-center py-8">
210
+ <CheckCircle className="mx-auto mb-4 text-emerald-500" size={48} />
211
+ <h3 className="text-xl font-bold mb-2">Transaction Submitted</h3>
212
+ <p className="text-foreground/60 mb-4 text-sm">Your operation has been sent to the bundler network.</p>
213
+
214
+ <div className="p-3 bg-white/5 rounded-lg border border-border/50 mb-6 text-left">
215
+ <p className="text-xs text-foreground/60 mb-1">UserOp Hash</p>
216
+ <div className="flex items-center gap-2">
217
+ <code className="text-xs font-mono flex-1 truncate">{txHash}</code>
218
+ <button className="p-1 hover:bg-white/10 rounded">
219
+ <ExternalLink size={16} className="text-foreground/60" />
220
+ </button>
221
+ </div>
222
+ </div>
223
+ <Button onClick={onCancel} className="w-full bg-emerald-600 hover:bg-emerald-700">
224
+ Return to Wallet
225
+ </Button>
226
+ </div>
227
+ )}
228
+
229
+ {status === 'failed' && (
230
+ <div className="text-center py-8">
231
+ <AlertCircle className="mx-auto mb-4 text-red-500" size={48} />
232
+ <h3 className="text-xl font-bold mb-2">Transaction Failed</h3>
233
+ <p className="text-foreground/60 mb-6 text-sm">{errorMessage}</p>
234
+
235
+ <Button onClick={() => setStatus('pending')} className="w-full bg-emerald-600 hover:bg-emerald-700">
236
+ Try Again
237
+ </Button>
238
+ </div>
239
+ )}
240
+ </Card>
241
+ )
242
+ }
@@ -0,0 +1,157 @@
1
+ 'use client'
2
+
3
+ import { Copy, ExternalLink, Send, Download, Loader2 } from 'lucide-react'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Card } from '@/components/ui/card'
6
+ import { useState, useEffect } from 'react'
7
+ import { useLumina } from './lumina-provider'
8
+
9
+ interface WalletDisplayProps {
10
+ signerAddress: string // We need the signer to fetch details from your backend
11
+ smartAccountAddress: string
12
+ balance?: string
13
+ token?: string
14
+ network?: string
15
+ chainId?: string
16
+ onSend?: () => void
17
+ onReceive?: () => void
18
+ }
19
+
20
+ export function WalletDisplay({
21
+ signerAddress,
22
+ smartAccountAddress,
23
+ balance = '0.00',
24
+ token = 'ETH',
25
+ network = 'Ethereum',
26
+ chainId = '1',
27
+ onSend,
28
+ onReceive,
29
+ }: WalletDisplayProps) {
30
+ const { fetchApi } = useLumina()
31
+ const [copied, setCopied] = useState(false)
32
+ const [isDeployed, setIsDeployed] = useState<boolean | null>(null)
33
+ const [isLoadingState, setIsLoadingState] = useState(true)
34
+
35
+ // Fetch wallet details on mount
36
+ useEffect(() => {
37
+ let isMounted = true
38
+
39
+ const syncWalletState = async () => {
40
+ try {
41
+ // 1. Fetch current DB state
42
+ const walletData = await fetchApi(`/v1/wallets/${signerAddress}/${chainId}`)
43
+
44
+ if (!isMounted) return
45
+
46
+ setIsDeployed(walletData.is_deployed)
47
+
48
+ // 2. If the DB thinks it's not deployed, double-check on-chain
49
+ // This is crucial because users might fund their counterfactual address
50
+ if (!walletData.is_deployed) {
51
+ const deployCheck = await fetchApi(`/v1/wallets/${signerAddress}/${chainId}/check-deployment`, {
52
+ method: 'POST'
53
+ })
54
+ if (isMounted) setIsDeployed(deployCheck.is_deployed)
55
+ }
56
+ } catch (error) {
57
+ console.error("Failed to sync wallet state:", error)
58
+ } finally {
59
+ if (isMounted) setIsLoadingState(false)
60
+ }
61
+ }
62
+
63
+ syncWalletState()
64
+ return () => { isMounted = false }
65
+ }, [signerAddress, chainId, fetchApi])
66
+
67
+ const handleCopy = () => {
68
+ navigator.clipboard.writeText(smartAccountAddress)
69
+ setCopied(true)
70
+ setTimeout(() => setCopied(false), 2000)
71
+ }
72
+
73
+ return (
74
+ <Card className="glassmorphism-dark p-8 border-border w-full max-w-md mx-auto">
75
+ <div className="text-center mb-8">
76
+ <h2 className="text-2xl font-bold mb-2">Your Wallet</h2>
77
+ <div className="flex items-center justify-center gap-2">
78
+ <p className="text-foreground/60 text-sm">{network} Network</p>
79
+ {isLoadingState ? (
80
+ <Loader2 className="w-3 h-3 animate-spin text-foreground/60" />
81
+ ) : (
82
+ <span className={`text-[10px] px-2 py-0.5 rounded-full ${isDeployed ? 'bg-emerald-500/20 text-emerald-400' : 'bg-amber-500/20 text-amber-400'}`}>
83
+ {isDeployed ? 'Deployed' : 'Counterfactual'}
84
+ </span>
85
+ )}
86
+ </div>
87
+ </div>
88
+
89
+ {/* Balance Display */}
90
+ <div className="text-center mb-8 p-6 bg-linear-to-b from-emerald-600/20 to-emerald-600/5 rounded-lg border border-emerald-600/30">
91
+ <p className="text-foreground/60 text-sm mb-2">Available Balance</p>
92
+ <p className="text-4xl font-bold text-emerald-400 mb-2">{balance}</p>
93
+ <p className="text-foreground/60">{token}</p>
94
+ </div>
95
+
96
+ {/* Address Display */}
97
+ <div className="mb-6 p-4 bg-white/5 rounded-lg border border-border/50">
98
+ <p className="text-xs text-foreground/60 mb-2">Smart Account Address</p>
99
+ <div className="flex items-center gap-2">
100
+ <code className="text-sm font-mono flex-1 truncate">{smartAccountAddress}</code>
101
+ <button
102
+ onClick={handleCopy}
103
+ className="p-2 hover:bg-white/10 rounded-lg transition"
104
+ title="Copy address"
105
+ >
106
+ <Copy size={16} className="text-foreground/60" />
107
+ </button>
108
+ </div>
109
+ {copied && <p className="text-xs text-emerald-500 mt-2">Copied!</p>}
110
+ </div>
111
+
112
+ {/* Action Buttons */}
113
+ <div className="grid grid-cols-2 gap-3 mb-6">
114
+ <Button
115
+ onClick={onReceive}
116
+ variant="outline"
117
+ className="gap-2"
118
+ >
119
+ <Download size={18} />
120
+ Receive
121
+ </Button>
122
+ <Button
123
+ onClick={onSend}
124
+ className="bg-emerald-600 hover:bg-emerald-700 gap-2"
125
+ >
126
+ <Send size={18} />
127
+ Send
128
+ </Button>
129
+ </div>
130
+
131
+ {/* Network Info */}
132
+ <div className="p-4 bg-white/5 rounded-lg border border-border/50 space-y-2">
133
+ <div className="flex justify-between text-sm">
134
+ <span className="text-foreground/60">Network</span>
135
+ <span className="font-medium">{network}</span>
136
+ </div>
137
+ <div className="flex justify-between text-sm border-t border-border/30 pt-2">
138
+ <span className="text-foreground/60">Network Status</span>
139
+ <div className="flex items-center gap-2">
140
+ <div className="w-2 h-2 rounded-full bg-emerald-500"></div>
141
+ <span className="font-medium">Operational</span>
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ {/* View on Explorer */}
147
+ <Button
148
+ variant="outline"
149
+ className="w-full mt-6 gap-2"
150
+ onClick={() => window.open(`https://etherscan.io/address/${smartAccountAddress}`, '_blank')}
151
+ >
152
+ <ExternalLink size={18} />
153
+ View on Explorer
154
+ </Button>
155
+ </Card>
156
+ )
157
+ }
@@ -0,0 +1,163 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import { Loader2, Lock, Mail } 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' // Our centralized API client
10
+ export interface WalletLoginProps {
11
+ authMethods?: string[];
12
+ onSuccess?: (address: string) => void;
13
+ onError?: (err: Error) => void;
14
+ }
15
+ export default function WalletLogin({ authMethods, onSuccess, onError }: WalletLoginProps) { const router = useRouter()
16
+ const [isLoading, setIsLoading] = useState(false)
17
+ const [isRegistering, setIsRegistering] = useState(false)
18
+ const [error, setError] = useState('')
19
+
20
+ const [formData, setFormData] = useState({
21
+ name: '',
22
+ email: '',
23
+ password: ''
24
+ })
25
+
26
+ const handleSubmit = async (e: React.FormEvent) => {
27
+ e.preventDefault()
28
+ setIsLoading(true)
29
+ setError('')
30
+
31
+ try {
32
+ if (isRegistering) {
33
+ // 1. Register a new developer
34
+ await apiClient.dashboard('/v1/developers/register', {
35
+ method: 'POST',
36
+ body: JSON.stringify({
37
+ name: formData.name,
38
+ email: formData.email,
39
+ password: formData.password
40
+ })
41
+ })
42
+ // Auto-login after registration
43
+ }
44
+
45
+ // 2. Log in to get the JWT access token
46
+ const data = await apiClient.dashboard('/v1/developers/login', {
47
+ method: 'POST',
48
+ body: JSON.stringify({
49
+ email: formData.email,
50
+ password: formData.password
51
+ })
52
+ })
53
+
54
+ // 3. Securely store the JWT for future dashboard API calls
55
+ if (typeof window !== 'undefined') {
56
+ localStorage.setItem('lumina_dev_token', data.access_token)
57
+ }
58
+
59
+ // 4. Redirect to the main dashboard
60
+ router.push('/dashboard')
61
+
62
+ } catch (err: any) {
63
+ setError(err.message || 'Authentication failed. Please check your credentials.')
64
+ } finally {
65
+ setIsLoading(false)
66
+ }
67
+ }
68
+
69
+ return (
70
+ <div className="min-h-screen flex items-center justify-center p-4 bg-background">
71
+ <Card className="glassmorphism-dark p-8 border-border w-full max-w-md">
72
+ <div className="mb-8 text-center">
73
+ <h1 className="text-3xl font-bold mb-2">Lumina Portal</h1>
74
+ <p className="text-foreground/60 text-sm">
75
+ {isRegistering ? 'Create your developer account' : 'Sign in to manage your workspace'}
76
+ </p>
77
+ </div>
78
+
79
+ {error && (
80
+ <div className="mb-6 p-3 bg-red-500/10 border border-red-500/50 rounded-lg text-red-400 text-sm text-center">
81
+ {error}
82
+ </div>
83
+ )}
84
+
85
+ <form onSubmit={handleSubmit} className="space-y-4">
86
+ {isRegistering && (
87
+ <div>
88
+ <label className="block text-sm font-medium mb-2">Full Name</label>
89
+ <Input
90
+ type="text"
91
+ placeholder="Jane Doe"
92
+ value={formData.name}
93
+ onChange={(e) => setFormData({ ...formData, name: e.target.value })}
94
+ required={isRegistering}
95
+ className="bg-white/5 border-border"
96
+ />
97
+ </div>
98
+ )}
99
+
100
+ <div>
101
+ <label className="block text-sm font-medium mb-2">Email Address</label>
102
+ <div className="relative">
103
+ <Mail className="absolute left-3 top-2.5 text-foreground/40" size={18} />
104
+ <Input
105
+ type="email"
106
+ placeholder="developer@company.com"
107
+ value={formData.email}
108
+ onChange={(e) => setFormData({ ...formData, email: e.target.value })}
109
+ required
110
+ className="pl-10 bg-white/5 border-border"
111
+ />
112
+ </div>
113
+ </div>
114
+
115
+ <div>
116
+ <label className="block text-sm font-medium mb-2">Password</label>
117
+ <div className="relative">
118
+ <Lock className="absolute left-3 top-2.5 text-foreground/40" size={18} />
119
+ <Input
120
+ type="password"
121
+ placeholder="••••••••••••"
122
+ value={formData.password}
123
+ onChange={(e) => setFormData({ ...formData, password: e.target.value })}
124
+ required
125
+ minLength={12}
126
+ className="pl-10 bg-white/5 border-border"
127
+ />
128
+ </div>
129
+ {isRegistering && (
130
+ <p className="text-xs text-foreground/50 mt-2">Must be at least 12 characters long.</p>
131
+ )}
132
+ </div>
133
+
134
+ <Button
135
+ type="submit"
136
+ disabled={isLoading}
137
+ className="w-full bg-emerald-600 hover:bg-emerald-700 mt-6"
138
+ >
139
+ {isLoading ? (
140
+ <Loader2 className="animate-spin" size={18} />
141
+ ) : (
142
+ isRegistering ? 'Create Account' : 'Sign In'
143
+ )}
144
+ </Button>
145
+ </form>
146
+
147
+ <div className="mt-6 text-center text-sm text-foreground/60">
148
+ {isRegistering ? 'Already have an account?' : "Don't have an account?"}{' '}
149
+ <button
150
+ type="button"
151
+ onClick={() => {
152
+ setIsRegistering(!isRegistering)
153
+ setError('')
154
+ }}
155
+ className="text-emerald-400 hover:text-emerald-300 font-medium"
156
+ >
157
+ {isRegistering ? 'Sign In' : 'Register'}
158
+ </button>
159
+ </div>
160
+ </Card>
161
+ </div>
162
+ )
163
+ }
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ ThemeProvider as NextThemesProvider,
6
+ type ThemeProviderProps,
7
+ } from 'next-themes'
8
+
9
+ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as AccordionPrimitive from '@radix-ui/react-accordion'
5
+ import { ChevronDownIcon } from 'lucide-react'
6
+
7
+ import { cn } from '@/lib/utils'
8
+
9
+ function Accordion({
10
+ ...props
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
12
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />
13
+ }
14
+
15
+ function AccordionItem({
16
+ className,
17
+ ...props
18
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
19
+ return (
20
+ <AccordionPrimitive.Item
21
+ data-slot="accordion-item"
22
+ className={cn('border-b last:border-b-0', className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ function AccordionTrigger({
29
+ className,
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
33
+ return (
34
+ <AccordionPrimitive.Header className="flex">
35
+ <AccordionPrimitive.Trigger
36
+ data-slot="accordion-trigger"
37
+ className={cn(
38
+ 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
39
+ className,
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
45
+ </AccordionPrimitive.Trigger>
46
+ </AccordionPrimitive.Header>
47
+ )
48
+ }
49
+
50
+ function AccordionContent({
51
+ className,
52
+ children,
53
+ ...props
54
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
55
+ return (
56
+ <AccordionPrimitive.Content
57
+ data-slot="accordion-content"
58
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
59
+ {...props}
60
+ >
61
+ <div className={cn('pt-0 pb-4', className)}>{children}</div>
62
+ </AccordionPrimitive.Content>
63
+ )
64
+ }
65
+
66
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }