spaps-mcp 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/CHANGELOG.md +7 -0
- package/README.md +76 -0
- package/dist/chunk-GPBTYWDD.js +496 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +9 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +26 -0
- package/dist/resources/SPAPS_SURFACE_CONTRACT.md +125 -0
- package/dist/resources/glossary.md +36 -0
- package/dist/resources/llms.txt +15 -0
- package/dist/wizard/step-01-setup.md +149 -0
- package/dist/wizard/step-02-environment.md +291 -0
- package/dist/wizard/step-03-sdk-init.md +351 -0
- package/dist/wizard/step-04-email-auth.md +311 -0
- package/dist/wizard/step-05-wallet-auth.md +368 -0
- package/dist/wizard/step-06-magic-link.md +560 -0
- package/dist/wizard/step-07-payments.md +529 -0
- package/dist/wizard/step-08-whitelist.md +338 -0
- package/dist/wizard/step-09-admin.md +579 -0
- package/dist/wizard/step-10-errors.md +525 -0
- package/dist/wizard/step-11-ui-polish.md +640 -0
- package/dist/wizard/step-12-testing.md +588 -0
- package/dist/wizard/wizard.lock +67 -0
- package/package.json +66 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
# SPAPS Integration Step 11/12: UI Polish & Theme
|
|
2
|
+
|
|
3
|
+
## Prerequisites Check
|
|
4
|
+
|
|
5
|
+
Before starting, confirm you have completed:
|
|
6
|
+
- ✅ Step 1-10: Full functionality implementation
|
|
7
|
+
- ✅ shadcn/ui components installed and working
|
|
8
|
+
- ✅ Tailwind CSS configured
|
|
9
|
+
|
|
10
|
+
## Required TodoWrite List
|
|
11
|
+
|
|
12
|
+
Create a TodoWrite with EXACTLY these items:
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
TodoWrite({
|
|
16
|
+
todos: [
|
|
17
|
+
{ content: "Install and configure next-themes", status: "pending", activeForm: "Installing next-themes" },
|
|
18
|
+
{ content: "Add theme provider to app layout", status: "pending", activeForm: "Adding theme provider" },
|
|
19
|
+
{ content: "Implement dark mode toggle component", status: "pending", activeForm: "Implementing theme toggle" },
|
|
20
|
+
{ content: "Create minimalist layout with whitespace", status: "pending", activeForm: "Creating minimalist layout" },
|
|
21
|
+
{ content: "Add loading states with shadcn/ui skeleton", status: "pending", activeForm: "Adding loading states" },
|
|
22
|
+
{ content: "Ensure responsive design across devices", status: "pending", activeForm: "Ensuring responsive design" },
|
|
23
|
+
{ content: "Add animations and transitions", status: "pending", activeForm: "Adding animations" },
|
|
24
|
+
{ content: "Request step 12 of SPAPS integration wizard", status: "pending", activeForm: "Requesting next step" }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## UI Polish Implementation
|
|
30
|
+
|
|
31
|
+
### 1. Install Theme Support
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install next-themes
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Theme Provider Setup
|
|
38
|
+
|
|
39
|
+
Create the theme provider:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// components/theme-provider.tsx
|
|
43
|
+
'use client'
|
|
44
|
+
|
|
45
|
+
import * as React from "react"
|
|
46
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
|
47
|
+
import { type ThemeProviderProps } from "next-themes/dist/types"
|
|
48
|
+
|
|
49
|
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
50
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Update your root layout:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// app/layout.tsx
|
|
58
|
+
import { Inter } from 'next/font/google'
|
|
59
|
+
import { ThemeProvider } from "@/components/theme-provider"
|
|
60
|
+
import { Toaster } from "@/components/ui/sonner"
|
|
61
|
+
import './globals.css'
|
|
62
|
+
|
|
63
|
+
const inter = Inter({ subsets: ['latin'] })
|
|
64
|
+
|
|
65
|
+
export default function RootLayout({
|
|
66
|
+
children,
|
|
67
|
+
}: {
|
|
68
|
+
children: React.ReactNode
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<html lang="en" suppressHydrationWarning>
|
|
72
|
+
<body className={inter.className}>
|
|
73
|
+
<ThemeProvider
|
|
74
|
+
attribute="class"
|
|
75
|
+
defaultTheme="system"
|
|
76
|
+
enableSystem
|
|
77
|
+
disableTransitionOnChange
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
<Toaster />
|
|
81
|
+
</ThemeProvider>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Theme Toggle Component
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// components/theme-toggle.tsx
|
|
92
|
+
'use client'
|
|
93
|
+
|
|
94
|
+
import { Moon, Sun } from "lucide-react"
|
|
95
|
+
import { useTheme } from "next-themes"
|
|
96
|
+
import { Button } from "@/components/ui/button"
|
|
97
|
+
import {
|
|
98
|
+
DropdownMenu,
|
|
99
|
+
DropdownMenuContent,
|
|
100
|
+
DropdownMenuItem,
|
|
101
|
+
DropdownMenuTrigger,
|
|
102
|
+
} from "@/components/ui/dropdown-menu"
|
|
103
|
+
|
|
104
|
+
export function ThemeToggle() {
|
|
105
|
+
const { setTheme } = useTheme()
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<DropdownMenu>
|
|
109
|
+
<DropdownMenuTrigger asChild>
|
|
110
|
+
<Button variant="outline" size="icon">
|
|
111
|
+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
112
|
+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
113
|
+
<span className="sr-only">Toggle theme</span>
|
|
114
|
+
</Button>
|
|
115
|
+
</DropdownMenuTrigger>
|
|
116
|
+
<DropdownMenuContent align="end">
|
|
117
|
+
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
118
|
+
Light
|
|
119
|
+
</DropdownMenuItem>
|
|
120
|
+
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
121
|
+
Dark
|
|
122
|
+
</DropdownMenuItem>
|
|
123
|
+
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
124
|
+
System
|
|
125
|
+
</DropdownMenuItem>
|
|
126
|
+
</DropdownMenuContent>
|
|
127
|
+
</DropdownMenu>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Minimalist Layout Component
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// components/layout/app-layout.tsx
|
|
136
|
+
'use client'
|
|
137
|
+
|
|
138
|
+
import { ReactNode } from 'react'
|
|
139
|
+
import { ThemeToggle } from '@/components/theme-toggle'
|
|
140
|
+
import { Button } from '@/components/ui/button'
|
|
141
|
+
import { useRouter } from 'next/navigation'
|
|
142
|
+
import { TokenManager } from 'spaps-sdk'
|
|
143
|
+
import { LogOut, User } from 'lucide-react'
|
|
144
|
+
|
|
145
|
+
interface AppLayoutProps {
|
|
146
|
+
children: ReactNode
|
|
147
|
+
user?: any
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function AppLayout({ children, user }: AppLayoutProps) {
|
|
151
|
+
const router = useRouter()
|
|
152
|
+
|
|
153
|
+
const handleLogout = () => {
|
|
154
|
+
TokenManager.clearTokens()
|
|
155
|
+
router.push('/auth')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div className="min-h-screen bg-background">
|
|
160
|
+
{/* Header */}
|
|
161
|
+
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
162
|
+
<div className="container flex h-16 items-center justify-between">
|
|
163
|
+
<div className="flex items-center gap-6">
|
|
164
|
+
<a href="/" className="flex items-center space-x-2">
|
|
165
|
+
<span className="font-bold text-xl">SPAPS</span>
|
|
166
|
+
</a>
|
|
167
|
+
<nav className="hidden md:flex items-center gap-6">
|
|
168
|
+
<a href="/dashboard" className="text-sm font-medium transition-colors hover:text-primary">
|
|
169
|
+
Dashboard
|
|
170
|
+
</a>
|
|
171
|
+
<a href="/payments" className="text-sm font-medium transition-colors hover:text-primary">
|
|
172
|
+
Payments
|
|
173
|
+
</a>
|
|
174
|
+
{user?.isAdmin && (
|
|
175
|
+
<a href="/admin" className="text-sm font-medium transition-colors hover:text-primary">
|
|
176
|
+
Admin
|
|
177
|
+
</a>
|
|
178
|
+
)}
|
|
179
|
+
</nav>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div className="flex items-center gap-4">
|
|
183
|
+
{user && (
|
|
184
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
185
|
+
<User className="h-4 w-4" />
|
|
186
|
+
{user.email}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
<ThemeToggle />
|
|
190
|
+
{user && (
|
|
191
|
+
<Button
|
|
192
|
+
variant="ghost"
|
|
193
|
+
size="icon"
|
|
194
|
+
onClick={handleLogout}
|
|
195
|
+
>
|
|
196
|
+
<LogOut className="h-4 w-4" />
|
|
197
|
+
</Button>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</header>
|
|
202
|
+
|
|
203
|
+
{/* Main Content */}
|
|
204
|
+
<main className="container mx-auto py-8 px-4 md:px-6 lg:px-8">
|
|
205
|
+
<div className="mx-auto max-w-7xl">
|
|
206
|
+
{children}
|
|
207
|
+
</div>
|
|
208
|
+
</main>
|
|
209
|
+
|
|
210
|
+
{/* Footer */}
|
|
211
|
+
<footer className="border-t mt-auto">
|
|
212
|
+
<div className="container py-8 md:py-12">
|
|
213
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
|
214
|
+
<div>
|
|
215
|
+
<h3 className="text-sm font-semibold mb-3">Product</h3>
|
|
216
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
217
|
+
<li><a href="#" className="hover:text-primary transition-colors">Features</a></li>
|
|
218
|
+
<li><a href="#" className="hover:text-primary transition-colors">Pricing</a></li>
|
|
219
|
+
<li><a href="#" className="hover:text-primary transition-colors">Documentation</a></li>
|
|
220
|
+
</ul>
|
|
221
|
+
</div>
|
|
222
|
+
<div>
|
|
223
|
+
<h3 className="text-sm font-semibold mb-3">Company</h3>
|
|
224
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
225
|
+
<li><a href="#" className="hover:text-primary transition-colors">About</a></li>
|
|
226
|
+
<li><a href="#" className="hover:text-primary transition-colors">Blog</a></li>
|
|
227
|
+
<li><a href="#" className="hover:text-primary transition-colors">Contact</a></li>
|
|
228
|
+
</ul>
|
|
229
|
+
</div>
|
|
230
|
+
<div>
|
|
231
|
+
<h3 className="text-sm font-semibold mb-3">Legal</h3>
|
|
232
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
233
|
+
<li><a href="#" className="hover:text-primary transition-colors">Privacy</a></li>
|
|
234
|
+
<li><a href="#" className="hover:text-primary transition-colors">Terms</a></li>
|
|
235
|
+
</ul>
|
|
236
|
+
</div>
|
|
237
|
+
<div>
|
|
238
|
+
<h3 className="text-sm font-semibold mb-3">Connect</h3>
|
|
239
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
240
|
+
<li><a href="#" className="hover:text-primary transition-colors">GitHub</a></li>
|
|
241
|
+
<li><a href="#" className="hover:text-primary transition-colors">Twitter</a></li>
|
|
242
|
+
<li><a href="#" className="hover:text-primary transition-colors">Discord</a></li>
|
|
243
|
+
</ul>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="border-t mt-8 pt-8">
|
|
247
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
248
|
+
© 2024 SPAPS. Built with Next.js and shadcn/ui.
|
|
249
|
+
</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</footer>
|
|
253
|
+
</div>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 5. Loading States with Skeleton
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// components/ui/loading-states.tsx
|
|
262
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
263
|
+
import { Card, CardContent, CardHeader } from "@/components/ui/card"
|
|
264
|
+
|
|
265
|
+
export function AuthFormSkeleton() {
|
|
266
|
+
return (
|
|
267
|
+
<Card className="w-[400px]">
|
|
268
|
+
<CardHeader>
|
|
269
|
+
<Skeleton className="h-8 w-[200px]" />
|
|
270
|
+
<Skeleton className="h-4 w-[250px]" />
|
|
271
|
+
</CardHeader>
|
|
272
|
+
<CardContent className="space-y-4">
|
|
273
|
+
<div className="space-y-2">
|
|
274
|
+
<Skeleton className="h-4 w-[60px]" />
|
|
275
|
+
<Skeleton className="h-10 w-full" />
|
|
276
|
+
</div>
|
|
277
|
+
<div className="space-y-2">
|
|
278
|
+
<Skeleton className="h-4 w-[80px]" />
|
|
279
|
+
<Skeleton className="h-10 w-full" />
|
|
280
|
+
</div>
|
|
281
|
+
<Skeleton className="h-10 w-full" />
|
|
282
|
+
</CardContent>
|
|
283
|
+
</Card>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function ProductListSkeleton() {
|
|
288
|
+
return (
|
|
289
|
+
<div className="space-y-4">
|
|
290
|
+
{[1, 2, 3].map((i) => (
|
|
291
|
+
<Card key={i}>
|
|
292
|
+
<CardContent className="p-4">
|
|
293
|
+
<div className="space-y-2">
|
|
294
|
+
<Skeleton className="h-5 w-[150px]" />
|
|
295
|
+
<Skeleton className="h-4 w-full" />
|
|
296
|
+
<div className="flex justify-between items-center mt-4">
|
|
297
|
+
<Skeleton className="h-6 w-[80px]" />
|
|
298
|
+
<Skeleton className="h-9 w-[100px]" />
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</CardContent>
|
|
302
|
+
</Card>
|
|
303
|
+
))}
|
|
304
|
+
</div>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function TableSkeleton() {
|
|
309
|
+
return (
|
|
310
|
+
<div className="space-y-3">
|
|
311
|
+
<div className="flex gap-3">
|
|
312
|
+
<Skeleton className="h-10 flex-1" />
|
|
313
|
+
<Skeleton className="h-10 flex-1" />
|
|
314
|
+
<Skeleton className="h-10 flex-1" />
|
|
315
|
+
<Skeleton className="h-10 flex-1" />
|
|
316
|
+
</div>
|
|
317
|
+
{[1, 2, 3, 4, 5].map((i) => (
|
|
318
|
+
<div key={i} className="flex gap-3">
|
|
319
|
+
<Skeleton className="h-12 flex-1" />
|
|
320
|
+
<Skeleton className="h-12 flex-1" />
|
|
321
|
+
<Skeleton className="h-12 flex-1" />
|
|
322
|
+
<Skeleton className="h-12 flex-1" />
|
|
323
|
+
</div>
|
|
324
|
+
))}
|
|
325
|
+
</div>
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 6. Responsive Design Utilities
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// components/ui/responsive-container.tsx
|
|
334
|
+
import { cn } from "@/lib/utils"
|
|
335
|
+
import { ReactNode } from "react"
|
|
336
|
+
|
|
337
|
+
interface ResponsiveContainerProps {
|
|
338
|
+
children: ReactNode
|
|
339
|
+
className?: string
|
|
340
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function ResponsiveContainer({
|
|
344
|
+
children,
|
|
345
|
+
className,
|
|
346
|
+
size = 'lg'
|
|
347
|
+
}: ResponsiveContainerProps) {
|
|
348
|
+
const sizeClasses = {
|
|
349
|
+
sm: 'max-w-2xl',
|
|
350
|
+
md: 'max-w-4xl',
|
|
351
|
+
lg: 'max-w-6xl',
|
|
352
|
+
xl: 'max-w-7xl',
|
|
353
|
+
full: 'w-full'
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div className={cn(
|
|
358
|
+
"mx-auto px-4 sm:px-6 lg:px-8",
|
|
359
|
+
sizeClasses[size],
|
|
360
|
+
className
|
|
361
|
+
)}>
|
|
362
|
+
{children}
|
|
363
|
+
</div>
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Responsive Grid
|
|
368
|
+
export function ResponsiveGrid({
|
|
369
|
+
children,
|
|
370
|
+
cols = { default: 1, sm: 2, md: 3, lg: 4 },
|
|
371
|
+
gap = 4,
|
|
372
|
+
className
|
|
373
|
+
}: {
|
|
374
|
+
children: ReactNode
|
|
375
|
+
cols?: { default: number; sm?: number; md?: number; lg?: number; xl?: number }
|
|
376
|
+
gap?: number
|
|
377
|
+
className?: string
|
|
378
|
+
}) {
|
|
379
|
+
const gridCols = cn(
|
|
380
|
+
`grid gap-${gap}`,
|
|
381
|
+
`grid-cols-${cols.default}`,
|
|
382
|
+
cols.sm && `sm:grid-cols-${cols.sm}`,
|
|
383
|
+
cols.md && `md:grid-cols-${cols.md}`,
|
|
384
|
+
cols.lg && `lg:grid-cols-${cols.lg}`,
|
|
385
|
+
cols.xl && `xl:grid-cols-${cols.xl}`,
|
|
386
|
+
className
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<div className={gridCols}>
|
|
391
|
+
{children}
|
|
392
|
+
</div>
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 7. Animations and Transitions
|
|
398
|
+
|
|
399
|
+
Add to your global CSS:
|
|
400
|
+
|
|
401
|
+
```css
|
|
402
|
+
/* app/globals.css */
|
|
403
|
+
@layer utilities {
|
|
404
|
+
/* Smooth transitions */
|
|
405
|
+
.transition-smooth {
|
|
406
|
+
@apply transition-all duration-300 ease-in-out;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Fade animations */
|
|
410
|
+
@keyframes fade-in {
|
|
411
|
+
from {
|
|
412
|
+
opacity: 0;
|
|
413
|
+
transform: translateY(10px);
|
|
414
|
+
}
|
|
415
|
+
to {
|
|
416
|
+
opacity: 1;
|
|
417
|
+
transform: translateY(0);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.animate-fade-in {
|
|
422
|
+
animation: fade-in 0.5s ease-out;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Slide animations */
|
|
426
|
+
@keyframes slide-in-right {
|
|
427
|
+
from {
|
|
428
|
+
transform: translateX(100%);
|
|
429
|
+
}
|
|
430
|
+
to {
|
|
431
|
+
transform: translateX(0);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.animate-slide-in-right {
|
|
436
|
+
animation: slide-in-right 0.3s ease-out;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Pulse animation for loading */
|
|
440
|
+
@keyframes pulse-scale {
|
|
441
|
+
0%, 100% {
|
|
442
|
+
transform: scale(1);
|
|
443
|
+
}
|
|
444
|
+
50% {
|
|
445
|
+
transform: scale(1.05);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.animate-pulse-scale {
|
|
450
|
+
animation: pulse-scale 2s ease-in-out infinite;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* Minimalist spacing */
|
|
455
|
+
.section-spacing {
|
|
456
|
+
@apply py-12 md:py-16 lg:py-20;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.content-spacing {
|
|
460
|
+
@apply space-y-6 md:space-y-8 lg:space-y-10;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Clean card styles */
|
|
464
|
+
.card-hover {
|
|
465
|
+
@apply transition-all duration-200 hover:shadow-lg hover:-translate-y-1;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/* Focus styles */
|
|
469
|
+
.focus-ring {
|
|
470
|
+
@apply focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-background;
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 8. Minimalist Dashboard Example
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// app/dashboard/page.tsx
|
|
478
|
+
'use client'
|
|
479
|
+
|
|
480
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
481
|
+
import { ResponsiveContainer, ResponsiveGrid } from "@/components/ui/responsive-container"
|
|
482
|
+
import { Button } from "@/components/ui/button"
|
|
483
|
+
import { ArrowRight, CreditCard, Shield, Zap } from "lucide-react"
|
|
484
|
+
|
|
485
|
+
export default function Dashboard() {
|
|
486
|
+
return (
|
|
487
|
+
<ResponsiveContainer size="xl" className="section-spacing">
|
|
488
|
+
<div className="content-spacing">
|
|
489
|
+
{/* Hero Section */}
|
|
490
|
+
<div className="text-center space-y-4 py-12">
|
|
491
|
+
<h1 className="text-4xl md:text-5xl font-bold tracking-tight animate-fade-in">
|
|
492
|
+
Welcome to SPAPS
|
|
493
|
+
</h1>
|
|
494
|
+
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
|
|
495
|
+
Complete authentication and payment solution with a minimalist design
|
|
496
|
+
</p>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
{/* Feature Cards */}
|
|
500
|
+
<ResponsiveGrid cols={{ default: 1, md: 3 }} gap={6}>
|
|
501
|
+
<Card className="card-hover">
|
|
502
|
+
<CardHeader>
|
|
503
|
+
<Shield className="h-8 w-8 mb-2 text-primary" />
|
|
504
|
+
<CardTitle>Secure Authentication</CardTitle>
|
|
505
|
+
<CardDescription>
|
|
506
|
+
Multi-method auth with email, wallet, and magic links
|
|
507
|
+
</CardDescription>
|
|
508
|
+
</CardHeader>
|
|
509
|
+
<CardContent>
|
|
510
|
+
<Button variant="ghost" className="group">
|
|
511
|
+
Learn more
|
|
512
|
+
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
513
|
+
</Button>
|
|
514
|
+
</CardContent>
|
|
515
|
+
</Card>
|
|
516
|
+
|
|
517
|
+
<Card className="card-hover">
|
|
518
|
+
<CardHeader>
|
|
519
|
+
<CreditCard className="h-8 w-8 mb-2 text-primary" />
|
|
520
|
+
<CardTitle>Stripe Payments</CardTitle>
|
|
521
|
+
<CardDescription>
|
|
522
|
+
Seamless payment processing and subscription management
|
|
523
|
+
</CardDescription>
|
|
524
|
+
</CardHeader>
|
|
525
|
+
<CardContent>
|
|
526
|
+
<Button variant="ghost" className="group">
|
|
527
|
+
View pricing
|
|
528
|
+
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
529
|
+
</Button>
|
|
530
|
+
</CardContent>
|
|
531
|
+
</Card>
|
|
532
|
+
|
|
533
|
+
<Card className="card-hover">
|
|
534
|
+
<CardHeader>
|
|
535
|
+
<Zap className="h-8 w-8 mb-2 text-primary" />
|
|
536
|
+
<CardTitle>Lightning Fast</CardTitle>
|
|
537
|
+
<CardDescription>
|
|
538
|
+
Built with Next.js 15 and optimized for performance
|
|
539
|
+
</CardDescription>
|
|
540
|
+
</CardHeader>
|
|
541
|
+
<CardContent>
|
|
542
|
+
<Button variant="ghost" className="group">
|
|
543
|
+
Get started
|
|
544
|
+
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
|
|
545
|
+
</Button>
|
|
546
|
+
</CardContent>
|
|
547
|
+
</Card>
|
|
548
|
+
</ResponsiveGrid>
|
|
549
|
+
|
|
550
|
+
{/* Stats Section */}
|
|
551
|
+
<Card>
|
|
552
|
+
<CardContent className="py-8">
|
|
553
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-8">
|
|
554
|
+
{[
|
|
555
|
+
{ label: 'Active Users', value: '2,451' },
|
|
556
|
+
{ label: 'API Calls', value: '48.2k' },
|
|
557
|
+
{ label: 'Uptime', value: '99.9%' },
|
|
558
|
+
{ label: 'Response Time', value: '45ms' }
|
|
559
|
+
].map((stat) => (
|
|
560
|
+
<div key={stat.label} className="text-center">
|
|
561
|
+
<p className="text-3xl font-bold">{stat.value}</p>
|
|
562
|
+
<p className="text-sm text-muted-foreground">{stat.label}</p>
|
|
563
|
+
</div>
|
|
564
|
+
))}
|
|
565
|
+
</div>
|
|
566
|
+
</CardContent>
|
|
567
|
+
</Card>
|
|
568
|
+
</div>
|
|
569
|
+
</ResponsiveContainer>
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Validation Checklist
|
|
575
|
+
|
|
576
|
+
✅ **Theme Support**:
|
|
577
|
+
- next-themes installed and configured
|
|
578
|
+
- Theme provider wraps app
|
|
579
|
+
- Dark mode toggle works
|
|
580
|
+
- System preference respected
|
|
581
|
+
|
|
582
|
+
✅ **Minimalist Design**:
|
|
583
|
+
- Plenty of whitespace
|
|
584
|
+
- Clean typography
|
|
585
|
+
- Subtle animations
|
|
586
|
+
- Consistent spacing
|
|
587
|
+
|
|
588
|
+
✅ **Loading States**:
|
|
589
|
+
- Skeleton loaders for all async content
|
|
590
|
+
- Loading buttons with spinners
|
|
591
|
+
- Smooth transitions
|
|
592
|
+
|
|
593
|
+
✅ **Responsive Design**:
|
|
594
|
+
- Mobile-first approach
|
|
595
|
+
- Works on all screen sizes
|
|
596
|
+
- Touch-friendly buttons
|
|
597
|
+
- Readable on small screens
|
|
598
|
+
|
|
599
|
+
## Common Mistakes to Avoid
|
|
600
|
+
|
|
601
|
+
❌ **Forgetting suppressHydrationWarning**:
|
|
602
|
+
```html
|
|
603
|
+
<!-- WRONG - Causes hydration mismatch -->
|
|
604
|
+
<html lang="en">
|
|
605
|
+
|
|
606
|
+
<!-- CORRECT -->
|
|
607
|
+
<html lang="en" suppressHydrationWarning>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
❌ **Too many animations**:
|
|
611
|
+
```css
|
|
612
|
+
/* WRONG - Overwhelming */
|
|
613
|
+
.everything { animation: bounce 1s infinite; }
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
❌ **Inconsistent spacing**:
|
|
617
|
+
```tsx
|
|
618
|
+
// WRONG - Random spacing
|
|
619
|
+
<div className="p-3 m-7 pt-9">
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Next Step
|
|
623
|
+
|
|
624
|
+
When ALL todos are ✅ complete:
|
|
625
|
+
|
|
626
|
+
```javascript
|
|
627
|
+
mcp__product-manager__get_agent_instructions({
|
|
628
|
+
category: "spaps-integration",
|
|
629
|
+
project: "spaps-demo",
|
|
630
|
+
step: 12
|
|
631
|
+
})
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Summary
|
|
635
|
+
|
|
636
|
+
UI polish provides:
|
|
637
|
+
- **Professional appearance** - Clean, modern design
|
|
638
|
+
- **Better UX** - Smooth transitions and loading states
|
|
639
|
+
- **Accessibility** - Dark mode and responsive design
|
|
640
|
+
- **Brand consistency** - Unified design language
|