diff --git a/package-lock.json b/package-lock.json index 129aedc..d02e532 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", + "formik": "^2.4.9", "framer-motion": "^12.23.3", "i18next": "^25.3.2", "i18next-resources-to-backend": "^1.2.1", @@ -40,9 +41,11 @@ "react-dom": "^19.0.0", "react-i18next": "^15.6.1", "react-leaflet": "^5.0.0", + "react-otp-input": "^3.1.1", "react-virtualized": "^9.22.6", "tailwind-merge": "^3.3.1", "use-long-press": "^3.3.0", + "yup": "^1.7.1", "zustand": "^5.0.6" }, "devDependencies": { @@ -126,7 +129,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -3231,7 +3233,6 @@ "node_modules/@tanstack/react-query": { "version": "5.83.0", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.83.0" }, @@ -3296,28 +3297,9 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", + "dev": true, "license": "MIT" }, "node_modules/@types/geojson": { @@ -3336,6 +3318,18 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "license": "MIT" @@ -3380,18 +3374,16 @@ }, "node_modules/@types/react": { "version": "19.1.8", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { "version": "19.1.6", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -3460,7 +3452,6 @@ "version": "8.38.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", @@ -3691,168 +3682,9 @@ "darwin" ] }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" - }, "node_modules/acorn": { "version": "8.15.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3860,18 +3692,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "dev": true, @@ -3885,7 +3705,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4319,7 +4138,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -4443,15 +4261,6 @@ "node": ">=18" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "license": "Apache-2.0", @@ -4892,6 +4701,7 @@ }, "node_modules/enhanced-resolve": { "version": "5.18.2", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -5007,12 +4817,6 @@ "node": ">= 0.4" } }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" - }, "node_modules/es-object-atoms": { "version": "1.1.1", "license": "MIT", @@ -5086,7 +4890,6 @@ "version": "9.32.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -5247,7 +5050,6 @@ "version": "2.32.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5465,6 +5267,7 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -5475,6 +5278,7 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5493,15 +5297,6 @@ "node": ">=0.10.0" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "license": "MIT" @@ -5720,6 +5515,40 @@ "node": ">= 6" } }, + "node_modules/formik": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.9.tgz", + "integrity": "sha512-5nI94BMnlFDdQRBY4Sz39WkhxajZJ57Fzs8wVbtsQlm5ScKIR1QLYqv/ultBnobObtlUyxpxoLodpixrsf36Og==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/formik/node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/framer-motion": { "version": "12.23.9", "license": "MIT", @@ -5925,12 +5754,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, "node_modules/globals": { "version": "14.0.0", "dev": true, @@ -6068,6 +5891,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "license": "MIT", @@ -6092,7 +5924,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -6697,12 +6528,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -6794,8 +6619,7 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/leven": { "version": "3.1.0", @@ -7053,19 +6877,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/loader-utils": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", @@ -7112,6 +6923,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -7365,18 +7182,11 @@ "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, "node_modules/next": { "version": "15.5.6", "resolved": "https://registry.npmjs.org/next/-/next-15.5.6.tgz", "integrity": "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", @@ -7744,7 +7554,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7909,6 +7718,12 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "license": "MIT" @@ -7950,7 +7765,6 @@ "node_modules/react": { "version": "19.1.0", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7977,7 +7791,6 @@ "node_modules/react-dom": { "version": "19.1.0", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -7985,6 +7798,12 @@ "react": "^19.1.0" } }, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", + "license": "MIT" + }, "node_modules/react-i18next": { "version": "15.6.1", "license": "MIT", @@ -8031,6 +7850,16 @@ "version": "3.0.4", "license": "MIT" }, + "node_modules/react-otp-input": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/react-otp-input/-/react-otp-input-3.1.1.tgz", + "integrity": "sha512-bjPavgJ0/Zmf/AYi4onj8FbH93IjeD+e8pWwxIJreDEWsU1ILR5fs8jEJmMGWSBe/yyvPP6X/W6Mk9UkOCkTPw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.6 || ^17.0.0 || ^18.0.0", + "react-dom": ">=16.8.6 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.1", "license": "MIT", @@ -8289,7 +8118,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -8453,7 +8281,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9313,6 +9140,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9436,6 +9264,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "dev": true, @@ -9461,6 +9301,12 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -9595,9 +9441,8 @@ }, "node_modules/typescript": { "version": "5.8.3", - "devOptional": true, + "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10079,74 +9924,12 @@ "node": ">=0.10.0" } }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "license": "BSD-2-Clause" }, - "node_modules/webpack": { - "version": "5.102.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", - "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, "node_modules/webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", @@ -10157,56 +9940,6 @@ "source-map": "~0.6.1" } }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", @@ -10567,6 +10300,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", + "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zustand": { "version": "5.0.6", "license": "MIT", diff --git a/package.json b/package.json index 8e2fe5b..7822c4a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", + "formik": "^2.4.9", "framer-motion": "^12.23.3", "i18next": "^25.3.2", "i18next-resources-to-backend": "^1.2.1", @@ -41,9 +42,11 @@ "react-dom": "^19.0.0", "react-i18next": "^15.6.1", "react-leaflet": "^5.0.0", + "react-otp-input": "^3.1.1", "react-virtualized": "^9.22.6", "tailwind-merge": "^3.3.1", "use-long-press": "^3.3.0", + "yup": "^1.7.1", "zustand": "^5.0.6" }, "devDependencies": { diff --git a/src/app/auth/login/hooks/useAuthData.ts b/src/app/auth/login/hooks/useAuthData.ts new file mode 100644 index 0000000..0ad3761 --- /dev/null +++ b/src/app/auth/login/hooks/useAuthData.ts @@ -0,0 +1,14 @@ +import { useMutation } from "@tanstack/react-query"; +import * as api from "../service/LoginService"; + +export const useOtpRequest = () => { + return useMutation({ + mutationFn: api.loginOTP, + }); +}; + +export const useOtpVerify = () => { + return useMutation({ + mutationFn: api.loginVerifyOTP, + }); +}; diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 6de8d96..136a1db 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -1,169 +1,23 @@ 'use client' -import React, { - useState, - type FormEvent, - type ChangeEvent, - useEffect, - useTransition -} from 'react' -import { useAuthStore } from '@/zustand/authStore' -import { redirect, useParams, useRouter } from 'next/navigation' -import StepEnterPassword from '@/features/auth/components/StepEnterPassword' +import React from 'react' import StepEnterNumber from '@/features/auth/components/StepEnterNumber' -import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums' import AuthFormWrapper from '@/features/auth/components/AuthFormWrapper' -import { useLogin } from '@/hooks/auth/useLogin' -import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists' -import { useAuthTempStore } from '@/zustand/authTempStroe' type Props = object -function AuthIndex ({}: Props) { - const update_temp_store = useAuthTempStore(state => state.update) - const user_temp_store = useAuthTempStore(state => state.user) - const [number, setNumber] = useState(user_temp_store?.number ?? '') - const [password, setPassword] = useState('') - const [showPassword, setShowPassword] = useState(false) - const [rememberMe, setRememberMe] = useState(false) - const [step, setStep] = useState( - number == '' ? AUTH_STEP.ENTER_NUMBER : AUTH_STEP.ENTER_CURRENT_PASSWORD - ) - const [isPending, startTransition] = useTransition() - - const isAuthenticated = useAuthStore(state => state.isAuthenticated) - const loginMutation = useLogin() - const { run: runUserExistCheck } = useCheckUserExistsLazy() - const router = useRouter() - const { name } = useParams() - const basePath = `/${name ?? ''}` - - useEffect(() => { - if (isAuthenticated) { - redirect(basePath) - } - }, [isAuthenticated, basePath]) - - const onChange = (e: ChangeEvent) => { - if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_PHONE) { - setNumber(() => e.target.value) - } else if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_PASSWORD) { - setPassword(() => e.target.value) - } else if (e.target.name == AUTH_PAGE_ELEMENT.INPUT_REMEMBER_ME) { - setRememberMe(state => !state) - } - } - - const onClick = async ( - e: - | React.MouseEvent - | React.MouseEvent - ) => { - e.stopPropagation() - const target = e.target as HTMLButtonElement - - if (target.id === AUTH_PAGE_ELEMENT.TOGGLE_PASSWORD) { - setShowPassword(state => !state) - } - } - - const onSubmit = async (e: FormEvent) => { - e.preventDefault() - startTransition(async () => { - try { - /* DEV SECTION */ - /* REMOVE THIS CODE FOR PRODUCTION */ - throw false - /* END OF DEV SECTION */ - - /* SECTION DETAILS */ - /* if number is entered in the /auth page this page directly shows the password page */ - /* Otherwise it shows the number page */ - if (step == AUTH_STEP.ENTER_NUMBER) { - const userExists = await runUserExistCheck(number) - update_temp_store({ - number: number, - mustLogin: userExists - }) - if (userExists) { - setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD) - } else { - router.replace(`${basePath}/auth/signup`) - } - } else if ( - step == AUTH_STEP.ENTER_OTP || - step == AUTH_STEP.ENTER_CREATE_PASSWORD || - step == AUTH_STEP.ENTER_RESET_PASSWORD - ) { - setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD) - } else if (step == AUTH_STEP.ENTER_CURRENT_PASSWORD) { - update_temp_store({ - password, - rememberMe - }) - try { - await loginMutation.mutateAsync({ phone: number, password }) - console.log('Signed in') - router.replace(basePath) - } catch (e) { - console.error('Could not Login: ', e) - } - } - } catch (ex) { - console.error(ex) - // TODO: Add toast errors - - /* DEV SECTION */ - /* REMOVE THIS CODE FOR PRODUCTION */ - if (step == AUTH_STEP.ENTER_NUMBER) { - update_temp_store({ - number, - mustLogin: !number.endsWith('0') - }) - setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD) - } else if ( - step == AUTH_STEP.ENTER_OTP || - step == AUTH_STEP.ENTER_CREATE_PASSWORD || - step == AUTH_STEP.ENTER_RESET_PASSWORD - ) { - setStep(AUTH_STEP.ENTER_CURRENT_PASSWORD) - } else if (step == AUTH_STEP.ENTER_CURRENT_PASSWORD) { - update_temp_store({ - password, - rememberMe - }) - try { - console.log('Signed in') - router.replace(basePath) - } catch (e) { - console.error('Could not Login: ', e) - } - } - /* END OF DEV SECTION */ - } - }) - } - +function AuthIndex({ }: Props) { return ( - - {step === AUTH_STEP.ENTER_NUMBER && ( - - )} - - {step === AUTH_STEP.ENTER_CURRENT_PASSWORD && ( - - )} + undefined}> + + {/* undefined} + onClick={() => undefined} + value='' + passwordVisible={false} + rememberMe={false} + /> */} ) } diff --git a/src/app/auth/login/service/LoginService.ts b/src/app/auth/login/service/LoginService.ts new file mode 100644 index 0000000..055f1fc --- /dev/null +++ b/src/app/auth/login/service/LoginService.ts @@ -0,0 +1,12 @@ +import { api } from "@/lib/api/axiosInstance"; +import { LoginOTPRequestType, LoginVerifyOTPType } from "../types/Types"; + +export const loginOTP = async (params: LoginOTPRequestType) => { + const { data } = await api.post("/auth/otp/request", params); + return data; +}; + +export const loginVerifyOTP = async (params: LoginVerifyOTPType) => { + const { data } = await api.post("/auth/otp/verify", params); + return data; +}; diff --git a/src/app/auth/login/types/Types.ts b/src/app/auth/login/types/Types.ts new file mode 100644 index 0000000..26cc37e --- /dev/null +++ b/src/app/auth/login/types/Types.ts @@ -0,0 +1,9 @@ +export type LoginOTPRequestType = { + phone: string; + slug: string; +}; + +export type LoginVerifyOTPType = { + phone: string; + otp: string; +}; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0f0fd88..584b0ab 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { notFound } from "next/navigation"; import i18nConfig from "../../i18nConfig"; import initTranslations from '@/lib/i18n'; import TranslationsProvider from "@/components/utils/TranslationsProdiver"; +import ToastContainer from "@/components/Toast"; export const metadata: Metadata = { title: 'Dashboard', @@ -49,6 +50,7 @@ export default async function RootLayout({ {children} + diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx new file mode 100644 index 0000000..8a83b89 --- /dev/null +++ b/src/components/Toast.tsx @@ -0,0 +1,73 @@ +'use client' +import { CloseCircle, InfoCircle, TickCircle } from 'iconsax-react'; +import React, { useState, useEffect } from 'react'; + +interface Toast { + id: string; + message: string; + type?: 'success' | 'error' | 'info'; + isExiting?: boolean; +} + +let addToast: (toast: Toast) => void; + +const ToastContainer: React.FC = () => { + const [toasts, setToasts] = useState([]); + + // ثبت toast جدید + const showToast = (toast: Toast) => { + setToasts((prev) => [...prev, toast]); + + // حذف خودکار بعد از ۳ ثانیه + setTimeout(() => { + setToasts((prev) => prev.map(t => + t.id === toast.id ? { ...t, isExiting: true } : t + )); + + // حذف از DOM بعد از اتمام انیمیشن + setTimeout(() => { + setToasts((prev) => prev.filter((t) => t.id !== toast.id)); + }, 300); // مدت زمان انیمیشن خروج + }, 3000); + }; + + // تخصیص تابع نمایش toast به متغیر سراسری + useEffect(() => { + addToast = showToast; + }, []); + + return ( +
+ {toasts.map((toast) => ( +
+ { + toast.type === 'success' ? : + toast.type === 'error' ? : + + } + {toast.message} +
+ ))} +
+ ); +}; + +export const toast = (message?: string, type: 'success' | 'error' | 'info' = 'info') => { + addToast({ id: Date.now().toString(), message: message || '', type }); +}; + +export default ToastContainer; \ No newline at end of file diff --git a/src/components/input/InputField.tsx b/src/components/input/InputField.tsx index fba3166..a73b27b 100644 --- a/src/components/input/InputField.tsx +++ b/src/components/input/InputField.tsx @@ -12,30 +12,35 @@ type Props = { } & Omit, "id">; -function InputField({ onChange, htmlFor, labelText, children, valid = true, className, ...inputProps }: Props) { - return ( -
- +const InputField = React.forwardRef( + ({ onChange, htmlFor, labelText, children, valid = true, className, ...inputProps }, ref) => { + return ( +
+ - -
- ) -} + +
+ ) + } +) + +InputField.displayName = 'InputField' export default InputField \ No newline at end of file diff --git a/src/enums/authStep.ts b/src/enums/authStep.ts index 1eb0c27..5e1227b 100644 --- a/src/enums/authStep.ts +++ b/src/enums/authStep.ts @@ -1,7 +1,4 @@ export enum AUTH_STEP { - ENTER_NUMBER = 0, - ENTER_CURRENT_PASSWORD = 1, - ENTER_OTP = 2, - ENTER_CREATE_PASSWORD = 3, - ENTER_RESET_PASSWORD = 4, -} \ No newline at end of file + ENTER_NUMBER = 0, + ENTER_OTP = 1, +} diff --git a/src/features/auth/components/AuthFormWrapper.tsx b/src/features/auth/components/AuthFormWrapper.tsx index 6c9d7ee..8c1eb50 100644 --- a/src/features/auth/components/AuthFormWrapper.tsx +++ b/src/features/auth/components/AuthFormWrapper.tsx @@ -5,12 +5,24 @@ import React from 'react' type Props = { isPending: boolean -} & Omit, "id">; +} & Omit, 'id'>; // eslint-disable-next-line @typescript-eslint/no-unused-vars -function AuthFormWrapper({ children, isPending, ...restProps }: Props) { +function AuthFormWrapper({ children, isPending, onSubmit, ...restProps }: Props) { + const handleSubmit = React.useCallback( + (event: React.FormEvent) => { + event.preventDefault(); + onSubmit?.(event); + }, + [onSubmit], + ); + return ( -
+
{children} {/* */} diff --git a/src/features/auth/components/StepEnterNumber.tsx b/src/features/auth/components/StepEnterNumber.tsx index 3c7073b..1bc4ad6 100644 --- a/src/features/auth/components/StepEnterNumber.tsx +++ b/src/features/auth/components/StepEnterNumber.tsx @@ -2,35 +2,55 @@ import Button from '@/components/button/PrimaryButton' import InputField from '@/components/input/InputField' -import { AUTH_PAGE_ELEMENT } from '@/enums' +import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums' +import { LoginOTPRequestType } from '@/app/auth/login/types/Types' import Image from 'next/image' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { useTranslation } from 'react-i18next' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import { useOtpRequest } from '@/app/auth/login/hooks/useAuthData' +import { useParams } from 'next/navigation' +import { toast } from '@/components/Toast' +import StepOtp from './StepOtp' -type Props = { - onChange: React.ChangeEventHandler - value: string - pending?: boolean | undefined -} & Omit, 'id'> -function StepEnterNumber ({ onChange, pending, disabled, value }: Props) { - const [error, setError] = useState('') + +function StepEnterNumber() { + const { t } = useTranslation('auth') + const { name } = useParams() + const [step, setStep] = useState(AUTH_STEP.ENTER_NUMBER) - const hasError = (e = error) => { - return e.trim().length > 0 - } + const { mutate: mutateOtpRequest } = useOtpRequest() - useEffect(() => { - let error = '' - if (value.length > 0) { - if (!/^09\d{7,9}$/.test(value)) { - error = t('Errors.PhoneNumberFormat') + const formik = useFormik({ + initialValues: { + phone: '', + slug: '', + }, + validationSchema: Yup.object({ + phone: Yup.string().required('Errors.PhoneNumberRequired').matches(/^09\d{9}$/, 'Errors.PhoneNumberFormat'), + }), + onSubmit: (values) => { + if (name) { + values.slug = name as string + values.phone = 'qtest' + mutateOtpRequest(values, { + onSuccess: () => { + toast(t('auth.otp_sent')) + setStep(AUTH_STEP.ENTER_OTP) + }, + onError: (error) => { + // toast(t('auth.otp_sent_error')) + console.log(error); + + } + }) } } + }) - setError(error) - }, [t, value]) return ( <> @@ -46,27 +66,43 @@ function StepEnterNumber ({ onChange, pending, disabled, value }: Props) {
-
{t('Enter.Heading')}
-

