added login page

This commit is contained in:
Samandar Turgunboyev
2025-11-08 11:47:47 +05:00
parent dc478c57bb
commit 5c41a87e23
13 changed files with 727 additions and 28 deletions

230
package-lock.json generated
View File

@@ -12,16 +12,17 @@
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.8", "@radix-ui/react-accordion": "^1.2.8",
"@radix-ui/react-avatar": "^1.1.7", "@radix-ui/react-avatar": "^1.1.7",
"@radix-ui/react-checkbox": "^1.2.3", "@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11", "@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12", "@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-navigation-menu": "^1.2.10",
"@radix-ui/react-select": "^2.2.2", "@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.9", "@radix-ui/react-tabs": "^1.1.9",
"@radix-ui/react-toggle": "^1.1.6", "@radix-ui/react-toggle": "^1.1.6",
"@radix-ui/react-toggle-group": "^1.1.7", "@radix-ui/react-toggle-group": "^1.1.7",
@@ -33,21 +34,24 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"js-cookie": "^3.0.5",
"lucide-react": "^0.503.0", "lucide-react": "^0.503.0",
"next": "15.3.1", "next": "15.3.1",
"next-intl": "^4.1.0", "next-intl": "^4.1.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.66.0",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"sonner": "^2.0.3", "sonner": "^2.0.7",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^3.24.3" "zod": "^3.25.76"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@@ -394,6 +398,18 @@
"tslib": "2" "tslib": "2"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
"integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
"license": "MIT",
"dependencies": {
"@standard-schema/utils": "^0.3.0"
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1185,6 +1201,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": { "node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -1248,6 +1282,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": { "node_modules/@radix-ui/react-direction": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
@@ -1372,11 +1424,35 @@
} }
}, },
"node_modules/@radix-ui/react-label": { "node_modules/@radix-ui/react-label": {
"version": "2.1.4", "version": "2.1.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
"integrity": "sha512-wy3dqizZnZVV4ja0FNnUhIWNwWdoldXrneEyUcVtLYDAt8ovGS4ridtMAOGgXBBIfggL4BOveVWsjXDORdGEQg==", "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
"license": "MIT",
"dependencies": { "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": { "peerDependencies": {
"@types/react": "*", "@types/react": "*",
@@ -1432,6 +1508,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": { "node_modules/@radix-ui/react-navigation-menu": {
"version": "1.2.10", "version": "1.2.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.10.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.10.tgz",
@@ -1566,6 +1660,24 @@
} }
} }
}, },
"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-roving-focus": { "node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz",
@@ -1638,6 +1750,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": { "node_modules/@radix-ui/react-separator": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz",
@@ -1661,9 +1791,10 @@
} }
}, },
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-slot": {
"version": "1.2.0", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-compose-refs": "1.1.2" "@radix-ui/react-compose-refs": "1.1.2"
}, },
@@ -1791,6 +1922,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": { "node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@@ -1980,6 +2129,12 @@
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==" "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="
}, },
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -2395,6 +2550,13 @@
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"dev": true "dev": true
}, },
"node_modules/@types/js-cookie": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
"integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2420,7 +2582,7 @@
"version": "19.1.2", "version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
"devOptional": true, "dev": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@@ -2429,7 +2591,7 @@
"version": "19.1.2", "version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz",
"integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
"devOptional": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
@@ -5428,6 +5590,15 @@
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
} }
}, },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6174,6 +6345,7 @@
"version": "0.4.6", "version": "0.4.6",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
@@ -6627,6 +6799,22 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.66.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.0.tgz",
"integrity": "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -7215,9 +7403,10 @@
} }
}, },
"node_modules/sonner": { "node_modules/sonner": {
"version": "2.0.3", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==", "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
@@ -7705,7 +7894,7 @@
"version": "5.8.3", "version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -8037,9 +8226,10 @@
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "3.24.3", "version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

View File

@@ -15,16 +15,17 @@
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.8", "@radix-ui/react-accordion": "^1.2.8",
"@radix-ui/react-avatar": "^1.1.7", "@radix-ui/react-avatar": "^1.1.7",
"@radix-ui/react-checkbox": "^1.2.3", "@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11", "@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12", "@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-navigation-menu": "^1.2.10",
"@radix-ui/react-select": "^2.2.2", "@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.9", "@radix-ui/react-tabs": "^1.1.9",
"@radix-ui/react-toggle": "^1.1.6", "@radix-ui/react-toggle": "^1.1.6",
"@radix-ui/react-toggle-group": "^1.1.7", "@radix-ui/react-toggle-group": "^1.1.7",
@@ -36,21 +37,24 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"js-cookie": "^3.0.5",
"lucide-react": "^0.503.0", "lucide-react": "^0.503.0",
"next": "15.3.1", "next": "15.3.1",
"next-intl": "^4.1.0", "next-intl": "^4.1.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.66.0",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"sonner": "^2.0.3", "sonner": "^2.0.7",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^3.24.3" "zod": "^3.25.76"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

View File

@@ -1,9 +1,9 @@
import Welcome from '@/widgets/welcome'; import Login from '@/widgets/auth/ui/login';
export default async function QrCode() { export default async function QrCode() {
return ( return (
<div className="flex justify-center items-center h-screen"> <div className="flex justify-center items-center h-screen">
<Welcome /> <Login />
</div> </div>
); );
} }

View File

@@ -9,6 +9,7 @@ import { setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import Script from 'next/script'; import Script from 'next/script';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Toaster } from 'sonner';
import '../globals.css'; import '../globals.css';
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -46,6 +47,7 @@ export default async function RootLayout({ children, params }: Props) {
disableTransitionOnChange disableTransitionOnChange
> >
<QueryProvider> <QueryProvider>
<Toaster />
{/* <Navbar /> */} {/* <Navbar /> */}
{children} {children}
{/* <Footer /> */} {/* <Footer /> */}

View File

@@ -1,5 +1,6 @@
const BASE_URL = process.env.NEXT_PUBLIC_API_URL; const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
const GET_BOXES = '/qr'; const GET_BOXES = '/qr';
const AUTH_LOGIN = '/auth/token';
export { BASE_URL, GET_BOXES }; export { AUTH_LOGIN, BASE_URL, GET_BOXES };

21
src/shared/lib/cookie.ts Normal file
View File

@@ -0,0 +1,21 @@
import cookie from 'js-cookie';
const token = 'qr_token';
// saved token cookie
export const saveToken = (value: string) => {
cookie.set(token, value, {
expires: 7,
secure: true,
});
};
// remove token cookie
export const removeToken = () => {
cookie.remove(token);
};
//get token cookie
export const getToken = () => {
return cookie.get(token);
};

92
src/shared/ui/card.tsx Normal file
View File

@@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/shared/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

167
src/shared/ui/form.tsx Normal file
View File

@@ -0,0 +1,167 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form"
import { cn } from "@/shared/lib/utils"
import { Label } from "@/shared/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn("grid gap-2", className)}
{...props}
/>
</FormItemContext.Provider>
)
}
function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField()
return (
<Label
data-slot="form-label"
data-error={!!error}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
}
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
}
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
const { formDescriptionId } = useFormField()
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : props.children
if (!body) {
return null
}
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
{...props}
>
{body}
</p>
)
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

