api registration done
This commit is contained in:
parent
fd0a327b13
commit
653055debc
203
package-lock.json
generated
203
package-lock.json
generated
@ -8,15 +8,12 @@
|
||||
"name": "thirukalyanam",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^3.2.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@files-ui/react": "^1.2.5",
|
||||
"@lottiefiles/dotlottie-react": "^0.17.8",
|
||||
"@mui/icons-material": "^7.3.5",
|
||||
"@mui/lab": "^7.0.1-beta.19",
|
||||
"@mui/material": "^7.3.5",
|
||||
"@mui/styled-engine-sc": "^7.3.5",
|
||||
"@mui/x-date-pickers": "^8.19.0",
|
||||
"@reduxjs/toolkit": "^2.11.0",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
@ -34,14 +31,11 @@
|
||||
"react-lazy-load-image-component": "^1.6.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.9.6",
|
||||
"styled-components": "^6.1.19",
|
||||
"swiper": "^12.0.3",
|
||||
"tailwindcss": "^4.1.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
@ -92,6 +86,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@ -343,29 +338,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@date-io/core": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-3.2.0.tgz",
|
||||
"integrity": "sha512-hqwXvY8/YBsT9RwQITG868ZNb1MVFFkF7W1Ecv4P472j/ZWa7EFcgSmxy8PUElNVZfvhdvfv+a8j6NWJqOX5mA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@date-io/date-fns": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-3.2.1.tgz",
|
||||
"integrity": "sha512-CtXgTOAamkImI+CmbWRNdBi4ljj9xm/tdoPa+eeeiygduzubJTsXp18vYz+Vs/9yLho1zUOXlxpsfsF7PsXSWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@date-io/core": "^3.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"date-fns": "^3.2.0 || ^4.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"date-fns": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||
@ -452,6 +424,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@ -495,6 +468,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
||||
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@ -1197,6 +1171,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.9.tgz",
|
||||
"integrity": "sha512-3gtUX0e584MYkKBQMgSECMvE1Dwzg+eONefDQ0wxVSe5YMBsZwdN5pL7UapwWBlV8+i8QCztF9TP947tEjZAGA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
@ -1263,6 +1238,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.9.tgz",
|
||||
"integrity": "sha512-e5LzqjO69/N2z7XcJeuMzIp4wWnW696dQeaHAUpQvGk89gIWHAIvG6W+mA3UotGW6jBoqdppEJ9DnuwbcBByug==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@firebase/app": "0.14.9",
|
||||
"@firebase/component": "0.7.1",
|
||||
@ -1278,7 +1254,8 @@
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
|
||||
"integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@firebase/auth-compat": {
|
||||
"version": "0.6.3",
|
||||
@ -1735,6 +1712,7 @@
|
||||
"integrity": "sha512-/gnejm7MKkVIXnSJGpc9L2CvvvzJvtDPeAEq5jAwgVlf/PeNxot+THx/bpD20wQ8uL5sz0xqgXy1nisOYMU+mw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@ -1886,24 +1864,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@lottiefiles/dotlottie-react": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-react/-/dotlottie-react-0.17.8.tgz",
|
||||
"integrity": "sha512-Hk0bISNURSqL7t+H7S5lW2NQVa1hiibnqRRg6kOWZpswBxfQk+/6WBPc9EfuetdoZmiMoDsmcI0HR4I20oTBRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lottiefiles/dotlottie-web": "0.57.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@lottiefiles/dotlottie-web": {
|
||||
"version": "0.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-web/-/dotlottie-web-0.57.0.tgz",
|
||||
"integrity": "sha512-gcgvu9T21YzeY3JjHCZrxftucsxzMH6e9h+8NMv8mbfo1y1M9/jdcsdu40S+pnSLz9/OyiSBQ/EjDsbSOHZy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mediapipe/tasks-vision": {
|
||||
"version": "0.10.17",
|
||||
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
|
||||
@ -2024,6 +1984,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
|
||||
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/core-downloads-tracker": "^7.3.5",
|
||||
@ -2129,34 +2090,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine-sc": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-7.3.5.tgz",
|
||||
"integrity": "sha512-tm2KcMF9Y4vhi38ITxhTkeGhux/sCB+AuBz2AyjXKOxuZ9iSUsixAKvzeH/HGbQaih8qVF3ocbq95PQZ/bd/gQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@types/hoist-non-react-statics": "^3.3.7",
|
||||
"csstype": "^3.1.3",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"styled-components": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz",
|
||||
"integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/private-theming": "^7.3.5",
|
||||
@ -3113,7 +3052,8 @@
|
||||
}
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tsparticles/interaction-external-attract": {
|
||||
"version": "3.9.1",
|
||||
@ -3731,20 +3671,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-reconciler": {
|
||||
"version": "0.26.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz",
|
||||
@ -3780,6 +3711,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.181.0.tgz",
|
||||
"integrity": "sha512-MLF1ks8yRM2k71D7RprFpDb9DOX0p22DbdPqT/uAkc6AtQXjxWCVDjCy23G9t1o8HcQPk7woD2NIyiaWcWPYmA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
@ -4052,6 +3984,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -4336,6 +4269,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
@ -4767,7 +4701,8 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
@ -4895,6 +4830,7 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@ -5009,7 +4945,8 @@
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "8.6.0",
|
||||
@ -5192,6 +5129,7 @@
|
||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@ -6925,6 +6863,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.18.0.tgz",
|
||||
"integrity": "sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@types/react-reconciler": "^0.26.7",
|
||||
@ -7007,6 +6946,7 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@ -7614,6 +7554,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -7722,6 +7663,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@ -7998,6 +7940,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -8007,6 +7950,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@ -8104,6 +8048,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@ -8330,7 +8275,8 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@ -8569,6 +8515,7 @@
|
||||
"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",
|
||||
@ -8789,89 +8736,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components": {
|
||||
"version": "6.1.19",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz",
|
||||
"integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "1.2.2",
|
||||
"@emotion/unitless": "0.8.1",
|
||||
"@types/stylis": "4.2.5",
|
||||
"css-to-react-native": "3.2.0",
|
||||
"csstype": "3.1.3",
|
||||
"postcss": "8.4.49",
|
||||
"shallowequal": "1.1.0",
|
||||
"stylis": "4.3.2",
|
||||
"tslib": "2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/styled-components"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0",
|
||||
"react-dom": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
|
||||
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/@emotion/memoize": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/styled-components/node_modules/@emotion/unitless": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
|
||||
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/styled-components/node_modules/postcss": {
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/stylis": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
|
||||
"integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
@ -9336,6 +9200,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@ -9488,6 +9353,7 @@
|
||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
|
||||
"integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@discoveryjs/json-ext": "^0.5.0",
|
||||
"@webpack-cli/configtest": "^1.2.0",
|
||||
@ -9726,6 +9592,7 @@
|
||||
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@ -10,15 +10,12 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^3.2.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@files-ui/react": "^1.2.5",
|
||||
"@lottiefiles/dotlottie-react": "^0.17.8",
|
||||
"@mui/icons-material": "^7.3.5",
|
||||
"@mui/lab": "^7.0.1-beta.19",
|
||||
"@mui/material": "^7.3.5",
|
||||
"@mui/styled-engine-sc": "^7.3.5",
|
||||
"@mui/x-date-pickers": "^8.19.0",
|
||||
"@reduxjs/toolkit": "^2.11.0",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
@ -36,14 +33,11 @@
|
||||
"react-lazy-load-image-component": "^1.6.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.9.6",
|
||||
"styled-components": "^6.1.19",
|
||||
"swiper": "^12.0.3",
|
||||
"tailwindcss": "^4.1.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
|
||||
14
src/App.jsx
14
src/App.jsx
@ -7,8 +7,18 @@ import { generateToken, listenToMessages } from "./notifications/firebase";
|
||||
function App() {
|
||||
useEffect(()=>{
|
||||
|
||||
generateToken();
|
||||
listenToMessages(); // foreground notifications
|
||||
const run = () => {
|
||||
generateToken();
|
||||
listenToMessages(); // foreground notifications
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined" && "requestIdleCallback" in window) {
|
||||
const handle = window.requestIdleCallback(run);
|
||||
return () => window.cancelIdleCallback(handle);
|
||||
}
|
||||
|
||||
const timeout = setTimeout(run, 800);
|
||||
return () => clearTimeout(timeout);
|
||||
|
||||
},[]);
|
||||
return (
|
||||
|
||||
@ -24,5 +24,7 @@ REGISTER_STEP2:"update_educational_details", // educational details updated ap
|
||||
REGSITER_STEP3:"update_family_details", // family details updated api
|
||||
REGISTER_STEP4:"update_lifestyle_details", // lifestyle details updated api
|
||||
REGISTER_STEP5:"update_preferred_details", // partner preference details updated api
|
||||
PREVIEW_DETAILS: "get_preview_details",
|
||||
REVIEWS: "reviews",
|
||||
|
||||
};
|
||||
|
||||
@ -21,9 +21,10 @@ export const getSubCasteMasters = async (caste_id) => {
|
||||
};
|
||||
|
||||
export const getCityMasters = async (state_id) => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.CITY_MASTER, {
|
||||
params: { state_id },
|
||||
});
|
||||
const params = Array.isArray(state_id)
|
||||
? { state_id: `[${state_id.join(",")}]` }
|
||||
: { state_id };
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.CITY_MASTER, { params });
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@ -39,6 +40,13 @@ export const getEducationMasters = async () => {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const getEducationList = async (study_field_id) => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.EDUCATION_LIST_API, {
|
||||
params: { study_field_id },
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const getFamilyMasters = async () => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.FAMILY_DETAILS_MASTER);
|
||||
return res.data;
|
||||
|
||||
7
src/api/preview.api.js
Normal file
7
src/api/preview.api.js
Normal file
@ -0,0 +1,7 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
import { API_ENDPOINTS } from "./apiEndpoints";
|
||||
|
||||
export const getPreviewDetails = async () => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.PREVIEW_DETAILS);
|
||||
return res.data;
|
||||
};
|
||||
@ -31,4 +31,12 @@ export const registerStep3API = async (payload) => {
|
||||
export const registerStep4API = async (payload) => {
|
||||
const res = await axiosInstance.post(API_ENDPOINTS.REGISTER_STEP4, payload);
|
||||
return res.data;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* STEP 5 – Partner Preferences
|
||||
*/
|
||||
export const registerStep5API = async (payload) => {
|
||||
const res = await axiosInstance.post(API_ENDPOINTS.REGISTER_STEP5, payload);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
7
src/api/reviews.api.js
Normal file
7
src/api/reviews.api.js
Normal file
@ -0,0 +1,7 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
import { API_ENDPOINTS } from "./apiEndpoints";
|
||||
|
||||
export const getReviews = async () => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.REVIEWS);
|
||||
return res.data;
|
||||
};
|
||||
9
src/api/terms.api.js
Normal file
9
src/api/terms.api.js
Normal file
@ -0,0 +1,9 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
import { API_ENDPOINTS } from "./apiEndpoints";
|
||||
|
||||
export const getTermsAndPolicies = async (type) => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.TERMS_AND_POLICIES_PRIVACY, {
|
||||
params: { type },
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
@ -1,158 +1,23 @@
|
||||
import React, { useState } from "react";
|
||||
import { Users, Grid3x3, Heart, Crown, Bookmark } from "lucide-react";
|
||||
import LazyImage from "./LazyImage";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import HeightIcon from "@mui/icons-material/Height";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
||||
import SchoolIcon from "@mui/icons-material/School";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import { motion } from 'framer-motion';
|
||||
import React from "react";
|
||||
import ProfileCardItem from "../profiledashboard/ProfileCardItem";
|
||||
|
||||
export default function ProfileCard() {
|
||||
const [isLiked, setIsLiked] = useState(false);
|
||||
const profile = {
|
||||
id: 1,
|
||||
name: "Jerome Bell",
|
||||
userId: "JB2847593",
|
||||
age: 22,
|
||||
height: "5.2",
|
||||
religion: "Hindu / Agamudiyar/thular",
|
||||
education: "BCA / Data analyst",
|
||||
location: "Chennai",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top",
|
||||
isPremium: true,
|
||||
};
|
||||
return (
|
||||
<div className=" flex items-center justify-center p-6">
|
||||
<div className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200">
|
||||
{/* Profile Image Section */}
|
||||
<div className="relative">
|
||||
|
||||
{/* Premium Badge */}
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
|
||||
|
||||
{/* Shortlist Button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
|
||||
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top"
|
||||
alt="Profile"
|
||||
className="w-full h-90 object-cover"
|
||||
/>
|
||||
|
||||
{/* <LazyImage
|
||||
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top"
|
||||
alt="Profile"
|
||||
className="w-full h-90 object-cover"
|
||||
/> */}
|
||||
|
||||
{/* White Gradient Overlay at bottom of image */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Profile Info Overlay - positioned at bottom */}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 text-gray-900">
|
||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
||||
Jerome bell
|
||||
</h1>
|
||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
||||
Matrimony ID: JB2847593
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats and Follow Section */}
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
|
||||
style={{
|
||||
background: "rgb(255, 255, 255)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Age : 22
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Height: 5.2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Hindu / Agamudiyar/thular
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
BCA / Data analyst
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Chennai
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-3 my-2 justify-between mx-4">
|
||||
<button className="w-[60px] h-[60px] bg-[#A70710] hover:bg-red-300 text-white
|
||||
font-semibold text-base py-2 rounded-full shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center gap-2 transform hover:scale-90">
|
||||
<svg className="w-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button className="w-[60px] h-[60px] bg-[#034E08] hover:from-green-600
|
||||
hover:to-green-600 text-white font-semibold text-base
|
||||
rounded-full shadow-lg hover:shadow-xl transition-all duration-300
|
||||
transform hover:scale-105 flex items-center justify-center gap-2"
|
||||
onClick={() => setIsLiked(!isLiked)}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-7 h-7" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="#EF4444"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center p-6">
|
||||
<ProfileCardItem profile={profile} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
72
src/components/common/Skeleton.jsx
Normal file
72
src/components/common/Skeleton.jsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
const STYLE_ID = "skeleton-shimmer-styles";
|
||||
|
||||
const injectSkeletonStyles = () => {
|
||||
if (typeof document === "undefined") return;
|
||||
if (document.getElementById(STYLE_ID)) return;
|
||||
const style = document.createElement("style");
|
||||
style.id = STYLE_ID;
|
||||
style.textContent = `
|
||||
.skeleton-shimmer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.skeleton-shimmer::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform: translateX(-100%);
|
||||
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.7) 50%, rgba(255,255,255,0) 100%);
|
||||
animation: skeleton-shimmer 1.6s infinite;
|
||||
}
|
||||
@keyframes skeleton-shimmer {
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
};
|
||||
|
||||
export const Skeleton = ({
|
||||
width = "100%",
|
||||
height = 12,
|
||||
rounded = 6,
|
||||
className = "",
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
injectSkeletonStyles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`skeleton-shimmer ${className}`}
|
||||
style={{ width, height, borderRadius: rounded }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonText = ({
|
||||
lines = 3,
|
||||
gap = 10,
|
||||
height = 12,
|
||||
className = "",
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
injectSkeletonStyles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className} style={{ display: "grid", gap }}>
|
||||
{Array.from({ length: lines }).map((_, idx) => (
|
||||
<Skeleton
|
||||
key={idx}
|
||||
height={height}
|
||||
width={idx === lines - 1 ? "70%" : "100%"}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Skeleton;
|
||||
@ -1,64 +1,39 @@
|
||||
import ThreeDScrollTriggerRow, {
|
||||
ThreeDScrollTriggerContainer,
|
||||
} from "../lightswind/ThreeDScrollTrigger";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const reviews = [
|
||||
{
|
||||
name: "Aria Thompson",
|
||||
company: "CreativePixel Studio",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1517841905240-472988babdf9?auto=format&fit=facearea&w=96&h=96&facepad=2",
|
||||
review:
|
||||
"Lightswind UI has completely changed how we design modern interfaces. Every component feels elegant, responsive, and intuitive.",
|
||||
},
|
||||
{
|
||||
name: "Ethan Rivera",
|
||||
company: "CodeLoom Technologies",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1465101162946-4377e57745c3?auto=format&fit=facearea&w=96&h=96&facepad=2",
|
||||
review:
|
||||
"The customization options in Lightswind UI are unmatched. We built an entire SaaS dashboard in days instead of weeks.",
|
||||
},
|
||||
{
|
||||
name: "Liam Patel",
|
||||
company: "NextGen Interfaces",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=facearea&w=96&h=96&facepad=2",
|
||||
review:
|
||||
"From landing pages to complex dashboards, Lightswind UI makes building visually consistent UIs effortless and fun.",
|
||||
},
|
||||
{
|
||||
name: "Sarah Mitchell",
|
||||
company: "DesignFlow Co",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&w=96&h=96&facepad=2",
|
||||
review:
|
||||
"The component library is extensive and well-documented. Saved us months of development time.",
|
||||
},
|
||||
{
|
||||
name: "James Chen",
|
||||
company: "TechVision Labs",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=facearea&w=96&h=96&facepad=2",
|
||||
review:
|
||||
"Outstanding support and continuous updates. Lightswind UI keeps getting better every release.",
|
||||
},
|
||||
{
|
||||
name: "Emma Davis",
|
||||
company: "PixelPerfect Studios",
|
||||
image:
|
||||
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=facearea&w=96&h=96&facepad=2",
|
||||
review:
|
||||
"Responsive by default and looks amazing on all devices. This is exactly what we needed.",
|
||||
},
|
||||
];
|
||||
import React, { memo, useCallback, useMemo, useState } from "react";
|
||||
import { useReviews } from "../../hooks/useReviews";
|
||||
import { Skeleton } from "./Skeleton";
|
||||
|
||||
export default function ThreeScrollTrigger() {
|
||||
// Split reviews into 2 rows with 3 cards each
|
||||
const row1 = reviews.slice(0, 3);
|
||||
const row2 = reviews.slice(3, 6);
|
||||
const { data, isLoading } = useReviews();
|
||||
const [paused, setPaused] = useState(false);
|
||||
const handlePause = useCallback(() => setPaused(true), []);
|
||||
const handleResume = useCallback(() => setPaused(false), []);
|
||||
|
||||
const reviews = useMemo(() => {
|
||||
const list = data?.data || [];
|
||||
if (!Array.isArray(list)) return [];
|
||||
return list.map((item) => ({
|
||||
name: item.name,
|
||||
company: item.about,
|
||||
image: item.profile_image,
|
||||
review: item.content,
|
||||
rating: item.rating,
|
||||
}));
|
||||
}, [data]);
|
||||
|
||||
const row1 = useMemo(() => reviews.slice(0, 3), [reviews]);
|
||||
const row2 = useMemo(() => reviews.slice(3, 6), [reviews]);
|
||||
const row3 = useMemo(() => reviews.slice(6, 9), [reviews]);
|
||||
|
||||
if (!isLoading && reviews.length === 0) {
|
||||
return (
|
||||
<div className="w-full mx-auto max-w-[1400px] py-10 text-center text-gray-600">
|
||||
No reviews available.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full mx-auto max-w-[1400px] overflow-hidden">
|
||||
@ -66,44 +41,75 @@ export default function ThreeScrollTrigger() {
|
||||
{/* Row 1 - 3 Cards scrolling right */}
|
||||
<ThreeDScrollTriggerRow baseVelocity={2} direction={1} paused={paused}>
|
||||
<div className="flex flex-row gap-6 px-3 py-1">
|
||||
{row1.map((rev, idx) => (
|
||||
<ReviewCard
|
||||
key={idx}
|
||||
{...rev}
|
||||
onMouseEnter={() => setPaused(true)}
|
||||
onMouseLeave={() => setPaused(false)}
|
||||
/>
|
||||
))}
|
||||
{isLoading
|
||||
? Array.from({ length: 3 }).map((_, idx) => (
|
||||
<ReviewSkeleton key={`row1-skel-${idx}`} />
|
||||
))
|
||||
: row1.map((rev, idx) => (
|
||||
<ReviewCard
|
||||
key={`${rev.name}-${idx}`}
|
||||
{...rev}
|
||||
onMouseEnter={handlePause}
|
||||
onMouseLeave={handleResume}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ThreeDScrollTriggerRow>
|
||||
|
||||
{/* Row 2 - 3 Cards scrolling left */}
|
||||
<ThreeDScrollTriggerRow baseVelocity={2} direction={-1} paused={paused}>
|
||||
<div className="flex flex-row gap-6 px-3 py-1">
|
||||
{row2.map((rev, idx) => (
|
||||
<ReviewCard
|
||||
key={idx}
|
||||
{...rev}
|
||||
onMouseEnter={() => setPaused(true)}
|
||||
onMouseLeave={() => setPaused(false)}
|
||||
/>
|
||||
))}
|
||||
{isLoading
|
||||
? Array.from({ length: 3 }).map((_, idx) => (
|
||||
<ReviewSkeleton key={`row2-skel-${idx}`} />
|
||||
))
|
||||
: row2.map((rev, idx) => (
|
||||
<ReviewCard
|
||||
key={`${rev.name}-${idx}`}
|
||||
{...rev}
|
||||
onMouseEnter={handlePause}
|
||||
onMouseLeave={handleResume}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ThreeDScrollTriggerRow>
|
||||
|
||||
{row3.length > 0 && (
|
||||
<ThreeDScrollTriggerRow baseVelocity={2} direction={1} paused={paused}>
|
||||
<div className="flex flex-row gap-6 px-3 py-1">
|
||||
{row3.map((rev, idx) => (
|
||||
<ReviewCard
|
||||
key={`${rev.name}-${idx}`}
|
||||
{...rev}
|
||||
onMouseEnter={handlePause}
|
||||
onMouseLeave={handleResume}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ThreeDScrollTriggerRow>
|
||||
)}
|
||||
</ThreeDScrollTriggerContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Card component for clarity and reuse
|
||||
function ReviewCard({
|
||||
const ReviewCard = memo(function ReviewCard({
|
||||
name,
|
||||
company,
|
||||
image,
|
||||
review,
|
||||
rating,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
}) {
|
||||
const stars = useMemo(() => {
|
||||
if (!rating) return null;
|
||||
return Array.from({ length: 5 }, (_, i) => (
|
||||
<span key={i} className="text-[24px] ">{i < rating ? "★" : "☆"}</span>
|
||||
));
|
||||
}, [rating]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-[#fbfbfb] rounded-xl shadow-md p-6 w-80 min-w-80 flex-shrink-0 hover:shadow-lg transition-shadow"
|
||||
@ -111,15 +117,20 @@ function ReviewCard({
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<img
|
||||
src={image}
|
||||
alt={name}
|
||||
className="w-12 h-12 rounded-full object-cover flex-shrink-0"
|
||||
loading="lazy"
|
||||
/>
|
||||
{image ? (
|
||||
<img
|
||||
src={image}
|
||||
alt={name}
|
||||
className="w-12 h-12 rounded-full object-cover flex-shrink-0"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-full bg-gray-200 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-semibold text-gray-900 truncate">{name}</div>
|
||||
<div className="text-xs text-gray-500 truncate">{company}</div>
|
||||
{stars && <div className="text-[11px] text-amber-500">{stars}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-700 line-clamp-4">
|
||||
@ -127,4 +138,21 @@ function ReviewCard({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function ReviewSkeleton() {
|
||||
return (
|
||||
<div className="bg-[#fbfbfb] rounded-xl shadow-md p-6 w-80 min-w-80 flex-shrink-0">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Skeleton width={48} height={48} rounded={999} />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton width="60%" height={12} />
|
||||
<Skeleton width="40%" height={10} />
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton width="100%" height={10} />
|
||||
<Skeleton width="90%" height={10} className="mt-2" />
|
||||
<Skeleton width="70%" height={10} className="mt-2" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -103,66 +103,53 @@ const DailyRecommendedCard = () => {
|
||||
}
|
||||
];
|
||||
|
||||
// Profile Card Component
|
||||
|
||||
|
||||
const ProfileCard = ({ profile }) => {
|
||||
const ProfileCard = ({ profile }) => {
|
||||
const [isLiked, setIsLiked] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
|
||||
<motion.div
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200">
|
||||
{/* Profile Image Section */}
|
||||
transition={{ duration: 0.5 }}
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
|
||||
>
|
||||
<div className="relative">
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Premium Badge */}
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
)}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
|
||||
{/* Shortlist Button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// shortlist logic
|
||||
}}
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
<div
|
||||
className=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
||||
style={{ height: "300px" }}
|
||||
>
|
||||
<img
|
||||
src={profile.image}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div classname=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]" style={{height:"300px"}}>
|
||||
<img
|
||||
src={profile.image}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/* <LazyImage
|
||||
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top"
|
||||
alt="Profile"
|
||||
className="w-full h-90 object-cover"
|
||||
/> */}
|
||||
|
||||
{/* White Gradient Overlay at bottom of image */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
||||
style={{
|
||||
@ -171,115 +158,127 @@ className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 bor
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Profile Info Overlay - positioned at bottom */}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
||||
{profile.name}
|
||||
{profile.name}
|
||||
</h1>
|
||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
||||
Matrimony ID: {profile.userId}
|
||||
Matrimony ID: {profile.userId}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats and Follow Section */}
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
|
||||
style={{
|
||||
background: "rgb(255, 255, 255)",
|
||||
}}
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
||||
style={{ background: "rgb(255, 255, 255)" }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Age : {profile.age}
|
||||
Age : {profile.age}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Height: {profile.height}
|
||||
Height: {profile.height}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.religion}
|
||||
{profile.religion}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.education}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.location}
|
||||
{profile.location}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// your decline logic
|
||||
}}
|
||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95">
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M18 6L6 18M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
||||
transform hover:scale-105 flex items-center justify-center"
|
||||
onClick={(e) =>{
|
||||
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsLiked(!isLiked);
|
||||
} }
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="#EF4444"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
)}
|
||||
|
||||
Interest
|
||||
</button>
|
||||
</div>
|
||||
|
||||
}}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||
fill="#EF4444"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
Interest
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className=" py-10">
|
||||
<div className="w-[100%] max-w-[1400px] mx-auto">
|
||||
@ -405,4 +404,4 @@ className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 bor
|
||||
);
|
||||
};
|
||||
|
||||
export default DailyRecommendedCard;
|
||||
export default DailyRecommendedCard;
|
||||
|
||||
@ -16,11 +16,13 @@ import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import 'swiper/css/effect-coverflow';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const NewJoinedProfile = () => {
|
||||
|
||||
|
||||
const swiperRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Sample profile data
|
||||
const profiles = [
|
||||
@ -111,123 +113,173 @@ const ProfileCard = ({ profile }) => {
|
||||
const [isLiked, setIsLiked] = useState(false);
|
||||
|
||||
return (
|
||||
|
||||
<motion.div
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-2 border-gray-200">
|
||||
{/* Profile Image Section */}
|
||||
<div className="relative">
|
||||
|
||||
{/* Premium Badge */}
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Shortlist Button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
transition={{ duration: 0.5 }}
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
<div className="relative">
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
|
||||
<img
|
||||
src={profile.image}
|
||||
alt={profile.name}
|
||||
className="w-full h-65 object-cover"
|
||||
/>
|
||||
|
||||
{/* <LazyImage
|
||||
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top"
|
||||
alt="Profile"
|
||||
className="w-full h-90 object-cover"
|
||||
/> */}
|
||||
|
||||
{/* White Gradient Overlay at bottom of image */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-25 pointer-events-none"
|
||||
className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
||||
style={{ height: "300px" }}
|
||||
>
|
||||
<img
|
||||
src={profile.image}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.75) 50%, rgb(255, 255, 255) 100%)",
|
||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
||||
}}
|
||||
></div>
|
||||
|
||||
{/* Profile Info Overlay - positioned at bottom */}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 text-gray-900">
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
||||
{profile.name}
|
||||
{profile.name}
|
||||
</h1>
|
||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
||||
Matrimony ID: {profile.userId}
|
||||
Matrimony ID: {profile.userId}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats and Follow Section */}
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
|
||||
style={{
|
||||
background: "rgb(255, 255, 255)",
|
||||
}}
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
||||
style={{ background: "rgb(255, 255, 255)" }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Age : {profile.age}
|
||||
Age : {profile.age}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Height: {profile.height}
|
||||
Height: {profile.height}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.religion}
|
||||
{profile.religion}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.education}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.location}
|
||||
{profile.location}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M18 6L6 18M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
||||
transform hover:scale-105 flex items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsLiked(!isLiked);
|
||||
}}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||
fill="#EF4444"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
Interest
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@ -365,4 +417,4 @@ className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-2 bor
|
||||
)
|
||||
}
|
||||
|
||||
export default NewJoinedProfile
|
||||
export default NewJoinedProfile
|
||||
|
||||
235
src/components/profiledashboard/ProfileCardItem.jsx
Normal file
235
src/components/profiledashboard/ProfileCardItem.jsx
Normal file
@ -0,0 +1,235 @@
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Crown, Bookmark } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import SchoolIcon from "@mui/icons-material/School";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
|
||||
const buildDefaultRows = (profile) => [
|
||||
[
|
||||
{
|
||||
icon: <CakeIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: `Age: ${profile?.age ?? "-"}`,
|
||||
},
|
||||
{
|
||||
icon: <AccessibilityNewIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: `Height: ${profile?.height ?? "-"}`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <GroupsIcon className="w-4 h-4 text-gray-700" />,
|
||||
text:
|
||||
profile?.religion ||
|
||||
profile?.caste ||
|
||||
profile?.community ||
|
||||
"-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <SchoolIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.education || profile?.qualification || "-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <LocationOnIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.location || "-",
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const getProfileIdText = (profile) =>
|
||||
profile?.userId ||
|
||||
profile?.idNumber ||
|
||||
profile?.matrimonyId ||
|
||||
profile?.id ||
|
||||
"-";
|
||||
|
||||
const ProfileCardItem = ({
|
||||
profile,
|
||||
metaRows,
|
||||
showActions = true,
|
||||
showShortlist = true,
|
||||
shortlistLabel = "Shortlist",
|
||||
declineLabel = "Decline",
|
||||
interestLabel = "Interest",
|
||||
onDecline,
|
||||
onInterest,
|
||||
onShortlist,
|
||||
onCardClick,
|
||||
detailsPath,
|
||||
className = "",
|
||||
}) => {
|
||||
const [isLiked, setIsLiked] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const rows = metaRows?.length ? metaRows : buildDefaultRows(profile);
|
||||
|
||||
const handleCardClick = () => {
|
||||
if (onCardClick) {
|
||||
onCardClick(profile);
|
||||
return;
|
||||
}
|
||||
const target = detailsPath || `/profile-details/${profile?.id ?? ""}`;
|
||||
if (target) {
|
||||
navigate(target);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
onClick={handleCardClick}
|
||||
className={`w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200 ${className}`}
|
||||
>
|
||||
<div className="relative">
|
||||
{profile?.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring" }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{showShortlist && (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onShortlist?.(profile);
|
||||
}}
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">{shortlistLabel}</span>
|
||||
</motion.button>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
||||
style={{ height: "300px" }}
|
||||
>
|
||||
<img
|
||||
src={profile?.image}
|
||||
alt={profile?.name || "Profile"}
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
||||
}}
|
||||
></div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
||||
{profile?.name || "-"}
|
||||
</h1>
|
||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
||||
Matrimony ID: {getProfileIdText(profile)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
||||
style={{ background: "rgb(255, 255, 255)" }}
|
||||
>
|
||||
{rows.map((row, rowIndex) => (
|
||||
<div key={rowIndex} className="flex items-center gap-4">
|
||||
{row.map((item, itemIndex) => (
|
||||
<div key={itemIndex} className="flex items-center gap-2">
|
||||
{item.icon}
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{item.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{showActions && (
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDecline?.(profile);
|
||||
}}
|
||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M18 6L6 18M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{declineLabel}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
||||
transform hover:scale-105 flex items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsLiked((prev) => !prev);
|
||||
onInterest?.(profile);
|
||||
}}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||
fill="#EF4444"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{interestLabel}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileCardItem;
|
||||
@ -1,11 +1,17 @@
|
||||
import React from "react";
|
||||
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react";
|
||||
// Import your images
|
||||
import Profile1 from "../../assets/images/bride1.jpg";
|
||||
import Profile2 from "../../assets/images/bride2.jpg";
|
||||
import Profile3 from "../../assets/images/bride3.jpg";
|
||||
import Profile4 from "../../assets/images/bride4.jpg";
|
||||
import { motion } from "framer-motion";
|
||||
import ProfileCardItem from "../profiledashboard/ProfileCardItem";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
|
||||
|
||||
|
||||
export default function ProfileCard() {
|
||||
@ -69,96 +75,75 @@ export default function ProfileCard() {
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="h-auto py-8 px-4">
|
||||
const buildProfileRows = (profile) => [
|
||||
[
|
||||
{
|
||||
icon: <CakeIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.age || "-",
|
||||
},
|
||||
{
|
||||
icon: <AccessibilityNewIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.height || "-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <AccountBalanceWalletIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.salary ? `${profile.salary} LPA` : "-",
|
||||
},
|
||||
{
|
||||
icon: <LocationOnIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.location || "-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <GroupsIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.caste || "-",
|
||||
},
|
||||
{
|
||||
icon: <AutoAwesomeIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.zodiac1 || "-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <AutoAwesomeIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.zodiac2 || "-",
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
{/* HEADING */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||
Daily Recommended
|
||||
</h1>
|
||||
<p className="text-gray-900 text-[12px]">
|
||||
Find your perfect match today
|
||||
</p>
|
||||
</motion.div>
|
||||
return (
|
||||
<div className="h-auto py-8 px-4">
|
||||
{/* HEADING */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||
Daily Recommended
|
||||
</h1>
|
||||
<p className="text-gray-900 text-[12px]">
|
||||
Find your perfect match today
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* CARDS GRID */}
|
||||
<div className="flex justify-center">
|
||||
<div className="w-full max-w-[1400px] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{profiles.map((profile) => (
|
||||
<div
|
||||
key={profile.id}
|
||||
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md"
|
||||
>
|
||||
{/* IMAGE SECTION */}
|
||||
<div className="relative">
|
||||
<img
|
||||
src={profile.image}
|
||||
alt="profile"
|
||||
className="w-full h-[320px] object-cover"
|
||||
/>
|
||||
|
||||
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
||||
<Crown size={18} color="#fff" />
|
||||
</div>
|
||||
|
||||
<div className="absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg">
|
||||
<Bookmark size={14} /> Shortlist
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||
{profile.name}
|
||||
</h2>
|
||||
|
||||
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||
<p>ID: {profile.idNumber}</p>
|
||||
<p className="flex items-center gap-0.5">
|
||||
<Eye size={12} /> {profile.lastSeen}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
profile.age,
|
||||
profile.height,
|
||||
profile.salary + " LPA",
|
||||
profile.location,
|
||||
profile.caste,
|
||||
profile.zodiac1,
|
||||
profile.zodiac2,
|
||||
].map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
<button className="px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900">
|
||||
<X size={18} /> Decline
|
||||
</button>
|
||||
|
||||
<button className="px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold">
|
||||
<Heart size={18} /> Interest
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* CARDS GRID */}
|
||||
<div className="flex justify-center">
|
||||
<div className="w-full max-w-[1400px] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{profiles.map((profile) => (
|
||||
<ProfileCardItem
|
||||
key={profile.id}
|
||||
profile={profile}
|
||||
metaRows={buildProfileRows(profile)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,102 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateEducationalDetails } from "../redux/registrationFormSlice";
|
||||
import { TextField, Button, Grid } from "@mui/material";
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
Grid,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
} from "@mui/material";
|
||||
import { useEducationMasters, useEducationList } from "../hooks/useMasters";
|
||||
|
||||
const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const EducationalDetailsForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.educationalDetails);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: educationMasters, isLoading: isEducationMastersLoading } =
|
||||
useEducationMasters();
|
||||
const educationListQuery = useEducationList(data.fieldOfStudy);
|
||||
|
||||
const studyFieldOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.studyFields || raw.study_fields || raw.fieldOfStudy || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const qualificationOptions = useMemo(() => {
|
||||
const raw = educationListQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.education || raw.data || [];
|
||||
}, [educationListQuery.data]);
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const employeeTypeOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.employeeType || raw.employee_type || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const annualIncomeOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.annualIncome || raw.annual_income || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const workLocationOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return [];
|
||||
return raw.workLocation || raw.work_location || raw.workLocations || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const getOptionLabel = (item, fallback = "") => {
|
||||
if (!item) return fallback;
|
||||
if (typeof item === "string") return item;
|
||||
return (
|
||||
item.study_field_name ||
|
||||
item.education_name ||
|
||||
item.occupation_name ||
|
||||
item.employee_type_name ||
|
||||
item.annual_income_name ||
|
||||
item.work_location_name ||
|
||||
item.name ||
|
||||
fallback
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateEducationalDetails({ [field]: value }));
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
if (field === "fieldOfStudy") {
|
||||
updates.qualification = "";
|
||||
fieldsToClear.push("qualification");
|
||||
}
|
||||
dispatch(updateEducationalDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@ -26,58 +109,168 @@ const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Highest Qualification</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="qualification"
|
||||
label="Highest Qualification"
|
||||
value={data.qualification}
|
||||
onChange={(e) => handleChange("qualification", e.target.value)}
|
||||
error={Boolean(errors.qualification)}
|
||||
helperText={errors.qualification}
|
||||
placeholder="Enter Highest Qualification"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Field of Study */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Field of Study</label>
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Field of Study{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.fieldOfStudy)}
|
||||
>
|
||||
<InputLabel id="fieldOfStudy-label">
|
||||
Select Field of Study
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="fieldOfStudy-label"
|
||||
label="Select Field of Study"
|
||||
name="fieldOfStudy"
|
||||
value={data.fieldOfStudy}
|
||||
onChange={(e) => handleChange("fieldOfStudy", e.target.value)}
|
||||
inputRef={inputRef}
|
||||
disabled={isEducationMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{studyFieldOptions.map((field) => (
|
||||
<MenuItem key={field.id ?? field} value={field.id ?? field}>
|
||||
{getOptionLabel(field, "Field of Study")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.fieldOfStudy && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.fieldOfStudy}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Highest Educational Qualification{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.qualification)}
|
||||
>
|
||||
<InputLabel id="qualification-label">
|
||||
Select Highest Qualification
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="qualification-label"
|
||||
label="Select Highest Qualification"
|
||||
name="qualification"
|
||||
value={data.qualification}
|
||||
onChange={(e) => handleChange("qualification", e.target.value)}
|
||||
disabled={
|
||||
!data.fieldOfStudy || educationListQuery.isLoading
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{qualificationOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{getOptionLabel(item, "Qualification")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.qualification && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.qualification}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* College Name */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
College Name
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="fieldOfStudy"
|
||||
label="Field of Study"
|
||||
value={data.fieldOfStudy}
|
||||
onChange={(e) => handleChange("fieldOfStudy", e.target.value)}
|
||||
error={Boolean(errors.fieldOfStudy)}
|
||||
helperText={errors.fieldOfStudy}
|
||||
placeholder="Enter Field of Study"
|
||||
name="collegeName"
|
||||
label="College Name"
|
||||
value={data.collegeName}
|
||||
onChange={(e) => handleChange("collegeName", e.target.value)}
|
||||
error={Boolean(errors.collegeName)}
|
||||
helperText={errors.collegeName}
|
||||
placeholder="Enter College Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Occupation */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Occupation</label>
|
||||
<TextField
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
name="occupation"
|
||||
label="Occupation"
|
||||
value={data.occupation}
|
||||
onChange={(e) => handleChange("occupation", e.target.value)}
|
||||
error={Boolean(errors.occupation)}
|
||||
helperText={errors.occupation}
|
||||
placeholder="Enter Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
error={Boolean(errors.occupation)}
|
||||
>
|
||||
<InputLabel id="occupation-label">Select Occupation</InputLabel>
|
||||
<Select
|
||||
labelId="occupation-label"
|
||||
label="Select Occupation"
|
||||
name="occupation"
|
||||
value={data.occupation}
|
||||
onChange={(e) => handleChange("occupation", e.target.value)}
|
||||
disabled={isEducationMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{occupationOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{getOptionLabel(item, "Occupation")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.occupation && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.occupation}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Company / Organization Name */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Company / Organization Name</label>
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Company / Organization Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="organization"
|
||||
@ -91,36 +284,156 @@ const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Employee Type */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Employee Type{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.employeeType)}
|
||||
>
|
||||
<InputLabel id="employeeType-label">
|
||||
Select Employee Type
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="employeeType-label"
|
||||
label="Select Employee Type"
|
||||
name="employeeType"
|
||||
value={data.employeeType}
|
||||
onChange={(e) => handleChange("employeeType", e.target.value)}
|
||||
disabled={isEducationMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{employeeTypeOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{getOptionLabel(item, "Employee Type")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.employeeType && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.employeeType}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Annual Income */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Annual Income</label>
|
||||
<TextField
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Annual Income{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
name="income"
|
||||
label="Annual Income"
|
||||
value={data.income}
|
||||
onChange={(e) => handleChange("income", e.target.value)}
|
||||
error={Boolean(errors.income)}
|
||||
helperText={errors.income}
|
||||
placeholder="Enter Annual Income"
|
||||
variant="outlined"
|
||||
/>
|
||||
error={Boolean(errors.income)}
|
||||
>
|
||||
<InputLabel id="income-label">Select Annual Income</InputLabel>
|
||||
<Select
|
||||
labelId="income-label"
|
||||
label="Select Annual Income"
|
||||
name="income"
|
||||
value={data.income}
|
||||
onChange={(e) => handleChange("income", e.target.value)}
|
||||
disabled={isEducationMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{annualIncomeOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{getOptionLabel(item, "Annual Income")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.income && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.income}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Work Location */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Work Location</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="workLocation"
|
||||
label="Work Location"
|
||||
value={data.workLocation}
|
||||
onChange={(e) => handleChange("workLocation", e.target.value)}
|
||||
error={Boolean(errors.workLocation)}
|
||||
helperText={errors.workLocation}
|
||||
placeholder="Enter Work Location"
|
||||
variant="outlined"
|
||||
/>
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Work Location
|
||||
</label>
|
||||
{workLocationOptions.length > 0 ? (
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.workLocation)}
|
||||
>
|
||||
<InputLabel id="workLocation-label">
|
||||
Select Work Location
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="workLocation-label"
|
||||
label="Select Work Location"
|
||||
name="workLocation"
|
||||
value={data.workLocation}
|
||||
onChange={(e) =>
|
||||
handleChange("workLocation", e.target.value)
|
||||
}
|
||||
disabled={isEducationMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{workLocationOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{getOptionLabel(item, "Work Location")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.workLocation && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.workLocation}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
) : (
|
||||
<TextField
|
||||
fullWidth
|
||||
name="workLocation"
|
||||
label="Work Location"
|
||||
value={data.workLocation}
|
||||
onChange={(e) => handleChange("workLocation", e.target.value)}
|
||||
error={Boolean(errors.workLocation)}
|
||||
helperText={errors.workLocation}
|
||||
placeholder="Enter Work Location"
|
||||
variant="outlined"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateFamilyDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
@ -9,19 +9,91 @@ import {
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { useFamilyMasters } from "../hooks/useMasters";
|
||||
|
||||
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.familyDetails);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: familyMasters, isLoading: isFamilyMastersLoading } =
|
||||
useFamilyMasters();
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const maritalStatusOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.maritalStatus || raw.marital_status || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const familyStatusOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return [];
|
||||
return raw.familyStatus || raw.family_status || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const createSibling = () => ({
|
||||
name: "",
|
||||
occupation: "",
|
||||
maritalStatus: "",
|
||||
haveChildrens: "",
|
||||
});
|
||||
|
||||
const syncSiblingArray = (arr, count) => {
|
||||
const target = Math.max(0, Number(count) || 0);
|
||||
const next = [...(arr || [])];
|
||||
while (next.length < target) {
|
||||
next.push(createSibling());
|
||||
}
|
||||
if (next.length > target) {
|
||||
next.length = target;
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateFamilyDetails({ [field]: value }));
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
|
||||
if (field === "brotherCount") {
|
||||
const count = Number(value) || 0;
|
||||
updates.brotherCount = count;
|
||||
updates.brothers = syncSiblingArray(data.brothers, count);
|
||||
fieldsToClear.push("brothers");
|
||||
}
|
||||
|
||||
if (field === "sisterCount") {
|
||||
const count = Number(value) || 0;
|
||||
updates.sisterCount = count;
|
||||
updates.sisters = syncSiblingArray(data.sisters, count);
|
||||
fieldsToClear.push("sisters");
|
||||
}
|
||||
|
||||
dispatch(updateFamilyDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const handleSiblingChange = (type, index, field, value) => {
|
||||
const list = [...(data[type] || [])];
|
||||
if (!list[index]) list[index] = createSibling();
|
||||
list[index] = { ...list[index], [field]: value };
|
||||
dispatch(updateFamilyDetails({ [type]: list }));
|
||||
if (onFieldChange) onFieldChange(type);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@ -29,155 +101,329 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
return (
|
||||
const countOptions = Array.from({ length: 11 }, (_, i) => i);
|
||||
|
||||
<>
|
||||
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Father Name</label>
|
||||
<TextField
|
||||
const renderSiblingCard = (type, index) => {
|
||||
const sibling = (data[type] || [])[index] || createSibling();
|
||||
const labelPrefix = type === "brothers" ? "Brother" : "Sister";
|
||||
return (
|
||||
<Box
|
||||
key={`${type}-${index}`}
|
||||
sx={{
|
||||
border: "1px solid #e5e7eb",
|
||||
borderRadius: 2,
|
||||
padding: 2,
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
<div className="text-gray-900 text-[14px] font-semibold mb-3">
|
||||
{labelPrefix} {index + 1}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="fatherName"
|
||||
label="Father Name"
|
||||
value={data.fatherName}
|
||||
onChange={(e) => handleChange("fatherName", e.target.value)}
|
||||
error={Boolean(errors.fatherName)}
|
||||
helperText={errors.fatherName}
|
||||
placeholder="Enter Father Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Father Occupation</label>
|
||||
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
name="fatherOccupation"
|
||||
label="Father Occupation"
|
||||
value={data.fatherOccupation}
|
||||
onChange={(e) => handleChange("fatherOccupation", e.target.value)}
|
||||
error={Boolean(errors.fatherOccupation)}
|
||||
helperText={errors.fatherOccupation}
|
||||
placeholder="Enter Father Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Mother Name</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherName"
|
||||
label="Mother Name"
|
||||
value={data.motherName}
|
||||
onChange={(e) => handleChange("motherName", e.target.value)}
|
||||
error={Boolean(errors.motherName)}
|
||||
helperText={errors.motherName}
|
||||
placeholder="Enter Mother Name"
|
||||
label="Name"
|
||||
value={sibling.name}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "name", e.target.value)
|
||||
}
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Mother Occupation</label>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherOccupation"
|
||||
label="Mother Occupation"
|
||||
value={data.motherOccupation}
|
||||
onChange={(e) => handleChange("motherOccupation", e.target.value)}
|
||||
error={Boolean(errors.motherOccupation)}
|
||||
helperText={errors.motherOccupation}
|
||||
placeholder="Enter Mother Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Number of Brothers / Sisters</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="siblings"
|
||||
label="Number of Brothers / Sisters"
|
||||
value={data.siblings}
|
||||
onChange={(e) => handleChange("siblings", e.target.value)}
|
||||
error={Boolean(errors.siblings)}
|
||||
helperText={errors.siblings}
|
||||
placeholder="Enter Number of Brothers / Sisters"
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]"> Brothers / Sisters (Married / Unmarried)</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.siblingsStatus)}
|
||||
>
|
||||
<InputLabel id="siblingsStatus-label">
|
||||
Brothers / Sisters (Married / Unmarried)
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-occupation-label`}>
|
||||
Occupation
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="siblingsStatus-label"
|
||||
label="Brothers / Sisters (Married / Unmarried)"
|
||||
name="siblingsStatus"
|
||||
value={data.siblingsStatus}
|
||||
onChange={(e) => handleChange("siblingsStatus", e.target.value)}
|
||||
labelId={`${type}-${index}-occupation-label`}
|
||||
label="Occupation"
|
||||
value={sibling.occupation}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "occupation", e.target.value)
|
||||
}
|
||||
disabled={isFamilyMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem value="All Married">All Married</MenuItem>
|
||||
<MenuItem value="All Unmarried">All Unmarried</MenuItem>
|
||||
<MenuItem value="Mixed">Mixed</MenuItem>
|
||||
{occupationOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.siblingsStatus && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.siblingsStatus}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
</div>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-marital-label`}>
|
||||
Marital Status
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`${type}-${index}-marital-label`}
|
||||
label="Marital Status"
|
||||
value={sibling.maritalStatus}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "maritalStatus", e.target.value)
|
||||
}
|
||||
disabled={isFamilyMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{maritalStatusOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-children-label`}>
|
||||
Have Children
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`${type}-${index}-children-label`}
|
||||
label="Have Children"
|
||||
value={sibling.haveChildrens}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(
|
||||
type,
|
||||
index,
|
||||
"haveChildrens",
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItem value={1}>Yes</MenuItem>
|
||||
<MenuItem value={0}>No</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Father Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="fatherName"
|
||||
label="Father Name"
|
||||
value={data.fatherName}
|
||||
onChange={(e) => handleChange("fatherName", e.target.value)}
|
||||
error={Boolean(errors.fatherName)}
|
||||
helperText={errors.fatherName}
|
||||
placeholder="Enter Father Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Father Occupation
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="fatherOccupation"
|
||||
label="Father Occupation"
|
||||
value={data.fatherOccupation}
|
||||
onChange={(e) => handleChange("fatherOccupation", e.target.value)}
|
||||
error={Boolean(errors.fatherOccupation)}
|
||||
helperText={errors.fatherOccupation}
|
||||
placeholder="Enter Father Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Mother Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherName"
|
||||
label="Mother Name"
|
||||
value={data.motherName}
|
||||
onChange={(e) => handleChange("motherName", e.target.value)}
|
||||
error={Boolean(errors.motherName)}
|
||||
helperText={errors.motherName}
|
||||
placeholder="Enter Mother Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Mother Occupation
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherOccupation"
|
||||
label="Mother Occupation"
|
||||
value={data.motherOccupation}
|
||||
onChange={(e) => handleChange("motherOccupation", e.target.value)}
|
||||
error={Boolean(errors.motherOccupation)}
|
||||
helperText={errors.motherOccupation}
|
||||
placeholder="Enter Mother Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Brother Count
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="brotherCount-label">Select Brother Count</InputLabel>
|
||||
<Select
|
||||
labelId="brotherCount-label"
|
||||
label="Select Brother Count"
|
||||
name="brotherCount"
|
||||
value={data.brotherCount}
|
||||
onChange={(e) => handleChange("brotherCount", e.target.value)}
|
||||
>
|
||||
{countOptions.map((count) => (
|
||||
<MenuItem key={count} value={count}>
|
||||
{count}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Sister Count
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="sisterCount-label">Select Sister Count</InputLabel>
|
||||
<Select
|
||||
labelId="sisterCount-label"
|
||||
label="Select Sister Count"
|
||||
name="sisterCount"
|
||||
value={data.sisterCount}
|
||||
onChange={(e) => handleChange("sisterCount", e.target.value)}
|
||||
>
|
||||
{countOptions.map((count) => (
|
||||
<MenuItem key={count} value={count}>
|
||||
{count}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Family Status{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.familyStatus)}
|
||||
>
|
||||
<InputLabel id="familyStatus-label">Select Family Status</InputLabel>
|
||||
<Select
|
||||
labelId="familyStatus-label"
|
||||
label="Select Family Status"
|
||||
name="familyStatus"
|
||||
value={data.familyStatus}
|
||||
onChange={(e) => handleChange("familyStatus", e.target.value)}
|
||||
disabled={isFamilyMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{familyStatusOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{item.family_type_name || item.name || item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.familyStatus && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.familyStatus}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Native Place</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="nativePlace"
|
||||
label="Native Place"
|
||||
value={data.nativePlace}
|
||||
onChange={(e) => handleChange("nativePlace", e.target.value)}
|
||||
error={Boolean(errors.nativePlace)}
|
||||
helperText={errors.nativePlace}
|
||||
placeholder="Enter Native Place"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{Number(data.brotherCount) > 0 && (
|
||||
<div className="mt-6">
|
||||
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
||||
Brother Details
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{Array.from({ length: Number(data.brotherCount) }).map(
|
||||
(_, index) => renderSiblingCard("brothers", index)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Grid item
|
||||
xs={12} sx={{ marginTop: 10, display: "flex", gap: 4 , justifyContent: "center"}}>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
{Number(data.sisterCount) > 0 && (
|
||||
<div className="mt-6">
|
||||
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
||||
Sister Details
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{Array.from({ length: Number(data.sisterCount) }).map(
|
||||
(_, index) => renderSiblingCard("sisters", index)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sx={{ marginTop: 10, display: "flex", gap: 4, justifyContent: "center" }}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateLifestyleDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
@ -8,12 +8,50 @@ import {
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
TextField,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { useLifestyleMasters } from "../hooks/useMasters";
|
||||
|
||||
const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const LifestyleDetailsForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: lifestyleMasters, isLoading: isLifestyleMastersLoading } =
|
||||
useLifestyleMasters();
|
||||
|
||||
const dietOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.diet || raw.diets || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const hobbyOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.hobbies || raw.hobby || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const grahaOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.grahas || raw.graha || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
@ -21,6 +59,27 @@ const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
};
|
||||
|
||||
const handleMultiChange = (field, value) => {
|
||||
const nextValue = Array.isArray(value) ? value : String(value).split(",");
|
||||
dispatch(updateLifestyleDetails({ [field]: nextValue }));
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
};
|
||||
|
||||
const handleGrahaChange = (house, value) => {
|
||||
const next = { ...(data.graha || {}) };
|
||||
next[house] = value;
|
||||
dispatch(updateLifestyleDetails({ graha: next }));
|
||||
if (onFieldChange) onFieldChange("graha");
|
||||
};
|
||||
|
||||
const handleAmsamChange = (house, value) => {
|
||||
const next = { ...(data.amsam || {}) };
|
||||
next[house] = value;
|
||||
dispatch(updateLifestyleDetails({ amsam: next }));
|
||||
if (onFieldChange) onFieldChange("amsam");
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@ -28,67 +87,302 @@ const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
const fields = [
|
||||
{ name: "diet", label: "Diet", options: ["Veg", "Non-Veg", "Eggetarian"] },
|
||||
// {
|
||||
// name: "drinking",
|
||||
// label: "Drinking Habits",
|
||||
// options: ["No", "Occasionally", "Regularly"],
|
||||
// },
|
||||
// {
|
||||
// name: "smoking",
|
||||
// label: "Smoking Habits",
|
||||
// options: ["No", "Occasionally", "Regularly"],
|
||||
// },
|
||||
{
|
||||
name: "hobbies",
|
||||
label: "Hobbies & Interests",
|
||||
options: ["Song", "Reading", "Sports", "Travel"],
|
||||
},
|
||||
];
|
||||
const parseDateValue = (value) => {
|
||||
if (!value) return null;
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
};
|
||||
|
||||
const parseTimeValue = (value) => {
|
||||
if (!value || typeof value !== "string") return null;
|
||||
const [hours, minutes] = value.split(":").map(Number);
|
||||
if (Number.isNaN(hours) || Number.isNaN(minutes)) return null;
|
||||
const date = new Date();
|
||||
date.setHours(hours, minutes, 0, 0);
|
||||
return date;
|
||||
};
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!(value instanceof Date) || Number.isNaN(value)) return "";
|
||||
const y = value.getFullYear();
|
||||
const m = String(value.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(value.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
};
|
||||
|
||||
const formatTime = (value) => {
|
||||
if (!(value instanceof Date) || Number.isNaN(value)) return "";
|
||||
const h = String(value.getHours()).padStart(2, "0");
|
||||
const m = String(value.getMinutes()).padStart(2, "0");
|
||||
return `${h}:${m}`;
|
||||
};
|
||||
|
||||
const renderChartCell = (label, value, onChange) => (
|
||||
<div className="bg-white border border-gray-300 rounded-lg p-2 flex flex-col items-center justify-center min-h-[70px]">
|
||||
<span className="text-[10px] font-medium text-gray-700 text-center">
|
||||
{label}
|
||||
</span>
|
||||
<FormControl fullWidth variant="standard" sx={{ mt: 0.5 }}>
|
||||
<Select
|
||||
multiple
|
||||
displayEmpty
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
renderValue={(selected) => {
|
||||
if (!selected || selected.length === 0) return `+ Add ${label}`;
|
||||
return selected.join(", ");
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: { style: { maxHeight: 280 } },
|
||||
}}
|
||||
>
|
||||
{grahaOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
<Checkbox checked={value.indexOf(opt) > -1} />
|
||||
<ListItemText primary={opt} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderChartGrid = (type) => {
|
||||
const getValue = (house) =>
|
||||
(type === "graha" ? data.graha : data.amsam)?.[house] || [];
|
||||
const onChange = (house, value) =>
|
||||
type === "graha"
|
||||
? handleGrahaChange(house, value)
|
||||
: handleAmsamChange(house, value);
|
||||
const label = type === "graha" ? "Rasi" : "Navamsam";
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{renderChartCell(label, getValue(1), (value) => onChange(1, value))}
|
||||
{renderChartCell(label, getValue(2), (value) => onChange(2, value))}
|
||||
{renderChartCell(label, getValue(3), (value) => onChange(3, value))}
|
||||
{renderChartCell(label, getValue(4), (value) => onChange(4, value))}
|
||||
|
||||
{renderChartCell(label, getValue(5), (value) => onChange(5, value))}
|
||||
<div className="col-span-2 row-span-2 flex items-center justify-center">
|
||||
<div className="w-full aspect-square rounded-full overflow-hidden border-4 border-gray-200 shadow-lg bg-white" />
|
||||
</div>
|
||||
{renderChartCell(label, getValue(6), (value) => onChange(6, value))}
|
||||
|
||||
{renderChartCell(label, getValue(7), (value) => onChange(7, value))}
|
||||
{renderChartCell(label, getValue(8), (value) => onChange(8, value))}
|
||||
|
||||
{renderChartCell(label, getValue(9), (value) => onChange(9, value))}
|
||||
{renderChartCell(label, getValue(10), (value) => onChange(10, value))}
|
||||
{renderChartCell(label, getValue(11), (value) => onChange(11, value))}
|
||||
{renderChartCell(label, getValue(12), (value) => onChange(12, value))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{fields.map(({ name, label, options }) => (
|
||||
<div key={name} className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">{label}</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors[name])}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Diet (Multi-select){requiredMark}
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
||||
<InputLabel id="diets-label">Select Diet</InputLabel>
|
||||
<Select
|
||||
labelId="diets-label"
|
||||
label="Select Diet"
|
||||
name="diets"
|
||||
multiple
|
||||
value={data.diets}
|
||||
onChange={(e) => handleMultiChange("diets", e.target.value)}
|
||||
inputRef={inputRef}
|
||||
disabled={isLifestyleMastersLoading}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = dietOptions.find(
|
||||
(opt) => (opt.id ?? opt) === id
|
||||
);
|
||||
return item?.diet_name || item?.name || id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<InputLabel id={`${name}-label`}>Select {label}</InputLabel>
|
||||
<Select
|
||||
labelId={`${name}-label`}
|
||||
label={`Select ${label}`}
|
||||
name={name}
|
||||
value={data[name]}
|
||||
onChange={(e) => handleChange(name, e.target.value)}
|
||||
inputRef={name === "diet" ? inputRef : null}
|
||||
>
|
||||
|
||||
{options.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
{dietOptions.map((opt) => {
|
||||
const value = opt.id ?? opt;
|
||||
const label = opt.diet_name || opt.name || String(opt);
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
<Checkbox checked={data.diets.indexOf(value) > -1} />
|
||||
<ListItemText primary={label} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors[name] && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors[name]}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors.diets && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.diets}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Hobbies & Interests (Multi-select){requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.hobbies)}
|
||||
>
|
||||
<InputLabel id="hobbies-label">
|
||||
Select Hobbies & Interests
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="hobbies-label"
|
||||
label="Select Hobbies & Interests"
|
||||
name="hobbies"
|
||||
multiple
|
||||
value={data.hobbies}
|
||||
onChange={(e) => handleMultiChange("hobbies", e.target.value)}
|
||||
disabled={isLifestyleMastersLoading}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = hobbyOptions.find(
|
||||
(opt) => (opt.id ?? opt) === id
|
||||
);
|
||||
return item?.hobby_name || item?.name || id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{hobbyOptions.map((opt) => {
|
||||
const value = opt.id ?? opt;
|
||||
const label = opt.hobby_name || opt.name || String(opt);
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
<Checkbox checked={data.hobbies.indexOf(value) > -1} />
|
||||
<ListItemText primary={label} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors.hobbies && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.hobbies}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center py-4">
|
||||
<h2 className="text-[18px] font-semibold text-gray-800">
|
||||
Astrology / Horoscope
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[900px] mx-auto">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Date of Birth{requiredMark}
|
||||
</label>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<DatePicker
|
||||
format="dd/MM/yyyy"
|
||||
value={parseDateValue(data.dob)}
|
||||
onChange={(value) => handleChange("dob", formatDate(value))}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "dob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.dob),
|
||||
helperText: errors.dob,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Time of Birth{requiredMark}
|
||||
</label>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<TimePicker
|
||||
value={parseTimeValue(data.tob)}
|
||||
onChange={(value) => handleChange("tob", formatTime(value))}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "tob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.tob),
|
||||
helperText: errors.tob,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Place of Birth</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="placeOfBirth"
|
||||
value={data.placeOfBirth}
|
||||
onChange={(e) => handleChange("placeOfBirth", e.target.value)}
|
||||
error={Boolean(errors.placeOfBirth)}
|
||||
helperText={errors.placeOfBirth}
|
||||
placeholder="Enter Place of Birth"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[950px] mx-auto mt-6">
|
||||
<div>
|
||||
<div className="py-4">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
||||
Add Rasi
|
||||
</h3>
|
||||
{renderChartGrid("graha")}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="py-4">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
||||
Add Navamsam
|
||||
</h3>
|
||||
{renderChartGrid("amsam")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Grid
|
||||
|
||||
@ -1,27 +1,137 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updatePartnerPreferences } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||
import { useCityMasters, useSubCasteMasters } from "../hooks/useDependentMasters";
|
||||
|
||||
const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const PartnerPreferencesForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.partnerPreferences);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: masters, isLoading: isPartnerMastersLoading } =
|
||||
usePartnerPreferenceMasters();
|
||||
const subCasteQuery = useSubCasteMasters(data.castes);
|
||||
const cityQuery = useCityMasters(data.states);
|
||||
|
||||
const ageRangeOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.ageRange || raw.age_range || [];
|
||||
}, [masters]);
|
||||
|
||||
const casteOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.caste || raw.castes || [];
|
||||
}, [masters]);
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
}, [masters]);
|
||||
|
||||
const educationOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.education || raw.educations || [];
|
||||
}, [masters]);
|
||||
|
||||
const hobbyOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.hobbies || raw.hobby || [];
|
||||
}, [masters]);
|
||||
|
||||
const annualIncomeOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.annual_income || raw.annualIncome || [];
|
||||
}, [masters]);
|
||||
|
||||
const stateOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.state || raw.states || [];
|
||||
}, [masters]);
|
||||
|
||||
const subCasteOptions = useMemo(() => {
|
||||
const raw = subCasteQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) {
|
||||
const merged = raw.flatMap((entry) => {
|
||||
if (!entry) return [];
|
||||
if (Array.isArray(entry)) return entry;
|
||||
return entry.sub_caste || entry.subCaste || entry.data || [];
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
return raw.sub_caste || raw.subCaste || raw.data || [];
|
||||
}, [subCasteQuery.data]);
|
||||
|
||||
const cityOptions = useMemo(() => {
|
||||
const raw = cityQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.subCaste || raw.district || raw.data || [];
|
||||
}, [cityQuery.data]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updatePartnerPreferences({ [field]: value }));
|
||||
const arrayFields = new Set([
|
||||
"castes",
|
||||
"subCastes",
|
||||
"occupations",
|
||||
"educations",
|
||||
"hobbies",
|
||||
"states",
|
||||
"districts",
|
||||
]);
|
||||
const nextValue =
|
||||
arrayFields.has(field) && typeof value === "string"
|
||||
? value.split(",").filter(Boolean)
|
||||
: value;
|
||||
const updates = { [field]: nextValue };
|
||||
const fieldsToClear = [field];
|
||||
|
||||
if (field === "castes") {
|
||||
updates.subCastes = [];
|
||||
fieldsToClear.push("subCastes");
|
||||
}
|
||||
if (field === "states") {
|
||||
updates.districts = [];
|
||||
fieldsToClear.push("districts");
|
||||
}
|
||||
|
||||
dispatch(updatePartnerPreferences(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@ -29,36 +139,98 @@ const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
const renderMultiSelect = ({
|
||||
name,
|
||||
label,
|
||||
options,
|
||||
value,
|
||||
getLabel,
|
||||
getValue,
|
||||
disabled,
|
||||
}) => (
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors[name])}>
|
||||
<InputLabel id={`${name}-label`}>Select {label}</InputLabel>
|
||||
<Select
|
||||
labelId={`${name}-label`}
|
||||
label={`Select ${label}`}
|
||||
name={name}
|
||||
multiple
|
||||
value={value}
|
||||
onChange={(e) => handleChange(name, e.target.value)}
|
||||
disabled={disabled}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = options.find((opt) => getValue(opt) === id);
|
||||
return item ? getLabel(item) : id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{options.map((opt) => {
|
||||
const optValue = getValue(opt);
|
||||
const optLabel = getLabel(opt);
|
||||
return (
|
||||
<MenuItem key={optValue} value={optValue}>
|
||||
<Checkbox checked={value.indexOf(optValue) > -1} />
|
||||
<ListItemText primary={optLabel} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors[name] && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors[name]}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{/* Preferred Age Range */}
|
||||
{/* Age Range */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Preferred Age Range
|
||||
Age Range{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.ageRange)}
|
||||
>
|
||||
<InputLabel id="ageRange-label">
|
||||
Select Preferred Age Range
|
||||
</InputLabel>
|
||||
<InputLabel id="ageRange-label">Select Age Range</InputLabel>
|
||||
<Select
|
||||
labelId="ageRange-label"
|
||||
label="Select Preferred Age Range"
|
||||
label="Select Age Range"
|
||||
name="ageRange"
|
||||
value={data.ageRange}
|
||||
onChange={(e) => handleChange("ageRange", e.target.value)}
|
||||
|
||||
inputRef={inputRef}
|
||||
disabled={isPartnerMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem value="25 - 28">25 - 28</MenuItem>
|
||||
<MenuItem value="29 - 32">29 - 32</MenuItem>
|
||||
<MenuItem value="33 - 36">33 - 36</MenuItem>
|
||||
{ageRangeOptions.map((opt) => (
|
||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
||||
{opt.name || opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.ageRange && (
|
||||
<p
|
||||
@ -74,36 +246,120 @@ const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Religion / Caste Preference */}
|
||||
{/* Caste */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Religion / Caste Preference
|
||||
Caste{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "castes",
|
||||
label: "Caste",
|
||||
options: casteOptions,
|
||||
value: data.castes,
|
||||
getLabel: (opt) => opt.caste_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Sub Caste */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Sub Caste{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "subCastes",
|
||||
label: "Sub Caste",
|
||||
options: subCasteOptions,
|
||||
value: data.subCastes,
|
||||
getLabel: (opt) =>
|
||||
opt.sub_caste_name || opt.subCaste_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: data.castes.length === 0 || subCasteQuery.isLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Occupation */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "occupations",
|
||||
label: "Occupation",
|
||||
options: occupationOptions,
|
||||
value: data.occupations,
|
||||
getLabel: (opt) => opt.occupation_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Qualification{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "educations",
|
||||
label: "Qualification",
|
||||
options: educationOptions,
|
||||
value: data.educations,
|
||||
getLabel: (opt) => opt.education_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Lifestyle and Hobbies */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Lifestyle & Hobbies{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "hobbies",
|
||||
label: "Lifestyle & Hobbies",
|
||||
options: hobbyOptions,
|
||||
value: data.hobbies,
|
||||
getLabel: (opt) => opt.hobby_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Annual Income */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Annual Income{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.religionCaste)}
|
||||
error={Boolean(errors.annualIncome)}
|
||||
>
|
||||
<InputLabel id="religionCaste-label">
|
||||
Select Religion / Caste Preference
|
||||
<InputLabel id="annualIncome-label">
|
||||
Select Annual Income
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="religionCaste-label"
|
||||
label="Select Religion / Caste Preference"
|
||||
name="religionCaste"
|
||||
value={data.religionCaste}
|
||||
onChange={(e) =>
|
||||
handleChange("religionCaste", e.target.value)
|
||||
}
|
||||
|
||||
labelId="annualIncome-label"
|
||||
label="Select Annual Income"
|
||||
name="annualIncome"
|
||||
value={data.annualIncome}
|
||||
onChange={(e) => handleChange("annualIncome", e.target.value)}
|
||||
disabled={isPartnerMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem value="Hindu">Hindu</MenuItem>
|
||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
||||
<MenuItem value="Christian">Christian</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
{annualIncomeOptions.map((opt) => (
|
||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
||||
{opt.annual_income_name || opt.name || opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.religionCaste && (
|
||||
{errors.annualIncome && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
@ -111,167 +367,43 @@ const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.religionCaste}
|
||||
{errors.annualIncome}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Occupation Preference */}
|
||||
{/* State */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation Preference
|
||||
State{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.occupationPref)}
|
||||
>
|
||||
<InputLabel id="occupationPref-label">
|
||||
Select Occupation Preference
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="occupationPref-label"
|
||||
label="Select Occupation Preference"
|
||||
name="occupationPref"
|
||||
value={data.occupationPref}
|
||||
onChange={(e) =>
|
||||
handleChange("occupationPref", e.target.value)
|
||||
}
|
||||
|
||||
>
|
||||
|
||||
<MenuItem value="IT, Business">IT, Business</MenuItem>
|
||||
<MenuItem value="Doctor">Doctor</MenuItem>
|
||||
<MenuItem value="Engineer">Engineer</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
</Select>
|
||||
{errors.occupationPref && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.occupationPref}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
{renderMultiSelect({
|
||||
name: "states",
|
||||
label: "State",
|
||||
options: stateOptions,
|
||||
value: data.states,
|
||||
getLabel: (opt) => opt.state_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Lifestyle & Habits Preference */}
|
||||
{/* City */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Lifestyle & Habits Preference
|
||||
City{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.lifestylePref)}
|
||||
>
|
||||
<InputLabel id="lifestylePref-label">
|
||||
Select Lifestyle & Habits Preference
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="lifestylePref-label"
|
||||
label="Select Lifestyle & Habits Preference"
|
||||
name="lifestylePref"
|
||||
value={data.lifestylePref}
|
||||
onChange={(e) =>
|
||||
handleChange("lifestylePref", e.target.value)
|
||||
}
|
||||
|
||||
>
|
||||
|
||||
<MenuItem value="Non-Smoking, Non-Drinking">
|
||||
Non-Smoking, Non-Drinking
|
||||
</MenuItem>
|
||||
<MenuItem value="Social Drinker">Social Drinker</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
</Select>
|
||||
{errors.lifestylePref && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.lifestylePref}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Prefer Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Prefer Qualification
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="qualificationPref"
|
||||
label="Enter prefer Qualification"
|
||||
value={data.qualificationPref}
|
||||
onChange={(e) =>
|
||||
handleChange("qualificationPref", e.target.value)
|
||||
}
|
||||
error={Boolean(errors.qualificationPref)}
|
||||
helperText={errors.qualificationPref}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Occupation (text) */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Occupation</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="occupationText"
|
||||
label="Enter Occupation"
|
||||
value={data.occupationText}
|
||||
onChange={(e) =>
|
||||
handleChange("occupationText", e.target.value)
|
||||
}
|
||||
error={Boolean(errors.occupationText)}
|
||||
helperText={errors.occupationText}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Prefer Annual Income */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Prefer Annual Income
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="incomePref"
|
||||
label="Enter Annual Income"
|
||||
value={data.incomePref}
|
||||
onChange={(e) => handleChange("incomePref", e.target.value)}
|
||||
error={Boolean(errors.incomePref)}
|
||||
helperText={errors.incomePref}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Prefer Location */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Prefer Location
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="locationPref"
|
||||
label="Enter Prefer Location"
|
||||
value={data.locationPref}
|
||||
onChange={(e) => handleChange("locationPref", e.target.value)}
|
||||
error={Boolean(errors.locationPref)}
|
||||
helperText={errors.locationPref}
|
||||
variant="outlined"
|
||||
/>
|
||||
{renderMultiSelect({
|
||||
name: "districts",
|
||||
label: "City",
|
||||
options: cityOptions,
|
||||
value: data.districts,
|
||||
getLabel: (opt) =>
|
||||
opt.district_name || opt.city_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: data.states.length === 0 || cityQuery.isLoading,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ import { useSendOtp, useVerifyOtp } from "../hooks/useAuth";
|
||||
const OTP_LENGTH = 4;
|
||||
const OTP_TIMER_SEC = 120; // 2 minutes
|
||||
|
||||
const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.personalDetails);
|
||||
const nameInputRef = useRef(null);
|
||||
@ -141,6 +141,52 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
setOtpTimer(OTP_TIMER_SEC);
|
||||
}, []);
|
||||
|
||||
const getApiErrorMessage = useCallback((error, fallback) => {
|
||||
const data = error?.response?.data ?? error?.data ?? error;
|
||||
if (!data) return fallback;
|
||||
if (typeof data === "string") return data;
|
||||
|
||||
const directMessage =
|
||||
data.message || data.error || data.detail || data.msg;
|
||||
if (directMessage) return directMessage;
|
||||
|
||||
if (Array.isArray(data.errors)) {
|
||||
const first = data.errors[0];
|
||||
if (typeof first === "string") return first;
|
||||
if (first && typeof first === "object") {
|
||||
return (
|
||||
first.message ||
|
||||
first.msg ||
|
||||
first.error ||
|
||||
first.detail ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.errors && typeof data.errors === "object") {
|
||||
const firstValue = Object.values(data.errors)[0];
|
||||
if (Array.isArray(firstValue)) {
|
||||
const joined = firstValue.filter(Boolean).join(" ");
|
||||
if (joined) return joined;
|
||||
}
|
||||
if (typeof firstValue === "string") return firstValue;
|
||||
if (firstValue && typeof firstValue === "object") {
|
||||
return (
|
||||
firstValue.message ||
|
||||
firstValue.msg ||
|
||||
firstValue.error ||
|
||||
firstValue.detail ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.otp) return String(data.otp);
|
||||
if (error?.message) return error.message;
|
||||
return fallback;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (otpTimer <= 0) return;
|
||||
const timerId = setInterval(() => {
|
||||
@ -207,18 +253,18 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
resetOtp();
|
||||
setMobileOtpVerified(false);
|
||||
} catch (error) {
|
||||
setMobileNumberError("Failed to send OTP. Please try again.");
|
||||
const message =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.otp ||
|
||||
error?.message ||
|
||||
"Failed to send OTP";
|
||||
const message = getApiErrorMessage(
|
||||
error,
|
||||
"Failed to send OTP. Please try again."
|
||||
);
|
||||
setMobileNumberError(message);
|
||||
toast.error(message, { position: "top-right" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
if (field === "mobileNumber") {
|
||||
setMobileNumberError("");
|
||||
setShowOtp(false);
|
||||
@ -230,21 +276,61 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
if (field === "religion") {
|
||||
updates.caste = "";
|
||||
updates.subCaste = "";
|
||||
fieldsToClear.push("caste", "subCaste");
|
||||
}
|
||||
if (field === "caste") {
|
||||
updates.subCaste = "";
|
||||
fieldsToClear.push("subCaste");
|
||||
}
|
||||
if (field === "state") {
|
||||
updates.city = "";
|
||||
fieldsToClear.push("city");
|
||||
}
|
||||
if (field === "raasi") {
|
||||
updates.star = "";
|
||||
fieldsToClear.push("star");
|
||||
}
|
||||
if (field === "password") {
|
||||
fieldsToClear.push("confirmPassword");
|
||||
}
|
||||
dispatch(updatePersonalDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const isOtpComplete = otp.every((digit) => digit !== "");
|
||||
|
||||
const passwordStrength = useMemo(() => {
|
||||
const value = data.password || "";
|
||||
if (!value) return null;
|
||||
const rules = [
|
||||
{ key: "length", label: "At least 8 characters", ok: value.length >= 8 },
|
||||
{ key: "upper", label: "Uppercase letter", ok: /[A-Z]/.test(value) },
|
||||
{ key: "lower", label: "Lowercase letter", ok: /[a-z]/.test(value) },
|
||||
{ key: "number", label: "Number", ok: /\d/.test(value) },
|
||||
{ key: "symbol", label: "Symbol", ok: /[^A-Za-z0-9]/.test(value) },
|
||||
{ key: "length12", label: "12+ characters (recommended)", ok: value.length >= 12 },
|
||||
];
|
||||
const score = rules.filter((rule) => rule.ok).length;
|
||||
const percent = Math.round((score / rules.length) * 100);
|
||||
|
||||
let label = "Weak";
|
||||
let color = "#d32f2f";
|
||||
if (score >= 5) {
|
||||
label = "Strong";
|
||||
color = "#2e7d32";
|
||||
} else if (score >= 3) {
|
||||
label = "Medium";
|
||||
color = "#ed6c02";
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
color,
|
||||
percent,
|
||||
rules,
|
||||
};
|
||||
}, [data.password]);
|
||||
|
||||
const handleOtpSubmit = async () => {
|
||||
if (!isOtpComplete) {
|
||||
setOtpError("Complete OTP is required");
|
||||
@ -265,11 +351,10 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
setMobileNumberError("");
|
||||
setOtpError("");
|
||||
} catch (error) {
|
||||
const message =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.otp ||
|
||||
error?.message ||
|
||||
"Invalid or expired OTP";
|
||||
const message = getApiErrorMessage(
|
||||
error,
|
||||
"Invalid or expired OTP"
|
||||
);
|
||||
setOtpError(message);
|
||||
toast.error(message, { position: "top-right" });
|
||||
}
|
||||
@ -576,6 +661,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "dob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.dob),
|
||||
helperText: errors.dob,
|
||||
@ -954,6 +1040,64 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
{passwordStrength && (
|
||||
<div className="rounded-lg border border-gray-200 bg-white/70 p-3">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-600">Password strength</span>
|
||||
<span
|
||||
style={{
|
||||
color: passwordStrength.color,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{passwordStrength.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 h-2 w-full rounded-full bg-gray-200 overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
width: `${passwordStrength.percent}%`,
|
||||
height: "100%",
|
||||
background: passwordStrength.color,
|
||||
transition: "width 200ms ease",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
Recommended: 12+ characters with a mix of upper/lowercase,
|
||||
numbers, and symbols.
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 gap-y-1 gap-x-3 text-xs">
|
||||
{passwordStrength.rules.map((rule) => (
|
||||
<div
|
||||
key={rule.key}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{rule.ok ? (
|
||||
<CheckCircleIcon sx={{ fontSize: 14, color: "#2e7d32" }} />
|
||||
) : (
|
||||
<span
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "999px",
|
||||
border: "1px solid #d1d5db",
|
||||
display: "inline-block",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
color: rule.ok ? "#111827" : "#6b7280",
|
||||
}}
|
||||
>
|
||||
{rule.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Confirm Password */}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Edit2,Info } from 'lucide-react';
|
||||
import {
|
||||
@ -12,44 +12,154 @@ import {
|
||||
Button,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
import { usePreviewDetails } from "../hooks/usePreview";
|
||||
|
||||
const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
const formData = useSelector((state) => state.registerform);
|
||||
const { data: previewData, isLoading, isError } = usePreviewDetails();
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: 'Personal Details',
|
||||
step: 1,
|
||||
data: formData.personalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Educational & Professional Details',
|
||||
step: 2,
|
||||
data: formData.educationalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Family Details',
|
||||
step: 3,
|
||||
data: formData.familyDetails,
|
||||
},
|
||||
{
|
||||
title: 'Lifestyle & Habits',
|
||||
step: 4,
|
||||
data: formData.lifestyleDetails,
|
||||
},
|
||||
{
|
||||
title: 'Partner Preferences',
|
||||
step: 5,
|
||||
data: formData.partnerPreferences,
|
||||
},
|
||||
];
|
||||
const sections = previewData?.personal_details
|
||||
? [
|
||||
{
|
||||
title: "Personal Details",
|
||||
step: 1,
|
||||
data: previewData.personal_details,
|
||||
},
|
||||
{
|
||||
title: "Educational & Professional Details",
|
||||
step: 2,
|
||||
data: previewData.educational_details,
|
||||
},
|
||||
{
|
||||
title: "Family Details",
|
||||
step: 3,
|
||||
data: previewData.family_details,
|
||||
},
|
||||
{
|
||||
title: "Lifestyle & Habits",
|
||||
step: 4,
|
||||
data: previewData.lifestyle_details,
|
||||
},
|
||||
{
|
||||
title: "Partner Preferences",
|
||||
step: 5,
|
||||
data: previewData.partner_preferences,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'Personal Details',
|
||||
step: 1,
|
||||
data: formData.personalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Educational & Professional Details',
|
||||
step: 2,
|
||||
data: formData.educationalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Family Details',
|
||||
step: 3,
|
||||
data: formData.familyDetails,
|
||||
},
|
||||
{
|
||||
title: 'Lifestyle & Habits',
|
||||
step: 4,
|
||||
data: formData.lifestyleDetails,
|
||||
},
|
||||
{
|
||||
title: 'Partner Preferences',
|
||||
step: 5,
|
||||
data: formData.partnerPreferences,
|
||||
},
|
||||
];
|
||||
|
||||
const renderValue = (key, value) => {
|
||||
if (key === "profiles" || key === "profile") {
|
||||
const list = Array.isArray(value) ? value : [];
|
||||
return (
|
||||
<Box display="flex" gap={0} flexWrap="wrap">
|
||||
{list.length > 0 ? (
|
||||
list.map((imgObj, index) => {
|
||||
const src =
|
||||
typeof imgObj === "string"
|
||||
? imgObj
|
||||
: imgObj?.preview ||
|
||||
imgObj?.url ||
|
||||
(imgObj?.file ? URL.createObjectURL(imgObj.file) : null);
|
||||
if (!src) return null;
|
||||
return (
|
||||
<img
|
||||
key={index}
|
||||
src={src}
|
||||
alt="Profile"
|
||||
style={{
|
||||
width: "90px",
|
||||
height: "90px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "50%",
|
||||
border: "1px solid #ccc",
|
||||
marginLeft: "-20px",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Box display="flex" alignItems="center" gap={1} sx={{ color: "#888" }}>
|
||||
<Info size={16} />
|
||||
<Typography>No photos uploaded</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return "-";
|
||||
if (typeof value[0] === "object") {
|
||||
return value.map((item, idx) => (
|
||||
<div key={idx}>{JSON.stringify(item)}</div>
|
||||
));
|
||||
}
|
||||
return value.join(", ");
|
||||
}
|
||||
|
||||
if (value && typeof value === "object") {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return value || "-";
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={3} sx={{maxWidth:"1400px", width:"100%"}} mx="auto" >
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 my-10'>
|
||||
|
||||
{sections.map((section) => (
|
||||
{isLoading && (
|
||||
<Box
|
||||
py={4}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
sx={{ color: "#666" }}
|
||||
>
|
||||
Loading preview...
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<Box
|
||||
py={4}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
sx={{ color: "#d32f2f" }}
|
||||
>
|
||||
Failed to load preview.
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
sections.map((section) => (
|
||||
|
||||
<Card key={section.title} variant="outlined" sx={{ borderRadius: 2, background:"#fff5ed" }}>
|
||||
<CardHeader
|
||||
@ -72,7 +182,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent sx={{ pt: 1, }}>
|
||||
{Object.entries(section.data).map(([key, value]) => {
|
||||
{Object.entries(section.data || {}).map(([key, value]) => {
|
||||
// if (value && key !== 'profiles') {
|
||||
|
||||
|
||||
@ -82,9 +192,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
||||
.trim();
|
||||
const isImage =
|
||||
typeof value === "string" &&
|
||||
(value.startsWith("http") || value.startsWith("data:image"));
|
||||
const content = renderValue(key, value);
|
||||
return (
|
||||
|
||||
<Box
|
||||
@ -106,56 +214,28 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
</Typography>
|
||||
|
||||
{/* Special Case: Profiles Image Preview */}
|
||||
{key === "profiles" ? (
|
||||
<Box display="flex" gap={0} flexWrap="wrap">
|
||||
{value && value.length > 0 ? (
|
||||
value.map((imgObj, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={URL.createObjectURL(imgObj.file)}
|
||||
alt="Profile"
|
||||
style={{
|
||||
width: "90px",
|
||||
height: "90px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "50%",
|
||||
border: "1px solid #ccc",
|
||||
marginLeft:"-20px"
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Box display="flex" alignItems="center" gap={1} sx={{ color: "#888" }}>
|
||||
<Info size={16} />
|
||||
<Typography>No photos uploaded</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{key === "profiles" || key === "profile" ? (
|
||||
content
|
||||
) : value ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
wordBreak: "break-word",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</Typography>
|
||||
) : (
|
||||
// Text OR No Data
|
||||
<>
|
||||
{value ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
wordBreak: "break-word",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
) : (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
sx={{ color: "#888", fontStyle: "italic" }}
|
||||
>
|
||||
<Info size={16} />
|
||||
<Typography>No data available</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
sx={{ color: "#888", fontStyle: "italic" }}
|
||||
>
|
||||
<Info size={16} />
|
||||
<Typography>No data available</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
@ -234,8 +234,10 @@ const ProfilePreviewPage = () => {
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Qualification", educationalDetails.qualification, 2)}
|
||||
{renderField("Field of Study", educationalDetails.fieldOfStudy, 2)}
|
||||
{renderField("College Name", educationalDetails.collegeName, 2)}
|
||||
{renderField("Occupation", educationalDetails.occupation, 2)}
|
||||
{renderField("Organization", educationalDetails.organization, 2)}
|
||||
{renderField("Employee Type", educationalDetails.employeeType, 2)}
|
||||
{renderField("Income", educationalDetails.income, 2)}
|
||||
{renderField("Work Location", educationalDetails.workLocation, 2)}
|
||||
</CardContent>
|
||||
@ -269,8 +271,10 @@ const ProfilePreviewPage = () => {
|
||||
{renderField("Father Occupation", familyDetails.fatherOccupation)}
|
||||
{renderField("Mother Name", familyDetails.motherName)}
|
||||
{renderField("Mother Occupation", familyDetails.motherOccupation)}
|
||||
{renderField("Number of Siblings", familyDetails.siblings)}
|
||||
{renderField("Siblings Marital status", familyDetails.siblingsStatus)}
|
||||
{renderField("Brother Count", familyDetails.brotherCount)}
|
||||
{renderField("Sister Count", familyDetails.sisterCount)}
|
||||
{renderField("Family Status", familyDetails.familyStatus)}
|
||||
{renderField("Native Place", familyDetails.nativePlace)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@ -298,11 +302,21 @@ const ProfilePreviewPage = () => {
|
||||
<Divider />
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Diet", lifestyleDetails.diet)}
|
||||
{/* {renderField("Drinking", lifestyleDetails.drinking)}
|
||||
{renderField("Smoking", lifestyleDetails.smoking)} */}
|
||||
{renderField("Hobbies", lifestyleDetails.hobbies)}
|
||||
|
||||
{renderField(
|
||||
"Diet",
|
||||
Array.isArray(lifestyleDetails.diets)
|
||||
? lifestyleDetails.diets.join(", ")
|
||||
: lifestyleDetails.diets
|
||||
)}
|
||||
{renderField(
|
||||
"Hobbies",
|
||||
Array.isArray(lifestyleDetails.hobbies)
|
||||
? lifestyleDetails.hobbies.join(", ")
|
||||
: lifestyleDetails.hobbies
|
||||
)}
|
||||
{renderField("Date of Birth", lifestyleDetails.dob)}
|
||||
{renderField("Time of Birth", lifestyleDetails.tob)}
|
||||
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@ -331,14 +345,50 @@ const ProfilePreviewPage = () => {
|
||||
<Divider />
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Age", partnerPreferences.ageRange)}
|
||||
{renderField("Religion", partnerPreferences.religionCaste)}
|
||||
{renderField("Occupation", partnerPreferences.occupationPref)}
|
||||
{renderField("Life style", partnerPreferences.lifestylePref)}
|
||||
{renderField("Qualification", partnerPreferences.qualificationPref)}
|
||||
{renderField("Occupation", partnerPreferences.occupationText)}
|
||||
{renderField("Income", partnerPreferences.incomePref)}
|
||||
{renderField("Location", partnerPreferences.locationPref)}
|
||||
{renderField("Age Range", partnerPreferences.ageRange)}
|
||||
{renderField(
|
||||
"Caste",
|
||||
Array.isArray(partnerPreferences.castes)
|
||||
? partnerPreferences.castes.join(", ")
|
||||
: partnerPreferences.castes
|
||||
)}
|
||||
{renderField(
|
||||
"Sub Caste",
|
||||
Array.isArray(partnerPreferences.subCastes)
|
||||
? partnerPreferences.subCastes.join(", ")
|
||||
: partnerPreferences.subCastes
|
||||
)}
|
||||
{renderField(
|
||||
"Occupation",
|
||||
Array.isArray(partnerPreferences.occupations)
|
||||
? partnerPreferences.occupations.join(", ")
|
||||
: partnerPreferences.occupations
|
||||
)}
|
||||
{renderField(
|
||||
"Qualification",
|
||||
Array.isArray(partnerPreferences.educations)
|
||||
? partnerPreferences.educations.join(", ")
|
||||
: partnerPreferences.educations
|
||||
)}
|
||||
{renderField(
|
||||
"Lifestyle & Hobbies",
|
||||
Array.isArray(partnerPreferences.hobbies)
|
||||
? partnerPreferences.hobbies.join(", ")
|
||||
: partnerPreferences.hobbies
|
||||
)}
|
||||
{renderField("Annual Income", partnerPreferences.annualIncome)}
|
||||
{renderField(
|
||||
"State",
|
||||
Array.isArray(partnerPreferences.states)
|
||||
? partnerPreferences.states.join(", ")
|
||||
: partnerPreferences.states
|
||||
)}
|
||||
{renderField(
|
||||
"City",
|
||||
Array.isArray(partnerPreferences.districts)
|
||||
? partnerPreferences.districts.join(", ")
|
||||
: partnerPreferences.districts
|
||||
)}
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState,useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
@ -16,7 +16,105 @@ import LifestyleDetailsForm from "./LifestyleDetailsForm";
|
||||
import PartnerPreferencesForm from "./PartnerPreferencesForm";
|
||||
import PreviewScreen from "./PreviewScreen";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useRegisterStep1 } from "../hooks/useRegister";
|
||||
import {
|
||||
useRegisterStep1,
|
||||
useRegisterStep2,
|
||||
useRegisterStep3,
|
||||
useRegisterStep4,
|
||||
useRegisterStep5,
|
||||
} from "../hooks/useRegister";
|
||||
import { setAccessToken } from "../api/axiosInstance";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const STEP_FIELD_ORDER = {
|
||||
1: [
|
||||
"name",
|
||||
"gender",
|
||||
"mobileNumber",
|
||||
"dob",
|
||||
"height",
|
||||
"weight",
|
||||
"maritalStatus",
|
||||
"religion",
|
||||
"profileFor",
|
||||
"caste",
|
||||
"subCaste",
|
||||
"gothram",
|
||||
"raasi",
|
||||
"star",
|
||||
"email",
|
||||
"password",
|
||||
"confirmPassword",
|
||||
"state",
|
||||
"city",
|
||||
"pincode",
|
||||
"profiles",
|
||||
],
|
||||
2: [
|
||||
"fieldOfStudy",
|
||||
"qualification",
|
||||
"collegeName",
|
||||
"occupation",
|
||||
"organization",
|
||||
"employeeType",
|
||||
"income",
|
||||
"workLocation",
|
||||
],
|
||||
3: [
|
||||
"fatherName",
|
||||
"fatherOccupation",
|
||||
"motherName",
|
||||
"motherOccupation",
|
||||
"brotherCount",
|
||||
"sisterCount",
|
||||
"familyStatus",
|
||||
"nativePlace",
|
||||
],
|
||||
4: ["diets", "hobbies", "dob", "tob", "placeOfBirth"],
|
||||
5: [
|
||||
"ageRange",
|
||||
"castes",
|
||||
"subCastes",
|
||||
"occupations",
|
||||
"educations",
|
||||
"hobbies",
|
||||
"annualIncome",
|
||||
"states",
|
||||
"districts",
|
||||
],
|
||||
};
|
||||
|
||||
const STEP1_SERVER_FIELD_MAP = {
|
||||
name: "name",
|
||||
mobile: "mobileNumber",
|
||||
mobile_number: "mobileNumber",
|
||||
mobileNumber: "mobileNumber",
|
||||
phone: "mobileNumber",
|
||||
email: "email",
|
||||
gender: "gender",
|
||||
dob: "dob",
|
||||
height: "height",
|
||||
weight: "weight",
|
||||
marital_status: "maritalStatus",
|
||||
maritalStatus: "maritalStatus",
|
||||
religion: "religion",
|
||||
profile_for: "profileFor",
|
||||
profileFor: "profileFor",
|
||||
caste: "caste",
|
||||
sub_caste: "subCaste",
|
||||
subCaste: "subCaste",
|
||||
gothram: "gothram",
|
||||
raasi: "raasi",
|
||||
star: "star",
|
||||
state: "state",
|
||||
district: "city",
|
||||
city: "city",
|
||||
pincode: "pincode",
|
||||
password: "password",
|
||||
confirm_password: "confirmPassword",
|
||||
confirmPassword: "confirmPassword",
|
||||
profiles: "profiles",
|
||||
};
|
||||
const Stepper = ({ currentStep, onStepClick }) => {
|
||||
|
||||
|
||||
@ -84,6 +182,136 @@ const StepperForm = () => {
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const registerStep1 = useRegisterStep1();
|
||||
const registerStep2 = useRegisterStep2();
|
||||
const registerStep3 = useRegisterStep3();
|
||||
const registerStep4 = useRegisterStep4();
|
||||
const registerStep5 = useRegisterStep5();
|
||||
|
||||
const normalizeStep1Field = (key) => {
|
||||
if (!key) return key;
|
||||
const trimmed = String(key).trim();
|
||||
if (!trimmed) return trimmed;
|
||||
return (
|
||||
STEP1_SERVER_FIELD_MAP[trimmed] ||
|
||||
STEP1_SERVER_FIELD_MAP[trimmed.toLowerCase()] ||
|
||||
trimmed
|
||||
);
|
||||
};
|
||||
|
||||
const coerceErrorMessage = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter(Boolean).join(" ");
|
||||
}
|
||||
if (typeof value === "string") return value;
|
||||
if (value && typeof value === "object") {
|
||||
return (
|
||||
value.message ||
|
||||
value.msg ||
|
||||
value.error ||
|
||||
value.detail ||
|
||||
""
|
||||
);
|
||||
}
|
||||
if (value === null || value === undefined) return "";
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const mapServerErrors = (error) => {
|
||||
const data = error?.response?.data ?? error?.data ?? error;
|
||||
if (!data) return {};
|
||||
const payload = data.errors ?? data.error ?? data.data ?? data;
|
||||
|
||||
if (typeof payload === "string") {
|
||||
return { _form: payload };
|
||||
}
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
const out = {};
|
||||
payload.forEach((item) => {
|
||||
if (!item) return;
|
||||
if (typeof item === "string") {
|
||||
out._form = out._form ? `${out._form} ${item}` : item;
|
||||
return;
|
||||
}
|
||||
const key = item.field || item.name || item.param || item.key;
|
||||
const message =
|
||||
item.message || item.msg || item.error || item.detail || "";
|
||||
if (key) {
|
||||
out[normalizeStep1Field(key)] =
|
||||
String(message || "Invalid value");
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
if (typeof payload === "object") {
|
||||
const out = {};
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
if (
|
||||
(key === "message" || key === "error" || key === "detail") &&
|
||||
typeof value === "string"
|
||||
) {
|
||||
out._form = out._form ? `${out._form} ${value}` : value;
|
||||
return;
|
||||
}
|
||||
const normalizedKey = normalizeStep1Field(key);
|
||||
const message = coerceErrorMessage(value);
|
||||
if (!normalizedKey) return;
|
||||
out[normalizedKey] = message || "Invalid value";
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const focusFirstError = (errorMap, fieldOrder = []) => {
|
||||
if (!errorMap || Object.keys(errorMap).length === 0) return;
|
||||
const order = fieldOrder.filter((key) => errorMap[key]);
|
||||
const firstKey =
|
||||
order[0] ||
|
||||
Object.keys(errorMap).find((key) => key !== "_form");
|
||||
if (!firstKey) return;
|
||||
setTimeout(() => {
|
||||
const byAria = document.querySelector(
|
||||
`[aria-labelledby~="${firstKey}-label"]`
|
||||
);
|
||||
if (byAria && typeof byAria.focus === "function") {
|
||||
byAria.focus();
|
||||
return;
|
||||
}
|
||||
const byName = document.querySelector(`[name="${firstKey}"]`);
|
||||
if (byName && typeof byName.focus === "function") {
|
||||
byName.focus();
|
||||
return;
|
||||
}
|
||||
const byId = document.getElementById(firstKey);
|
||||
if (byId && typeof byId.focus === "function") {
|
||||
byId.focus();
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const clearFieldErrors = (fields) => {
|
||||
if (!fields) return;
|
||||
const list = Array.isArray(fields) ? fields : [fields];
|
||||
setErrors((prev) => {
|
||||
if (!prev || Object.keys(prev).length === 0) return prev;
|
||||
let changed = false;
|
||||
const next = { ...prev };
|
||||
list.forEach((field) => {
|
||||
if (field in next) {
|
||||
delete next[field];
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (next._form) {
|
||||
delete next._form;
|
||||
changed = true;
|
||||
}
|
||||
return changed ? next : prev;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@ -154,8 +382,8 @@ const StepperForm = () => {
|
||||
"fieldOfStudy",
|
||||
"occupation",
|
||||
"organization",
|
||||
"employeeType",
|
||||
"income",
|
||||
"workLocation",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!educationalDetails[field]) {
|
||||
@ -165,12 +393,8 @@ const StepperForm = () => {
|
||||
} else if (step === 3) {
|
||||
const required = [
|
||||
"fatherName",
|
||||
"fatherOccupation",
|
||||
"motherName",
|
||||
"motherOccupation",
|
||||
"siblings",
|
||||
"siblingsStatus",
|
||||
|
||||
"familyStatus",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!familyDetails[field]) {
|
||||
@ -178,25 +402,36 @@ const StepperForm = () => {
|
||||
}
|
||||
});
|
||||
} else if (step === 4) {
|
||||
const required = ["diet", "drinking", "smoking", "hobbies"];
|
||||
const required = ["diets", "hobbies", "dob", "tob"];
|
||||
required.forEach((field) => {
|
||||
if (!lifestyleDetails[field]) {
|
||||
const value = lifestyleDetails[field];
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) newErrors[field] = "This field is required";
|
||||
} else if (!value) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
} else if (step === 5) {
|
||||
const required = [
|
||||
"ageRange",
|
||||
"religionCaste",
|
||||
"occupationPref",
|
||||
"lifestylePref",
|
||||
"qualificationPref",
|
||||
"occupationText",
|
||||
"incomePref",
|
||||
"locationPref",
|
||||
"castes",
|
||||
"subCastes",
|
||||
"occupations",
|
||||
"educations",
|
||||
"hobbies",
|
||||
"annualIncome",
|
||||
"states",
|
||||
"districts",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!partnerPreferences[field]) {
|
||||
const value = partnerPreferences[field];
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
@ -204,11 +439,8 @@ const StepperForm = () => {
|
||||
|
||||
setErrors(newErrors);
|
||||
|
||||
// Autofocus first invalid field
|
||||
if (Object.keys(newErrors).length > 0) {
|
||||
const firstKey = Object.keys(newErrors)[0];
|
||||
const el = document.querySelector(`[name="${firstKey}"]`);
|
||||
if (el) el.focus();
|
||||
focusFirstError(newErrors, STEP_FIELD_ORDER[step] || []);
|
||||
}
|
||||
|
||||
return Object.keys(newErrors).length === 0;
|
||||
@ -237,6 +469,132 @@ const StepperForm = () => {
|
||||
fcm_token: localStorage.getItem("fcm_token") || "",
|
||||
});
|
||||
|
||||
const buildRegisterStep2Payload = () => ({
|
||||
college_name: educationalDetails.collegeName || "",
|
||||
study_field: educationalDetails.fieldOfStudy,
|
||||
education: educationalDetails.qualification,
|
||||
occupation: educationalDetails.occupation,
|
||||
company_name: educationalDetails.organization || "",
|
||||
employee_type: educationalDetails.employeeType,
|
||||
annual_income: educationalDetails.income,
|
||||
work_location: educationalDetails.workLocation || "",
|
||||
});
|
||||
|
||||
const buildRegisterStep3Payload = () => {
|
||||
const payload = {
|
||||
father_name: familyDetails.fatherName,
|
||||
father_occupation: familyDetails.fatherOccupation || "",
|
||||
mother_name: familyDetails.motherName,
|
||||
mother_occupation: familyDetails.motherOccupation || "",
|
||||
family_status: familyDetails.familyStatus,
|
||||
native_place: familyDetails.nativePlace || "",
|
||||
brother_count: familyDetails.brotherCount || 0,
|
||||
sister_count: familyDetails.sisterCount || 0,
|
||||
};
|
||||
|
||||
(familyDetails.brothers || []).forEach((brother, index) => {
|
||||
payload[`brothers[${index}][name]`] = brother?.name || "";
|
||||
payload[`brothers[${index}][occupation]`] = brother?.occupation || "";
|
||||
payload[`brothers[${index}][marital_status]`] =
|
||||
brother?.maritalStatus || "";
|
||||
payload[`brothers[${index}][have_childrens]`] =
|
||||
brother?.haveChildrens ?? "";
|
||||
});
|
||||
|
||||
(familyDetails.sisters || []).forEach((sister, index) => {
|
||||
payload[`sisters[${index}][name]`] = sister?.name || "";
|
||||
payload[`sisters[${index}][occupation]`] = sister?.occupation || "";
|
||||
payload[`sisters[${index}][marital_status]`] =
|
||||
sister?.maritalStatus || "";
|
||||
payload[`sisters[${index}][have_childrens]`] =
|
||||
sister?.haveChildrens ?? "";
|
||||
});
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const buildRegisterStep4Payload = () => {
|
||||
const payload = {
|
||||
dob: lifestyleDetails.dob,
|
||||
tob: lifestyleDetails.tob,
|
||||
};
|
||||
|
||||
(lifestyleDetails.diets || []).forEach((id, index) => {
|
||||
payload[`diets[${index}]`] = id;
|
||||
});
|
||||
|
||||
(lifestyleDetails.hobbies || []).forEach((id, index) => {
|
||||
payload[`hobbies[${index}]`] = id;
|
||||
});
|
||||
|
||||
const graha = lifestyleDetails.graha || {};
|
||||
Object.keys(graha).forEach((house) => {
|
||||
const values = graha[house] || [];
|
||||
values.forEach((value, index) => {
|
||||
payload[`graha_${house}[${index}]`] = value;
|
||||
});
|
||||
});
|
||||
|
||||
const amsam = lifestyleDetails.amsam || {};
|
||||
Object.keys(amsam).forEach((house) => {
|
||||
const values = amsam[house] || [];
|
||||
values.forEach((value, index) => {
|
||||
payload[`amsam_${house}[${index}]`] = value;
|
||||
});
|
||||
});
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const buildRegisterStep5Payload = () => {
|
||||
const payload = {
|
||||
age_range: partnerPreferences.ageRange,
|
||||
annual_income: partnerPreferences.annualIncome,
|
||||
};
|
||||
|
||||
(partnerPreferences.castes || []).forEach((id, index) => {
|
||||
payload[`castes[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.subCastes || []).forEach((id, index) => {
|
||||
payload[`sub_castes[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.occupations || []).forEach((id, index) => {
|
||||
payload[`occupations[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.educations || []).forEach((id, index) => {
|
||||
payload[`educations[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.hobbies || []).forEach((id, index) => {
|
||||
payload[`hobbies[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.states || []).forEach((id, index) => {
|
||||
payload[`states[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.districts || []).forEach((id, index) => {
|
||||
payload[`districts[${index}]`] = id;
|
||||
});
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const extractAccessToken = (res) =>
|
||||
res?.access_token ||
|
||||
res?.accessToken ||
|
||||
res?.token ||
|
||||
res?.data?.access_token ||
|
||||
res?.data?.accessToken ||
|
||||
res?.data?.token ||
|
||||
res?.result?.access_token ||
|
||||
res?.result?.accessToken ||
|
||||
res?.result?.token ||
|
||||
null;
|
||||
|
||||
const handleStepSubmit = async () => {
|
||||
const isValid = validateStep(currentStep);
|
||||
if (!isValid) return;
|
||||
@ -244,11 +602,45 @@ const StepperForm = () => {
|
||||
try {
|
||||
if (currentStep === 1) {
|
||||
const payload = buildRegisterStep1Payload();
|
||||
await registerStep1.mutateAsync(payload);
|
||||
const res = await registerStep1.mutateAsync(payload);
|
||||
const token = extractAccessToken(res);
|
||||
if (token) {
|
||||
setAccessToken(token);
|
||||
}
|
||||
} else if (currentStep === 2) {
|
||||
const payload = buildRegisterStep2Payload();
|
||||
await registerStep2.mutateAsync(payload);
|
||||
} else if (currentStep === 3) {
|
||||
const payload = buildRegisterStep3Payload();
|
||||
await registerStep3.mutateAsync(payload);
|
||||
} else if (currentStep === 4) {
|
||||
const payload = buildRegisterStep4Payload();
|
||||
await registerStep4.mutateAsync(payload);
|
||||
} else if (currentStep === 5) {
|
||||
const payload = buildRegisterStep5Payload();
|
||||
await registerStep5.mutateAsync(payload);
|
||||
}
|
||||
setErrors({});
|
||||
setCurrentStep((prev) => Math.min(prev + 1, 6));
|
||||
window.scrollTo(0, 0);
|
||||
} catch (e) {
|
||||
if (currentStep === 1) {
|
||||
const serverErrors = mapServerErrors(e);
|
||||
if (Object.keys(serverErrors).length > 0) {
|
||||
const hasFieldErrors = Object.keys(serverErrors).some(
|
||||
(key) => key !== "_form"
|
||||
);
|
||||
if (hasFieldErrors) {
|
||||
setErrors(serverErrors);
|
||||
focusFirstError(serverErrors, STEP_FIELD_ORDER[1]);
|
||||
return;
|
||||
}
|
||||
if (serverErrors._form) {
|
||||
toast.error(serverErrors._form, { position: "top-right" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
alert("Failed to submit step. Please try again.");
|
||||
}
|
||||
};
|
||||
@ -296,6 +688,7 @@ const StepperForm = () => {
|
||||
<PersonalDetailsForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
@ -304,6 +697,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
@ -312,6 +706,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 4:
|
||||
@ -320,6 +715,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 5:
|
||||
@ -328,6 +724,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 6:
|
||||
|
||||
@ -27,8 +27,16 @@ export const useCasteMasters = (religion_id) =>
|
||||
export const useSubCasteMasters = (caste_id) =>
|
||||
useQuery({
|
||||
queryKey: ["sub-caste-masters", caste_id],
|
||||
queryFn: () => getSubCasteMasters(caste_id),
|
||||
enabled: !!caste_id,
|
||||
queryFn: async () => {
|
||||
if (Array.isArray(caste_id)) {
|
||||
const results = await Promise.all(
|
||||
caste_id.map((id) => getSubCasteMasters(id))
|
||||
);
|
||||
return results;
|
||||
}
|
||||
return getSubCasteMasters(caste_id);
|
||||
},
|
||||
enabled: Array.isArray(caste_id) ? caste_id.length > 0 : !!caste_id,
|
||||
});
|
||||
|
||||
/** City depends on state */
|
||||
@ -36,7 +44,7 @@ export const useCityMasters = (state_id) =>
|
||||
useQuery({
|
||||
queryKey: ["city-masters", state_id],
|
||||
queryFn: () => getCityMasters(state_id),
|
||||
enabled: !!state_id,
|
||||
enabled: Array.isArray(state_id) ? state_id.length > 0 : !!state_id,
|
||||
});
|
||||
|
||||
/** Star depends on raasi */
|
||||
|
||||
@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getPersonalDetailsMasters,
|
||||
getEducationMasters,
|
||||
getEducationList,
|
||||
getFamilyMasters,
|
||||
getLifestyleMasters,
|
||||
getPartnerPreferenceMasters,
|
||||
@ -21,6 +22,13 @@ export const useEducationMasters = () =>
|
||||
queryFn: getEducationMasters,
|
||||
});
|
||||
|
||||
export const useEducationList = (studyFieldId) =>
|
||||
useQuery({
|
||||
queryKey: ["education-list", studyFieldId],
|
||||
queryFn: () => getEducationList(studyFieldId),
|
||||
enabled: Boolean(studyFieldId),
|
||||
});
|
||||
|
||||
/** Family master */
|
||||
export const useFamilyMasters = () =>
|
||||
useQuery({
|
||||
@ -40,4 +48,4 @@ export const usePartnerPreferenceMasters = () =>
|
||||
useQuery({
|
||||
queryKey: ["partner-masters"],
|
||||
queryFn: getPartnerPreferenceMasters,
|
||||
});
|
||||
});
|
||||
|
||||
8
src/hooks/usePreview.js
Normal file
8
src/hooks/usePreview.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getPreviewDetails } from "../api/preview.api";
|
||||
|
||||
export const usePreviewDetails = () =>
|
||||
useQuery({
|
||||
queryKey: ["preview-details"],
|
||||
queryFn: getPreviewDetails,
|
||||
});
|
||||
@ -4,6 +4,7 @@ import {
|
||||
registerStep2API,
|
||||
registerStep3API,
|
||||
registerStep4API,
|
||||
registerStep5API,
|
||||
} from "../api/register.api";
|
||||
|
||||
/**
|
||||
@ -37,3 +38,11 @@ export const useRegisterStep4 = () =>
|
||||
useMutation({
|
||||
mutationFn: registerStep4API,
|
||||
});
|
||||
|
||||
/**
|
||||
* Register - Step 5
|
||||
*/
|
||||
export const useRegisterStep5 = () =>
|
||||
useMutation({
|
||||
mutationFn: registerStep5API,
|
||||
});
|
||||
|
||||
10
src/hooks/useReviews.js
Normal file
10
src/hooks/useReviews.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getReviews } from "../api/reviews.api";
|
||||
|
||||
export const useReviews = () =>
|
||||
useQuery({
|
||||
queryKey: ["reviews"],
|
||||
queryFn: getReviews,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
11
src/hooks/useTermsAndPolicies.js
Normal file
11
src/hooks/useTermsAndPolicies.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getTermsAndPolicies } from "../api/terms.api";
|
||||
|
||||
export const useTermsAndPolicies = (type) =>
|
||||
useQuery({
|
||||
queryKey: ["terms-and-policies", type],
|
||||
queryFn: () => getTermsAndPolicies(type),
|
||||
enabled: Boolean(type),
|
||||
staleTime: 1000 * 60 * 60,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
@ -1,13 +1,22 @@
|
||||
|
||||
import React from 'react';
|
||||
import { ArrowBackIosNew, Security, Shield, VerifiedUser } from '@mui/icons-material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const text = `It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy.`;
|
||||
import React, { useMemo } from "react";
|
||||
import { Security, Shield, VerifiedUser } from "@mui/icons-material";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTermsAndPolicies } from "../hooks/useTermsAndPolicies";
|
||||
import { Skeleton, SkeletonText } from "../components/common/Skeleton";
|
||||
|
||||
export default function PoliciesPage() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const type = searchParams.get("type") || "privacy";
|
||||
const { data, isLoading, isError } = useTermsAndPolicies(type);
|
||||
|
||||
const content = useMemo(() => {
|
||||
return data?.data?.message || "";
|
||||
}, [data]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
return data?.data?.name || "Privacy Policy";
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen relative z-20">
|
||||
@ -19,40 +28,66 @@ export default function PoliciesPage() {
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/10" />
|
||||
<div className="relative flex items-center justify-between px-6 py-8 safe-area-top">
|
||||
|
||||
<div className="text-center flex-1">
|
||||
<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }} transition={{ delay: 0.3 }}
|
||||
className="inline-block p-4 bg-white/20 rounded-2xl mb-3">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="inline-block p-4 bg-white/20 rounded-2xl mb-3"
|
||||
>
|
||||
<Shield className="text-4xl text-[#A70710]" />
|
||||
</motion.div>
|
||||
<h1 className="text-3xl font-bold tracking-wider">Policies</h1>
|
||||
<p className="text-green-100 text-sm mt-1">Your Safety Is Our First Priority</p>
|
||||
<h1 className="text-3xl font-bold tracking-wider">{title}</h1>
|
||||
<p className="text-green-100 text-sm mt-1">
|
||||
Your Safety Is Our First Priority
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-12" />
|
||||
</div>
|
||||
</motion.header>
|
||||
|
||||
<main className="md:px-4 py-10 max-w-[1400px] mx-auto space-y-8">
|
||||
<motion.div whileHover={{ scale: 1.02 }} className="bg-white/90 backdrop-blur rounded-3xl p-8 shadow-xl border border-green-100">
|
||||
<div className="flex items-start gap-5">
|
||||
<div className="p-4 bg-gradient-to-br from-green-500 to-emerald-600 rounded-2xl shadow-lg">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.01 }}
|
||||
className="bg-white/90 backdrop-blur rounded-3xl p-8 shadow-xl border border-green-100 mx-auto max-w-[1100px] overflow-hidden"
|
||||
>
|
||||
<div>
|
||||
{/* <div className="p-4 bg-gradient-to-br from-green-500 to-emerald-600 rounded-2xl shadow-lg">
|
||||
<Security className="text-white text-3xl" />
|
||||
</div>
|
||||
</div> */}
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-green-900 mb-3">Privacy Policy</h3>
|
||||
<p className="text-gray-800 text-lg leading-8 font-light tracking-wide">{text}</p>
|
||||
{/* <h3 className="text-xl font-bold text-green-900 mb-3">{title}</h3> */}
|
||||
{isLoading && (
|
||||
<div className="space-y-4">
|
||||
<Skeleton height={22} width="45%" />
|
||||
<SkeletonText lines={6} height={14} />
|
||||
<SkeletonText lines={4} height={14} />
|
||||
</div>
|
||||
)}
|
||||
{isError && (
|
||||
<p className="text-red-600 text-lg leading-8 font-light tracking-wide">
|
||||
Failed to load policy.
|
||||
</p>
|
||||
)}
|
||||
{!isLoading && !isError && (
|
||||
<div
|
||||
className="text-gray-800 text-[16px] leading-7"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div whileHover={{ scale: 1.02 }} className="bg-white/90 backdrop-blur rounded-3xl p-8 shadow-xl border border-green-100">
|
||||
<h3 className="text-xl font-bold text-emerald-900 mb-3">Safety Guidelines</h3>
|
||||
<p className="text-gray-800 text-lg leading-8 font-light tracking-wide">{text}</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div initial={{ scale: 0.9 }} animate={{ scale: 1 }} className="bg-[#f5fbff] text-black rounded-3xl p-10 text-center shadow-2xl">
|
||||
<motion.div
|
||||
initial={{ scale: 0.9 }}
|
||||
animate={{ scale: 1 }}
|
||||
className="bg-[#f5fbff] text-black rounded-3xl p-10 text-center shadow-2xl"
|
||||
>
|
||||
<h2 className="text-2xl font-bold mb-3">Protected With Love & Care</h2>
|
||||
<p className="text-black text-lg">Every profile, every chat, every dream — safeguarded 24×7.</p>
|
||||
<p className="text-black text-lg">
|
||||
Every profile, every chat, every dream — safeguarded 24×7.
|
||||
</p>
|
||||
<div className="flex justify-center gap-3 mt-6">
|
||||
<VerifiedUser className="text-4xl animate-ping text-[#A70710]" />
|
||||
<Shield className="text-5xl text-[#A70710]" />
|
||||
@ -63,4 +98,4 @@ export default function PoliciesPage() {
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
import React from 'react';
|
||||
import { ArrowBackIosNew, Favorite, AutoStories } from '@mui/icons-material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Favorite, AutoStories } from '@mui/icons-material';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const loremText = `It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy.`;
|
||||
import { useTermsAndPolicies } from '../hooks/useTermsAndPolicies';
|
||||
import { Skeleton, SkeletonText } from '../components/common/Skeleton';
|
||||
|
||||
export default function TermsAndCondition() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const type = searchParams.get("type") || "terms";
|
||||
const { data, isLoading, isError } = useTermsAndPolicies(type);
|
||||
|
||||
const content = useMemo(() => {
|
||||
return data?.data?.message || "";
|
||||
}, [data]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
return data?.data?.name || "Terms & Conditions";
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen overflow-hidden relative z-20">
|
||||
@ -33,7 +43,7 @@ export default function TermsAndCondition() {
|
||||
<Favorite className="text-4xl text-[#A70710] drop-shadow-lg" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<h1 className="text-3xl font-bold tracking-wider">Terms & Conditions</h1>
|
||||
<h1 className="text-3xl font-bold tracking-wider">{title}</h1>
|
||||
<p className="text-[#ffff] text-sm mt-1 font-medium">Your Trust, Our Promise</p>
|
||||
</div>
|
||||
|
||||
@ -49,37 +59,38 @@ export default function TermsAndCondition() {
|
||||
transition={{ delay: 0.4 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
{/* Card 1 */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
className="bg-white/90 backdrop-blur-lg rounded-3xl p-8 shadow-xl border border-amber-100 relative overflow-hidden"
|
||||
whileHover={{ scale: 1.01 }}
|
||||
className="bg-white/90 backdrop-blur-lg rounded-3xl p-8 shadow-xl border border-amber-100 relative overflow-hidden mx-auto max-w-[1100px]"
|
||||
>
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-amber-200 to-transparent rounded-full blur-3xl opacity-40" />
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 bg-gradient-to-br from-amber-400 to-orange-500 rounded-2xl shadow-lg">
|
||||
<div className="">
|
||||
{/* <div className="p-3 bg-gradient-to-br from-amber-400 to-orange-500 rounded-2xl shadow-lg">
|
||||
<AutoStories className="text-white text-2xl" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-gray-800 text-lg leading-8 font-light tracking-wide">
|
||||
{loremText}
|
||||
</p>
|
||||
</div> */}
|
||||
<div className="">
|
||||
{isLoading && (
|
||||
<div className="space-y-4">
|
||||
<Skeleton height={22} width="45%" />
|
||||
<SkeletonText lines={6} height={14} />
|
||||
<SkeletonText lines={4} height={14} />
|
||||
</div>
|
||||
)}
|
||||
{isError && (
|
||||
<p className="text-red-600 text-lg leading-8 font-light tracking-wide">
|
||||
Failed to load terms.
|
||||
</p>
|
||||
)}
|
||||
{!isLoading && !isError && (
|
||||
<div
|
||||
className="text-gray-800 text-[16px] leading-7"
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Card 2 */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
className="bg-gradient-to-r from-pink-50 to-rose-50 rounded-3xl p-8 shadow-xl border border-pink-200 relative overflow-hidden"
|
||||
>
|
||||
<div className="absolute -top-10 -right-10 w-40 h-40">
|
||||
<Favorite className="text-pink-200 text-9xl opacity-50" />
|
||||
</div>
|
||||
<p className="text-gray-800 text-lg leading-8 font-light tracking-wide relative z-10">
|
||||
{loremText}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Final Trust Message */}
|
||||
<motion.div
|
||||
initial={{ scale: 0.9 }}
|
||||
@ -104,4 +115,4 @@ export default function TermsAndCondition() {
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +1,22 @@
|
||||
import { Suspense, lazy } from "react";
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Autoplay } from 'swiper/modules';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import ProfileCompletion from '../components/profiledashboard/ProfileCompletion';
|
||||
import DailyRecommendedCard from '../components/profiledashboard/DailyRecommendedCard';
|
||||
import MatrimonyArticles from '../components/profiledashboard/MatrimonyArticles';
|
||||
import MatchingList from '../components/profiledashboard/MatchingList';
|
||||
import PaidMemberCard from '../components/profiledashboard/PaidMemberCard';
|
||||
import LazyImage from '../components/common/LazyImage';
|
||||
import Skeleton from "../components/common/Skeleton";
|
||||
import weddingpromo1 from "../assets/images/weddingpromo1.jpg";
|
||||
import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
|
||||
|
||||
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
||||
|
||||
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
|
||||
import ProfileCard from '../components/common/ProfileCard';
|
||||
import NewJoinedProfile from '../components/profiledashboard/NewJoinedProfile';
|
||||
import CustomerSupportCard from '../components/profiledashboard/CustomerSupportCard';
|
||||
import VideoSwiperGallery from '../components/profiledashboard/VideoSwiperGallery';
|
||||
import Profilecardemo from '../components/ui/ProfileCardDemo';
|
||||
|
||||
const ProfileCompletion = lazy(() => import("../components/profiledashboard/ProfileCompletion"));
|
||||
const MatrimonyArticles = lazy(() => import("../components/profiledashboard/MatrimonyArticles"));
|
||||
const MatchingList = lazy(() => import("../components/profiledashboard/MatchingList"));
|
||||
const VideoSwiperGallery = lazy(() => import("../components/profiledashboard/VideoSwiperGallery"));
|
||||
const Profilecardemo = lazy(() => import("../components/ui/ProfileCardDemo"));
|
||||
|
||||
|
||||
const images = [
|
||||
@ -35,6 +32,20 @@ const images = [
|
||||
weddingpromo3 // couple full shot
|
||||
];
|
||||
|
||||
const SectionFallback = ({ height = 280 }) => (
|
||||
<div className="py-10 px-4">
|
||||
<div className="w-full max-w-[1400px] mx-auto space-y-4">
|
||||
<Skeleton height={26} width="40%" rounded={10} />
|
||||
<Skeleton height={14} width="30%" rounded={8} />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
<Skeleton key={idx} height={height} rounded={16} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
const UserDashboardHome = () => {
|
||||
return (
|
||||
@ -88,6 +99,8 @@ const UserDashboardHome = () => {
|
||||
src={img}
|
||||
alt={`Slide ${idx + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
loading={idx === 0 ? "eager" : "lazy"}
|
||||
decoding="async"
|
||||
/>
|
||||
|
||||
{/* <LazyImage
|
||||
@ -192,21 +205,30 @@ const UserDashboardHome = () => {
|
||||
|
||||
`}</style>
|
||||
</div>
|
||||
<Profilecardemo/>
|
||||
<Suspense fallback={<SectionFallback height={320} />}>
|
||||
<Profilecardemo />
|
||||
</Suspense>
|
||||
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
<ProfileCompletion/>
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
<Suspense fallback={<SectionFallback height={220} />}>
|
||||
<ProfileCompletion />
|
||||
</Suspense>
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
<MatrimonyArticles/>
|
||||
<MatchingList/>
|
||||
{/* <PaidMemberCard/> */}
|
||||
<Suspense fallback={<SectionFallback height={280} />}>
|
||||
<MatrimonyArticles />
|
||||
</Suspense>
|
||||
<Suspense fallback={<SectionFallback height={320} />}>
|
||||
<MatchingList />
|
||||
</Suspense>
|
||||
{/* <PaidMemberCard/> */}
|
||||
|
||||
{/* <NewJoinedProfile/> */}
|
||||
{/* <CustomerSupportCard/> */}
|
||||
<VideoSwiperGallery/>
|
||||
{/* <NewJoinedProfile/> */}
|
||||
{/* <CustomerSupportCard/> */}
|
||||
<Suspense fallback={<SectionFallback height={240} />}>
|
||||
<VideoSwiperGallery />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ const registrationformSlice = createSlice({
|
||||
profiles: [],
|
||||
},
|
||||
educationalDetails: {
|
||||
collegeName: "",
|
||||
employeeType: "",
|
||||
qualification: "",
|
||||
fieldOfStudy: "",
|
||||
occupation: "",
|
||||
@ -40,25 +42,58 @@ const registrationformSlice = createSlice({
|
||||
fatherOccupation: "",
|
||||
motherName: "",
|
||||
motherOccupation: "",
|
||||
siblings: "",
|
||||
siblingsStatus: "",
|
||||
|
||||
brotherCount: 0,
|
||||
sisterCount: 0,
|
||||
brothers: [],
|
||||
sisters: [],
|
||||
familyStatus: "",
|
||||
nativePlace: "",
|
||||
},
|
||||
lifestyleDetails: {
|
||||
diet: "",
|
||||
// drinking: "",
|
||||
// smoking: "",
|
||||
hobbies: "",
|
||||
diets: [],
|
||||
hobbies: [],
|
||||
dob: "",
|
||||
tob: "",
|
||||
placeOfBirth: "",
|
||||
graha: {
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
5: [],
|
||||
6: [],
|
||||
7: [],
|
||||
8: [],
|
||||
9: [],
|
||||
10: [],
|
||||
11: [],
|
||||
12: [],
|
||||
},
|
||||
amsam: {
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
5: [],
|
||||
6: [],
|
||||
7: [],
|
||||
8: [],
|
||||
9: [],
|
||||
10: [],
|
||||
11: [],
|
||||
12: [],
|
||||
},
|
||||
},
|
||||
partnerPreferences: {
|
||||
ageRange: "",
|
||||
religionCaste: "",
|
||||
occupationPref: "",
|
||||
lifestylePref: "",
|
||||
qualificationPref: "",
|
||||
occupationText: "",
|
||||
incomePref: "",
|
||||
locationPref: "",
|
||||
castes: [],
|
||||
subCastes: [],
|
||||
occupations: [],
|
||||
educations: [],
|
||||
hobbies: [],
|
||||
annualIncome: "",
|
||||
states: [],
|
||||
districts: [],
|
||||
},
|
||||
},
|
||||
|
||||
@ -148,8 +183,10 @@ preloadDummyProfile: (state) => {
|
||||
...state.educationalDetails,
|
||||
qualification: "B.E",
|
||||
fieldOfStudy: "CSE",
|
||||
collegeName: "ABC Engineering College",
|
||||
occupation: "Software Engineer",
|
||||
organization: "ABC Pvt Ltd",
|
||||
employeeType: "Private",
|
||||
income: "10 LPA",
|
||||
workLocation: "Chennai",
|
||||
};
|
||||
@ -159,26 +196,74 @@ preloadDummyProfile: (state) => {
|
||||
fatherOccupation: "Business",
|
||||
motherName: "Mother Name",
|
||||
motherOccupation: "Homemaker",
|
||||
siblings: "1",
|
||||
siblingsStatus: "Married",
|
||||
brotherCount: 1,
|
||||
sisterCount: 1,
|
||||
brothers: [
|
||||
{
|
||||
name: "Rahul",
|
||||
occupation: "Engineer",
|
||||
maritalStatus: "Married",
|
||||
haveChildrens: 1,
|
||||
},
|
||||
],
|
||||
sisters: [
|
||||
{
|
||||
name: "Rohini",
|
||||
occupation: "Teacher",
|
||||
maritalStatus: "Married",
|
||||
haveChildrens: 0,
|
||||
},
|
||||
],
|
||||
familyStatus: 3,
|
||||
nativePlace: "Chennai",
|
||||
};
|
||||
state.lifestyleDetails = {
|
||||
...state.lifestyleDetails,
|
||||
diet: "Veg",
|
||||
// drinking: "No",
|
||||
// smoking: "No",
|
||||
hobbies: "Reading, Music",
|
||||
diets: [1],
|
||||
hobbies: [1, 3],
|
||||
dob: "1995-05-01",
|
||||
tob: "09:30",
|
||||
placeOfBirth: "Chennai",
|
||||
graha: {
|
||||
1: ["LAGNAM", "SURIYAN"],
|
||||
2: [],
|
||||
3: [],
|
||||
4: ["CHANDRAN", "CHEVVAI"],
|
||||
5: [],
|
||||
6: ["BUDHAN", "GURU"],
|
||||
7: [],
|
||||
8: ["SUKRAN", "SANI"],
|
||||
9: [],
|
||||
10: ["RAHU", "KETHU"],
|
||||
11: [],
|
||||
12: ["MANDHI"],
|
||||
},
|
||||
amsam: {
|
||||
1: ["LAGNAM", "SURIYAN"],
|
||||
2: [],
|
||||
3: [],
|
||||
4: ["CHANDRAN", "CHEVVAI"],
|
||||
5: [],
|
||||
6: ["BUDHAN", "GURU"],
|
||||
7: [],
|
||||
8: ["SUKRAN", "SANI"],
|
||||
9: [],
|
||||
10: ["RAHU", "KETHU"],
|
||||
11: [],
|
||||
12: ["MANDHI"],
|
||||
},
|
||||
};
|
||||
state.partnerPreferences = {
|
||||
...state.partnerPreferences,
|
||||
ageRange: "24-30",
|
||||
religionCaste: "Hindu / Brahmin",
|
||||
occupationPref: "IT / Software",
|
||||
lifestylePref: "Non-smoker, Non-drinker",
|
||||
qualificationPref: "Degree",
|
||||
occupationText: "IT, Banking",
|
||||
incomePref: "10-20 LPA",
|
||||
locationPref: "Chennai, Bengaluru",
|
||||
ageRange: 2,
|
||||
castes: [1],
|
||||
subCastes: [1],
|
||||
occupations: [57],
|
||||
educations: [14],
|
||||
hobbies: [1, 3],
|
||||
annualIncome: 1,
|
||||
states: [31],
|
||||
districts: [1],
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -1,8 +1,21 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import UserRoutes from './UserRoutes';
|
||||
import PublicRoutes from './PublicRoutes';
|
||||
import ScrollToTop from '../components/common/ScrollToTop';
|
||||
import Skeleton from "../components/common/Skeleton";
|
||||
|
||||
const RouteFallback = () => (
|
||||
<div className="min-h-[60vh] flex items-center justify-center px-6">
|
||||
<div className="w-full max-w-3xl space-y-4">
|
||||
<Skeleton height={32} rounded={12} />
|
||||
<Skeleton height={220} rounded={16} />
|
||||
<Skeleton height={16} />
|
||||
<Skeleton height={16} width="80%" />
|
||||
<Skeleton height={16} width="60%" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AppRoutes = () => {
|
||||
return (
|
||||
@ -10,11 +23,12 @@ const AppRoutes = () => {
|
||||
|
||||
<ScrollToTop />
|
||||
{/* Wrap UserRoutes inside AuthProvider separately */}
|
||||
<Routes>
|
||||
{UserRoutes()}
|
||||
{PublicRoutes()}
|
||||
|
||||
</Routes>
|
||||
<Suspense fallback={<RouteFallback />}>
|
||||
<Routes>
|
||||
{UserRoutes()}
|
||||
{PublicRoutes()}
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { lazy } from "react";
|
||||
import { Route } from "react-router-dom";
|
||||
import HomePage from "../pages/HomePage";
|
||||
import LandingLayout from "../layout/LandingLayout";
|
||||
import LoginPage from "../pages/auth/LoginPage";
|
||||
import ForgotPasswordPage from "../pages/auth/ForgotPasswordPage";
|
||||
import ChangePasswordPage from "../components/auth/ChangePasswordForm";
|
||||
import ProfileLayout from "../layout/ProfileLayout";
|
||||
import HomeLayout from "../layout/HomeLayout";
|
||||
import StepperForm from "../feature/StepperForm";
|
||||
import NotFound from "../pages/NotFound";
|
||||
|
||||
const HomePage = lazy(() => import("../pages/HomePage"));
|
||||
const LoginPage = lazy(() => import("../pages/auth/LoginPage"));
|
||||
const ForgotPasswordPage = lazy(() => import("../pages/auth/ForgotPasswordPage"));
|
||||
const StepperForm = lazy(() => import("../feature/StepperForm"));
|
||||
const NotFound = lazy(() => import("../pages/NotFound"));
|
||||
const PublicRoutes = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,26 +1,28 @@
|
||||
import { Route, useNavigate } from "react-router-dom";
|
||||
import ProfileLayout from "../layout/ProfileLayout";
|
||||
import UserDashboardHome from "../pages/UserDashboardHome";
|
||||
import PoliciesPage from '../pages/PoliciesPage';
|
||||
import TermsAndConditionPage from '../pages/TermsAndCondition';
|
||||
import SafetyCentrePage from '../pages/SafetyCentre';
|
||||
import SubscriptionPlanPage from '../pages/SubscriptionPlan';
|
||||
import SubscriptionHistoryPage from '../pages/SubscriptionHistory'
|
||||
import MatchesPage from "../pages/MatchesPage";
|
||||
import InterestSendPage from "../pages/InterestSendPage";
|
||||
import BlockedProfileListPage from "../pages/BlockedProfileListPage";
|
||||
import AccountSettingPage from "../pages/AccountSettingsPage";
|
||||
import ProfileDetailPage from "../pages/ProfileDetailPage";
|
||||
import ChatUI from "../pages/ChatPage";
|
||||
import HoroscopeGenerator from "../pages/HoroscoperGeneratePage";
|
||||
import ContactUsPage from "../pages/ContactUsPage";
|
||||
import ChangePasswordPage from "../components/auth/ChangePasswordForm";
|
||||
import StepperForm from "../feature/StepperForm";
|
||||
import FilterForm from "../feature/FilterForm";
|
||||
import ProfilePreviewPage from "../feature/ProfilePreviewPage";
|
||||
import NotificationPage from "../pages/NotificationPage";
|
||||
import MatchesLayout from "../layout/MatchesLayout";
|
||||
import ProfileCardDemo from "../components/ui/ProfileCardDemo";
|
||||
import { lazy } from "react";
|
||||
import { Route } from "react-router-dom";
|
||||
const ProfileLayout = lazy(() => import("../layout/ProfileLayout"));
|
||||
const MatchesLayout = lazy(() => import("../layout/MatchesLayout"));
|
||||
|
||||
const UserDashboardHome = lazy(() => import("../pages/UserDashboardHome"));
|
||||
const PoliciesPage = lazy(() => import("../pages/PoliciesPage"));
|
||||
const TermsAndConditionPage = lazy(() => import("../pages/TermsAndCondition"));
|
||||
const SafetyCentrePage = lazy(() => import("../pages/SafetyCentre"));
|
||||
const SubscriptionPlanPage = lazy(() => import("../pages/SubscriptionPlan"));
|
||||
const SubscriptionHistoryPage = lazy(() => import("../pages/SubscriptionHistory"));
|
||||
const MatchesPage = lazy(() => import("../pages/MatchesPage"));
|
||||
const InterestSendPage = lazy(() => import("../pages/InterestSendPage"));
|
||||
const BlockedProfileListPage = lazy(() => import("../pages/BlockedProfileListPage"));
|
||||
const AccountSettingPage = lazy(() => import("../pages/AccountSettingsPage"));
|
||||
const ProfileDetailPage = lazy(() => import("../pages/ProfileDetailPage"));
|
||||
const ChatUI = lazy(() => import("../pages/ChatPage"));
|
||||
const HoroscopeGenerator = lazy(() => import("../pages/HoroscoperGeneratePage"));
|
||||
const ContactUsPage = lazy(() => import("../pages/ContactUsPage"));
|
||||
const ChangePasswordPage = lazy(() => import("../components/auth/ChangePasswordForm"));
|
||||
const StepperForm = lazy(() => import("../feature/StepperForm"));
|
||||
const FilterForm = lazy(() => import("../feature/FilterForm"));
|
||||
const ProfilePreviewPage = lazy(() => import("../feature/ProfilePreviewPage"));
|
||||
const NotificationPage = lazy(() => import("../pages/NotificationPage"));
|
||||
const ProfileCardDemo = lazy(() => import("../components/ui/ProfileCardDemo"));
|
||||
|
||||
const UserRoutes = () => {
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export const isAuthenticated = () => {
|
||||
return !!localStorage.getItem("token");
|
||||
return !!localStorage.getItem("access_token");
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user