From 436f3d7fada2dce6c041915e5919ed786e1b0f6e Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Thu, 5 Mar 2026 19:15:52 -0500 Subject: [PATCH] Fix Select trigger display values and add TMDB attribution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Base UI Select.Value renders raw values by default — add children render functions to map values to human-readable labels (e.g. "this_month" → "This Month", "0" → "unlimited"). Switch inline select underlines from border-bottom to text-decoration for proper baseline alignment. Add TMDB attribution with logo and disclaimer to settings footer. Co-Authored-By: Claude Opus 4.6 --- app/(auth)/setup/page.tsx | 21 +- .../dashboard/_components/stats-display.tsx | 57 +++--- .../filterable-title-row.tsx | 12 +- .../explore/{ => _components}/hero-banner.tsx | 0 .../explore/{ => _components}/title-row.tsx | 0 app/(pages)/explore/page.tsx | 6 +- .../[id]/_components/filmography-grid.tsx | 49 +++-- .../people/[id]/_components/person-hero.tsx | 5 +- .../settings/_components/account-section.tsx | 5 +- .../_components/backup-restore-section.tsx | 2 +- .../_components/backup-schedule-section.tsx | 192 +++++++++--------- .../settings/_components/backup-section.tsx | 2 +- .../settings/_components/webhook-card.tsx | 86 ++++---- app/(pages)/settings/page.tsx | 15 ++ .../titles/[id]/_components/title-actions.tsx | 12 +- .../titles/[id]/_components/title-hero.tsx | 12 +- .../titles/[id]/_components/title-seasons.tsx | 30 +-- components/auth-form.tsx | 103 +++++----- components/command-palette.tsx | 2 +- components/nav-bar.tsx | 6 +- components/ui/native-select.tsx | 55 ----- 21 files changed, 351 insertions(+), 321 deletions(-) rename app/(pages)/explore/{ => _components}/filterable-title-row.tsx (94%) rename app/(pages)/explore/{ => _components}/hero-banner.tsx (100%) rename app/(pages)/explore/{ => _components}/title-row.tsx (100%) delete mode 100644 components/ui/native-select.tsx diff --git a/app/(auth)/setup/page.tsx b/app/(auth)/setup/page.tsx index 48a710c..5f79e5c 100644 --- a/app/(auth)/setup/page.tsx +++ b/app/(auth)/setup/page.tsx @@ -9,6 +9,8 @@ import { import { motion } from "motion/react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/spinner"; const steps = [ { @@ -181,10 +183,11 @@ export default function SetupPage() { {snippet.label} - +
                         {snippet.code}
@@ -244,21 +247,21 @@ export default function SetupPage() {
                   Click the button to verify your configuration
                 

- + )} diff --git a/app/(pages)/dashboard/_components/stats-display.tsx b/app/(pages)/dashboard/_components/stats-display.tsx index 517a00d..c8e9347 100644 --- a/app/(pages)/dashboard/_components/stats-display.tsx +++ b/app/(pages)/dashboard/_components/stats-display.tsx @@ -2,19 +2,18 @@ import { IconCheck, - IconChevronDown, IconLibrary, IconMovie, IconPlayerPlay, } from "@tabler/icons-react"; import { useAtom, useAtomValue } from "jotai"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { episodePeriodAtom, episodeStatsAtom, @@ -83,6 +82,9 @@ function StatCard({ ); } +const inlineTriggerClass = + "h-auto w-auto gap-0.5 rounded-none border-0 bg-transparent p-0 [font-size:inherit] [line-height:inherit] shadow-none underline decoration-dotted decoration-muted-foreground/50 underline-offset-4 hover:bg-transparent hover:text-foreground hover:decoration-foreground/50 focus-visible:ring-0 focus-visible:decoration-solid focus-visible:decoration-foreground dark:bg-transparent dark:hover:bg-transparent"; + function PeriodSelector({ noun, period, @@ -95,24 +97,29 @@ function PeriodSelector({ return ( {noun}{" "} - - - {periodLabels[period]} - - - - onPeriodChange(v as TimePeriod)} - > - {periods.map((p) => ( - - {periodLabels[p]} - - ))} - - - + ); } diff --git a/app/(pages)/explore/filterable-title-row.tsx b/app/(pages)/explore/_components/filterable-title-row.tsx similarity index 94% rename from app/(pages)/explore/filterable-title-row.tsx rename to app/(pages)/explore/_components/filterable-title-row.tsx index bd51d9a..31e6b47 100644 --- a/app/(pages)/explore/filterable-title-row.tsx +++ b/app/(pages)/explore/_components/filterable-title-row.tsx @@ -5,6 +5,7 @@ import { createStore, Provider, useAtom, useAtomValue } from "jotai"; import { useState } from "react"; import { TitleCardSkeleton } from "@/components/skeletons"; import { ExploreTitleCard } from "@/components/title-card"; +import { Button } from "@/components/ui/button"; import { Carousel, CarouselContent, @@ -114,18 +115,19 @@ function FilterableTitleRowInner({ {/* Genre chips */}
{genres.map((genre) => ( - + ))}
diff --git a/app/(pages)/explore/hero-banner.tsx b/app/(pages)/explore/_components/hero-banner.tsx similarity index 100% rename from app/(pages)/explore/hero-banner.tsx rename to app/(pages)/explore/_components/hero-banner.tsx diff --git a/app/(pages)/explore/title-row.tsx b/app/(pages)/explore/_components/title-row.tsx similarity index 100% rename from app/(pages)/explore/title-row.tsx rename to app/(pages)/explore/_components/title-row.tsx diff --git a/app/(pages)/explore/page.tsx b/app/(pages)/explore/page.tsx index 4a067c7..4ffbf4a 100644 --- a/app/(pages)/explore/page.tsx +++ b/app/(pages)/explore/page.tsx @@ -6,9 +6,9 @@ import { } from "@/lib/services/tracking"; import { getGenres, getPopular, getTrending } from "@/lib/tmdb/client"; import { tmdbImageUrl } from "@/lib/tmdb/image"; -import { FilterableTitleRow } from "./filterable-title-row"; -import { HeroBanner } from "./hero-banner"; -import { TitleRow } from "./title-row"; +import { FilterableTitleRow } from "./_components/filterable-title-row"; +import { HeroBanner } from "./_components/hero-banner"; +import { TitleRow } from "./_components/title-row"; function mapResults( results: { diff --git a/app/(pages)/people/[id]/_components/filmography-grid.tsx b/app/(pages)/people/[id]/_components/filmography-grid.tsx index 3efef65..f6f6a7c 100644 --- a/app/(pages)/people/[id]/_components/filmography-grid.tsx +++ b/app/(pages)/people/[id]/_components/filmography-grid.tsx @@ -4,6 +4,14 @@ import { IconMovie } from "@tabler/icons-react"; import Image from "next/image"; import Link from "next/link"; import { useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import type { PersonCredit } from "@/lib/types/title"; type Filter = "all" | "movie" | "tv"; @@ -62,31 +70,44 @@ export function FilmographyGrid({ credits }: FilmographyGridProps) { - + + + {(value: string | null) => + value === "newest" + ? "Newest" + : value === "rating" + ? "Rating" + : null + } + + + + Newest + Rating + +
{filters.map((f) => ( - + ))}
diff --git a/app/(pages)/people/[id]/_components/person-hero.tsx b/app/(pages)/people/[id]/_components/person-hero.tsx index 83e03a0..2d19112 100644 --- a/app/(pages)/people/[id]/_components/person-hero.tsx +++ b/app/(pages)/people/[id]/_components/person-hero.tsx @@ -3,6 +3,7 @@ import { IconCalendar, IconMapPin } from "@tabler/icons-react"; import Image from "next/image"; import { useState } from "react"; +import { Badge } from "@/components/ui/badge"; import type { ResolvedPerson } from "@/lib/types/title"; interface PersonHeroProps { @@ -53,9 +54,9 @@ export function PersonHero({ person }: PersonHeroProps) { {person.knownForDepartment && ( - + {person.knownForDepartment} - + )}
diff --git a/app/(pages)/settings/_components/account-section.tsx b/app/(pages)/settings/_components/account-section.tsx index b5bc435..f99b461 100644 --- a/app/(pages)/settings/_components/account-section.tsx +++ b/app/(pages)/settings/_components/account-section.tsx @@ -2,6 +2,7 @@ import { IconLogout, IconUser } from "@tabler/icons-react"; import { useRouter } from "next/navigation"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, @@ -41,9 +42,9 @@ export function AccountSection({ {user.name} {user.role === "admin" && ( - + Admin - + )} {user.email} diff --git a/app/(pages)/settings/_components/backup-restore-section.tsx b/app/(pages)/settings/_components/backup-restore-section.tsx index ca827bf..5d4e9d4 100644 --- a/app/(pages)/settings/_components/backup-restore-section.tsx +++ b/app/(pages)/settings/_components/backup-restore-section.tsx @@ -119,7 +119,7 @@ export function BackupRestoreSection() { disabled={restoring} > {restoring ? : } - {restoring ? "Restoring\u2026" : "Upload"} + {restoring ? "Restoring…" : "Upload"}
diff --git a/app/(pages)/settings/_components/backup-schedule-section.tsx b/app/(pages)/settings/_components/backup-schedule-section.tsx index 072865f..43d339c 100644 --- a/app/(pages)/settings/_components/backup-schedule-section.tsx +++ b/app/(pages)/settings/_components/backup-schedule-section.tsx @@ -1,19 +1,20 @@ "use client"; -import { IconCalendarWeek, IconChevronDown } from "@tabler/icons-react"; +import { IconCalendarWeek } from "@tabler/icons-react"; import { format, formatDistanceToNow } from "date-fns"; import { useAtomValue } from "jotai"; import { useHydrateAtoms } from "jotai/utils"; import { AnimatePresence, motion } from "motion/react"; import { Button } from "@/components/ui/button"; +import { ButtonGroup } from "@/components/ui/button-group"; import { CardContent, CardDescription, CardTitle } from "@/components/ui/card"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { backupScheduleAtom, @@ -161,29 +162,33 @@ function BackupScheduleInner() { {formatNextBackup(frequency, time, dow)}. {" "} Keeping{" "} - - - {maxRetention === 0 - ? "unlimited" - : `last ${maxRetention}`} - - - - changeMaxRetention(Number(v))} - > - {[3, 5, 7, 14, 30, 0].map((n) => ( - - {n === 0 ? "unlimited" : n} - - ))} - - - {" "} + {" "} backups. ) : ( @@ -217,7 +222,7 @@ function BackupScheduleInner() { Frequency -
+ {FREQUENCY_OPTIONS.map((opt) => (
+ {/* Day of week — shown for 7d only */} @@ -250,32 +255,33 @@ function BackupScheduleInner() { Day:{" "} - - - {DAYS_OF_WEEK[dow]} - - - - - changeSchedule(frequency, time, Number(v)) + )} @@ -293,41 +299,43 @@ function BackupScheduleInner() { {frequency === "12h" ? "Starting at" : "Time:"}{" "} - - - {format( - new Date( - 2000, - 0, - 1, - ...(time.split(":").map(Number) as [ - number, - number, - ]), - ), - "h:mm a", - )} - - - - changeSchedule(frequency, v)} - > - {HOURS.map((h) => { - const val = `${String(h).padStart(2, "0")}:00`; - return ( - - {format(new Date(2000, 0, 1, h, 0), "h:mm a")} - - ); - })} - - - + )} diff --git a/app/(pages)/settings/_components/backup-section.tsx b/app/(pages)/settings/_components/backup-section.tsx index 1830736..3807116 100644 --- a/app/(pages)/settings/_components/backup-section.tsx +++ b/app/(pages)/settings/_components/backup-section.tsx @@ -119,7 +119,7 @@ export function BackupSection({ ) : ( )} - {creating ? "Creating\u2026" : "New backup"} + {creating ? "Creating…" : "New backup"} diff --git a/app/(pages)/settings/_components/webhook-card.tsx b/app/(pages)/settings/_components/webhook-card.tsx index 73a98e4..0ed310d 100644 --- a/app/(pages)/settings/_components/webhook-card.tsx +++ b/app/(pages)/settings/_components/webhook-card.tsx @@ -13,6 +13,7 @@ import { import { formatDistanceToNow } from "date-fns"; import { AnimatePresence, motion } from "motion/react"; import { useState } from "react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card, @@ -25,7 +26,13 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { Input } from "@/components/ui/input"; +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, +} from "@/components/ui/input-group"; +import { Label } from "@/components/ui/label"; import { Tooltip, TooltipContent, @@ -117,12 +124,9 @@ export function WebhookCard({ {isPlex && ( -
- -

+ + + Requires an active{" "} {" "} subscription. -

-
+ + )} {isEmby && ( -
- -

+ + + Requires{" "} Emby Server 4.7.9+ @@ -166,8 +167,8 @@ export function WebhookCard({ /> {" "} license. -

-
+ + )} {!connection ? ( @@ -177,7 +178,7 @@ export function WebhookCard({ size="lg" className="w-full" > - {connecting ? "Connecting\u2026" : `Connect ${label}`} + {connecting ? "Connecting…" : `Connect ${label}`} ) : ( @@ -189,38 +190,39 @@ export function WebhookCard({ className="space-y-3 overflow-hidden" >
- -
- + + - - - } - > - {copied ? ( - - ) : ( - - )} - - Copy URL - -
+ + + + } + > + {copied ? ( + + ) : ( + + )} + + Copy URL + + +
diff --git a/app/(pages)/settings/page.tsx b/app/(pages)/settings/page.tsx index 53a6e12..88bc25c 100644 --- a/app/(pages)/settings/page.tsx +++ b/app/(pages)/settings/page.tsx @@ -5,6 +5,7 @@ import { } from "@tabler/icons-react"; import { desc, eq } from "drizzle-orm"; import { redirect } from "next/navigation"; +import { TmdbLogo } from "@/components/tmdb-logo"; import { Card } from "@/components/ui/card"; import { getSession } from "@/lib/auth/session"; import { db } from "@/lib/db/client"; @@ -145,6 +146,20 @@ export default async function SettingsPage() { )}

+
+ + + +

+ This product uses the TMDB API but is not endorsed or certified by + TMDB. +

+
} > diff --git a/app/(pages)/titles/[id]/_components/title-actions.tsx b/app/(pages)/titles/[id]/_components/title-actions.tsx index 67fcdbb..06a5f69 100644 --- a/app/(pages)/titles/[id]/_components/title-actions.tsx +++ b/app/(pages)/titles/[id]/_components/title-actions.tsx @@ -2,6 +2,8 @@ import { IconCheck } from "@tabler/icons-react"; import { useAtomValue } from "jotai"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { titleTypeAtom, userRatingAtom, @@ -25,16 +27,16 @@ export function TitleActions() { onChange={handleStatusChange} /> {titleType === "movie" && ( - + )} - +
); diff --git a/app/(pages)/titles/[id]/_components/title-hero.tsx b/app/(pages)/titles/[id]/_components/title-hero.tsx index cc24fbf..7e3a34a 100644 --- a/app/(pages)/titles/[id]/_components/title-hero.tsx +++ b/app/(pages)/titles/[id]/_components/title-hero.tsx @@ -1,6 +1,7 @@ import Image from "next/image"; import type { ReactNode } from "react"; import { TmdbLogo } from "@/components/tmdb-logo"; +import { Badge } from "@/components/ui/badge"; import type { ColorPalette, ResolvedTitle } from "@/lib/types/title"; import { TrailerDialog } from "./trailer-dialog"; @@ -100,9 +101,9 @@ export function TitleHero({ {title.title}
- + {title.type} - + {year && {year}} {title.voteAverage != null && title.voteAverage > 0 && ( @@ -115,9 +116,12 @@ export function TitleHero({ )} {title.status && ( - + {title.status} - + )}
-

Seasons

+

Episodes

{userStatus && userStatus !== "completed" && ( Mark All Watched - + } /> @@ -151,28 +153,30 @@ export function TitleSeasons({ )} {totalCount > 0 && watchedCount < totalCount && ( - + )} {totalCount > 0 && watchedCount === totalCount && ( - + )} {totalCount > 0 && ( diff --git a/components/auth-form.tsx b/components/auth-form.tsx index be94a69..413fcef 100644 --- a/components/auth-form.tsx +++ b/components/auth-form.tsx @@ -6,6 +6,10 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { SofaLogo } from "@/components/sofa-logo"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { authClient, signIn, signUp } from "@/lib/auth/client"; export interface AuthConfig { @@ -15,6 +19,9 @@ export interface AuthConfig { registrationOpen?: boolean; } +const authInputClass = + "h-11 rounded-lg border-border/50 bg-background/50 px-4 py-0 placeholder:text-muted-foreground/50 focus-visible:border-primary/40 focus-visible:ring-ring md:text-sm"; + const fieldVariants = { hidden: { opacity: 0, y: 10 }, visible: { @@ -117,19 +124,20 @@ export function AuthForm({ visible: { transition: { staggerChildren: 0.08 } }, }} > - - - {oidcLoading - ? "Redirecting\u2026" - : `Sign in with ${authConfig?.oidcProviderName || "SSO"}`} - + + + )} @@ -154,33 +162,33 @@ export function AuthForm({ > {isRegister && ( - - + setName(e.target.value)} - className="flex h-11 w-full rounded-lg border border-border/50 bg-background/50 px-4 text-sm transition-colors placeholder:text-muted-foreground/50 focus-visible:border-primary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" - placeholder="Your name\u2026" + className={authInputClass} + placeholder="Your name…" /> )} - - + setEmail(e.target.value)} - className="flex h-11 w-full rounded-lg border border-border/50 bg-background/50 px-4 text-sm transition-colors placeholder:text-muted-foreground/50 focus-visible:border-primary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" + className={authInputClass} placeholder="wwhite@graymatter.biz" /> - - + setPassword(e.target.value)} - className="flex h-11 w-full rounded-lg border border-border/50 bg-background/50 px-4 text-sm transition-colors placeholder:text-muted-foreground/50 focus-visible:border-primary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" - placeholder="Min 8 characters\u2026" + className={authInputClass} + placeholder="Min 8 characters…" /> - - {loading - ? "Loading\u2026" - : isRegister - ? "Create account" - : "Sign in"} - + + + )} {error && ( - {error} + + + {error} + + )} diff --git a/components/command-palette.tsx b/components/command-palette.tsx index 2c7ce34..ca1de96 100644 --- a/components/command-palette.tsx +++ b/components/command-palette.tsx @@ -152,7 +152,7 @@ export function CommandPalette() { > diff --git a/components/nav-bar.tsx b/components/nav-bar.tsx index 15deebc..117aec0 100644 --- a/components/nav-bar.tsx +++ b/components/nav-bar.tsx @@ -7,6 +7,7 @@ import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { SofaLogo } from "@/components/sofa-logo"; import { Kbd } from "@/components/ui/kbd"; +import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, @@ -86,7 +87,10 @@ export function NavBar() { Search… ⌘K -
+ } diff --git a/components/ui/native-select.tsx b/components/ui/native-select.tsx deleted file mode 100644 index 70e2477..0000000 --- a/components/ui/native-select.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { IconSelector } from "@tabler/icons-react"; -import type * as React from "react"; -import { cn } from "@/lib/utils"; - -type NativeSelectProps = Omit, "size"> & { - size?: "sm" | "default"; -}; - -function NativeSelect({ - className, - size = "default", - ...props -}: NativeSelectProps) { - return ( -
-