Add Turso/Vercel deployment configuration

- Updated database connection to support Turso auth token
- Added vercel.json with bun build configuration
- Updated environment schema for production deployment
- Added new features and components for production readiness
This commit is contained in:
2025-07-12 01:42:43 -04:00
parent 2d217fab47
commit a1b40e7a9c
75 changed files with 8821 additions and 1803 deletions

View File

@@ -0,0 +1,356 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon, Search } from "lucide-react"
import { cn } from "~/lib/utils"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-full items-center justify-between gap-2 rounded-md border border-gray-200 bg-gray-50 px-3 h-10 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-emerald-500 focus-visible:ring-emerald-500 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
// Enhanced SelectContent with search functionality
function SelectContentWithSearch({
className,
children,
position = "popper",
searchPlaceholder = "Search...",
onSearchChange,
searchValue,
isOpen,
filteredOptions,
...props
}: React.ComponentProps<typeof SelectPrimitive.Content> & {
searchPlaceholder?: string;
onSearchChange?: (value: string) => void;
searchValue?: string;
isOpen?: boolean;
filteredOptions?: { value: string; label: string }[];
}) {
const searchInputRef = React.useRef<HTMLInputElement>(null);
const wasOpen = React.useRef(false);
React.useEffect(() => {
// Only focus when dropdown transitions from closed to open
if (isOpen && !wasOpen.current && searchInputRef.current) {
searchInputRef.current.focus();
}
wasOpen.current = !!isOpen;
}, [isOpen]);
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
onEscapeKeyDown={(e) => {
// Prevent escape from closing the dropdown when typing
if (searchValue) {
e.preventDefault();
}
}}
onPointerDownOutside={(e) => {
// Prevent closing when clicking inside the search input
if (searchInputRef.current?.contains(e.target as Node)) {
e.preventDefault();
}
}}
{...props}
>
{onSearchChange && (
<div className="flex items-center px-3 py-2 border-b">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<input
ref={searchInputRef}
className="flex h-8 w-full rounded-md bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 border-0 focus:ring-0 focus:outline-none"
placeholder={searchPlaceholder}
value={searchValue}
onChange={(e) => onSearchChange(e.target.value)}
onKeyDown={(e) => {
// Prevent the dropdown from closing when typing
if (e.key === "Escape") {
e.stopPropagation();
}
// Prevent arrow keys from moving focus away from search
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
e.stopPropagation();
}
}}
onFocus={(e) => {
// Ensure the search input stays focused
e.target.select();
}}
autoFocus
/>
</div>
)}
<SelectScrollUpButton />
<SelectPrimitive.Viewport className="p-1">
{filteredOptions && filteredOptions.length === 0 ? (
<div className="px-3 py-2 text-sm text-muted-foreground select-none">No results found</div>
) : (
children
)}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
// Searchable Select component
interface SearchableSelectProps {
value?: string;
onValueChange?: (value: string) => void;
placeholder?: string;
options: { value: string; label: string }[];
searchPlaceholder?: string;
className?: string;
disabled?: boolean;
}
function SearchableSelect({
value,
onValueChange,
placeholder,
options,
searchPlaceholder = "Search...",
className,
disabled
}: SearchableSelectProps) {
const [searchValue, setSearchValue] = React.useState("");
const [isOpen, setIsOpen] = React.useState(false);
const filteredOptions = React.useMemo(() => {
if (!searchValue) return options;
return options.filter(option =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
);
}, [options, searchValue]);
// Convert empty string to placeholder value for display
const displayValue = value === "" ? "__placeholder__" : value;
// Convert placeholder value back to empty string when selected
const handleValueChange = (newValue: string) => {
const actualValue = newValue === "__placeholder__" ? "" : newValue;
onValueChange?.(actualValue);
// Clear search when an option is selected
setSearchValue("");
setIsOpen(false);
};
return (
<Select
value={displayValue}
onValueChange={handleValueChange}
disabled={disabled}
open={isOpen}
onOpenChange={setIsOpen}
>
<SelectTrigger className={cn("w-full", className)}>
<SelectValue
placeholder={placeholder}
// Always show placeholder if nothing is selected
data-placeholder={displayValue === "__placeholder__"}
/>
</SelectTrigger>
<SelectContentWithSearch
searchPlaceholder={searchPlaceholder}
searchValue={searchValue}
onSearchChange={setSearchValue}
isOpen={isOpen}
filteredOptions={filteredOptions}
>
{filteredOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContentWithSearch>
</Select>
);
}
export {
Select,
SelectContent,
SelectContentWithSearch,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
SearchableSelect,
}