|
| 1 | +import { |
| 2 | + Card, |
| 3 | + CardContent, |
| 4 | + CardDescription, |
| 5 | + CardHeader, |
| 6 | + CardTitle, |
| 7 | +} from "@/components/ui/card"; |
| 8 | +import { Input } from "@/components/ui/input"; |
| 9 | +import { Label } from "@/components/ui/label"; |
| 10 | +import { Badge } from "@/components/ui/badge"; |
| 11 | +import { Separator } from "@/components/ui/separator"; |
| 12 | + |
| 13 | +const PUBLISH_DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; |
| 14 | +const DEFAULT_PUBLISH_DAYS = ["Mon", "Wed", "Fri"]; |
| 15 | + |
| 16 | +const CONTENT_CATEGORIES = [ |
| 17 | + "JavaScript", |
| 18 | + "TypeScript", |
| 19 | + "React", |
| 20 | + "Next.js", |
| 21 | + "Angular", |
| 22 | + "Svelte", |
| 23 | + "Node.js", |
| 24 | + "CSS", |
| 25 | + "DevOps", |
| 26 | + "AI / ML", |
| 27 | + "Web Performance", |
| 28 | + "Tooling", |
| 29 | +]; |
| 30 | + |
| 31 | +const RATE_CARD_TIERS = [ |
| 32 | + { |
| 33 | + name: "Pre-roll Mention", |
| 34 | + description: "15-second sponsor mention at the start of the video", |
| 35 | + price: "$200", |
| 36 | + }, |
| 37 | + { |
| 38 | + name: "Mid-roll Segment", |
| 39 | + description: "60-second dedicated sponsor segment mid-video", |
| 40 | + price: "$500", |
| 41 | + }, |
| 42 | + { |
| 43 | + name: "Dedicated Video", |
| 44 | + description: "Full sponsored video with product deep-dive", |
| 45 | + price: "$1,500", |
| 46 | + }, |
| 47 | +]; |
| 48 | + |
| 49 | +const INTEGRATIONS = [ |
| 50 | + { |
| 51 | + name: "Sanity", |
| 52 | + envVar: "SANITY_API_TOKEN", |
| 53 | + description: "Content management and data store", |
| 54 | + }, |
| 55 | + { |
| 56 | + name: "YouTube", |
| 57 | + envVar: "YOUTUBE_API_KEY", |
| 58 | + description: "Video publishing and analytics", |
| 59 | + }, |
| 60 | + { |
| 61 | + name: "Supabase", |
| 62 | + envVar: "NEXT_PUBLIC_SUPABASE_URL", |
| 63 | + description: "Authentication and database", |
| 64 | + }, |
| 65 | + { |
| 66 | + name: "ElevenLabs", |
| 67 | + envVar: "ELEVENLABS_API_KEY", |
| 68 | + description: "AI voice generation for videos", |
| 69 | + }, |
| 70 | + { |
| 71 | + name: "OpenAI", |
| 72 | + envVar: "OPENAI_API_KEY", |
| 73 | + description: "Script generation and content AI", |
| 74 | + }, |
| 75 | + { |
| 76 | + name: "Stripe", |
| 77 | + envVar: "STRIPE_SECRET_KEY", |
| 78 | + description: "Sponsor payment processing", |
| 79 | + }, |
| 80 | + { |
| 81 | + name: "Resend", |
| 82 | + envVar: "RESEND_API_KEY", |
| 83 | + description: "Transactional email delivery", |
| 84 | + }, |
| 85 | +]; |
| 86 | + |
| 87 | +function IntegrationDot({ connected }: { connected: boolean }) { |
| 88 | + return ( |
| 89 | + <span |
| 90 | + className={`inline-block h-2.5 w-2.5 rounded-full ${ |
| 91 | + connected |
| 92 | + ? "bg-green-500" |
| 93 | + : "bg-gray-300 dark:bg-gray-600" |
| 94 | + }`} |
| 95 | + /> |
| 96 | + ); |
| 97 | +} |
| 98 | + |
1 | 99 | export default function SettingsPage() { |
2 | | - return ( |
3 | | - <div className="flex flex-col gap-6"> |
4 | | - <div> |
5 | | - <h1 className="text-3xl font-bold tracking-tight">Settings</h1> |
6 | | - <p className="text-muted-foreground"> |
7 | | - Configure your content engine — cadence, categories, and rate card. |
8 | | - </p> |
9 | | - </div> |
10 | | - |
11 | | - <div className="grid gap-4 md:grid-cols-2"> |
12 | | - <div className="rounded-lg border p-6"> |
13 | | - <h2 className="text-lg font-semibold">Publishing Cadence</h2> |
14 | | - <p className="mt-2 text-sm text-muted-foreground"> |
15 | | - Set the publishing schedule — how many videos per week, preferred |
16 | | - publish days, and time slots. Controls the automated pipeline |
17 | | - pacing. |
18 | | - </p> |
19 | | - <div className="mt-4 rounded-md bg-muted p-4 text-sm text-muted-foreground"> |
20 | | - 📅 Cadence settings coming in Phase 1b |
21 | | - </div> |
22 | | - </div> |
23 | | - |
24 | | - <div className="rounded-lg border p-6"> |
25 | | - <h2 className="text-lg font-semibold">Categories</h2> |
26 | | - <p className="mt-2 text-sm text-muted-foreground"> |
27 | | - Manage content categories and tags — used for content idea |
28 | | - classification and YouTube metadata. |
29 | | - </p> |
30 | | - <div className="mt-4 rounded-md bg-muted p-4 text-sm text-muted-foreground"> |
31 | | - 🏷️ Category management coming in Phase 1b |
32 | | - </div> |
33 | | - </div> |
34 | | - |
35 | | - <div className="rounded-lg border p-6"> |
36 | | - <h2 className="text-lg font-semibold">Sponsor Rate Card</h2> |
37 | | - <p className="mt-2 text-sm text-muted-foreground"> |
38 | | - Define sponsorship tiers, pricing, and deliverables. Used by the |
39 | | - sponsor portal and pipeline. |
40 | | - </p> |
41 | | - <div className="mt-4 rounded-md bg-muted p-4 text-sm text-muted-foreground"> |
42 | | - 💳 Rate card editor coming in Phase 1c |
43 | | - </div> |
44 | | - </div> |
45 | | - |
46 | | - <div className="rounded-lg border p-6"> |
47 | | - <h2 className="text-lg font-semibold">Integrations</h2> |
48 | | - <p className="mt-2 text-sm text-muted-foreground"> |
49 | | - API keys and connections — YouTube API, Sanity webhooks, and |
50 | | - notification channels. |
51 | | - </p> |
52 | | - <div className="mt-4 rounded-md bg-muted p-4 text-sm text-muted-foreground"> |
53 | | - 🔌 Integration settings coming in Phase 2 |
54 | | - </div> |
55 | | - </div> |
56 | | - </div> |
57 | | - </div> |
58 | | - ) |
| 100 | + // Check which env vars are likely set (server component has access) |
| 101 | + const integrationStatus = INTEGRATIONS.map((integration) => ({ |
| 102 | + ...integration, |
| 103 | + connected: !!process.env[integration.envVar], |
| 104 | + })); |
| 105 | + |
| 106 | + return ( |
| 107 | + <div className="flex flex-col gap-6"> |
| 108 | + <div> |
| 109 | + <h1 className="text-3xl font-bold tracking-tight">Settings</h1> |
| 110 | + <p className="text-muted-foreground"> |
| 111 | + Configure your content engine — cadence, categories, and rate card. |
| 112 | + </p> |
| 113 | + </div> |
| 114 | + |
| 115 | + <div className="rounded-lg border border-dashed bg-muted/50 p-4 text-sm text-muted-foreground"> |
| 116 | + <strong>Note:</strong> Settings are currently read-only. Editing will be |
| 117 | + enabled in a future phase once a settings schema is added to Sanity. |
| 118 | + </div> |
| 119 | + |
| 120 | + <div className="grid gap-6 md:grid-cols-2"> |
| 121 | + {/* Publishing Cadence */} |
| 122 | + <Card> |
| 123 | + <CardHeader> |
| 124 | + <CardTitle>Publishing Cadence</CardTitle> |
| 125 | + <CardDescription> |
| 126 | + Control how often videos are published and on which days. |
| 127 | + </CardDescription> |
| 128 | + </CardHeader> |
| 129 | + <CardContent className="space-y-4"> |
| 130 | + <div className="space-y-2"> |
| 131 | + <Label htmlFor="videos-per-week">Videos per week</Label> |
| 132 | + <Input |
| 133 | + id="videos-per-week" |
| 134 | + type="number" |
| 135 | + defaultValue={3} |
| 136 | + min={1} |
| 137 | + max={7} |
| 138 | + disabled |
| 139 | + className="w-24" |
| 140 | + /> |
| 141 | + </div> |
| 142 | + <Separator /> |
| 143 | + <div className="space-y-2"> |
| 144 | + <Label>Preferred publish days</Label> |
| 145 | + <div className="flex flex-wrap gap-2"> |
| 146 | + {PUBLISH_DAYS.map((day) => ( |
| 147 | + <Badge |
| 148 | + key={day} |
| 149 | + variant={ |
| 150 | + DEFAULT_PUBLISH_DAYS.includes(day) |
| 151 | + ? "default" |
| 152 | + : "outline" |
| 153 | + } |
| 154 | + className="cursor-default" |
| 155 | + > |
| 156 | + {day} |
| 157 | + </Badge> |
| 158 | + ))} |
| 159 | + </div> |
| 160 | + </div> |
| 161 | + <p className="text-xs text-muted-foreground"> |
| 162 | + Settings will be stored in Sanity once a settings schema is |
| 163 | + created. |
| 164 | + </p> |
| 165 | + </CardContent> |
| 166 | + </Card> |
| 167 | + |
| 168 | + {/* Content Categories */} |
| 169 | + <Card> |
| 170 | + <CardHeader> |
| 171 | + <CardTitle>Content Categories</CardTitle> |
| 172 | + <CardDescription> |
| 173 | + Categories used for content idea classification and YouTube |
| 174 | + metadata. |
| 175 | + </CardDescription> |
| 176 | + </CardHeader> |
| 177 | + <CardContent className="space-y-4"> |
| 178 | + <div className="flex flex-wrap gap-2"> |
| 179 | + {CONTENT_CATEGORIES.map((category) => ( |
| 180 | + <Badge key={category} variant="secondary"> |
| 181 | + {category} |
| 182 | + </Badge> |
| 183 | + ))} |
| 184 | + </div> |
| 185 | + <p className="text-xs text-muted-foreground"> |
| 186 | + Custom category management will be available in a future phase. |
| 187 | + </p> |
| 188 | + </CardContent> |
| 189 | + </Card> |
| 190 | + |
| 191 | + {/* Sponsor Rate Card */} |
| 192 | + <Card> |
| 193 | + <CardHeader> |
| 194 | + <CardTitle>Sponsor Rate Card</CardTitle> |
| 195 | + <CardDescription> |
| 196 | + Sponsorship tiers and pricing used by the sponsor portal and |
| 197 | + pipeline. |
| 198 | + </CardDescription> |
| 199 | + </CardHeader> |
| 200 | + <CardContent> |
| 201 | + <div className="space-y-3"> |
| 202 | + {RATE_CARD_TIERS.map((tier) => ( |
| 203 | + <div |
| 204 | + key={tier.name} |
| 205 | + className="flex items-center justify-between rounded-lg border p-3" |
| 206 | + > |
| 207 | + <div> |
| 208 | + <p className="text-sm font-medium">{tier.name}</p> |
| 209 | + <p className="text-xs text-muted-foreground"> |
| 210 | + {tier.description} |
| 211 | + </p> |
| 212 | + </div> |
| 213 | + <span className="text-sm font-semibold">{tier.price}</span> |
| 214 | + </div> |
| 215 | + ))} |
| 216 | + </div> |
| 217 | + <p className="mt-4 text-xs text-muted-foreground"> |
| 218 | + Rate card editing will be available once the sponsor rate card |
| 219 | + schema is finalized. |
| 220 | + </p> |
| 221 | + </CardContent> |
| 222 | + </Card> |
| 223 | + |
| 224 | + {/* Integrations Status */} |
| 225 | + <Card> |
| 226 | + <CardHeader> |
| 227 | + <CardTitle>Integrations Status</CardTitle> |
| 228 | + <CardDescription> |
| 229 | + Connection status for external services. Green indicates the |
| 230 | + environment variable is configured. |
| 231 | + </CardDescription> |
| 232 | + </CardHeader> |
| 233 | + <CardContent> |
| 234 | + <div className="space-y-3"> |
| 235 | + {integrationStatus.map((integration) => ( |
| 236 | + <div |
| 237 | + key={integration.name} |
| 238 | + className="flex items-center gap-3" |
| 239 | + > |
| 240 | + <IntegrationDot connected={integration.connected} /> |
| 241 | + <div className="flex-1"> |
| 242 | + <p className="text-sm font-medium">{integration.name}</p> |
| 243 | + <p className="text-xs text-muted-foreground"> |
| 244 | + {integration.description} |
| 245 | + </p> |
| 246 | + </div> |
| 247 | + <Badge |
| 248 | + variant={integration.connected ? "default" : "outline"} |
| 249 | + className="text-xs" |
| 250 | + > |
| 251 | + {integration.connected ? "Connected" : "Not configured"} |
| 252 | + </Badge> |
| 253 | + </div> |
| 254 | + ))} |
| 255 | + </div> |
| 256 | + </CardContent> |
| 257 | + </Card> |
| 258 | + </div> |
| 259 | + </div> |
| 260 | + ); |
59 | 261 | } |
0 commit comments