diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..966e122
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,3 @@
+VITE_API_URL=
+TWITCH_CLIENT_ID=
+TWITCH_CLIENT_SECRET=
diff --git a/.mise.toml b/.mise.toml
new file mode 100644
index 0000000..567d5e8
--- /dev/null
+++ b/.mise.toml
@@ -0,0 +1,3 @@
+[tools]
+node = "22"
+bun = "latest"
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..c060693
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
+ "organizeImports": {
+ "enabled": true
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "tab",
+ "lineWidth": 100
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double",
+ "semicolons": "asNeeded"
+ }
+ }
+}
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..3fc2199
--- /dev/null
+++ b/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/app.css",
+ "baseColor": "slate",
+ "cssVariables": true
+ },
+ "aliases": {
+ "components": "@/shared/components/ui",
+ "utils": "@/shared/lib/utils",
+ "ui": "@/shared/components/ui",
+ "lib": "@/shared/lib",
+ "hooks": "@/shared/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..821bc32
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ WhatToPlay
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..302c6d2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "whattoplay",
+ "private": true,
+ "version": "2026.03.01",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "dev:server": "cd server && bun run dev",
+ "dev:all": "bun run dev & bun run dev:server",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview",
+ "lint": "biome check .",
+ "lint:fix": "biome check --write .",
+ "test": "vitest run",
+ "prepare": "simple-git-hooks"
+ },
+ "dependencies": {
+ "@electric-sql/pglite": "^0.2.17",
+ "@tanstack/react-form": "^1.0.0",
+ "@tanstack/react-router": "^1.114.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "hono": "^4.7.0",
+ "lucide-react": "^0.474.0",
+ "radix-ui": "^1.4.3",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwind-merge": "^3.5.0",
+ "zod": "^3.24.0",
+ "zustand": "^5.0.0"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "^1.9.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "@tanstack/router-plugin": "^1.114.0",
+ "@testing-library/react": "^16.0.0",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^4.4.0",
+ "lint-staged": "^15.0.0",
+ "simple-git-hooks": "^2.11.0",
+ "tailwindcss": "^4.0.0",
+ "typescript": "^5.7.0",
+ "vite": "^6.2.0",
+ "vite-plugin-pwa": "^0.21.0",
+ "vitest": "^3.0.0",
+ "workbox-window": "^7.0.0"
+ },
+ "simple-git-hooks": {
+ "pre-commit": "bunx lint-staged"
+ },
+ "lint-staged": {
+ "*.{ts,tsx,js,json}": ["biome check --write"]
+ }
+}
diff --git a/src/.sync-conflict-20260301-130309-TZ5MTB7.DS_Store b/src/.sync-conflict-20260301-130309-TZ5MTB7.DS_Store
new file mode 100644
index 0000000..f2b0070
Binary files /dev/null and b/src/.sync-conflict-20260301-130309-TZ5MTB7.DS_Store differ
diff --git a/src/app.css b/src/app.css
new file mode 100644
index 0000000..12c40b8
--- /dev/null
+++ b/src/app.css
@@ -0,0 +1,72 @@
+@import "tailwindcss";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme {
+ --color-background: hsl(0 0% 100%);
+ --color-foreground: hsl(222.2 84% 4.9%);
+ --color-card: hsl(0 0% 100%);
+ --color-card-foreground: hsl(222.2 84% 4.9%);
+ --color-popover: hsl(0 0% 100%);
+ --color-popover-foreground: hsl(222.2 84% 4.9%);
+ --color-primary: hsl(222.2 47.4% 11.2%);
+ --color-primary-foreground: hsl(210 40% 98%);
+ --color-secondary: hsl(210 40% 96.1%);
+ --color-secondary-foreground: hsl(222.2 47.4% 11.2%);
+ --color-muted: hsl(210 40% 96.1%);
+ --color-muted-foreground: hsl(215.4 16.3% 46.9%);
+ --color-accent: hsl(210 40% 96.1%);
+ --color-accent-foreground: hsl(222.2 47.4% 11.2%);
+ --color-destructive: hsl(0 84.2% 60.2%);
+ --color-destructive-foreground: hsl(210 40% 98%);
+ --color-border: hsl(214.3 31.8% 91.4%);
+ --color-input: hsl(214.3 31.8% 91.4%);
+ --color-ring: hsl(222.2 84% 4.9%);
+ --radius: 0.5rem;
+
+ --color-sidebar: hsl(0 0% 98%);
+ --color-sidebar-foreground: hsl(240 5.3% 26.1%);
+ --color-sidebar-primary: hsl(240 5.9% 10%);
+ --color-sidebar-primary-foreground: hsl(0 0% 98%);
+ --color-sidebar-accent: hsl(240 4.8% 95.9%);
+ --color-sidebar-accent-foreground: hsl(240 5.9% 10%);
+ --color-sidebar-border: hsl(220 13% 91%);
+ --color-sidebar-ring: hsl(217.2 91.2% 59.8%);
+}
+
+@layer base {
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ border-color: var(--color-border);
+ }
+ body {
+ background-color: var(--color-background);
+ color: var(--color-foreground);
+ font-family: system-ui, -apple-system, sans-serif;
+ }
+}
+
+.dark {
+ --color-background: hsl(222.2 84% 4.9%);
+ --color-foreground: hsl(210 40% 98%);
+ --color-card: hsl(222.2 84% 4.9%);
+ --color-card-foreground: hsl(210 40% 98%);
+ --color-popover: hsl(222.2 84% 4.9%);
+ --color-popover-foreground: hsl(210 40% 98%);
+ --color-primary: hsl(210 40% 98%);
+ --color-primary-foreground: hsl(222.2 47.4% 11.2%);
+ --color-secondary: hsl(217.2 32.6% 17.5%);
+ --color-secondary-foreground: hsl(210 40% 98%);
+ --color-muted: hsl(217.2 32.6% 17.5%);
+ --color-muted-foreground: hsl(215 20.2% 65.1%);
+ --color-accent: hsl(217.2 32.6% 17.5%);
+ --color-accent-foreground: hsl(210 40% 98%);
+ --color-destructive: hsl(0 62.8% 30.6%);
+ --color-destructive-foreground: hsl(210 40% 98%);
+ --color-border: hsl(217.2 32.6% 17.5%);
+ --color-input: hsl(217.2 32.6% 17.5%);
+ --color-ring: hsl(212.7 26.8% 83.9%);
+}
diff --git a/src/shared/components/ui/badge.tsx b/src/shared/components/ui/badge.tsx
new file mode 100644
index 0000000..1fdb58a
--- /dev/null
+++ b/src/shared/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import { type VariantProps, cva } from "class-variance-authority"
+import { Slot } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+)
+
+function Badge({
+ className,
+ variant = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot.Root : "span"
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/shared/components/ui/button.tsx b/src/shared/components/ui/button.tsx
new file mode 100644
index 0000000..1e15413
--- /dev/null
+++ b/src/shared/components/ui/button.tsx
@@ -0,0 +1,62 @@
+import { type VariantProps, cva } from "class-variance-authority"
+import { Slot } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/src/shared/components/ui/card.tsx b/src/shared/components/ui/card.tsx
new file mode 100644
index 0000000..760c10b
--- /dev/null
+++ b/src/shared/components/ui/card.tsx
@@ -0,0 +1,75 @@
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }
diff --git a/src/shared/components/ui/dialog.tsx b/src/shared/components/ui/dialog.tsx
new file mode 100644
index 0000000..9a53029
--- /dev/null
+++ b/src/shared/components/ui/dialog.tsx
@@ -0,0 +1,142 @@
+import { XIcon } from "lucide-react"
+import { Dialog as DialogPrimitive } from "radix-ui"
+import type * as React from "react"
+
+import { Button } from "@/shared/components/ui/button"
+import { cn } from "@/shared/lib/utils"
+
+function Dialog({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DialogClose({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({
+ className,
+ showCloseButton = false,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+ {children}
+ {showCloseButton && (
+
+
+
+ )}
+
+ )
+}
+
+function DialogTitle({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/src/shared/components/ui/dropdown-menu.tsx b/src/shared/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..f8b708d
--- /dev/null
+++ b/src/shared/components/ui/dropdown-menu.tsx
@@ -0,0 +1,228 @@
+"use client"
+
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function DropdownMenu({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function DropdownMenuGroup({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({ ...props }: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+}
diff --git a/src/shared/components/ui/input.tsx b/src/shared/components/ui/input.tsx
new file mode 100644
index 0000000..85189bf
--- /dev/null
+++ b/src/shared/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/src/shared/components/ui/select.tsx b/src/shared/components/ui/select.tsx
new file mode 100644
index 0000000..b09478f
--- /dev/null
+++ b/src/shared/components/ui/select.tsx
@@ -0,0 +1,175 @@
+"use client"
+
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+import { Select as SelectPrimitive } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function Select({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SelectValue({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "item-aligned",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/src/shared/components/ui/separator.tsx b/src/shared/components/ui/separator.tsx
new file mode 100644
index 0000000..aad9ea7
--- /dev/null
+++ b/src/shared/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import { Separator as SeparatorPrimitive } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/src/shared/components/ui/sheet.tsx b/src/shared/components/ui/sheet.tsx
new file mode 100644
index 0000000..1d9ec32
--- /dev/null
+++ b/src/shared/components/ui/sheet.tsx
@@ -0,0 +1,134 @@
+"use client"
+
+import { XIcon } from "lucide-react"
+import { Dialog as SheetPrimitive } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetTrigger({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetClose({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetPortal({ ...props }: React.ComponentProps) {
+ return
+}
+
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left"
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/src/shared/components/ui/tabs.tsx b/src/shared/components/ui/tabs.tsx
new file mode 100644
index 0000000..22d8b43
--- /dev/null
+++ b/src/shared/components/ui/tabs.tsx
@@ -0,0 +1,79 @@
+import { type VariantProps, cva } from "class-variance-authority"
+import { Tabs as TabsPrimitive } from "radix-ui"
+import type * as React from "react"
+
+import { cn } from "@/shared/lib/utils"
+
+function Tabs({
+ className,
+ orientation = "horizontal",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const tabsListVariants = cva(
+ "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
+ {
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+)
+
+function TabsList({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps & VariantProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..b6929c7
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..af79c87
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,32 @@
+import tailwindcss from "@tailwindcss/vite"
+import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
+import react from "@vitejs/plugin-react"
+import { defineConfig } from "vite"
+import { VitePWA } from "vite-plugin-pwa"
+
+export default defineConfig({
+ plugins: [
+ TanStackRouterVite(),
+ react(),
+ tailwindcss(),
+ VitePWA({
+ registerType: "autoUpdate",
+ workbox: {
+ globPatterns: ["**/*.{js,css,html,ico,png,svg,wasm,data}"],
+ },
+ }),
+ ],
+ resolve: {
+ alias: {
+ "@": "/src",
+ },
+ },
+ server: {
+ proxy: {
+ "/api": {
+ target: "http://localhost:3001",
+ changeOrigin: true,
+ },
+ },
+ },
+})