aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-05-29 20:57:31 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-05-29 20:57:31 +0530
commitca3ae0db6e8e3f2cf99423797c60f5c2cc66a780 (patch)
treef1d5fa2174283a811a5422cf148a96f435b461a6
parenta8fc4438ff1b890a78b7d2ba470011d8f87c6043 (diff)
downloadblcklst-ca3ae0db6e8e3f2cf99423797c60f5c2cc66a780.tar.gz
blcklst-ca3ae0db6e8e3f2cf99423797c60f5c2cc66a780.tar.bz2
blcklst-ca3ae0db6e8e3f2cf99423797c60f5c2cc66a780.zip
feat: added the product page and enhanced the UI
- added the product page where the product details is shown - enhanced the nav bar to handle the rendering - added the loading screen to the page - fixed the rendering method to the navigation-menu
-rw-r--r--frontend/package-lock.json37
-rw-r--r--frontend/package.json1
-rw-r--r--frontend/src/app/globals.css686
-rw-r--r--frontend/src/app/layout.tsx18
-rw-r--r--frontend/src/app/loading.tsx54
-rw-r--r--frontend/src/app/men/page.tsx178
-rw-r--r--frontend/src/app/page.tsx117
-rw-r--r--frontend/src/app/products/[id]/page.tsx32
-rw-r--r--frontend/src/app/unisex/page.tsx178
-rw-r--r--frontend/src/app/women/page.tsx178
-rw-r--r--frontend/src/components/footer.tsx2
-rw-r--r--frontend/src/components/header.tsx667
-rw-r--r--frontend/src/components/loading.tsx111
-rw-r--r--frontend/src/components/product-card.tsx2
-rw-r--r--frontend/src/components/product-page.tsx567
-rw-r--r--frontend/src/components/theme-provider.tsx2
-rw-r--r--frontend/src/components/theme-toggle.tsx23
-rw-r--r--frontend/src/components/ui/navigation-menu.tsx19
-rw-r--r--frontend/src/components/ui/tabs.tsx55
19 files changed, 1821 insertions, 1106 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index c0135a9..33db14d 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -19,6 +19,7 @@
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tabs": "^1.1.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
@@ -1696,6 +1697,36 @@
}
}
},
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
+ "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "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
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@@ -2230,7 +2261,7 @@
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@@ -2240,7 +2271,7 @@
"version": "19.1.5",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
"integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"
@@ -3314,7 +3345,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
diff --git a/frontend/package.json b/frontend/package.json
index 72820a5..595afee 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -20,6 +20,7 @@
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tabs": "^1.1.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index 5e91d83..8b5bbb3 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -3,13 +3,10 @@
@custom-variant dark (&:is(.dark *));
-/* Custom responsive breakpoints */
-@custom-variant xs (&:is(@media (min-width: 475px) *));
-
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
- --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-mono: ui-monospace, "SFMono-Regular", "Consolas", monospace;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
@@ -124,698 +121,137 @@
}
}
-/* Complete override for ghost variant default styles */
-/* This targets the specific ghost variant classes from the button component */
+/* Simple transparent navigation buttons */
.nav-button-transparent {
background: transparent !important;
background-color: transparent !important;
- background-image: none !important;
border: none !important;
- border-color: transparent !important;
box-shadow: none !important;
- --tw-bg-opacity: 0 !important;
- --accent: transparent !important;
- --background: transparent !important;
- --tw-border-opacity: 0 !important;
- --tw-shadow: none !important;
- --tw-ring-shadow: none !important;
- --tw-ring-offset-shadow: none !important;
- cursor: pointer !important;
+ backdrop-filter: none !important;
+ border-radius: 0 !important;
+ transition: color 0.2s ease !important;
}
-/* Force override ghost variant styles completely */
-button[class*="nav-button-transparent"],
-[data-slot="button"][class*="nav-button-transparent"],
-[data-slot="navigation-menu-trigger"][class*="nav-button-transparent"] {
+/* Remove all hover backgrounds */
+.nav-button-transparent:hover {
background: transparent !important;
background-color: transparent !important;
- border: none !important;
box-shadow: none !important;
-}
-
-/* Override ghost variant hover specifically */
-button[class*="nav-button-transparent"][class*="hover:bg-accent"],
-[data-slot="button"][class*="nav-button-transparent"][class*="hover:bg-accent"],
-[data-slot="navigation-menu-trigger"][class*="nav-button-transparent"][class*="hover:bg-accent"] {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
-}
-
-/* Dark mode ghost variant overrides */
-.dark button[class*="nav-button-transparent"][class*="dark:hover:bg-accent"],
-.dark [data-slot="button"][class*="nav-button-transparent"][class*="dark:hover:bg-accent"],
-.dark [data-slot="navigation-menu-trigger"][class*="nav-button-transparent"][class*="dark:hover:bg-accent"] {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
-}
-
-/* Remove focus rings and outlines for nav buttons */
-.nav-button-transparent:focus,
-.nav-button-transparent:focus-visible {
- outline: none !important;
- ring: none !important;
- --tw-ring-shadow: none !important;
- border: none !important;
- background: transparent !important;
- background-color: transparent !important;
-}
-
-/* Light mode subtle hover effects - only show on hover */
-.nav-button-transparent:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- --tw-bg-opacity: 1 !important;
+ backdrop-filter: none !important;
border: none !important;
- transition: background-color 0.2s ease-in-out !important;
}
-/* Dark mode even more subtle hover effects */
+/* Remove all dark mode hover backgrounds */
.dark .nav-button-transparent:hover {
- background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
- transition: background-color 0.2s ease-in-out !important;
-}
-
-/* Override NavigationMenuTrigger specific classes with maximum specificity */
-.nav-button-transparent.bg-background {
background: transparent !important;
background-color: transparent !important;
- border: none !important;
-}
-
-/* Focus states - only subtle background, no borders or rings */
-.nav-button-transparent:focus:not(:hover) {
- background: rgba(0, 0, 0, 0.04) !important;
- background-color: rgba(0, 0, 0, 0.04) !important;
- border: none !important;
- box-shadow: none !important;
-}
-
-.dark .nav-button-transparent:focus:not(:hover) {
- background: rgba(255, 255, 255, 0.03) !important;
- background-color: rgba(255, 255, 255, 0.03) !important;
- border: none !important;
box-shadow: none !important;
-}
-
-/* Override open states */
-.nav-button-transparent[data-state="open"] {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
-}
-
-.dark .nav-button-transparent[data-state="open"] {
- background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
+ backdrop-filter: none !important;
border: none !important;
}
-/* Ensure all shadcn/ui utility classes are overridden with high specificity */
-button.nav-button-transparent,
-[data-slot="button"].nav-button-transparent,
-[data-slot="navigation-menu-trigger"].nav-button-transparent {
+/* Remove all focus backgrounds */
+.nav-button-transparent:focus {
background: transparent !important;
background-color: transparent !important;
- border: none !important;
- border-color: transparent !important;
- box-shadow: none !important;
- cursor: pointer !important;
-}
-
-/* Hover states for all button types */
-button.nav-button-transparent:hover,
-[data-slot="button"].nav-button-transparent:hover,
-[data-slot="navigation-menu-trigger"].nav-button-transparent:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
- transition: background-color 0.2s ease-in-out !important;
- cursor: pointer !important;
-}
-
-/* Focus states for all button types */
-button.nav-button-transparent:focus,
-[data-slot="button"].nav-button-transparent:focus,
-[data-slot="navigation-menu-trigger"].nav-button-transparent:focus {
- background: rgba(0, 0, 0, 0.04) !important;
- background-color: rgba(0, 0, 0, 0.04) !important;
- border: none !important;
- box-shadow: none !important;
outline: none !important;
- cursor: pointer !important;
-}
-
-/* Dark mode hover and focus states */
-.dark button.nav-button-transparent:hover,
-.dark [data-slot="button"].nav-button-transparent:hover,
-.dark [data-slot="navigation-menu-trigger"].nav-button-transparent:hover {
- background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
- border: none !important;
- transition: background-color 0.2s ease-in-out !important;
- cursor: pointer !important;
-}
-
-.dark button.nav-button-transparent:focus,
-.dark [data-slot="button"].nav-button-transparent:focus,
-.dark [data-slot="navigation-menu-trigger"].nav-button-transparent:focus {
- background: rgba(255, 255, 255, 0.03) !important;
- background-color: rgba(255, 255, 255, 0.03) !important;
- border: none !important;
box-shadow: none !important;
- outline: none !important;
- cursor: pointer !important;
-}
-
-/* Dropdown and navigation item transparency */
-.nav-dropdown-transparent {
- background: rgba(255, 255, 255, 0.85) !important;
- backdrop-filter: blur(20px) saturate(180%) !important;
- border: 1px solid rgba(255, 255, 255, 0.2) !important;
- box-shadow:
- 0 8px 32px rgba(0, 0, 0, 0.12),
- 0 2px 16px rgba(0, 0, 0, 0.08),
- inset 0 1px 0 rgba(255, 255, 255, 0.4) !important;
- border-radius: 0.75rem !important;
-}
-
-.dark .nav-dropdown-transparent {
- background: rgba(15, 15, 15, 0.85) !important;
- border: 1px solid rgba(82, 82, 82, 0.3) !important;
- box-shadow:
- 0 8px 32px rgba(0, 0, 0, 0.4),
- 0 2px 16px rgba(0, 0, 0, 0.3),
- inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
-}
-
-.nav-dropdown-item {
- background: transparent !important;
- border: none !important;
- transition: background-color 0.2s ease-in-out !important;
-}
-
-.nav-dropdown-item:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
-}
-
-.dark .nav-dropdown-item:hover {
- background: rgba(255, 255, 255, 0.08) !important;
- border: none !important;
-}
-
-/* Additional overrides for complete transparency */
-/* Handle any remaining shadcn/ui styling conflicts */
-.nav-button-transparent[data-variant="ghost"] {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
- cursor: pointer !important;
-}
-
-.nav-button-transparent[data-variant="ghost"]:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
- cursor: pointer !important;
-}
-
-.dark .nav-button-transparent[data-variant="ghost"]:hover {
- background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
- border: none !important;
- cursor: pointer !important;
-}
-
-/* Override any remaining background utilities */
-.nav-button-transparent.bg-transparent,
-.nav-button-transparent.bg-accent,
-.nav-button-transparent.hover\:bg-accent,
-.nav-button-transparent.focus\:bg-accent {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
- cursor: pointer !important;
-}
-
-/* Ensure link elements in navigation also have transparent styling */
-a.nav-button-transparent {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
- text-decoration: none !important;
- cursor: pointer !important;
-}
-
-a.nav-button-transparent:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
- text-decoration: none !important;
- cursor: pointer !important;
-}
-
-.dark a.nav-button-transparent:hover {
- background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
- border: none !important;
- text-decoration: none !important;
- cursor: pointer !important;
-}
-
-/* Ultra-specific overrides to ensure transparency takes precedence */
-/* Handle all possible combinations of button classes */
-button.nav-button-transparent.hover\:bg-accent,
-button.nav-button-transparent.dark\:hover\:bg-accent\/50,
-button.nav-button-transparent[class*="hover:bg"],
-[data-slot="button"].nav-button-transparent.hover\:bg-accent,
-[data-slot="button"].nav-button-transparent.dark\:hover\:bg-accent\/50,
-[data-slot="button"].nav-button-transparent[class*="hover:bg"],
-[data-slot="navigation-menu-trigger"].nav-button-transparent.hover\:bg-accent,
-[data-slot="navigation-menu-trigger"].nav-button-transparent.dark\:hover\:bg-accent\/50,
-[data-slot="navigation-menu-trigger"].nav-button-transparent[class*="hover:bg"] {
- background: transparent !important;
- background-color: transparent !important;
+ backdrop-filter: none !important;
border: none !important;
}
-/* Override any CSS custom properties that might affect background */
-.nav-button-transparent {
- --tw-bg-accent: transparent !important;
- --tw-bg-background: transparent !important;
- --tw-bg-muted: transparent !important;
- --tw-bg-secondary: transparent !important;
-}
-
-/* Ensure no background on any state for nav buttons */
-.nav-button-transparent,
-.nav-button-transparent:not(:hover):not(:focus):not(:active) {
+.dark .nav-button-transparent:focus {
background: transparent !important;
background-color: transparent !important;
box-shadow: none !important;
+ backdrop-filter: none !important;
border: none !important;
}
-/* Maximum specificity overrides - nuclear option for transparency */
-html body header button.nav-button-transparent,
-html body header [data-slot="button"].nav-button-transparent,
-html body header [data-slot="navigation-menu-trigger"].nav-button-transparent,
-html body header div button.nav-button-transparent,
-html body header div [data-slot="button"].nav-button-transparent,
-html body header div [data-slot="navigation-menu-trigger"].nav-button-transparent {
+/* Remove any active state backgrounds */
+.nav-button-transparent:active {
background: transparent !important;
background-color: transparent !important;
- background-image: none !important;
- border: none !important;
- border-color: transparent !important;
box-shadow: none !important;
backdrop-filter: none !important;
-}
-
-/* Override any inline styles or computed styles */
-.nav-button-transparent[style*="background"] {
- background: transparent !important;
- background-color: transparent !important;
-}
-
-/* Target specific button classes that might have background */
-.nav-button-transparent.inline-flex,
-.nav-button-transparent[class*="inline-flex"] {
- background: transparent !important;
- background-color: transparent !important;
border: none !important;
}
-/* Enhanced link element transparency for Sale button and similar nav links */
-/* Apply maximum specificity for link elements in navigation */
-html body header a.nav-button-transparent,
-html body header div a.nav-button-transparent,
-html body header nav a.nav-button-transparent,
-[data-slot="navigation-menu-link"].nav-button-transparent {
+.dark .nav-button-transparent:active {
background: transparent !important;
background-color: transparent !important;
- background-image: none !important;
- border: none !important;
- border-color: transparent !important;
box-shadow: none !important;
backdrop-filter: none !important;
- cursor: pointer !important;
-}
-
-/* Override any NavigationMenuLink default styles */
-a.nav-button-transparent[data-slot="navigation-menu-link"],
-[data-slot="navigation-menu-link"].nav-button-transparent {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
- box-shadow: none !important;
-}
-
-/* Ensure link hover states maintain transparency in default state */
-a.nav-button-transparent:not(:hover):not(:focus):not(:active) {
- background: transparent !important;
- background-color: transparent !important;
border: none !important;
}
-/* Specific overrides for NavigationMenuLink component (Sale button) */
-[data-slot="navigation-menu-link"].nav-button-transparent {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
- box-shadow: none !important;
- cursor: pointer !important;
+/* Navigation dropdowns */
+.nav-dropdown-transparent {
+ background: rgba(255, 255, 255, 0.95) !important;
+ backdrop-filter: blur(20px) !important;
+ border: 1px solid rgba(255, 255, 255, 0.3) !important;
+ border-radius: 0.75rem !important;
}
-/* Override NavigationMenuLink hover and focus states */
-[data-slot="navigation-menu-link"].nav-button-transparent.hover\:bg-accent,
-[data-slot="navigation-menu-link"].nav-button-transparent.focus\:bg-accent,
-[data-slot="navigation-menu-link"].nav-button-transparent[class*="hover:bg-accent"],
-[data-slot="navigation-menu-link"].nav-button-transparent[class*="focus:bg-accent"],
-[data-slot="navigation-menu-link"].nav-button-transparent[class*="data-[active=true]:bg-accent"] {
- background: transparent !important;
- background-color: transparent !important;
- border: none !important;
+.dark .nav-dropdown-transparent {
+ background: rgba(23, 23, 23, 0.95) !important;
+ border: 1px solid rgba(82, 82, 82, 0.4) !important;
}
-/* Override NavigationMenuLink data states */
-[data-slot="navigation-menu-link"].nav-button-transparent[data-active="true"] {
+/* Dropdown items */
+.nav-dropdown-item {
background: transparent !important;
- background-color: transparent !important;
border: none !important;
+ transition: all 0.2s ease !important;
}
-/* Ensure NavigationMenuLink has proper hover effect */
-[data-slot="navigation-menu-link"].nav-button-transparent:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
- transition: background-color 0.2s ease-in-out !important;
-}
-
-.dark [data-slot="navigation-menu-link"].nav-button-transparent:hover {
- background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
-}
-
-/* Enhanced Theme Toggle Transparency Effects */
-/* Theme toggle specific enhancements for better navbar integration */
-.nav-button-transparent.group {
- position: relative;
- overflow: hidden;
- backdrop-filter: blur(8px) saturate(150%);
- border-radius: 0.5rem;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-/* Enhanced hover state for theme toggle */
-.nav-button-transparent.group:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- transform: scale(1.05);
- backdrop-filter: blur(12px) saturate(180%);
+.nav-dropdown-item:hover {
+ background: rgba(0, 0, 0, 0.06) !important;
}
-.dark .nav-button-transparent.group:hover {
+.dark .nav-dropdown-item:hover {
background: rgba(255, 255, 255, 0.06) !important;
- background-color: rgba(255, 255, 255, 0.06) !important;
-}
-
-/* Active state for theme toggle */
-.nav-button-transparent.group:active {
- transform: scale(0.95);
- transition: transform 0.1s ease-in-out;
-}
-
-/* Enhanced focus state for theme toggle */
-.nav-button-transparent.group:focus-visible {
- background: rgba(0, 0, 0, 0.04) !important;
- background-color: rgba(0, 0, 0, 0.04) !important;
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1) !important;
- outline: none !important;
-}
-
-.dark .nav-button-transparent.group:focus-visible {
- background: rgba(255, 255, 255, 0.03) !important;
- background-color: rgba(255, 255, 255, 0.03) !important;
- box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1) !important;
-}
-
-
-/* Enhanced dropdown items for theme toggle */
-.nav-dropdown-item.group\/item {
- background: transparent !important;
- border: none !important;
- border-radius: 0.5rem !important;
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
- margin: 2px !important;
- padding: 0.5rem 0.75rem !important;
-}
-
-.nav-dropdown-item.group\/item:hover {
- background: rgba(0, 0, 0, 0.08) !important;
- backdrop-filter: blur(8px) !important;
- transform: translateX(2px) !important;
-}
-
-.dark .nav-dropdown-item.group\/item:hover {
- background: rgba(255, 255, 255, 0.08) !important;
-}
-
-/* Smooth icon transitions for theme toggle */
-.nav-button-transparent.group svg {
- transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
- filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
-}
-
-.dark .nav-button-transparent.group svg {
- filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
-}
-
-/* Enhanced glow effect for theme toggle */
-.nav-button-transparent.group:hover svg {
- filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15));
-}
-
-.dark .nav-button-transparent.group:hover svg {
- filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4));
-}
-
-/* Ultra-smooth dropdown animations */
-@keyframes dropdown-in {
- from {
- opacity: 0;
- transform: scale(0.95) translateY(-4px);
- }
- to {
- opacity: 1;
- transform: scale(1) translateY(0);
- }
-}
-
-@keyframes dropdown-out {
- from {
- opacity: 1;
- transform: scale(1) translateY(0);
- }
- to {
- opacity: 0;
- transform: scale(0.95) translateY(-4px);
- }
-}
-
-/* Apply dropdown animation to nav dropdowns */
-.nav-dropdown-transparent[data-state="open"] {
- animation: dropdown-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-/* Improved checkmark animation */
-.nav-dropdown-item span[class*="animate-in"] {
- animation: checkmark-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-@keyframes checkmark-in {
- from {
- opacity: 0;
- transform: scale(0.3) rotate(-12deg);
- }
- to {
- opacity: 1;
- transform: scale(1) rotate(0deg);
- }
}
-/* Prevent any conflicting backgrounds on theme toggle */
-.nav-button-transparent.group[data-state="open"] {
- background: rgba(0, 0, 0, 0.08) !important;
- background-color: rgba(0, 0, 0, 0.08) !important;
- border: none !important;
- transform: scale(1.05);
+/* Prevent flash of unstyled content */
+html {
+ visibility: hidden;
+ opacity: 0;
}
-.dark .nav-button-transparent.group[data-state="open"] {
- background: rgba(255, 255, 255, 0.08) !important;
- backdrop-filter: blur(8px) !important;
- border-radius: 0.5rem !important;
+html.loaded {
+ visibility: visible;
+ opacity: 1;
+ transition: opacity 0.1s ease-in-out;
}
-/* Mobile-friendly enhancements */
-@media (max-width: 768px) {
- /* Ensure touch targets are at least 44px */
- .touch-manipulation {
- touch-action: manipulation;
- -webkit-tap-highlight-color: transparent;
- }
-
- /* Better mobile spacing for action buttons */
- .mobile-action-buttons {
- gap: 0.25rem !important;
- }
-
- /* Mobile menu improvements */
- .mobile-menu-item {
- min-height: 44px;
- padding: 0.75rem 1rem;
- display: flex;
- align-items: center;
- justify-content: space-between;
- border-radius: 0.5rem;
- transition: all 0.2s ease-in-out;
- }
-
- .mobile-menu-item:active {
- transform: scale(0.98);
- background: rgba(0, 0, 0, 0.05);
- }
-
- .dark .mobile-menu-item:active {
- background: rgba(255, 255, 255, 0.05);
- }
-
- /* Prevent text selection on mobile UI elements */
- .no-select {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
-
- /* Improve mobile sheet content scrolling */
- .mobile-sheet-content {
- overscroll-behavior: contain;
- -webkit-overflow-scrolling: touch;
- }
-
- /* Hide scrollbar in mobile menu while maintaining scroll functionality */
- [data-radix-dialog-content] {
- scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* Internet Explorer and Edge */
- }
-
- [data-radix-dialog-content]::-webkit-scrollbar {
- display: none; /* Chrome, Safari, Opera */
- width: 0;
- height: 0;
- }
-
- /* Specifically target Sheet content scrolling areas */
- [data-radix-dialog-content] .overflow-y-auto,
- [data-radix-dialog-content] .overflow-auto {
- scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* Internet Explorer and Edge */
- }
-
- [data-radix-dialog-content] .overflow-y-auto::-webkit-scrollbar,
- [data-radix-dialog-content] .overflow-auto::-webkit-scrollbar {
- display: none; /* Chrome, Safari, Opera */
- width: 0;
- height: 0;
- }
-
- /* Mobile navigation specific scrollbar hiding */
- .mobile-nav-scroll {
- scrollbar-width: none !important; /* Firefox */
- -ms-overflow-style: none !important; /* Internet Explorer and Edge */
- }
-
- .mobile-nav-scroll::-webkit-scrollbar {
- display: none !important; /* Chrome, Safari, Opera */
- width: 0 !important;
- height: 0 !important;
- background: transparent !important;
- }
-
- /* Additional scrollbar hiding for all mobile menu elements */
- [data-radix-dialog-content] *,
- [data-radix-dialog-content] *::-webkit-scrollbar {
- scrollbar-width: none !important;
- -ms-overflow-style: none !important;
- display: none !important;
- width: 0 !important;
- height: 0 !important;
- }
+/* Ensure smooth theme transitions */
+* {
+ transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out, border-color 0.15s ease-in-out;
}
-/* Small mobile optimizations */
-@media (max-width: 475px) {
- /* Tighter spacing on very small screens */
- .action-buttons-compact {
- gap: 0.125rem !important;
- }
-
- /* Slightly smaller touch targets for very small screens */
- .compact-touch-target {
- min-width: 40px !important;
- min-height: 40px !important;
- }
-
- /* Reduce header height on very small screens */
- .compact-header {
- height: 3.5rem !important; /* 56px */
- }
+/* Prevent layout shifts during hydration */
+img[priority] {
+ content-visibility: auto;
}
-/* Accessibility improvements */
-@media (prefers-reduced-motion: reduce) {
- .nav-button-transparent,
- .nav-dropdown-item,
- .mobile-menu-item {
- transition: none !important;
- animation: none !important;
+/* Loading animation */
+@keyframes loading {
+ 0% {
+ width: 0%;
+ transform: translateX(-100%);
}
-}
-
-/* High contrast mode support */
-@media (prefers-contrast: high) {
- .nav-button-transparent {
- border: 1px solid currentColor !important;
+ 50% {
+ width: 100%;
+ transform: translateX(0%);
}
-
- .nav-dropdown-transparent {
- border: 1px solid currentColor !important;
- background: var(--background) !important;
+ 100% {
+ width: 0%;
+ transform: translateX(100%);
}
}
-/* Focus visible for keyboard navigation */
-.nav-button-transparent:focus-visible {
- outline: 2px solid var(--ring) !important;
- outline-offset: 2px !important;
-}
-
-/* Mobile menu icon stroke uniformity */
-.mobile-nav-scroll svg,
-[data-radix-dialog-content] svg {
- stroke-linecap: round !important;
- stroke-linejoin: round !important;
-}
-
-/* Force consistent stroke width for mobile quick action icons */
-@media (max-width: 768px) {
- [data-radix-dialog-content] button svg {
- stroke-width: inherit !important;
- }
+/* Ensure loading screen is on top */
+.loading-screen {
+ z-index: 9999;
}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index ffa571b..60f0601 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -35,6 +35,20 @@ export default function RootLayout({
as="image"
type="image/png"
/>
+ <script dangerouslySetInnerHTML={{
+ __html: `
+ (function() {
+ function addLoadedClass() {
+ document.documentElement.classList.add('loaded');
+ }
+ if (document.readyState === 'complete') {
+ addLoadedClass();
+ } else {
+ window.addEventListener('load', addLoadedClass);
+ }
+ })();
+ `
+ }} />
</head>
<body className={`${inter.variable} font-sans antialiased`}>
<ThemeProvider
@@ -42,8 +56,8 @@ export default function RootLayout({
defaultTheme="system"
enableSystem
disableTransitionOnChange
- >
- {children}
+ >
+ {children}
<Toaster />
</ThemeProvider>
</body>
diff --git a/frontend/src/app/loading.tsx b/frontend/src/app/loading.tsx
new file mode 100644
index 0000000..56455c3
--- /dev/null
+++ b/frontend/src/app/loading.tsx
@@ -0,0 +1,54 @@
+import Image from "next/image";
+
+export default function Loading() {
+ return (
+ <div className="fixed inset-0 z-[9999] bg-background flex items-center justify-center">
+ {/* Background pattern */}
+ <div className="absolute inset-0 bg-gradient-to-br from-background via-background to-muted/20" />
+
+ {/* Loading content */}
+ <div className="relative z-10 flex flex-col items-center space-y-4">
+ {/* Logo with animation */}
+ <div className="relative">
+ <div className="animate-pulse">
+ <Image
+ src="/black-logo.png"
+ alt="blcklst"
+ width={160}
+ height={50}
+ className="h-12 w-auto block dark:hidden"
+ priority
+ />
+ <Image
+ src="/white-logo.png"
+ alt="blcklst"
+ width={160}
+ height={50}
+ className="h-12 w-auto hidden dark:block"
+ priority
+ />
+ </div>
+ </div>
+
+ {/* Loading text - closer to logo */}
+ <div className="text-center space-y-3 -mt-2">
+ <p className="text-sm text-muted-foreground animate-pulse">
+ not everyone gets blcklsted
+ </p>
+
+ {/* Loading dots */}
+ <div className="flex items-center justify-center space-x-1">
+ <div className="w-2 h-2 bg-foreground/60 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
+ <div className="w-2 h-2 bg-foreground/60 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
+ <div className="w-2 h-2 bg-foreground/60 rounded-full animate-bounce"></div>
+ </div>
+ </div>
+
+ {/* Progress bar */}
+ <div className="w-64 h-1 bg-muted rounded-full overflow-hidden">
+ <div className="h-full bg-foreground rounded-full animate-[loading_1s_ease-in-out_infinite]"></div>
+ </div>
+ </div>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/frontend/src/app/men/page.tsx b/frontend/src/app/men/page.tsx
new file mode 100644
index 0000000..8c0dd51
--- /dev/null
+++ b/frontend/src/app/men/page.tsx
@@ -0,0 +1,178 @@
+import { Header } from "@/components/header";
+import { Footer } from "@/components/footer";
+import { ProductCard } from "@/components/product-card";
+
+export default function MenPage() {
+ // Mock men's products data
+ const menProducts = [
+ {
+ id: "1",
+ name: "Oversized Cotton Hoodie",
+ price: 89,
+ originalPrice: 129,
+ image: "/api/placeholder/400/500",
+ images: ["/api/placeholder/400/500", "/api/placeholder/400/500"],
+ rating: 4.5,
+ reviewCount: 234,
+ isNew: true,
+ isSale: true,
+ category: "Hoodies",
+ colors: ["#000000", "#FFFFFF", "#6B7280"],
+ sizes: ["S", "M", "L", "XL"],
+ },
+ {
+ id: "2",
+ name: "Classic Denim Jacket",
+ price: 159,
+ originalPrice: 199,
+ image: "/api/placeholder/400/500",
+ rating: 4.7,
+ reviewCount: 189,
+ isSale: true,
+ category: "Jackets",
+ colors: ["#1E40AF", "#000000"],
+ sizes: ["M", "L", "XL"],
+ },
+ {
+ id: "3",
+ name: "Minimal White T-Shirt",
+ price: 45,
+ image: "/api/placeholder/400/500",
+ rating: 4.3,
+ reviewCount: 456,
+ isNew: true,
+ category: "T-Shirts",
+ colors: ["#FFFFFF", "#000000", "#6B7280"],
+ sizes: ["XS", "S", "M", "L", "XL"],
+ },
+ {
+ id: "4",
+ name: "Cargo Pants",
+ price: 119,
+ originalPrice: 149,
+ image: "/api/placeholder/400/500",
+ rating: 4.2,
+ reviewCount: 98,
+ isSale: true,
+ category: "Pants",
+ colors: ["#000000", "#4B5563", "#059669"],
+ sizes: ["28", "30", "32", "34", "36"],
+ },
+ {
+ id: "5",
+ name: "Oxford Button Shirt",
+ price: 79,
+ image: "/api/placeholder/400/500",
+ rating: 4.6,
+ reviewCount: 156,
+ category: "Shirts",
+ colors: ["#FFFFFF", "#3B82F6", "#6B7280"],
+ sizes: ["S", "M", "L", "XL"],
+ },
+ {
+ id: "6",
+ name: "Leather Sneakers",
+ price: 189,
+ originalPrice: 229,
+ image: "/api/placeholder/400/500",
+ rating: 4.8,
+ reviewCount: 267,
+ isSale: true,
+ category: "Shoes",
+ colors: ["#FFFFFF", "#000000", "#8B4513"],
+ sizes: ["8", "9", "10", "11", "12"],
+ },
+ {
+ id: "7",
+ name: "Wool Blazer",
+ price: 299,
+ image: "/api/placeholder/400/500",
+ rating: 4.9,
+ reviewCount: 78,
+ isNew: true,
+ category: "Blazers",
+ colors: ["#000000", "#374151", "#1E40AF"],
+ sizes: ["S", "M", "L", "XL"],
+ },
+ {
+ id: "8",
+ name: "Casual Chinos",
+ price: 89,
+ originalPrice: 109,
+ image: "/api/placeholder/400/500",
+ rating: 4.4,
+ reviewCount: 234,
+ isSale: true,
+ category: "Pants",
+ colors: ["#D2B48C", "#000000", "#1F2937"],
+ sizes: ["28", "30", "32", "34", "36"],
+ },
+ ];
+
+ return (
+ <>
+ <Header />
+ <main className="min-h-screen bg-background">
+ {/* Hero Section */}
+ <section className="relative h-96 bg-gradient-to-r from-neutral-900 to-neutral-700 flex items-center justify-center">
+ <div className="text-center text-white space-y-4">
+ <h1 className="text-4xl md:text-6xl font-bold">Men's Collection</h1>
+ <p className="text-lg md:text-xl max-w-2xl mx-auto px-4">
+ Discover our latest men's fashion collection featuring premium quality pieces for the modern man
+ </p>
+ </div>
+ </section>
+
+ {/* Filter Section */}
+ <section className="border-b dark:border-neutral-800 py-6">
+ <div className="container mx-auto px-4">
+ <div className="flex flex-wrap items-center justify-between gap-4">
+ <div className="flex items-center space-x-4">
+ <span className="text-sm text-muted-foreground">
+ Showing {menProducts.length} products
+ </span>
+ </div>
+ <div className="flex items-center space-x-4">
+ <select className="px-3 py-2 border rounded-lg text-sm bg-background">
+ <option>Sort by: Featured</option>
+ <option>Price: Low to High</option>
+ <option>Price: High to Low</option>
+ <option>Newest First</option>
+ <option>Best Rating</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ {/* Products Grid */}
+ <section className="py-12">
+ <div className="container mx-auto px-4">
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
+ {menProducts.map((product) => (
+ <ProductCard key={product.id} {...product} />
+ ))}
+ </div>
+ </div>
+ </section>
+
+ {/* Load More Section */}
+ <section className="py-12 border-t dark:border-neutral-800">
+ <div className="container mx-auto px-4 text-center">
+ <button className="px-8 py-3 border border-primary text-primary hover:bg-primary hover:text-primary-foreground transition-colors rounded-lg">
+ Load More Products
+ </button>
+ </div>
+ </section>
+ </main>
+ <Footer />
+ </>
+ );
+}
+
+export async function generateMetadata() {
+ return {
+ title: "Men's Fashion Collection | blcklst",
+ description: "Shop the latest men's fashion at blcklst. Premium quality clothing, shoes, and accessories for the modern man.",
+ };
+} \ No newline at end of file
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 7d6b7a2..dce3b54 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -2,6 +2,7 @@ import { Header } from "@/components/header";
import { HeroSection } from "@/components/hero-section";
import { ProductCard } from "@/components/product-card";
import { Footer } from "@/components/footer";
+import { PageWrapper } from "@/components/loading";
export default function Home() {
// Sample product data for demonstration
@@ -86,67 +87,69 @@ export default function Home() {
];
return (
- <div className="min-h-screen bg-background">
- <Header />
-
- <main>
- <HeroSection />
+ <PageWrapper>
+ <div className="min-h-screen bg-background" suppressHydrationWarning>
+ <Header />
- {/* Featured Products Section */}
- <section className="py-16 bg-white dark:bg-neutral-950">
- <div className="container mx-auto px-4">
- <div className="text-center mb-12">
- <h2 className="text-3xl font-bold mb-4 text-foreground">Featured Products</h2>
- <p className="text-muted-foreground max-w-2xl mx-auto">
- Discover our handpicked selection of trending items that our customers love most.
- </p>
+ <main>
+ <HeroSection />
+
+ {/* Featured Products Section */}
+ <section className="py-16 bg-white dark:bg-neutral-950">
+ <div className="container mx-auto px-4">
+ <div className="text-center mb-12">
+ <h2 className="text-3xl font-bold mb-4 text-foreground">Featured Products</h2>
+ <p className="text-muted-foreground max-w-2xl mx-auto">
+ Discover our handpicked selection of trending items that our customers love most.
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
+ {featuredProducts.map((product) => (
+ <ProductCard key={product.id} {...product} />
+ ))}
+ </div>
+
+ <div className="text-center mt-12">
+ <button className="bg-primary text-primary-foreground px-8 py-3 rounded-lg font-medium hover:bg-primary/90 transition-colors">
+ View All Products
+ </button>
+ </div>
</div>
-
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
- {featuredProducts.map((product) => (
- <ProductCard key={product.id} {...product} />
- ))}
- </div>
-
- <div className="text-center mt-12">
- <button className="bg-primary text-primary-foreground px-8 py-3 rounded-lg font-medium hover:bg-primary/90 transition-colors">
- View All Products
- </button>
- </div>
- </div>
- </section>
+ </section>
- {/* Categories Section */}
- <section className="py-16 bg-neutral-50 dark:bg-neutral-900">
- <div className="container mx-auto px-4">
- <div className="text-center mb-12">
- <h2 className="text-3xl font-bold mb-4">Shop by Category</h2>
- <p className="text-muted-foreground">
- Explore our diverse range of fashion categories
- </p>
- </div>
-
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
- {[
- { name: "Women", image: "/api/placeholder/600/400", count: "2,345+ items" },
- { name: "Men", image: "/api/placeholder/600/400", count: "1,892+ items" },
- { name: "Kids", image: "/api/placeholder/600/400", count: "956+ items" },
- ].map((category) => (
- <div key={category.name} className="group relative overflow-hidden rounded-2xl bg-neutral-100 dark:bg-neutral-800 aspect-[4/3]">
- <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
- <div className="absolute bottom-6 left-6 text-white">
- <h3 className="text-2xl font-bold mb-1">{category.name}</h3>
- <p className="text-neutral-200">{category.count}</p>
+ {/* Categories Section */}
+ <section className="py-16 bg-neutral-50 dark:bg-neutral-900">
+ <div className="container mx-auto px-4">
+ <div className="text-center mb-12">
+ <h2 className="text-3xl font-bold mb-4">Shop by Category</h2>
+ <p className="text-muted-foreground">
+ Explore our diverse range of fashion categories
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
+ {[
+ { name: "Women", image: "/api/placeholder/600/400", count: "2,345+ items" },
+ { name: "Men", image: "/api/placeholder/600/400", count: "1,892+ items" },
+ { name: "Kids", image: "/api/placeholder/600/400", count: "956+ items" },
+ ].map((category) => (
+ <div key={category.name} className="group relative overflow-hidden rounded-2xl bg-neutral-100 dark:bg-neutral-800 aspect-[4/3]">
+ <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
+ <div className="absolute bottom-6 left-6 text-white">
+ <h3 className="text-2xl font-bold mb-1">{category.name}</h3>
+ <p className="text-neutral-200">{category.count}</p>
+ </div>
+ <div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
</div>
- <div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
- </div>
- ))}
+ ))}
+ </div>
</div>
- </div>
- </section>
- </main>
-
- <Footer />
- </div>
+ </section>
+ </main>
+
+ <Footer />
+ </div>
+ </PageWrapper>
);
}
diff --git a/frontend/src/app/products/[id]/page.tsx b/frontend/src/app/products/[id]/page.tsx
new file mode 100644
index 0000000..31ceded
--- /dev/null
+++ b/frontend/src/app/products/[id]/page.tsx
@@ -0,0 +1,32 @@
+import { Header } from "@/components/header";
+import { Footer } from "@/components/footer";
+import { ProductPage } from "@/components/product-page";
+
+interface ProductPageProps {
+ params: Promise<{
+ id: string;
+ }>;
+}
+
+export default async function Product({ params }: ProductPageProps) {
+ const { id } = await params;
+
+ return (
+ <>
+ <Header />
+ <ProductPage productId={id} />
+ <Footer />
+ </>
+ );
+}
+
+export async function generateMetadata({ params }: ProductPageProps) {
+ // In a real app, you'd fetch the product data here based on params.id
+ const { id } = await params;
+ const productName = id === "1" ? "Oversized Cotton Hoodie" : "Product";
+
+ return {
+ title: `${productName} | blcklst`,
+ description: `Shop the ${productName} at blcklst. Premium quality fashion pieces that define modern elegance.`,
+ };
+} \ No newline at end of file
diff --git a/frontend/src/app/unisex/page.tsx b/frontend/src/app/unisex/page.tsx
new file mode 100644
index 0000000..3c7c0d1
--- /dev/null
+++ b/frontend/src/app/unisex/page.tsx
@@ -0,0 +1,178 @@
+import { Header } from "@/components/header";
+import { Footer } from "@/components/footer";
+import { ProductCard } from "@/components/product-card";
+
+export default function UnisexPage() {
+ // Mock unisex products data
+ const unisexProducts = [
+ {
+ id: "17",
+ name: "Organic Cotton Hoodie",
+ price: 89,
+ originalPrice: 119,
+ image: "/api/placeholder/400/500",
+ images: ["/api/placeholder/400/500", "/api/placeholder/400/500"],
+ rating: 4.7,
+ reviewCount: 342,
+ isNew: true,
+ isSale: true,
+ category: "Hoodies",
+ colors: ["#000000", "#FFFFFF", "#6B7280", "#059669"],
+ sizes: ["XS", "S", "M", "L", "XL", "XXL"],
+ },
+ {
+ id: "18",
+ name: "Minimalist T-Shirt",
+ price: 39,
+ originalPrice: 49,
+ image: "/api/placeholder/400/500",
+ rating: 4.5,
+ reviewCount: 567,
+ isSale: true,
+ category: "T-Shirts",
+ colors: ["#FFFFFF", "#000000", "#6B7280", "#1E40AF"],
+ sizes: ["XS", "S", "M", "L", "XL", "XXL"],
+ },
+ {
+ id: "19",
+ name: "Relaxed Fit Jeans",
+ price: 119,
+ image: "/api/placeholder/400/500",
+ rating: 4.6,
+ reviewCount: 234,
+ isNew: true,
+ category: "Jeans",
+ colors: ["#4169E1", "#000000", "#6B7280"],
+ sizes: ["26", "28", "30", "32", "34", "36"],
+ },
+ {
+ id: "20",
+ name: "Oversized Sweatshirt",
+ price: 79,
+ originalPrice: 99,
+ image: "/api/placeholder/400/500",
+ rating: 4.8,
+ reviewCount: 189,
+ isSale: true,
+ category: "Sweatshirts",
+ colors: ["#F5F5DC", "#000000", "#6B7280", "#059669"],
+ sizes: ["XS", "S", "M", "L", "XL", "XXL"],
+ },
+ {
+ id: "21",
+ name: "Canvas Backpack",
+ price: 89,
+ image: "/api/placeholder/400/500",
+ rating: 4.4,
+ reviewCount: 156,
+ category: "Accessories",
+ colors: ["#000000", "#8B4513", "#6B7280", "#FFFFFF"],
+ sizes: ["One Size"],
+ },
+ {
+ id: "22",
+ name: "Cargo Joggers",
+ price: 99,
+ originalPrice: 129,
+ image: "/api/placeholder/400/500",
+ rating: 4.3,
+ reviewCount: 278,
+ isSale: true,
+ category: "Pants",
+ colors: ["#000000", "#6B7280", "#059669", "#8B4513"],
+ sizes: ["XS", "S", "M", "L", "XL", "XXL"],
+ },
+ {
+ id: "23",
+ name: "Bomber Jacket",
+ price: 159,
+ image: "/api/placeholder/400/500",
+ rating: 4.9,
+ reviewCount: 123,
+ isNew: true,
+ category: "Outerwear",
+ colors: ["#000000", "#1E40AF", "#6B7280", "#8B4513"],
+ sizes: ["XS", "S", "M", "L", "XL", "XXL"],
+ },
+ {
+ id: "24",
+ name: "Classic Sneakers",
+ price: 129,
+ originalPrice: 159,
+ image: "/api/placeholder/400/500",
+ rating: 4.6,
+ reviewCount: 445,
+ isSale: true,
+ category: "Shoes",
+ colors: ["#FFFFFF", "#000000", "#6B7280"],
+ sizes: ["36", "37", "38", "39", "40", "41", "42", "43", "44"],
+ },
+ ];
+
+ return (
+ <>
+ <Header />
+ <main className="min-h-screen bg-background">
+ {/* Hero Section */}
+ <section className="relative h-96 bg-gradient-to-r from-neutral-100 to-neutral-200 dark:from-neutral-800 dark:to-neutral-900 flex items-center justify-center">
+ <div className="text-center space-y-4">
+ <h1 className="text-4xl md:text-6xl font-bold text-foreground">Unisex Collection</h1>
+ <p className="text-lg md:text-xl max-w-2xl mx-auto px-4 text-muted-foreground">
+ Gender-neutral fashion for everyone. Discover versatile pieces designed for all body types and styles
+ </p>
+ </div>
+ </section>
+
+ {/* Filter Section */}
+ <section className="border-b dark:border-neutral-800 py-6">
+ <div className="container mx-auto px-4">
+ <div className="flex flex-wrap items-center justify-between gap-4">
+ <div className="flex items-center space-x-4">
+ <span className="text-sm text-muted-foreground">
+ Showing {unisexProducts.length} products
+ </span>
+ </div>
+ <div className="flex items-center space-x-4">
+ <select className="px-3 py-2 border rounded-lg text-sm bg-background">
+ <option>Sort by: Featured</option>
+ <option>Price: Low to High</option>
+ <option>Price: High to Low</option>
+ <option>Newest First</option>
+ <option>Best Rating</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ {/* Products Grid */}
+ <section className="py-12">
+ <div className="container mx-auto px-4">
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
+ {unisexProducts.map((product) => (
+ <ProductCard key={product.id} {...product} />
+ ))}
+ </div>
+ </div>
+ </section>
+
+ {/* Load More Section */}
+ <section className="py-12 border-t dark:border-neutral-800">
+ <div className="container mx-auto px-4 text-center">
+ <button className="px-8 py-3 border border-primary text-primary hover:bg-primary hover:text-primary-foreground transition-colors rounded-lg">
+ Load More Products
+ </button>
+ </div>
+ </section>
+ </main>
+ <Footer />
+ </>
+ );
+}
+
+export async function generateMetadata() {
+ return {
+ title: "Unisex Fashion Collection | blcklst",
+ description: "Shop gender-neutral fashion at blcklst. Versatile, inclusive clothing designed for all body types and personal styles.",
+ };
+} \ No newline at end of file
diff --git a/frontend/src/app/women/page.tsx b/frontend/src/app/women/page.tsx
new file mode 100644
index 0000000..6814760
--- /dev/null
+++ b/frontend/src/app/women/page.tsx
@@ -0,0 +1,178 @@
+import { Header } from "@/components/header";
+import { Footer } from "@/components/footer";
+import { ProductCard } from "@/components/product-card";
+
+export default function WomenPage() {
+ // Mock women's products data
+ const womenProducts = [
+ {
+ id: "9",
+ name: "Floral Summer Dress",
+ price: 129,
+ originalPrice: 169,
+ image: "/api/placeholder/400/500",
+ images: ["/api/placeholder/400/500", "/api/placeholder/400/500"],
+ rating: 4.8,
+ reviewCount: 324,
+ isNew: true,
+ isSale: true,
+ category: "Dresses",
+ colors: ["#FFB6C1", "#FFFFFF", "#98FB98"],
+ sizes: ["XS", "S", "M", "L", "XL"],
+ },
+ {
+ id: "10",
+ name: "Silk Blouse",
+ price: 89,
+ originalPrice: 119,
+ image: "/api/placeholder/400/500",
+ rating: 4.6,
+ reviewCount: 198,
+ isSale: true,
+ category: "Tops",
+ colors: ["#FFFFFF", "#000000", "#FFB6C1"],
+ sizes: ["XS", "S", "M", "L"],
+ },
+ {
+ id: "11",
+ name: "High-Waist Jeans",
+ price: 109,
+ image: "/api/placeholder/400/500",
+ rating: 4.7,
+ reviewCount: 456,
+ isNew: true,
+ category: "Jeans",
+ colors: ["#4169E1", "#000000", "#FFFFFF"],
+ sizes: ["24", "26", "28", "30", "32"],
+ },
+ {
+ id: "12",
+ name: "Cashmere Sweater",
+ price: 199,
+ originalPrice: 249,
+ image: "/api/placeholder/400/500",
+ rating: 4.9,
+ reviewCount: 167,
+ isSale: true,
+ category: "Sweaters",
+ colors: ["#F5F5DC", "#FFB6C1", "#6B7280"],
+ sizes: ["XS", "S", "M", "L", "XL"],
+ },
+ {
+ id: "13",
+ name: "Leather Handbag",
+ price: 249,
+ image: "/api/placeholder/400/500",
+ rating: 4.5,
+ reviewCount: 89,
+ category: "Accessories",
+ colors: ["#000000", "#8B4513", "#FFFFFF"],
+ sizes: ["One Size"],
+ },
+ {
+ id: "14",
+ name: "Midi Skirt",
+ price: 79,
+ originalPrice: 99,
+ image: "/api/placeholder/400/500",
+ rating: 4.4,
+ reviewCount: 234,
+ isSale: true,
+ category: "Skirts",
+ colors: ["#000000", "#FFB6C1", "#6B7280"],
+ sizes: ["XS", "S", "M", "L", "XL"],
+ },
+ {
+ id: "15",
+ name: "Trench Coat",
+ price: 299,
+ image: "/api/placeholder/400/500",
+ rating: 4.8,
+ reviewCount: 156,
+ isNew: true,
+ category: "Outerwear",
+ colors: ["#F5F5DC", "#000000", "#8B4513"],
+ sizes: ["XS", "S", "M", "L", "XL"],
+ },
+ {
+ id: "16",
+ name: "Ankle Boots",
+ price: 179,
+ originalPrice: 219,
+ image: "/api/placeholder/400/500",
+ rating: 4.6,
+ reviewCount: 278,
+ isSale: true,
+ category: "Shoes",
+ colors: ["#000000", "#8B4513", "#6B7280"],
+ sizes: ["5", "6", "7", "8", "9", "10"],
+ },
+ ];
+
+ return (
+ <>
+ <Header />
+ <main className="min-h-screen bg-background">
+ {/* Hero Section */}
+ <section className="relative h-96 bg-gradient-to-r from-pink-100 to-purple-100 dark:from-pink-900/20 dark:to-purple-900/20 flex items-center justify-center">
+ <div className="text-center space-y-4">
+ <h1 className="text-4xl md:text-6xl font-bold text-foreground">Women's Collection</h1>
+ <p className="text-lg md:text-xl max-w-2xl mx-auto px-4 text-muted-foreground">
+ Embrace your style with our curated women's fashion collection featuring elegant and contemporary pieces
+ </p>
+ </div>
+ </section>
+
+ {/* Filter Section */}
+ <section className="border-b dark:border-neutral-800 py-6">
+ <div className="container mx-auto px-4">
+ <div className="flex flex-wrap items-center justify-between gap-4">
+ <div className="flex items-center space-x-4">
+ <span className="text-sm text-muted-foreground">
+ Showing {womenProducts.length} products
+ </span>
+ </div>
+ <div className="flex items-center space-x-4">
+ <select className="px-3 py-2 border rounded-lg text-sm bg-background">
+ <option>Sort by: Featured</option>
+ <option>Price: Low to High</option>
+ <option>Price: High to Low</option>
+ <option>Newest First</option>
+ <option>Best Rating</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ {/* Products Grid */}
+ <section className="py-12">
+ <div className="container mx-auto px-4">
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
+ {womenProducts.map((product) => (
+ <ProductCard key={product.id} {...product} />
+ ))}
+ </div>
+ </div>
+ </section>
+
+ {/* Load More Section */}
+ <section className="py-12 border-t dark:border-neutral-800">
+ <div className="container mx-auto px-4 text-center">
+ <button className="px-8 py-3 border border-primary text-primary hover:bg-primary hover:text-primary-foreground transition-colors rounded-lg">
+ Load More Products
+ </button>
+ </div>
+ </section>
+ </main>
+ <Footer />
+ </>
+ );
+}
+
+export async function generateMetadata() {
+ return {
+ title: "Women's Fashion Collection | blcklst",
+ description: "Shop the latest women's fashion at blcklst. Elegant dresses, tops, accessories, and more for the modern woman.",
+ };
+} \ No newline at end of file
diff --git a/frontend/src/components/footer.tsx b/frontend/src/components/footer.tsx
index 62ebf94..b126661 100644
--- a/frontend/src/components/footer.tsx
+++ b/frontend/src/components/footer.tsx
@@ -97,7 +97,7 @@ export function Footer() {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-8">
{/* Brand Info */}
<div className="lg:col-span-2">
- <Link href="/" className="flex items-center space-x-2 mb-4">
+ <Link href="/" className="flex items-center space-x-2 mb-4" suppressHydrationWarning>
{/* Light theme logo - visible by default, hidden in dark mode */}
<Image
src="/black-logo.png"
diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx
index b792767..566ea71 100644
--- a/frontend/src/components/header.tsx
+++ b/frontend/src/components/header.tsx
@@ -42,8 +42,6 @@ import {
export function Header() {
const [cartItems] = useState(3);
const [wishlistItems] = useState(5);
- const [isMounted, setIsMounted] = useState(false);
- const [sheetError, setSheetError] = useState(false);
const sheetRef = useRef<HTMLDivElement>(null);
const { setTheme, theme } = useTheme();
@@ -69,49 +67,19 @@ export function Header() {
],
},
{
- title: "Kids",
+ title: "Unisex",
items: [
- { name: "Boys", href: "/kids/boys" },
- { name: "Girls", href: "/kids/girls" },
- { name: "Baby", href: "/kids/baby" },
- { name: "Shoes", href: "/kids/shoes" },
+ { name: "Hoodies & Sweatshirts", href: "/unisex/hoodies" },
+ { name: "T-Shirts", href: "/unisex/tshirts" },
+ { name: "Jeans & Pants", href: "/unisex/pants" },
+ { name: "Outerwear", href: "/unisex/outerwear" },
+ { name: "Accessories", href: "/unisex/accessories" },
],
},
];
- // Preload both logo variants to ensure smooth loading
+ // Preload logos for smooth loading
useEffect(() => {
- setIsMounted(true);
-
- // Add global error handler for scroll-related errors
- const handleError = (event: ErrorEvent) => {
- if (event.error?.message?.includes('parameter 1 is not of type \'Node\'') ||
- event.error?.message?.includes('handleScroll') ||
- event.error?.message?.includes('RemoveScrollSideCar') ||
- event.error?.message?.includes('shouldCancelEvent') ||
- event.error?.message?.includes('shouldPrevent')) {
- console.warn('Scroll handling error caught and suppressed:', event.error);
- event.preventDefault();
- setSheetError(true);
- return false;
- }
- };
-
- // Also add an unhandled rejection handler for async scroll errors
- const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
- if (event.reason?.message?.includes('handleScroll') ||
- event.reason?.message?.includes('RemoveScrollSideCar') ||
- event.reason?.message?.includes('parameter 1 is not of type \'Node\'')) {
- console.warn('Scroll promise rejection caught and suppressed:', event.reason);
- event.preventDefault();
- setSheetError(true);
- return false;
- }
- };
-
- window.addEventListener('error', handleError);
- window.addEventListener('unhandledrejection', handleUnhandledRejection);
-
const preloadLogos = () => {
if (typeof window !== 'undefined') {
const lightLogo = new window.Image();
@@ -122,117 +90,10 @@ export function Header() {
};
preloadLogos();
-
- return () => {
- window.removeEventListener('error', handleError);
- window.removeEventListener('unhandledrejection', handleUnhandledRejection);
- };
}, []);
- // Add scroll error prevention
- useEffect(() => {
- const sheetElement = sheetRef.current;
- if (sheetElement) {
- const handleScroll = (event: Event) => {
- try {
- // Allow default scroll behavior but catch any errors
- } catch (error) {
- console.warn('Sheet scroll error caught:', error);
- event.preventDefault();
- }
- };
-
- sheetElement.addEventListener('scroll', handleScroll, { passive: false });
-
- return () => {
- sheetElement.removeEventListener('scroll', handleScroll);
- };
- }
- }, [isMounted]);
-
- // Prevent hydration issues with Sheet component
- if (!isMounted) {
- return (
- <header className="sticky top-0 z-50 w-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
- {/* Top banner */}
- <div className="bg-black text-white text-center py-2 px-4">
- <p className="text-sm font-medium">
- FREE SHIPPING ON ORDERS OVER $100 • NEW ARRIVALS EVERY WEEK
- </p>
- </div>
-
- {/* Announcement bar */}
- <div className="bg-neutral-100 dark:bg-neutral-800 text-center py-2 px-4">
- <p className="text-sm">
- 🔥 <span className="font-semibold">WINTER SALE</span> - Up to 50% off on selected items
- </p>
- </div>
-
- {/* Main header */}
- <div className="border-b dark:border-neutral-800">
- <div className="container mx-auto px-3 sm:px-4 lg:px-6">
- <div className="flex h-14 sm:h-16 items-center justify-between gap-2 sm:gap-4 md:justify-between">
- {/* Mobile menu placeholder */}
- <Button
- variant="ghost"
- size="icon"
- className="md:hidden nav-button-transparent backdrop-blur-sm"
- disabled
- >
- <Menu className="h-5 w-5" />
- </Button>
-
- {/* Logo */}
- <div className="flex items-center flex-1 md:flex-initial justify-center md:justify-start">
- <Link href="/" className="flex items-center space-x-2 ml-8 md:ml-0">
- <Image
- src="/black-logo.png"
- alt="blcklst"
- width={120}
- height={40}
- className="h-8 w-auto block dark:hidden"
- priority
- />
- <Image
- src="/white-logo.png"
- alt="blcklst"
- width={120}
- height={40}
- className="h-8 w-auto hidden dark:block"
- priority
- />
- </Link>
- </div>
-
- {/* Simplified action buttons for SSR */}
- <div className="flex items-center space-x-1 sm:space-x-2">
- <Button
- variant="ghost"
- size="icon"
- className="lg:hidden nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
- disabled
- >
- <Search className="h-5 w-5" />
- </Button>
-
- <Button
- variant="ghost"
- size="icon"
- className="relative nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
- disabled
- >
- <ShoppingBag className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground flex-shrink-0" />
- </Button>
- </div>
- </div>
- </div>
- </div>
- </header>
- );
- }
-
return (
- <header className="sticky top-0 z-50 w-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
+ <header className="sticky top-0 z-50 w-full bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60" suppressHydrationWarning>
{/* Top banner */}
<div className="bg-black text-white text-center py-2 px-4">
<p className="text-sm font-medium">
@@ -252,229 +113,235 @@ export function Header() {
<div className="container mx-auto px-3 sm:px-4 lg:px-6">
<div className="flex h-14 sm:h-16 items-center justify-between gap-2 sm:gap-4 md:justify-between">
{/* Mobile menu */}
- {!sheetError ? (
- <Sheet>
- <SheetTrigger asChild>
- <Button
- variant="ghost"
- size="icon"
- className="md:hidden nav-button-transparent backdrop-blur-sm"
- >
- <Menu className="h-5 w-5" />
- </Button>
- </SheetTrigger>
- <SheetContent
- ref={sheetRef}
- side="left"
- className="w-[280px] xs:w-[320px] sm:w-[380px] p-0 border-r dark:border-neutral-800"
- onOpenAutoFocus={(event) => {
- // Prevent auto focus to avoid scroll handling issues
- event.preventDefault();
- }}
- onCloseAutoFocus={(event) => {
- // Prevent auto focus to avoid scroll handling issues
- event.preventDefault();
- }}
- onEscapeKeyDown={(event) => {
- // Handle escape key gracefully
- try {
- // Default behavior
- } catch (error) {
- console.warn('Escape key handling error:', error);
- event.preventDefault();
- setSheetError(true);
- }
- }}
- onPointerDownOutside={(event) => {
- // Handle pointer events gracefully
- try {
- // Default behavior
- } catch (error) {
- console.warn('Pointer down outside error:', error);
- event.preventDefault();
- setSheetError(true);
- }
- }}
- onInteractOutside={(event) => {
- // Additional interaction handler
- try {
- // Default behavior
- } catch (error) {
- console.warn('Interact outside error:', error);
- event.preventDefault();
- setSheetError(true);
- }
- }}
- onFocusOutside={(event) => {
- // Additional focus handler
- try {
- // Default behavior
- } catch (error) {
- console.warn('Focus outside error:', error);
- event.preventDefault();
- setSheetError(true);
- }
- }}
+ <Sheet>
+ <SheetTrigger asChild>
+ <Button
+ variant="ghost"
+ size="icon"
+ className="md:hidden nav-button-transparent cursor-pointer"
>
- <div className="flex flex-col h-full">
- {/* Header Section */}
- <SheetHeader className="px-6 py-4 border-b dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900">
- <SheetTitle className="text-left text-lg font-semibold">
- Menu
- </SheetTitle>
- </SheetHeader>
+ <Menu className="h-5 w-5" />
+ </Button>
+ </SheetTrigger>
+ <SheetContent
+ ref={sheetRef}
+ side="left"
+ className="w-[280px] xs:w-[320px] sm:w-[380px] p-0 border-r dark:border-neutral-800"
+ >
+ <div className="flex flex-col h-full">
+ {/* Header Section */}
+ <SheetHeader className="px-6 py-4 border-b dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900">
+ <SheetTitle className="text-left text-lg font-semibold">
+ Menu
+ </SheetTitle>
+ </SheetHeader>
- {/* Navigation Section */}
- <div className="flex-1 overflow-y-auto mobile-nav-scroll">
- <nav className="px-4 py-6 space-y-6">
- {categories.map((category) => (
- <div key={category.title} className="space-y-3">
- {/* Category Header */}
- <h3 className="px-2 text-sm font-semibold text-foreground uppercase tracking-wider border-b border-neutral-200 dark:border-neutral-700 pb-2">
- {category.title}
- </h3>
-
- {/* Category Items */}
- <div className="space-y-1">
- {category.items.map((item) => (
- <Link
- key={item.name}
- href={item.href}
- className="flex items-center px-3 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors touch-manipulation min-h-[44px]"
+ {/* Navigation Section */}
+ <div className="flex-1 overflow-y-auto mobile-nav-scroll">
+ <nav className="px-4 py-6 space-y-6">
+ {categories.map((category) => (
+ <div key={category.title} className="space-y-3">
+ {/* Category Header */}
+ <h3 className="px-2 text-sm font-semibold text-foreground uppercase tracking-wider border-b border-neutral-200 dark:border-neutral-700 pb-2">
+ {category.title}
+ </h3>
+
+ {/* Category Items */}
+ <div className="space-y-1">
+ {category.items.map((item) => (
+ <Link
+ key={item.name}
+ href={item.href}
+ className="flex items-center px-3 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors touch-manipulation min-h-[44px]"
+ >
+ <span className="flex-1">{item.name}</span>
+ <svg
+ className="w-4 h-4 opacity-40"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
>
- <span className="flex-1">{item.name}</span>
- <svg
- className="w-4 h-4 opacity-40"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
- </svg>
- </Link>
- ))}
- </div>
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </Link>
+ ))}
</div>
- ))}
+ </div>
+ ))}
- {/* Sale Section */}
- <div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700">
- <h3 className="px-2 text-sm font-semibold text-red-600 uppercase tracking-wider">
- Special
- </h3>
- <Link
- href="/sale"
- className="flex items-center px-3 py-3 text-sm font-medium text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/30 rounded-lg transition-colors touch-manipulation min-h-[44px]"
+ {/* Sale Section */}
+ <div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700">
+ <h3 className="px-2 text-sm font-semibold text-red-600 uppercase tracking-wider">
+ Special
+ </h3>
+ <Link
+ href="/sale"
+ className="flex items-center px-3 py-3 text-sm font-medium text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/30 rounded-lg transition-colors touch-manipulation min-h-[44px]"
+ >
+ <span className="flex-1">Sale Items</span>
+ <svg
+ className="w-4 h-4"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
>
- <span className="flex-1">Sale Items</span>
- <svg
- className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
- </svg>
- </Link>
- </div>
- </nav>
- </div>
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </Link>
+ </div>
- {/* Footer Section */}
- <div className="border-t dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900 px-4 py-4">
- <div className="grid grid-cols-2 gap-3">
- {/* Search Button */}
- <Button
- variant="outline"
- size="sm"
- className="flex items-center justify-center gap-2 h-10 text-xs font-medium"
+ {/* More Section */}
+ <div className="space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700">
+ <h3 className="px-2 text-sm font-semibold text-foreground uppercase tracking-wider">
+ More
+ </h3>
+ <Link
+ href="/about"
+ className="flex items-center px-3 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors touch-manipulation min-h-[44px]"
>
- <Search className="h-4 w-4" />
- Search
- </Button>
-
- {/* Account Button */}
- <Button
- variant="outline"
- size="sm"
- className="flex items-center justify-center gap-2 h-10 text-xs font-medium"
+ <span className="flex-1">About Us</span>
+ <svg
+ className="w-4 h-4 opacity-40"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </Link>
+ <Link
+ href="/contact"
+ className="flex items-center px-3 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors touch-manipulation min-h-[44px]"
>
- <User className="h-4 w-4" />
- Account
- </Button>
- </div>
-
- {/* Quick Actions */}
- <div className="grid grid-cols-4 gap-1 mt-4 pt-3 border-t border-neutral-200 dark:border-neutral-700">
- <button className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700">
- <div className="relative">
- <Heart className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
- {wishlistItems > 0 && (
- <Badge className="absolute -top-2 -right-2 h-4 w-4 rounded-full p-0 text-[10px] leading-none">
- {wishlistItems}
- </Badge>
- )}
- </div>
- <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">Wishlist</span>
- </button>
-
- <button className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700">
- <div className="relative">
- <ShoppingBag className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
- {cartItems > 0 && (
- <Badge className="absolute -top-2 -right-2 h-4 w-4 rounded-full p-0 text-[10px] leading-none">
- {cartItems}
- </Badge>
- )}
- </div>
- <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">Cart</span>
- </button>
-
- {/* Theme Toggle in Mobile Menu */}
- <button
- onClick={() => {
- if (theme === 'dark') {
- setTheme('light');
- } else {
- setTheme('dark');
- }
- }}
- className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700"
+ <span className="flex-1">Contact</span>
+ <svg
+ className="w-4 h-4 opacity-40"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </Link>
+ <Link
+ href="/size-guide"
+ className="flex items-center px-3 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors touch-manipulation min-h-[44px]"
>
- <div className="relative">
- {theme === 'dark' ? (
- <Moon className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
- ) : (
- <Sun className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
- )}
- </div>
- <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">Theme</span>
- </button>
-
- <button className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700">
- <div className="relative">
- <Globe className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
- </div>
- <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">USD</span>
- </button>
+ <span className="flex-1">Size Guide</span>
+ <svg
+ className="w-4 h-4 opacity-40"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </Link>
+ <Link
+ href="/help"
+ className="flex items-center px-3 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors touch-manipulation min-h-[44px]"
+ >
+ <span className="flex-1">Help & FAQ</span>
+ <svg
+ className="w-4 h-4 opacity-40"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </Link>
</div>
+ </nav>
+ </div>
+
+ {/* Footer Section */}
+ <div className="border-t dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900 px-4 py-4">
+ <div className="grid grid-cols-2 gap-3">
+ {/* Search Button */}
+ <Button
+ variant="outline"
+ size="sm"
+ className="flex items-center justify-center gap-2 h-10 text-xs font-medium"
+ >
+ <Search className="h-4 w-4" />
+ Search
+ </Button>
+
+ {/* Account Button */}
+ <Button
+ variant="outline"
+ size="sm"
+ className="flex items-center justify-center gap-2 h-10 text-xs font-medium"
+ >
+ <User className="h-4 w-4" />
+ Account
+ </Button>
+ </div>
+
+ {/* Quick Actions */}
+ <div className="grid grid-cols-4 gap-1 mt-4 pt-3 border-t border-neutral-200 dark:border-neutral-700">
+ <button className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700">
+ <div className="relative">
+ <Heart className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
+ {wishlistItems > 0 && (
+ <Badge className="absolute -top-2 -right-2 h-4 w-4 rounded-full p-0 text-[10px] leading-none">
+ {wishlistItems}
+ </Badge>
+ )}
+ </div>
+ <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">Wishlist</span>
+ </button>
+
+ <button className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700">
+ <div className="relative">
+ <ShoppingBag className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
+ {cartItems > 0 && (
+ <Badge className="absolute -top-2 -right-2 h-4 w-4 rounded-full p-0 text-[10px] leading-none">
+ {cartItems}
+ </Badge>
+ )}
+ </div>
+ <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">Cart</span>
+ </button>
+
+ {/* Theme Toggle in Mobile Menu */}
+ <button
+ onClick={() => {
+ if (theme === 'dark') {
+ setTheme('light');
+ } else {
+ setTheme('dark');
+ }
+ }}
+ className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700"
+ suppressHydrationWarning
+ >
+ <div className="relative">
+ {theme === 'dark' ? (
+ <Moon className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
+ ) : (
+ <Sun className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
+ )}
+ </div>
+ <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">Theme</span>
+ </button>
+
+ <button className="flex flex-col items-center justify-center space-y-2 p-3 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-all duration-200 touch-manipulation group min-h-[72px] border border-transparent hover:border-neutral-200 dark:hover:border-neutral-700">
+ <div className="relative">
+ <Globe className="h-5 w-5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
+ </div>
+ <span className="text-[10px] text-muted-foreground group-hover:text-foreground transition-colors text-center leading-tight font-medium">USD</span>
+ </button>
</div>
</div>
- </SheetContent>
- </Sheet>
- ) : (
- <Button
- variant="ghost"
- size="icon"
- className="md:hidden nav-button-transparent backdrop-blur-sm"
- >
- <Menu className="h-5 w-5" />
- </Button>
- )}
+ </div>
+ </SheetContent>
+ </Sheet>
{/* Logo */}
<div className="flex items-center flex-1 md:flex-initial justify-center md:justify-start">
- <Link href="/" className="flex items-center space-x-2 ml-8 md:ml-0">
+ <Link href="/" className="flex items-center space-x-2 ml-8 md:ml-0" suppressHydrationWarning>
<Image
src="/black-logo.png"
alt="blcklst"
@@ -495,40 +362,54 @@ export function Header() {
</div>
{/* Desktop Navigation */}
- <NavigationMenu className="hidden md:flex">
- <NavigationMenuList>
- {categories.map((category) => (
- <NavigationMenuItem key={category.title}>
- <NavigationMenuTrigger
- className="font-medium nav-button-transparent backdrop-blur-sm"
- >
- {category.title}
- </NavigationMenuTrigger>
- <NavigationMenuContent>
- <div className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] nav-dropdown-transparent">
- {category.items.map((item) => (
- <NavigationMenuLink key={item.name} asChild>
- <Link
- href={item.href}
- className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors nav-dropdown-item backdrop-blur-sm"
- >
- <div className="text-sm font-medium leading-none">{item.name}</div>
- </Link>
- </NavigationMenuLink>
- ))}
- </div>
- </NavigationMenuContent>
+ <div className="hidden md:flex">
+ <NavigationMenu
+ className="w-full"
+ delayDuration={150}
+ skipDelayDuration={250}
+ >
+ <NavigationMenuList>
+ {categories.map((category) => (
+ <NavigationMenuItem key={category.title} value={category.title}>
+ <NavigationMenuTrigger
+ className="font-medium nav-button-transparent text-foreground hover:text-foreground/90 transition-all duration-200 group cursor-pointer"
+ aria-haspopup="true"
+ >
+ {category.title}
+ </NavigationMenuTrigger>
+ <NavigationMenuContent
+ className="data-[motion=from-start]:animate-in data-[motion=from-end]:animate-in data-[motion=to-start]:animate-out data-[motion=to-end]:animate-out data-[motion=from-start]:slide-in-from-left-52 data-[motion=from-end]:slide-in-from-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion=to-end]:slide-out-to-right-52"
+ >
+ <div className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] nav-dropdown-transparent">
+ {category.items.map((item) => (
+ <NavigationMenuLink key={item.name} asChild>
+ <Link
+ href={item.href}
+ className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors nav-dropdown-item group"
+ >
+ <div className="text-sm font-medium leading-none group-hover:text-foreground transition-colors">
+ {item.name}
+ </div>
+ <p className="line-clamp-2 text-xs leading-snug text-muted-foreground group-hover:text-foreground/80 transition-colors mt-1">
+ Discover our latest {item.name.toLowerCase()} collection
+ </p>
+ </Link>
+ </NavigationMenuLink>
+ ))}
+ </div>
+ </NavigationMenuContent>
+ </NavigationMenuItem>
+ ))}
+ <NavigationMenuItem>
+ <NavigationMenuLink asChild>
+ <Link href="/sale" className="font-medium text-red-600 hover:text-red-700 px-4 py-2 rounded-md nav-button-transparent transition-colors cursor-pointer">
+ Sale
+ </Link>
+ </NavigationMenuLink>
</NavigationMenuItem>
- ))}
- <NavigationMenuItem>
- <NavigationMenuLink asChild>
- <Link href="/sale" className="font-medium text-red-600 hover:text-red-700 px-4 py-2 rounded-md nav-button-transparent backdrop-blur-sm transition-colors">
- Sale
- </Link>
- </NavigationMenuLink>
- </NavigationMenuItem>
- </NavigationMenuList>
- </NavigationMenu>
+ </NavigationMenuList>
+ </NavigationMenu>
+ </div>
{/* Search Bar */}
<div className="hidden lg:flex flex-1 max-w-sm mx-8">
@@ -547,13 +428,13 @@ export function Header() {
<Button
variant="ghost"
size="icon"
- className="lg:hidden nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
+ className="lg:hidden nav-button-transparent min-w-[44px] min-h-[44px] cursor-pointer"
>
<Search className="h-5 w-5" />
</Button>
{/* Theme toggle */}
- <div className="hidden sm:block">
+ <div className="hidden sm:block" suppressHydrationWarning>
<ThemeToggle />
</div>
@@ -563,7 +444,7 @@ export function Header() {
<Button
variant="ghost"
size="icon"
- className="hidden sm:flex nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
+ className="hidden sm:flex nav-button-transparent min-w-[44px] min-h-[44px] cursor-pointer"
>
<User className="h-5 w-5" />
</Button>
@@ -595,7 +476,7 @@ export function Header() {
<Button
variant="ghost"
size="icon"
- className="relative hidden xs:flex nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
+ className="relative hidden xs:flex nav-button-transparent min-w-[44px] min-h-[44px] cursor-pointer"
>
<Heart className="h-5 w-5" />
{wishlistItems > 0 && (
@@ -609,7 +490,7 @@ export function Header() {
<Button
variant="ghost"
size="icon"
- className="relative nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
+ className="relative nav-button-transparent min-w-[44px] min-h-[44px] cursor-pointer"
>
<ShoppingBag className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground flex-shrink-0" />
{cartItems > 0 && (
@@ -625,7 +506,7 @@ export function Header() {
<Button
variant="ghost"
size="icon"
- className="hidden md:flex nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
+ className="hidden md:flex nav-button-transparent min-w-[44px] min-h-[44px] cursor-pointer"
>
<Globe className="h-5 w-5" />
</Button>
diff --git a/frontend/src/components/loading.tsx b/frontend/src/components/loading.tsx
new file mode 100644
index 0000000..cbd5c15
--- /dev/null
+++ b/frontend/src/components/loading.tsx
@@ -0,0 +1,111 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import Image from "next/image";
+
+export function LoadingScreen() {
+ const [isVisible, setIsVisible] = useState(true);
+
+ useEffect(() => {
+ // Hide loading screen after a short delay to ensure everything is ready
+ const timer = setTimeout(() => {
+ setIsVisible(false);
+ }, 1000);
+
+ return () => clearTimeout(timer);
+ }, []);
+
+ if (!isVisible) return null;
+
+ return (
+ <div className="fixed inset-0 z-[9999] bg-background flex items-center justify-center">
+ {/* Background pattern */}
+ <div className="absolute inset-0 bg-gradient-to-br from-background via-background to-muted/20" />
+
+ {/* Loading content */}
+ <div className="relative z-10 flex flex-col items-center space-y-4">
+ {/* Logo with animation */}
+ <div className="relative">
+ <div className="animate-pulse">
+ <Image
+ src="/black-logo.png"
+ alt="blcklst"
+ width={160}
+ height={50}
+ className="h-12 w-auto block dark:hidden"
+ priority
+ />
+ <Image
+ src="/white-logo.png"
+ alt="blcklst"
+ width={160}
+ height={50}
+ className="h-12 w-auto hidden dark:block"
+ priority
+ />
+ </div>
+ </div>
+
+ {/* Loading text - closer to logo */}
+ <div className="text-center space-y-3 -mt-2">
+ <p className="text-sm text-muted-foreground animate-pulse">
+ not everyone gets blcklsted
+ </p>
+
+ {/* Loading dots */}
+ <div className="flex items-center justify-center space-x-1">
+ <div className="w-2 h-2 bg-foreground/60 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
+ <div className="w-2 h-2 bg-foreground/60 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
+ <div className="w-2 h-2 bg-foreground/60 rounded-full animate-bounce"></div>
+ </div>
+ </div>
+
+ {/* Progress bar */}
+ <div className="w-64 h-1 bg-muted rounded-full overflow-hidden">
+ <div className="h-full bg-foreground rounded-full animate-[loading_1s_ease-in-out_infinite]"></div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+export function PageWrapper({ children }: { children: React.ReactNode }) {
+ const [isLoading, setIsLoading] = useState(true);
+ const [showContent, setShowContent] = useState(false);
+
+ useEffect(() => {
+ // Check if page is ready
+ const checkReady = () => {
+ if (document.readyState === 'complete') {
+ setTimeout(() => {
+ setIsLoading(false);
+ setTimeout(() => setShowContent(true), 100);
+ }, 800);
+ }
+ };
+
+ if (document.readyState === 'complete') {
+ checkReady();
+ } else {
+ window.addEventListener('load', checkReady);
+ }
+
+ return () => {
+ window.removeEventListener('load', checkReady);
+ };
+ }, []);
+
+ return (
+ <>
+ {isLoading && <LoadingScreen />}
+ <div
+ className={`transition-opacity duration-500 ${
+ showContent ? 'opacity-100' : 'opacity-0'
+ }`}
+ suppressHydrationWarning
+ >
+ {children}
+ </div>
+ </>
+ );
+} \ No newline at end of file
diff --git a/frontend/src/components/product-card.tsx b/frontend/src/components/product-card.tsx
index 25717f4..e953e83 100644
--- a/frontend/src/components/product-card.tsx
+++ b/frontend/src/components/product-card.tsx
@@ -75,7 +75,7 @@ export function ProductCard({
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
- <Link href={`/product/${id}`}>
+ <Link href={`/products/${id}`}>
<div className="relative overflow-hidden">
{/* Product Image */}
<div className="aspect-[3/4] bg-neutral-100 dark:bg-neutral-800 relative overflow-hidden">
diff --git a/frontend/src/components/product-page.tsx b/frontend/src/components/product-page.tsx
new file mode 100644
index 0000000..aff9d53
--- /dev/null
+++ b/frontend/src/components/product-page.tsx
@@ -0,0 +1,567 @@
+"use client";
+
+import { useState } from "react";
+import Image from "next/image";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Separator } from "@/components/ui/separator";
+import {
+ Heart,
+ Share2,
+ ShoppingBag,
+ Star,
+ Truck,
+ RefreshCw,
+ Shield,
+ Ruler,
+ ChevronLeft,
+ ChevronRight,
+ Minus,
+ Plus,
+ Check,
+} from "lucide-react";
+
+interface ProductPageProps {
+ productId?: string;
+}
+
+export function ProductPage({ productId = "1" }: ProductPageProps) {
+ const [selectedImage, setSelectedImage] = useState(0);
+ const [selectedSize, setSelectedSize] = useState("");
+ const [selectedColor, setSelectedColor] = useState("");
+ const [quantity, setQuantity] = useState(1);
+ const [isWishlisted, setIsWishlisted] = useState(false);
+
+ // Mock product data - in real app, this would come from an API
+ const product = {
+ id: productId,
+ name: "Oversized Cotton Hoodie",
+ brand: "blcklst",
+ price: 89,
+ originalPrice: 129,
+ rating: 4.5,
+ reviewCount: 234,
+ description: "A premium oversized cotton hoodie crafted for ultimate comfort and style. Made from 100% organic cotton with a soft fleece lining, this hoodie features a relaxed fit perfect for layering or wearing solo.",
+ features: [
+ "100% organic cotton construction",
+ "Soft fleece interior lining",
+ "Kangaroo pocket with hidden phone compartment",
+ "Reinforced double-stitched seams",
+ "Pre-shrunk for lasting fit",
+ "Unisex design"
+ ],
+ images: [
+ "/api/placeholder/600/800",
+ "/api/placeholder/600/800",
+ "/api/placeholder/600/800",
+ "/api/placeholder/600/800",
+ ],
+ colors: [
+ { name: "Black", value: "#000000", available: true },
+ { name: "White", value: "#FFFFFF", available: true },
+ { name: "Gray", value: "#6B7280", available: true },
+ { name: "Navy", value: "#1E3A8A", available: false },
+ ],
+ sizes: [
+ { name: "XS", available: true },
+ { name: "S", available: true },
+ { name: "M", available: true },
+ { name: "L", available: true },
+ { name: "XL", available: true },
+ { name: "XXL", available: false },
+ ],
+ inStock: true,
+ stockCount: 12,
+ };
+
+ const relatedProducts = [
+ {
+ id: "2",
+ name: "Minimal T-Shirt",
+ price: 45,
+ originalPrice: 65,
+ image: "/api/placeholder/300/400",
+ rating: 4.3,
+ },
+ {
+ id: "3",
+ name: "Denim Jacket",
+ price: 129,
+ originalPrice: 179,
+ image: "/api/placeholder/300/400",
+ rating: 4.7,
+ },
+ {
+ id: "4",
+ name: "Cargo Pants",
+ price: 89,
+ originalPrice: 119,
+ image: "/api/placeholder/300/400",
+ rating: 4.2,
+ },
+ {
+ id: "5",
+ name: "Sneakers",
+ price: 149,
+ originalPrice: 199,
+ image: "/api/placeholder/300/400",
+ rating: 4.6,
+ },
+ ];
+
+ const reviews = [
+ {
+ id: 1,
+ name: "Sarah M.",
+ rating: 5,
+ date: "2024-01-15",
+ comment: "Amazing quality! The fit is perfect and the material feels premium. Definitely worth the price.",
+ verified: true,
+ },
+ {
+ id: 2,
+ name: "Alex K.",
+ rating: 4,
+ date: "2024-01-10",
+ comment: "Great hoodie, very comfortable. Only wish it came in more colors.",
+ verified: true,
+ },
+ {
+ id: 3,
+ name: "Jordan L.",
+ rating: 5,
+ date: "2024-01-05",
+ comment: "Best hoodie I've ever owned. The oversized fit is exactly what I wanted.",
+ verified: true,
+ },
+ ];
+
+ const nextImage = () => {
+ setSelectedImage((prev) => (prev + 1) % product.images.length);
+ };
+
+ const prevImage = () => {
+ setSelectedImage((prev) => (prev - 1 + product.images.length) % product.images.length);
+ };
+
+ const addToCart = () => {
+ if (!selectedSize || !selectedColor) {
+ alert("Please select size and color");
+ return;
+ }
+ // Add to cart logic here
+ console.log("Added to cart:", { product, selectedSize, selectedColor, quantity });
+ };
+
+ const renderStars = (rating: number) => {
+ return Array.from({ length: 5 }, (_, i) => (
+ <Star
+ key={i}
+ className={`h-4 w-4 ${
+ i < Math.floor(rating)
+ ? "fill-yellow-400 text-yellow-400"
+ : i < rating
+ ? "fill-yellow-400/50 text-yellow-400"
+ : "text-gray-300"
+ }`}
+ />
+ ));
+ };
+
+ return (
+ <div className="min-h-screen bg-background">
+ {/* Breadcrumb */}
+ <div className="border-b">
+ <div className="container mx-auto px-4 py-4">
+ <nav className="flex items-center space-x-2 text-sm text-muted-foreground">
+ <Link href="/" className="hover:text-foreground">Home</Link>
+ <span>/</span>
+ <Link href="/men" className="hover:text-foreground">Men</Link>
+ <span>/</span>
+ <Link href="/men/hoodies" className="hover:text-foreground">Hoodies</Link>
+ <span>/</span>
+ <span className="text-foreground">{product.name}</span>
+ </nav>
+ </div>
+ </div>
+
+ <div className="container mx-auto px-4 py-8">
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
+ {/* Product Images */}
+ <div className="space-y-4">
+ {/* Main Image */}
+ <div className="relative aspect-[3/4] overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-800">
+ <Image
+ src={product.images[selectedImage]}
+ alt={product.name}
+ fill
+ className="object-cover"
+ priority
+ />
+ {product.originalPrice && (
+ <Badge className="absolute top-4 left-4 bg-red-500 hover:bg-red-600">
+ {Math.round(((product.originalPrice - product.price) / product.originalPrice) * 100)}% OFF
+ </Badge>
+ )}
+
+ {/* Navigation Arrows */}
+ <button
+ onClick={prevImage}
+ className="absolute left-4 top-1/2 -translate-y-1/2 bg-white/80 dark:bg-black/80 p-2 rounded-full hover:bg-white dark:hover:bg-black transition-colors"
+ >
+ <ChevronLeft className="h-5 w-5" />
+ </button>
+ <button
+ onClick={nextImage}
+ className="absolute right-4 top-1/2 -translate-y-1/2 bg-white/80 dark:bg-black/80 p-2 rounded-full hover:bg-white dark:hover:bg-black transition-colors"
+ >
+ <ChevronRight className="h-5 w-5" />
+ </button>
+ </div>
+
+ {/* Thumbnail Images */}
+ <div className="grid grid-cols-4 gap-3">
+ {product.images.map((image, index) => (
+ <button
+ key={index}
+ onClick={() => setSelectedImage(index)}
+ className={`relative aspect-square overflow-hidden rounded-lg border-2 transition-colors ${
+ selectedImage === index
+ ? "border-primary"
+ : "border-transparent hover:border-neutral-300"
+ }`}
+ >
+ <Image
+ src={image}
+ alt={`${product.name} ${index + 1}`}
+ fill
+ className="object-cover"
+ />
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Product Details */}
+ <div className="space-y-6">
+ {/* Header */}
+ <div>
+ <p className="text-sm text-muted-foreground font-medium mb-2">{product.brand}</p>
+ <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
+
+ {/* Rating */}
+ <div className="flex items-center space-x-2 mb-4">
+ <div className="flex items-center space-x-1">
+ {renderStars(product.rating)}
+ </div>
+ <span className="text-sm text-muted-foreground">
+ {product.rating} ({product.reviewCount} reviews)
+ </span>
+ </div>
+
+ {/* Price */}
+ <div className="flex items-center space-x-3 mb-6">
+ <span className="text-3xl font-bold">${product.price}</span>
+ {product.originalPrice && (
+ <span className="text-xl text-muted-foreground line-through">
+ ${product.originalPrice}
+ </span>
+ )}
+ </div>
+ </div>
+
+ {/* Color Selection */}
+ <div className="space-y-3">
+ <div className="flex items-center justify-between">
+ <h3 className="font-medium">Color</h3>
+ {selectedColor && (
+ <span className="text-sm text-muted-foreground capitalize">{selectedColor}</span>
+ )}
+ </div>
+ <div className="flex space-x-3">
+ {product.colors.map((color) => (
+ <button
+ key={color.name}
+ onClick={() => color.available && setSelectedColor(color.name)}
+ disabled={!color.available}
+ className={`relative w-10 h-10 rounded-full border-2 transition-all ${
+ selectedColor === color.name
+ ? "border-primary scale-110"
+ : "border-neutral-300 hover:border-neutral-400"
+ } ${!color.available && "opacity-50 cursor-not-allowed"}`}
+ style={{ backgroundColor: color.value }}
+ >
+ {selectedColor === color.name && (
+ <Check className="absolute inset-0 m-auto h-4 w-4 text-white" />
+ )}
+ {!color.available && (
+ <div className="absolute inset-0 bg-neutral-500/50 rounded-full" />
+ )}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Size Selection */}
+ <div className="space-y-3">
+ <div className="flex items-center justify-between">
+ <h3 className="font-medium">Size</h3>
+ <Button variant="link" className="h-auto p-0 text-sm">
+ <Ruler className="h-4 w-4 mr-1" />
+ Size Guide
+ </Button>
+ </div>
+ <div className="grid grid-cols-3 gap-3">
+ {product.sizes.map((size) => (
+ <button
+ key={size.name}
+ onClick={() => size.available && setSelectedSize(size.name)}
+ disabled={!size.available}
+ className={`py-3 px-4 border rounded-lg text-sm font-medium transition-colors ${
+ selectedSize === size.name
+ ? "border-primary bg-primary text-primary-foreground"
+ : size.available
+ ? "border-neutral-300 hover:border-neutral-400 hover:bg-white dark:hover:bg-white hover:text-black"
+ : "border-neutral-200 text-muted-foreground opacity-50 cursor-not-allowed"
+ }`}
+ >
+ {size.name}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Quantity */}
+ <div className="space-y-3">
+ <h3 className="font-medium">Quantity</h3>
+ <div className="flex items-center space-x-3">
+ <div className="flex items-center border rounded-lg">
+ <button
+ onClick={() => setQuantity(Math.max(1, quantity - 1))}
+ className="p-3 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors focus:outline-none focus:ring-0 m-1"
+ >
+ <Minus className="h-4 w-4" />
+ </button>
+ <span className="px-4 py-3 min-w-[60px] text-center">{quantity}</span>
+ <button
+ onClick={() => setQuantity(Math.min(product.stockCount, quantity + 1))}
+ className="p-3 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors focus:outline-none focus:ring-0 m-1"
+ >
+ <Plus className="h-4 w-4" />
+ </button>
+ </div>
+ <span className="text-sm text-muted-foreground">
+ {product.stockCount} items left
+ </span>
+ </div>
+ </div>
+
+ {/* Add to Cart */}
+ <div className="space-y-3">
+ <div className="flex space-x-3">
+ <Button
+ onClick={addToCart}
+ className="flex-1 h-12"
+ disabled={!product.inStock}
+ >
+ <ShoppingBag className="h-5 w-5 mr-2" />
+ {product.inStock ? "Add to Cart" : "Out of Stock"}
+ </Button>
+ <Button
+ variant="outline"
+ size="icon"
+ className="h-12 w-12"
+ onClick={() => setIsWishlisted(!isWishlisted)}
+ >
+ <Heart className={`h-5 w-5 ${isWishlisted ? "fill-red-500 text-red-500" : ""}`} />
+ </Button>
+ <Button variant="outline" size="icon" className="h-12 w-12">
+ <Share2 className="h-5 w-5" />
+ </Button>
+ </div>
+
+ {/* Features */}
+ <div className="grid grid-cols-3 gap-4 pt-4">
+ <div className="flex items-center space-x-2 text-sm">
+ <Truck className="h-4 w-4 text-muted-foreground" />
+ <span>Free Shipping</span>
+ </div>
+ <div className="flex items-center space-x-2 text-sm">
+ <RefreshCw className="h-4 w-4 text-muted-foreground" />
+ <span>Free Returns</span>
+ </div>
+ <div className="flex items-center space-x-2 text-sm">
+ <Shield className="h-4 w-4 text-muted-foreground" />
+ <span>2 Year Warranty</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* Product Details Tabs */}
+ <div className="mt-16">
+ <Tabs defaultValue="description" className="w-full">
+ <TabsList className="grid w-full grid-cols-3">
+ <TabsTrigger value="description">Description</TabsTrigger>
+ <TabsTrigger value="specifications">Specifications</TabsTrigger>
+ <TabsTrigger value="reviews">Reviews ({product.reviewCount})</TabsTrigger>
+ </TabsList>
+
+ <TabsContent value="description" className="mt-8">
+ <div className="prose dark:prose-invert max-w-none">
+ <p className="text-lg text-muted-foreground mb-6">{product.description}</p>
+ <h3 className="text-xl font-semibold mb-4">Features</h3>
+ <ul className="space-y-2">
+ {product.features.map((feature, index) => (
+ <li key={index} className="flex items-start space-x-2">
+ <Check className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
+ <span>{feature}</span>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="specifications" className="mt-8">
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+ <div className="space-y-4">
+ <h3 className="text-lg font-semibold">Material & Care</h3>
+ <div className="space-y-2">
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Material:</span>
+ <span>100% Organic Cotton</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Weight:</span>
+ <span>400 GSM</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Care:</span>
+ <span>Machine Wash Cold</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Origin:</span>
+ <span>Made in Portugal</span>
+ </div>
+ </div>
+ </div>
+ <div className="space-y-4">
+ <h3 className="text-lg font-semibold">Sizing</h3>
+ <div className="space-y-2">
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Fit:</span>
+ <span>Oversized</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Model Height:</span>
+ <span>6&apos;0&quot; / 183cm</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Model Size:</span>
+ <span>Size M</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="reviews" className="mt-8">
+ <div className="space-y-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <div className="flex items-center space-x-2 mb-2">
+ <div className="flex items-center space-x-1">
+ {renderStars(product.rating)}
+ </div>
+ <span className="text-2xl font-bold">{product.rating}</span>
+ <span className="text-muted-foreground">({product.reviewCount} reviews)</span>
+ </div>
+ <p className="text-sm text-muted-foreground">Based on verified purchases</p>
+ </div>
+ <Button variant="outline">Write a Review</Button>
+ </div>
+
+ <Separator />
+
+ <div className="space-y-6">
+ {reviews.map((review) => (
+ <div key={review.id} className="space-y-3">
+ <div className="flex items-center justify-between">
+ <div className="flex items-center space-x-3">
+ <div>
+ <div className="flex items-center space-x-2">
+ <span className="font-medium">{review.name}</span>
+ {review.verified && (
+ <Badge variant="secondary" className="text-xs">
+ Verified Purchase
+ </Badge>
+ )}
+ </div>
+ <div className="flex items-center space-x-2 mt-1">
+ <div className="flex items-center space-x-1">
+ {renderStars(review.rating)}
+ </div>
+ <span className="text-sm text-muted-foreground">{review.date}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <p className="text-muted-foreground">{review.comment}</p>
+ <Separator />
+ </div>
+ ))}
+ </div>
+ </div>
+ </TabsContent>
+ </Tabs>
+ </div>
+
+ {/* Related Products */}
+ <div className="mt-16">
+ <h2 className="text-2xl font-bold mb-8">You might also like</h2>
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
+ {relatedProducts.map((item) => (
+ <Link key={item.id} href={`/products/${item.id}`} className="group">
+ <div className="space-y-3">
+ <div className="relative aspect-[3/4] overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-800">
+ <Image
+ src={item.image}
+ alt={item.name}
+ fill
+ className="object-cover group-hover:scale-105 transition-transform duration-300"
+ />
+ {item.originalPrice && (
+ <Badge className="absolute top-3 left-3 bg-red-500 hover:bg-red-600">
+ {Math.round(((item.originalPrice - item.price) / item.originalPrice) * 100)}% OFF
+ </Badge>
+ )}
+ </div>
+ <div>
+ <h3 className="font-medium group-hover:text-primary transition-colors">
+ {item.name}
+ </h3>
+ <div className="flex items-center space-x-1 mt-1">
+ {renderStars(item.rating)}
+ <span className="text-sm text-muted-foreground">({item.rating})</span>
+ </div>
+ <div className="flex items-center space-x-2 mt-2">
+ <span className="font-semibold">${item.price}</span>
+ {item.originalPrice && (
+ <span className="text-sm text-muted-foreground line-through">
+ ${item.originalPrice}
+ </span>
+ )}
+ </div>
+ </div>
+ </div>
+ </Link>
+ ))}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/frontend/src/components/theme-provider.tsx b/frontend/src/components/theme-provider.tsx
index ab9d9da..d6e536b 100644
--- a/frontend/src/components/theme-provider.tsx
+++ b/frontend/src/components/theme-provider.tsx
@@ -3,7 +3,7 @@
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import type { ThemeProviderProps } from "next-themes"
-
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
} \ No newline at end of file
diff --git a/frontend/src/components/theme-toggle.tsx b/frontend/src/components/theme-toggle.tsx
index 7532775..09a5fb8 100644
--- a/frontend/src/components/theme-toggle.tsx
+++ b/frontend/src/components/theme-toggle.tsx
@@ -14,26 +14,6 @@ import {
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
- const [mounted, setMounted] = React.useState(false)
-
- // Ensure we only render the correct icon after mounting to avoid hydration mismatch
- React.useEffect(() => {
- setMounted(true)
- }, [])
-
- if (!mounted) {
- return (
- <Button
- variant="ghost"
- size="icon"
- className="nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
- disabled
- >
- <Monitor className="h-5 w-5" />
- <span className="sr-only">Toggle theme</span>
- </Button>
- )
- }
return (
<DropdownMenu>
@@ -41,7 +21,8 @@ export function ThemeToggle() {
<Button
variant="ghost"
size="icon"
- className="nav-button-transparent backdrop-blur-sm min-w-[44px] min-h-[44px]"
+ className="nav-button-transparent min-w-[44px] min-h-[44px] cursor-pointer"
+ suppressHydrationWarning
>
{/* Simple icon transitions with consistent styling */}
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all duration-500 ease-in-out dark:-rotate-180 dark:scale-0" />
diff --git a/frontend/src/components/ui/navigation-menu.tsx b/frontend/src/components/ui/navigation-menu.tsx
index 1199945..65b5803 100644
--- a/frontend/src/components/ui/navigation-menu.tsx
+++ b/frontend/src/components/ui/navigation-menu.tsx
@@ -21,6 +21,8 @@ function NavigationMenu({
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className
)}
+ delayDuration={150}
+ skipDelayDuration={300}
{...props}
>
{children}
@@ -59,7 +61,7 @@ function NavigationMenuItem({
}
const navigationMenuTriggerStyle = cva(
- "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 [&]:touch-action-manipulation"
)
function NavigationMenuTrigger({
@@ -67,15 +69,26 @@ function NavigationMenuTrigger({
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
+ const [isHovered, setIsHovered] = React.useState(false)
+
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ style={{
+ WebkitTapHighlightColor: 'transparent',
+ touchAction: 'manipulation',
+ }}
{...props}
>
{children}{" "}
<ChevronDownIcon
- className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
+ className={cn(
+ "relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180",
+ "transform-gpu will-change-transform"
+ )}
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
@@ -91,6 +104,7 @@ function NavigationMenuContent({
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
+ "transform-gpu will-change-transform backface-visibility-hidden",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className
)}
@@ -113,6 +127,7 @@ function NavigationMenuViewport({
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
+ "transform-gpu will-change-transform backface-visibility-hidden",
className
)}
{...props}
diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..d6bb57f
--- /dev/null
+++ b/frontend/src/components/ui/tabs.tsx
@@ -0,0 +1,55 @@
+"use client"
+
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+ React.ElementRef<typeof TabsPrimitive.List>,
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+ <TabsPrimitive.List
+ ref={ref}
+ className={cn(
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+ <TabsPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
+ className
+ )}
+ {...props}
+ />
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+ React.ElementRef<typeof TabsPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
+>(({ className, ...props }, ref) => (
+ <TabsPrimitive.Content
+ ref={ref}
+ className={cn(
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+ className
+ )}
+ {...props}
+ />
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent } \ No newline at end of file