diff --git a/package-lock.json b/package-lock.json
index 96ae3ef..bf505bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,16 +13,20 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-accordion": "^1.2.8",
- "@radix-ui/react-avatar": "^1.1.7",
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
+ "@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12",
- "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-navigation-menu": "^1.2.10",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4",
- "@radix-ui/react-slot": "^1.2.0",
- "@radix-ui/react-tabs": "^1.1.9",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toggle": "^1.1.6",
"@radix-ui/react-toggle-group": "^1.1.7",
"@radix-ui/react-tooltip": "^1.2.4",
@@ -33,6 +37,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
+ "embla-carousel-react": "^8.6.0",
"lucide-react": "^0.503.0",
"next": "^15.5.4",
"next-intl": "^4.3.9",
@@ -41,9 +46,11 @@
"react-dom": "^19.1.1",
"recharts": "^2.15.3",
"sonner": "^2.0.3",
+ "swiper": "^12.0.3",
"tailwind-merge": "^3.2.0",
"vaul": "^1.1.2",
- "zod": "^4.1.11"
+ "zod": "^4.1.11",
+ "zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -1162,13 +1169,60 @@
}
}
},
- "node_modules/@radix-ui/react-avatar": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.7.tgz",
- "integrity": "sha512-V7ODUt4mUoJTe3VUxZw6nfURxaPALVqmDQh501YmaQsk3D8AZQrOPRnfKn4H7JGDLBc0KqLhT94H79nV88ppNg==",
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz",
+ "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==",
+ "license": "MIT",
"dependencies": {
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-primitive": "2.1.0",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz",
+ "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-is-hydrated": "0.1.0",
"@radix-ui/react-use-layout-effect": "1.1.1"
@@ -1188,6 +1242,44 @@
}
}
},
+ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-checkbox": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.2.3.tgz",
@@ -1271,6 +1363,24 @@
}
}
},
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -1334,6 +1444,24 @@
}
}
},
+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-direction": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
@@ -1458,11 +1586,35 @@
}
},
"node_modules/@radix-ui/react-label": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.4.tgz",
- "integrity": "sha512-wy3dqizZnZVV4ja0FNnUhIWNwWdoldXrneEyUcVtLYDAt8ovGS4ridtMAOGgXBBIfggL4BOveVWsjXDORdGEQg==",
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
+ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
+ "license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.1.0"
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
},
"peerDependencies": {
"@types/react": "*",
@@ -1518,6 +1670,24 @@
}
}
},
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.10.tgz",
@@ -1553,6 +1723,260 @@
}
}
},
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-popper": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz",
@@ -1652,6 +2076,86 @@
}
}
},
+ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
+ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz",
@@ -1724,6 +2228,24 @@
}
}
},
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-separator": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz",
@@ -1747,9 +2269,86 @@
}
},
"node_modules/@radix-ui/react-slot": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
- "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
@@ -1764,17 +2363,18 @@
}
},
"node_modules/@radix-ui/react-tabs": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.9.tgz",
- "integrity": "sha512-KIjtwciYvquiW/wAFkELZCVnaNLBsYNhTNcvl+zfMAbMhRkcvNuCLXDDd22L0j7tagpzVh/QwbFpwAATg7ILPw==",
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
+ "license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/primitive": "1.1.3",
"@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.0",
- "@radix-ui/react-roving-focus": "1.1.7",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
@@ -1792,6 +2392,134 @@
}
}
},
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "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-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@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-tabs/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-toggle": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.6.tgz",
@@ -1877,6 +2605,24 @@
}
}
},
+ "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "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",
@@ -3831,6 +4577,34 @@
"node": ">= 0.4"
}
},
+ "node_modules/embla-carousel": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
+ "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
+ "license": "MIT"
+ },
+ "node_modules/embla-carousel-react": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
+ "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
+ "license": "MIT",
+ "dependencies": {
+ "embla-carousel": "8.6.0",
+ "embla-carousel-reactive-utils": "8.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/embla-carousel-reactive-utils": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
+ "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -7353,6 +8127,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/swiper": {
+ "version": "12.0.3",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-12.0.3.tgz",
+ "integrity": "sha512-BHd6U1VPEIksrXlyXjMmRWO0onmdNPaTAFduzqR3pgjvi7KfmUCAm/0cj49u2D7B0zNjMw02TSeXfinC1hDCXg==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/swiperjs"
+ },
+ {
+ "type": "open_collective",
+ "url": "http://opencollective.com/swiper"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.7.0"
+ }
+ },
"node_modules/synckit": {
"version": "0.11.4",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz",
@@ -7948,6 +8741,35 @@
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+ },
+ "node_modules/zustand": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
+ "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index cf16f14..2104582 100644
--- a/package.json
+++ b/package.json
@@ -16,16 +16,20 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@radix-ui/react-accordion": "^1.2.8",
- "@radix-ui/react-avatar": "^1.1.7",
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
+ "@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12",
- "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-navigation-menu": "^1.2.10",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4",
- "@radix-ui/react-slot": "^1.2.0",
- "@radix-ui/react-tabs": "^1.1.9",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toggle": "^1.1.6",
"@radix-ui/react-toggle-group": "^1.1.7",
"@radix-ui/react-tooltip": "^1.2.4",
@@ -36,6 +40,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
+ "embla-carousel-react": "^8.6.0",
"lucide-react": "^0.503.0",
"next": "^15.5.4",
"next-intl": "^4.3.9",
@@ -44,9 +49,11 @@
"react-dom": "^19.1.1",
"recharts": "^2.15.3",
"sonner": "^2.0.3",
+ "swiper": "^12.0.3",
"tailwind-merge": "^3.2.0",
"vaul": "^1.1.2",
- "zod": "^4.1.11"
+ "zod": "^4.1.11",
+ "zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -71,4 +78,4 @@
"eslint src --fix"
]
}
-}
\ No newline at end of file
+}
diff --git a/public/abstract-geometric-pattern.png b/public/abstract-geometric-pattern.png
new file mode 100644
index 0000000..416fa5a
Binary files /dev/null and b/public/abstract-geometric-pattern.png differ
diff --git a/public/adidas-ultraboost-running-shoes.jpg b/public/adidas-ultraboost-running-shoes.jpg
new file mode 100644
index 0000000..25d9a24
Binary files /dev/null and b/public/adidas-ultraboost-running-shoes.jpg differ
diff --git a/public/ahmad-tea-earl-grey-box.jpg b/public/ahmad-tea-earl-grey-box.jpg
new file mode 100644
index 0000000..66b0d85
Binary files /dev/null and b/public/ahmad-tea-earl-grey-box.jpg differ
diff --git a/public/apple-icon.png b/public/apple-icon.png
new file mode 100644
index 0000000..f9418b4
Binary files /dev/null and b/public/apple-icon.png differ
diff --git a/public/apple-ipad-pro-tablet.jpg b/public/apple-ipad-pro-tablet.jpg
new file mode 100644
index 0000000..2e96206
Binary files /dev/null and b/public/apple-ipad-pro-tablet.jpg differ
diff --git a/public/apple-juice-carton.jpg b/public/apple-juice-carton.jpg
new file mode 100644
index 0000000..d1f2b96
Binary files /dev/null and b/public/apple-juice-carton.jpg differ
diff --git a/public/apple-macbook-pro-laptop.jpg b/public/apple-macbook-pro-laptop.jpg
new file mode 100644
index 0000000..75fb024
Binary files /dev/null and b/public/apple-macbook-pro-laptop.jpg differ
diff --git a/public/apple-watch-series-9-smartwatch.jpg b/public/apple-watch-series-9-smartwatch.jpg
new file mode 100644
index 0000000..f7a0177
Binary files /dev/null and b/public/apple-watch-series-9-smartwatch.jpg differ
diff --git a/public/bon-aqua-water-bottle.jpg b/public/bon-aqua-water-bottle.jpg
new file mode 100644
index 0000000..70c8de1
Binary files /dev/null and b/public/bon-aqua-water-bottle.jpg differ
diff --git a/public/bottled-water-nestle.jpg b/public/bottled-water-nestle.jpg
new file mode 100644
index 0000000..13d9e15
Binary files /dev/null and b/public/bottled-water-nestle.jpg differ
diff --git a/public/burn-energy-drink.jpg b/public/burn-energy-drink.jpg
new file mode 100644
index 0000000..5cace3c
Binary files /dev/null and b/public/burn-energy-drink.jpg differ
diff --git a/public/canon-eos-r6-camera.jpg b/public/canon-eos-r6-camera.jpg
new file mode 100644
index 0000000..6cb7fbc
Binary files /dev/null and b/public/canon-eos-r6-camera.jpg differ
diff --git a/public/classic-coca-cola.png b/public/classic-coca-cola.png
new file mode 100644
index 0000000..e373d75
Binary files /dev/null and b/public/classic-coca-cola.png differ
diff --git a/public/clear-soda-bottle.png b/public/clear-soda-bottle.png
new file mode 100644
index 0000000..4e631b3
Binary files /dev/null and b/public/clear-soda-bottle.png differ
diff --git a/public/contact/Telegram.png b/public/contact/Telegram.png
new file mode 100644
index 0000000..c45f3d4
Binary files /dev/null and b/public/contact/Telegram.png differ
diff --git a/public/dyson-v15-detect-vacuum-cleaner.jpg b/public/dyson-v15-detect-vacuum-cleaner.jpg
new file mode 100644
index 0000000..77c5f9a
Binary files /dev/null and b/public/dyson-v15-detect-vacuum-cleaner.jpg differ
diff --git a/public/fanta-orange-bottle.png b/public/fanta-orange-bottle.png
new file mode 100644
index 0000000..6ab09af
Binary files /dev/null and b/public/fanta-orange-bottle.png differ
diff --git a/public/flags/ru.png b/public/flags/ru.png
new file mode 100644
index 0000000..a662dec
Binary files /dev/null and b/public/flags/ru.png differ
diff --git a/public/flags/uz.png b/public/flags/uz.png
new file mode 100644
index 0000000..a589e0d
Binary files /dev/null and b/public/flags/uz.png differ
diff --git a/public/fonts/Poppins-Black.ttf b/public/fonts/Poppins-Black.ttf
new file mode 100644
index 0000000..71c0f99
Binary files /dev/null and b/public/fonts/Poppins-Black.ttf differ
diff --git a/public/fonts/Poppins-Bold.ttf b/public/fonts/Poppins-Bold.ttf
new file mode 100644
index 0000000..00559ee
Binary files /dev/null and b/public/fonts/Poppins-Bold.ttf differ
diff --git a/public/fonts/Poppins-ExtraBold.ttf b/public/fonts/Poppins-ExtraBold.ttf
new file mode 100644
index 0000000..df70936
Binary files /dev/null and b/public/fonts/Poppins-ExtraBold.ttf differ
diff --git a/public/fonts/Poppins-Medium.ttf b/public/fonts/Poppins-Medium.ttf
new file mode 100644
index 0000000..6bcdcc2
Binary files /dev/null and b/public/fonts/Poppins-Medium.ttf differ
diff --git a/public/fonts/Poppins-Regular.ttf b/public/fonts/Poppins-Regular.ttf
new file mode 100644
index 0000000..9f0c71b
Binary files /dev/null and b/public/fonts/Poppins-Regular.ttf differ
diff --git a/public/fonts/Poppins-SemiBold.ttf b/public/fonts/Poppins-SemiBold.ttf
new file mode 100644
index 0000000..74c726e
Binary files /dev/null and b/public/fonts/Poppins-SemiBold.ttf differ
diff --git a/public/greenfield-green-tea.jpg b/public/greenfield-green-tea.jpg
new file mode 100644
index 0000000..375012a
Binary files /dev/null and b/public/greenfield-green-tea.jpg differ
diff --git a/public/icon-dark-32x32.png b/public/icon-dark-32x32.png
new file mode 100644
index 0000000..12c825a
Binary files /dev/null and b/public/icon-dark-32x32.png differ
diff --git a/public/icon-light-32x32.png b/public/icon-light-32x32.png
new file mode 100644
index 0000000..a3462cc
Binary files /dev/null and b/public/icon-light-32x32.png differ
diff --git a/public/icon.svg b/public/icon.svg
new file mode 100644
index 0000000..5c11e6c
--- /dev/null
+++ b/public/icon.svg
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/public/jacobs-coffee-jar.jpg b/public/jacobs-coffee-jar.jpg
new file mode 100644
index 0000000..fbce4fe
Binary files /dev/null and b/public/jacobs-coffee-jar.jpg differ
diff --git a/public/large-water-bottle-5l.jpg b/public/large-water-bottle-5l.jpg
new file mode 100644
index 0000000..146d1f0
Binary files /dev/null and b/public/large-water-bottle-5l.jpg differ
diff --git a/public/lg-oled-tv-55-inch.jpg b/public/lg-oled-tv-55-inch.jpg
new file mode 100644
index 0000000..6316bb8
Binary files /dev/null and b/public/lg-oled-tv-55-inch.jpg differ
diff --git a/public/milka-chocolate-bar.jpg b/public/milka-chocolate-bar.jpg
new file mode 100644
index 0000000..7db6ffb
Binary files /dev/null and b/public/milka-chocolate-bar.jpg differ
diff --git a/public/monster-energy-drink.jpg b/public/monster-energy-drink.jpg
new file mode 100644
index 0000000..15b7622
Binary files /dev/null and b/public/monster-energy-drink.jpg differ
diff --git a/public/multifruit-juice.jpg b/public/multifruit-juice.jpg
new file mode 100644
index 0000000..f40501f
Binary files /dev/null and b/public/multifruit-juice.jpg differ
diff --git a/public/nescafe-gold-jar.jpg b/public/nescafe-gold-jar.jpg
new file mode 100644
index 0000000..535612c
Binary files /dev/null and b/public/nescafe-gold-jar.jpg differ
diff --git a/public/new-balance-574-classic-shoes.jpg b/public/new-balance-574-classic-shoes.jpg
new file mode 100644
index 0000000..38f2675
Binary files /dev/null and b/public/new-balance-574-classic-shoes.jpg differ
diff --git a/public/nike-air-max-270.png b/public/nike-air-max-270.png
new file mode 100644
index 0000000..ad9f636
Binary files /dev/null and b/public/nike-air-max-270.png differ
diff --git a/public/orange-juice-carton.jpg b/public/orange-juice-carton.jpg
new file mode 100644
index 0000000..7fe2d50
Binary files /dev/null and b/public/orange-juice-carton.jpg differ
diff --git a/public/pepsi-bottle.jpg b/public/pepsi-bottle.jpg
new file mode 100644
index 0000000..b4220d3
Binary files /dev/null and b/public/pepsi-bottle.jpg differ
diff --git a/public/placeholder-logo.png b/public/placeholder-logo.png
new file mode 100644
index 0000000..8a792ac
Binary files /dev/null and b/public/placeholder-logo.png differ
diff --git a/public/placeholder-logo.svg b/public/placeholder-logo.svg
new file mode 100644
index 0000000..b1695aa
--- /dev/null
+++ b/public/placeholder-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg
new file mode 100644
index 0000000..6fa7543
Binary files /dev/null and b/public/placeholder-user.jpg differ
diff --git a/public/placeholder.jpg b/public/placeholder.jpg
new file mode 100644
index 0000000..6bfe963
Binary files /dev/null and b/public/placeholder.jpg differ
diff --git a/public/placeholder.svg b/public/placeholder.svg
new file mode 100644
index 0000000..e763910
--- /dev/null
+++ b/public/placeholder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/puma-rs-x-sneakers.jpg b/public/puma-rs-x-sneakers.jpg
new file mode 100644
index 0000000..c2f92ac
Binary files /dev/null and b/public/puma-rs-x-sneakers.jpg differ
diff --git a/public/red-bull-energy-drink.jpg b/public/red-bull-energy-drink.jpg
new file mode 100644
index 0000000..b86744e
Binary files /dev/null and b/public/red-bull-energy-drink.jpg differ
diff --git a/public/samsung-galaxy-s24-smartphone.jpg b/public/samsung-galaxy-s24-smartphone.jpg
new file mode 100644
index 0000000..7f242c9
Binary files /dev/null and b/public/samsung-galaxy-s24-smartphone.jpg differ
diff --git a/public/samsung-modern-refrigerator.jpg b/public/samsung-modern-refrigerator.jpg
new file mode 100644
index 0000000..e07b848
Binary files /dev/null and b/public/samsung-modern-refrigerator.jpg differ
diff --git a/public/small-mineral-water-bottle.jpg b/public/small-mineral-water-bottle.jpg
new file mode 100644
index 0000000..13bce97
Binary files /dev/null and b/public/small-mineral-water-bottle.jpg differ
diff --git a/public/sony-wh-1000xm5.png b/public/sony-wh-1000xm5.png
new file mode 100644
index 0000000..a8f6ba2
Binary files /dev/null and b/public/sony-wh-1000xm5.png differ
diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx
new file mode 100644
index 0000000..02d09b7
--- /dev/null
+++ b/src/app/[locale]/auth/page.tsx
@@ -0,0 +1,11 @@
+import Login from '@/features/auth/ui/Login';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/cart/order/page.tsx b/src/app/[locale]/cart/order/page.tsx
new file mode 100644
index 0000000..6728f70
--- /dev/null
+++ b/src/app/[locale]/cart/order/page.tsx
@@ -0,0 +1,11 @@
+import OrderPage from '@/features/cart/ui/OrderPage';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/cart/page.tsx b/src/app/[locale]/cart/page.tsx
new file mode 100644
index 0000000..03bfd0d
--- /dev/null
+++ b/src/app/[locale]/cart/page.tsx
@@ -0,0 +1,11 @@
+import CartPage from '@/features/cart/ui/CartPage';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/category/[categoryId]/[subId]/page.tsx b/src/app/[locale]/category/[categoryId]/[subId]/page.tsx
new file mode 100644
index 0000000..bb01546
--- /dev/null
+++ b/src/app/[locale]/category/[categoryId]/[subId]/page.tsx
@@ -0,0 +1,11 @@
+import Product from '@/features/category/ui/Product';
+
+const page = () => {
+ return (
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/category/[categoryId]/page.tsx b/src/app/[locale]/category/[categoryId]/page.tsx
new file mode 100644
index 0000000..de0dae1
--- /dev/null
+++ b/src/app/[locale]/category/[categoryId]/page.tsx
@@ -0,0 +1,11 @@
+import SubCategory from '@/features/category/ui/SubCategory';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/category/page.tsx b/src/app/[locale]/category/page.tsx
new file mode 100644
index 0000000..479f090
--- /dev/null
+++ b/src/app/[locale]/category/page.tsx
@@ -0,0 +1,11 @@
+import Category from '@/features/category/ui/Category';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/favourite/page.tsx b/src/app/[locale]/favourite/page.tsx
new file mode 100644
index 0000000..502b9e4
--- /dev/null
+++ b/src/app/[locale]/favourite/page.tsx
@@ -0,0 +1,11 @@
+import Favourite from '@/features/favourite/ui/Favourite';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/layout-shell.tsx b/src/app/[locale]/layout-shell.tsx
new file mode 100644
index 0000000..658f25a
--- /dev/null
+++ b/src/app/[locale]/layout-shell.tsx
@@ -0,0 +1,27 @@
+'use client';
+
+import { usePathname } from '@/shared/config/i18n/navigation';
+import Footer from '@/widgets/footer/ui';
+import Navbar from '@/widgets/navbar/ui';
+
+const HIDE_FOOTER_ROUTES = ['/auth', '/checkout'];
+
+export default function LayoutShell({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const pathname = usePathname();
+
+ const hideFooter = HIDE_FOOTER_ROUTES.some((route) =>
+ pathname.startsWith(route),
+ );
+
+ return (
+ <>
+
+ {children}
+ {!hideFooter && }
+ >
+ );
+}
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index ede921c..ba40a4b 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -1,17 +1,16 @@
-import type { Metadata } from 'next';
-import '../globals.css';
-import { golosText } from '@/shared/config/fonts';
+import { poppins } from '@/shared/config/fonts';
+import { routing } from '@/shared/config/i18n/routing';
+import QueryProvider from '@/shared/config/react-query/QueryProvider';
import { ThemeProvider } from '@/shared/config/theme-provider';
import { PRODUCT_INFO } from '@/shared/constants/data';
+import type { Metadata } from 'next';
import { hasLocale, Locale, NextIntlClientProvider } from 'next-intl';
-import { routing } from '@/shared/config/i18n/routing';
-import { notFound } from 'next/navigation';
-import Footer from '@/widgets/footer/ui';
-import Navbar from '@/widgets/navbar/ui';
-import { ReactNode } from 'react';
import { setRequestLocale } from 'next-intl/server';
-import QueryProvider from '@/shared/config/react-query/QueryProvider';
+import { notFound } from 'next/navigation';
import Script from 'next/script';
+import { ReactNode } from 'react';
+import '../globals.css';
+import LayoutShell from './layout-shell';
export const metadata: Metadata = {
title: PRODUCT_INFO.name,
@@ -39,7 +38,7 @@ export default async function RootLayout({ children, params }: Props) {
return (
-
+
-
- {children}
-
+ {children}
diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx
index e70e0a8..6633530 100644
--- a/src/app/[locale]/page.tsx
+++ b/src/app/[locale]/page.tsx
@@ -1,13 +1,14 @@
-import { getPosts } from '@/shared/config/api/testApi';
-import Welcome from '@/widgets/welcome';
+import { subCategoriesData } from '@/features/category/lib/data';
+import { CategoryCarousel } from '@/widgets/categories/ui/category-carousel';
+import Welcome from '@/widgets/welcome/ui';
export default async function Home() {
- const res = await getPosts({ _limit: 1 });
- console.log('SSR res', res.data);
-
return (
+ {subCategoriesData.slice(0, 6).map((e) => (
+
+ ))}
);
}
diff --git a/src/app/[locale]/product/[product]/page.tsx b/src/app/[locale]/product/[product]/page.tsx
new file mode 100644
index 0000000..d63eac4
--- /dev/null
+++ b/src/app/[locale]/product/[product]/page.tsx
@@ -0,0 +1,11 @@
+import ProductDetail from '@/features/product/ui/Product';
+
+const page = () => {
+ return (
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/profile/page.tsx b/src/app/[locale]/profile/page.tsx
new file mode 100644
index 0000000..a35f7ae
--- /dev/null
+++ b/src/app/[locale]/profile/page.tsx
@@ -0,0 +1,11 @@
+import Profile from '@/features/profile/ui/Profile';
+
+const page = () => {
+ return (
+
+ );
+};
+
+export default page;
diff --git a/src/app/[locale]/search/page.tsx b/src/app/[locale]/search/page.tsx
new file mode 100644
index 0000000..de5fbc7
--- /dev/null
+++ b/src/app/[locale]/search/page.tsx
@@ -0,0 +1,11 @@
+import SearchResult from '@/features/search/ui/Search';
+
+const page = () => {
+ return (
+
+
+
+ );
+};
+
+export default page;
diff --git a/src/assets/banner.webp b/src/assets/banner.webp
new file mode 100644
index 0000000..50347e0
Binary files /dev/null and b/src/assets/banner.webp differ
diff --git a/src/assets/water-bottle.png b/src/assets/water-bottle.png
new file mode 100644
index 0000000..e2fdb75
Binary files /dev/null and b/src/assets/water-bottle.png differ
diff --git a/src/features/auth/ui/Login.tsx b/src/features/auth/ui/Login.tsx
new file mode 100644
index 0000000..825c538
--- /dev/null
+++ b/src/features/auth/ui/Login.tsx
@@ -0,0 +1,321 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import formatPhone from '@/shared/lib/formatPhone';
+import { Input } from '@/shared/ui/input';
+import { ArrowRight, Check, Lock, Phone } from 'lucide-react';
+import { useEffect, useRef, useState } from 'react';
+
+type Step = 'phone' | 'otp';
+
+const Login = () => {
+ const [step, setStep] = useState('phone');
+ const [phoneNumber, setPhoneNumber] = useState('+998');
+ const [otp, setOtp] = useState(['', '', '', '', '', '']);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [countdown, setCountdown] = useState(60);
+ const [canResend, setCanResend] = useState(false);
+ const router = useRouter();
+
+ const otpInputs = useRef>([]);
+
+ /* Countdown */
+ useEffect(() => {
+ if (step === 'otp' && countdown > 0) {
+ const timer = setTimeout(() => setCountdown((c) => c - 1), 1000);
+ return () => clearTimeout(timer);
+ }
+
+ if (countdown === 0) {
+ setCanResend(true);
+ }
+ }, [countdown, step]);
+
+ /* Phone submit */
+ const handlePhoneSubmit = (): void => {
+ setError('');
+
+ if (phoneNumber.length < 9) {
+ setError("Telefon raqamni to'liq kiriting");
+ return;
+ }
+
+ setIsLoading(true);
+
+ setTimeout(() => {
+ setIsLoading(false);
+ setStep('otp');
+ setCountdown(60);
+ setCanResend(false);
+ }, 1500);
+ };
+
+ /* OTP change */
+ const handleOtpChange = (index: number, value: string): void => {
+ if (value && !/^\d$/.test(value)) return;
+
+ const newOtp = [...otp];
+ newOtp[index] = value;
+ setOtp(newOtp);
+
+ if (value && index < 5) {
+ otpInputs.current[index + 1]?.focus();
+ }
+
+ if (newOtp.every((d) => d !== '') && index === 5) {
+ handleOtpSubmit(newOtp);
+ }
+ };
+
+ /* OTP keydown */
+ const handleOtpKeyDown = (
+ index: number,
+ e: React.KeyboardEvent,
+ ): void => {
+ if (e.key === 'Backspace' && !otp[index] && index > 0) {
+ otpInputs.current[index - 1]?.focus();
+ }
+ };
+
+ /* OTP paste */
+ const handleOtpPaste = (e: React.ClipboardEvent): void => {
+ e.preventDefault();
+ const pasted = e.clipboardData.getData('text').slice(0, 6);
+
+ if (!/^\d+$/.test(pasted)) return;
+
+ const newOtp = pasted.split('');
+ setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]);
+
+ const lastIndex = Math.min(newOtp.length - 1, 5);
+ otpInputs.current[lastIndex]?.focus();
+
+ if (pasted.length === 6) {
+ setTimeout(() => handleOtpSubmit(newOtp), 100);
+ }
+ };
+
+ const handleOtpSubmit = (otpArray: string[] = otp): void => {
+ setError('');
+ const otpCode = otpArray.join('');
+
+ if (otpCode.length < 6) {
+ setError("Kodni to'liq kiriting");
+ return;
+ }
+
+ setIsLoading(true);
+
+ setTimeout(() => {
+ setIsLoading(false);
+
+ if (otpCode === '123456') {
+ localStorage.setItem('user', 'true');
+ router.push('/');
+ } else {
+ setError("Noto'g'ri kod. Qayta urinib ko'ring.");
+ setOtp(['', '', '', '', '', '']);
+ otpInputs.current[0]?.focus();
+ }
+ }, 1500);
+ };
+
+ /* Resend */
+ const handleResendOtp = (): void => {
+ if (!canResend) return;
+
+ setIsLoading(true);
+
+ setTimeout(() => {
+ setIsLoading(false);
+ setCountdown(60);
+ setCanResend(false);
+ setOtp(['', '', '', '', '', '']);
+ otpInputs.current[0]?.focus();
+ alert('Yangi kod yuborildi!');
+ }, 1000);
+ };
+
+ const handleChangeNumber = (): void => {
+ setStep('phone');
+ setPhoneNumber('');
+ setOtp(['', '', '', '', '', '']);
+ setError('');
+ setCountdown(60);
+ setCanResend(false);
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+ {step === 'phone' ? (
+
+ ) : (
+
+ )}
+
+
+ {step === 'phone' ? 'Xush kelibsiz!' : 'Kodni tasdiqlang'}
+
+
+ {step === 'phone'
+ ? 'Telefon raqamingizni kiriting'
+ : `${phoneNumber} raqamiga yuborilgan kodni kiriting`}
+
+
+
+ {/* Form */}
+
+ {step === 'phone' ? (
+ // Phone Number Step
+
+
+
+
+
{
+ const value = e.target.value.replace(/\D/g, '');
+ setPhoneNumber(value);
+ setError('');
+ }}
+ placeholder="+998 90 123-45-67"
+ maxLength={17}
+ className="w-full pl-12 pr-4 py-4 h-12 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-blue-500 transition text-lg"
+ />
+
+
+ {error &&
{error}
}
+
+
+
+
+ Davom etish orqali siz bizning{' '}
+
+ Foydalanish shartlari
+ {' '}
+ va{' '}
+
+ Maxfiylik siyosati
+
+ ga rozilik bildirasiz
+
+
+ ) : (
+ // OTP Step
+
+
+
+
+ {otp.map((digit, index) => (
+ {
+ otpInputs.current[index] = el;
+ }}
+ type="text"
+ inputMode="numeric"
+ maxLength={1}
+ value={digit}
+ onChange={(e) => handleOtpChange(index, e.target.value)}
+ onKeyDown={(e) => handleOtpKeyDown(index, e)}
+ className="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition"
+ />
+ ))}
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ {/* Resend OTP */}
+
+ {canResend ? (
+
+ ) : (
+
+ Kodni qayta yuborish ({countdown}s)
+
+ )}
+
+
+ {/* Change Number */}
+
+
+ {/* Demo info */}
+
+
+ Demo uchun:
+ {`Kod sifatida "123456" kiriting`}
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default Login;
diff --git a/src/features/cart/ui/CartPage.tsx b/src/features/cart/ui/CartPage.tsx
new file mode 100644
index 0000000..f74ab9c
--- /dev/null
+++ b/src/features/cart/ui/CartPage.tsx
@@ -0,0 +1,318 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import { Button } from '@/shared/ui/button';
+import { Input } from '@/shared/ui/input';
+import {
+ ArrowLeft,
+ CreditCard,
+ Minus,
+ Plus,
+ ShoppingBag,
+ Trash,
+ Truck,
+} from 'lucide-react';
+import Image from 'next/image';
+import { useState } from 'react';
+
+interface CartItem {
+ id: number;
+ name: string;
+ price: number;
+ oldPrice: number;
+ image: string;
+ quantity: number | string;
+ inStock: boolean;
+}
+
+const CartPage = () => {
+ const router = useRouter();
+ const [cartItems, setCartItems] = useState([
+ {
+ id: 5,
+ name: 'Coca-Cola 1.5L',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/classic-coca-cola.png',
+ quantity: 2,
+ inStock: true,
+ },
+ {
+ id: 6,
+ name: 'Pepsi 2L',
+ price: 11000,
+ oldPrice: 13000,
+ image: '/pepsi-bottle.jpg',
+ quantity: 1,
+ inStock: true,
+ },
+ {
+ id: 8,
+ name: 'Sprite 1.5L',
+ price: 10000,
+ oldPrice: 12000,
+ image: '/clear-soda-bottle.png',
+ quantity: 3,
+ inStock: true,
+ },
+ ]);
+
+ const subtotal = cartItems.reduce(
+ (sum, item) => sum + item.price * Number(item.quantity),
+ 0,
+ );
+ const discount = cartItems.reduce((sum, item) => {
+ if (item.oldPrice) {
+ return sum + (item.oldPrice - item.price) * Number(item.quantity);
+ }
+ return sum;
+ }, 0);
+ const deliveryFee = subtotal > 50000 ? 0 : 15000;
+ const total = subtotal - discount + deliveryFee;
+
+ const handleQuantityChange = (id: number, type: 'increase' | 'decrease') => {
+ setCartItems((prev) =>
+ prev.map((item) => {
+ if (item.id === id) {
+ if (type === 'increase')
+ return { ...item, quantity: Number(item.quantity) + 1 };
+ if (type === 'decrease' && Number(item.quantity) > 1)
+ return { ...item, quantity: Number(item.quantity) - 1 };
+ }
+ return item;
+ }),
+ );
+ };
+
+ // Remove item from cart
+ const handleRemoveItem = (id: number) => {
+ setCartItems((prev) => prev.filter((item) => item.id !== id));
+ };
+
+ const handleCheckout = () => {
+ router.push('/cart/order');
+ };
+
+ if (cartItems.length === 0) {
+ return (
+
+
+
+
+ {"Savatingiz bo'sh"}
+
+
+ {"Mahsulotlar qo'shish uchun katalogga o'ting"}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
Savat
+
{cartItems.length} ta mahsulot
+
+
+
+ {/* Cart Items */}
+
+
+ {cartItems.map((item, index) => (
+
+ {/* Product Image */}
+
+
+
+
+
+ {/* Product Info */}
+
+
{item.name}
+
+
+ {item.price.toLocaleString()} {"so'm"}
+
+ {item.oldPrice && (
+
+ {item.oldPrice.toLocaleString()} {"so'm"}
+
+ )}
+
+
+
+ {/* Quantity Controls */}
+
+
+
{
+ const value = e.target.value;
+ // Bo'sh qiymatga ruxsat berish
+ if (value === '') {
+ setCartItems((prev) =>
+ prev.map((cartItem) =>
+ cartItem.id === item.id
+ ? { ...cartItem, quantity: '' }
+ : cartItem,
+ ),
+ );
+ return;
+ }
+ const number = parseInt(value, 10);
+ if (!isNaN(number) && number > 0) {
+ setCartItems((prev) =>
+ prev.map((cartItem) =>
+ cartItem.id === item.id
+ ? { ...cartItem, quantity: number }
+ : cartItem,
+ ),
+ );
+ }
+ }}
+ className="w-16 text-center outline-none ring-0 focus-visible:ring-0 border-none font-semibold"
+ />
+
+
+
+
+
+ ))}
+
+
+
+ {/* Order Summary */}
+
+
+
Buyurtma xulasasi
+
+
+
+ Mahsulotlar narxi:
+
+ {subtotal.toLocaleString()} {"so'm"}
+
+
+
+ {discount > 0 && (
+
+ Chegirma:
+
+ -{discount.toLocaleString()} {"so'm"}
+
+
+ )}
+
+
+
+
+ Yetkazib berish:
+
+
+ {deliveryFee === 0 ? (
+
+ Bepul
+
+ ) : (
+ `${deliveryFee.toLocaleString()} so'm`
+ )}
+
+
+
+ {deliveryFee > 0 && (
+
+ {
+ "50,000 so'mdan ortiq xarid qiling va yetkazib berishni bepul oling!"
+ }
+
+ )}
+
+
+
+
+ Jami:
+
+ {total.toLocaleString()} {"so'm"}
+
+
+
+
+
+
+
+
+ {/* Additional Info */}
+
+
+
+ Tez yetkazib berish 1-2 kun ichida
+
+
+
+ {"Xavfsiz to'lov usullari"}
+
+
+
+
+
+
+
+ );
+};
+
+export default CartPage;
diff --git a/src/features/cart/ui/OrderPage.tsx b/src/features/cart/ui/OrderPage.tsx
new file mode 100644
index 0000000..e0b1eca
--- /dev/null
+++ b/src/features/cart/ui/OrderPage.tsx
@@ -0,0 +1,437 @@
+'use client';
+
+import formatPhone from '@/shared/lib/formatPhone';
+import { Input } from '@/shared/ui/input';
+import { Label } from '@/shared/ui/label';
+import { Textarea } from '@/shared/ui/textarea';
+import {
+ Building2,
+ CheckCircle2,
+ Clock,
+ CreditCard,
+ MapPin,
+ Package,
+ Truck,
+ User,
+ Wallet,
+} from 'lucide-react';
+import Image from 'next/image';
+import { useState } from 'react';
+
+const OrderPage = () => {
+ const [formData, setFormData] = useState({
+ fullName: '',
+ phone: '+998',
+ email: '',
+ city: '',
+ address: '',
+ postalCode: '',
+ notes: '',
+ });
+
+ const [paymentMethod, setPaymentMethod] = useState('cash');
+ const [deliveryMethod, setDeliveryMethod] = useState('standard');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [orderSuccess, setOrderSuccess] = useState(false);
+
+ // Cart items from previous page (in real app, this would come from context/store)
+ const cartItems = [
+ {
+ id: 5,
+ name: 'Coca-Cola 1.5L',
+ price: 12000,
+ quantity: 2,
+ image: '/classic-coca-cola.png',
+ },
+ {
+ id: 6,
+ name: 'Pepsi 2L',
+ price: 11000,
+ quantity: 1,
+ image: '/pepsi-bottle.jpg',
+ },
+ {
+ id: 8,
+ name: 'Sprite 1.5L',
+ price: 10000,
+ quantity: 3,
+ image: '/clear-soda-bottle.png',
+ },
+ ];
+
+ const subtotal = cartItems.reduce(
+ (sum, item) => sum + item.price * item.quantity,
+ 0,
+ );
+ const deliveryFee =
+ deliveryMethod === 'express' ? 25000 : subtotal > 50000 ? 0 : 15000;
+ const total = subtotal + deliveryFee;
+
+ const handleInputChange = (
+ e: React.ChangeEvent,
+ ) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsSubmitting(true);
+
+ // Simulate API call
+ setTimeout(() => {
+ setIsSubmitting(false);
+ setOrderSuccess(true);
+ }, 2000);
+ };
+
+ if (orderSuccess) {
+ return (
+
+
+
+
+
+
+ Buyurtma qabul qilindi!
+
+
+ Buyurtma raqami:{' '}
+
+ #ORD-{Math.floor(Math.random() * 10000)}
+
+
+
+ Buyurtmangiz muvaffaqiyatli qabul qilindi. Tez orada sizga aloqaga
+ chiqamiz.
+
+
+
+ Buyurtma holati haqida SMS orqali xabardor qilinasiz
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+ Buyurtmani rasmiylashtirish
+
+
{"Ma'lumotlaringizni to'ldiring"}
+
+
+
+
+
+ );
+};
+
+export default OrderPage;
diff --git a/src/features/category/lib/data.ts b/src/features/category/lib/data.ts
new file mode 100644
index 0000000..592ba02
--- /dev/null
+++ b/src/features/category/lib/data.ts
@@ -0,0 +1,1099 @@
+import type { ProductDetail } from '@/widgets/categories/lib/data';
+
+export interface SubCategory {
+ id: number;
+ name: string;
+ category: string;
+ products: ProductDetail[];
+}
+
+export const subCategoriesData: SubCategory[] = [
+ {
+ id: 1,
+ category: 'Ichimliklar',
+ name: 'Suvlar',
+ products: [
+ {
+ id: 1,
+ name: 'Nestle Pure Life 1.5L',
+ price: 3000,
+ oldPrice: 3500,
+ image: '/bottled-water-nestle.jpg',
+ rating: 4.5,
+ reviews: 128,
+ discount: 14,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 2,
+ name: 'Hydrolife 5L',
+ price: 8000,
+ oldPrice: 9000,
+ image: '/large-water-bottle-5l.jpg',
+ rating: 4.7,
+ reviews: 89,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 3,
+ name: 'Aqua Minerale 0.5L',
+ price: 2000,
+ oldPrice: 2500,
+ image: '/small-mineral-water-bottle.jpg',
+ rating: 4.3,
+ reviews: 56,
+ discount: 20,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 4,
+ name: 'Bon Aqua 1L',
+ price: 2500,
+ oldPrice: 3000,
+ image: '/bon-aqua-water-bottle.jpg',
+ rating: 4.4,
+ reviews: 72,
+ discount: 17,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 2,
+ category: 'Ichimliklar',
+ name: 'Gazlangan ichimliklar',
+ products: [
+ {
+ id: 5,
+ name: 'Coca-Cola 1.5L',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/classic-coca-cola.png',
+ rating: 4.8,
+ reviews: 342,
+ discount: 14,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 6,
+ name: 'Pepsi 2L',
+ price: 11000,
+ oldPrice: 13000,
+ image: '/pepsi-bottle.jpg',
+ rating: 4.6,
+ reviews: 215,
+ discount: 15,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 7,
+ name: 'Fanta Orange 1L',
+ price: 9000,
+ oldPrice: 10000,
+ image: '/fanta-orange-bottle.png',
+ rating: 4.4,
+ reviews: 178,
+ discount: 10,
+ inStock: false,
+ liked: false,
+ },
+ {
+ id: 8,
+ name: 'Sprite 1.5L',
+ price: 10000,
+ oldPrice: 12000,
+ image: '/clear-soda-bottle.png',
+ rating: 4.5,
+ reviews: 156,
+ discount: 17,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 3,
+ category: 'Ichimliklar',
+ name: 'Sharbatlar',
+ products: [
+ {
+ id: 9,
+ name: 'Rich Orange Juice 1L',
+ price: 15000,
+ oldPrice: 18000,
+ image: '/orange-juice-carton.jpg',
+ rating: 4.6,
+ reviews: 134,
+ discount: 17,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 10,
+ name: 'Добрый Apple Juice 1L',
+ price: 14000,
+ oldPrice: 16000,
+ image: '/apple-juice-carton.jpg',
+ rating: 4.5,
+ reviews: 98,
+ discount: 13,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 11,
+ name: 'J7 Multifruit 0.97L',
+ price: 16000,
+ oldPrice: 19000,
+ image: '/multifruit-juice.jpg',
+ rating: 4.7,
+ reviews: 112,
+ discount: 16,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Ichimliklar - Energetik ichimliklar
+ {
+ id: 4,
+ category: 'Ichimliklar',
+ name: 'Energetik ichimliklar',
+ products: [
+ {
+ id: 12,
+ name: 'Red Bull 250ml',
+ price: 18000,
+ oldPrice: 20000,
+ image: '/red-bull-energy-drink.jpg',
+ rating: 4.7,
+ reviews: 267,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 13,
+ name: 'Monster Energy 500ml',
+ price: 22000,
+ oldPrice: 25000,
+ image: '/monster-energy-drink.jpg',
+ rating: 4.6,
+ reviews: 198,
+ discount: 12,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 14,
+ name: 'Burn 250ml',
+ price: 15000,
+ oldPrice: 17000,
+ image: '/burn-energy-drink.jpg',
+ rating: 4.4,
+ reviews: 145,
+ discount: 12,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Ichimliklar - Choy va qahva
+ {
+ id: 5,
+ category: 'Ichimliklar',
+ name: 'Choy va qahva',
+ products: [
+ {
+ id: 15,
+ name: 'Ahmad Tea Earl Grey 100g',
+ price: 25000,
+ oldPrice: 28000,
+ image: '/ahmad-tea-earl-grey-box.jpg',
+ rating: 4.8,
+ reviews: 312,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 16,
+ name: 'Nescafe Gold 95g',
+ price: 45000,
+ oldPrice: 50000,
+ image: '/nescafe-gold-jar.jpg',
+ rating: 4.7,
+ reviews: 234,
+ discount: 10,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 17,
+ name: 'Greenfield Green Tea 100g',
+ price: 22000,
+ oldPrice: 26000,
+ image: '/greenfield-green-tea.jpg',
+ rating: 4.5,
+ reviews: 167,
+ discount: 15,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 18,
+ name: 'Jacobs Monarch 150g',
+ price: 55000,
+ oldPrice: 62000,
+ image: '/jacobs-coffee-jar.jpg',
+ rating: 4.6,
+ reviews: 189,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Shirinliklar - Shokoladlar
+ {
+ id: 6,
+ category: 'Shirinliklar',
+ name: 'Shokoladlar',
+ products: [
+ {
+ id: 19,
+ name: 'Milka Alpine Milk 100g',
+ price: 18000,
+ oldPrice: 20000,
+ image: '/milka-chocolate-bar.jpg',
+ rating: 4.9,
+ reviews: 456,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 20,
+ name: 'Snickers 50g',
+ price: 5000,
+ oldPrice: 6000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 289,
+ discount: 17,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 21,
+ name: 'Alpen Gold 90g',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 198,
+ discount: 14,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 22,
+ name: 'Twix 50g',
+ price: 6000,
+ oldPrice: 7000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 234,
+ discount: 14,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Shirinliklar - Konfetlar
+ {
+ category: 'Shirinliklar',
+ id: 7,
+ name: 'Konfetlar',
+ products: [
+ {
+ id: 23,
+ name: 'Rafaello 150g',
+ price: 35000,
+ oldPrice: 40000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.9,
+ reviews: 378,
+ discount: 13,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 24,
+ name: 'Ferrero Rocher 200g',
+ price: 65000,
+ oldPrice: 75000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.8,
+ reviews: 412,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 25,
+ name: 'Kinder Surprise',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 523,
+ discount: 14,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Shirinliklar - Pechene va vafli
+ {
+ id: 8,
+ category: 'Shirinliklar',
+ name: 'Pechene va vafli',
+ products: [
+ {
+ id: 26,
+ name: 'Oreo Original 154g',
+ price: 15000,
+ oldPrice: 17000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 267,
+ discount: 12,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 27,
+ name: 'Barni Bear 150g',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 189,
+ discount: 14,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 28,
+ name: 'KitKat Wafer 45g',
+ price: 7000,
+ oldPrice: 8000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 312,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Shirinliklar - Tortlar
+ {
+ category: 'Shirinliklar',
+ id: 9,
+ name: 'Tortlar',
+ products: [
+ {
+ id: 29,
+ name: 'Napoleon Tort 1kg',
+ price: 85000,
+ oldPrice: 95000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.8,
+ reviews: 156,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 30,
+ name: 'Shokoladli Tort 800g',
+ price: 75000,
+ oldPrice: 85000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 134,
+ discount: 12,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 31,
+ name: 'Medovik 1kg',
+ price: 90000,
+ oldPrice: 100000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.9,
+ reviews: 198,
+ discount: 10,
+ inStock: false,
+ liked: false,
+ },
+ ],
+ },
+ // Shirinliklar - Murabbo va asal
+ {
+ category: 'Shirinliklar',
+ id: 10,
+ name: 'Murabbo va asal',
+ products: [
+ {
+ id: 32,
+ name: 'Olcha Murabbo 400g',
+ price: 18000,
+ oldPrice: 20000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 89,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 33,
+ name: 'Tabiiy Asal 500g',
+ price: 45000,
+ oldPrice: 52000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.8,
+ reviews: 234,
+ discount: 13,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 34,
+ name: "O'rik Murabbo 400g",
+ price: 16000,
+ oldPrice: 18000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.4,
+ reviews: 67,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 11,
+ category: 'Moylar',
+ name: "Kungaboqar yog'i",
+ products: [
+ {
+ id: 35,
+ name: 'Oltin Kungaboqar 1L',
+ price: 22000,
+ oldPrice: 25000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 178,
+ discount: 12,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 36,
+ name: "Ideal Yog' 0.9L",
+ price: 20000,
+ oldPrice: 23000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 145,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 37,
+ name: 'Слобода Premium 1L',
+ price: 28000,
+ oldPrice: 32000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 198,
+ discount: 13,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 12,
+ category: 'Moylar',
+ name: "Zaytun yog'i",
+ products: [
+ {
+ id: 38,
+ name: 'Borges Extra Virgin 500ml',
+ price: 65000,
+ oldPrice: 75000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.8,
+ reviews: 234,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 39,
+ name: 'Filippo Berio 750ml',
+ price: 85000,
+ oldPrice: 95000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.9,
+ reviews: 189,
+ discount: 11,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 13,
+ category: 'Moylar',
+ name: "Sariyog'",
+ products: [
+ {
+ id: 40,
+ name: 'Anchor Butter 200g',
+ price: 35000,
+ oldPrice: 40000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 167,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 41,
+ name: 'President Butter 250g',
+ price: 42000,
+ oldPrice: 48000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.8,
+ reviews: 145,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 14,
+ category: 'Moylar',
+ name: 'Margarin',
+ products: [
+ {
+ id: 42,
+ name: 'Rama Classic 400g',
+ price: 18000,
+ oldPrice: 20000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.4,
+ reviews: 98,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 43,
+ name: 'Flora Light 500g',
+ price: 22000,
+ oldPrice: 25000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 112,
+ discount: 12,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ // Non mahsulotlari - Oq non
+ {
+ id: 15,
+ category: ' Non mahsulotlari',
+ name: 'Oq non',
+ products: [
+ {
+ id: 44,
+ name: 'Oq Non 500g',
+ price: 4000,
+ oldPrice: 4500,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 312,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 45,
+ name: 'Baget Non 300g',
+ price: 5000,
+ oldPrice: 5500,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 189,
+ discount: 9,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Non mahsulotlari - Qora non
+ {
+ id: 16,
+ category: 'Non mahsulotlari',
+ name: 'Qora non',
+ products: [
+ {
+ id: 46,
+ name: 'Borodinskiy Non 400g',
+ price: 6000,
+ oldPrice: 7000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 234,
+ discount: 14,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 47,
+ name: 'Javdar Non 450g',
+ price: 5500,
+ oldPrice: 6500,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 156,
+ discount: 15,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 17,
+ category: 'Non mahsulotlari',
+ name: 'Bulochkalar',
+ products: [
+ {
+ id: 48,
+ name: 'Smetanali Bulochka 4dona',
+ price: 8000,
+ oldPrice: 9000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 145,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 49,
+ name: 'Shokoladli Bulochka 6dona',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 198,
+ discount: 14,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 18,
+ category: 'Non mahsulotlari',
+ name: 'Lavash va pita',
+ products: [
+ {
+ id: 50,
+ name: 'Lavash Yupqa 5dona',
+ price: 7000,
+ oldPrice: 8000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 167,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 51,
+ name: 'Pita Non 6dona',
+ price: 9000,
+ oldPrice: 10000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 134,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Non mahsulotlari - Kekslar
+ {
+ category: 'Non mahsulotlari',
+ id: 19,
+ name: 'Kekslar',
+ products: [
+ {
+ id: 52,
+ name: 'Vanilli Keks 400g',
+ price: 25000,
+ oldPrice: 28000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 112,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 53,
+ name: 'Shokoladli Keks 450g',
+ price: 28000,
+ oldPrice: 32000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 145,
+ discount: 13,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 20,
+ category: 'Go‘sht',
+ name: "Mol go'shti",
+ products: [
+ {
+ id: 54,
+ name: "Mol Go'shti 1kg",
+ price: 85000,
+ oldPrice: 95000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 289,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 55,
+ name: 'Mol Bifshteks 500g',
+ price: 55000,
+ oldPrice: 62000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.8,
+ reviews: 234,
+ discount: 11,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 21,
+ category: 'Go‘sht',
+ name: "Qo'y go'shti",
+ products: [
+ {
+ id: 56,
+ name: "Qo'y Go'shti 1kg",
+ price: 95000,
+ oldPrice: 105000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 178,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 57,
+ name: "Qo'y Qovurg'a 800g",
+ price: 78000,
+ oldPrice: 88000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 145,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ // Go'sht - Tovuq go'shti
+ {
+ id: 22,
+ category: 'Go‘sht',
+ name: "Tovuq go'shti",
+ products: [
+ {
+ id: 58,
+ name: 'Tovuq Butun 1.5kg',
+ price: 45000,
+ oldPrice: 50000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 312,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 59,
+ name: 'Tovuq Filesi 1kg',
+ price: 55000,
+ oldPrice: 62000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 267,
+ discount: 11,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 60,
+ name: 'Tovuq Qanotchasi 1kg',
+ price: 35000,
+ oldPrice: 40000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 198,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 23,
+ category: 'Go‘sht',
+ name: "Kurka go'shti",
+ products: [
+ {
+ id: 61,
+ name: 'Kurka Filesi 1kg',
+ price: 65000,
+ oldPrice: 72000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 134,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 62,
+ name: 'Kurka Soni 800g',
+ price: 52000,
+ oldPrice: 58000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 98,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 24,
+ category: 'Go‘sht',
+ name: 'Qiyma mahsulotlar',
+ products: [
+ {
+ id: 63,
+ name: 'Mol Qiyma 1kg',
+ price: 65000,
+ oldPrice: 72000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 234,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 64,
+ name: 'Tovuq Qiyma 1kg',
+ price: 42000,
+ oldPrice: 48000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 178,
+ discount: 13,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 65,
+ name: 'Aralash Qiyma 1kg',
+ price: 55000,
+ oldPrice: 62000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.4,
+ reviews: 145,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 25,
+ category: 'Boshqa',
+ name: 'Ziravorlar',
+ products: [
+ {
+ id: 66,
+ name: 'Qora Murch 50g',
+ price: 8000,
+ oldPrice: 9000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 156,
+ discount: 11,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 67,
+ name: 'Zira 100g',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 189,
+ discount: 14,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 68,
+ name: 'Paprika 80g',
+ price: 10000,
+ oldPrice: 12000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 112,
+ discount: 17,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+ {
+ id: 26,
+ category: 'Boshqa',
+ name: 'Konserva mahsulotlari',
+ products: [
+ {
+ id: 69,
+ name: 'Tuna Konserva 185g',
+ price: 28000,
+ oldPrice: 32000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 198,
+ discount: 13,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 70,
+ name: 'Pomidor Pasta 400g',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 234,
+ discount: 14,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 71,
+ name: 'Loviya Konserva 400g',
+ price: 9000,
+ oldPrice: 10000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.4,
+ reviews: 145,
+ discount: 10,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 27,
+ name: 'Soslar',
+ category: 'Boshqa',
+ products: [
+ {
+ id: 72,
+ name: 'Heinz Ketchup 570g',
+ price: 22000,
+ oldPrice: 25000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.7,
+ reviews: 312,
+ discount: 12,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 73,
+ name: 'Calve Mayonez 400g',
+ price: 18000,
+ oldPrice: 20000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.6,
+ reviews: 267,
+ discount: 10,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 74,
+ name: 'Soya Sousi 250ml',
+ price: 15000,
+ oldPrice: 17000,
+ image: '/placeholder.svg?height=200&width=200',
+ rating: 4.5,
+ reviews: 178,
+ discount: 12,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+];
diff --git a/src/features/category/ui/Category.tsx b/src/features/category/ui/Category.tsx
new file mode 100644
index 0000000..fadfbfa
--- /dev/null
+++ b/src/features/category/ui/Category.tsx
@@ -0,0 +1,36 @@
+'use client';
+import { useRouter } from '@/shared/config/i18n/navigation';
+import { categoryList, CategoryType } from '@/widgets/welcome/lib/data';
+import { ChevronRight } from 'lucide-react';
+
+const Category = () => {
+ const router = useRouter();
+ const handleCategoryClick = (category: CategoryType) => {
+ router.push(`/category/${category.name}`);
+ };
+
+ return (
+
+
+
+ Kategoriyalar
+
+
+
+ {categoryList.map((category, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default Category;
diff --git a/src/features/category/ui/Product.tsx b/src/features/category/ui/Product.tsx
new file mode 100644
index 0000000..f8ed914
--- /dev/null
+++ b/src/features/category/ui/Product.tsx
@@ -0,0 +1,76 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import { ProductCard } from '@/widgets/categories/ui/product-card';
+import { ArrowLeft } from 'lucide-react';
+import { useParams } from 'next/navigation';
+import { useState } from 'react';
+import { subCategoriesData } from '../lib/data';
+
+const Product = () => {
+ const { subId } = useParams();
+ const router = useRouter();
+
+ const decodedSubId = decodeURIComponent(subId as string);
+
+ const subCategory =
+ subCategoriesData.find((cat) => cat.name === decodedSubId) ||
+ subCategoriesData[0];
+ const [products, setProducts] = useState(subCategory.products);
+ const handleBack = () => {
+ router.back();
+ };
+
+ const handleRemove = (id: number) => {
+ setProducts((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: false } : product,
+ ),
+ );
+ };
+
+ const handleLiked = (id: number) => {
+ setProducts((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: true } : product,
+ ),
+ );
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+ {decodedSubId}
+
+
+ {subCategory.products.length} ta mahsulot
+
+
+
+
+ {products.map((product) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default Product;
diff --git a/src/features/category/ui/SubCategory.tsx b/src/features/category/ui/SubCategory.tsx
new file mode 100644
index 0000000..9bac2aa
--- /dev/null
+++ b/src/features/category/ui/SubCategory.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import { categoryList } from '@/widgets/welcome/lib/data';
+import { ChevronRight } from 'lucide-react';
+import { useParams } from 'next/navigation';
+
+const SubCategory = () => {
+ const { categoryId } = useParams();
+
+ const router = useRouter();
+ const category =
+ categoryList.find((cat) => cat.name === categoryId) || categoryList[0];
+
+ const handleSubCategoryClick = (subCategory: { name: string }) => {
+ router.push(`/category/${categoryId}/${subCategory.name}`);
+ };
+
+ return (
+
+
+
+ {category.name}
+
+
+
+ {category.subCategories.map((subCategory, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default SubCategory;
diff --git a/src/features/favourite/ui/Favourite.tsx b/src/features/favourite/ui/Favourite.tsx
new file mode 100644
index 0000000..f5e3930
--- /dev/null
+++ b/src/features/favourite/ui/Favourite.tsx
@@ -0,0 +1,145 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import { Button } from '@/shared/ui/button';
+import { ProductCard } from '@/widgets/categories/ui/product-card';
+import { Heart } from 'lucide-react';
+import { useState } from 'react';
+
+// Fake data
+const LIKED_PRODUCTS = [
+ {
+ id: 1,
+ name: 'Samsung Galaxy S23 Ultra 256GB, Phantom Black',
+ price: 12500000,
+ oldPrice: 15000000,
+ image: 'https://images.unsplash.com/photo-1610945415295-d9bbf067e59c?w=400',
+ rating: 4.8,
+ reviews: 342,
+ discount: 17,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 2,
+ name: 'Apple AirPods Pro 2-chi avlod (USB-C)',
+ price: 2850000,
+ oldPrice: 3200000,
+ image: 'https://images.unsplash.com/photo-1606841837239-c5a1a4a07af7?w=400',
+ rating: 4.9,
+ reviews: 567,
+ discount: 11,
+ liked: true,
+ inStock: true,
+ },
+ {
+ id: 3,
+ name: "Sony PlayStation 5 Slim 1TB + 2 ta o'yin",
+ price: 7500000,
+ oldPrice: 8500000,
+ image: 'https://images.unsplash.com/photo-1606813907291-d86efa9b94db?w=400',
+ rating: 4.7,
+ reviews: 234,
+ discount: 12,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 4,
+ name: 'MacBook Air 13 M2 chip, 8GB RAM, 256GB SSD',
+ price: 14200000,
+ oldPrice: 16000000,
+ image: 'https://images.unsplash.com/photo-1517336714731-489689fd1ca8?w=400',
+ rating: 4.9,
+ reviews: 891,
+ liked: true,
+ discount: 11,
+ inStock: true,
+ },
+ {
+ id: 5,
+ name: 'Dyson V15 Detect Simsiz Changyutgich',
+ price: 6800000,
+ oldPrice: 7800000,
+ image: 'https://images.unsplash.com/photo-1558317374-067fb5f30001?w=400',
+ rating: 4.6,
+ reviews: 178,
+ discount: 13,
+ liked: true,
+ inStock: false,
+ },
+ {
+ id: 6,
+ name: 'Nike Air Max 270 React Erkaklar Krosovkasi',
+ price: 1250000,
+ oldPrice: 1650000,
+ image: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400',
+ rating: 4.5,
+ liked: true,
+ reviews: 423,
+ discount: 24,
+ inStock: true,
+ },
+];
+
+export default function Favourite() {
+ const [likedProducts, setLikedProducts] = useState(LIKED_PRODUCTS);
+ const router = useRouter();
+
+ const handleRemove = (id: number) => {
+ setLikedProducts((prev) => prev.filter((product) => product.id !== id));
+ };
+
+ if (likedProducts.length === 0) {
+ return (
+
+
+
+
+
+
+
+ {"Sevimlilar bo'sh"}
+
+
+ {`Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz.
+ Mahsulotlar ro'yxatiga o'ting va yoqqan mahsulotlaringizni
+ saqlang.`}
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ Sevimli mahsulotlar
+
+
{likedProducts.length} ta mahsulot
+
+
+
+
+ {likedProducts.map((product) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/features/product/ui/Product.tsx b/src/features/product/ui/Product.tsx
new file mode 100644
index 0000000..d2cfe46
--- /dev/null
+++ b/src/features/product/ui/Product.tsx
@@ -0,0 +1,360 @@
+'use client';
+
+import { Carousel, CarouselContent, CarouselItem } from '@/shared/ui/carousel';
+import { ProductCard } from '@/widgets/categories/ui/product-card';
+import {
+ Heart,
+ Minus,
+ Plus,
+ RotateCcw,
+ Shield,
+ ShoppingCart,
+ Star,
+ Truck,
+} from 'lucide-react';
+import Image from 'next/image';
+import { useState } from 'react';
+
+const ProductDetail = () => {
+ const [quantity, setQuantity] = useState(1);
+ const [selectedImage, setSelectedImage] = useState(0);
+ const [liked, setLiked] = useState(false);
+
+ // Fake product data
+ const product = {
+ id: 5,
+ name: 'Coca-Cola 1.5L',
+ price: 12000,
+ oldPrice: 14000,
+ image: '/classic-coca-cola.png',
+ rating: 4.8,
+ reviews: 342,
+ discount: 14,
+ inStock: true,
+ description:
+ "Coca-Cola klassik ta'mi bilan ajoyib gazlangan ichimlik. 1.5 litrlik shisha butilkada. Sovuq holda iste'mol qilish tavsiya etiladi.",
+ category: 'Ichimliklar',
+ brand: 'Coca-Cola',
+ volume: '1.5L',
+ images: [
+ '/classic-coca-cola.png',
+ '/clear-soda-bottle.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ '/classic-coca-cola.png',
+ ],
+ specifications: {
+ Hajmi: '1.5 litr',
+ 'Qadoq turi': 'Plastik butilka',
+ 'Ishlab chiqaruvchi': 'Coca-Cola Company',
+ 'Saqlash muddati': '12 oy',
+ 'Energiya qiymati': '180 kJ / 43 kcal',
+ },
+ };
+
+ const [relatedProducts, setRelatedProducts] = useState([
+ {
+ id: 6,
+ name: 'Pepsi 2L',
+ price: 11000,
+ reviews: 342,
+ liked: false,
+ inStock: true,
+ oldPrice: 13000,
+ image: '/pepsi-bottle.jpg',
+ rating: 4.6,
+ discount: 15,
+ },
+ {
+ id: 8,
+ name: 'Sprite 1.5L',
+ price: 10000,
+ inStock: true,
+ oldPrice: 12000,
+ image: '/clear-soda-bottle.png',
+ rating: 4.5,
+ reviews: 342,
+ liked: false,
+ discount: 17,
+ },
+ {
+ id: 7,
+ name: 'Fanta Orange 1L',
+ price: 9000,
+ oldPrice: 10000,
+ inStock: true,
+ image: '/fanta-orange-bottle.png',
+ rating: 4.4,
+ reviews: 342,
+ liked: true,
+ discount: 10,
+ },
+ ]);
+
+ const handleQuantityChange = (type: string) => {
+ if (type === 'increase') {
+ setQuantity(quantity + 1);
+ } else if (type === 'decrease' && quantity > 1) {
+ setQuantity(quantity - 1);
+ }
+ };
+
+ const addToCart = () => {
+ alert(`${quantity} ta ${product.name} savatchaga qo'shildi!`);
+ };
+
+ const handleRemove = (id: number) => {
+ setRelatedProducts((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: false } : product,
+ ),
+ );
+ };
+
+ const handleLiked = (id: number) => {
+ setRelatedProducts((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: true } : product,
+ ),
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+ {product.discount > 0 && (
+
+ -{product.discount}%
+
+ )}
+ {!product.inStock && (
+
+
+ Mavjud emas
+
+
+ )}
+
+
+
+
+ {product.images.map((img, index) => (
+
+
+
+ ))}
+
+
+
+
+ {/* Product Info */}
+
+
+ {product.name}
+
+
+ {/* Rating */}
+
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+
{product.rating}
+
({product.reviews} sharh)
+
+
+ {/* Price */}
+
+
+
+ {product.price.toLocaleString()} {"so'm"}
+
+ {product.oldPrice && (
+
+ {product.oldPrice.toLocaleString()} {"so'm"}
+
+ )}
+
+
+
+ {/* Description */}
+
{product.description}
+
+ {/* Brand and Category */}
+
+
+
Brand:
+
{product.brand}
+
+
+
Kategoriya:
+
{product.category}
+
+
+
+ {/* Quantity Selector */}
+
+
+
+
+
+
+ {quantity}
+
+
+
+
+ Jami:{' '}
+
+ {(product.price * quantity).toLocaleString()} {"so'm"}
+
+
+
+
+
+ {/* Action Buttons */}
+
+
+
+
+
+ {/* Features */}
+
+
+
+
+ Bepul yetkazib berish
+
+
+
+
+ Kafolat
+
+
+
+
+ 14 kun qaytarish
+
+
+
+
+
+
+
+ {/* Specifications */}
+
+
Xususiyatlari
+
+ {Object.entries(product.specifications).map(([key, value]) => (
+
+ {key}:
+ {value}
+
+ ))}
+
+
+
+ {/* Related Products */}
+
+
{"O'xshash mahsulotlar"}
+
+ {relatedProducts.map((item) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ProductDetail;
diff --git a/src/features/profile/ui/Profile.tsx b/src/features/profile/ui/Profile.tsx
new file mode 100644
index 0000000..dfeef5f
--- /dev/null
+++ b/src/features/profile/ui/Profile.tsx
@@ -0,0 +1,1069 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import { Avatar, AvatarFallback, AvatarImage } from '@/shared/ui/avatar';
+import { Badge } from '@/shared/ui/badge';
+import { Button } from '@/shared/ui/button';
+import { Card, CardContent } from '@/shared/ui/card';
+import { Input } from '@/shared/ui/input';
+import { Label } from '@/shared/ui/label';
+import { Switch } from '@/shared/ui/switch';
+import { Tabs, TabsList, TabsTrigger } from '@/shared/ui/tabs';
+import {
+ Bell,
+ Calendar,
+ Camera,
+ CheckCircle,
+ ChevronDown,
+ ChevronRight,
+ Clock,
+ CreditCard,
+ Edit3,
+ Globe,
+ Heart,
+ History,
+ Home,
+ LogOut,
+ MapPin,
+ Moon,
+ Package,
+ Plus,
+ RefreshCw,
+ Settings,
+ Shield,
+ ShoppingBag,
+ Star,
+ TrendingUp,
+ Truck,
+ User,
+ Wallet,
+} from 'lucide-react';
+import Image from 'next/image';
+import { useState } from 'react';
+
+const Profile = () => {
+ const [activeSection, setActiveSection] = useState('overview');
+ const [ordersTab, setOrdersTab] = useState('active');
+ const [historyTab, setHistoryTab] = useState('all');
+ const router = useRouter();
+
+ const user = {
+ phone: '+998 90 123 45 67',
+ email: 'akmal@example.com',
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Akmal',
+ memberSince: '2024-yil yanvar',
+ loyaltyPoints: 2450,
+ totalOrders: 47,
+ totalSpent: 3250000,
+ address: 'Toshkent sh., Chilonzor tumani, 12-kvartal, 5-uy',
+ birthDate: '1995-05-15',
+ };
+
+ const orders = [
+ {
+ id: 'BUY-001',
+ date: '2024-12-10',
+ time: '14:30',
+ items: [
+ {
+ name: 'RedBull',
+ quantity: 2,
+ price: 15000,
+ image: '/red-bull-energy-drink.jpg',
+ },
+ {
+ name: 'Coca-Cola 1.5L',
+ quantity: 1,
+ price: 12000,
+ image: '/classic-coca-cola.png',
+ },
+ {
+ name: 'Suv',
+ quantity: 1,
+ price: 18000,
+ image: '/small-mineral-water-bottle.jpg',
+ },
+ ],
+ total: 45000,
+ deliveryFee: 5000,
+ address: 'Chilonzor tumani, 12-kvartal, 5-uy',
+ paymentMethod: 'Naqd',
+ courierName: 'Javohir Karimov',
+ courierPhone: '+998 90 111 22 33',
+ status: 'delivered',
+ paymentStatus: 'paid',
+ },
+ {
+ id: 'BUY-002',
+ date: '2024-12-08',
+ time: '19:15',
+ items: [
+ {
+ name: 'Milka',
+ quantity: 3,
+ price: 25000,
+ image: '/milka-chocolate-bar.jpg',
+ },
+ ],
+ total: 62000,
+ deliveryFee: 7000,
+ address: 'Yunusobod tumani, 7-kvartal, 23-uy',
+ paymentMethod: 'Karta',
+ status: 'inTransit',
+ paymentStatus: 'paid',
+ estimatedTime: '15-20 daqiqa',
+ },
+ {
+ id: 'BUY-003',
+ date: '2024-12-05',
+ time: '12:45',
+ items: [
+ {
+ name: 'Coca-Cola',
+ quantity: 1,
+ price: 65000,
+ image: '/classic-coca-cola.png',
+ },
+ {
+ name: 'Monster',
+ quantity: 2,
+ price: 15000,
+ image: '/monster-energy-drink.jpg',
+ },
+ ],
+ total: 85000,
+ deliveryFee: 10000,
+ address: 'Sergeli tumani, 5-kvartal, 12-uy',
+ paymentMethod: 'Naqd',
+ status: 'atPickup',
+ paymentStatus: 'pending',
+ },
+ {
+ id: 'BUY-004',
+ date: '2024-12-01',
+ time: '20:00',
+ items: [
+ {
+ name: 'Milka',
+ quantity: 5,
+ price: 35000,
+ image: '/milka-chocolate-bar.jpg',
+ },
+ {
+ name: 'Suv',
+ quantity: 3,
+ price: 3000,
+ image: '/small-mineral-water-bottle.jpg',
+ },
+ ],
+ total: 184000,
+ deliveryFee: 8000,
+ address: 'Mirzo Ulugbek tumani, 3-kvartal',
+ paymentMethod: 'Karta',
+ status: 'delivered',
+ paymentStatus: 'paid',
+ },
+ {
+ id: 'BUY-005',
+ date: '2024-11-28',
+ time: '13:20',
+ items: [
+ {
+ name: 'Milka',
+ quantity: 1,
+ price: 120000,
+ image: '/milka-chocolate-bar.jpg',
+ },
+ ],
+ total: 120000,
+ deliveryFee: 15000,
+ address: "Yakkasaroy tumani, Bobur ko'chasi",
+ paymentMethod: 'Karta',
+ status: 'delivered',
+ paymentStatus: 'paid',
+ },
+ ];
+
+ const getStatusInfo = (status: string) => {
+ const statusMap: Record<
+ string,
+ { text: string; color: string; bgColor: string }
+ > = {
+ inTransit: {
+ text: "Yo'lda",
+ color: 'text-blue-600',
+ bgColor: 'bg-blue-100',
+ },
+ atPickup: {
+ text: 'Punktda',
+ color: 'text-amber-600',
+ bgColor: 'bg-amber-100',
+ },
+ delivered: {
+ text: 'Yetkazildi',
+ color: 'text-emerald-600',
+ bgColor: 'bg-emerald-100',
+ },
+ cancelled: {
+ text: 'Bekor qilindi',
+ color: 'text-red-600',
+ bgColor: 'bg-red-100',
+ },
+ };
+ return (
+ statusMap[status] || {
+ text: status,
+ color: 'text-muted-foreground',
+ bgColor: 'bg-muted',
+ }
+ );
+ };
+
+ const getStatusIcon = (status: string) => {
+ switch (status) {
+ case 'inTransit':
+ return ;
+ case 'atPickup':
+ return ;
+ case 'delivered':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const menuItems = [
+ { id: 'overview', label: 'Umumiy', icon: Home },
+ { id: 'orders', label: 'Buyurtmalar', icon: ShoppingBag },
+ { id: 'history', label: 'Tarix', icon: History },
+ ];
+
+ const statistics = {
+ delivered: orders.filter((o) => o.status === 'delivered').length,
+ inTransit: orders.filter((o) => o.status === 'inTransit').length,
+ atPickup: orders.filter((o) => o.status === 'atPickup').length,
+ };
+
+ const renderContent = () => {
+ switch (activeSection) {
+ case 'orders':
+ return (
+
+
+
+ Buyurtmalar
+
+
+
+
+
+
+ Faol ({orders.filter((o) => o.status !== 'delivered').length})
+
+
+ Tugadi (
+ {orders.filter((o) => o.status === 'delivered').length})
+
+
+ Barcha ({orders.length})
+
+
+
+
+
+ {orders
+ .filter((o) => {
+ if (ordersTab === 'active') return o.status !== 'delivered';
+ if (ordersTab === 'completed')
+ return o.status === 'delivered';
+ return true;
+ })
+ .map((order) => {
+ const statusInfo = getStatusInfo(order.status);
+ return (
+
+
+
+
+
+
+ {getStatusIcon(order.status)}
+
+
+
+
+ {order.id}
+
+
+ {order.date} • {order.time}
+
+
+
+
+ {statusInfo.text}
+
+
+
+
+ {order.items.map((item, idx) => (
+
+
+
+
+ {item.name}
+
+
+ {item.quantity} ×{' '}
+ {item.price.toLocaleString()}
+
+
+
+ ))}
+
+
+
+
+
+ {order.address}
+
+
+
+ {(
+ order.total + order.deliveryFee
+ ).toLocaleString()}{' '}
+ {"so'm"}
+
+
+ Yetkazish: {order.deliveryFee.toLocaleString()}{' '}
+ {"so'm"}
+
+
+
+
+
+ );
+ })}
+
+
+ );
+
+ case 'history':
+ return (
+
+
+
+ Tarix
+
+
+
+
+
+
+ Barchasi
+
+
+ Bu hafta
+
+
+ Bu oy
+
+
+
+
+
+ {orders
+ .filter((o) => o.status === 'delivered')
+ .map((order, idx) => (
+
+
+
+
+
+ {idx <
+ orders.filter((o) => o.status === 'delivered').length -
+ 1 && (
+
+ )}
+
+
+
+
+
+
+ {order.id}
+
+
+
+
+ {order.date}
+
+
+
+ {order.time}
+
+
+
+
+ {(order.total + order.deliveryFee).toLocaleString()}{' '}
+ {"so'm"}
+
+
+
+ {order.items.map((item, i) => (
+
+ ))}
+
+ {order.items.map((i) => i.name).join(', ')}
+
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+
+ case 'settings':
+ return (
+
+
+ Sozlamalar
+
+
+
+
+
+
+ {"Shaxsiy ma'lumotlar"}
+
+
+
+
+
+ {"A'zo"}: {user.memberSince}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {"To'lov usullari"}
+
+
+
+
+
+
+ VISA
+
+
+
+ •••• •••• •••• 4532
+
+
+ 12/26
+
+
+
+
+ Asosiy
+
+
+
+
+
+
+
+
+
+ Ilova sozlamalari
+
+
+
+
+
+
+
+ Bildirishnomalar
+
+
+ Push xabarlar
+
+
+
+
+
+
+
+
+
+
+ {"Qorong'u rejim"}
+
+
+ Tungi rejim
+
+
+
+
+
+
+
+
+
+
+ Til
+
+
+ Ilova tili
+
+
+
+
+
+
+
+
+
+
+ Xavfsizlik
+
+
+ {"Parolni o'zgartirish"}
+
+
+
+
+
+
+
+
+
+ {/* Danger Zone */}
+
+
+
+ Xavfli zona
+
+
+
+
+ {"Hisobni o'chirish"}
+
+
+ {"Barcha ma'lumotlar o'chiriladi"}
+
+
+
+
+
+
+
+ );
+
+ case 'favorites':
+ return (
+
+
+ Sevimlilar
+
+
+
+
+
+
+ {"Sevimli mahsulotlar yo'q"}
+
+
+ {"Mahsulotlarni sevimlilar ro'yxatiga qo'shing"}
+
+
+
+ );
+
+ default:
+ // Overview section
+ return (
+ <>
+
+
+
+
+
+ {user.totalOrders}
+
+
+ Jami buyurtmalar
+
+
+
+
+
+
+
+
+ {(user.totalSpent / 1000000).toFixed(1)}M
+
+
+ Jami xarajat
+
+
+
+
+
+
+
+
+ {statistics.inTransit}
+
+
+ {"Yo'lda"}
+
+
+
+
+
+
+
+
+ {statistics.delivered}
+
+
+ Yetkazildi
+
+
+
+
+
+ {/* Active Orders Section */}
+
+
+
+ Faol buyurtmalar
+
+
+
+
+
+ {orders
+ .filter((o) => o.status !== 'delivered')
+ .slice(0, 2)
+ .map((order) => {
+ const statusInfo = getStatusInfo(order.status);
+ return (
+
+
+
+
+
+
+ {getStatusIcon(order.status)}
+
+
+
+
+ {order.id}
+
+
+ {order.date} • {order.time}
+
+
+
+
+ {statusInfo.text}
+
+
+
+
+ {order.items.map((item, idx) => (
+
+ {item.name} ×{item.quantity}
+
+ ))}
+
+
+
+
+
+ {order.address}
+
+
+ {(
+ order.total + order.deliveryFee
+ ).toLocaleString()}{' '}
+ {"so'm"}
+
+
+
+
+ );
+ })}
+
+
+
+ {/* Order History */}
+
+
+
+ Buyurtmalar tarixi
+
+
+
+
+
+ {orders
+ .filter((o) => o.status === 'delivered')
+ .slice(0, 2)
+ .map((order) => (
+
+
+
+
+
+
+
+
+
+ {order.id}
+
+
+ {order.items.map((i) => i.name).join(', ')}
+
+
+
+
+
+ {(
+ order.total + order.deliveryFee
+ ).toLocaleString()}
+
+
+ {order.date}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ >
+ );
+ }
+ };
+
+ return (
+
+
+
+ {menuItems.map((item) => {
+ const Icon = item.icon;
+ return (
+
+ );
+ })}
+
+
+
+
+
+ {/* Desktop Sidebar - hidden on mobile */}
+
+
+
+ {/* Navigation */}
+
+
+ {/* Logout */}
+
+
+
+ {/* Main Content */}
+
+
+
+ {renderContent()}
+
+
+
+
+ );
+};
+
+export default Profile;
diff --git a/src/features/search/ui/Search.tsx b/src/features/search/ui/Search.tsx
new file mode 100644
index 0000000..f503f00
--- /dev/null
+++ b/src/features/search/ui/Search.tsx
@@ -0,0 +1,156 @@
+'use client';
+
+import { Input } from '@/shared/ui/input';
+import {
+ categories,
+ Product,
+ ProductDetail,
+} from '@/widgets/categories/lib/data';
+import { ProductCard } from '@/widgets/categories/ui/product-card';
+import { Search, X } from 'lucide-react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { useEffect, useMemo, useState } from 'react';
+
+const SearchResult: React.FC = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const queryFromUrl = searchParams.get('q') ?? '';
+
+ const [query, setQuery] = useState(queryFromUrl);
+ const [loading, setLoading] = useState(false);
+ const [results, setResults] = useState([]);
+
+ const allProducts = useMemo(() => {
+ return categories.flatMap((category: Product) =>
+ category.products.map((product) => ({
+ ...product,
+ categoryName: category.name,
+ })),
+ );
+ }, []);
+
+ const recommendedProducts = useMemo(() => {
+ return allProducts.filter((product) => product.rating >= 4.5).slice(0, 8);
+ }, [allProducts]);
+
+ const handleSearch = (searchQuery: string) => {
+ if (!searchQuery.trim()) return;
+
+ setLoading(true);
+ router.push(`/search?q=${encodeURIComponent(searchQuery)}`);
+
+ setTimeout(() => {
+ const filtered = allProducts.filter(
+ (product) =>
+ product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ product.categoryName
+ ?.toLowerCase()
+ .includes(searchQuery.toLowerCase()),
+ );
+
+ setResults(filtered);
+ setLoading(false);
+ }, 300);
+ };
+
+ useEffect(() => {
+ if (queryFromUrl) {
+ handleSearch(queryFromUrl);
+ }
+ }, [queryFromUrl]);
+
+ const clearSearch = () => {
+ setQuery('');
+ setResults([]);
+ router.push('/search');
+ };
+
+ const handleRemove = (id: number) => {
+ setResults((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: false } : product,
+ ),
+ );
+ };
+
+ const handleLiked = (id: number) => {
+ setResults((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: true } : product,
+ ),
+ );
+ };
+
+ return (
+
+
+
+
+
+ {
+ setQuery(e.target.value);
+ handleSearch(e.target.value);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') handleSearch(query);
+ }}
+ className="w-full border rounded-lg pl-10 pr-10 h-12"
+ />
+
+ {query && (
+
+ )}
+
+
+
+
+ {loading ? (
+
Yuklanmoqda...
+ ) : query ? (
+ results.length ? (
+
+ {results.map((product) => (
+
+ ))}
+
+ ) : (
+
Natija topilmadi
+ )
+ ) : (
+
+
+ Tavsiya etilgan mahsulotlar
+
+
+
+ {recommendedProducts.map((product) => (
+
+ ))}
+
+
+ )}
+
+
+ );
+};
+
+export default SearchResult;
diff --git a/src/shared/config/api/httpClient.ts b/src/shared/config/api/httpClient.ts
index 92b475b..f32e25e 100644
--- a/src/shared/config/api/httpClient.ts
+++ b/src/shared/config/api/httpClient.ts
@@ -11,14 +11,11 @@ const httpClient = axios.create({
httpClient.interceptors.request.use(
async (config) => {
- console.log(`API REQUEST to ${config.url}`, config);
-
// Language configs
let language = LanguageRoutes.UZ;
try {
language = (await getLocale()) as LanguageRoutes;
- } catch (e) {
- console.log('error', e);
+ } catch {
language = getLocaleCS() || LanguageRoutes.UZ;
}
diff --git a/src/shared/config/fonts.ts b/src/shared/config/fonts.ts
index 517ca42..d44e404 100644
--- a/src/shared/config/fonts.ts
+++ b/src/shared/config/fonts.ts
@@ -1,9 +1,9 @@
-import { Golos_Text } from 'next/font/google';
+import { Poppins } from 'next/font/google';
-const golosText = Golos_Text({
+const poppins = Poppins({
weight: ['400', '500', '600', '700', '800'],
- variable: '--font-golos-text',
- subsets: ['latin', 'cyrillic'],
+ variable: '--font-poppins',
+ subsets: ['latin'],
});
-export { golosText };
+export { poppins };
diff --git a/src/shared/hooks/use-closer.ts b/src/shared/hooks/use-closer.ts
index 4e005d4..1b60884 100644
--- a/src/shared/hooks/use-closer.ts
+++ b/src/shared/hooks/use-closer.ts
@@ -33,7 +33,7 @@ const useCloser = (
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('scroll', handleScroll);
};
- }, [ref, closeFunction]);
+ }, [ref, closeFunction, scrollClose]);
};
export default useCloser;
diff --git a/src/shared/lib/formatPrice.ts b/src/shared/lib/formatPrice.ts
index 2fbfd5d..5718434 100644
--- a/src/shared/lib/formatPrice.ts
+++ b/src/shared/lib/formatPrice.ts
@@ -1,5 +1,5 @@
import { LanguageRoutes } from '../config/i18n/types';
-import { getLocale } from 'next-intl/server';
+import getLocaleCS from './getLocaleCS';
/**
* Format price. With label.
@@ -7,12 +7,12 @@ import { getLocale } from 'next-intl/server';
* @param withLabel Show label. Default false
* @returns string. Ex. X XXX XXX sum
*/
-const formatPrice = async (amount: number | string, withLabel?: boolean) => {
- const locale = (await getLocale()) as LanguageRoutes;
+const formatPrice = (amount: number | string, withLabel?: boolean) => {
+ const locale = getLocaleCS() as LanguageRoutes;
const label = withLabel
- ? locale == LanguageRoutes.RU
+ ? locale === LanguageRoutes.RU
? ' сум'
- : locale == LanguageRoutes.KI
+ : locale === LanguageRoutes.KI
? ' сўм'
: ' so‘m'
: '';
@@ -22,7 +22,7 @@ const formatPrice = async (amount: number | string, withLabel?: boolean) => {
const formattedDollars = dollars.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
- if (String(amount).length == 0) {
+ if (String(amount).length === 0) {
return formattedDollars + '.' + cents + label;
} else {
return formattedDollars + label;
diff --git a/src/shared/ui/aspect-ratio.tsx b/src/shared/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..afb7845
--- /dev/null
+++ b/src/shared/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+'use client';
+
+import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio';
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { AspectRatio };
diff --git a/src/shared/ui/avatar.tsx b/src/shared/ui/avatar.tsx
new file mode 100644
index 0000000..276f32f
--- /dev/null
+++ b/src/shared/ui/avatar.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import * as React from 'react';
+import * as AvatarPrimitive from '@radix-ui/react-avatar';
+
+import { cn } from '@/shared/lib/utils';
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/shared/ui/badge.tsx b/src/shared/ui/badge.tsx
new file mode 100644
index 0000000..3bdfd58
--- /dev/null
+++ b/src/shared/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/shared/lib/utils';
+
+const badgeVariants = cva(
+ 'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
+ secondary:
+ 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
+ destructive:
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'span'> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'span';
+
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/src/shared/ui/card.tsx b/src/shared/ui/card.tsx
new file mode 100644
index 0000000..d67be40
--- /dev/null
+++ b/src/shared/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+
+function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/src/shared/ui/carousel.tsx b/src/shared/ui/carousel.tsx
new file mode 100644
index 0000000..e6c4c95
--- /dev/null
+++ b/src/shared/ui/carousel.tsx
@@ -0,0 +1,241 @@
+'use client';
+
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from 'embla-carousel-react';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+import { Button } from '@/shared/ui/button';
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+export type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: 'horizontal' | 'vertical';
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+export function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error('useCarousel must be used within a ');
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = 'horizontal',
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === 'horizontal' ? 'x' : 'y',
+ },
+ plugins,
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === 'ArrowLeft') {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === 'ArrowRight') {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext],
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on('reInit', onSelect);
+ api.on('select', onSelect);
+
+ return () => {
+ api?.off('select', onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<'div'>) {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<'div'>) {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = 'outline',
+ size = 'icon',
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = 'outline',
+ size = 'icon',
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+ );
+}
+
+export {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+ type CarouselApi,
+};
diff --git a/src/shared/ui/label.tsx b/src/shared/ui/label.tsx
new file mode 100644
index 0000000..0ad2da0
--- /dev/null
+++ b/src/shared/ui/label.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import * as React from 'react';
+import * as LabelPrimitive from '@radix-ui/react-label';
+
+import { cn } from '@/shared/lib/utils';
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Label };
diff --git a/src/shared/ui/popover.tsx b/src/shared/ui/popover.tsx
new file mode 100644
index 0000000..c45ebd6
--- /dev/null
+++ b/src/shared/ui/popover.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import * as PopoverPrimitive from '@radix-ui/react-popover';
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+
+function Popover({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function PopoverTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function PopoverContent({
+ className,
+ align = 'center',
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function PopoverAnchor({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
diff --git a/src/shared/ui/progress.tsx b/src/shared/ui/progress.tsx
new file mode 100644
index 0000000..3b66837
--- /dev/null
+++ b/src/shared/ui/progress.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import * as React from 'react';
+import * as ProgressPrimitive from '@radix-ui/react-progress';
+
+import { cn } from '@/shared/lib/utils';
+
+function Progress({
+ className,
+ value,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { Progress };
diff --git a/src/shared/ui/switch.tsx b/src/shared/ui/switch.tsx
new file mode 100644
index 0000000..7d0ad78
--- /dev/null
+++ b/src/shared/ui/switch.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import * as React from 'react';
+import * as SwitchPrimitive from '@radix-ui/react-switch';
+
+import { cn } from '@/shared/lib/utils';
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { Switch };
diff --git a/src/shared/ui/tabs.tsx b/src/shared/ui/tabs.tsx
new file mode 100644
index 0000000..7a27792
--- /dev/null
+++ b/src/shared/ui/tabs.tsx
@@ -0,0 +1,66 @@
+'use client';
+
+import * as React from 'react';
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+
+import { cn } from '@/shared/lib/utils';
+
+function Tabs({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/src/shared/ui/textarea.tsx b/src/shared/ui/textarea.tsx
new file mode 100644
index 0000000..99dd374
--- /dev/null
+++ b/src/shared/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+
+function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
+ return (
+
+ );
+}
+
+export { Textarea };
diff --git a/src/widgets/categories/lib/data.ts b/src/widgets/categories/lib/data.ts
new file mode 100644
index 0000000..a7342a8
--- /dev/null
+++ b/src/widgets/categories/lib/data.ts
@@ -0,0 +1,148 @@
+export const categories: Product[] = [
+ {
+ id: 1,
+ name: 'Elektronika',
+ products: [
+ {
+ id: 1,
+ name: 'Krasovka',
+ price: 125000,
+ oldPrice: 14000000,
+ image: '/adidas-ultraboost-running-shoes.jpg',
+ rating: 4.5,
+ reviews: 128,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 2,
+ name: 'Apple MacBook Pro 14"',
+ price: 25000000,
+ oldPrice: 27000000,
+ image: '/apple-ipad-pro-tablet.jpg',
+ rating: 5.0,
+ reviews: 89,
+ discount: 8,
+ liked: false,
+ inStock: true,
+ },
+ {
+ id: 3,
+ name: 'Apple MacBook Pro 14"',
+ price: 25000000,
+ oldPrice: 27000000,
+ image: '/apple-ipad-pro-tablet.jpg',
+ rating: 5.0,
+ reviews: 89,
+ discount: 8,
+ inStock: true,
+ liked: true,
+ },
+ {
+ id: 4,
+ name: 'Apple MacBook Pro 14"',
+ price: 25000000,
+ oldPrice: 27000000,
+ image: '/apple-ipad-pro-tablet.jpg',
+ rating: 5.0,
+ reviews: 89,
+ discount: 8,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 5,
+ name: 'Sony WH-1000XM5',
+ price: 3500000,
+ oldPrice: 4000000,
+ image: '/sony-wh-1000xm5.png',
+ rating: 4.8,
+ reviews: 256,
+ discount: 12,
+ inStock: true,
+ liked: true,
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Sport kiyimlari',
+ products: [
+ {
+ id: 6,
+ name: 'Nike Air Max 270',
+ price: 1800000,
+ oldPrice: 2000000,
+ image: '/nike-air-max-270.png',
+ rating: 4.3,
+ reviews: 342,
+ discount: 10,
+ inStock: false,
+ liked: false,
+ },
+ {
+ id: 7,
+ name: 'Adidas Ultraboost',
+ price: 2000000,
+ oldPrice: 2200000,
+ image: '/adidas-ultraboost-running-shoes.jpg',
+ rating: 4.6,
+ reviews: 215,
+ discount: 9,
+ liked: true,
+ inStock: true,
+ },
+ ],
+ },
+ {
+ id: 3,
+ name: 'Uy jihozlari',
+ products: [
+ {
+ id: 8,
+ name: 'LG OLED TV 55"',
+ price: 18000000,
+ oldPrice: 20000000,
+ image: '/lg-oled-tv-55-inch.jpg',
+ rating: 4.7,
+ reviews: 76,
+ discount: 10,
+ inStock: true,
+ liked: false,
+ },
+ {
+ id: 9,
+ name: 'Canon EOS R6',
+ price: 22000000,
+ oldPrice: 24000000,
+ image: '/canon-eos-r6-camera.jpg',
+ rating: 4.9,
+ reviews: 54,
+ discount: 8,
+ inStock: true,
+ liked: false,
+ },
+ ],
+ },
+];
+
+export interface Product {
+ id: number;
+ name: string;
+ products: ProductDetail[];
+}
+
+export interface ProductDetail {
+ id: number;
+ name: string;
+ categoryName?: string;
+ price: number;
+ oldPrice: number;
+ image: string;
+ rating: number;
+ reviews: number;
+ discount: number;
+ inStock: boolean;
+ liked: boolean;
+}
diff --git a/src/widgets/categories/ui/category-carousel.tsx b/src/widgets/categories/ui/category-carousel.tsx
new file mode 100644
index 0000000..2816342
--- /dev/null
+++ b/src/widgets/categories/ui/category-carousel.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import { SubCategory } from '@/features/category/lib/data';
+import { useRouter } from '@/shared/config/i18n/navigation';
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from '@/shared/ui/carousel';
+import { ChevronRight } from 'lucide-react';
+import { useState } from 'react';
+import { ProductCard } from './product-card';
+
+export function CategoryCarousel({ category }: { category: SubCategory }) {
+ const [products, setProducts] = useState(category.products);
+ const router = useRouter();
+
+ const handleRemove = (id: number) => {
+ setProducts((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: false } : product,
+ ),
+ );
+ };
+
+ const handleLiked = (id: number) => {
+ setProducts((prev) =>
+ prev.map((product) =>
+ product.id === id ? { ...product, liked: true } : product,
+ ),
+ );
+ };
+
+ return (
+
+
+
+ router.push(`/category/${category.category}/${category.name}`)
+ }
+ >
+
+ {category.name}
+
+
+
+
+
+
+
+
+
+ {products.slice(0, 12).map((product) => (
+
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/categories/ui/product-card.tsx b/src/widgets/categories/ui/product-card.tsx
new file mode 100644
index 0000000..d60c53c
--- /dev/null
+++ b/src/widgets/categories/ui/product-card.tsx
@@ -0,0 +1,166 @@
+'use client';
+
+import { useRouter } from '@/shared/config/i18n/navigation';
+import formatPrice from '@/shared/lib/formatPrice';
+import { Button } from '@/shared/ui/button';
+import { Card, CardContent } from '@/shared/ui/card';
+import { Input } from '@/shared/ui/input';
+import { Heart, Minus, Plus, ShoppingCart, Star } from 'lucide-react';
+import Image from 'next/image';
+import { MouseEvent, useState } from 'react';
+
+interface Product {
+ id: number;
+ name: string;
+ price: number;
+ oldPrice: number;
+ image: string;
+ rating: number;
+ reviews: number;
+ discount: number;
+ inStock: boolean;
+ liked: boolean;
+}
+
+export function ProductCard({
+ product,
+ handleRemove,
+ handleLiked,
+}: {
+ product: Product;
+ handleRemove: (id: number) => void;
+ handleLiked?: (id: number) => void;
+}) {
+ const [quantity, setQuantity] = useState(0);
+ const router = useRouter();
+
+ const increase = (e: MouseEvent) => {
+ e.stopPropagation();
+ setQuantity((q) => (q === '' ? 1 : q + 1));
+ };
+ const decrease = (e: MouseEvent) => {
+ setQuantity((q) => (q && q > 1 ? q - 1 : 0));
+ e.stopPropagation();
+ };
+
+ return (
+ router.push(`/product/${product.id}`)}
+ className="group relative p-0 overflow-hidden border border-slate-200 bg-white shadow-sm hover:shadow-xl transition-all duration-300 rounded-2xl hover:border-blue-400"
+ >
+
+
+ {product.discount > 0 && (
+
+ -{product.discount}%
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {product.rating}
+
+
+
+ ({product.reviews} ta sharh)
+
+
+
+
+ {product.name}
+
+
+
+
+
+ {formatPrice(product.price)}
+
+
+ {product.oldPrice && (
+
+
+ {formatPrice(product.oldPrice)}
+
+
+ Tejang!
+
+
+ )}
+
+
+ {quantity === 0 ? (
+
+ ) : (
+
+
+
+
{
+ const value = e.target.value;
+
+ if (/^\d*$/.test(value)) {
+ setQuantity(value === '' ? '' : Number(value));
+ }
+ }}
+ onBlur={() => {
+ if (quantity === '' || quantity < 1) {
+ setQuantity(1);
+ }
+ }}
+ className="w-full text-center outline-none ring-0 focus-visible:ring-0 border-none font-semibold"
+ />
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/widgets/footer/lib/data.ts b/src/widgets/footer/lib/data.ts
index b3ffbe9..5001f90 100644
--- a/src/widgets/footer/lib/data.ts
+++ b/src/widgets/footer/lib/data.ts
@@ -1,6 +1,6 @@
const sections = [
{
- title: 'Product',
+ title: 'Kategoriyalar',
links: [
{ name: 'Overview', href: '#' },
{ name: 'Pricing', href: '#' },
diff --git a/src/widgets/footer/ui/index.tsx b/src/widgets/footer/ui/index.tsx
index 9b6a900..19ad27e 100644
--- a/src/widgets/footer/ui/index.tsx
+++ b/src/widgets/footer/ui/index.tsx
@@ -1,82 +1,114 @@
import { PRODUCT_INFO } from '@/shared/constants/data';
-import { InstagramIcon, YoutubeIcon } from 'lucide-react';
-import { sections } from '../lib/data';
-import { ModeToggle } from '@/shared/ui/theme-toggle';
+import formatPhone from '@/shared/lib/formatPhone';
+import { categoryList } from '@/widgets/welcome/lib/data';
+import { Facebook, Instagram, Mail, Phone, Send, Twitter } from 'lucide-react';
+import Image from 'next/image';
+import { Fragment } from 'react';
const Footer = () => {
return (
-
-
-
-
- {/* Logo */}
-
-
-
+
+
+
+
+
+
-
-
{PRODUCT_INFO.name}
-
-
- A collection of 100+ responsive HTML templates for your startup
- business or side project.
-
-
-
-
-
- {sections.map((section, sectionIdx) => (
-
-
{section.title}
-
- {section.links.map((link, linkIdx) => (
- -
- {link.name}
-
- ))}
-
- ))}
+
+ {PRODUCT_INFO.name}
+
+
+
+ Lorem ipsum dolor sit amet consectetur, adipisicing elit. Est,
+ totam?
+
+
+
+
+
+ Kategoriyalar
+
+
+ {categoryList.slice(0, 3).map((link) => (
+
+ {link.subCategories.slice(0, 2).map((e, linkIdx) => (
+ -
+
+ {e.name}
+
+
+ ))}
+
+ ))}
+
+
+
+
-
-
-
- © {new Date().getFullYear()} {PRODUCT_INFO.creator}. All rights
- reserved.
-
-
diff --git a/src/widgets/navbar/lib/data.ts b/src/widgets/navbar/lib/data.ts
index 866e27e..99d8ee3 100644
--- a/src/widgets/navbar/lib/data.ts
+++ b/src/widgets/navbar/lib/data.ts
@@ -1,6 +1,6 @@
+import { LanguageRoutes } from '@/shared/config/i18n/types';
import { Book, Sunset, Trees, Zap } from 'lucide-react';
import { MenuItem } from './model';
-import { LanguageRoutes } from '@/shared/config/i18n/types';
const menu: MenuItem[] = [
{ title: 'Home', url: '#' },
@@ -80,14 +80,14 @@ const languages: { name: string; key: LanguageRoutes }[] = [
name: "O'zbekcha",
key: LanguageRoutes.UZ,
},
- {
- name: 'Ўзбекча',
- key: LanguageRoutes.KI,
- },
+ // {
+ // name: 'Ўзбекча',
+ // key: LanguageRoutes.KI,
+ // },
{
name: 'Русский',
key: LanguageRoutes.RU,
},
];
-export { menu, languages };
+export { languages, menu };
diff --git a/src/widgets/navbar/lib/openCategory.ts b/src/widgets/navbar/lib/openCategory.ts
new file mode 100644
index 0000000..2f80ea5
--- /dev/null
+++ b/src/widgets/navbar/lib/openCategory.ts
@@ -0,0 +1,23 @@
+import { CategoryType } from '@/widgets/welcome/lib/data';
+import { create } from 'zustand';
+
+type Category = {
+ active: CategoryType | null;
+ openToolbar: boolean;
+};
+
+type Actions = {
+ setActive: (active: CategoryType | null) => void;
+ setOpenToolbar: (openToolbar: boolean) => void;
+ setCloseToolbar: (openToolbar: boolean) => void;
+};
+
+const useCategoryActive = create((set) => ({
+ active: null,
+ openToolbar: false,
+ setActive: (active: CategoryType | null) => set(() => ({ active })),
+ setOpenToolbar: () => set(() => ({ openToolbar: true })),
+ setCloseToolbar: () => set(() => ({ openToolbar: false })),
+}));
+
+export default useCategoryActive;
diff --git a/src/widgets/navbar/ui/ChangeLang.tsx b/src/widgets/navbar/ui/ChangeLang.tsx
index d338cb5..f960fcd 100644
--- a/src/widgets/navbar/ui/ChangeLang.tsx
+++ b/src/widgets/navbar/ui/ChangeLang.tsx
@@ -1,17 +1,16 @@
'use client';
-import * as React from 'react';
-import { GlobeIcon } from 'lucide-react';
+import type { LanguageRoutes } from '@/shared/config/i18n/types';
+import { Button } from '@/shared/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/shared/ui/dropdown-menu';
-import { Button } from '@/shared/ui/button';
-import { languages } from '../lib/data';
+import Image from 'next/image';
import { useParams, usePathname, useRouter } from 'next/navigation';
-import { LanguageRoutes } from '@/shared/config/i18n/types';
+import { languages } from '../lib/data';
export function ChangeLang() {
const { locale } = useParams();
@@ -27,15 +26,45 @@ export function ChangeLang() {
return (
-
-