{t('Enter.Description')}

+ { + step === AUTH_STEP.ENTER_NUMBER && ( + <> +
{t('Enter.Heading')}
+

{t('Enter.Description')}

+ + ) + } + { + step === AUTH_STEP.ENTER_OTP && ( + <> +
{t('OTP.Heading')}
+

{t('OTP.Description', { phoneNumber: formik.values.phone })}

+ + ) + }
- + {step === AUTH_STEP.ENTER_NUMBER && ( + + )} + {step === AUTH_STEP.ENTER_OTP && ( + + )}
diff --git a/src/features/auth/components/StepOtp.tsx b/src/features/auth/components/StepOtp.tsx new file mode 100644 index 0000000..3b00704 --- /dev/null +++ b/src/features/auth/components/StepOtp.tsx @@ -0,0 +1,59 @@ +import { LoginVerifyOTPType } from '@/app/auth/login/types/Types' +import React from 'react' +import OTPInput from 'react-otp-input' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import { useTranslation } from 'react-i18next' +import { useOtpVerify } from '@/app/auth/login/hooks/useAuthData' +import { toast } from '@/components/Toast' + +type Props = { + phone: string +} + +const StepOtp = ({ phone }: Props) => { + + const { t } = useTranslation('auth') + const { mutate: mutateOtpVerify } = useOtpVerify() + const formik = useFormik({ + initialValues: { + phone: phone, + otp: '' + }, + validationSchema: Yup.object({ + otp: Yup.string().required(t('Errors.OTPRequired')) + }), + onSubmit: (values) => { + mutateOtpVerify(values, { + onSuccess: () => { + toast(t('auth.otp_verified')) + }, + onError: (error) => { + console.log(error) + } + }) + } + }) + return ( +
+ formik.setFieldValue('otp', otp)} + renderInput={(props) => + + } + /> +
+ ) +} + +export default StepOtp \ No newline at end of file diff --git a/src/lib/api/axiosInstance.ts b/src/lib/api/axiosInstance.ts index 6873e33..08b7be3 100644 --- a/src/lib/api/axiosInstance.ts +++ b/src/lib/api/axiosInstance.ts @@ -1,11 +1,11 @@ -import { useAuthStore } from '@/zustand/authStore'; -import axios from 'axios'; -import { refreshToken } from './auth/refresh-token'; +import { useAuthStore } from "@/zustand/authStore"; +import axios from "axios"; +import { refreshToken } from "./auth/refresh-token"; // Create axios instance const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com', - headers: { 'Content-Type': 'application/json' }, + baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, + headers: { "Content-Type": "application/json" }, }); let isRefreshing = false; @@ -65,10 +65,9 @@ api.interceptors.response.use( isRefreshing = true; const token = useAuthStore.getState().user?.refreshToken; - console.log(useAuthStore.getState()) + console.log(useAuthStore.getState()); if (!token) { - useAuthStore.getState().logout(); return Promise.reject(error); } diff --git a/src/locales/en/auth.json b/src/locales/en/auth.json index 467ea6e..95edf97 100644 --- a/src/locales/en/auth.json +++ b/src/locales/en/auth.json @@ -7,7 +7,7 @@ }, "OTP": { "Heading": "Enter Activation Code", - "Description": "Please enter the 6-digit code sent to the number {phoneNumber}.", + "Description": "Please enter the 6-digit code sent to the number {{phoneNumber}}.", "Label": "", "TimerRunning": "{seconds} seconds left to receive the code", "TimerOut": "Didn’t receive the code?", @@ -33,6 +33,7 @@ "Errors": { "PasswordLength": "Password must be at least 6 characters long", "PasswordMatch": "Repeated password does not match", + "PhoneNumberRequired": "Phone number is required", "PhoneNumberFormat": "Phone number format is incorrect", "OTPFormat": "Activation code is incomplete" } diff --git a/src/locales/fa/auth.json b/src/locales/fa/auth.json index e0a2462..dc14264 100644 --- a/src/locales/fa/auth.json +++ b/src/locales/fa/auth.json @@ -1,39 +1,40 @@ { - "Enter": { - "Heading": "ورود به سیستم", - "Description": "برای ورود شماره همراه خود را وارد نمایید.", - "LabelPhoneNumber": "شماره همراه", - "ButtonSubmit": "بعدی" - }, - "OTP": { - "Heading": "کد فعالسازی را وارد کنید", - "Description": "کد ۶ رقمی ارسال شده به شماره {phoneNumber} را وارد نمایید.", - "Label": "", - "TimerRunning": "{seconds} ثانیه دیگر تا دریافت کد", - "TimerOut": "کد را دریافت نکردید؟", - "TimerReset": "دوباره امتحان کنید", - "ButtonSubmit": "بعدی" - }, - "NewPassword": { - "HeadingCreatePass": "انتخاب کلمه عبور", - "HeadingResetPass": "تغییر کلمه عبور", - "Description": "کلمه عبور جدید باید ترکیبی از حروف بزرگ و کوچک و نشانه ها باشد مثل A126bdz@", - "LabelPass": "کلمه عبور", - "LabelRepeatPass": "تکرار کلمه عبور", - "ButtonSubmit": "ورود" - }, - "EnterPass": { - "Heading": "ورود به سیستم", - "Description": "کلمه عبور خود را وارد نمایید.", - "LabelPass": "کلمه عبور", - "ButtonRecover": "بازیابی کلمه عبور", - "LabelRememberance": "مرا به خاطر بسپار", - "ButtonSubmit": "ورود به منو" - }, - "Errors": { - "PasswordLength": "کلمه عبور باید شامل حداقل 6 کاراکتر باشد", - "PasswordMatch": "تکرار کلمه عبور مطابقت ندارد", - "PhoneNumberFormat": "فرمت شماره تلفن اشتباه است", - "OTPFormat": "کد فعالسازی کامل نیست" - } -} \ No newline at end of file + "Enter": { + "Heading": "ورود به سیستم", + "Description": "برای ورود شماره همراه خود را وارد نمایید.", + "LabelPhoneNumber": "شماره همراه", + "ButtonSubmit": "بعدی" + }, + "OTP": { + "Heading": "کد فعالسازی را وارد کنید", + "Description": "کد ۵ رقمی ارسال شده به شماره {{phoneNumber}} را وارد نمایید.", + "Label": "", + "TimerRunning": "{seconds} ثانیه دیگر تا دریافت کد", + "TimerOut": "کد را دریافت نکردید؟", + "TimerReset": "دوباره امتحان کنید", + "ButtonSubmit": "بعدی" + }, + "NewPassword": { + "HeadingCreatePass": "انتخاب کلمه عبور", + "HeadingResetPass": "تغییر کلمه عبور", + "Description": "کلمه عبور جدید باید ترکیبی از حروف بزرگ و کوچک و نشانه ها باشد مثل A126bdz@", + "LabelPass": "کلمه عبور", + "LabelRepeatPass": "تکرار کلمه عبور", + "ButtonSubmit": "ورود" + }, + "EnterPass": { + "Heading": "ورود به سیستم", + "Description": "کلمه عبور خود را وارد نمایید.", + "LabelPass": "کلمه عبور", + "ButtonRecover": "بازیابی کلمه عبور", + "LabelRememberance": "مرا به خاطر بسپار", + "ButtonSubmit": "ورود به منو" + }, + "Errors": { + "PasswordLength": "کلمه عبور باید شامل حداقل 6 کاراکتر باشد", + "PasswordMatch": "تکرار کلمه عبور مطابقت ندارد", + "PhoneNumberRequired": "وارد کردن شماره تلفن اجباری است", + "PhoneNumberFormat": "فرمت شماره تلفن اشتباه است", + "OTPFormat": "کد فعالسازی کامل نیست" + } +}