From d35bd08f5fd6f3a5b71fbd82813ce55b61beed56 Mon Sep 17 00:00:00 2001 From: Meenadeveloper Date: Thu, 27 Nov 2025 18:26:55 +0530 Subject: [PATCH] form done --- package-lock.json | 264 +++++- package.json | 6 + src/App.jsx | 1 + src/components/matches/MatchesProfilesTab.jsx | 29 +- src/feature/AdvancedDropzone.jsx | 114 +++ src/feature/EducationalDetailsForm.jsx | 144 ++++ src/feature/FamilyDetailsForm.jsx | 164 ++++ src/feature/FilterForm.jsx | 623 ++++++++++++++ src/feature/FilterModal.jsx | 50 ++ src/feature/LifestyleDetailsForm.jsx | 116 +++ src/feature/PartnerPreferencesForm.jsx | 308 +++++++ src/feature/PersonalDetailsForm.jsx | 777 ++++++++++++++++++ src/feature/PreviewScreen.jsx | 124 +++ src/feature/StepperForm.jsx | 329 ++++++++ src/main.jsx | 5 + src/redux/filterSlice.jsx | 140 ++++ src/redux/registrationFormSlice.jsx | 99 +++ src/redux/store.js | 9 + src/routes/UserRoutes.jsx | 13 + 19 files changed, 3301 insertions(+), 14 deletions(-) create mode 100644 src/feature/AdvancedDropzone.jsx create mode 100644 src/feature/EducationalDetailsForm.jsx create mode 100644 src/feature/FamilyDetailsForm.jsx create mode 100644 src/feature/FilterForm.jsx create mode 100644 src/feature/FilterModal.jsx create mode 100644 src/feature/LifestyleDetailsForm.jsx create mode 100644 src/feature/PartnerPreferencesForm.jsx create mode 100644 src/feature/PersonalDetailsForm.jsx create mode 100644 src/feature/PreviewScreen.jsx create mode 100644 src/feature/StepperForm.jsx create mode 100644 src/redux/filterSlice.jsx create mode 100644 src/redux/registrationFormSlice.jsx create mode 100644 src/redux/store.js diff --git a/package-lock.json b/package-lock.json index 2b9a12c..6082de9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 551a2b0..fd1e82a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.jsx b/src/App.jsx index bf7a6d8..868ddee 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import "./App.css"; import { BrowserRouter as Router } from "react-router-dom"; import AppRoutes from "./routes/AppRoutes"; + function App() { return ( <> diff --git a/src/components/matches/MatchesProfilesTab.jsx b/src/components/matches/MatchesProfilesTab.jsx index 04e0437..144a847 100644 --- a/src/components/matches/MatchesProfilesTab.jsx +++ b/src/components/matches/MatchesProfilesTab.jsx @@ -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 }) { -
-
@@ -286,11 +290,14 @@ export default function MatchesInterface() { {/* Right Content Area - Scrollable */}
-

+
+

{tabs.find(t => t.id === selectedTab)?.title}

- -
+ + +
+
{profiles.map((profile) => ( ))} diff --git a/src/feature/AdvancedDropzone.jsx b/src/feature/AdvancedDropzone.jsx new file mode 100644 index 0000000..4523b3d --- /dev/null +++ b/src/feature/AdvancedDropzone.jsx @@ -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 ( + <> + + {extFiles.map((file) => ( + + ))} + + + setImageSrc(undefined)} + > + + + + setVideoSrc(undefined)} + > + + + + ); +}; + +export default AdvancedDropzone; diff --git a/src/feature/EducationalDetailsForm.jsx b/src/feature/EducationalDetailsForm.jsx new file mode 100644 index 0000000..5d96171 --- /dev/null +++ b/src/feature/EducationalDetailsForm.jsx @@ -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 ( + <> +
+
+
+ {/* Highest Qualification */} +
+ handleChange("qualification", e.target.value)} + error={Boolean(errors.qualification)} + helperText={errors.qualification} + placeholder="Enter Highest Qualification" + variant="outlined" + /> +
+ + {/* Field of Study */} +
+ handleChange("fieldOfStudy", e.target.value)} + error={Boolean(errors.fieldOfStudy)} + helperText={errors.fieldOfStudy} + placeholder="Enter Field of Study" + variant="outlined" + /> +
+ + {/* Occupation */} +
+ handleChange("occupation", e.target.value)} + error={Boolean(errors.occupation)} + helperText={errors.occupation} + placeholder="Enter Occupation" + variant="outlined" + /> +
+ + {/* Company / Organization Name */} +
+ handleChange("organization", e.target.value)} + error={Boolean(errors.organization)} + helperText={errors.organization} + placeholder="Enter Company / Organization Name" + variant="outlined" + /> +
+ + {/* Annual Income */} +
+ handleChange("income", e.target.value)} + error={Boolean(errors.income)} + helperText={errors.income} + placeholder="Enter Annual Income" + variant="outlined" + /> +
+ + {/* Work Location */} +
+ handleChange("workLocation", e.target.value)} + error={Boolean(errors.workLocation)} + helperText={errors.workLocation} + placeholder="Enter Work Location" + variant="outlined" + /> +
+
+ + + + + +
+
+ + ); +}; + +export default EducationalDetailsForm; diff --git a/src/feature/FamilyDetailsForm.jsx b/src/feature/FamilyDetailsForm.jsx new file mode 100644 index 0000000..48d3bfc --- /dev/null +++ b/src/feature/FamilyDetailsForm.jsx @@ -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 ( + + <> + +
+
+
+ {/* Highest Qualification */} +
+ + handleChange("fatherName", e.target.value)} + error={Boolean(errors.fatherName)} + helperText={errors.fatherName} + placeholder="Enter Father Name" + variant="outlined" + /> +
+ +
+ + handleChange("fatherOccupation", e.target.value)} + error={Boolean(errors.fatherOccupation)} + helperText={errors.fatherOccupation} + placeholder="Enter Father Occupation" + variant="outlined" + /> +
+ +
+ handleChange("motherName", e.target.value)} + error={Boolean(errors.motherName)} + helperText={errors.motherName} + placeholder="Enter Mother Name" + variant="outlined" + /> + +
+
+ handleChange("motherOccupation", e.target.value)} + error={Boolean(errors.motherOccupation)} + helperText={errors.motherOccupation} + placeholder="Enter Mother Occupation" + variant="outlined" + /> + +
+ +
+ handleChange("siblings", e.target.value)} + error={Boolean(errors.siblings)} + helperText={errors.siblings} + placeholder="Enter Number of Brothers / Sisters" + variant="outlined" + /> + +
+ +
+ + + Brothers / Sisters (Married / Unmarried) + + + {errors.siblingsStatus && ( +

+ {errors.siblingsStatus} +

+ )} +
+ +
+ + + + + +
+
+
+ + + + + ); +}; + +export default FamilyDetailsForm; diff --git a/src/feature/FilterForm.jsx b/src/feature/FilterForm.jsx new file mode 100644 index 0000000..f5bfc65 --- /dev/null +++ b/src/feature/FilterForm.jsx @@ -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 ( +
+
+ {/* Header */} +
+ + Filter Options + +
+ +
+ {/* Basic Details Section */} + + }> + + Basic Details + + + +
+ {/* Age Slider */} +
+ Age +
+ { + dispatch(setAge(newValue)); + handleSelectionChange("Age", newValue); + }} + valueLabelDisplay="on" + min={18} + max={70} + /> +
+ 18 Age + 70 Age +
+
+
+ + {/* Height Slider */} +
+ Height +
+ { + dispatch(setHeight(newValue)); + handleSelectionChange("Height", newValue); + }} + valueLabelDisplay="on" + min={4.0} + max={7.11} + step={0.01} + valueLabelFormat={(value) => `${value.toFixed(2)}'`} + /> +
+ 4'0" + 7'11" +
+
+
+ + {/* Marital Status */} + + Marital Status + + + + {/* Mother Tongue */} + + Mother Tongue + + +
+
+
+ + {/* Religious Details Section */} + + }> + + Religious Details + + + +
+ {/* Religion */} + + Religion + + + + {/* Matches with Horoscope */} + { + dispatch(setMatchesWithHoroscope(e.target.checked)); + handleSelectionChange( + "Matches with Horoscope", + e.target.checked + ); + }} + /> + } + label="Matches with horoscope" + /> + + {/* Caste */} + + Caste (Multi Select) + + + + {/* Sub-Caste */} + + Sub-Caste (Multi Select) + + + + {/* Star */} + + Star + + + + {/* Dasham */} + + Dasham + + +
+
+
+ + {/* Professional Details */} + + }> + + Professional Details + + + +
+ + Occupation + + + + + Annual Income + + + + + Employee Type + + + + + Education + + +
+
+
+ + {/* Location Details */} + + }> + + Location Details + + + +
+ + State + + + + + Country + + + + + Citizenship + + +
+
+
+ + {/* Lifestyle Details */} + + }> + + Lifestyle Details + + + +
+ + Eating Habits + + + + + Smoking Habits + + + + + Drinking Habits + + +
+
+
+ + {/* Family Details */} + + }> + + Family Details + + + +
+ + Family Type + + + + + Family Status + + + + + Family Value + + +
+
+
+ + {/* Submit Button */} +
+ +
+
+
+
+ ); +}; + +export default FilterForm; diff --git a/src/feature/FilterModal.jsx b/src/feature/FilterModal.jsx new file mode 100644 index 0000000..fd1ad83 --- /dev/null +++ b/src/feature/FilterModal.jsx @@ -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) */} + + + + + {/* Modal */} + + + + Filter Options + + + + + + + + {/* Use your existing FilterForm here */} + + + + + ); +}; + +export default FilterModal; diff --git a/src/feature/LifestyleDetailsForm.jsx b/src/feature/LifestyleDetailsForm.jsx new file mode 100644 index 0000000..cc83a48 --- /dev/null +++ b/src/feature/LifestyleDetailsForm.jsx @@ -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 ( +
+
+
+ {fields.map(({ name, label, options }) => ( +
+ + + Select {label} + + {errors[name] && ( +

+ {errors[name]} +

+ )} +
+
+ ))} +
+ + + + + +
+
+ ); +}; + +export default LifestyleDetailsForm; diff --git a/src/feature/PartnerPreferencesForm.jsx b/src/feature/PartnerPreferencesForm.jsx new file mode 100644 index 0000000..6010b16 --- /dev/null +++ b/src/feature/PartnerPreferencesForm.jsx @@ -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 ( +
+
+
+ {/* Preferred Age Range */} +
+ + + + Select Preferred Age Range + + + {errors.ageRange && ( +

+ {errors.ageRange} +

+ )} +
+
+ + {/* Religion / Caste Preference */} +
+ + + + Select Religion / Caste Preference + + + {errors.religionCaste && ( +

+ {errors.religionCaste} +

+ )} +
+
+ + {/* Occupation Preference */} +
+ + + + Select Occupation Preference + + + {errors.occupationPref && ( +

+ {errors.occupationPref} +

+ )} +
+
+ + {/* Lifestyle & Habits Preference */} +
+ + + + Select Lifestyle & Habits Preference + + + {errors.lifestylePref && ( +

+ {errors.lifestylePref} +

+ )} +
+
+ + {/* Prefer Qualification */} +
+ + + handleChange("qualificationPref", e.target.value) + } + error={Boolean(errors.qualificationPref)} + helperText={errors.qualificationPref} + variant="outlined" + /> +
+ + {/* Occupation (text) */} +
+ + + handleChange("occupationText", e.target.value) + } + error={Boolean(errors.occupationText)} + helperText={errors.occupationText} + variant="outlined" + /> +
+ + {/* Prefer Annual Income */} +
+ + handleChange("incomePref", e.target.value)} + error={Boolean(errors.incomePref)} + helperText={errors.incomePref} + variant="outlined" + /> +
+ + {/* Prefer Location */} +
+ + handleChange("locationPref", e.target.value)} + error={Boolean(errors.locationPref)} + helperText={errors.locationPref} + variant="outlined" + /> +
+
+ + + + + +
+
+ ); +}; + +export default PartnerPreferencesForm; diff --git a/src/feature/PersonalDetailsForm.jsx b/src/feature/PersonalDetailsForm.jsx new file mode 100644 index 0000000..03dafdd --- /dev/null +++ b/src/feature/PersonalDetailsForm.jsx @@ -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 ( + <> +
+
+
+ {/* Name */} +
+ + handleChange("name", e.target.value)} + error={Boolean(errors.name)} + helperText={errors.name} + placeholder="Enter Name" + variant="outlined" + /> +
+ + {/* Gender */} +
+ + + Gender + + {errors.gender && ( +

+ {errors.gender} +

+ )} +
+
+ + {/* Mobile Number and Send OTP Button */} +
+ 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" }} + /> + + {!showOtp && !mobileOtpVerified && ( + + )} + +
+ + {/* OTP Inputs */} +
+ {showOtp && !mobileOtpVerified && ( + <> + + {otp.map((digit, index) => ( + handleOtpChange(index, e.target.value)} + error={Boolean(otpError)} + autoFocus={index === 0} + variant="outlined" + /> + ))} + + + + + + {otpTimer > 0 ? ( + formatTimer(otpTimer) + ) : ( + 0} + > + Resend OTP + + )} + + + {otpError && ( + + {otpError} + + )} + + )} + + {mobileOtpVerified && ( + + Mobile number verified + + )} +
+ + {/* Other fields like DOB, height, marital status, etc. */} + {/* Your other inputs here */} + {/* DOB */} + {/*
+ + handleChange("dob", e.target.value)} + error={Boolean(errors.dob)} + helperText={errors.dob} + InputLabelProps={{ shrink: true }} + variant="outlined" + /> +
*/} + + {/* DOB with MUI DatePicker */} +
+ + + { + 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, + }, + }} + /> + +
+ + {/* Height */} +
+ + handleChange("height", e.target.value)} + error={Boolean(errors.height)} + helperText={errors.height} + variant="outlined" + /> +
+ + {/* Weight */} +
+ + handleChange("weight", e.target.value)} + error={Boolean(errors.weight)} + helperText={errors.weight} + variant="outlined" + /> +
+ + {/* Marital Status */} +
+ + + + Select Marital Status + + + {errors.maritalStatus && ( + + {errors.maritalStatus} + + )} + +
+ + {/* Religion */} +
+ + + Select Religion + + {errors.religion && ( + + {errors.religion} + + )} + +
+ + {/* Caste / Community */} +
+ + + Select Caste / Community + + {errors.caste && ( + + {errors.caste} + + )} + +
+ + {/* Sub-Caste (optional) */} +
+ + + + Select Sub-Caste (optional) + + + +
+ + {/* Gothram (optional) */} +
+ + + + Select Gothram (optional) + + + +
+ + {/* Blood Group (optional) */} +
+ + + + Select Blood Group (optional) + + + +
+ + {/* Email Id */} +
+ + handleChange("email", e.target.value)} + error={Boolean(errors.email)} + helperText={errors.email} + variant="outlined" + /> +
+ + {/* State */} +
+ + + Select State + + {errors.state && ( + + {errors.state} + + )} + +
+ + {/* City */} +
+ + + Select City + + {errors.city && ( + + {errors.city} + + )} + +
+ + {/* Pin code */} +
+ + handleChange("pincode", e.target.value)} + error={Boolean(errors.pincode)} + helperText={errors.pincode} + inputProps={{ maxLength: 6 }} + variant="outlined" + /> +
+ +
+ {/* Upload Profile (UI only) */} + {/*
+ +
+
+

Upload PDF, IMG, JPG

+
+
*/} + + +
+ + { + // if you want to keep in Redux as plain metadata + dispatch(updatePersonalDetails({ profiles: files })); + }} + /> +
+ + +
+ + + + +
+ + + + + + +
+
+ + ); +}; + +export default PersonalDetailsForm; diff --git a/src/feature/PreviewScreen.jsx b/src/feature/PreviewScreen.jsx new file mode 100644 index 0000000..eaf5b3e --- /dev/null +++ b/src/feature/PreviewScreen.jsx @@ -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 ( + + + {sections.map((section) => ( + + + + {section.title} + + } + action={ + onEdit(section.step)} + size="large" + > + + + } + sx={{ pb: 0 }} + /> + + + {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 ( + + + {formattedKey}: + + + {value} + + + ); + } + return null; + })} + + + + ))} + + + + + + ); +}; + +export default PreviewScreen; diff --git a/src/feature/StepperForm.jsx b/src/feature/StepperForm.jsx new file mode 100644 index 0000000..515178b --- /dev/null +++ b/src/feature/StepperForm.jsx @@ -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 ( +
+ {steps.map((step, index) => ( + +
onStepClick(step.num)} + > +
= step.num + ? "bg-red-600 text-white" + : "bg-gray-300 text-gray-600" + }`} + > + {step.num} +
+
+ {index < steps.length - 1 && ( +
step.num ? "bg-red-600" : "bg-gray-300" + }`} + /> + )} + + ))} +
+ ); +}; + +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 ( + + ); + case 2: + return ( + + ); + case 3: + return ( + + ); + case 4: + return ( + + ); + case 5: + return ( + + ); + case 6: + return ; + 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 ( +
+
+ {/* Header */} +
+ + +
+ {currentStep > 1 && currentStep < 6 && ( + + )} +

{getTitle()}

+
+ +
+ + {/* Content */} +
{renderStepContent()}
+
+
+ ); +}; + +export default StepperForm; diff --git a/src/main.jsx b/src/main.jsx index b61e74c..1423694 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -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( + + ); diff --git a/src/redux/filterSlice.jsx b/src/redux/filterSlice.jsx new file mode 100644 index 0000000..8f38444 --- /dev/null +++ b/src/redux/filterSlice.jsx @@ -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; diff --git a/src/redux/registrationFormSlice.jsx b/src/redux/registrationFormSlice.jsx new file mode 100644 index 0000000..1cd678b --- /dev/null +++ b/src/redux/registrationFormSlice.jsx @@ -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; diff --git a/src/redux/store.js b/src/redux/store.js new file mode 100644 index 0000000..92e486c --- /dev/null +++ b/src/redux/store.js @@ -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, + }, +}); diff --git a/src/routes/UserRoutes.jsx b/src/routes/UserRoutes.jsx index 3649d37..d4cd0a8 100644 --- a/src/routes/UserRoutes.jsx +++ b/src/routes/UserRoutes.jsx @@ -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 ( <> + }> + } /> + + + }> + } /> + + + }> + } /> + }> } />