@digilogiclabs/create-saas-app 2.10.0 → 2.11.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 (60) hide show
  1. package/README.md +153 -113
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cli/commands/create.d.ts.map +1 -1
  4. package/dist/cli/commands/create.js +2 -6
  5. package/dist/cli/commands/create.js.map +1 -1
  6. package/dist/templates/web/ai-platform/template/src/app/api/auth/route.ts +57 -0
  7. package/dist/templates/web/ai-platform/template/src/app/login/page.tsx +112 -0
  8. package/dist/templates/web/ai-platform/template/src/app/models/page.tsx +186 -0
  9. package/dist/templates/web/ai-platform/template/src/app/playground/page.tsx +251 -0
  10. package/dist/templates/web/ai-platform/template/src/app/settings/page.tsx +190 -0
  11. package/dist/templates/web/ai-platform/template/src/app/signup/page.tsx +133 -0
  12. package/dist/templates/web/ai-platform/template/src/lib/auth-session.ts +52 -0
  13. package/dist/templates/web/iot-dashboard/template/src/app/alerts/page.tsx +157 -0
  14. package/dist/templates/web/iot-dashboard/template/src/app/api/auth/route.ts +57 -0
  15. package/dist/templates/web/iot-dashboard/template/src/app/devices/[id]/page.tsx +204 -0
  16. package/dist/templates/web/iot-dashboard/template/src/app/devices/new/page.tsx +139 -0
  17. package/dist/templates/web/iot-dashboard/template/src/app/devices/page.tsx +171 -0
  18. package/dist/templates/web/iot-dashboard/template/src/app/login/page.tsx +112 -0
  19. package/dist/templates/web/iot-dashboard/template/src/app/settings/page.tsx +186 -0
  20. package/dist/templates/web/iot-dashboard/template/src/app/signup/page.tsx +133 -0
  21. package/dist/templates/web/iot-dashboard/template/src/lib/auth-session.ts +52 -0
  22. package/dist/templates/web/marketplace/template/src/app/api/auth/route.ts +57 -0
  23. package/dist/templates/web/marketplace/template/src/app/login/page.tsx +112 -0
  24. package/dist/templates/web/marketplace/template/src/app/orders/page.tsx +160 -0
  25. package/dist/templates/web/marketplace/template/src/app/products/[id]/page.tsx +218 -0
  26. package/dist/templates/web/marketplace/template/src/app/settings/page.tsx +150 -0
  27. package/dist/templates/web/marketplace/template/src/app/signup/page.tsx +133 -0
  28. package/dist/templates/web/marketplace/template/src/lib/auth-session.ts +52 -0
  29. package/dist/templates/web/micro-saas/template/src/app/api/auth/route.ts +57 -0
  30. package/dist/templates/web/micro-saas/template/src/app/login/page.tsx +14 -3
  31. package/dist/templates/web/micro-saas/template/src/app/signup/page.tsx +15 -4
  32. package/dist/templates/web/micro-saas/template/src/lib/auth-session.ts +52 -0
  33. package/package.json +1 -1
  34. package/src/templates/web/ai-platform/template/src/app/api/auth/route.ts +57 -0
  35. package/src/templates/web/ai-platform/template/src/app/login/page.tsx +112 -0
  36. package/src/templates/web/ai-platform/template/src/app/models/page.tsx +186 -0
  37. package/src/templates/web/ai-platform/template/src/app/playground/page.tsx +251 -0
  38. package/src/templates/web/ai-platform/template/src/app/settings/page.tsx +190 -0
  39. package/src/templates/web/ai-platform/template/src/app/signup/page.tsx +133 -0
  40. package/src/templates/web/ai-platform/template/src/lib/auth-session.ts +52 -0
  41. package/src/templates/web/iot-dashboard/template/src/app/alerts/page.tsx +157 -0
  42. package/src/templates/web/iot-dashboard/template/src/app/api/auth/route.ts +57 -0
  43. package/src/templates/web/iot-dashboard/template/src/app/devices/[id]/page.tsx +204 -0
  44. package/src/templates/web/iot-dashboard/template/src/app/devices/new/page.tsx +139 -0
  45. package/src/templates/web/iot-dashboard/template/src/app/devices/page.tsx +171 -0
  46. package/src/templates/web/iot-dashboard/template/src/app/login/page.tsx +112 -0
  47. package/src/templates/web/iot-dashboard/template/src/app/settings/page.tsx +186 -0
  48. package/src/templates/web/iot-dashboard/template/src/app/signup/page.tsx +133 -0
  49. package/src/templates/web/iot-dashboard/template/src/lib/auth-session.ts +52 -0
  50. package/src/templates/web/marketplace/template/src/app/api/auth/route.ts +57 -0
  51. package/src/templates/web/marketplace/template/src/app/login/page.tsx +112 -0
  52. package/src/templates/web/marketplace/template/src/app/orders/page.tsx +160 -0
  53. package/src/templates/web/marketplace/template/src/app/products/[id]/page.tsx +218 -0
  54. package/src/templates/web/marketplace/template/src/app/settings/page.tsx +150 -0
  55. package/src/templates/web/marketplace/template/src/app/signup/page.tsx +133 -0
  56. package/src/templates/web/marketplace/template/src/lib/auth-session.ts +52 -0
  57. package/src/templates/web/micro-saas/template/src/app/api/auth/route.ts +57 -0
  58. package/src/templates/web/micro-saas/template/src/app/login/page.tsx +14 -3
  59. package/src/templates/web/micro-saas/template/src/app/signup/page.tsx +15 -4
  60. package/src/templates/web/micro-saas/template/src/lib/auth-session.ts +52 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Auth session adapter — demo implementation.