24
src/shared/ui/label.tsx Normal file
View File

@@ -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<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props}
/>
)
}
export { Label }

40
src/shared/ui/sonner.tsx Normal file
View File

@@ -0,0 +1,40 @@
"use client"
import {
CircleCheckIcon,
InfoIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties
}
{...props}
/>
)
}
export { Toaster }

View File

@@ -0,0 +1,22 @@
import httpClient from '@/shared/config/api/httpClient';
import { AUTH_LOGIN } from '@/shared/config/api/URLs';
import { AxiosResponse } from 'axios';
interface LoginRes {
data: {
accessToken: string;
roles: string[];
username: string;
};
message: string;
}
const login = (body: {
username: string;
password: string;
}): Promise<AxiosResponse<LoginRes>> => {
const res = httpClient.post(AUTH_LOGIN, body);
return res;
};
export { login };

View File

@@ -0,0 +1,125 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import { saveToken } from '@/shared/lib/cookie';
import { Button } from '@/shared/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/ui/card';
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from '@/shared/ui/form';
import { Input } from '@/shared/ui/input';
import { Label } from '@/shared/ui/label';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation } from '@tanstack/react-query';
import { Loader2 } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import * as z from 'zod';
import { login } from '../lib/api';
const loginSchema = z.object({
login: z.string().min(1, 'Login is required'),
password: z.string().min(1, 'Password is required'),
});
const Login = () => {
const navigate = useRouter();
const form = useForm<z.infer<typeof loginSchema>>({
resolver: zodResolver(loginSchema),
defaultValues: {
login: '',
password: '',
},
});
const { mutate, isPending } = useMutation({
mutationFn: (body: { username: string; password: string }) => login(body),
onSuccess: (res) => {
saveToken(res.data.data.accessToken);
navigate.back();
},
onError: () => {
toast.error('Xatolik yuz berdi', {
richColors: true,
position: 'top-center',
});
},
});
function onSubmit(values: z.infer<typeof loginSchema>) {
mutate({
password: values.password,
username: values.login,
});
}
return (
<div className="min-h-screen flex items-center justify-center p-4 w-full">
<Card className="w-full max-w-md sm:max-w-lg md:max-w-xl lg:max-w-2xl shadow-lg">
<CardHeader>
<CardTitle className="text-2xl sm:text-3xl font-semibold text-center">
Login
</CardTitle>
</CardHeader>
<CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 sm:space-y-8"
>
<FormField
control={form.control}
name="login"
render={({ field }) => (
<FormItem>
<Label className="text-base sm:text-lg">Login</Label>
<FormControl>
<Input
placeholder="Enter your login"
className="h-12 sm:h-14 text-base sm:text-lg placeholder:text-base sm:placeholder:text-lg"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<Label className="text-base sm:text-lg">Password</Label>
<FormControl>
<Input
type="password"
placeholder="Enter your password"
className="h-12 sm:h-14 text-base sm:text-lg placeholder:text-base sm:placeholder:text-lg"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full h-12 sm:h-14 text-lg sm:text-xl rounded-xl sm:rounded-2xl cursor-pointer"
>
{isPending ? <Loader2 className="animate-spin" /> : 'Send'}
</Button>
</form>
</Form>
</CardContent>
</Card>
</div>
);
};
export default Login;

View File

@@ -2,16 +2,27 @@
import Passport from '@/assets/passport.jpg'; import Passport from '@/assets/passport.jpg';
import { getBoxes } from '@/shared/config/api/testApi'; import { getBoxes } from '@/shared/config/api/testApi';
import { useRouter } from '@/shared/config/i18n/navigation';
import { getToken } from '@/shared/lib/cookie';
import formatPhone from '@/shared/lib/formatPhone'; import formatPhone from '@/shared/lib/formatPhone';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import Image from 'next/image'; import Image from 'next/image';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useEffect } from 'react';
const Boxes = () => { const Boxes = () => {
const { id } = useParams(); const { id } = useParams();
const token = getToken();
const navigate = useRouter();
useEffect(() => {
if (!token) {
navigate.push('/boxes/qr-code');
}
}, [token]);
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['boxes_detail'], queryKey: ['boxes_detail', id],
queryFn: () => getBoxes.boxesDetail({ id: Number(id) }), queryFn: () => getBoxes.boxesDetail({ id: Number(id) }),
select(data) { select(data) {
return data.data.data; return data.data.data;