@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,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,19 @@
1
+ import * as React from 'react'
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener('change', onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener('change', onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }
@@ -0,0 +1,191 @@
1
+ 'use client'
2
+
3
+ // Inspired by react-hot-toast library
4
+ import * as React from 'react'
5
+
6
+ import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
7
+
8
+ const TOAST_LIMIT = 1
9
+ const TOAST_REMOVE_DELAY = 1000000
10
+
11
+ type ToasterToast = ToastProps & {
12
+ id: string
13
+ title?: React.ReactNode
14
+ description?: React.ReactNode
15
+ action?: ToastActionElement
16
+ }
17
+
18
+ const actionTypes = {
19
+ ADD_TOAST: 'ADD_TOAST',
20
+ UPDATE_TOAST: 'UPDATE_TOAST',
21
+ DISMISS_TOAST: 'DISMISS_TOAST',
22
+ REMOVE_TOAST: 'REMOVE_TOAST',
23
+ } as const
24
+
25
+ let count = 0
26
+
27
+ function genId() {
28
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
29
+ return count.toString()
30
+ }
31
+
32
+ type ActionType = typeof actionTypes
33
+
34
+ type Action =
35
+ | {
36
+ type: ActionType['ADD_TOAST']
37
+ toast: ToasterToast
38
+ }
39
+ | {
40
+ type: ActionType['UPDATE_TOAST']
41
+ toast: Partial<ToasterToast>
42
+ }
43
+ | {
44
+ type: ActionType['DISMISS_TOAST']
45
+ toastId?: ToasterToast['id']
46
+ }
47
+ | {
48
+ type: ActionType['REMOVE_TOAST']
49
+ toastId?: ToasterToast['id']
50
+ }
51
+
52
+ interface State {
53
+ toasts: ToasterToast[]
54
+ }
55
+
56
+ const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
57
+
58
+ const addToRemoveQueue = (toastId: string) => {
59
+ if (toastTimeouts.has(toastId)) {
60
+ return
61
+ }
62
+
63
+ const timeout = setTimeout(() => {
64
+ toastTimeouts.delete(toastId)
65
+ dispatch({
66
+ type: 'REMOVE_TOAST',
67
+ toastId: toastId,
68
+ })
69
+ }, TOAST_REMOVE_DELAY)
70
+
71
+ toastTimeouts.set(toastId, timeout)
72
+ }
73
+
74
+ export const reducer = (state: State, action: Action): State => {
75
+ switch (action.type) {
76
+ case 'ADD_TOAST':
77
+ return {
78
+ ...state,
79
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
80
+ }
81
+
82
+ case 'UPDATE_TOAST':
83
+ return {
84
+ ...state,
85
+ toasts: state.toasts.map((t) =>
86
+ t.id === action.toast.id ? { ...t, ...action.toast } : t,
87
+ ),
88
+ }
89
+
90
+ case 'DISMISS_TOAST': {
91
+ const { toastId } = action
92
+
93
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
94
+ // but I'll keep it here for simplicity
95
+ if (toastId) {
96
+ addToRemoveQueue(toastId)
97
+ } else {
98
+ state.toasts.forEach((toast) => {
99
+ addToRemoveQueue(toast.id)
100
+ })
101
+ }
102
+
103
+ return {
104
+ ...state,
105
+ toasts: state.toasts.map((t) =>
106
+ t.id === toastId || toastId === undefined
107
+ ? {
108
+ ...t,
109
+ open: false,
110
+ }
111
+ : t,
112
+ ),
113
+ }
114
+ }
115
+ case 'REMOVE_TOAST':
116
+ if (action.toastId === undefined) {
117
+ return {
118
+ ...state,
119
+ toasts: [],
120
+ }
121
+ }
122
+ return {
123
+ ...state,
124
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
125
+ }
126
+ }
127
+ }
128
+
129
+ const listeners: Array<(state: State) => void> = []
130
+
131
+ let memoryState: State = { toasts: [] }
132
+
133
+ function dispatch(action: Action) {
134
+ memoryState = reducer(memoryState, action)
135
+ listeners.forEach((listener) => {
136
+ listener(memoryState)
137
+ })
138
+ }
139
+
140
+ type Toast = Omit<ToasterToast, 'id'>
141
+
142
+ function toast({ ...props }: Toast) {
143
+ const id = genId()
144
+
145
+ const update = (props: ToasterToast) =>
146
+ dispatch({
147
+ type: 'UPDATE_TOAST',
148
+ toast: { ...props, id },
149
+ })
150
+ const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
151
+
152
+ dispatch({
153
+ type: 'ADD_TOAST',
154
+ toast: {
155
+ ...props,
156
+ id,
157
+ open: true,
158
+ onOpenChange: (open) => {
159
+ if (!open) dismiss()
160
+ },
161
+ },
162
+ })
163
+
164
+ return {
165
+ id: id,
166
+ dismiss,
167
+ update,
168
+ }
169
+ }
170
+
171
+ function useToast() {
172
+ const [state, setState] = React.useState<State>(memoryState)
173
+
174
+ React.useEffect(() => {
175
+ listeners.push(setState)
176
+ return () => {
177
+ const index = listeners.indexOf(setState)
178
+ if (index > -1) {
179
+ listeners.splice(index, 1)
180
+ }
181
+ }
182
+ }, [state])
183
+
184
+ return {
185
+ ...state,
186
+ toast,
187
+ dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
188
+ }
189
+ }
190
+
191
+ export { useToast, toast }
File without changes
@@ -0,0 +1,66 @@
1
+ // lib/api.ts
2
+
3
+ const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://lumina-z3v8.onrender.com';
4
+
5
+ export const apiClient = {
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // SDK Client (Uses X-API-Key)
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ sdk: async (endpoint: string, apiKey: string, options: RequestInit = {}) => {
10
+ if (!apiKey) throw new Error("API Key is required for SDK calls");
11
+
12
+ const headers = {
13
+ 'Content-Type': 'application/json',
14
+ 'X-API-Key': apiKey,
15
+ ...options.headers,
16
+ };
17
+
18
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers });
19
+
20
+ // Attempt to parse JSON safely, even on error
21
+ let data;
22
+ try {
23
+ data = await response.json();
24
+ } catch {
25
+ data = { detail: response.statusText };
26
+ }
27
+
28
+ if (!response.ok) {
29
+ throw new Error(data.detail || `SDK API Error: ${response.status}`);
30
+ }
31
+
32
+ return data;
33
+ },
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Dashboard Client (Uses Bearer JWT)
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ dashboard: async (endpoint: string, options: RequestInit = {}) => {
39
+ // Note: Assuming you store the dev token in localStorage upon login
40
+ const token = typeof window !== 'undefined' ? localStorage.getItem('lumina_dev_token') : null;
41
+
42
+ const headers: Record<string, string> = {
43
+ 'Content-Type': 'application/json',
44
+ ...options.headers as Record<string, string>,
45
+ };
46
+
47
+ if (token) {
48
+ headers['Authorization'] = `Bearer ${token}`;
49
+ }
50
+
51
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers });
52
+
53
+ let data;
54
+ try {
55
+ data = await response.json();
56
+ } catch {
57
+ data = { detail: response.statusText };
58
+ }
59
+
60
+ if (!response.ok) {
61
+ throw new Error(data.detail || `Dashboard API Error: ${response.status}`);
62
+ }
63
+
64
+ return data;
65
+ }
66
+ };
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@lumina/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Lumina SDK - Web3 wallet auth and identity verification",
5
+ "private": false,
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": ["dist", "README.md"],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react --external react-dom",
19
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
20
+ "prepublishOnly": "pnpm run build"
21
+ },
22
+ "keywords": ["web3", "wallet", "auth", "kyc", "lumina"],
23
+ "author": "Oyeniran Jeremiah Damilare <jerydan148@gmail.com>",
24
+ "license": "MIT",
25
+ "peerDependencies": {
26
+ "react": ">=18",
27
+ "react-dom": ">=18",
28
+ "next": ">=13"
29
+ },
30
+ "devDependencies": {
31
+ "tsup": "^8.0.0",
32
+ "typescript": "^5.7.3",
33
+ "@types/react": "^19",
34
+ "@types/react-dom": "^19"
35
+ },
36
+ "dependencies": {
37
+ "clsx": "^2.1.1",
38
+ "tailwind-merge": "^3.3.1",
39
+ "viem": "^2.48.11",
40
+ "lucide-react": "^0.564.0"
41
+ }
42
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "skipLibCheck": true,
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["./src/*"]
15
+ }
16
+ },
17
+ "include": ["src"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }