diff --git a/frontend/components.json b/frontend/components.json
new file mode 100644
index 0000000..3c359c2
--- /dev/null
+++ b/frontend/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/styles/globals.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/frontend/package.json b/frontend/package.json
index a62a7dc..e9162bf 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -10,14 +10,18 @@
"preview": "vite preview"
},
"dependencies": {
+ "@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
+ "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-form": "^1.28.3",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-router": "^1.162.2",
@@ -32,6 +36,7 @@
"@eslint/js": "^9.39.1",
"@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/postcss": "^4.2.0",
+ "@tailwindcss/vite": "^4.2.0",
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
diff --git a/frontend/skills-lock.json b/frontend/skills-lock.json
new file mode 100644
index 0000000..4de9310
--- /dev/null
+++ b/frontend/skills-lock.json
@@ -0,0 +1,10 @@
+{
+ "version": 1,
+ "skills": {
+ "shadcn-ui": {
+ "source": "google-labs-code/stitch-skills",
+ "sourceType": "github",
+ "computedHash": "dadbca54d35a33fe73e40018611af2179689305bf6da812ab2643987fefd9da3"
+ }
+ }
+}
diff --git a/frontend/src/components/layout/Layout.tsx b/frontend/src/components/layout/Layout.tsx
index 6ae508f..ee98c86 100644
--- a/frontend/src/components/layout/Layout.tsx
+++ b/frontend/src/components/layout/Layout.tsx
@@ -1,38 +1,14 @@
-import { useState } from 'react';
import { Outlet } from '@tanstack/react-router';
-import { Header } from './Header';
-import { Sidebar } from './Sidebar';
-
-interface LayoutProps {
- sidebarOpen?: boolean;
- onSidebarChange?: (open: boolean) => void;
-}
-
-export function Layout({ sidebarOpen = false, onSidebarChange }: LayoutProps) {
- const [isSidebarOpen, setIsSidebarOpen] = useState(sidebarOpen);
-
- const handleSidebarToggle = (open: boolean) => {
- setIsSidebarOpen(open);
- onSidebarChange?.(open);
- };
+import { AppSidebar } from './Sidebar';
+export function Layout() {
return (
-
- {/* Sidebar */}
-
handleSidebarToggle(false)} />
-
- {/* Contenido principal */}
-
- {/* Header */}
-
handleSidebarToggle(true)} />
-
- {/* Main content */}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx
index 790220d..408fa62 100644
--- a/frontend/src/components/layout/Sidebar.tsx
+++ b/frontend/src/components/layout/Sidebar.tsx
@@ -1,119 +1,309 @@
-import { Link, useLocation } from '@tanstack/react-router';
-import { Home, Gamepad2, FileText, Settings, Database, Tag, Download, Upload } from 'lucide-react';
-import { cn } from '@/lib/utils';
-import { Button } from '@/components/ui/button';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import { Badge } from '@/components/ui/badge';
+'use client';
-interface SidebarProps {
- isOpen?: boolean;
- onClose?: () => void;
-}
+import * as React from 'react';
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarTrigger,
+ useSidebar,
+} from '@/components/ui/sidebar';
+import {
+ Gamepad2,
+ Home,
+ Settings,
+ User,
+ Database,
+ Star,
+ TrendingUp,
+ ChevronRight,
+ ChevronsUpDown,
+} from 'lucide-react';
-const navigation = [
- { name: 'Dashboard', href: '/', icon: Home, count: null },
- { name: 'Juegos', href: '/games', icon: Gamepad2, count: null },
- {
- name: 'Plataformas',
- href: '/platforms',
- icon: Database,
- count: null,
+const data = {
+ user: {
+ name: 'Game Library',
+ email: 'admin@gamelibrary.com',
+ avatar: '/avatars/default.jpg',
},
- {
- name: 'Etiquetas',
- href: '/tags',
- icon: Tag,
- count: null,
- },
- { name: 'Importar ROMs', href: '/import', icon: Download, count: null },
- { name: 'Exportar', href: '/export', icon: Upload, count: null },
- { name: 'Configuración', href: '/settings', icon: Settings, count: null },
-];
+ teams: [
+ {
+ name: 'Personal',
+ logo: Gamepad2,
+ plan: 'Standard',
+ },
+ {
+ name: 'Work',
+ logo: Database,
+ plan: 'Professional',
+ },
+ ],
+ navMain: [
+ {
+ title: 'Dashboard',
+ url: '/',
+ icon: Home,
+ isActive: true,
+ },
+ {
+ title: 'Games',
+ url: '/games',
+ icon: Gamepad2,
+ items: [
+ {
+ title: 'All Games',
+ url: '/games',
+ },
+ {
+ title: 'Favorites',
+ url: '/games/favorites',
+ },
+ {
+ title: 'Recently Played',
+ url: '/games/recent',
+ },
+ ],
+ },
+ {
+ title: 'Collections',
+ url: '/collections',
+ icon: Star,
+ items: [
+ {
+ title: 'My Collections',
+ url: '/collections',
+ },
+ {
+ title: 'Shared Collections',
+ url: '/collections/shared',
+ },
+ ],
+ },
+ {
+ title: 'Statistics',
+ url: '/stats',
+ icon: TrendingUp,
+ },
+ {
+ title: 'Settings',
+ url: '/settings',
+ icon: Settings,
+ },
+ ],
+};
-export function Sidebar({ isOpen, onClose }: SidebarProps) {
- const location = useLocation();
+function TeamSwitcher({
+ teams,
+}: {
+ teams: {
+ name: string;
+ logo: React.ElementType;
+ plan: string;
+ }[];
+}) {
+ const { isMobile } = useSidebar();
+ const [activeTeam, setActiveTeam] = React.useState(teams[0]);
+
+ if (!activeTeam) {
+ return null;
+ }
return (
- <>
- {/* Overlay para móviles */}
- {isOpen && }
-
- {/* Sidebar */}
-
-
- {/* Header del sidebar */}
-
-
- {/* Contenido del sidebar */}
-
-
-
- {/* Sección adicional con información */}
-
-
- Información
-
-
-
- Versión
- 1.0.0
-
-
- Última actualización
- 2024-01-15
-
+
+
+
+
+
+
-
-
-
- {/* Footer del sidebar */}
-
-
-
- >
+
+ {activeTeam.name}
+ {activeTeam.plan}
+
+
+
+
+
+
+ Teams
+ {teams.map((team) => (
+ setActiveTeam(team)}
+ className="cursor-pointer"
+ >
+
+
+
+
+ {team.name}
+ {team.plan}
+
+
+ ))}
+
+
+
+
+
+ );
+}
+
+function NavMain({
+ items,
+}: {
+ items: {
+ title: string;
+ url: string;
+ icon?: React.ElementType;
+ isActive?: boolean;
+ items?: {
+ title: string;
+ url: string;
+ }[];
+ }[];
+}) {
+ return (
+
+ Main Navigation
+
+ {items.map((item) => (
+
+
+ }>
+ {item.icon && }
+ {item.title}
+
+
+
+
+ {item.items?.map((subItem) => (
+
+ }>
+ {subItem.title}
+
+
+ ))}
+
+
+
+
+ ))}
+
+
+ );
+}
+
+function NavUser({ user }: { user: { name: string; email: string; avatar: string } }) {
+ const { isMobile } = useSidebar();
+
+ return (
+
+
+
+
+
+
+
+ {user.name.charAt(0)}
+
+
+ {user.name}
+ {user.email}
+
+
+
+
+
+
+ Logged in as
+
+
+
+
+ Profile
+
+
+
+ Settings
+
+
+
+
+ Log out
+
+
+
+
+
+ );
+}
+
+export function AppSidebar({ ...props }: React.ComponentProps
) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css
index f589dc9..baa1e66 100644
--- a/frontend/src/styles/globals.css
+++ b/frontend/src/styles/globals.css
@@ -1,6 +1,5 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import "tailwindcss/preflight";
+@import "tailwindcss";
@layer base {
:root {
diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts
index f144ef5..0c6481c 100644
--- a/frontend/tailwind.config.ts
+++ b/frontend/tailwind.config.ts
@@ -38,6 +38,16 @@ const config: Config = {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ primaryForeground: 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ accentForeground: 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))',
+ },
},
borderRadius: {
lg: 'var(--radius)',
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index f4e8f1c..965c1e3 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,10 +1,11 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
+import tailwindcss from '@tailwindcss/vite';
import path from 'path';
// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
diff --git a/yarn.lock b/yarn.lock
index 5f4f70c..23b014a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1395,6 +1395,29 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-avatar@npm:^1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-avatar@npm:1.1.11"
+ dependencies:
+ "@radix-ui/react-context": "npm:1.1.3"
+ "@radix-ui/react-primitive": "npm:2.1.4"
+ "@radix-ui/react-use-callback-ref": "npm:1.1.1"
+ "@radix-ui/react-use-is-hydrated": "npm:0.1.0"
+ "@radix-ui/react-use-layout-effect": "npm:1.1.1"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/b1b3d4b11a8e05a8479d2410fb4e7b1bf825135c4cd42f7e5152568a54a55a3073bd87d50325150417a29306e7b1b371289dc3c4f11739af8a2a7bb8dd7c38c9
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-checkbox@npm:^1.3.3":
version: 1.3.3
resolution: "@radix-ui/react-checkbox@npm:1.3.3"
@@ -1421,6 +1444,32 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-collapsible@npm:^1.1.12":
+ version: 1.1.12
+ resolution: "@radix-ui/react-collapsible@npm:1.1.12"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.3"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-presence": "npm:1.1.5"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ "@radix-ui/react-use-layout-effect": "npm:1.1.1"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/777cced73fbbec9cfafe6325aa5605e90f49d889af2778f4c4a6be101c07cacd69ae817d0b41cc27e3181f49392e2c06db7f32d6b084db047a51805ec70729b3
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-collection@npm:1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-collection@npm:1.1.7"
@@ -1469,6 +1518,19 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-context@npm:1.1.3":
+ version: 1.1.3
+ resolution: "@radix-ui/react-context@npm:1.1.3"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/0f271b4100dbb007ad2675f2529453f07454f214b7ce796d72680bf2dff050d0719083ee6e8962919a74048ff853eff2e50de07d8f8c674d6be91bfa76204cc2
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-dialog@npm:^1.1.15":
version: 1.1.15
resolution: "@radix-ui/react-dialog@npm:1.1.15"
@@ -1865,6 +1927,25 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-separator@npm:^1.1.8":
+ version: 1.1.8
+ resolution: "@radix-ui/react-separator@npm:1.1.8"
+ dependencies:
+ "@radix-ui/react-primitive": "npm:2.1.4"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/92e1353f696a22167c90f2c610b440be1fae3c05128287560914f124eef83d74c06ad25431923f3595032e6d89c23d479c95434390f4c0d9d4a68ec8d563ae0c
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-slot@npm:1.2.3":
version: 1.2.3
resolution: "@radix-ui/react-slot@npm:1.2.3"
@@ -1951,6 +2032,36 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-tooltip@npm:^1.2.8":
+ version: 1.2.8
+ resolution: "@radix-ui/react-tooltip@npm:1.2.8"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.3"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.11"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-popper": "npm:1.2.8"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.5"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ "@radix-ui/react-visually-hidden": "npm:1.2.3"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/de0cbae9c571a00671f160928d819e59502f59be8749f536ab4b180181d9d70aee3925a5b2555f8f32d0bea622bc35f65b70ca7ff0449e4844f891302310cc48
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-use-callback-ref@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
@@ -2010,6 +2121,21 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-use-is-hydrated@npm:0.1.0":
+ version: 0.1.0
+ resolution: "@radix-ui/react-use-is-hydrated@npm:0.1.0"
+ dependencies:
+ use-sync-external-store: "npm:^1.5.0"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/635079bafe32829fc7405895154568ea94a22689b170489fd6d77668e4885e72ff71ed6d0ea3d602852841ef0f1927aa400fee2178d5dfbeb8bc9297da7d6498
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-use-layout-effect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
@@ -2627,6 +2753,19 @@ __metadata:
languageName: node
linkType: hard
+"@tailwindcss/vite@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "@tailwindcss/vite@npm:4.2.0"
+ dependencies:
+ "@tailwindcss/node": "npm:4.2.0"
+ "@tailwindcss/oxide": "npm:4.2.0"
+ tailwindcss: "npm:4.2.0"
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7
+ checksum: 10c0/4bd28ea2984907930a2ea4818581ce24bbb7276ffc4d316b32143eef2f2bf9f82bd2b937fe6bb78c9be96b61df5464d884b9c7b1e71f0eb3df2c4a890d34ff79
+ languageName: node
+ linkType: hard
+
"@tanstack/devtools-event-client@npm:^0.4.0":
version: 0.4.0
resolution: "@tanstack/devtools-event-client@npm:0.4.0"
@@ -5095,16 +5234,21 @@ __metadata:
resolution: "frontend@workspace:frontend"
dependencies:
"@eslint/js": "npm:^9.39.1"
+ "@radix-ui/react-avatar": "npm:^1.1.11"
"@radix-ui/react-checkbox": "npm:^1.3.3"
+ "@radix-ui/react-collapsible": "npm:^1.1.12"
"@radix-ui/react-dialog": "npm:^1.1.15"
"@radix-ui/react-dropdown-menu": "npm:^2.1.16"
"@radix-ui/react-label": "npm:^2.1.8"
"@radix-ui/react-scroll-area": "npm:^1.2.10"
"@radix-ui/react-select": "npm:^2.2.6"
+ "@radix-ui/react-separator": "npm:^1.1.8"
"@radix-ui/react-slot": "npm:^1.2.4"
"@radix-ui/react-tabs": "npm:^1.1.13"
"@radix-ui/react-toast": "npm:^1.2.15"
+ "@radix-ui/react-tooltip": "npm:^1.2.8"
"@tailwindcss/postcss": "npm:^4.2.0"
+ "@tailwindcss/vite": "npm:^4.2.0"
"@tanstack/react-form": "npm:^1.28.3"
"@tanstack/react-query": "npm:^5.90.21"
"@tanstack/react-router": "npm:^1.162.2"
@@ -7841,7 +7985,7 @@ __metadata:
languageName: node
linkType: hard
-"use-sync-external-store@npm:^1.6.0":
+"use-sync-external-store@npm:^1.5.0, use-sync-external-store@npm:^1.6.0":
version: 1.6.0
resolution: "use-sync-external-store@npm:1.6.0"
peerDependencies: