form done
This commit is contained in:
parent
b1d9d46e4e
commit
d35bd08f5f
264
package-lock.json
generated
264
package-lock.json
generated
@ -8,21 +8,27 @@
|
||||
"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",
|
||||
"axios": "^1.13.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.23.24",
|
||||
"lightswind": "^3.1.18",
|
||||
"lucide-react": "^0.553.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"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",
|
||||
@ -334,6 +340,29 @@
|
||||
"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",
|
||||
@ -349,6 +378,13 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dynamicss/dynamicss": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@dynamicss/dynamicss/-/dynamicss-2.2.8.tgz",
|
||||
"integrity": "sha512-e6hrGUydr8f+c9E/9fHFSG5LoSLdq/MdZXXfbzEDWIVuzKF2hcdxZE7nHNqUNF2htw1mZ17Pyoshu3A6kFEeFA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||
@ -398,6 +434,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
|
||||
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0"
|
||||
}
|
||||
@ -1076,6 +1113,27 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@files-ui/core": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@files-ui/core/-/core-2.0.7.tgz",
|
||||
"integrity": "sha512-L/DqdMWMusULUTrOd6AEpiC+Lkfy9eMNN8LVLzPyfHrUgW9jw2b2Aa5BBJyjdiTHTmd8hM/huc0kVu7CgeZWIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@files-ui/react": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@files-ui/react/-/react-1.2.5.tgz",
|
||||
"integrity": "sha512-in6RAdF/MsJ31lgeuSYSzo+4pe1bHrzpfEUfvIBwgv/JlUgwtdj5jE2xom/qoKovMy+Gg+WI+dmNzkrzCtkXBg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dynamicss/dynamicss": "^2.2.8",
|
||||
"@files-ui/core": "^2.0.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.2 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@ -1455,6 +1513,7 @@
|
||||
"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",
|
||||
@ -1537,6 +1596,94 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.19.0.tgz",
|
||||
"integrity": "sha512-TQ4FsGUsiGJVs+Ie4q7nHXUmFqZADXL/1hVtZpOKsdr3WQXwpX7C5YmeakZGFR2NZnuv4snFj+WTee3kgyFbyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/utils": "^7.3.5",
|
||||
"@mui/x-internals": "8.19.0",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
|
||||
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
|
||||
"dayjs": "^1.10.7",
|
||||
"luxon": "^3.0.2",
|
||||
"moment": "^2.29.4",
|
||||
"moment-hijri": "^2.1.2 || ^3.0.0",
|
||||
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"date-fns": {
|
||||
"optional": true
|
||||
},
|
||||
"date-fns-jalali": {
|
||||
"optional": true
|
||||
},
|
||||
"dayjs": {
|
||||
"optional": true
|
||||
},
|
||||
"luxon": {
|
||||
"optional": true
|
||||
},
|
||||
"moment": {
|
||||
"optional": true
|
||||
},
|
||||
"moment-hijri": {
|
||||
"optional": true
|
||||
},
|
||||
"moment-jalaali": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.19.0.tgz",
|
||||
"integrity": "sha512-mMmiyJAN5fW27srXJjhXhXJa+w2xGO45rwcjws6OQc9rdXGdJqRXhBwJd+OT7J1xwSdFIIUhjZRTz1KAfCSGBg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/utils": "^7.3.5",
|
||||
"reselect": "^5.1.1",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1594,6 +1741,32 @@
|
||||
"integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.0.tgz",
|
||||
"integrity": "sha512-hBjYg0aaRL1O2Z0IqWhnTLytnjDIxekmRxm1snsHjHaKVmIF1HiImWqsq+PuEbn6zdMlkIj9WofK1vR8jjx+Xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@standard-schema/utils": "^0.3.0",
|
||||
"immer": "^11.0.0",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"reselect": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.47",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
|
||||
@ -1887,6 +2060,18 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/utils": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
|
||||
@ -2881,6 +3066,12 @@
|
||||
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
@ -3952,9 +4143,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
@ -5045,6 +5236,16 @@
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-11.0.0.tgz",
|
||||
"integrity": "sha512-XtRG4SINt4dpqlnJvs70O2j6hH7H0X8fUzFsjMn1rwnETaxwp83HLNimXBjZ78MrKl3/d3/pkzDH0o0Lkxm37Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
@ -5940,6 +6141,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/lightswind/node_modules/date-fns": {
|
||||
"version": "3.6.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/lightswind/node_modules/jiti": {
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
@ -6963,6 +7175,30 @@
|
||||
"react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x || ^19.x.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"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"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.25 || ^19",
|
||||
"react": "^18.0 || ^19",
|
||||
"redux": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
||||
@ -7167,6 +7403,22 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"redux": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/refractor": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
||||
@ -7206,6 +7458,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
|
||||
@ -10,21 +10,27 @@
|
||||
"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",
|
||||
"axios": "^1.13.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.23.24",
|
||||
"lightswind": "^3.1.18",
|
||||
"lucide-react": "^0.553.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"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",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import "./App.css";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import AppRoutes from "./routes/AppRoutes";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -10,6 +10,7 @@ import StarIcon from "@mui/icons-material/Star";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
||||
import { motion } from 'framer-motion';
|
||||
import FilterModal from "../../feature/FilterModal";
|
||||
|
||||
// Profile Card Component
|
||||
function ProfileCard({ profile }) {
|
||||
@ -98,30 +99,33 @@ function ProfileCard({ profile }) {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[20px]">
|
||||
<button className="w-[60px] h-[60px] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-full shadow-md
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<button 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-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<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-[60px] h-[60px] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
rounded-full shadow-lg hover:shadow-xl transition-all duration-300
|
||||
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={() => setIsLiked(!isLiked)}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-7 h-7" viewBox="0 0 24 24" fill="currentColor">
|
||||
<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-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<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>
|
||||
@ -286,11 +290,14 @@ export default function MatchesInterface() {
|
||||
{/* Right Content Area - Scrollable */}
|
||||
<div className=" px-2 py-8">
|
||||
<div className="w-[100%] max-w-[1200px] mx-auto">
|
||||
<h1 className="text-[24px] font-bold text-gray-900 mb-8">
|
||||
<div className="flex justify-between gap-2 itemes-center mb-8">
|
||||
<h1 className="text-[24px] font-bold text-gray-900 ">
|
||||
{tabs.find(t => t.id === selectedTab)?.title}
|
||||
</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-2">
|
||||
<FilterModal/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
{profiles.map((profile) => (
|
||||
<ProfileCard key={profile.id} profile={profile} />
|
||||
))}
|
||||
|
||||
114
src/feature/AdvancedDropzone.jsx
Normal file
114
src/feature/AdvancedDropzone.jsx
Normal file
@ -0,0 +1,114 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Dropzone,
|
||||
FileMosaic,
|
||||
FullScreen,
|
||||
ImagePreview,
|
||||
VideoPreview,
|
||||
} from "@files-ui/react";
|
||||
|
||||
const BASE_URL = "https://www.myserver.com";
|
||||
|
||||
const AdvancedDropzone = ({ value, onChange }) => {
|
||||
const [extFiles, setExtFiles] = React.useState(value || []);
|
||||
const [imageSrc, setImageSrc] = React.useState(undefined);
|
||||
const [videoSrc, setVideoSrc] = React.useState(undefined);
|
||||
|
||||
const updateFiles = (incomingFiles) => {
|
||||
console.log("incoming files", incomingFiles);
|
||||
setExtFiles(incomingFiles);
|
||||
onChange && onChange(incomingFiles); // pass back to parent (e.g. Redux)
|
||||
};
|
||||
|
||||
const onDelete = (id) => {
|
||||
const updated = extFiles.filter((x) => x.id !== id);
|
||||
setExtFiles(updated);
|
||||
onChange && onChange(updated);
|
||||
};
|
||||
|
||||
const handleSee = (imageSource) => setImageSrc(imageSource);
|
||||
const handleWatch = (videoSource) => setVideoSrc(videoSource);
|
||||
|
||||
const handleStart = (filesToUpload) => {
|
||||
console.log("advanced demo start upload", filesToUpload);
|
||||
};
|
||||
|
||||
const handleFinish = (uploadedFiles) => {
|
||||
console.log("advanced demo finish upload", uploadedFiles);
|
||||
};
|
||||
|
||||
const handleAbort = (id) => {
|
||||
setExtFiles((prev) =>
|
||||
prev.map((ef) =>
|
||||
ef.id === id ? { ...ef, uploadStatus: "aborted" } : ef
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleCancel = (id) => {
|
||||
setExtFiles((prev) =>
|
||||
prev.map((ef) =>
|
||||
ef.id === id ? { ...ef, uploadStatus: undefined } : ef
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropzone
|
||||
onChange={updateFiles}
|
||||
minHeight="195px"
|
||||
value={extFiles}
|
||||
accept="image/*, video/*"
|
||||
maxFiles={3}
|
||||
maxFileSize={2 * 1024 * 1024}
|
||||
label="Drag'n drop files here or click to browse"
|
||||
uploadConfig={{
|
||||
url: BASE_URL + "/file",
|
||||
cleanOnUpload: true,
|
||||
}}
|
||||
onUploadStart={handleStart}
|
||||
onUploadFinish={handleFinish}
|
||||
fakeUpload
|
||||
actionButtons={{
|
||||
position: "after",
|
||||
abortButton: {},
|
||||
deleteButton: {},
|
||||
uploadButton: {},
|
||||
}}
|
||||
>
|
||||
{extFiles.map((file) => (
|
||||
<FileMosaic
|
||||
{...file}
|
||||
key={file.id}
|
||||
onDelete={onDelete}
|
||||
onSee={handleSee}
|
||||
onWatch={handleWatch}
|
||||
onAbort={handleAbort}
|
||||
onCancel={handleCancel}
|
||||
resultOnTooltip
|
||||
alwaysActive
|
||||
preview
|
||||
info
|
||||
/>
|
||||
))}
|
||||
</Dropzone>
|
||||
|
||||
<FullScreen
|
||||
open={imageSrc !== undefined}
|
||||
onClose={() => setImageSrc(undefined)}
|
||||
>
|
||||
<ImagePreview src={imageSrc} />
|
||||
</FullScreen>
|
||||
|
||||
<FullScreen
|
||||
open={videoSrc !== undefined}
|
||||
onClose={() => setVideoSrc(undefined)}
|
||||
>
|
||||
<VideoPreview src={videoSrc} autoPlay controls />
|
||||
</FullScreen>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedDropzone;
|
||||
144
src/feature/EducationalDetailsForm.jsx
Normal file
144
src/feature/EducationalDetailsForm.jsx
Normal file
@ -0,0 +1,144 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateEducationalDetails } from "../redux/registrationFormSlice";
|
||||
import { TextField, Button, Grid } from "@mui/material";
|
||||
|
||||
const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.educationalDetails);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateEducationalDetails({ [field]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Submitting educational details:", data);
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
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">
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<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">
|
||||
<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"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Occupation */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<TextField
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Company / Organization Name */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<TextField
|
||||
fullWidth
|
||||
name="organization"
|
||||
label="Company / Organization Name"
|
||||
value={data.organization}
|
||||
onChange={(e) => handleChange("organization", e.target.value)}
|
||||
error={Boolean(errors.organization)}
|
||||
helperText={errors.organization}
|
||||
placeholder="Enter Company / Organization Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Annual Income */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<TextField
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Work Location */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<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>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
marginTop: "40px",
|
||||
display: "flex",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Next
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EducationalDetailsForm;
|
||||
164
src/feature/FamilyDetailsForm.jsx
Normal file
164
src/feature/FamilyDetailsForm.jsx
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateFamilyDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
|
||||
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.familyDetails);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateFamilyDetails({ [field]: value }));
|
||||
};
|
||||
|
||||
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">
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-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">
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.siblingsStatus)}
|
||||
>
|
||||
<InputLabel id="siblingsStatus-label">
|
||||
Brothers / Sisters (Married / Unmarried)
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="siblingsStatus-label"
|
||||
label="Brothers / Sisters (Married / Unmarried)"
|
||||
name="siblingsStatus"
|
||||
value={data.siblingsStatus}
|
||||
onChange={(e) => handleChange("siblingsStatus", e.target.value)}
|
||||
>
|
||||
|
||||
<MenuItem value="All Married">All Married</MenuItem>
|
||||
<MenuItem value="All Unmarried">All Unmarried</MenuItem>
|
||||
<MenuItem value="Mixed">Mixed</MenuItem>
|
||||
</Select>
|
||||
{errors.siblingsStatus && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.siblingsStatus}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
</div>
|
||||
|
||||
<Grid item xs={12} style={{ marginTop: 16, display: "flex", gap: 16 , justifyContent: "center"}}>
|
||||
<Button variant="outlined" fullWidth onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" fullWidth onClick={onSubmitStep}>
|
||||
Submit & Next
|
||||
</Button>
|
||||
</Grid>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default FamilyDetailsForm;
|
||||
623
src/feature/FilterForm.jsx
Normal file
623
src/feature/FilterForm.jsx
Normal file
@ -0,0 +1,623 @@
|
||||
import React, { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
setAge,
|
||||
setHeight,
|
||||
setMaritalStatus,
|
||||
setMotherTongue,
|
||||
setReligion,
|
||||
setMatchesWithHoroscope,
|
||||
setCaste,
|
||||
setSubCaste,
|
||||
updateFilter,
|
||||
} from "../redux/filterSlice";
|
||||
import {
|
||||
Slider,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Chip,
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
} from "@mui/material";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
const FilterForm = () => {
|
||||
const dispatch = useDispatch();
|
||||
const filters = useSelector((state) => state.filters);
|
||||
|
||||
const [expandedSections, setExpandedSections] = useState({
|
||||
basic: true,
|
||||
religious: false,
|
||||
professional: false,
|
||||
location: false,
|
||||
lifestyle: false,
|
||||
family: false,
|
||||
});
|
||||
|
||||
const handleAccordionChange = (section) => (event, isExpanded) => {
|
||||
setExpandedSections((prev) => ({ ...prev, [section]: isExpanded }));
|
||||
};
|
||||
|
||||
const casteOptions = ["Agamudayar", "Pillai", "Vellalar"];
|
||||
const motherTongueOptions = [
|
||||
"Tamil",
|
||||
"Telugu",
|
||||
"Malayalam",
|
||||
"Kannada",
|
||||
"Hindi",
|
||||
];
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Filter Values:", filters);
|
||||
};
|
||||
|
||||
const handleSelectionChange = (field, value) => {
|
||||
console.log(`${field} selected:`, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto p-4 md:p-6 ">
|
||||
<div className="bg-white rounded-lg shadow-sm">
|
||||
{/* Header */}
|
||||
<div className="border-b p-4">
|
||||
<Typography variant="h5" className="font-semibold">
|
||||
Filter Options
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className="p-4 md:p-6">
|
||||
{/* Basic Details Section */}
|
||||
<Accordion
|
||||
expanded={expandedSections.basic}
|
||||
onChange={handleAccordionChange("basic")}
|
||||
className="mb-4"
|
||||
>
|
||||
<AccordionSummary expandIcon={<ChevronDown />}>
|
||||
<Typography className="font-semibold bg-gray-100 -mx-4 -my-2 px-4 py-2 w-full">
|
||||
Basic Details
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
{/* Age Slider */}
|
||||
<div className="md:col-span-1 mr-2 ">
|
||||
<Typography gutterBottom sx={{marginBottom:"30px"}}>Age</Typography>
|
||||
<div className="px-2">
|
||||
<Slider
|
||||
value={filters.age}
|
||||
onChange={(e, newValue) => {
|
||||
dispatch(setAge(newValue));
|
||||
handleSelectionChange("Age", newValue);
|
||||
}}
|
||||
valueLabelDisplay="on"
|
||||
min={18}
|
||||
max={70}
|
||||
/>
|
||||
<div className="flex justify-between text-sm text-gray-600">
|
||||
<span>18 Age</span>
|
||||
<span>70 Age</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Height Slider */}
|
||||
<div className="md:col-span-1">
|
||||
<Typography gutterBottom sx={{marginBottom:"30px"}}>Height</Typography>
|
||||
<div className="px-2">
|
||||
<Slider
|
||||
value={filters.height}
|
||||
onChange={(e, newValue) => {
|
||||
dispatch(setHeight(newValue));
|
||||
handleSelectionChange("Height", newValue);
|
||||
}}
|
||||
valueLabelDisplay="on"
|
||||
min={4.0}
|
||||
max={7.11}
|
||||
step={0.01}
|
||||
valueLabelFormat={(value) => `${value.toFixed(2)}'`}
|
||||
/>
|
||||
<div className="flex justify-between text-sm text-gray-600">
|
||||
<span>4'0"</span>
|
||||
<span>7'11"</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Marital Status */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Marital Status</InputLabel>
|
||||
<Select
|
||||
value={filters.maritalStatus}
|
||||
label="Marital Status"
|
||||
onChange={(e) => {
|
||||
dispatch(setMaritalStatus(e.target.value));
|
||||
handleSelectionChange("Marital Status", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="All Profile">All Profile</MenuItem>
|
||||
<MenuItem value="Never Married">Never Married</MenuItem>
|
||||
<MenuItem value="Divorced">Divorced</MenuItem>
|
||||
<MenuItem value="Widowed">Widowed</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Mother Tongue */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Mother Tongue</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={filters.motherTongue}
|
||||
label="Mother Tongue"
|
||||
onChange={(e) => {
|
||||
dispatch(setMotherTongue(e.target.value));
|
||||
handleSelectionChange("Mother Tongue", e.target.value);
|
||||
}}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} size="small" />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{motherTongueOptions.map((lang) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{lang}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Religious Details Section */}
|
||||
<Accordion
|
||||
expanded={expandedSections.religious}
|
||||
onChange={handleAccordionChange("religious")}
|
||||
className="mb-4"
|
||||
>
|
||||
<AccordionSummary expandIcon={<ChevronDown />}>
|
||||
<Typography className="font-semibold bg-gray-100 -mx-4 -my-2 px-4 py-2 w-full">
|
||||
Religious Details
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
{/* Religion */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Religion</InputLabel>
|
||||
<Select
|
||||
value={filters.religion}
|
||||
label="Religion"
|
||||
onChange={(e) => {
|
||||
dispatch(setReligion(e.target.value));
|
||||
handleSelectionChange("Religion", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Hindu">Hindu</MenuItem>
|
||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
||||
<MenuItem value="Christian">Christian</MenuItem>
|
||||
<MenuItem value="Sikh">Sikh</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Matches with Horoscope */}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={filters.matchesWithHoroscope}
|
||||
onChange={(e) => {
|
||||
dispatch(setMatchesWithHoroscope(e.target.checked));
|
||||
handleSelectionChange(
|
||||
"Matches with Horoscope",
|
||||
e.target.checked
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Matches with horoscope"
|
||||
/>
|
||||
|
||||
{/* Caste */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Caste (Multi Select)</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={filters.caste}
|
||||
label="Caste (Multi Select)"
|
||||
onChange={(e) => {
|
||||
dispatch(setCaste(e.target.value));
|
||||
handleSelectionChange("Caste", e.target.value);
|
||||
}}
|
||||
renderValue={(selected) => (
|
||||
<Box
|
||||
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
|
||||
>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} size="small" />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{["Agamudayar", "Pillai", "Vellalar"].map((caste) => (
|
||||
<MenuItem key={caste} value={caste}>
|
||||
{caste}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Sub-Caste */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sub-Caste (Multi Select)</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={filters.subCaste}
|
||||
label="Sub-Caste (Multi Select)"
|
||||
onChange={(e) => {
|
||||
dispatch(setSubCaste(e.target.value));
|
||||
handleSelectionChange("Sub-Caste", e.target.value);
|
||||
}}
|
||||
renderValue={(selected) => (
|
||||
<Box
|
||||
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
|
||||
>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} size="small" />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{["Agamudayar", "Pillai", "Vellalar"].map((caste) => (
|
||||
<MenuItem key={caste} value={caste}>
|
||||
{caste}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Star */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Star</InputLabel>
|
||||
<Select
|
||||
value={filters.star}
|
||||
label="Star"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ star: e.target.value }));
|
||||
handleSelectionChange("Star", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Ashwini">Ashwini</MenuItem>
|
||||
<MenuItem value="Bharani">Bharani</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Dasham */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Dasham</InputLabel>
|
||||
<Select
|
||||
value={filters.dasham}
|
||||
label="Dasham"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ dasham: e.target.value }));
|
||||
handleSelectionChange("Dasham", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Doesn't matter">Doesn't matter</MenuItem>
|
||||
<MenuItem value="Yes">Yes</MenuItem>
|
||||
<MenuItem value="No">No</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Professional Details */}
|
||||
<Accordion
|
||||
expanded={expandedSections.professional}
|
||||
onChange={handleAccordionChange("professional")}
|
||||
className="mb-4"
|
||||
>
|
||||
<AccordionSummary expandIcon={<ChevronDown />}>
|
||||
<Typography className="font-semibold bg-gray-100 -mx-4 -my-2 px-4 py-2 w-full">
|
||||
Professional Details
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Occupation</InputLabel>
|
||||
<Select
|
||||
value={filters.occupation}
|
||||
label="Occupation"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ occupation: e.target.value }));
|
||||
handleSelectionChange("Occupation", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Software Engineer">
|
||||
Software Engineer
|
||||
</MenuItem>
|
||||
<MenuItem value="Doctor">Doctor</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Annual Income</InputLabel>
|
||||
<Select
|
||||
value={filters.annualIncome}
|
||||
label="Annual Income"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ annualIncome: e.target.value }));
|
||||
handleSelectionChange("Annual Income", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="0-5 Lakhs">0-5 Lakhs</MenuItem>
|
||||
<MenuItem value="5-10 Lakhs">5-10 Lakhs</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Employee Type</InputLabel>
|
||||
<Select
|
||||
value={filters.employeeType}
|
||||
label="Employee Type"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ employeeType: e.target.value }));
|
||||
handleSelectionChange("Employee Type", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Government">Government</MenuItem>
|
||||
<MenuItem value="Private">Private</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Education</InputLabel>
|
||||
<Select
|
||||
value={filters.education}
|
||||
label="Education"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ education: e.target.value }));
|
||||
handleSelectionChange("Education", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Bachelor">Bachelor</MenuItem>
|
||||
<MenuItem value="Master">Master</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Location Details */}
|
||||
<Accordion
|
||||
expanded={expandedSections.location}
|
||||
onChange={handleAccordionChange("location")}
|
||||
className="mb-4"
|
||||
>
|
||||
<AccordionSummary expandIcon={<ChevronDown />}>
|
||||
<Typography className="font-semibold bg-gray-100 -mx-4 -my-2 px-4 py-2 w-full">
|
||||
Location Details
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>State</InputLabel>
|
||||
<Select
|
||||
value={filters.state}
|
||||
label="State"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ state: e.target.value }));
|
||||
handleSelectionChange("State", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Tamil Nadu">Tamil Nadu</MenuItem>
|
||||
<MenuItem value="Karnataka">Karnataka</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Country</InputLabel>
|
||||
<Select
|
||||
value={filters.country}
|
||||
label="Country"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ country: e.target.value }));
|
||||
handleSelectionChange("Country", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="India">India</MenuItem>
|
||||
<MenuItem value="USA">USA</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Citizenship</InputLabel>
|
||||
<Select
|
||||
value={filters.citizenship}
|
||||
label="Citizenship"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ citizenship: e.target.value }));
|
||||
handleSelectionChange("Citizenship", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Indian">Indian</MenuItem>
|
||||
<MenuItem value="US Citizen">US Citizen</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Lifestyle Details */}
|
||||
<Accordion
|
||||
expanded={expandedSections.lifestyle}
|
||||
onChange={handleAccordionChange("lifestyle")}
|
||||
className="mb-4"
|
||||
>
|
||||
<AccordionSummary expandIcon={<ChevronDown />}>
|
||||
<Typography className="font-semibold bg-gray-100 -mx-4 -my-2 px-4 py-2 w-full">
|
||||
Lifestyle Details
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Eating Habits</InputLabel>
|
||||
<Select
|
||||
value={filters.eatingHabits}
|
||||
label="Eating Habits"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ eatingHabits: e.target.value }));
|
||||
handleSelectionChange("Eating Habits", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Vegetarian">Vegetarian</MenuItem>
|
||||
<MenuItem value="Non-Vegetarian">Non-Vegetarian</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Smoking Habits</InputLabel>
|
||||
<Select
|
||||
value={filters.smokingHabits}
|
||||
label="Smoking Habits"
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
updateFilter({ smokingHabits: e.target.value })
|
||||
);
|
||||
handleSelectionChange("Smoking Habits", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Doesn't matter">Doesn't matter</MenuItem>
|
||||
<MenuItem value="Non-Smoker">Non-Smoker</MenuItem>
|
||||
<MenuItem value="Smoker">Smoker</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Drinking Habits</InputLabel>
|
||||
<Select
|
||||
value={filters.drinkingHabits}
|
||||
label="Drinking Habits"
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
updateFilter({ drinkingHabits: e.target.value })
|
||||
);
|
||||
handleSelectionChange("Drinking Habits", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Doesn't matter">Doesn't matter</MenuItem>
|
||||
<MenuItem value="Non-Drinker">Non-Drinker</MenuItem>
|
||||
<MenuItem value="Social Drinker">Social Drinker</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Family Details */}
|
||||
<Accordion
|
||||
expanded={expandedSections.family}
|
||||
onChange={handleAccordionChange("family")}
|
||||
className="mb-4"
|
||||
>
|
||||
<AccordionSummary expandIcon={<ChevronDown />}>
|
||||
<Typography className="font-semibold bg-gray-100 -mx-4 -my-2 px-4 py-2 w-full">
|
||||
Family Details
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Family Type</InputLabel>
|
||||
<Select
|
||||
value={filters.familyType}
|
||||
label="Family Type"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ familyType: e.target.value }));
|
||||
handleSelectionChange("Family Type", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Nuclear">Nuclear</MenuItem>
|
||||
<MenuItem value="Joint">Joint</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Family Status</InputLabel>
|
||||
<Select
|
||||
value={filters.familyStatus}
|
||||
label="Family Status"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ familyStatus: e.target.value }));
|
||||
handleSelectionChange("Family Status", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Middle Class">Middle Class</MenuItem>
|
||||
<MenuItem value="Upper Middle Class">
|
||||
Upper Middle Class
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Family Value</InputLabel>
|
||||
<Select
|
||||
value={filters.familyValue}
|
||||
label="Family Value"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ familyValue: e.target.value }));
|
||||
handleSelectionChange("Family Value", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Traditional">Traditional</MenuItem>
|
||||
<MenuItem value="Moderate">Moderate</MenuItem>
|
||||
<MenuItem value="Liberal">Liberal</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-6 flex justify-center">
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={handleSubmit}
|
||||
className="px-12"
|
||||
>
|
||||
Apply Filters
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterForm;
|
||||
50
src/feature/FilterModal.jsx
Normal file
50
src/feature/FilterModal.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
// FilterModal.jsx
|
||||
import React, { useState } from "react";
|
||||
import { IconButton, Dialog, DialogTitle, DialogContent, Box } from "@mui/material";
|
||||
import { X, SlidersHorizontal } from "lucide-react";
|
||||
import FilterForm from "./FilterForm";
|
||||
|
||||
const FilterModal = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Filter Icon Button (place this in your page header / toolbar) */}
|
||||
<IconButton
|
||||
onClick={handleOpen}
|
||||
aria-label="open filters"
|
||||
color="primary"
|
||||
size="large"
|
||||
>
|
||||
<SlidersHorizontal size={22} />
|
||||
</IconButton>
|
||||
|
||||
{/* Modal */}
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
fullWidth
|
||||
maxWidth="lg" // adjust: "sm" | "md" | "lg"
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<span>Filter Options</span>
|
||||
<IconButton onClick={handleClose} aria-label="close">
|
||||
<X size={20} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent dividers>
|
||||
{/* Use your existing FilterForm here */}
|
||||
<FilterForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterModal;
|
||||
116
src/feature/LifestyleDetailsForm.jsx
Normal file
116
src/feature/LifestyleDetailsForm.jsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateLifestyleDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
|
||||
const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Submitting lifestyle details:", data);
|
||||
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"],
|
||||
},
|
||||
];
|
||||
|
||||
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])}
|
||||
>
|
||||
<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}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors[name] && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors[name]}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
marginTop: 24,
|
||||
display: "flex",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Next
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LifestyleDetailsForm;
|
||||
308
src/feature/PartnerPreferencesForm.jsx
Normal file
308
src/feature/PartnerPreferencesForm.jsx
Normal file
@ -0,0 +1,308 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updatePartnerPreferences } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
|
||||
const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.partnerPreferences);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updatePartnerPreferences({ [field]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Submitting partner preferences:", data);
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
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 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Preferred Age Range
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.ageRange)}
|
||||
>
|
||||
<InputLabel id="ageRange-label">
|
||||
Select Preferred Age Range
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="ageRange-label"
|
||||
label="Select Preferred Age Range"
|
||||
name="ageRange"
|
||||
value={data.ageRange}
|
||||
onChange={(e) => handleChange("ageRange", e.target.value)}
|
||||
displayEmpty
|
||||
inputRef={inputRef}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select Preferred Age Range</em>
|
||||
</MenuItem>
|
||||
<MenuItem value="25 - 28">25 - 28</MenuItem>
|
||||
<MenuItem value="29 - 32">29 - 32</MenuItem>
|
||||
<MenuItem value="33 - 36">33 - 36</MenuItem>
|
||||
</Select>
|
||||
{errors.ageRange && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.ageRange}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Religion / Caste Preference */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Religion / Caste Preference
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.religionCaste)}
|
||||
>
|
||||
<InputLabel id="religionCaste-label">
|
||||
Select Religion / Caste Preference
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="religionCaste-label"
|
||||
label="Select Religion / Caste Preference"
|
||||
name="religionCaste"
|
||||
value={data.religionCaste}
|
||||
onChange={(e) =>
|
||||
handleChange("religionCaste", e.target.value)
|
||||
}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select Religion / Caste Preference</em>
|
||||
</MenuItem>
|
||||
<MenuItem value="Hindu">Hindu</MenuItem>
|
||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
||||
<MenuItem value="Christian">Christian</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
</Select>
|
||||
{errors.religionCaste && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.religionCaste}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Occupation Preference */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation Preference
|
||||
</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)
|
||||
}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select Occupation Preference</em>
|
||||
</MenuItem>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* Lifestyle & Habits Preference */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Lifestyle & Habits Preference
|
||||
</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)
|
||||
}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select Lifestyle & Habits Preference</em>
|
||||
</MenuItem>
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
marginTop: 24,
|
||||
display: "flex",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Next
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PartnerPreferencesForm;
|
||||
777
src/feature/PersonalDetailsForm.jsx
Normal file
777
src/feature/PersonalDetailsForm.jsx
Normal file
@ -0,0 +1,777 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
Link,
|
||||
} from "@mui/material";
|
||||
import AdvancedDropzone from "./AdvancedDropzone";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
const OTP_LENGTH = 4;
|
||||
const OTP_TIMER_SEC = 120; // 2 minutes
|
||||
|
||||
const PersonalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.personalDetails);
|
||||
const nameInputRef = useRef(null);
|
||||
|
||||
const [showOtp, setShowOtp] = useState(false);
|
||||
const [otp, setOtp] = useState(new Array(OTP_LENGTH).fill(""));
|
||||
const [otpTimer, setOtpTimer] = useState(0);
|
||||
const [otpError, setOtpError] = useState("");
|
||||
const [mobileOtpVerified, setMobileOtpVerified] = useState(false);
|
||||
const [mobileNumberError, setMobileNumberError] = useState("");
|
||||
|
||||
const startOtpTimer = useCallback(() => {
|
||||
setOtpTimer(OTP_TIMER_SEC);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (otpTimer <= 0) return;
|
||||
const timerId = setInterval(() => {
|
||||
setOtpTimer((sec) => sec - 1);
|
||||
}, 1000);
|
||||
return () => clearInterval(timerId);
|
||||
}, [otpTimer]);
|
||||
|
||||
useEffect(() => {
|
||||
nameInputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleOtpChange = (index, value) => {
|
||||
if (!/^\d*$/.test(value)) return;
|
||||
const newOtp = [...otp];
|
||||
newOtp[index] = value.slice(-1);
|
||||
setOtp(newOtp);
|
||||
if (value && index < OTP_LENGTH - 1) {
|
||||
const next = document.getElementById(`otp-${index + 1}`);
|
||||
if (next) next.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const resetOtp = () => {
|
||||
setOtp(new Array(OTP_LENGTH).fill(""));
|
||||
setOtpError("");
|
||||
startOtpTimer();
|
||||
};
|
||||
|
||||
const handleMobileSubmit = () => {
|
||||
if (!data.mobileNumber || data.mobileNumber.length !== 10) {
|
||||
setMobileNumberError(
|
||||
"Please enter a valid 10-digit mobile number before sending OTP"
|
||||
);
|
||||
return;
|
||||
}
|
||||
setMobileNumberError("");
|
||||
setShowOtp(true);
|
||||
resetOtp();
|
||||
setMobileOtpVerified(false);
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updatePersonalDetails({ [field]: value }));
|
||||
if (field === "mobileNumber") {
|
||||
setMobileNumberError("");
|
||||
setShowOtp(false);
|
||||
setOtp(new Array(OTP_LENGTH).fill(""));
|
||||
setOtpError("");
|
||||
setOtpTimer(0);
|
||||
setMobileOtpVerified(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isOtpComplete = otp.every((digit) => digit !== "");
|
||||
|
||||
// Simulated API OTP verification (replace with actual)
|
||||
const verifyOtpApi = async (mobile, otpValue) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (otpValue === "1234") resolve(true);
|
||||
else reject("Invalid OTP");
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
const handleOtpSubmit = async () => {
|
||||
if (!isOtpComplete) {
|
||||
setOtpError("Complete OTP is required");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await verifyOtpApi(data.mobileNumber, otp.join(""));
|
||||
setMobileOtpVerified(true);
|
||||
setMobileNumberError("");
|
||||
} catch (error) {
|
||||
setOtpError(error || "OTP verification failed");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// file upload
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// const handleSubmit = async () => {
|
||||
// if (showOtp && !isOtpComplete) {
|
||||
// setOtpError("OTP is required and must be complete");
|
||||
// return;
|
||||
// }
|
||||
// if (showOtp && !mobileOtpVerified) {
|
||||
// try {
|
||||
// await verifyOtpApi(data.mobileNumber, otp.join(""));
|
||||
// setMobileOtpVerified(true);
|
||||
// setOtpError("");
|
||||
// onSubmitStep();
|
||||
// console.log("OTP verified on submit");
|
||||
// } catch (err) {
|
||||
// setOtpError(err || "OTP verification failed");
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// onSubmitStep();
|
||||
|
||||
// };
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (showOtp && !isOtpComplete) {
|
||||
setOtpError("OTP is required and must be complete");
|
||||
return;
|
||||
}
|
||||
|
||||
if (showOtp && !mobileOtpVerified) {
|
||||
try {
|
||||
await verifyOtpApi(data.mobileNumber, otp.join(""));
|
||||
setMobileOtpVerified(true);
|
||||
setOtpError("");
|
||||
console.log("Submitting personal details:", data); // log here
|
||||
onSubmitStep();
|
||||
console.log("OTP verified on submit");
|
||||
} catch (err) {
|
||||
setOtpError(err || "OTP verification failed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// no OTP or already verified
|
||||
console.log("Submitting personal details:", data); // log here
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
|
||||
const formatTimer = (sec) => {
|
||||
const m = Math.floor(sec / 60)
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
const s = (sec % 60).toString().padStart(2, "0");
|
||||
return `${m}:${s}`;
|
||||
};
|
||||
const parseDobValue = data.dob ? new Date(data.dob) : null;
|
||||
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">
|
||||
{/* Name */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[17px]">
|
||||
Enter the Name
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={nameInputRef}
|
||||
name="name"
|
||||
label="Name"
|
||||
value={data.name}
|
||||
onChange={(e) => handleChange("name", e.target.value)}
|
||||
error={Boolean(errors.name)}
|
||||
helperText={errors.name}
|
||||
placeholder="Enter Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Gender */}
|
||||
<div className="flex flex-col gap-6">
|
||||
<label className="text-gray-900 text-[17px]">
|
||||
Enter the Gender
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.gender)}
|
||||
>
|
||||
<InputLabel id="gender-label">Gender</InputLabel>
|
||||
<Select
|
||||
labelId="gender-label"
|
||||
label="Gender"
|
||||
name="gender"
|
||||
value={data.gender}
|
||||
onChange={(e) => handleChange("gender", e.target.value)}
|
||||
>
|
||||
<MenuItem value="Male">Male</MenuItem>
|
||||
<MenuItem value="Female">Female</MenuItem>
|
||||
</Select>
|
||||
{errors.gender && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.gender}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Mobile Number and Send OTP Button */}
|
||||
<div className="flex flex-col gap-6 relative">
|
||||
<TextField
|
||||
fullWidth
|
||||
name="mobileNumber"
|
||||
label="Mobile Number"
|
||||
type="tel"
|
||||
value={data.mobileNumber}
|
||||
onChange={(e) => handleChange("mobileNumber", e.target.value)}
|
||||
error={
|
||||
Boolean(errors.mobileNumber) || Boolean(mobileNumberError)
|
||||
}
|
||||
helperText={mobileNumberError || errors.mobileNumber}
|
||||
placeholder="Enter Mobile Number"
|
||||
inputProps={{ maxLength: 10 }}
|
||||
variant="outlined"
|
||||
disabled={mobileOtpVerified}
|
||||
// sx={{ maxWidth: "430px" }}
|
||||
/>
|
||||
<Box sx={{ position: "absolute", right: 0 }}>
|
||||
{!showOtp && !mobileOtpVerified && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={handleMobileSubmit}
|
||||
sx={{
|
||||
padding: "15px 10px",
|
||||
background: "green",
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
Send OTP
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
{/* OTP Inputs */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{showOtp && !mobileOtpVerified && (
|
||||
<>
|
||||
<Box display="flex" gap={1} alignItems="center">
|
||||
{otp.map((digit, index) => (
|
||||
<TextField
|
||||
key={index}
|
||||
id={`otp-${index}`}
|
||||
inputProps={{
|
||||
maxLength: 1,
|
||||
style: {
|
||||
textAlign: "center",
|
||||
width: 40,
|
||||
fontSize: 20,
|
||||
},
|
||||
}}
|
||||
value={digit}
|
||||
onChange={(e) => handleOtpChange(index, e.target.value)}
|
||||
error={Boolean(otpError)}
|
||||
autoFocus={index === 0}
|
||||
variant="outlined"
|
||||
/>
|
||||
))}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={handleOtpSubmit} // new handler to verify OTP
|
||||
sx={{ height: 40, ml: 2 }}
|
||||
disabled={!isOtpComplete}
|
||||
>
|
||||
Submit OTP
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography sx={{ ml: 2, minWidth: 56 }}>
|
||||
{otpTimer > 0 ? (
|
||||
formatTimer(otpTimer)
|
||||
) : (
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
onClick={resetOtp}
|
||||
disabled={otpTimer > 0}
|
||||
>
|
||||
Resend OTP
|
||||
</Link>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
{otpError && (
|
||||
<Typography color="error" variant="caption" sx={{ mt: 1 }}>
|
||||
{otpError}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{mobileOtpVerified && (
|
||||
<Typography
|
||||
color="primary"
|
||||
variant="body2"
|
||||
sx={{
|
||||
background: "green",
|
||||
padding: "17px 15px",
|
||||
width: "fit-content",
|
||||
borderRadius: "5px",
|
||||
color: "#fff",
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
Mobile number verified
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Other fields like DOB, height, marital status, etc. */}
|
||||
{/* Your other inputs here */}
|
||||
{/* DOB */}
|
||||
{/* <div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Date of Birth</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="dob"
|
||||
type="date"
|
||||
value={data.dob}
|
||||
onChange={(e) => handleChange("dob", e.target.value)}
|
||||
error={Boolean(errors.dob)}
|
||||
helperText={errors.dob}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div> */}
|
||||
|
||||
{/* DOB with MUI DatePicker */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Date of Birth</label>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<DatePicker
|
||||
format="dd/MM/yyyy"
|
||||
value={parseDobValue}
|
||||
onChange={(value) => {
|
||||
let formatted = "";
|
||||
if (value instanceof Date && !isNaN(value)) {
|
||||
const y = value.getFullYear();
|
||||
const m = String(value.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(value.getDate()).padStart(2, "0");
|
||||
formatted = `${y}-${m}-${d}`;
|
||||
}
|
||||
handleChange("dob", formatted);
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.dob),
|
||||
helperText: errors.dob,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
{/* Height */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Height</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="height"
|
||||
label="Enter Height"
|
||||
value={data.height}
|
||||
onChange={(e) => handleChange("height", e.target.value)}
|
||||
error={Boolean(errors.height)}
|
||||
helperText={errors.height}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Weight */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Weight</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="weight"
|
||||
label="Enter Weight"
|
||||
value={data.weight}
|
||||
onChange={(e) => handleChange("weight", e.target.value)}
|
||||
error={Boolean(errors.weight)}
|
||||
helperText={errors.weight}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Marital Status */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Marital Status</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.maritalStatus)}
|
||||
>
|
||||
<InputLabel id="maritalStatus-label">
|
||||
Select Marital Status
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="maritalStatus-label"
|
||||
label="Select Marital Status"
|
||||
name="maritalStatus"
|
||||
value={data.maritalStatus}
|
||||
onChange={(e) =>
|
||||
handleChange("maritalStatus", e.target.value)
|
||||
}
|
||||
>
|
||||
<MenuItem value="Single">Single</MenuItem>
|
||||
<MenuItem value="Divorced">Divorced</MenuItem>
|
||||
<MenuItem value="Widowed">Widowed</MenuItem>
|
||||
</Select>
|
||||
{errors.maritalStatus && (
|
||||
<Typography
|
||||
color="error"
|
||||
variant="caption"
|
||||
sx={{ mt: 0.5, ml: 1.8 }}
|
||||
>
|
||||
{errors.maritalStatus}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Religion */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Religion</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.religion)}
|
||||
>
|
||||
<InputLabel id="religion-label">Select Religion</InputLabel>
|
||||
<Select
|
||||
labelId="religion-label"
|
||||
label="Select Religion"
|
||||
name="religion"
|
||||
value={data.religion}
|
||||
onChange={(e) => handleChange("religion", e.target.value)}
|
||||
>
|
||||
<MenuItem value="Hindu">Hindu</MenuItem>
|
||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
||||
<MenuItem value="Christian">Christian</MenuItem>
|
||||
<MenuItem value="Others">Others</MenuItem>
|
||||
</Select>
|
||||
{errors.religion && (
|
||||
<Typography
|
||||
color="error"
|
||||
variant="caption"
|
||||
sx={{ mt: 0.5, ml: 1.8 }}
|
||||
>
|
||||
{errors.religion}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Caste / Community */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Caste / Community
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.caste)}
|
||||
>
|
||||
<InputLabel id="caste-label">Select Caste / Community</InputLabel>
|
||||
<Select
|
||||
labelId="caste-label"
|
||||
label="Select Caste / Community"
|
||||
name="caste"
|
||||
value={data.caste}
|
||||
onChange={(e) => handleChange("caste", e.target.value)}
|
||||
>
|
||||
<MenuItem value="Agamudayar">Agamudayar</MenuItem>
|
||||
<MenuItem value="Brahmin">Brahmin</MenuItem>
|
||||
<MenuItem value="Others">Others</MenuItem>
|
||||
</Select>
|
||||
{errors.caste && (
|
||||
<Typography
|
||||
color="error"
|
||||
variant="caption"
|
||||
sx={{ mt: 0.5, ml: 1.8 }}
|
||||
>
|
||||
{errors.caste}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Sub-Caste (optional) */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Sub-Caste (optional)
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="subCaste-label">
|
||||
Select Sub-Caste (optional)
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="subCaste-label"
|
||||
label="Select Sub-Caste (optional)"
|
||||
name="subCaste"
|
||||
value={data.subCaste}
|
||||
onChange={(e) => handleChange("subCaste", e.target.value)}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value="Thuluva vellala">Thuluva vellala</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Gothram (optional) */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Gothram (optional)
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="gothram-label">
|
||||
Select Gothram (optional)
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="gothram-label"
|
||||
label="Select Gothram (optional)"
|
||||
name="gothram"
|
||||
value={data.gothram}
|
||||
onChange={(e) => handleChange("gothram", e.target.value)}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value="Siva Gotheram">Siva Gotheram</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Blood Group (optional) */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Blood Group (optional)
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="blood-label">
|
||||
Select Blood Group (optional)
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="blood-label"
|
||||
label="Select Blood Group (optional)"
|
||||
name="bloodGroup"
|
||||
value={data.bloodGroup}
|
||||
onChange={(e) => handleChange("bloodGroup", e.target.value)}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
<MenuItem value="O+">O+</MenuItem>
|
||||
<MenuItem value="A+">A+</MenuItem>
|
||||
<MenuItem value="B+">B+</MenuItem>
|
||||
<MenuItem value="AB+">AB+</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Email Id */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Email Id</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="email"
|
||||
label="Enter Email Id"
|
||||
value={data.email}
|
||||
onChange={(e) => handleChange("email", e.target.value)}
|
||||
error={Boolean(errors.email)}
|
||||
helperText={errors.email}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* State */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">State</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.state)}
|
||||
>
|
||||
<InputLabel id="state-label">Select State</InputLabel>
|
||||
<Select
|
||||
labelId="state-label"
|
||||
label="Select State"
|
||||
name="state"
|
||||
value={data.state}
|
||||
onChange={(e) => handleChange("state", e.target.value)}
|
||||
>
|
||||
<MenuItem value="Tamil Nadu">Tamil Nadu</MenuItem>
|
||||
<MenuItem value="Kerala">Kerala</MenuItem>
|
||||
<MenuItem value="Karnataka">Karnataka</MenuItem>
|
||||
</Select>
|
||||
{errors.state && (
|
||||
<Typography
|
||||
color="error"
|
||||
variant="caption"
|
||||
sx={{ mt: 0.5, ml: 1.8 }}
|
||||
>
|
||||
{errors.state}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* City */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">City</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.city)}
|
||||
>
|
||||
<InputLabel id="city-label">Select City</InputLabel>
|
||||
<Select
|
||||
labelId="city-label"
|
||||
label="Select City"
|
||||
name="city"
|
||||
value={data.city}
|
||||
onChange={(e) => handleChange("city", e.target.value)}
|
||||
>
|
||||
<MenuItem value="Chennai">Chennai</MenuItem>
|
||||
<MenuItem value="Coimbatore">Coimbatore</MenuItem>
|
||||
<MenuItem value="Madurai">Madurai</MenuItem>
|
||||
</Select>
|
||||
{errors.city && (
|
||||
<Typography
|
||||
color="error"
|
||||
variant="caption"
|
||||
sx={{ mt: 0.5, ml: 1.8 }}
|
||||
>
|
||||
{errors.city}
|
||||
</Typography>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Pin code */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Pin code</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="pincode"
|
||||
label="Enter Pin code"
|
||||
value={data.pincode}
|
||||
onChange={(e) => handleChange("pincode", e.target.value)}
|
||||
error={Boolean(errors.pincode)}
|
||||
helperText={errors.pincode}
|
||||
inputProps={{ maxLength: 6 }}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Upload Profile (UI only) */}
|
||||
{/* <div className="flex flex-col gap-2 md:col-span-2 w-full max-w-[900px] ">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Upload Profile (Multi - Select)
|
||||
</label>
|
||||
<div className=" border-2 border-dashed border-gray-300 rounded-lg p-6 text-center bg-white">
|
||||
<div className="text-3xl mb-2">⬆</div>
|
||||
<p className="text-sm text-gray-600">Upload PDF, IMG, JPG</p>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
<div className="flex flex-col gap-2 md:col-span-2 w-full">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Upload Profile (Multi - Select)
|
||||
</label>
|
||||
<AdvancedDropzone
|
||||
value={data.profiles || []}
|
||||
onChange={(files) => {
|
||||
// if you want to keep in Redux as plain metadata
|
||||
dispatch(updatePersonalDetails({ profiles: files }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
marginTop: "40px",
|
||||
display: "flex",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={onSkipStep}
|
||||
disabled={mobileOtpVerified}
|
||||
>
|
||||
Skip
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleSubmit}
|
||||
// disabled={!mobileOtpVerified}
|
||||
>
|
||||
Submit & Next
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonalDetailsForm;
|
||||
124
src/feature/PreviewScreen.jsx
Normal file
124
src/feature/PreviewScreen.jsx
Normal file
@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Edit2 } from 'lucide-react';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Typography,
|
||||
Box,
|
||||
Divider,
|
||||
IconButton,
|
||||
Button,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
|
||||
const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
const formData = useSelector((state) => state.registerform);
|
||||
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box p={3} maxWidth={720} mx="auto" minHeight="100vh" bgcolor="#f9fafb">
|
||||
<Grid container spacing={3}>
|
||||
{sections.map((section) => (
|
||||
<Grid item xs={12} key={section.title}>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{section.title}
|
||||
</Typography>
|
||||
}
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
color="primary"
|
||||
onClick={() => onEdit(section.step)}
|
||||
size="large"
|
||||
>
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ pb: 0 }}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{Object.entries(section.data).map(([key, value]) => {
|
||||
if (value && key !== 'profiles') {
|
||||
// Convert camelCase or camel_Snake_case to readable words
|
||||
const formattedKey = key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
||||
.trim();
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={key}
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
py={0.7}
|
||||
borderBottom="1px solid #e0e0e0"
|
||||
>
|
||||
<Typography color="text.secondary" sx={{ fontWeight: 500 }}>
|
||||
{formattedKey}:
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{ fontWeight: 600, ml: 2, maxWidth: '60%', wordBreak: 'break-word' }}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
fullWidth
|
||||
size="large"
|
||||
onClick={onSubmit}
|
||||
sx={{ borderRadius: 2 }}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewScreen;
|
||||
329
src/feature/StepperForm.jsx
Normal file
329
src/feature/StepperForm.jsx
Normal file
@ -0,0 +1,329 @@
|
||||
import React, { useState } from "react";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
updatePersonalDetails,
|
||||
updateEducationalDetails,
|
||||
updateFamilyDetails,
|
||||
updateLifestyleDetails,
|
||||
updatePartnerPreferences,
|
||||
submitForm,
|
||||
} from "../redux/registrationFormSlice";
|
||||
import PersonalDetailsForm from "./PersonalDetailsForm";
|
||||
import EducationalDetailsForm from "./EducationalDetailsForm";
|
||||
import FamilyDetailsForm from "./FamilyDetailsForm";
|
||||
import LifestyleDetailsForm from "./LifestyleDetailsForm";
|
||||
import PartnerPreferencesForm from "./PartnerPreferencesForm";
|
||||
import PreviewScreen from "./PreviewScreen";
|
||||
|
||||
const Stepper = ({ currentStep, onStepClick }) => {
|
||||
const steps = [
|
||||
{ num: 1, label: "Personal" },
|
||||
{ num: 2, label: "Educational" },
|
||||
{ num: 3, label: "Family" },
|
||||
{ num: 4, label: "Lifestyle" },
|
||||
{ num: 5, label: "Partner" },
|
||||
{ num: 6, label: "Preview" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4 py-6">
|
||||
{steps.map((step, index) => (
|
||||
<React.Fragment key={step.num}>
|
||||
<div
|
||||
className="flex flex-col items-center cursor-pointer"
|
||||
onClick={() => onStepClick(step.num)}
|
||||
>
|
||||
<div
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
|
||||
currentStep >= step.num
|
||||
? "bg-red-600 text-white"
|
||||
: "bg-gray-300 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{step.num}
|
||||
</div>
|
||||
</div>
|
||||
{index < steps.length - 1 && (
|
||||
<div
|
||||
className={`flex-1 h-0.5 mx-1 ${
|
||||
currentStep > step.num ? "bg-red-600" : "bg-gray-300"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StepperForm = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const personalDetails = useSelector(
|
||||
(state) => state.registerform.personalDetails
|
||||
);
|
||||
const educationalDetails = useSelector(
|
||||
(state) => state.registerform.educationalDetails
|
||||
);
|
||||
const familyDetails = useSelector((state) => state.registerform.familyDetails);
|
||||
const lifestyleDetails = useSelector(
|
||||
(state) => state.registerform.lifestyleDetails
|
||||
);
|
||||
const partnerPreferences = useSelector(
|
||||
(state) => state.registerform.partnerPreferences
|
||||
);
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const validateStep = (step) => {
|
||||
const newErrors = {};
|
||||
|
||||
if (step === 1) {
|
||||
const required = [
|
||||
"name",
|
||||
"mobileNumber",
|
||||
"gender",
|
||||
"dob",
|
||||
"height",
|
||||
"weight",
|
||||
"maritalStatus",
|
||||
"religion",
|
||||
"caste",
|
||||
"email",
|
||||
"state",
|
||||
"city",
|
||||
"pincode",
|
||||
];
|
||||
|
||||
required.forEach((field) => {
|
||||
if (!personalDetails[field]) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
personalDetails.email &&
|
||||
!/\S+@\S+\.\S+/.test(personalDetails.email)
|
||||
) {
|
||||
newErrors.email = "Invalid email format";
|
||||
}
|
||||
if (
|
||||
personalDetails.mobileNumber &&
|
||||
personalDetails.mobileNumber.length !== 10
|
||||
) {
|
||||
newErrors.mobileNumber = "Mobile number must be 10 digits";
|
||||
}
|
||||
} else if (step === 2) {
|
||||
const required = [
|
||||
"qualification",
|
||||
"fieldOfStudy",
|
||||
"occupation",
|
||||
"organization",
|
||||
"income",
|
||||
"workLocation",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!educationalDetails[field]) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
} else if (step === 3) {
|
||||
const required = [
|
||||
"fatherName",
|
||||
"fatherOccupation",
|
||||
"motherName",
|
||||
"motherOccupation",
|
||||
"siblings",
|
||||
"siblingsStatus",
|
||||
"familyType",
|
||||
"nativePlace",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!familyDetails[field]) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
} else if (step === 4) {
|
||||
const required = ["diet", "drinking", "smoking", "hobbies"];
|
||||
required.forEach((field) => {
|
||||
if (!lifestyleDetails[field]) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
} else if (step === 5) {
|
||||
const required = [
|
||||
"ageRange",
|
||||
"religionCaste",
|
||||
"occupationPref",
|
||||
"lifestylePref",
|
||||
"qualificationPref",
|
||||
"occupationText",
|
||||
"incomePref",
|
||||
"locationPref",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!partnerPreferences[field]) {
|
||||
newErrors[field] = "This field is required";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
// API call simulation for each step submit
|
||||
const submitStepAPI = async (step) => {
|
||||
// Replace with actual API call
|
||||
return new Promise((resolve) => setTimeout(resolve, 500));
|
||||
};
|
||||
|
||||
const handleStepSubmit = async () => {
|
||||
const isValid = validateStep(currentStep);
|
||||
if (!isValid) return;
|
||||
|
||||
try {
|
||||
await submitStepAPI(currentStep);
|
||||
setCurrentStep((prev) => Math.min(prev + 1, 6));
|
||||
window.scrollTo(0, 0);
|
||||
} catch (e) {
|
||||
alert("Failed to submit step. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
setErrors({});
|
||||
setCurrentStep((prev) => Math.min(prev + 1, 6));
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
const handleStepClick = (step) => {
|
||||
setCurrentStep(step);
|
||||
setErrors({});
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
const handleEdit = (step) => {
|
||||
setCurrentStep(step);
|
||||
setErrors({});
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
const handleFinalSubmit = async () => {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const ok = validateStep(i);
|
||||
if (!ok) {
|
||||
setCurrentStep(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// final combined API call - replace with your final API
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
alert("Form submitted successfully!");
|
||||
} catch (e) {
|
||||
alert("Failed to submit form.");
|
||||
}
|
||||
};
|
||||
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<PersonalDetailsForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<EducationalDetailsForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<FamilyDetailsForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<LifestyleDetailsForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<PartnerPreferencesForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
case 6:
|
||||
return <PreviewScreen onEdit={handleEdit} onSubmit={handleFinalSubmit} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getTitle = () => {
|
||||
const titles = {
|
||||
1: "Personal Details",
|
||||
2: "Educational & Professional Details",
|
||||
3: "Family Details",
|
||||
4: "Lifestyle & Habits",
|
||||
5: "Partner Preferences",
|
||||
6: "Details Preview",
|
||||
};
|
||||
return titles[currentStep] || "";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="max-w-[1400px] mx-auto bg-white ">
|
||||
{/* Header */}
|
||||
<div className="my-4 rounded-[10px] py-4 bg-[#e3ffed] w-full max-w-[1200px] mx-auto">
|
||||
|
||||
<Stepper currentStep={currentStep} onStepClick={handleStepClick} />
|
||||
<div className="flex items-center p-4 justify-center">
|
||||
{currentStep > 1 && currentStep < 6 && (
|
||||
<button
|
||||
onClick={() => setCurrentStep((prev) => prev - 1)}
|
||||
className="mr-3"
|
||||
>
|
||||
<ChevronLeft size={24} />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-[24px] font-semibold text-center uppercase bg-[#fff2f2] py-2 px-3 rounded-5">{getTitle()}</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="pb-6">{renderStepContent()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepperForm;
|
||||
@ -5,10 +5,15 @@ import "./index.css";
|
||||
import App from "./App.jsx";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import theme from "./theme";
|
||||
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store.js";
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
140
src/redux/filterSlice.jsx
Normal file
140
src/redux/filterSlice.jsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
age: [18, 70],
|
||||
height: [4.0, 7.11],
|
||||
maritalStatus: "All Profile",
|
||||
motherTongue: [],
|
||||
religion: "Hindu",
|
||||
matchesWithHoroscope: false,
|
||||
caste: [],
|
||||
subCaste: [],
|
||||
star: "Any",
|
||||
dasham: "Doesn't matter",
|
||||
occupation: "Any",
|
||||
annualIncome: "Any",
|
||||
employeeType: "Any",
|
||||
education: "Any",
|
||||
state: "Any",
|
||||
country: "Any",
|
||||
citizenship: "Any",
|
||||
eatingHabits: "Any",
|
||||
smokingHabits: "Doesn't matter",
|
||||
drinkingHabits: "Doesn't matter",
|
||||
familyType: "Any",
|
||||
familyStatus: "Any",
|
||||
familyValue: "Any",
|
||||
};
|
||||
|
||||
const filterSlice = createSlice({
|
||||
name: "filters",
|
||||
initialState,
|
||||
reducers: {
|
||||
setAge: (state, action) => {
|
||||
state.age = action.payload;
|
||||
},
|
||||
setHeight: (state, action) => {
|
||||
state.height = action.payload;
|
||||
},
|
||||
setMaritalStatus: (state, action) => {
|
||||
state.maritalStatus = action.payload;
|
||||
},
|
||||
setMotherTongue: (state, action) => {
|
||||
state.motherTongue = action.payload;
|
||||
},
|
||||
setReligion: (state, action) => {
|
||||
state.religion = action.payload;
|
||||
},
|
||||
setMatchesWithHoroscope: (state, action) => {
|
||||
state.matchesWithHoroscope = action.payload;
|
||||
},
|
||||
setCaste: (state, action) => {
|
||||
state.caste = action.payload;
|
||||
},
|
||||
setSubCaste: (state, action) => {
|
||||
state.subCaste = action.payload;
|
||||
},
|
||||
setStar: (state, action) => {
|
||||
state.star = action.payload;
|
||||
},
|
||||
setDasham: (state, action) => {
|
||||
state.dasham = action.payload;
|
||||
},
|
||||
setOccupation: (state, action) => {
|
||||
state.occupation = action.payload;
|
||||
},
|
||||
setAnnualIncome: (state, action) => {
|
||||
state.annualIncome = action.payload;
|
||||
},
|
||||
setEmployeeType: (state, action) => {
|
||||
state.employeeType = action.payload;
|
||||
},
|
||||
setEducation: (state, action) => {
|
||||
state.education = action.payload;
|
||||
},
|
||||
setState: (state, action) => {
|
||||
state.state = action.payload;
|
||||
},
|
||||
setCountry: (state, action) => {
|
||||
state.country = action.payload;
|
||||
},
|
||||
setCitizenship: (state, action) => {
|
||||
state.citizenship = action.payload;
|
||||
},
|
||||
setEatingHabits: (state, action) => {
|
||||
state.eatingHabits = action.payload;
|
||||
},
|
||||
setSmokingHabits: (state, action) => {
|
||||
state.smokingHabits = action.payload;
|
||||
},
|
||||
setDrinkingHabits: (state, action) => {
|
||||
state.drinkingHabits = action.payload;
|
||||
},
|
||||
setFamilyType: (state, action) => {
|
||||
state.familyType = action.payload;
|
||||
},
|
||||
setFamilyStatus: (state, action) => {
|
||||
state.familyStatus = action.payload;
|
||||
},
|
||||
setFamilyValue: (state, action) => {
|
||||
state.familyValue = action.payload;
|
||||
},
|
||||
|
||||
// universal update
|
||||
updateFilter: (state, action) => {
|
||||
return { ...state, ...action.payload };
|
||||
},
|
||||
|
||||
resetFilters: () => initialState,
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setAge,
|
||||
setHeight,
|
||||
setMaritalStatus,
|
||||
setMotherTongue,
|
||||
setReligion,
|
||||
setMatchesWithHoroscope,
|
||||
setCaste,
|
||||
setSubCaste,
|
||||
setStar,
|
||||
setDasham,
|
||||
setOccupation,
|
||||
setAnnualIncome,
|
||||
setEmployeeType,
|
||||
setEducation,
|
||||
setState,
|
||||
setCountry,
|
||||
setCitizenship,
|
||||
setEatingHabits,
|
||||
setSmokingHabits,
|
||||
setDrinkingHabits,
|
||||
setFamilyType,
|
||||
setFamilyStatus,
|
||||
setFamilyValue,
|
||||
updateFilter,
|
||||
resetFilters,
|
||||
} = filterSlice.actions;
|
||||
|
||||
export default filterSlice.reducer;
|
||||
99
src/redux/registrationFormSlice.jsx
Normal file
99
src/redux/registrationFormSlice.jsx
Normal file
@ -0,0 +1,99 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const registrationformSlice = createSlice({
|
||||
name: "registerform",
|
||||
initialState: {
|
||||
personalDetails: {
|
||||
name: "",
|
||||
mobileNumber: "",
|
||||
gender: "",
|
||||
dob: "",
|
||||
height: "",
|
||||
weight: "",
|
||||
maritalStatus: "",
|
||||
religion: "",
|
||||
caste: "",
|
||||
subCaste: "",
|
||||
gothram: "",
|
||||
bloodGroup: "",
|
||||
email: "",
|
||||
state: "",
|
||||
city: "",
|
||||
pincode: "",
|
||||
profiles: [],
|
||||
},
|
||||
educationalDetails: {
|
||||
qualification: "",
|
||||
fieldOfStudy: "",
|
||||
occupation: "",
|
||||
organization: "",
|
||||
income: "",
|
||||
workLocation: "",
|
||||
},
|
||||
familyDetails: {
|
||||
fatherName: "",
|
||||
fatherOccupation: "",
|
||||
motherName: "",
|
||||
motherOccupation: "",
|
||||
siblings: "",
|
||||
siblingsStatus: "",
|
||||
familyType: "",
|
||||
nativePlace: "",
|
||||
},
|
||||
lifestyleDetails: {
|
||||
diet: "",
|
||||
drinking: "",
|
||||
smoking: "",
|
||||
hobbies: "",
|
||||
},
|
||||
partnerPreferences: {
|
||||
ageRange: "",
|
||||
religionCaste: "",
|
||||
occupationPref: "",
|
||||
lifestylePref: "",
|
||||
qualificationPref: "",
|
||||
occupationText: "",
|
||||
incomePref: "",
|
||||
locationPref: "",
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
updatePersonalDetails: (state, action) => {
|
||||
state.personalDetails = { ...state.personalDetails, ...action.payload };
|
||||
},
|
||||
updateEducationalDetails: (state, action) => {
|
||||
state.educationalDetails = { ...state.educationalDetails, ...action.payload };
|
||||
},
|
||||
updateFamilyDetails: (state, action) => {
|
||||
state.familyDetails = { ...state.familyDetails, ...action.payload };
|
||||
},
|
||||
updateLifestyleDetails: (state, action) => {
|
||||
state.lifestyleDetails = { ...state.lifestyleDetails, ...action.payload };
|
||||
},
|
||||
updatePartnerPreferences: (state, action) => {
|
||||
state.partnerPreferences = { ...state.partnerPreferences, ...action.payload };
|
||||
},
|
||||
|
||||
submitForm: (state) => {
|
||||
console.log("Form Submitted:", {
|
||||
personalDetails: state.personalDetails,
|
||||
educationalDetails: state.educationalDetails,
|
||||
familyDetails: state.familyDetails,
|
||||
lifestyleDetails: state.lifestyleDetails,
|
||||
partnerPreferences: state.partnerPreferences,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
updatePersonalDetails,
|
||||
updateEducationalDetails,
|
||||
updateFamilyDetails,
|
||||
updateLifestyleDetails,
|
||||
updatePartnerPreferences,
|
||||
submitForm,
|
||||
} = registrationformSlice.actions;
|
||||
|
||||
export default registrationformSlice.reducer;
|
||||
9
src/redux/store.js
Normal file
9
src/redux/store.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import registerformReducer from "./registrationFormSlice";
|
||||
import filtersReducer from "./filterSlice";
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
registerform:registerformReducer,
|
||||
filters:filtersReducer,
|
||||
},
|
||||
});
|
||||
@ -15,11 +15,24 @@ 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";
|
||||
|
||||
const UserRoutes = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Route element={<ProfileLayout />}>
|
||||
<Route path="/registration" element={<StepperForm />} />
|
||||
</Route>
|
||||
|
||||
<Route element={<ProfileLayout />}>
|
||||
<Route path="/filter" element={<FilterForm />} />
|
||||
</Route>
|
||||
|
||||
<Route element={<ProfileLayout />}>
|
||||
<Route path="/" element={<UserDashboardHome />} />
|
||||
</Route>
|
||||
<Route element={<ProfileLayout />}>
|
||||
<Route path="/main/dashboard" element={<UserDashboardHome />} />
|
||||
</Route>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user