3
+ *
4
+ * Replace this file with a real provider when configuring your app:
5
+ * - Keycloak: import { auth } from '@/auth' (Auth.js)
6
+ * - Supabase: import { createClient } from '@/lib/supabase/server'
7
+ * - Firebase: import { getServerSession } from 'next-auth'
8
+ *
9
+ * The getSession() and getUser() exports are consumed by auth-server.ts
10
+ * and must return the same shape regardless of provider.
11
+ */
12
+ import 'server-only'
13
+ import { cookies } from 'next/headers'
14
+
15
+ type SessionUser = {
16
+ id?: string
17
+ email?: string | null
18
+ name?: string | null
19
+ roles?: string[]
20
+ }
21
+
22
+ type Session = {
23
+ user?: SessionUser
24
+ }
25
+
26
+ /**
27
+ * Get the current session from cookies.
28
+ * Demo: reads a JSON cookie set at login. Replace with your auth provider.
29
+ */
30
+ export async function getSession(): Promise<Session | null> {
31
+ const cookieStore = await cookies()
32
+ const sessionCookie = cookieStore.get('session')
33
+
34
+ if (!sessionCookie?.value) {
35
+ return null
36
+ }
37
+
38
+ try {
39
+ const session = JSON.parse(sessionCookie.value) as Session
40
+ return session
41
+ } catch {
42
+ return null
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Get the current authenticated user, or null.
48
+ */
49
+ export async function getUser(): Promise<SessionUser | null> {
50
+ const session = await getSession()
51
+ return session?.user ?? null
52
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Demo auth API routes.
3
+ *
4
+ * POST /api/auth — Login or Signup
5
+ * DELETE /api/auth — Logout
6
+ *
7
+ * Replace with your real auth provider (Auth.js, Supabase Auth, etc.)
8
+ * when configuring your app for production.
9
+ */
10
+ import { NextRequest, NextResponse } from 'next/server'
11
+ import { cookies } from 'next/headers'
12
+
13
+ export async function POST(request: NextRequest) {
14
+ try {
15
+ const body = await request.json()
16
+ const { email, password, action } = body
17
+
18
+ if (!email || !password) {
19
+ return NextResponse.json(
20
+ { error: 'Email and password are required' },
21
+ { status: 400 }
22
+ )
23
+ }
24
+
25
+ // Demo: accept any email/password combination.
26
+ // Replace with real authentication (database lookup, bcrypt compare, etc.)
27
+ const user = {
28
+ id: crypto.randomUUID(),
29
+ email,
30
+ name: email.split('@')[0],
31
+ }
32
+
33
+ const session = JSON.stringify({ user })
34
+ const cookieStore = await cookies()
35
+
36
+ cookieStore.set('session', session, {
37
+ httpOnly: true,
38
+ secure: process.env.NODE_ENV === 'production',
39
+ sameSite: 'lax',
40
+ path: '/',
41
+ maxAge: 60 * 60 * 24 * 7, // 7 days
42
+ })
43
+
44
+ return NextResponse.json({ user })
45
+ } catch {
46
+ return NextResponse.json(
47
+ { error: 'Authentication failed' },
48
+ { status: 500 }
49
+ )
50
+ }
51
+ }
52
+
53
+ export async function DELETE() {
54
+ const cookieStore = await cookies()
55
+ cookieStore.delete('session')
56
+ return NextResponse.json({ success: true })
57
+ }
@@ -1,7 +1,6 @@
1
1
  'use client'
2
2
 
3
3
  import { useState } from 'react'
4
- import { useAuth } from '@digilogiclabs/app-sdk'
5
4
  import { Button, Card, CardContent } from '@digilogiclabs/saas-factory-ui'
6
5
  import { Zap } from 'lucide-react'
7
6
  import { useRouter } from 'next/navigation'
@@ -11,17 +10,29 @@ export default function LoginPage() {
11
10
  const [email, setEmail] = useState('')
12
11
  const [password, setPassword] = useState('')
13
12
  const [error, setError] = useState('')
14
- const { signIn, loading } = useAuth()
13
+ const [loading, setLoading] = useState(false)
15
14
  const router = useRouter()
16
15
 
17
16
  const handleSubmit = async (e: React.FormEvent) => {
18
17
  e.preventDefault()
19
18
  setError('')
19
+ setLoading(true)
20
20
  try {
21
- await signIn(email, password)
21
+ const res = await fetch('/api/auth', {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify({ email, password, action: 'login' }),
25
+ })
26
+ if (!res.ok) {
27
+ const data = await res.json()
28
+ throw new Error(data.error || 'Failed to sign in')
29
+ }
22
30
  router.push('/dashboard')
31
+ router.refresh()
23
32
  } catch (err) {
24
33
  setError(err instanceof Error ? err.message : 'Failed to sign in')
34
+ } finally {
35
+ setLoading(false)
25
36
  }
26
37
  }
27
38
 
@@ -1,7 +1,6 @@
1
1
  'use client'
2
2
 
3
3
  import { useState } from 'react'
4
- import { useAuth } from '@digilogiclabs/app-sdk'
5
4
  import { Button, Card, CardContent } from '@digilogiclabs/saas-factory-ui'
6
5
  import { Zap } from 'lucide-react'
7
6
  import { useRouter } from 'next/navigation'
@@ -12,7 +11,7 @@ export default function SignupPage() {
12
11
  const [password, setPassword] = useState('')
13
12
  const [confirmPassword, setConfirmPassword] = useState('')
14
13
  const [error, setError] = useState('')
15
- const { signUp, loading } = useAuth()
14
+ const [loading, setLoading] = useState(false)
16
15
  const router = useRouter()
17
16
 
18
17
  const handleSubmit = async (e: React.FormEvent) => {
@@ -22,11 +21,23 @@ export default function SignupPage() {
22
21
  setError('Passwords do not match')
23
22
  return
24
23
  }
24
+ setLoading(true)
25
25
  try {
26
- await signUp(email, password)
26
+ const res = await fetch('/api/auth', {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body: JSON.stringify({ email, password, action: 'signup' }),
30
+ })
31
+ if (!res.ok) {
32
+ const data = await res.json()
33
+ throw new Error(data.error || 'Failed to create account')
34
+ }
27
35
  router.push('/dashboard')
36
+ router.refresh()
28
37
  } catch (err) {
29
- setError(err instanceof Error ? err.message : 'Failed to sign up')
38
+ setError(err instanceof Error ? err.message : 'Failed to create account')
39
+ } finally {
40
+ setLoading(false)
30
41
  }
31
42
  }
32
43
 
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Auth session adapter — demo implementation.
3
+ *
4
+ * Replace this file with a real provider when configuring your app:
5
+ * - Keycloak: import { auth } from '@/auth' (Auth.js)
6
+ * - Supabase: import { createClient } from '@/lib/supabase/server'
7
+ * - Firebase: import { getServerSession } from 'next-auth'
8
+ *
9
+ * The getSession() and getUser() exports are consumed by auth-server.ts
10
+ * and must return the same shape regardless of provider.
11
+ */
12
+ import 'server-only'
13
+ import { cookies } from 'next/headers'
14
+
15
+ type SessionUser = {
16
+ id?: string
17
+ email?: string | null
18
+ name?: string | null
19
+ roles?: string[]
20
+ }
21
+
22
+ type Session = {
23
+ user?: SessionUser
24
+ }
25
+
26
+ /**
27
+ * Get the current session from cookies.
28
+ * Demo: reads a JSON cookie set at login. Replace with your auth provider.
29
+ */
30
+ export async function getSession(): Promise<Session | null> {
31
+ const cookieStore = await cookies()
32
+ const sessionCookie = cookieStore.get('session')
33
+
34
+ if (!sessionCookie?.value) {
35
+ return null
36
+ }
37
+
38
+ try {
39
+ const session = JSON.parse(sessionCookie.value) as Session
40
+ return session
41
+ } catch {
42
+ return null
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Get the current authenticated user, or null.
48
+ */
49
+ export async function getUser(): Promise<SessionUser | null> {
50
+ const session = await getSession()
51
+ return session?.user ?? null
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digilogiclabs/create-saas-app",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "description": "Create modern SaaS applications with DLL Platform - tier-aware scaffolding with platform-core and app-sdk",
5
5
  "main": "dist/cli/index.js",
6
6
  "bin": {
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Demo auth API routes.
3
+ *
4
+ * POST /api/auth — Login or Signup
5
+ * DELETE /api/auth — Logout
6
+ *
7
+ * Replace with your real auth provider (Auth.js, Supabase Auth, etc.)
8
+ * when configuring your app for production.
9
+ */
10
+ import { NextRequest, NextResponse } from 'next/server'
11
+ import { cookies } from 'next/headers'
12
+
13
+ export async function POST(request: NextRequest) {
14
+ try {
15
+ const body = await request.json()
16
+ const { email, password, action } = body
17
+
18
+ if (!email || !password) {
19
+ return NextResponse.json(
20
+ { error: 'Email and password are required' },
21
+ { status: 400 }
22
+ )
23
+ }
24
+
25
+ // Demo: accept any email/password combination.
26
+ // Replace with real authentication (database lookup, bcrypt compare, etc.)
27
+ const user = {
28
+ id: crypto.randomUUID(),
29
+ email,
30
+ name: email.split('@')[0],
31
+ }
32
+
33
+ const session = JSON.stringify({ user })
34
+ const cookieStore = await cookies()
35
+
36
+ cookieStore.set('session', session, {
37
+ httpOnly: true,
38
+ secure: process.env.NODE_ENV === 'production',
39
+ sameSite: 'lax',
40
+ path: '/',
41
+ maxAge: 60 * 60 * 24 * 7, // 7 days
42
+ })
43
+
44
+ return NextResponse.json({ user })
45
+ } catch {
46
+ return NextResponse.json(
47
+ { error: 'Authentication failed' },
48
+ { status: 500 }
49
+ )
50
+ }
51
+ }
52
+
53
+ export async function DELETE() {
54
+ const cookieStore = await cookies()
55
+ cookieStore.delete('session')
56
+ return NextResponse.json({ success: true })
57
+ }
@@ -0,0 +1,112 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { Button, Card, CardContent } from '@digilogiclabs/saas-factory-ui'
5
+ import { Bot } from 'lucide-react'
6
+ import { useRouter } from 'next/navigation'
7
+ import Link from 'next/link'
8
+
9
+ export default function LoginPage() {
10
+ const [email, setEmail] = useState('')
11
+ const [password, setPassword] = useState('')
12
+ const [error, setError] = useState('')
13
+ const [loading, setLoading] = useState(false)
14
+ const router = useRouter()
15
+
16
+ const handleSubmit = async (e: React.FormEvent) => {
17
+ e.preventDefault()
18
+ setError('')
19
+ setLoading(true)
20
+ try {
21
+ const res = await fetch('/api/auth', {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify({ email, password, action: 'login' }),
25
+ })
26
+ if (!res.ok) {
27
+ const data = await res.json()
28
+ throw new Error(data.error || 'Failed to sign in')
29
+ }
30
+ router.push('/dashboard')
31
+ router.refresh()
32
+ } catch (err) {
33
+ setError(err instanceof Error ? err.message : 'Failed to sign in')
34
+ } finally {
35
+ setLoading(false)
36
+ }
37
+ }
38
+
39
+ return (
40
+ <div className="min-h-screen flex items-center justify-center bg-background px-4">
41
+ <div className="w-full max-w-md">
42
+ {/* Logo */}
43
+ <Link href="/" className="flex items-center justify-center gap-2 mb-8 hover:opacity-80 transition-opacity">
44
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-brand-from via-brand-via to-brand-to flex items-center justify-center">
45
+ <Bot className="w-5 h-5 text-white" />
46
+ </div>
47
+ <span className="font-bold text-xl">AI Platform</span>
48
+ </Link>
49
+
50
+ <Card className="border border-border">
51
+ <CardContent className="p-8">
52
+ <div className="text-center mb-8">
53
+ <h1 className="text-2xl font-bold">Welcome back</h1>
54
+ <p className="text-muted-foreground mt-2">Sign in to access AI models</p>
55
+ </div>
56
+
57
+ {error && (
58
+ <div className="bg-destructive/10 text-destructive p-3 rounded-lg mb-6 text-sm" role="alert">
59
+ {error}
60
+ </div>
61
+ )}
62
+
63
+ <form onSubmit={handleSubmit} className="space-y-4">
64
+ <div>
65
+ <label htmlFor="email" className="block text-sm font-medium mb-1.5">
66
+ Email
67
+ </label>
68
+ <input
69
+ id="email"
70
+ type="email"
71
+ value={email}
72
+ onChange={(e) => setEmail(e.target.value)}
73
+ className="w-full px-3 py-2.5 rounded-lg border border-input bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-shadow"
74
+ placeholder="you@example.com"
75
+ required
76
+ />
77
+ </div>
78
+
79
+ <div>
80
+ <label htmlFor="password" className="block text-sm font-medium mb-1.5">
81
+ Password
82
+ </label>
83
+ <input
84
+ id="password"
85
+ type="password"
86
+ value={password}
87
+ onChange={(e) => setPassword(e.target.value)}
88
+ className="w-full px-3 py-2.5 rounded-lg border border-input bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-shadow"
89
+ placeholder="Enter your password"
90
+ required
91
+ />
92
+ </div>
93
+
94
+ <Button type="submit" className="w-full h-11 text-base cursor-pointer hover:brightness-110 active:scale-[0.98] transition-all duration-200" disabled={loading}>
95
+ {loading ? 'Signing in...' : 'Sign In'}
96
+ </Button>
97
+ </form>
98
+
99
+ <div className="mt-6 text-center">
100
+ <p className="text-muted-foreground text-sm">
101
+ Don&apos;t have an account?{' '}
102
+ <Link href="/signup" className="text-primary hover:underline font-medium">
103
+ Create an account
104
+ </Link>
105
+ </p>
106
+ </div>
107
+ </CardContent>
108
+ </Card>
109
+ </div>
110
+ </div>
111
+ )
112
+ }
@@ -0,0 +1,186 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { Button, Card } from '@digilogiclabs/saas-factory-ui'
5
+ import { Bot, MessageSquare, Code, Eye, Sparkles, Zap } from 'lucide-react'
6
+ import Link from 'next/link'
7
+
8
+ interface Model {
9
+ id: string
10
+ name: string
11
+ provider: 'OpenAI' | 'Anthropic' | 'Google'
12
+ description: string
13
+ capabilities: { label: string; icon: React.ComponentType<{ className?: string }> }[]
14
+ pricing: string
15
+ badge: string
16
+ }
17
+
18
+ const MODELS: Model[] = [
19
+ {
20
+ id: 'gpt-4o',
21
+ name: 'GPT-4o',
22
+ provider: 'OpenAI',
23
+ description: 'Most capable OpenAI model with vision and advanced reasoning.',
24
+ capabilities: [
25
+ { label: 'Chat', icon: MessageSquare },
26
+ { label: 'Code', icon: Code },
27
+ { label: 'Vision', icon: Eye },
28
+ ],
29
+ pricing: '~2.5M tokens/$1',
30
+ badge: 'bg-green-500/20 text-green-400',
31
+ },
32
+ {
33
+ id: 'gpt-4o-mini',
34
+ name: 'GPT-4o Mini',
35
+ provider: 'OpenAI',
36
+ description: 'Fast and affordable for everyday tasks and simple queries.',
37
+ capabilities: [
38
+ { label: 'Chat', icon: MessageSquare },
39
+ { label: 'Code', icon: Code },
40
+ ],
41
+ pricing: '~16.6M tokens/$1',
42
+ badge: 'bg-green-500/20 text-green-400',
43
+ },
44
+ {
45
+ id: 'claude-3-5-sonnet',
46
+ name: 'Claude 3.5 Sonnet',
47
+ provider: 'Anthropic',
48
+ description: 'Excellent at analysis, writing, and complex reasoning tasks.',
49
+ capabilities: [
50
+ { label: 'Chat', icon: MessageSquare },
51
+ { label: 'Code', icon: Code },
52
+ { label: 'Vision', icon: Eye },
53
+ { label: 'Analysis', icon: Sparkles },
54
+ ],
55
+ pricing: '~3.3M tokens/$1',
56
+ badge: 'bg-orange-500/20 text-orange-400',
57
+ },
58
+ {
59
+ id: 'claude-3-haiku',
60
+ name: 'Claude 3 Haiku',
61
+ provider: 'Anthropic',
62
+ description: 'Fastest Anthropic model, great for quick responses and automation.',
63
+ capabilities: [
64
+ { label: 'Chat', icon: MessageSquare },
65
+ { label: 'Code', icon: Code },
66
+ ],
67
+ pricing: '~20M tokens/$1',
68
+ badge: 'bg-orange-500/20 text-orange-400',
69
+ },
70
+ {
71
+ id: 'gemini-1-5-pro',
72
+ name: 'Gemini 1.5 Pro',
73
+ provider: 'Google',
74
+ description: 'Long context window with multimodal support from Google.',
75
+ capabilities: [
76
+ { label: 'Chat', icon: MessageSquare },
77
+ { label: 'Code', icon: Code },
78
+ { label: 'Vision', icon: Eye },
79
+ { label: 'Analysis', icon: Sparkles },
80
+ ],
81
+ pricing: '~4M tokens/$1',
82
+ badge: 'bg-blue-500/20 text-blue-400',
83
+ },
84
+ {
85
+ id: 'gemini-flash',
86
+ name: 'Gemini Flash',
87
+ provider: 'Google',
88
+ description: 'Ultra-fast responses with efficient token usage.',
89
+ capabilities: [
90
+ { label: 'Chat', icon: MessageSquare },
91
+ { label: 'Code', icon: Code },
92
+ ],
93
+ pricing: '~26.6M tokens/$1',
94
+ badge: 'bg-blue-500/20 text-blue-400',
95
+ },
96
+ ]
97
+
98
+ const PROVIDERS = ['All', 'OpenAI', 'Anthropic', 'Google'] as const
99
+
100
+ export default function ModelsPage() {
101
+ const [filter, setFilter] = useState<string>('All')
102
+
103
+ const filteredModels = filter === 'All'
104
+ ? MODELS
105
+ : MODELS.filter(m => m.provider === filter)
106
+
107
+ return (
108
+ <div className="min-h-screen bg-background">
109
+ <div className="max-w-7xl mx-auto px-4 py-8">
110
+ {/* Header */}
111
+ <div className="mb-8">
112
+ <h1 className="text-3xl font-bold text-foreground">AI Models</h1>
113
+ <p className="text-muted-foreground mt-1">
114
+ Browse available models and find the right one for your use case.
115
+ </p>
116
+ </div>
117
+
118
+ {/* Filter */}
119
+ <div className="flex flex-wrap gap-2 mb-8">
120
+ {PROVIDERS.map((provider) => (
121
+ <Button
122
+ key={provider}
123
+ variant={filter === provider ? 'default' : 'outline'}
124
+ size="sm"
125
+ onClick={() => setFilter(provider)}
126
+ >
127
+ {provider}
128
+ </Button>
129
+ ))}
130
+ </div>
131
+
132
+ {/* Models Grid */}
133
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
134
+ {filteredModels.map((model) => (
135
+ <Card key={model.id} className="p-6 bg-secondary border-border flex flex-col">
136
+ <div className="flex items-start justify-between mb-3">
137
+ <div className="flex items-center gap-3">
138
+ <div className="w-10 h-10 bg-primary/20 rounded-lg flex items-center justify-center">
139
+ <Bot className="w-5 h-5 text-primary" />
140
+ </div>
141
+ <div>
142
+ <h3 className="text-lg font-semibold text-foreground">{model.name}</h3>
143
+ <span className={`text-xs font-medium px-2 py-0.5 rounded-full ${model.badge}`}>
144
+ {model.provider}
145
+ </span>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <p className="text-sm text-muted-foreground mb-4 flex-1">
151
+ {model.description}
152
+ </p>
153
+
154
+ {/* Capabilities */}
155
+ <div className="flex flex-wrap gap-2 mb-4">
156
+ {model.capabilities.map((cap) => (
157
+ <span
158
+ key={cap.label}
159
+ className="inline-flex items-center gap-1 text-xs px-2 py-1 bg-muted rounded-md text-muted-foreground"
160
+ >
161
+ <cap.icon className="w-3 h-3" />
162
+ {cap.label}
163
+ </span>
164
+ ))}
165
+ </div>
166
+
167
+ {/* Pricing */}
168
+ <div className="flex items-center gap-1 text-sm text-muted-foreground mb-4">
169
+ <Zap className="w-4 h-4 text-primary" />
170
+ <span>{model.pricing}</span>
171
+ </div>
172
+
173
+ {/* Action */}
174
+ <Link href={`/chat?model=${model.id}`}>
175
+ <Button className="w-full">
176
+ <MessageSquare className="w-4 h-4 mr-2" />
177
+ Try in Chat
178
+ </Button>
179
+ </Link>
180
+ </Card>
181
+ ))}
182
+ </div>
183
+ </div>
184
+ </div>
185
+ )
186
+ }