Compare commits
10 Commits
65bd6c646b
...
4ba4ce1e1b
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ba4ce1e1b | |||
| c467271927 | |||
| 68f97c40dc | |||
| cd880e10e5 | |||
| ccf638f7f3 | |||
| 9427677a72 | |||
| 5392a4211e | |||
| 805c93b3f3 | |||
| 8f6ddbcb2c | |||
| 135f6bba48 |
9
.gitignore
vendored
9
.gitignore
vendored
@ -22,3 +22,12 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist.zip
|
||||||
|
|
||||||
|
|||||||
62
README.md
62
README.md
@ -1,16 +1,60 @@
|
|||||||
# React + Vite
|
# Thirukalyanam Web Application
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
A modern matrimonial web application built with React, Vite, and Tailwind CSS.
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
## Live Application
|
||||||
|
- [Live Site](https://www.thirukalyanam.amrithaa.net/)
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
## Features
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
- Multi-step Registration Form (Personal, Educational, Family, Lifestyle, Partner Preferences)
|
||||||
|
- User Dashboard
|
||||||
|
- Real-time Chat (WebSocket)
|
||||||
|
- Profile Filtering and Matching
|
||||||
|
- Notification System
|
||||||
|
|
||||||
## React Compiler
|
## Tech Stack
|
||||||
|
- **Frontend**: React 19, Vite
|
||||||
|
- **Styling**: Tailwind CSS v4, Material UI
|
||||||
|
- **State Management**: Redux Toolkit, Redux Persist
|
||||||
|
- **Data Fetching**: React Query (TanStack Query)
|
||||||
|
- **Icons**: Lucide React, Material Icons
|
||||||
|
- **Notifications**: Firebase Cloud Messaging
|
||||||
|
|
||||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
## Getting Started
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
### Prerequisites
|
||||||
|
- Node.js (v18 or higher)
|
||||||
|
- npm or yarn
|
||||||
|
|
||||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
### Installation
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://gitpro.amrithaa.com/mageshwaran/thirukalyanamweb.git
|
||||||
|
```
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
3. Create a `.env` file and add the API base URL:
|
||||||
|
```env
|
||||||
|
VITE_THIRUKALYANAM_API_BASE_URL=https://www.thirukalyanam.amrithaa.net/backend/api/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
Run the development server:
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Build
|
||||||
|
Generate a production-ready build:
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
Preview the build locally:
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
Proprietary. All rights reserved.
|
||||||
|
|||||||
88
package-lock.json
generated
88
package-lock.json
generated
@ -31,6 +31,9 @@
|
|||||||
"react-lazy-load-image-component": "^1.6.3",
|
"react-lazy-load-image-component": "^1.6.3",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.9.6",
|
"react-router-dom": "^7.9.6",
|
||||||
|
"react-select": "^5.10.2",
|
||||||
|
"react-window": "^2.2.7",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
"swiper": "^12.0.3",
|
"swiper": "^12.0.3",
|
||||||
"tailwindcss": "^4.1.17"
|
"tailwindcss": "^4.1.17"
|
||||||
},
|
},
|
||||||
@ -1719,6 +1722,31 @@
|
|||||||
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
|
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||||
|
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
||||||
|
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.7.5",
|
||||||
|
"@floating-ui/utils": "^0.2.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@grpc/grpc-js": {
|
"node_modules/@grpc/grpc-js": {
|
||||||
"version": "1.9.15",
|
"version": "1.9.15",
|
||||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
|
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
|
||||||
@ -7139,6 +7167,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memoize-one": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/merge-stream": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
@ -8053,6 +8087,27 @@
|
|||||||
"react-dom": ">=18"
|
"react-dom": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-select": {
|
||||||
|
"version": "5.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
|
||||||
|
"integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.0",
|
||||||
|
"@emotion/cache": "^11.4.0",
|
||||||
|
"@emotion/react": "^11.8.1",
|
||||||
|
"@floating-ui/dom": "^1.0.1",
|
||||||
|
"@types/react-transition-group": "^4.4.0",
|
||||||
|
"memoize-one": "^6.0.0",
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"react-transition-group": "^4.3.0",
|
||||||
|
"use-isomorphic-layout-effect": "^1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-smooth": {
|
"node_modules/react-smooth": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||||
@ -8116,6 +8171,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-window": {
|
||||||
|
"version": "2.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz",
|
||||||
|
"integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -8205,6 +8270,15 @@
|
|||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/redux-persist": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": ">4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
@ -9069,6 +9143,20 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-isomorphic-layout-effect": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/use-sync-external-store": {
|
"node_modules/use-sync-external-store": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
|
|||||||
@ -33,6 +33,9 @@
|
|||||||
"react-lazy-load-image-component": "^1.6.3",
|
"react-lazy-load-image-component": "^1.6.3",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.9.6",
|
"react-router-dom": "^7.9.6",
|
||||||
|
"react-select": "^5.10.2",
|
||||||
|
"react-window": "^2.2.7",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
"swiper": "^12.0.3",
|
"swiper": "^12.0.3",
|
||||||
"tailwindcss": "^4.1.17"
|
"tailwindcss": "^4.1.17"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,57 +1,71 @@
|
|||||||
export const API_ENDPOINTS = {
|
export const API_ENDPOINTS = {
|
||||||
LOGOUT: "logout",
|
LOGOUT: "logout",
|
||||||
TERMS_AND_POLICIES_PRIVACY:"terms-and-policies",
|
TERMS_AND_POLICIES_PRIVACY: "terms-and-policies",
|
||||||
// registration api's
|
// registration api's
|
||||||
PERSONAL_DETAILS_MASTER :"personal_details_masters",
|
PERSONAL_DETAILS_MASTER: "personal_details_masters",
|
||||||
CASTE_MASTER : "get_caste_masters",
|
CASTE_MASTER: "get_caste_masters",
|
||||||
SUB_CASTE_MASTER : "get_sub_caste_masters",
|
SUB_CASTE_MASTER: "get_sub_caste_masters",
|
||||||
CITY_MASTER : "get_district_masters",
|
CITY_MASTER: "get_district_masters",
|
||||||
STAR_MASTER : "get_star_masters",
|
STAR_MASTER: "get_star_masters",
|
||||||
MOBILE_SEND_OTP: "send_otp",
|
MOBILE_SEND_OTP: "send_otp",
|
||||||
MOBILE_VERIFY_OTP: "verify_otp",
|
MOBILE_VERIFY_OTP: "verify_otp",
|
||||||
|
|
||||||
EDUCATION_DETAILS_MASTER: "educational_details_masters",
|
EDUCATION_DETAILS_MASTER: "educational_details_masters",
|
||||||
EDUCATION_LIST_API:"get_education",
|
EDUCATION_LIST_API: "get_education",
|
||||||
|
|
||||||
FAMILY_DETAILS_MASTER: "family_details_masters", // family details master api
|
FAMILY_DETAILS_MASTER: "family_details_masters", // family details master api
|
||||||
|
|
||||||
LIFESTYLE_DETAILS_MASTER:"lifetstyle_details_masters",
|
LIFESTYLE_DETAILS_MASTER: "lifetstyle_details_masters",
|
||||||
|
|
||||||
PREFERED_PARTNER_DETAILS_MASTER:"prefered_details_masters",
|
PREFERED_PARTNER_DETAILS_MASTER: "prefered_details_masters",
|
||||||
|
|
||||||
REGISTER_STEP1: "register", // register api
|
REGISTER_STEP1: "register", // register api
|
||||||
REGISTER_STEP2:"update_educational_details", // educational details updated api
|
REGISTER_STEP2: "update_educational_details", // educational details updated api
|
||||||
REGSITER_STEP3:"update_family_details", // family details updated api
|
REGSITER_STEP3: "update_family_details", // family details updated api
|
||||||
REGISTER_STEP4:"update_lifestyle_details", // lifestyle details updated api
|
REGISTER_STEP4: "update_lifestyle_details", // lifestyle details updated api
|
||||||
REGISTER_STEP5:"update_preferred_details", // partner preference details updated api
|
REGISTER_STEP5: "update_preferred_details", // partner preference details updated api
|
||||||
PREVIEW_DETAILS: "get_preview_details",
|
PREVIEW_DETAILS: "get_preview_details",
|
||||||
REVIEWS: "reviews",
|
REVIEWS: "reviews",
|
||||||
|
|
||||||
// edit api's autopapulated
|
// edit api's autopapulated
|
||||||
|
|
||||||
EDIT_PERSONAL_DETAILS: "get_personal_details",
|
EDIT_PERSONAL_DETAILS: "get_personal_details",
|
||||||
EDIT_EDUCATION_DETAILS: "get_educational_details",
|
EDIT_EDUCATION_DETAILS: "get_educational_details",
|
||||||
EDIT_FAMILY_DETAILS: "get_family_details",
|
EDIT_FAMILY_DETAILS: "get_family_details",
|
||||||
EDIT_LIFESTYLE_DETAILS: "get_lifestyle_details",
|
EDIT_LIFESTYLE_DETAILS: "get_lifestyle_details",
|
||||||
EDIT_PREFERED_PARTNER_DETAILS: "get_preferred_details",
|
EDIT_PREFERED_PARTNER_DETAILS: "get_preferred_details",
|
||||||
|
|
||||||
// delete api
|
// delete api
|
||||||
|
|
||||||
DELETE_ACCOUNT: "delete_account",
|
DELETE_ACCOUNT: "delete_account",
|
||||||
PHONE_NUMBER_VISIBILITY: "get_phone_number_visibility",
|
PHONE_NUMBER_VISIBILITY: "get_phone_number_visibility",
|
||||||
UPDATE_PHONE_NUMBER_VISIBILITY: "update_phone_number_visibility",
|
UPDATE_PHONE_NUMBER_VISIBILITY: "update_phone_number_visibility",
|
||||||
CHAT_ALERT_NOTIFICATION:"get_chat_alert_notification",
|
CHAT_ALERT_NOTIFICATION: "get_chat_alert_notification",
|
||||||
UPDATE_CHAT_ALERT_NOTIFICATION:"update_chat_alert_notification",
|
UPDATE_CHAT_ALERT_NOTIFICATION: "update_chat_alert_notification",
|
||||||
PROFILE_PROTECT_API:"get_profile_protection",
|
PROFILE_PROTECT_API: "get_profile_protection",
|
||||||
UPDATE_PROFILE_PROTECT_API:"update_profile_protection",
|
UPDATE_PROFILE_PROTECT_API: "update_profile_protection",
|
||||||
MATCH_ALERT:"get_match_alert",
|
MATCH_ALERT: "get_match_alert",
|
||||||
UPDATE_MATCH_ALERT:"update_match_alert",
|
UPDATE_MATCH_ALERT: "update_match_alert",
|
||||||
WHO_CAN_VIEW_MESSAGE:"get_who_can_message_me",
|
WHO_CAN_VIEW_MESSAGE: "get_who_can_message_me",
|
||||||
UPDATE_WHO_CAN_VIEW_MESSAGE:"update_who_can_message_me",
|
UPDATE_WHO_CAN_VIEW_MESSAGE: "update_who_can_message_me",
|
||||||
CONTACT_US:"get_contact_us",
|
CONTACT_US: "get_contact_us",
|
||||||
BE_SAFE_ONLINE:"get_be_safe_online",
|
BE_SAFE_ONLINE: "get_be_safe_online",
|
||||||
NOTIFICATION_LIST:"notification/lists",
|
NOTIFICATION_LIST: "notification/lists",
|
||||||
NOTIFICATION_COUNT:"notification/un_read_count",
|
NOTIFICATION_COUNT: "notification/un_read_count",
|
||||||
|
|
||||||
|
// filter with profiles list api's
|
||||||
|
PROFILES_FILTER_LIST: "profiles/lists",
|
||||||
|
PROFILES_FILTER_MASTER: "profiles/filter/masters",
|
||||||
|
|
||||||
|
DASHBOARD_API: "dashboard",
|
||||||
|
HEADER_API: "header_data",
|
||||||
|
SHORTLIST_API: "shortlist_profile",
|
||||||
|
BLOCK_PROFILE_LIST: "block_profile_list",
|
||||||
|
REPORT_PROFILE_LIST: "report_profile_list",
|
||||||
|
PROFILE_DETAIL: "profiles/detail",
|
||||||
|
INTEREST_LIST: "interest_lists",
|
||||||
|
UPDATE_INTEREST_STATUS: "update_interest_status",
|
||||||
|
CHAT_LIST: "chat/lists",
|
||||||
|
CHAT_MESSAGES: (id) => `chat/${id}/messages`,
|
||||||
|
UNREAD_CHAT_COUNT: "chat/un_read_chat_count",
|
||||||
};
|
};
|
||||||
|
|||||||
7
src/api/dashboard.api.js
Normal file
7
src/api/dashboard.api.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import axiosInstance from "./axiosInstance";
|
||||||
|
import { API_ENDPOINTS } from "./apiEndpoints";
|
||||||
|
|
||||||
|
export const getDashboardDetails = async () => {
|
||||||
|
const res = await axiosInstance.get(API_ENDPOINTS.DASHBOARD_API);
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
@ -63,3 +63,17 @@ export const getPartnerPreferenceMasters = async () => {
|
|||||||
);
|
);
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// profile filter masters
|
||||||
|
export const getProfilesFilterList = async (filters) => {
|
||||||
|
const res = await axiosInstance.get(API_ENDPOINTS.PROFILES_FILTER_LIST, {
|
||||||
|
params: filters,
|
||||||
|
});
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfilesFilterMasters = async () => {
|
||||||
|
const res = await axiosInstance.get(API_ENDPOINTS.PROFILES_FILTER_MASTER);
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
@ -5,3 +5,9 @@ export const getPreviewDetails = async () => {
|
|||||||
const res = await axiosInstance.get(API_ENDPOINTS.PREVIEW_DETAILS);
|
const res = await axiosInstance.get(API_ENDPOINTS.PREVIEW_DETAILS);
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getHeaderDetails = async () => {
|
||||||
|
const res = await axiosInstance.get(API_ENDPOINTS.HEADER_API);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
BIN
src/assets/images/horoscopeimg.png
Normal file
BIN
src/assets/images/horoscopeimg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 788 KiB |
BIN
src/assets/images/kiridam.png
Normal file
BIN
src/assets/images/kiridam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 569 B |
@ -242,7 +242,7 @@ const navigate = useNavigate();
|
|||||||
>
|
>
|
||||||
<div onClick={(e) => e.stopPropagation()} className="bg-white rounded-2xl shadow-xl overflow-hidden select-none">
|
<div onClick={(e) => e.stopPropagation()} className="bg-white rounded-2xl shadow-xl overflow-hidden select-none">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div classname=" relative bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]" style={{height:"300px"}}>
|
<div className=" relative bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]" style={{height:"300px"}}>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={profile.image}
|
src={profile.image}
|
||||||
|
|||||||
20
src/components/common/ProfileCardSkeleton.jsx
Normal file
20
src/components/common/ProfileCardSkeleton.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function ProfileCardSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-sm rounded-[10px] border shadow-lg p-4 animate-pulse">
|
||||||
|
|
||||||
|
<div className="w-full h-[280px] bg-gray-200 rounded mb-4"></div>
|
||||||
|
|
||||||
|
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
|
||||||
|
|
||||||
|
<div className="h-3 bg-gray-200 rounded w-1/2 mb-3"></div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="h-3 bg-gray-200 rounded"></div>
|
||||||
|
<div className="h-3 bg-gray-200 rounded"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
142
src/components/common/ProfileCardUI.jsx
Normal file
142
src/components/common/ProfileCardUI.jsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Crown, Bookmark, Receipt, Sparkles, MoonStar, IdCard } from "lucide-react";
|
||||||
|
import CakeIcon from "@mui/icons-material/Cake";
|
||||||
|
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||||
|
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||||
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ProfileCardUI({ profile }) {
|
||||||
|
const [isLiked, setIsLiked] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Map API fields to UI, handling missing values
|
||||||
|
const imageSrc = profile.photo || profile.image || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||||
|
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200 bg-white cursor-pointer hover:shadow-2xl transition-all duration-300"
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
{/* Premium Badge */}
|
||||||
|
{profile.isPremium && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
transition={{ delay: 0.2, type: "spring" }}
|
||||||
|
className="absolute top-4 left-4 z-10 bg-red-900 rounded-full p-2 shadow-lg"
|
||||||
|
>
|
||||||
|
<Crown className="w-5 h-5 text-white" />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// Shortlist logic here
|
||||||
|
}}
|
||||||
|
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||||
|
>
|
||||||
|
<Bookmark className="w-4 h-4 text-gray-600" />
|
||||||
|
<span className="text-[12px] font-medium text-gray-700">Shortlist</span>
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<div className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]">
|
||||||
|
<img
|
||||||
|
src={imageSrc}
|
||||||
|
alt={profile.name}
|
||||||
|
className="w-full h-full object-cover bg-gray-200"
|
||||||
|
style={{ objectPosition: "top" }}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gradient Overlay */}
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 h-24 pointer-events-none" style={{ background: "linear-gradient(to top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 100%)" }}></div>
|
||||||
|
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 p-6 pb-2 text-gray-900">
|
||||||
|
<h1 className="text-[18px] text-green-900 font-bold mb-1 truncate">{profile.name}</h1>
|
||||||
|
<p className="text-[14px] text-gray-700 leading-relaxed font-medium">ID: {profile.member_id || profile.id}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-4 pt-2 pb-4 flex flex-col gap-3 bg-white">
|
||||||
|
<div className="flex items-center gap-2 text-gray-600">
|
||||||
|
<VisibilityIcon sx={{ fontSize: 18 }} />
|
||||||
|
<span className="text-[13px] font-medium">Last seen: {profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-y-2 gap-x-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<CakeIcon sx={{ fontSize: 18, color: "#374151" }} />
|
||||||
|
<span className="text-[14px] font-semibold text-gray-900">{profile.age ? `${profile.age} yrs` : "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<AccessibilityNewIcon sx={{ fontSize: 18, color: "#374151" }} />
|
||||||
|
<span className="text-[14px] font-semibold text-gray-900">{profile.height ? `${profile.height} cm` : "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 col-span-2">
|
||||||
|
<Receipt className="w-4 h-4 text-gray-700" />
|
||||||
|
<span className="text-[14px] font-semibold text-gray-900 truncate">{profile.annual_income_name || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-gray-700">
|
||||||
|
<div className="flex items-center gap-1.5" title="Raasi">
|
||||||
|
<MoonStar className="w-4 h-4" />
|
||||||
|
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.raasi_name || "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5" title="Star">
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.star_name || "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5" title="Caste">
|
||||||
|
<IdCard className="w-4 h-4" />
|
||||||
|
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.caste_name || "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<LocationOnIcon sx={{ fontSize: 18, color: "#DC2626" }} />
|
||||||
|
<span className="text-[14px] font-semibold text-gray-900 truncate">
|
||||||
|
{profile.district_name || profile.location || "-"}
|
||||||
|
{profile.state_name ? `, ${profile.state_name}` : ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 mt-2">
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); }}
|
||||||
|
className="flex-1 flex items-center justify-center gap-2 px-4 py-2 bg-red-50 border border-red-200 text-red-700 rounded-full font-medium text-sm hover:bg-red-100 transition-colors active:scale-95"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||||
|
Decline
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-full font-medium text-sm border transition-colors active:scale-95 ${isLiked ? "bg-green-100 border-green-300 text-green-700" : "bg-green-50 border-green-200 text-green-700 hover:bg-green-100"}`}
|
||||||
|
onClick={(e) =>{ e.stopPropagation(); setIsLiked(!isLiked); }}
|
||||||
|
>
|
||||||
|
{isLiked ? (
|
||||||
|
<>
|
||||||
|
<svg className="w-4 h-4 text-red-500 fill-current" viewBox="0 0 24 24"><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"/></svg>
|
||||||
|
Sent
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import Toolbar from "@mui/material/Toolbar";
|
|||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import SwipeableDrawer from "@mui/material/SwipeableDrawer";
|
import SwipeableDrawer from "@mui/material/SwipeableDrawer";
|
||||||
|
import { useWebSocket } from "../../hooks/useWebSocket";
|
||||||
import List from "@mui/material/List";
|
import List from "@mui/material/List";
|
||||||
import ListItem from "@mui/material/ListItem";
|
import ListItem from "@mui/material/ListItem";
|
||||||
import ListItemButton from "@mui/material/ListItemButton";
|
import ListItemButton from "@mui/material/ListItemButton";
|
||||||
@ -19,7 +20,7 @@ import Button from "@mui/material/Button";
|
|||||||
import LazyImage from "./LazyImage";
|
import LazyImage from "./LazyImage";
|
||||||
import Logo from "../../assets/images/logo.png";
|
import Logo from "../../assets/images/logo.png";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect, useMemo } from "react";
|
||||||
import { useTheme, useMediaQuery, ListItemIcon } from "@mui/material";
|
import { useTheme, useMediaQuery, ListItemIcon } from "@mui/material";
|
||||||
import { Home, Users, Heart, MessageCircle, Search, Bell } from "lucide-react";
|
import { Home, Users, Heart, MessageCircle, Search, Bell } from "lucide-react";
|
||||||
import { isAuthenticated } from "../../utills/auth";
|
import { isAuthenticated } from "../../utills/auth";
|
||||||
@ -27,13 +28,15 @@ import userimg from "../../assets/images/bride1.jpg"
|
|||||||
import axiosInstance, { logoutAPI } from "../../api/axiosInstance";
|
import axiosInstance, { logoutAPI } from "../../api/axiosInstance";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { API_ENDPOINTS } from "../../api/apiEndpoints";
|
import { API_ENDPOINTS } from "../../api/apiEndpoints";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { getHeaderDetails } from "../../api/preview.api";
|
||||||
const NAV_LINKS = [
|
const NAV_LINKS = [
|
||||||
// { label: "Home", path: "/" },
|
// { label: "Home", path: "/" },
|
||||||
{ label: "Matches", path: "/matches" },
|
{ label: "Matches", path: "/matches" },
|
||||||
// { label: "ProfileCard", path: "/profile-card" },
|
// { label: "ProfileCard", path: "/profile-card" },
|
||||||
{ label: "Interest", path: "/interest" },
|
{ label: "Interest", path: "/interest" },
|
||||||
{ label: "Horoscope", path: "/horoscoper-generate" },
|
// { label: "Horoscope", path: "/horoscoper-generate" },
|
||||||
{ label: "Messages", path: "/chat" },
|
{ label: "Messages", path: "/chat" },
|
||||||
{ label: "Search", path: "/matches" },
|
{ label: "Search", path: "/matches" },
|
||||||
{ label: "Notifications", path: "/notifications" }
|
{ label: "Notifications", path: "/notifications" }
|
||||||
@ -158,6 +161,15 @@ const ProfileHeader = () => {
|
|||||||
const [profileDrawerOpen, setProfileDrawerOpen] = useState(false);
|
const [profileDrawerOpen, setProfileDrawerOpen] = useState(false);
|
||||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||||
const [logoutModalOpen, setLogoutModalOpen] = useState(false);
|
const [logoutModalOpen, setLogoutModalOpen] = useState(false);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { personalDetails } = useSelector((state) => state.registerform);
|
||||||
|
|
||||||
|
const profileImage =
|
||||||
|
personalDetails?.profiles?.[0]?.preview ||
|
||||||
|
personalDetails?.profiles?.[0]?.url ||
|
||||||
|
personalDetails?.profiles?.[0] ||
|
||||||
|
userimg;
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDesktop = useMediaQuery(theme.breakpoints.up("md"));
|
const isDesktop = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
@ -179,11 +191,105 @@ const ProfileHeader = () => {
|
|||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
enabled: !!auth,
|
enabled: !!auth,
|
||||||
|
refetchInterval: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// WebSocket for real-time updates - Match the robust strategy from ChatPage
|
||||||
|
const profileId = localStorage.getItem("profile_id");
|
||||||
|
const userId = localStorage.getItem("user_id");
|
||||||
|
|
||||||
|
const wsChannels = useMemo(() => {
|
||||||
|
const channels = [];
|
||||||
|
if (profileId && profileId !== "null") {
|
||||||
|
channels.push(`user-chat-notification${profileId}`);
|
||||||
|
channels.push(`user-chat-notification.${profileId}`);
|
||||||
|
channels.push(`partner-chat${profileId}`);
|
||||||
|
channels.push(`partner-chat.${profileId}`);
|
||||||
|
}
|
||||||
|
if (userId && userId !== "null") {
|
||||||
|
channels.push(`user-notification${userId}`);
|
||||||
|
channels.push(`user-notification.${userId}`);
|
||||||
|
}
|
||||||
|
return [...new Set(channels.filter(Boolean))];
|
||||||
|
}, [profileId, userId]);
|
||||||
|
|
||||||
|
const { messages: wsMessages, isConnected } = useWebSocket(wsChannels);
|
||||||
|
const processedMsgCount = useRef(wsMessages.length);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isConnected) {
|
||||||
|
console.log("[HEADER-WS] Connected, refreshing badges...");
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["notificationCount"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["unreadChatCount"] });
|
||||||
|
}
|
||||||
|
}, [isConnected, queryClient]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (wsMessages.length > processedMsgCount.current) {
|
||||||
|
const newWsMsgs = wsMessages.slice(processedMsgCount.current);
|
||||||
|
console.log(`[HEADER-WS] Detected ${newWsMsgs.length} new signals.`);
|
||||||
|
|
||||||
|
let shouldRefresh = false;
|
||||||
|
|
||||||
|
newWsMsgs.forEach(lastMsg => {
|
||||||
|
if (lastMsg.event?.startsWith('pusher:')) return;
|
||||||
|
|
||||||
|
// Lenient detection matching ChatPage
|
||||||
|
const isMessageEvent = lastMsg.event?.toLowerCase().includes('message') ||
|
||||||
|
lastMsg.event?.toLowerCase().includes('chat') ||
|
||||||
|
lastMsg.event?.toLowerCase().includes('notification');
|
||||||
|
|
||||||
|
if (isMessageEvent) {
|
||||||
|
console.log(`[HEADER-WS] Relevant event detected: ${lastMsg.event}, refreshing counts...`);
|
||||||
|
shouldRefresh = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldRefresh) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["unreadChatCount"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["notificationCount"] });
|
||||||
|
}
|
||||||
|
|
||||||
|
processedMsgCount.current = wsMessages.length;
|
||||||
|
}
|
||||||
|
}, [wsMessages, queryClient]);
|
||||||
|
|
||||||
|
const { data: chatCountData } = useQuery({
|
||||||
|
queryKey: ["unreadChatCount"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await axiosInstance.get(API_ENDPOINTS.UNREAD_CHAT_COUNT);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
enabled: !!auth,
|
||||||
|
refetchInterval: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const notificationCount = notificationData?.count || 0;
|
const notificationCount = notificationData?.count || 0;
|
||||||
|
const chatCount = chatCountData?.count || 0;
|
||||||
|
|
||||||
|
const { data: headerData } = useQuery({
|
||||||
|
queryKey: ["headerDetails"],
|
||||||
|
queryFn: getHeaderDetails,
|
||||||
|
enabled: !!auth,
|
||||||
|
});
|
||||||
|
|
||||||
|
// AUTO-SYNC: Recover missing IDs from Header API data
|
||||||
|
useEffect(() => {
|
||||||
|
if (headerData?.myDetails) {
|
||||||
|
const myId = headerData.myDetails.id || headerData.myDetails.profile_id;
|
||||||
|
const uId = headerData.myDetails.user_id;
|
||||||
|
if (myId && (localStorage.getItem("profile_id") === "null" || !localStorage.getItem("profile_id"))) {
|
||||||
|
localStorage.setItem("profile_id", myId);
|
||||||
|
console.log("Header API auto-synced profileId:", myId);
|
||||||
|
}
|
||||||
|
if (uId && (localStorage.getItem("user_id") === "null" || !localStorage.getItem("user_id"))) {
|
||||||
|
localStorage.setItem("user_id", uId);
|
||||||
|
console.log("Header API auto-synced userId:", uId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [headerData]);
|
||||||
|
|
||||||
|
const apiProfileImage = headerData?.myDetails?.profile;
|
||||||
|
|
||||||
const handleMenuClick = (item) => {
|
const handleMenuClick = (item) => {
|
||||||
if (item.action === "delete") {
|
if (item.action === "delete") {
|
||||||
@ -200,7 +306,7 @@ const ProfileHeader = () => {
|
|||||||
|
|
||||||
const deleteAccountMutation = useMutation({
|
const deleteAccountMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
return await axiosInstance.delete(API_ENDPOINTS.DELETE_ACCOUNT);
|
return await axiosInstance.post(API_ENDPOINTS.DELETE_ACCOUNT);
|
||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
toast.success(response?.data?.message || "Account deleted successfully");
|
toast.success(response?.data?.message || "Account deleted successfully");
|
||||||
@ -310,10 +416,17 @@ const ProfileHeader = () => {
|
|||||||
{getNavIcon(index)}
|
{getNavIcon(index)}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary={
|
<ListItemText primary={
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between w-full pr-4">
|
||||||
{label}
|
<span>{label}</span>
|
||||||
{label === "Notifications" && notificationCount > 0 && (
|
{label === "Notifications" && notificationCount > 0 && (
|
||||||
<span className="bg-red-600 text-white text-xs px-2 py-0.5 rounded-full">{notificationCount}</span>
|
<span className="bg-red-600 text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full min-w-[18px] flex items-center justify-center ml-2">
|
||||||
|
{notificationCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{label === "Messages" && chatCount > 0 && (
|
||||||
|
<span className="bg-red-600 text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full min-w-[18px] flex items-center justify-center ml-2">
|
||||||
|
{chatCount}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
} />
|
} />
|
||||||
@ -359,7 +472,10 @@ const ProfileHeader = () => {
|
|||||||
items={NAV_LINKS.map(link => link.label)}
|
items={NAV_LINKS.map(link => link.label)}
|
||||||
color="#034E08"
|
color="#034E08"
|
||||||
activeItem={currentLabel}
|
activeItem={currentLabel}
|
||||||
badges={{ "Notifications": notificationCount }}
|
badges={{
|
||||||
|
"Notifications": notificationCount,
|
||||||
|
"Messages": chatCount
|
||||||
|
}}
|
||||||
onItemClick={(item) => {
|
onItemClick={(item) => {
|
||||||
setSelectedItem(item);
|
setSelectedItem(item);
|
||||||
const link = NAV_LINKS.find(l => l.label === item);
|
const link = NAV_LINKS.find(l => l.label === item);
|
||||||
@ -369,16 +485,16 @@ const ProfileHeader = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{(auth ? (
|
{(auth ? (
|
||||||
<Box sx={{ flexGrow: 0 }}>
|
<Box key="user-menu-box" sx={{ flexGrow: 0 }}>
|
||||||
<Tooltip title="Account Menu">
|
<Tooltip title="Account Menu">
|
||||||
<IconButton onClick={toggleProfileDrawer(true)}>
|
<IconButton onClick={toggleProfileDrawer(true)}>
|
||||||
<Avatar sx={{width:"50px", height:"50px"}} src={userimg || "/static/images/avatar/2.jpg" }/>
|
<Avatar sx={{width:"50px", height:"50px"}} src={apiProfileImage || profileImage || userimg || "/static/images/avatar/2.jpg" }/>
|
||||||
|
|
||||||
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
):( <button className="ml-1 bg-red-900 text-white px-4 py-2 rounded-md hover:bg-red-800 transition-colors"
|
):( <button key="sign-in-btn" className="ml-1 bg-red-900 text-white px-4 py-2 rounded-md hover:bg-red-800 transition-colors"
|
||||||
onClick={() => navigate("/login")}>Sign In / Sign Up</button>))}
|
onClick={() => navigate("/login")}>Sign In / Sign Up</button>))}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -52,10 +52,6 @@ export const SkeletonText = ({
|
|||||||
height = 12,
|
height = 12,
|
||||||
className = "",
|
className = "",
|
||||||
}) => {
|
}) => {
|
||||||
useEffect(() => {
|
|
||||||
injectSkeletonStyles();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} style={{ display: "grid", gap }}>
|
<div className={className} style={{ display: "grid", gap }}>
|
||||||
{Array.from({ length: lines }).map((_, idx) => (
|
{Array.from({ length: lines }).map((_, idx) => (
|
||||||
@ -69,4 +65,44 @@ export const SkeletonText = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SkeletonPage = ({ className = "" }) => {
|
||||||
|
return (
|
||||||
|
<div className={className} style={{ width: "100%", padding: "20px" }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "24px" }}>
|
||||||
|
<Skeleton width="150px" height={40} />
|
||||||
|
<div style={{ display: "flex", gap: "16px" }}>
|
||||||
|
<Skeleton width="80px" height={36} />
|
||||||
|
<Skeleton width="80px" height={36} />
|
||||||
|
<Skeleton width="80px" height={36} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Banner */}
|
||||||
|
<div style={{ marginBottom: "32px" }}>
|
||||||
|
<Skeleton width="100%" height={250} rounded={12} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 4 Cards */}
|
||||||
|
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: "24px", marginBottom: "40px" }}>
|
||||||
|
{Array.from({ length: 4 }).map((_, idx) => (
|
||||||
|
<div key={idx} style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
|
<Skeleton width="100%" height={180} rounded={8} />
|
||||||
|
<Skeleton width="80%" height={20} />
|
||||||
|
<Skeleton width="60%" height={20} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div style={{ borderTop: "1px solid #e5e7eb", paddingTop: "32px", display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "24px" }}>
|
||||||
|
<SkeletonText lines={3} />
|
||||||
|
<SkeletonText lines={3} />
|
||||||
|
<SkeletonText lines={3} />
|
||||||
|
<SkeletonText lines={3} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Skeleton;
|
export default Skeleton;
|
||||||
|
|||||||
@ -125,7 +125,7 @@ const AppPromoteSection = () => {
|
|||||||
className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-16 max-w-6xl mx-auto"
|
className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-16 max-w-6xl mx-auto"
|
||||||
>
|
>
|
||||||
{features.map((feature, index) => (
|
{features.map((feature, index) => (
|
||||||
<div className="relative overflow-hidden bg-white rounded-2xl p-2 shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden">
|
<div key={index} className="relative overflow-hidden bg-white rounded-2xl p-2 shadow-xl hover:shadow-2xl transition-all duration-300 overflow-hidden">
|
||||||
<BorderBeam
|
<BorderBeam
|
||||||
colorFrom="#ff0000ff"
|
colorFrom="#ff0000ff"
|
||||||
colorTo="#338105ff"
|
colorTo="#338105ff"
|
||||||
|
|||||||
@ -1,15 +1,10 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Crown, Bookmark, CurrencyIcon, Currency, Wallet, Receipt, Sparkles, MoonStar, IdCard, RockingChair, LocateFixed, School, WorkflowIcon } from "lucide-react";
|
import { useInView } from "react-intersection-observer";
|
||||||
import CakeIcon from "@mui/icons-material/Cake";
|
import { RockingChair, LocateFixed, School, WorkflowIcon, Lock } from "lucide-react";
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
|
||||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
|
||||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
|
||||||
import PersonIcon from "@mui/icons-material/Person";
|
import PersonIcon from "@mui/icons-material/Person";
|
||||||
import StarIcon from "@mui/icons-material/Star";
|
import StarIcon from "@mui/icons-material/Star";
|
||||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import FilterModal from "../../feature/FilterModal";
|
import FilterModal from "../../feature/FilterModal";
|
||||||
import bride1 from "../../assets/images/bride1.jpg";
|
import bride1 from "../../assets/images/bride1.jpg";
|
||||||
import bride2 from "../../assets/images/bride2.jpg";
|
import bride2 from "../../assets/images/bride2.jpg";
|
||||||
@ -23,260 +18,135 @@ import groom4 from "../../assets/images/groom4.jpg";
|
|||||||
|
|
||||||
import horoscope from "../../assets/images/horoscopeicon.png";
|
import horoscope from "../../assets/images/horoscopeicon.png";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Button, Fab } from "@mui/material";
|
import toast from "react-hot-toast";
|
||||||
import MessageIcon from "@mui/icons-material/Message";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import PhoneIcon from "@mui/icons-material/Phone";
|
import { updateFilter } from "../../redux/filterSlice";
|
||||||
// Profile Card Component
|
import { useProfiles } from "../../hooks/useProfiles";
|
||||||
function ProfileCard({ profile }) {
|
import ProfileCardUI from "../common/ProfileCardUI";
|
||||||
const [isLiked, setIsLiked] = useState(false);
|
import ProfileCardSkeleton from "../common/ProfileCardSkeleton";
|
||||||
const navigate = useNavigate();
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
|
||||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200"
|
|
||||||
>
|
|
||||||
<div className="relative">
|
|
||||||
<motion.div
|
|
||||||
initial={{ scale: 0 }}
|
|
||||||
animate={{ scale: 1 }}
|
|
||||||
transition={{ delay: 0.2, type: "spring" }}
|
|
||||||
className="absolute top-4 left-4 z-10 bg-red-900 rounded-full p-2 shadow-lg"
|
|
||||||
>
|
|
||||||
<Crown className="w-5 h-5 text-white" />
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1 }}
|
|
||||||
whileTap={{ scale: 0.9 }}
|
|
||||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
<Bookmark className="w-4 h-4" />
|
|
||||||
<span className="text-[12px] font-medium">Shortlist</span>
|
|
||||||
</motion.button>
|
|
||||||
<div
|
|
||||||
classname=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
|
||||||
style={{ height: "300px" }}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={profile.image}
|
|
||||||
alt="Profile"
|
|
||||||
className="w-full h-full object-cover bg-gray-200"
|
|
||||||
style={{
|
|
||||||
// objectFit:"inherit",
|
|
||||||
objectPosition: "top",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="absolute bottom-0 left-0 right-0 h-25 pointer-events-none"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
|
||||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
|
||||||
{profile.name}
|
|
||||||
</h1>
|
|
||||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
|
||||||
Matrimony ID: {profile.id}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-4 pt-2 pb-4 flex flex-col gap-2 bg-white">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<VisibilityIcon />
|
|
||||||
<span className="text-[14px] text-gray-900">{profile.lastseen}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
{profile.age} yr
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
{profile.height} cm
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Receipt className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
5 - 10 LPA
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<MoonStar className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
Aries
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Sparkles className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
Scorpio
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<IdCard className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
Bramin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-semibold text-gray-900">
|
|
||||||
{profile.location}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
// your decline logic
|
|
||||||
}} className="gap-2 px-3 w-[fit-content] bg-red-50 border-1 border-red-200
|
|
||||||
font-400 text-base py-1.5 rounded-[20px] shadow-md text-[14px]
|
|
||||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95">
|
|
||||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" strokeLinejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
Decline
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="w-[fit-content] bg-green-50 border-1 border-green-200 font-400 text-base text-[14px]
|
|
||||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
|
||||||
transform hover:scale-105 flex items-center justify-center"
|
|
||||||
onClick={(e) =>{
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsLiked(!isLiked);
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
{isLiked ? (
|
|
||||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="#EF4444"/>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" strokeLinecap="round" strokeLinejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
|
|
||||||
Interest
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Fab size="medium" color="primary" aria-label="add">
|
|
||||||
<MessageIcon />
|
|
||||||
</Fab>
|
|
||||||
|
|
||||||
<Fab size="medium" color="secondary" aria-label="add">
|
|
||||||
<PhoneIcon />
|
|
||||||
</Fab>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="#f5fbff"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
navigate(`/profile-details/${profile.id}`);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
color: "#000000",
|
|
||||||
background: "#f5fbff",
|
|
||||||
fontWeight: "600",
|
|
||||||
borderRadius: "30px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View Details
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main Component
|
// Main Component
|
||||||
export default function MatchesInterface() {
|
export default function MatchesInterface() {
|
||||||
|
const [showSkeleton, setShowSkeleton] = React.useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [selectedTab, setSelectedTab] = useState("your-matches");
|
const dispatch = useDispatch();
|
||||||
|
const filters = useSelector((state) => state.filters);
|
||||||
|
const filterType = filters.filter_type;
|
||||||
|
const selectedTab = filterType || "all_matches";
|
||||||
|
const isPaidMember = filters.isPaidMember;
|
||||||
|
|
||||||
|
const { ref, inView } = useInView({
|
||||||
|
threshold: 0,
|
||||||
|
rootMargin: "300px"
|
||||||
|
});
|
||||||
|
// Fetch real profiles data
|
||||||
|
const {
|
||||||
|
data: profilesData,
|
||||||
|
isLoading,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetchingNextPage,
|
||||||
|
} = useProfiles(filters);
|
||||||
|
const profiles =
|
||||||
|
profilesData?.pages.flatMap((page) => page?.data|| []) || [];
|
||||||
|
|
||||||
|
// const { ref, inView } = useInView();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (inView && hasNextPage && !isFetchingNextPage) {
|
||||||
|
// fetchNextPage();
|
||||||
|
// }
|
||||||
|
// }, [inView, hasNextPage, isFetchingNextPage]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inView && hasNextPage && !isFetchingNextPage) {
|
||||||
|
|
||||||
|
setShowSkeleton(true); // show skeleton
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
fetchNextPage();
|
||||||
|
setShowSkeleton(false); // hide skeleton after API call
|
||||||
|
}, 120); // 0.5 seconds
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log("Fetched profiles:", profiles);
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
inView,
|
||||||
|
hasNextPage,
|
||||||
|
isFetchingNextPage,
|
||||||
|
});
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
id: "your-matches",
|
id: "all_matches",
|
||||||
icon: <PersonIcon className="w-6 h-6" />,
|
icon: <PersonIcon className="w-6 h-6" />,
|
||||||
title: "Your Matches",
|
title: "Your Matches",
|
||||||
description: "View all the profiles that match your preferences",
|
description: "View all the profiles that match your preferences",
|
||||||
category: "All Matches",
|
category: "All Matches",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "shortlisted-by-you",
|
id: "shorlisted_by_you",
|
||||||
icon: <StarIcon className="w-6 h-6" />,
|
icon: <StarIcon className="w-6 h-6" />,
|
||||||
title: "Shortlisted by you",
|
title: "Shortlisted by you",
|
||||||
description: "Matches you have shortlisted",
|
description: "Matches you have shortlisted",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "viewed-you",
|
id: "viewed_you",
|
||||||
icon: <VisibilityIcon className="w-6 h-6" />,
|
icon: <VisibilityIcon className="w-6 h-6" />,
|
||||||
title: "Viewed you",
|
title: "Viewed you",
|
||||||
description: "Matches who have viewed your profile",
|
description: "Matches who have viewed your profile",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "shortlisted-you",
|
id: "shorlisted_you",
|
||||||
icon: <PersonAddIcon className="w-6 h-6" />,
|
icon: <PersonAddIcon className="w-6 h-6" />,
|
||||||
title: "Shortlisted you",
|
title: "Shortlisted you",
|
||||||
description: "Matches who have shortlisted your profile",
|
description: "Matches who have shortlisted your profile",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "viewed-by-you",
|
id: "viewed_by_you",
|
||||||
icon: <VisibilityIcon className="w-6 h-6" />,
|
icon: <VisibilityIcon className="w-6 h-6" />,
|
||||||
title: "Viewed by you",
|
title: "Viewed by you",
|
||||||
description: "Matches you have viewed",
|
description: "Matches you have viewed",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "newly-joined",
|
id: "newly_joined",
|
||||||
icon: <RockingChair className="w-6 h-6" />,
|
icon: <RockingChair className="w-6 h-6" />,
|
||||||
title: "Newly Joined",
|
title: "Newly Joined",
|
||||||
description: "Matches who Joined within the last 30 days",
|
description: "Matches who Joined within the last 30 days",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "location",
|
id: "location_matches",
|
||||||
icon: <LocateFixed className="w-6 h-6" />,
|
icon: <LocateFixed className="w-6 h-6" />,
|
||||||
title: "Location matches",
|
title: "Location matches",
|
||||||
description: "Matches near your location",
|
description: "Matches near your location",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "education",
|
id: "education_matches",
|
||||||
icon: <School className="w-6 h-6" />,
|
icon: <School className="w-6 h-6" />,
|
||||||
title: "Education matches",
|
title: "Education matches",
|
||||||
description: "Matches near your education match",
|
description: "Matches near your education match",
|
||||||
category: "Based on activity",
|
category: "Based on activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "job",
|
id: "job_matches",
|
||||||
icon: <WorkflowIcon className="w-6 h-6" />,
|
icon: <WorkflowIcon className="w-6 h-6" />,
|
||||||
title: "Job matches",
|
title: "Job matches",
|
||||||
description: "Matches near your job",
|
description: "Matches near your job",
|
||||||
@ -284,89 +154,6 @@ export default function MatchesInterface() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const profiles = [
|
|
||||||
{
|
|
||||||
id: "JB2847593",
|
|
||||||
name: "Jerome Bell",
|
|
||||||
age: 22,
|
|
||||||
height: "5.2",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "BCA / Data analyst",
|
|
||||||
location: "Chennai",
|
|
||||||
image: bride1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "SA8392847",
|
|
||||||
name: "Sarah Anderson",
|
|
||||||
age: 24,
|
|
||||||
height: "5.4",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "MBA / Marketing Manager",
|
|
||||||
location: "Bangalore",
|
|
||||||
image: bride4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PR9384756",
|
|
||||||
name: "Priya Reddy",
|
|
||||||
age: 23,
|
|
||||||
height: "5.3",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "B.Tech / Software Engineer",
|
|
||||||
location: "Hyderabad",
|
|
||||||
image: bride2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AN4758392",
|
|
||||||
name: "Ananya Krishnan",
|
|
||||||
age: 25,
|
|
||||||
height: "5.5",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "MD / Doctor",
|
|
||||||
location: "Kochi",
|
|
||||||
image: bride3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AN4758392",
|
|
||||||
name: "Ananya Krishnan",
|
|
||||||
age: 25,
|
|
||||||
height: "5.5",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "MD / Doctor",
|
|
||||||
location: "Kochi",
|
|
||||||
image: groom1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AN4758392",
|
|
||||||
name: "Ananya Krishnan",
|
|
||||||
age: 25,
|
|
||||||
height: "5.5",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "MD / Doctor",
|
|
||||||
location: "Kochi",
|
|
||||||
image: groom2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AN4758392",
|
|
||||||
name: "Ananya Krishnan",
|
|
||||||
age: 25,
|
|
||||||
height: "5.5",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "MD / Doctor",
|
|
||||||
location: "Kochi",
|
|
||||||
image: groom4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AN4758392",
|
|
||||||
name: "Ananya Krishnan",
|
|
||||||
age: 25,
|
|
||||||
height: "5.5",
|
|
||||||
lastseen: "Last seen 14 Nov 2025",
|
|
||||||
education: "MD / Doctor",
|
|
||||||
location: "Kochi",
|
|
||||||
image: groom3,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentCategory = "";
|
let currentCategory = "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -381,7 +168,7 @@ export default function MatchesInterface() {
|
|||||||
|
|
||||||
<div className="w-full md:w-80">
|
<div className="w-full md:w-80">
|
||||||
<div
|
<div
|
||||||
className="rounded-[10px] border border-gray-200 bg-white my-6
|
className="relative rounded-[10px] border border-gray-200 bg-white my-6
|
||||||
shadow-lg h-[400px] md:h-[600px] overflow-y-auto md:sticky md:top-[150px]"
|
shadow-lg h-[400px] md:h-[600px] overflow-y-auto md:sticky md:top-[150px]"
|
||||||
>
|
>
|
||||||
|
|
||||||
@ -406,7 +193,9 @@ export default function MatchesInterface() {
|
|||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
onClick={() => setSelectedTab(tab.id)}
|
onClick={() => {
|
||||||
|
dispatch(updateFilter({ filter_type: tab.id }));
|
||||||
|
}}
|
||||||
className={`p-4 rounded-lg mb-3 cursor-pointer transition-all ${
|
className={`p-4 rounded-lg mb-3 cursor-pointer transition-all ${
|
||||||
selectedTab === tab.id
|
selectedTab === tab.id
|
||||||
? "bg-green-50 border-l-4 border-green-600"
|
? "bg-green-50 border-l-4 border-green-600"
|
||||||
@ -472,19 +261,58 @@ export default function MatchesInterface() {
|
|||||||
{tabs.find((t) => t.id === selectedTab)?.title}
|
{tabs.find((t) => t.id === selectedTab)?.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<img
|
<div className="relative cursor-pointer" onClick={() => {
|
||||||
src={horoscope}
|
if (isPaidMember) {
|
||||||
onClick={() => {
|
|
||||||
navigate("/horoscoper-generate");
|
navigate("/horoscoper-generate");
|
||||||
}}
|
} else {
|
||||||
/>
|
toast.error("Star Matching is locked for free members");
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<img
|
||||||
|
src={horoscope}
|
||||||
|
className={!isPaidMember ? "opacity-50 blur-[1px]" : ""}
|
||||||
|
/>
|
||||||
|
{!isPaidMember && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<Lock className="w-4 h-4 text-black" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<FilterModal />
|
<FilterModal />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||||
{profiles.map((profile) => (
|
{isLoading && !isFetchingNextPage ? (
|
||||||
<ProfileCard key={profile.id} profile={profile} />
|
[...Array(6)].map((_, i) => <ProfileCardSkeleton key={i} />)
|
||||||
))}
|
) : profiles.length > 0 ? (
|
||||||
|
profiles.map((profile) => (
|
||||||
|
<ProfileCardUI key={profile.id} profile={profile} />
|
||||||
|
))
|
||||||
|
) : !isLoading && !isFetchingNextPage ? (
|
||||||
|
<div className="col-span-full text-center py-10 text-gray-500">
|
||||||
|
No profiles found
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
{/* {isFetchingNextPage &&
|
||||||
|
[...Array(5)].map((_, i) => (
|
||||||
|
<ProfileCardSkeleton key={`skel-${i}`} />
|
||||||
|
))} */}
|
||||||
|
|
||||||
|
|
||||||
|
{(isFetchingNextPage || showSkeleton) &&
|
||||||
|
[...Array(6)].map((_, i) => (
|
||||||
|
<ProfileCardSkeleton key={`skel-${i}`} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div ref={ref} className="h-[20px]">
|
||||||
|
{!isLoading && !hasNextPage && profiles.length > 0 && (
|
||||||
|
<p className="text-center text-gray-500 py-8">
|
||||||
|
You've reached the end.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { updateFilter } from '../../redux/filterSlice';
|
||||||
|
import useDebounce from '../../hooks/useDebounce.jsx';
|
||||||
|
|
||||||
export default function SearchUI() {
|
export default function SearchUI() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const searchFromStore = useSelector((state) => state.filters.search);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
|
const debouncedSearchValue = useDebounce(searchValue, 500);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchValue(searchFromStore || '');
|
||||||
|
}, [searchFromStore]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(updateFilter({ search: debouncedSearchValue }));
|
||||||
|
}, [debouncedSearchValue, dispatch]);
|
||||||
|
|
||||||
// Sample suggestions data - you can replace with dynamic data
|
// Sample suggestions data - you can replace with dynamic data
|
||||||
const allSuggestions = [
|
const allSuggestions = [
|
||||||
|
|||||||
@ -379,7 +379,7 @@ const DailyRecommendedCard = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Swiper Styles */}
|
{/* Custom Swiper Styles */}
|
||||||
<style jsx global>{`
|
<style>{`
|
||||||
.swiper-pagination-bullet {
|
.swiper-pagination-bullet {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useRef, useState } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import {
|
import {
|
||||||
@ -10,141 +10,84 @@ import {
|
|||||||
import {
|
import {
|
||||||
Crown,
|
Crown,
|
||||||
Bookmark,
|
Bookmark,
|
||||||
User,
|
|
||||||
Briefcase,
|
|
||||||
MapPin,
|
|
||||||
X,
|
X,
|
||||||
Send,
|
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Heart,
|
||||||
|
Eye,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import CakeIcon from "@mui/icons-material/Cake";
|
|
||||||
import HeightIcon from "@mui/icons-material/Height";
|
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
|
||||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
|
||||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
|
||||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
|
||||||
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
|
|
||||||
import profilebg from "../../assets/images/profilebg.jpg";
|
|
||||||
import Image from "../../assets/images/astrology-horoscope-svgrepo-com.svg";
|
|
||||||
import Image1 from "../../assets/images/scorpio-svgrepo-com.svg";
|
|
||||||
// Import Swiper styles
|
// Import Swiper styles
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
import "swiper/css/effect-coverflow";
|
import "swiper/css/effect-coverflow";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { updateFilter } from "../../redux/filterSlice";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
|
||||||
|
|
||||||
const MatchingList = () => {
|
const ProfileCard = ({ profile }) => {
|
||||||
const swiperRef = useRef(null);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
// Sample profile data
|
|
||||||
const profiles = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: "Selva Kumar . R",
|
|
||||||
userId: "TK52586A",
|
|
||||||
lastSeen: "14 Nov 25",
|
|
||||||
age: 23,
|
|
||||||
height: "5'2\"",
|
|
||||||
salary: "5-10 LPA",
|
|
||||||
location: "chennai",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=500&fit=crop",
|
|
||||||
isPremium: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "Priya Sharma",
|
|
||||||
userId: "TK52587B",
|
|
||||||
lastSeen: "15 Nov 25",
|
|
||||||
age: 25,
|
|
||||||
height: "5'4\"",
|
|
||||||
salary: "8-12 LPA",
|
|
||||||
location: "hyderabad",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=500&fit=crop",
|
|
||||||
isPremium: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: "Rahul Venkat",
|
|
||||||
userId: "TK52588C",
|
|
||||||
lastSeen: "16 Nov 25",
|
|
||||||
age: 28,
|
|
||||||
height: "5'10\"",
|
|
||||||
salary: "6-11 LPA",
|
|
||||||
location: "Mumbai",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=500&fit=crop",
|
|
||||||
isPremium: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Aishwarya Reddy",
|
|
||||||
userId: "TK52589D",
|
|
||||||
lastSeen: "17 Nov 25",
|
|
||||||
age: 26,
|
|
||||||
height: "5'5\"",
|
|
||||||
salary: "7-11 LPA",
|
|
||||||
location: "Bangalore",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=500&fit=crop",
|
|
||||||
isPremium: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: "Karthik Mohan",
|
|
||||||
userId: "TK52590E",
|
|
||||||
lastSeen: "18 Nov 25",
|
|
||||||
age: 27,
|
|
||||||
height: "5'8\"",
|
|
||||||
salary: "9-14 LPA",
|
|
||||||
location: "kerala",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=400&h=500&fit=crop",
|
|
||||||
isPremium: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
name: "Divya Lakshmi",
|
|
||||||
userId: "TK52591F",
|
|
||||||
lastSeen: "19 Nov 25",
|
|
||||||
age: 24,
|
|
||||||
height: "5'3\"",
|
|
||||||
salary: "5-10 LPA",
|
|
||||||
location: "madya pradesh",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=500&fit=crop",
|
|
||||||
isPremium: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Profile Card Component
|
|
||||||
|
|
||||||
const ProfileCard = ({ profile }) => {
|
|
||||||
const [isLiked, setIsLiked] = useState(false);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
|
}, [profile?.is_shortlisted]);
|
||||||
|
|
||||||
|
const shortlistMutation = useMutation({
|
||||||
|
mutationFn: shortlistProfile,
|
||||||
|
onMutate: () => {
|
||||||
|
setIsShortlisted((prev) => !prev);
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message || "Profile shortlisted successfully.");
|
||||||
|
// Invalidating queries will refetch data and update all profile cards simultaneously
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
|
toast.error(error.message || "Failed to update shortlist status.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const interestMutation = useMutation({
|
||||||
|
mutationFn: sendInterest,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message || "Interest sent successfully.");
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message || "Failed to send interest.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const declineMutation = useMutation({
|
||||||
|
mutationFn: declineProfile,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message || "Profile declined.");
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message || "Failed to decline profile.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = profile.id;
|
||||||
|
const image = profile.photo || profile.image;
|
||||||
|
const name = profile.name || "Unknown";
|
||||||
|
const idNumber = profile.member_id || profile.userId || "N/A";
|
||||||
|
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
|
||||||
|
const age = profile.age ? `${profile.age} yrs` : null;
|
||||||
|
const height = profile.height || null;
|
||||||
|
const salary = profile.annual_income_name || profile.salary || null;
|
||||||
|
const location = profile.district_name || profile.location || null;
|
||||||
|
const caste = profile.caste_name || profile.caste || null;
|
||||||
|
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
|
||||||
|
const zodiac2 = profile.star_name || profile.zodiac2 || null;
|
||||||
|
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : profile.isPremium;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -152,140 +95,110 @@ const MatchingList = () => {
|
|||||||
whileInView={{ opacity: 1, scale: 1 }}
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
onClick={() => navigate(`/profile-details/${id}`)}
|
||||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-2 border-gray-200"
|
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
|
||||||
>
|
>
|
||||||
{/* Profile Image Section */}
|
{/* IMAGE SECTION */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{/* Premium Badge */}
|
<img
|
||||||
{profile.isPremium && (
|
src={image}
|
||||||
<motion.div
|
alt="profile"
|
||||||
initial={{ scale: 0 }}
|
className="w-full h-[320px] object-cover"
|
||||||
animate={{ scale: 1 }}
|
onError={(e) => {
|
||||||
transition={{ delay: 0.2, type: "spring" }}
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
className="absolute top-4 left-4 z-10 bg-red-900 rounded-full p-2 shadow-lg"
|
}}
|
||||||
>
|
/>
|
||||||
<Crown className="w-5 h-5 text-white" />
|
|
||||||
</motion.div>
|
{isPremium && (
|
||||||
|
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
||||||
|
<Crown size={18} color="#fff" />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Shortlist Button */}
|
<div
|
||||||
<motion.button
|
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
|
||||||
whileHover={{ scale: 1 }}
|
|
||||||
whileTap={{ scale: 0.9 }}
|
|
||||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// shortlist logic
|
if (!shortlistMutation.isPending) {
|
||||||
|
shortlistMutation.mutate(id);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Bookmark className="w-4 h-4" />
|
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||||
<span className="text-[12px] font-medium">Shortlist</span>
|
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
<div
|
|
||||||
classname=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
|
||||||
style={{ height: "300px" }}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={profile.image}
|
|
||||||
alt={profile.name}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* <LazyImage
|
|
||||||
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top"
|
|
||||||
alt="Profile"
|
|
||||||
className="w-full h-90 object-cover"
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
{/* White Gradient Overlay at bottom of image */}
|
|
||||||
<div
|
|
||||||
className="absolute bottom-0 left-0 right-0 h-25 pointer-events-none"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.75) 50%, rgb(255, 255, 255) 100%)",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
{/* Profile Info Overlay - positioned at bottom */}
|
|
||||||
<div className="absolute bottom-1 left-0 right-0 p-6 pb-1 text-gray-900">
|
|
||||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
|
||||||
{profile.name}
|
|
||||||
</h1>
|
|
||||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
|
||||||
Matrimony ID: {profile.userId}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats and Follow Section */}
|
{/* CONTENT */}
|
||||||
<div
|
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
|
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||||
style={{
|
{name}
|
||||||
background: "rgb(255, 255, 255)",
|
</h2>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
|
||||||
Age : {profile.age}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
<p>ID: {idNumber}</p>
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<p className="flex items-center gap-0.5">
|
||||||
Height: {profile.height}
|
<Eye size={12} /> {lastSeen}
|
||||||
</span>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
{[
|
||||||
<AccountBalanceWalletIcon className="w-4 h-4 text-gray-700" />
|
age,
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
height,
|
||||||
{profile.salary}
|
salary,
|
||||||
</span>
|
location,
|
||||||
</div>
|
caste,
|
||||||
<div className="flex items-center gap-2">
|
zodiac1,
|
||||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
zodiac2,
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
]
|
||||||
{profile.location}
|
.filter(Boolean)
|
||||||
</span>
|
.map((v, i) => (
|
||||||
</div>
|
<span
|
||||||
|
key={i}
|
||||||
|
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||||
|
>
|
||||||
|
{v}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex gap-4 mt-[15px] justify-center">
|
||||||
<div className="flex items-center gap-2">
|
<button
|
||||||
<TempleHinduIcon className="w-4 h-4 text-gray-700" />
|
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
onClick={(e) => {
|
||||||
{profile.caste}
|
e.stopPropagation();
|
||||||
</span>
|
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||||
</div>
|
}}
|
||||||
<div className="flex items-center gap-2">
|
disabled={declineMutation.isPending}
|
||||||
<img src={Image} alt="" className="w-4 h-4 text-gray-700" />
|
>
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||||
{profile.zodiac1}
|
</button>
|
||||||
</span>
|
|
||||||
</div>
|
<button
|
||||||
</div>
|
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||||
<div className="flex items-center gap-4">
|
onClick={(e) => {
|
||||||
<div className="flex items-center gap-2">
|
e.stopPropagation();
|
||||||
<img src={Image1} alt="" className="w-4 h-4 text-gray-700" />
|
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
}}
|
||||||
{profile.zodiac2}
|
disabled={interestMutation.isPending}
|
||||||
</span>
|
>
|
||||||
</div>
|
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MatchingList = ({ matches }) => {
|
||||||
|
const swiperRef = useRef(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const displayProfiles = matches || [];
|
||||||
|
if (displayProfiles.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -341,8 +254,8 @@ const MatchingList = () => {
|
|||||||
}}
|
}}
|
||||||
className="pb-16"
|
className="pb-16"
|
||||||
>
|
>
|
||||||
{profiles.map((profile) => (
|
{displayProfiles.map((profile, index) => (
|
||||||
<SwiperSlide key={profile.id}>
|
<SwiperSlide key={profile.id || index}>
|
||||||
<ProfileCard profile={profile} />
|
<ProfileCard profile={profile} />
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
@ -383,7 +296,10 @@ const MatchingList = () => {
|
|||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
|
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
|
||||||
onClick={() => navigate("/matches")}
|
onClick={() => {
|
||||||
|
dispatch(updateFilter({ filter_type: "all_matches" }));
|
||||||
|
navigate("/matches", { state: { activeTab: "allmatches", filter_type: "all_matches" } });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
View All Matches
|
View All Matches
|
||||||
</motion.button>
|
</motion.button>
|
||||||
@ -391,7 +307,7 @@ const MatchingList = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Swiper Styles */}
|
{/* Custom Swiper Styles */}
|
||||||
<style jsx global>{`
|
<style>{`
|
||||||
.swiper-pagination-bullet {
|
.swiper-pagination-bullet {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@ -5,48 +5,13 @@ import "swiper/css/navigation";
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
|
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import LazyImage from "../common/LazyImage";
|
|
||||||
import weddingImg1 from "../../assets/images/wedding6.jpeg";
|
|
||||||
import weddingImg2 from "../../assets/images/wedding8.jpg";
|
|
||||||
import weddingImg3 from "../../assets/images/wedding7.jpg";
|
|
||||||
|
|
||||||
|
const MatrimonyArticles = ({ articles }) => {
|
||||||
|
|
||||||
const articleData = [
|
|
||||||
{
|
|
||||||
title: "Marriage is not just finding the right partner, it's creating a lifetime of moments together Find someone who understands your heart and walks with you in every season.",
|
|
||||||
img: weddingImg1,
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Top 10 Qualities for a Happy Marriage, A perfect match begins with trust, respect, and shared dreams",
|
|
||||||
img: weddingImg2
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: "Expert Tips for a Strong Relationship, A perfect match begins with trust, respect, and shared dreams",
|
|
||||||
img: weddingImg3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "How to Build Trust in Marriage, A perfect match begins with trust, respect, and shared dreams",
|
|
||||||
img: weddingImg1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Communication Secrets for Couples, A perfect match begins with trust, respect, and shared dreams,A perfect match begins with trust, respect, and shared dreams.A perfect match begins with trust, respect, and shared dreams.A perfect match begins with trust, respect, and shared dreams,Real relationships are built on honesty, compassion, and understanding.,Real relationships are built on honesty, compassion, and understanding.",
|
|
||||||
img: weddingImg1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const twoLineStyle = {
|
|
||||||
display: '-webkit-box',
|
|
||||||
WebkitBoxOrient: 'vertical',
|
|
||||||
WebkitLineClamp: 2,
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'normal'
|
|
||||||
};
|
|
||||||
const MatrimonyArticles = () => {
|
|
||||||
const swiperRef = useRef(null);
|
const swiperRef = useRef(null);
|
||||||
|
const displayArticles = articles || [];
|
||||||
|
|
||||||
|
if (displayArticles.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="custom-article-swiper py-10 px-2 max-w-[1400px] mx-auto my-10">
|
<div className="custom-article-swiper py-10 px-2 max-w-[1400px] mx-auto my-10">
|
||||||
@ -70,51 +35,19 @@ const MatrimonyArticles = () => {
|
|||||||
loop={true}
|
loop={true}
|
||||||
className="mySwiper"
|
className="mySwiper"
|
||||||
>
|
>
|
||||||
{articleData.map((item, index) => (
|
{displayArticles.map((item) => (
|
||||||
<SwiperSlide key={index}>
|
<SwiperSlide key={item.id}>
|
||||||
<div className="w-full max-w-[600px] rounded-[10px] overflow-hidden border border-2 border-[#f2f2f2] bg-white">
|
<div className="w-full max-w-[600px] rounded-[10px] overflow-hidden border border-2 border-[#f2f2f2] bg-white">
|
||||||
|
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<div classname=" bg-gray-200 overflow-hidden w-full max-w-sm " style={{height:"240px"}}>
|
<div className="bg-gray-200 overflow-hidden w-full max-w-sm" style={{height:"240px"}}>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={item.img}
|
src={item.image}
|
||||||
className="w-full h-88 object-cover"
|
className="w-full h-full object-cover"
|
||||||
alt={item.title}
|
alt={`Article ${item.id}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* <LazyImage
|
|
||||||
src={item.img}
|
|
||||||
className="w-full h-48 object-cover"
|
|
||||||
alt={item.title}
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
{/* White Gradient Overlay at bottom of image */}
|
|
||||||
|
|
||||||
|
|
||||||
{/* Profile Info Overlay - positioned at bottom */}
|
|
||||||
<div
|
|
||||||
className="px-4 pb-4 flex flex-col gap-2 relative "
|
|
||||||
style={{
|
|
||||||
background: "rgb(255, 255, 255)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="z-9 absolute top-[-40px] left-0 right-0 h-10 pointer-events-none"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="z-10 relative pb-4" style={{
|
|
||||||
background: "rgb(255, 255, 255)",
|
|
||||||
}}>
|
|
||||||
<h3 className="text-[16px] font-semibold two-line-ellipsis" style={twoLineStyle} >{item.title}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,19 @@
|
|||||||
|
|
||||||
import { Phone, MessageCircle, ThumbsUp, Eye } from 'lucide-react';
|
import { Phone, MessageCircle, ThumbsUp, Eye } from 'lucide-react';
|
||||||
import promogirl from "../../assets/images/mobile.png"
|
import promogirl from "../../assets/images/mobile.png"
|
||||||
const MembershipCard = ()=>{
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const MembershipCard = ({ becomePaidMember })=>{
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const offerPercentage = becomePaidMember?.offer_percentage
|
||||||
|
? parseInt(becomePaidMember.offer_percentage)
|
||||||
|
: 58;
|
||||||
|
|
||||||
|
const points = becomePaidMember?.points || [];
|
||||||
|
|
||||||
|
const icons = [Phone, MessageCircle, Eye, ThumbsUp];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<>
|
<>
|
||||||
@ -19,50 +31,31 @@ return (
|
|||||||
|
|
||||||
{/* Subheading with offer */}
|
{/* Subheading with offer */}
|
||||||
<p className="text-[16px] lg:text-[16px] text-gray-900 mb-4">
|
<p className="text-[16px] lg:text-[16px] text-gray-900 mb-4">
|
||||||
Get up to <span className="text-red-600 font-bold">58% OFF</span> on paid membership!
|
Get up to <span className="text-red-600 font-bold">{offerPercentage}% OFF</span> on paid membership!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Features List */}
|
{/* Features List */}
|
||||||
<div className="space-y-2 mb-2">
|
<div className="space-y-2 mb-2">
|
||||||
<div className="flex items-start gap-4">
|
{points.map((point, index) => {
|
||||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
const Icon = icons[index % icons.length];
|
||||||
<Phone className="w-5 h-5 text-gray-700" />
|
return (
|
||||||
</div>
|
<div key={index} className="flex items-start gap-4">
|
||||||
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
||||||
Call/WhatsApp matches
|
<Icon className="w-5 h-5 text-gray-700" />
|
||||||
</p>
|
</div>
|
||||||
</div>
|
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
||||||
|
{point}
|
||||||
<div className="flex items-start gap-4">
|
</p>
|
||||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
</div>
|
||||||
<MessageCircle className="w-5 h-5 text-gray-700" />
|
);
|
||||||
</div>
|
})}
|
||||||
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
|
||||||
Unlimited messages
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
|
||||||
<ThumbsUp className="w-5 h-5 text-gray-700" />
|
|
||||||
</div>
|
|
||||||
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
|
||||||
Higher chances of response
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
|
||||||
<Eye className="w-5 h-5 text-gray-700" />
|
|
||||||
</div>
|
|
||||||
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
|
||||||
View and match horoscopes
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button */}
|
{/* CTA Button */}
|
||||||
<button className="mt-2 bg-[#034E08] hover:bg-[#A70710] text-white font-bold text-[14px] lg:text-[14px] py-4 px-6 rounded-full shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105">
|
<button
|
||||||
|
onClick={() => navigate('/subscription-plan')}
|
||||||
|
className="mt-2 bg-[#034E08] hover:bg-[#A70710] text-white font-bold text-[14px] lg:text-[14px] py-4 px-6 rounded-full shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105"
|
||||||
|
>
|
||||||
See membership plans
|
See membership plans
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -86,4 +79,3 @@ return (
|
|||||||
</>);
|
</>);
|
||||||
};
|
};
|
||||||
export default MembershipCard
|
export default MembershipCard
|
||||||
|
|
||||||
|
|||||||
@ -1,116 +1,88 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
import { Navigation, Pagination, Autoplay, EffectCoverflow } from 'swiper/modules';
|
import { Navigation, Pagination, Autoplay, EffectCoverflow } from 'swiper/modules';
|
||||||
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
|
import {
|
||||||
import CakeIcon from "@mui/icons-material/Cake";
|
Crown,
|
||||||
import HeightIcon from "@mui/icons-material/Height";
|
Bookmark,
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
X,
|
||||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
ChevronLeft,
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
ChevronRight,
|
||||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
Heart,
|
||||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
Eye,
|
||||||
import profilebg from "../../assets/images/profilebg.jpg";
|
} from 'lucide-react';
|
||||||
// Import Swiper styles
|
// Import Swiper styles
|
||||||
import 'swiper/css';
|
import 'swiper/css';
|
||||||
import 'swiper/css/navigation';
|
import 'swiper/css/navigation';
|
||||||
import 'swiper/css/pagination';
|
import 'swiper/css/pagination';
|
||||||
import 'swiper/css/effect-coverflow';
|
import 'swiper/css/effect-coverflow';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
const NewJoinedProfile = () => {
|
import { updateFilter } from "../../redux/filterSlice";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
const swiperRef = useRef(null);
|
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
// Sample profile data
|
|
||||||
const profiles = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'Selva Kumar . R',
|
|
||||||
userId: 'TK52586A',
|
|
||||||
lastSeen: '14 Nov 25',
|
|
||||||
age: 23,
|
|
||||||
height: '5\'2"',
|
|
||||||
religion: 'Hindu / Agamudayar / Thuluva Vellal',
|
|
||||||
education: 'BCA, Data Analyst',
|
|
||||||
location: 'Vellore, Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=500&fit=crop',
|
|
||||||
isPremium: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Priya Sharma',
|
|
||||||
userId: 'TK52587B',
|
|
||||||
lastSeen: '15 Nov 25',
|
|
||||||
age: 25,
|
|
||||||
height: '5\'4"',
|
|
||||||
religion: 'Hindu / Brahmin / Iyer',
|
|
||||||
education: 'MBA, Marketing Manager',
|
|
||||||
location: 'Chennai, Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=500&fit=crop',
|
|
||||||
isPremium: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'Rahul Venkat',
|
|
||||||
userId: 'TK52588C',
|
|
||||||
lastSeen: '16 Nov 25',
|
|
||||||
age: 28,
|
|
||||||
height: '5\'10"',
|
|
||||||
religion: 'Hindu / Mudaliar / Arcot',
|
|
||||||
education: 'B.Tech, Software Engineer',
|
|
||||||
location: 'Bangalore, Karnataka',
|
|
||||||
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=500&fit=crop',
|
|
||||||
isPremium: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'Aishwarya Reddy',
|
|
||||||
userId: 'TK52589D',
|
|
||||||
lastSeen: '17 Nov 25',
|
|
||||||
age: 26,
|
|
||||||
height: '5\'5"',
|
|
||||||
religion: 'Hindu / Reddy / Telangana',
|
|
||||||
education: 'CA, Chartered Accountant',
|
|
||||||
location: 'Hyderabad, Telangana',
|
|
||||||
image: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=500&fit=crop',
|
|
||||||
isPremium: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: 'Karthik Mohan',
|
|
||||||
userId: 'TK52590E',
|
|
||||||
lastSeen: '18 Nov 25',
|
|
||||||
age: 27,
|
|
||||||
height: '5\'8"',
|
|
||||||
religion: 'Hindu / Nadar / Tamil',
|
|
||||||
education: 'M.Tech, Civil Engineer',
|
|
||||||
location: 'Madurai, Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=400&h=500&fit=crop',
|
|
||||||
isPremium: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
name: 'Divya Lakshmi',
|
|
||||||
userId: 'TK52591F',
|
|
||||||
lastSeen: '19 Nov 25',
|
|
||||||
age: 24,
|
|
||||||
height: '5\'3"',
|
|
||||||
religion: 'Hindu / Pillai / Tamil',
|
|
||||||
education: 'B.Com, HR Executive',
|
|
||||||
location: 'Coimbatore, Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=500&fit=crop',
|
|
||||||
isPremium: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Profile Card Component
|
|
||||||
|
|
||||||
|
|
||||||
const ProfileCard = ({ profile }) => {
|
const ProfileCard = ({ profile }) => {
|
||||||
const [isLiked, setIsLiked] = useState(false);
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
|
}, [profile?.is_shortlisted]);
|
||||||
|
|
||||||
|
const shortlistMutation = useMutation({
|
||||||
|
mutationFn: shortlistProfile,
|
||||||
|
onMutate: () => {
|
||||||
|
setIsShortlisted((prev) => !prev);
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message || "Profile shortlisted successfully.");
|
||||||
|
// Invalidating queries will refetch data and update all profile cards simultaneously
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
|
toast.error(error.message || "Failed to update shortlist status.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const interestMutation = useMutation({
|
||||||
|
mutationFn: sendInterest,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message || "Interest sent successfully.");
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message || "Failed to send interest.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const declineMutation = useMutation({
|
||||||
|
mutationFn: declineProfile,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message || "Profile declined.");
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message || "Failed to decline profile.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = profile.id;
|
||||||
|
const image = profile.photo || profile.image;
|
||||||
|
const name = profile.name || "Unknown";
|
||||||
|
const idNumber = profile.member_id || profile.userId || "N/A";
|
||||||
|
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
|
||||||
|
const age = profile.age ? `${profile.age} yrs` : null;
|
||||||
|
const height = profile.height || null;
|
||||||
|
const salary = profile.annual_income_name || profile.salary || null;
|
||||||
|
const location = profile.district_name || profile.location || null;
|
||||||
|
const caste = profile.caste_name || profile.caste || null;
|
||||||
|
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
|
||||||
|
const zodiac2 = profile.star_name || profile.zodiac2 || null;
|
||||||
|
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : profile.isPremium;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -118,173 +90,110 @@ const ProfileCard = ({ profile }) => {
|
|||||||
whileInView={{ opacity: 1, scale: 1 }}
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
onClick={() => navigate(`/profile-details/${id}`)}
|
||||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
|
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
|
||||||
>
|
>
|
||||||
|
{/* IMAGE SECTION */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{profile.isPremium && (
|
<img
|
||||||
<motion.div
|
src={image}
|
||||||
initial={{ scale: 0 }}
|
alt="profile"
|
||||||
animate={{ scale: 1 }}
|
className="w-full h-[320px] object-cover"
|
||||||
transition={{ delay: 0.2, type: 'spring' }}
|
onError={(e) => {
|
||||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
>
|
}}
|
||||||
<Crown className="w-5 h-5 text-white" />
|
/>
|
||||||
</motion.div>
|
|
||||||
|
{isPremium && (
|
||||||
|
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
||||||
|
<Crown size={18} color="#fff" />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<motion.button
|
<div
|
||||||
whileHover={{ scale: 1 }}
|
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
|
||||||
whileTap={{ scale: 0.9 }}
|
|
||||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (!shortlistMutation.isPending) {
|
||||||
|
shortlistMutation.mutate(id);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Bookmark className="w-4 h-4" />
|
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||||
<span className="text-[12px] font-medium">Shortlist</span>
|
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
|
||||||
style={{ height: "300px" }}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={profile.image}
|
|
||||||
alt={profile.name}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
|
||||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
|
||||||
{profile.name}
|
|
||||||
</h1>
|
|
||||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
|
||||||
Matrimony ID: {profile.userId}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{/* CONTENT */}
|
||||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||||
style={{ background: "rgb(255, 255, 255)" }}
|
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||||
>
|
{name}
|
||||||
<div className="flex items-center gap-4">
|
</h2>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
|
||||||
Age : {profile.age}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
<p>ID: {idNumber}</p>
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<p className="flex items-center gap-0.5">
|
||||||
Height: {profile.height}
|
<Eye size={12} /> {lastSeen}
|
||||||
</span>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
{[
|
||||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
age,
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
height,
|
||||||
{profile.religion}
|
salary,
|
||||||
</span>
|
location,
|
||||||
</div>
|
caste,
|
||||||
</div>
|
zodiac1,
|
||||||
|
zodiac2,
|
||||||
<div className="flex items-center gap-4">
|
]
|
||||||
<div className="flex items-center gap-2">
|
.filter(Boolean)
|
||||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
.map((v, i) => (
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span
|
||||||
{profile.education}
|
key={i}
|
||||||
</span>
|
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
|
||||||
{profile.location}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
|
||||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
|
||||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M18 6L6 18M6 6l12 12"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Decline
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
|
||||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
|
||||||
transform hover:scale-105 flex items-center justify-center"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsLiked(!isLiked);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isLiked ? (
|
|
||||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
|
||||||
fill="#EF4444"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
>
|
||||||
<path
|
{v}
|
||||||
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"
|
</span>
|
||||||
strokeLinecap="round"
|
))}
|
||||||
strokeLinejoin="round"
|
</div>
|
||||||
/>
|
|
||||||
</svg>
|
<div className="flex gap-4 mt-[15px] justify-center">
|
||||||
)}
|
{/* <button
|
||||||
Interest
|
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
disabled={declineMutation.isPending}
|
||||||
|
>
|
||||||
|
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||||
|
</button> */}
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
disabled={interestMutation.isPending}
|
||||||
|
>
|
||||||
|
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NewJoinedProfile = ({ profiles }) => {
|
||||||
|
const swiperRef = useRef(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const displayProfiles = profiles || [];
|
||||||
|
if (displayProfiles.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
@ -301,10 +210,10 @@ const ProfileCard = ({ profile }) => {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="text-center mb-12"
|
className="text-center mb-12"
|
||||||
>
|
>
|
||||||
<h1 className="text-[20px] text-[#034E08] sm:text-[22px] lg:text-[24px] font-bold mb-3">
|
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||||
New Joined
|
New Joined
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 text-[12px]">Find your perfect match today</p>
|
<p className="text-gray-900 text-[12px]">Find your perfect match today</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Swiper Container */}
|
{/* Swiper Container */}
|
||||||
@ -319,10 +228,6 @@ const ProfileCard = ({ profile }) => {
|
|||||||
disableOnInteraction: false,
|
disableOnInteraction: false,
|
||||||
pauseOnMouseEnter: true
|
pauseOnMouseEnter: true
|
||||||
}}
|
}}
|
||||||
pagination={{
|
|
||||||
clickable: true,
|
|
||||||
dynamicBullets: true
|
|
||||||
}}
|
|
||||||
loop={true}
|
loop={true}
|
||||||
speed={800}
|
speed={800}
|
||||||
breakpoints={{
|
breakpoints={{
|
||||||
@ -341,8 +246,8 @@ const ProfileCard = ({ profile }) => {
|
|||||||
}}
|
}}
|
||||||
className="pb-16"
|
className="pb-16"
|
||||||
>
|
>
|
||||||
{profiles.map((profile) => (
|
{displayProfiles.map((profile, index) => (
|
||||||
<SwiperSlide key={profile.id}>
|
<SwiperSlide key={profile.id || index}>
|
||||||
<ProfileCard profile={profile} />
|
<ProfileCard profile={profile} />
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
@ -383,14 +288,18 @@ const ProfileCard = ({ profile }) => {
|
|||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
|
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(updateFilter({ filter_type: "newly_joined" }));
|
||||||
|
navigate("/matches", { state: { activeTab: "newly_joined", filter_type: "newly_joined" } });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
View All
|
View All Matches
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Swiper Styles */}
|
{/* Custom Swiper Styles */}
|
||||||
<style jsx global>{`
|
<style>{`
|
||||||
.swiper-pagination-bullet {
|
.swiper-pagination-bullet {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@ -9,8 +9,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import AstroChatUI from "./AstroChatUI";
|
import AstroChatUI from "./AstroChatUI";
|
||||||
import MembershipCard from "./MembershipCard";
|
import MembershipCard from "./MembershipCard";
|
||||||
|
|
||||||
const ProfileCompletion = () => {
|
const ProfileCompletion = ({ percentage = 0, missingDetails,becomePaidMember }) => {
|
||||||
const [completeness, setCompleteness] = useState(30);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
@ -95,7 +94,7 @@ const ProfileCompletion = () => {
|
|||||||
transition={{ delay: 0.6, type: "spring", stiffness: 200 }}
|
transition={{ delay: 0.6, type: "spring", stiffness: 200 }}
|
||||||
className="text-lg sm:text-xl font-bold text-gray-900"
|
className="text-lg sm:text-xl font-bold text-gray-900"
|
||||||
>
|
>
|
||||||
{completeness}%
|
{percentage}%
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -103,7 +102,7 @@ const ProfileCompletion = () => {
|
|||||||
<div className="relative h-3 bg-gray-200 rounded-full overflow-hidden">
|
<div className="relative h-3 bg-gray-200 rounded-full overflow-hidden">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ width: 0 }}
|
initial={{ width: 0 }}
|
||||||
animate={{ width: `${completeness}%` }}
|
animate={{ width: `${percentage}%` }}
|
||||||
transition={{ delay: 0.8, duration: 1, ease: "easeOut" }}
|
transition={{ delay: 0.8, duration: 1, ease: "easeOut" }}
|
||||||
className="absolute top-0 left-0 h-full bg-gradient-to-r from-red-500 to-red-600 rounded-full"
|
className="absolute top-0 left-0 h-full bg-gradient-to-r from-red-500 to-red-600 rounded-full"
|
||||||
/>
|
/>
|
||||||
@ -123,22 +122,24 @@ const ProfileCompletion = () => {
|
|||||||
</Box> */}
|
</Box> */}
|
||||||
|
|
||||||
<AstroChatUI/>
|
<AstroChatUI/>
|
||||||
<div className="my-4 rounded-2xl bg-green-50 border border-1 border-green-200 p-4 ">
|
|
||||||
<motion.div
|
<div className="my-4 rounded-2xl bg-green-50 border border-1 border-green-200 p-4 ">
|
||||||
variants={container}
|
<motion.div
|
||||||
initial="hidden"
|
variants={container}
|
||||||
animate="show"
|
initial="hidden"
|
||||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
animate="show"
|
||||||
>
|
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
||||||
|
>
|
||||||
{cards.map((card, index) => (
|
{/* NOTE: The cards are static for now. To make them dynamic,
|
||||||
|
the `missingDetails` prop should be an array of objects to map over. */}
|
||||||
<div
|
{cards.map((card, index) => (
|
||||||
onClick={() => navigate(card.url)}
|
<div
|
||||||
|
key={card.id}
|
||||||
className=" border border-1 border-red-50 bg-white rounded-3xl hover:bg-red-50 hover:border-2
|
onClick={() => navigate(card.url)}
|
||||||
|
className=" border border-1 border-red-50 bg-white rounded-3xl hover:bg-red-50 hover:border-2
|
||||||
flex flex-col items-center space-x-2 h-32 justify-center transition-colors duration-500
|
flex flex-col items-center space-x-2 h-32 justify-center transition-colors duration-500
|
||||||
cursor-pointer ">
|
cursor-pointer "
|
||||||
|
>
|
||||||
{/* Icon Container */}
|
{/* Icon Container */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ rotate: -180, opacity: 0 }}
|
initial={{ rotate: -180, opacity: 0 }}
|
||||||
@ -163,15 +164,12 @@ const ProfileCompletion = () => {
|
|||||||
{card.title}
|
{card.title}
|
||||||
</motion.h3>
|
</motion.h3>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<MembershipCard becomePaidMember={becomePaidMember} />
|
||||||
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<MembershipCard/>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* Additional Info Section */}
|
{/* Additional Info Section */}
|
||||||
{/* <motion.div
|
{/* <motion.div
|
||||||
|
|||||||
@ -2,113 +2,31 @@ import React, { useState, useRef } from 'react';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
import { Navigation, Pagination, Autoplay } from 'swiper/modules';
|
import { Navigation, Pagination, Autoplay } from 'swiper/modules';
|
||||||
import { Play, X, Heart, Share2, Eye, ChevronLeft, ChevronRight } from 'lucide-react';
|
import { Play, X, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import weddingImg1 from "../../assets/images/wedding6.jpeg";
|
|
||||||
import weddingImg2 from "../../assets/images/wedding8.jpg";
|
|
||||||
import weddingImg3 from "../../assets/images/wedding6.jpeg";
|
|
||||||
|
|
||||||
// Import Swiper styles
|
// Import Swiper styles
|
||||||
import 'swiper/css';
|
import 'swiper/css';
|
||||||
import 'swiper/css/navigation';
|
import 'swiper/css/navigation';
|
||||||
import 'swiper/css/pagination';
|
import 'swiper/css/pagination';
|
||||||
|
|
||||||
const VideoSwiperGallery = () => {
|
const VideoSwiperGallery = ({ videos }) => {
|
||||||
const [selectedVideo, setSelectedVideo] = useState(null);
|
const [selectedVideo, setSelectedVideo] = useState(null);
|
||||||
const swiperRef = useRef(null);
|
const swiperRef = useRef(null);
|
||||||
|
|
||||||
// Video data with online sources
|
const displayVideos = videos || [];
|
||||||
const videos = [
|
|
||||||
{
|
if (displayVideos.length === 0) return null;
|
||||||
id: 1,
|
|
||||||
title: 'Priya & Rahul - Wedding Story',
|
const getEmbedUrl = (url) => {
|
||||||
thumbnail: weddingImg1,
|
if (!url) return '';
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
let embedUrl = url;
|
||||||
views: '2.4K',
|
if (url.includes('youtu.be/')) {
|
||||||
likes: '142',
|
embedUrl = url.replace('youtu.be/', 'www.youtube.com/embed/');
|
||||||
duration: '3:45'
|
} else if (url.includes('youtube.com/watch?v=')) {
|
||||||
},
|
embedUrl = url.replace('youtube.com/watch?v=', 'youtube.com/embed/');
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'Aisha - Profile Introduction',
|
|
||||||
thumbnail: weddingImg2,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
|
|
||||||
views: '1.8K',
|
|
||||||
likes: '98',
|
|
||||||
duration: '2:30'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: 'Rohan - Life Journey',
|
|
||||||
thumbnail: weddingImg3,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
|
|
||||||
views: '3.2K',
|
|
||||||
likes: '256',
|
|
||||||
duration: '4:15'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: 'Divya - Family Values',
|
|
||||||
thumbnail: weddingImg1,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4',
|
|
||||||
views: '1.5K',
|
|
||||||
likes: '87',
|
|
||||||
duration: '3:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: 'Karthik & Meera - First Meet',
|
|
||||||
thumbnail: weddingImg2,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4',
|
|
||||||
views: '4.1K',
|
|
||||||
likes: '312',
|
|
||||||
duration: '5:20'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: 'Sneha - Hobbies & Interests',
|
|
||||||
thumbnail: weddingImg3,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4',
|
|
||||||
views: '2.7K',
|
|
||||||
likes: '178',
|
|
||||||
duration: '2:45'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
title: 'Arjun - Career & Dreams',
|
|
||||||
thumbnail: weddingImg1,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4',
|
|
||||||
views: '1.9K',
|
|
||||||
likes: '134',
|
|
||||||
duration: '3:30'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
title: 'Lakshmi - Traditional Values',
|
|
||||||
thumbnail: weddingImg2,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4',
|
|
||||||
views: '3.5K',
|
|
||||||
likes: '267',
|
|
||||||
duration: '4:00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
title: 'Vikram - Adventure Life',
|
|
||||||
thumbnail: weddingImg3,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
|
||||||
views: '5.2K',
|
|
||||||
likes: '423',
|
|
||||||
duration: '4:30'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
title: 'Anjali - Creative Journey',
|
|
||||||
thumbnail: weddingImg1,
|
|
||||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
|
|
||||||
views: '3.8K',
|
|
||||||
likes: '289',
|
|
||||||
duration: '3:15'
|
|
||||||
}
|
}
|
||||||
];
|
return embedUrl.split('?')[0] + '?autoplay=1';
|
||||||
|
};
|
||||||
|
|
||||||
const VideoCard = ({ video }) => {
|
const VideoCard = ({ video }) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
@ -129,12 +47,12 @@ const VideoSwiperGallery = () => {
|
|||||||
<div className="relative aspect-video">
|
<div className="relative aspect-video">
|
||||||
<img
|
<img
|
||||||
src={video.thumbnail}
|
src={video.thumbnail}
|
||||||
alt={video.title}
|
alt="Video Thumbnail"
|
||||||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Overlay */}
|
{/* Overlay */}
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent opacity-60 group-hover:opacity-80 transition-opacity" />
|
<div className="absolute inset-0 bg-black/30 group-hover:bg-black/50 transition-colors" />
|
||||||
|
|
||||||
{/* Play Button */}
|
{/* Play Button */}
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -146,28 +64,6 @@ const VideoSwiperGallery = () => {
|
|||||||
<Play className="w-7 h-7 text-[#034E08] group-hover:text-white ml-1" fill="currentColor" />
|
<Play className="w-7 h-7 text-[#034E08] group-hover:text-white ml-1" fill="currentColor" />
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Duration Badge */}
|
|
||||||
<div className="absolute top-2 right-2 bg-black/70 text-white text-xs font-semibold px-2 py-1 rounded">
|
|
||||||
{video.duration}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats at bottom */}
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-3">
|
|
||||||
<h3 className="text-white font-semibold text-sm mb-2 line-clamp-2">
|
|
||||||
{video.title}
|
|
||||||
</h3>
|
|
||||||
<div className="flex items-center gap-3 text-white/90 text-xs">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Eye className="w-3 h-3" />
|
|
||||||
<span>{video.views}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Heart className="w-3 h-3" />
|
|
||||||
<span>{video.likes}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -202,36 +98,13 @@ const VideoSwiperGallery = () => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Video Player */}
|
{/* Video Player */}
|
||||||
<div className="bg-[#034E08] rounded-2xl overflow-y-scroll h-[100%] max-h-[480px] shadow-2xl">
|
<div className="bg-black rounded-2xl overflow-hidden shadow-2xl">
|
||||||
<video
|
<iframe
|
||||||
src={selectedVideo.videoUrl}
|
src={getEmbedUrl(selectedVideo.youtube_url)}
|
||||||
controls
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
autoPlay
|
allowFullScreen
|
||||||
className="w-full aspect-video"
|
className="w-full aspect-video"
|
||||||
>
|
></iframe>
|
||||||
Your browser does not support the video tag.
|
|
||||||
</video>
|
|
||||||
|
|
||||||
{/* Video Info */}
|
|
||||||
<div className="bg-gray-900 p-6">
|
|
||||||
<h2 className="text-white text-2xl font-bold mb-3">
|
|
||||||
{selectedVideo.title}
|
|
||||||
</h2>
|
|
||||||
<div className="flex items-center gap-6 text-gray-300">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Eye className="w-5 h-5" />
|
|
||||||
<span>{selectedVideo.views} views</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Heart className="w-5 h-5" />
|
|
||||||
<span>{selectedVideo.likes} likes</span>
|
|
||||||
</div>
|
|
||||||
<button className="flex items-center gap-2 ml-auto bg-[#034E08] hover:bg-green-700 text-white px-6 py-2 rounded-full transition-colors">
|
|
||||||
<Share2 className="w-4 h-4" />
|
|
||||||
<span>Share</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -290,7 +163,7 @@ const VideoSwiperGallery = () => {
|
|||||||
}}
|
}}
|
||||||
className="pb-16"
|
className="pb-16"
|
||||||
>
|
>
|
||||||
{videos.map((video) => (
|
{displayVideos.map((video) => (
|
||||||
<SwiperSlide key={video.id}>
|
<SwiperSlide key={video.id}>
|
||||||
<VideoCard video={video} />
|
<VideoCard video={video} />
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
@ -343,7 +216,7 @@ const VideoSwiperGallery = () => {
|
|||||||
{selectedVideo && <VideoModal />}
|
{selectedVideo && <VideoModal />}
|
||||||
|
|
||||||
{/* Custom Swiper Styles */}
|
{/* Custom Swiper Styles */}
|
||||||
<style jsx global>{`
|
<style>{`
|
||||||
.swiper-pagination-bullet {
|
.swiper-pagination-bullet {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Heart,
|
Heart,
|
||||||
X,
|
X,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
SkipForward,
|
|
||||||
Bookmark,
|
Bookmark,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Ban,
|
Ban,
|
||||||
@ -19,8 +18,10 @@ import "swiper/css/navigation";
|
|||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
import "swiper/css/thumbs";
|
import "swiper/css/thumbs";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { sendInterest, shortlistProfile } from "../../services/shortlistapi";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
const MatrimonyProfile = () => {
|
const MatrimonyProfile = ({ data }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
@ -28,26 +29,17 @@ const MatrimonyProfile = () => {
|
|||||||
const mainSwiperRef = useRef(null);
|
const mainSwiperRef = useRef(null);
|
||||||
const modalSwiperRef = useRef(null);
|
const modalSwiperRef = useRef(null);
|
||||||
|
|
||||||
const profile = {
|
if (!data) return null;
|
||||||
name: "Sudharshan M",
|
|
||||||
id: "M8355880",
|
const profile = data.profile;
|
||||||
verified: true,
|
const personal = data.personalDetails;
|
||||||
lastSeen: "Last seen few hour ago",
|
const family = data.familyDetails;
|
||||||
age: "30 yrs",
|
const education = data.educationalDetails;
|
||||||
height: "5'5\"",
|
const lifestyle = data.lifestyleDetails;
|
||||||
caste: "Brahmin",
|
|
||||||
education: "Engineer - Non IT",
|
const profileImages = personal.images && personal.images.length > 0
|
||||||
location: "Chennai",
|
? personal.images
|
||||||
maritalStatus: "Never Married",
|
: [profile.profile_picture || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png"];
|
||||||
createdBy: "Profile created by sibling",
|
|
||||||
images: [
|
|
||||||
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=800&fit=crop",
|
|
||||||
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=600&h=800&fit=crop",
|
|
||||||
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=600&h=800&fit=crop",
|
|
||||||
"https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=600&h=800&fit=crop",
|
|
||||||
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=600&h=800&fit=crop",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const openModal = (index) => {
|
const openModal = (index) => {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
@ -58,6 +50,31 @@ const MatrimonyProfile = () => {
|
|||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendInterest = async () => {
|
||||||
|
try {
|
||||||
|
const res = await sendInterest(profile.id);
|
||||||
|
toast.success(res.message || "Interest sent successfully");
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error.message || "Failed to send interest");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShortlist = async () => {
|
||||||
|
try {
|
||||||
|
const res = await shortlistProfile(profile.id);
|
||||||
|
toast.success(res.message || "Profile shortlisted successfully");
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error.message || "Failed to shortlist");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const safeVal = (val, key) => {
|
||||||
|
if (typeof val === 'object' && val !== null) {
|
||||||
|
return val[key] || "N/A";
|
||||||
|
}
|
||||||
|
return val || "N/A";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<div
|
<div
|
||||||
@ -76,16 +93,12 @@ const MatrimonyProfile = () => {
|
|||||||
prevEl: ".swiper-button-prev-custom",
|
prevEl: ".swiper-button-prev-custom",
|
||||||
nextEl: ".swiper-button-next-custom",
|
nextEl: ".swiper-button-next-custom",
|
||||||
}}
|
}}
|
||||||
pagination={{
|
|
||||||
type: "fraction",
|
|
||||||
el: ".swiper-pagination-custom",
|
|
||||||
}}
|
|
||||||
onSwiper={(swiper) => {
|
onSwiper={(swiper) => {
|
||||||
mainSwiperRef.current = swiper;
|
mainSwiperRef.current = swiper;
|
||||||
}}
|
}}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
>
|
>
|
||||||
{profile.images.map((img, idx) => (
|
{profileImages.map((img, idx) => (
|
||||||
<SwiperSlide key={idx}>
|
<SwiperSlide key={idx}>
|
||||||
<div
|
<div
|
||||||
className="w-[320px] h-[330px] cursor-pointer"
|
className="w-[320px] h-[330px] cursor-pointer"
|
||||||
@ -95,35 +108,21 @@ const MatrimonyProfile = () => {
|
|||||||
src={img}
|
src={img}
|
||||||
alt={`${profile.name} ${idx + 1}`}
|
alt={`${profile.name} ${idx + 1}`}
|
||||||
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
|
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
||||||
{/* Swiper Navigation Buttons */}
|
|
||||||
<button className="swiper-button-prev-custom absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-2 rounded-full hover:bg-black/70 transition-colors">
|
<button className="swiper-button-prev-custom absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-2 rounded-full hover:bg-black/70 transition-colors">
|
||||||
<ChevronLeft className="w-5 h-5" />
|
<ChevronLeft className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
<button className="swiper-button-next-custom absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-2 rounded-full hover:bg-black/70 transition-colors">
|
<button className="swiper-button-next-custom absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-2 rounded-full hover:bg-black/70 transition-colors">
|
||||||
<ChevronRight className="w-5 h-5" />
|
<ChevronRight className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Pagination */}
|
|
||||||
{/* <div className="swiper-pagination-custom absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/50 text-white px-3 py-1 rounded-full text-sm z-10"></div> */}
|
|
||||||
|
|
||||||
{/* Thumbnail Navigation */}
|
|
||||||
{/* <div className="absolute bottom-0 right-3 flex flex-row gap-2 z-10">
|
|
||||||
{profile.images.slice(0, 4).map((img, idx) => (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className="w-12 h-12 rounded-lg overflow-hidden border-2 border-white cursor-pointer hover:scale-110 transition-transform shadow-lg"
|
|
||||||
onClick={() => mainSwiperRef.current?.slideTo(idx)}
|
|
||||||
>
|
|
||||||
<img src={img} alt="" className="w-full h-full object-cover" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ const MatrimonyProfile = () => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[#034E08] font-semibold">ID verified</span>
|
<span className="text-[#034E08] font-semibold">{profile.approved ? "ID verified" : "Pending Verification"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
@ -161,10 +160,16 @@ const MatrimonyProfile = () => {
|
|||||||
</button>
|
</button>
|
||||||
{showMenu && (
|
{showMenu && (
|
||||||
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-xl border z-10">
|
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-xl border z-10">
|
||||||
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
<button
|
||||||
|
onClick={() => { setShowMenu(false); handleShortlist(); }}
|
||||||
|
className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3"
|
||||||
|
>
|
||||||
<Bookmark className="w-4 h-4" /> Shortlist
|
<Bookmark className="w-4 h-4" /> Shortlist
|
||||||
</button>
|
</button>
|
||||||
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
<button
|
||||||
|
onClick={() => { setShowMenu(false); navigate("/chat"); }}
|
||||||
|
className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3"
|
||||||
|
>
|
||||||
<MessageCircle className="w-4 h-4" /> Send Message
|
<MessageCircle className="w-4 h-4" /> Send Message
|
||||||
</button>
|
</button>
|
||||||
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
||||||
@ -182,44 +187,47 @@ const MatrimonyProfile = () => {
|
|||||||
{profile.name}
|
{profile.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-500 text-sm mb-4">
|
<p className="text-gray-500 text-sm mb-4">
|
||||||
{profile.id} | {profile.lastSeen}
|
{profile.member_id} | {profile.last_seen_at}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-2 mb-6 text-gray-700">
|
<div className="space-y-2 mb-6 text-gray-700">
|
||||||
<p className="flex flex-wrap gap-2">
|
<p className="flex flex-wrap gap-2 items-center">
|
||||||
<span className="font-semibold">{profile.maritalStatus}</span>
|
|
||||||
<span>•</span>
|
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-sm text-gray-500">
|
||||||
{profile.createdBy}
|
Profile created by {personal.profile_for || "N/A"}
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
{(personal.age || profile.age) && (
|
||||||
<span>{profile.age}</span>
|
<>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>{profile.height}</span>
|
<span className="text-sm">{personal.age || profile.age} yrs</span>
|
||||||
<span>•</span>
|
</>
|
||||||
<span>{profile.caste}</span>
|
)}
|
||||||
</p>
|
{(profile.religion || profile.caste || profile.sub_caste || profile.college_name) && (
|
||||||
<p>
|
<>
|
||||||
<span className="font-semibold">{profile.education}</span>
|
<span>•</span>
|
||||||
<span> • </span>
|
<span className="text-sm">
|
||||||
<span>{profile.location}</span>
|
{[
|
||||||
|
safeVal(profile.religion, 'religion_name'),
|
||||||
|
safeVal(profile.caste, 'caste_name'),
|
||||||
|
safeVal(profile.sub_caste, 'sub_caste_name'),
|
||||||
|
profile.college_name
|
||||||
|
].filter(v => v !== "N/A" && v !== undefined).join(" / ")}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<div className="flex justify-start gap-3">
|
<div className="flex justify-start gap-3">
|
||||||
<button
|
<button
|
||||||
// onClick={()=>{
|
className="w-[fit-content] border-2 border-gray-300 text-gray-700 py-2 px-6 rounded-full hover:bg-gray-50 transition-colors flex items-center justify-center gap-2 text-sm"
|
||||||
// navigate("/chat")
|
>
|
||||||
// }}
|
|
||||||
className="w-[fit-content] border-2 border-gray-300 text-gray-700 py-2 px-6 rounded-full hover:bg-gray-50 transition-colors flex items-center justify-center gap-2 text-sm">
|
|
||||||
<X className="w-5 h-5" /> Don't Show
|
<X className="w-5 h-5" /> Don't Show
|
||||||
{/* Message */}
|
|
||||||
</button>
|
</button>
|
||||||
{/* <button className="w-[fit-content] border-2 border-orange-500 text-[#034E08] py-2 px-6 rounded-full hover:bg-orange-50 transition-colors flex items-center justify-center gap-2 text-sm">
|
<button
|
||||||
<SkipForward className="w-5 h-5" /> Skip
|
onClick={handleSendInterest}
|
||||||
</button> */}
|
className="w-[fit-content] bg-[#034E08] text-white py-2 px-6 rounded-full hover:bg-[#A70710] transition-colors flex items-center justify-center gap-2 font-semibold text-sm"
|
||||||
<button className="w-[fit-content] bg-[#034E08] text-white py-2 px-6 rounded-full hover:bg-[#A70710] transition-colors flex items-center justify-center gap-2 font-semibold text-sm">
|
>
|
||||||
<Heart className="w-5 h-5" /> Send Interest
|
<Heart className="w-5 h-5" /> Send Interest
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -227,7 +235,7 @@ const MatrimonyProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Image Modal with Swiper */}
|
{/* Image Modal */}
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<div
|
<div
|
||||||
style={{ backdropFilter: "blur(5px)" }}
|
style={{ backdropFilter: "blur(5px)" }}
|
||||||
@ -243,7 +251,6 @@ const MatrimonyProfile = () => {
|
|||||||
<div className="max-w-4xl w-full bg-white p-4 rounded-md">
|
<div className="max-w-4xl w-full bg-white p-4 rounded-md">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
{/* Main Modal Swiper */}
|
|
||||||
<div
|
<div
|
||||||
className="relative bg-gray-900 rounded-lg overflow-hidden"
|
className="relative bg-gray-900 rounded-lg overflow-hidden"
|
||||||
style={{ height: "65vh" }}
|
style={{ height: "65vh" }}
|
||||||
@ -271,20 +278,22 @@ const MatrimonyProfile = () => {
|
|||||||
}}
|
}}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
>
|
>
|
||||||
{profile.images.map((img, idx) => (
|
{profileImages.map((img, idx) => (
|
||||||
<SwiperSlide key={idx}>
|
<SwiperSlide key={idx}>
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
<img
|
<img
|
||||||
src={img}
|
src={img}
|
||||||
alt={`${profile.name} ${idx + 1}`}
|
alt={`${profile.name} ${idx + 1}`}
|
||||||
className="max-w-full max-h-full object-contain"
|
className="max-w-full max-h-full object-contain"
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
||||||
{/* Modal Navigation Buttons */}
|
|
||||||
<button className="modal-swiper-button-prev absolute left-4 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-3 rounded-full hover:bg-black/70 transition-colors">
|
<button className="modal-swiper-button-prev absolute left-4 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-3 rounded-full hover:bg-black/70 transition-colors">
|
||||||
<ChevronLeft className="w-6 h-6" />
|
<ChevronLeft className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
@ -295,24 +304,13 @@ const MatrimonyProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{/* Top Info Bar */}
|
|
||||||
<div className="bg-white rounded-t-lg p-4 mb-2">
|
<div className="bg-white rounded-t-lg p-4 mb-2">
|
||||||
<div className="flex items-center justify-between">
|
<h3 className="font-bold text-lg">{profile.name}</h3>
|
||||||
<div className="swiper-pagination-modal text-lg font-semibold"></div>
|
<p className="text-sm text-gray-600">
|
||||||
<div className="text-right">
|
{profile.member_id} | Profile created by {personal.profile_for}
|
||||||
<h3 className="font-bold text-lg">{profile.name}</h3>
|
</p>
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
{profile.id} | {profile.createdBy}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
{profile.age} • {profile.height} • {profile.caste} • BE
|
|
||||||
• {profile.education} • {profile.location}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Thumbnail Swiper */}
|
|
||||||
<Swiper
|
<Swiper
|
||||||
modules={[Thumbs]}
|
modules={[Thumbs]}
|
||||||
watchSlidesProgress
|
watchSlidesProgress
|
||||||
@ -321,25 +319,28 @@ const MatrimonyProfile = () => {
|
|||||||
slidesPerView={5}
|
slidesPerView={5}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
>
|
>
|
||||||
{profile.images.map((img, idx) => (
|
{profileImages.map((img, idx) => (
|
||||||
<SwiperSlide key={idx}>
|
<SwiperSlide key={idx}>
|
||||||
<div className="w-full h-16 rounded-lg overflow-hidden border-2 border-white cursor-pointer hover:border-orange-500 transition-colors">
|
<div className="w-full h-16 rounded-lg overflow-hidden border-2 border-white cursor-pointer hover:border-orange-500 transition-colors">
|
||||||
<img
|
<img
|
||||||
src={img}
|
src={img}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
||||||
{/* Bottom Action Bar */}
|
|
||||||
<div className="bg-white p-4 rounded-b-lg mt-2 text-center">
|
<div className="bg-white p-4 rounded-b-lg mt-2 text-center">
|
||||||
<p className="text-sm text-gray-600 mb-2">
|
<p className="text-sm text-gray-600 mb-2">Like this member?</p>
|
||||||
Like this member?
|
<button
|
||||||
</p>
|
onClick={handleSendInterest}
|
||||||
<button className="bg-[#034E08] text-white px-8 py-2 rounded-full hover:bg-orange-700 transition-colors font-semibold">
|
className="bg-[#034E08] text-white px-8 py-2 rounded-full hover:bg-orange-700 transition-colors font-semibold"
|
||||||
|
>
|
||||||
Send Interest
|
Send Interest
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -351,19 +352,11 @@ const MatrimonyProfile = () => {
|
|||||||
|
|
||||||
<div className="w-[100%] max-w-[1200px] mx-auto my-10 grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="w-[100%] max-w-[1200px] mx-auto my-10 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{/* Personal Information Section */}
|
{/* Personal Information Section */}
|
||||||
<div className=" border border-gray-200 rounded-lg bg-pink-50/30">
|
<div className="border border-gray-200 rounded-lg bg-pink-50/30">
|
||||||
<div className="flex items-center gap-2 mb-4 p-3 py-3 bg-green-100">
|
<div className="flex items-center gap-2 mb-4 p-3 py-3 bg-green-100">
|
||||||
<div className="bg-pink-100 p-2 rounded-full">
|
<div className="bg-pink-100 p-2 rounded-full">
|
||||||
<svg
|
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||||
className="w-5 h-5 text-[#A70710]"
|
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-lg">Personal Information</h3>
|
<h3 className="font-semibold text-lg">Personal Information</h3>
|
||||||
@ -371,188 +364,103 @@ const MatrimonyProfile = () => {
|
|||||||
|
|
||||||
<div className="p-5 mb-6 space-y-3 text-sm">
|
<div className="p-5 mb-6 space-y-3 text-sm">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Age</span>
|
<span className="text-gray-600 w-40">Name</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">30 Years and 8 months</span>
|
<span className="ml-3 text-gray-900">{profile.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Gender</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{personal.gender || profile.type || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Date of Birth</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{personal.dob || profile.dob || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Place of Birth</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.place_of_birth || personal.place_of_birth || profile.place_of_birth || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Time of Birth</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.time_of_birth || personal.time_of_birth || "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Height</span>
|
<span className="text-gray-600 w-40">Height</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">5'5"</span>
|
<span className="ml-3 text-gray-900">{profile.height ? `${profile.height} ft` : "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Weight</span>
|
<span className="text-gray-600 w-40">Weight</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">97 Kg</span>
|
<span className="ml-3 text-gray-900">{profile.weight ? `${profile.weight} Kg` : "N/A"}</span>
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Body Type</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Average</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Spoken Languages</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">
|
|
||||||
Tamil (Mother Tongue), English, Hindi
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Profile Created By</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Sibling</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Marital Status</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Never Married</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Lives In</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Chennai, Tamil Nadu</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Eating Habits</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Vegetarian</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Religion</span>
|
<span className="text-gray-600 w-40">Religion</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Hindu</span>
|
<span className="ml-3 text-gray-900">{personal.religion || safeVal(profile.religion, 'religion_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Profile Created By</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{personal.profile_for || safeVal(profile.profile_for, 'profile_for_name')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Caste</span>
|
<span className="text-gray-600 w-40">Caste</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Brahmin - Iyer</span>
|
<span className="ml-3 text-gray-900">{personal.caste || safeVal(profile.caste, 'caste_name')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Subcaste</span>
|
<span className="text-gray-600 w-40">Sub Caste</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Brahacharmam</span>
|
<span className="ml-3 text-gray-900">{personal.sub_caste || safeVal(profile.sub_caste, 'sub_caste_name')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Gothra(m)</span>
|
<span className="text-gray-600 w-40">Gothram</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Kashyapa / Kaashyapa</span>
|
<span className="ml-3 text-gray-900">{personal.gothram || safeVal(profile.gothram, 'gothram_name')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Dosha(m)</span>
|
<span className="text-gray-600 w-40">Rasi</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Don't know</span>
|
<span className="ml-3 text-gray-900">{personal.raasi || safeVal(profile.raasi, 'raasi_name')}</span>
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="text-gray-600 w-40">Date Of Birth</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900"> 23-12-1991</span>
|
|
||||||
{/* <button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Upgrade to view
|
|
||||||
</button> */}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="text-gray-600 w-40">Star</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Piscus</span>
|
|
||||||
{/* <button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Upgrade to view
|
|
||||||
</button> */}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="text-gray-600 w-40">Rassi</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900"> Revathy</span>
|
|
||||||
{/* <button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Upgrade to view
|
|
||||||
</button> */}
|
|
||||||
</div>
|
|
||||||
{/* <div className="flex items-center">
|
|
||||||
<span className="text-gray-600 w-40">Horoscope</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Upgrade to view
|
|
||||||
</button>
|
|
||||||
</div> */}
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Employment</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">Employed in private</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Income</span>
|
<span className="text-gray-600 w-40">Birth Star</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">₹ 4 - 5 Lakhs</span>
|
<span className="ml-3 text-gray-900">{personal.star || safeVal(profile.star, 'star_name')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Education</span>
|
<span className="text-gray-600 w-40">Known Languages</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">BE</span>
|
<span className="ml-3 text-gray-900">{personal.known_languages || "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Occupation</span>
|
<span className="text-gray-600 w-40">Speaks Telugu</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Engineer - Non IT</span>
|
<span className="ml-3 text-gray-900">{personal.do_you_speak_telugu === 1 ? "Yes" : personal.do_you_speak_telugu === 0 ? "No" : "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">City</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{personal.district || safeVal(profile.district, 'district_name') || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Pin Code</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{personal.pincode || profile.zip || "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Family Information Section */}
|
{/* Family Information Section */}
|
||||||
<div className="border border-gray-200 rounded-lg bg-pink-50/30 ">
|
<div className="border border-gray-200 rounded-lg bg-pink-50/30">
|
||||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||||
<div className="bg-white p-2 rounded-full">
|
<div className="bg-white p-2 rounded-full">
|
||||||
<svg
|
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||||
className="w-5 h-5 text-[#A70710]"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@ -561,104 +469,44 @@ const MatrimonyProfile = () => {
|
|||||||
|
|
||||||
<div className="p-5 space-y-3 text-sm">
|
<div className="p-5 space-y-3 text-sm">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Parents</span>
|
<span className="text-gray-600 w-40">Father Name</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">
|
<span className="ml-3 text-gray-900">{family.father_name || profile.father_name || "N/A"}</span>
|
||||||
Father Passed Away, Mother is a Home Maker
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Ancestral Origin</span>
|
<span className="text-gray-600 w-40">Father Occupation</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Rameshwaram</span>
|
<span className="ml-3 text-gray-900">{family.father_occupation || profile.father_occupation || "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex">
|
||||||
{/* Contact Information Section */}
|
<span className="text-gray-600 w-40">Mother Name</span>
|
||||||
<div className="my-8">
|
<span className="text-gray-400">:</span>
|
||||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
<span className="ml-3 text-gray-900">{family.mother_name || profile.mother_name || "N/A"}</span>
|
||||||
<div className="bg-white p-2 rounded-full">
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-[#A70710]"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 className="font-semibold text-lg">Contact Information</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex">
|
||||||
<div className="p-5 space-y-3 text-sm">
|
<span className="text-gray-600 w-40">Mother Occupation</span>
|
||||||
<div className="flex items-center">
|
<span className="text-gray-400">:</span>
|
||||||
<span className="text-gray-600 w-40">Mobile Number</span>
|
<span className="ml-3 text-gray-900">{family.mother_occupation || profile.mother_occupation || "N/A"}</span>
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<div className="ml-3 flex items-center gap-2">
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3 text-green-600"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
className="w-3 h-3 text-red-600"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span className="text-gray-900">+91 99••••••••</span>
|
|
||||||
<button className="text-[#034E08] hover:text-orange-700 text-xs font-medium">
|
|
||||||
Upgrade to view
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Siblings</span>
|
||||||
{/* About Myself Section */}
|
<span className="text-gray-400">:</span>
|
||||||
<div className="my-8">
|
<span className="ml-3 text-gray-900">{family.brother_count || profile.brother_count} Brothers, {family.sister_count || profile.sister_count} Sisters</span>
|
||||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
|
||||||
<div className="bg-white p-2 rounded-full">
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-[#A70710]"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 className="font-semibold text-lg">About Myself</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex">
|
||||||
<div className="p-5 space-y-4 text-sm">
|
<span className="text-gray-600 w-40">Family Type</span>
|
||||||
<div>
|
<span className="text-gray-400">:</span>
|
||||||
<h4 className="font-semibold text-gray-900 mb-2">
|
<span className="ml-3 text-gray-900">{family.family_status || safeVal(profile.family_type, 'family_type_name') || "N/A"}</span>
|
||||||
About Sudharshan M
|
</div>
|
||||||
</h4>
|
<div className="flex">
|
||||||
<p className="text-gray-700 leading-relaxed">
|
<span className="text-gray-600 w-40">Settled</span>
|
||||||
I am making this profile for my brother. He completed his
|
<span className="text-gray-400">:</span>
|
||||||
bachelor's degree and is now working as a project engineer -
|
<span className="ml-3 text-gray-900">{family.settled || "N/A"}</span>
|
||||||
non IT. We belong to a middle class, nuclear family with
|
</div>
|
||||||
traditional values, currently settled in Chennai.
|
<div className="flex">
|
||||||
</p>
|
<span className="text-gray-600 w-40">Native Place</span>
|
||||||
</div>
|
<span className="text-gray-400">:</span>
|
||||||
<div>
|
<span className="ml-3 text-gray-900">{family.native_place || profile.native_place || "N/A"}</span>
|
||||||
<h4 className="font-semibold text-gray-900 mb-2">
|
|
||||||
What we are looking for
|
|
||||||
</h4>
|
|
||||||
<p className="text-gray-700">
|
|
||||||
Traditional, homely girl with moderate values
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -666,11 +514,7 @@ const MatrimonyProfile = () => {
|
|||||||
<div className="my-8">
|
<div className="my-8">
|
||||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||||
<div className="bg-white p-2 rounded-full">
|
<div className="bg-white p-2 rounded-full">
|
||||||
<svg
|
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||||
className="w-5 h-5 text-[#A70710]"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
|
<path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@ -679,55 +523,108 @@ const MatrimonyProfile = () => {
|
|||||||
|
|
||||||
<div className="p-5 space-y-3 text-sm">
|
<div className="p-5 space-y-3 text-sm">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Cuisine</span>
|
<span className="text-gray-600 w-40">Diet</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.diet || safeVal(profile.diet, 'diet_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Place of Birth</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.place_of_birth || personal.place_of_birth || profile.place_of_birth || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Time of Birth</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.time_of_birth || personal.time_of_birth || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Panjangam Type</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.panjangam_type || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Dasa Balance</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{lifestyle.dasa_balance || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Dasa Period</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">
|
<span className="ml-3 text-gray-900">
|
||||||
Chinese, North Indian, South Indian
|
{lifestyle.dasa_years || "0"} Years, {lifestyle.dasa_months || "0"} Months, {lifestyle.dasa_days || "0"} Days
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Books</span>
|
<span className="text-gray-600 w-40">Age</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">
|
<span className="ml-3 text-gray-900">{(personal.age || profile.age || lifestyle.age) ? `${personal.age || profile.age || lifestyle.age} Years` : "N/A"}</span>
|
||||||
History, Philosophy / Spiritual
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<span className="text-gray-600 w-40">Hobbies</span>
|
<span className="text-gray-600 w-40">Hobbies</span>
|
||||||
<span className="text-gray-400">:</span>
|
<span className="text-gray-400">:</span>
|
||||||
<span className="ml-3 text-gray-900">Cooking</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<span className="text-gray-600 w-40">Movies</span>
|
|
||||||
<span className="text-gray-400">:</span>
|
|
||||||
<span className="ml-3 text-gray-900">
|
<span className="ml-3 text-gray-900">
|
||||||
Anime, Comedy, Sci-Fi
|
{lifestyle.hobbies && lifestyle.hobbies.length > 0 ? lifestyle.hobbies.join(", ") : "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
</div>
|
||||||
<span className="text-gray-600 w-40">Sports</span>
|
</div>
|
||||||
<span className="text-gray-400">:</span>
|
</div>
|
||||||
<span className="ml-3 text-gray-900">Yoga / Meditation</span>
|
|
||||||
</div>
|
{/* Educational Details Section */}
|
||||||
<div className="flex">
|
<div className="border border-gray-200 rounded-lg bg-pink-50/30">
|
||||||
<span className="text-gray-600 w-40">Smoking Habits</span>
|
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||||
<span className="text-gray-400">:</span>
|
<div className="bg-white p-2 rounded-full">
|
||||||
<span className="ml-3 text-gray-900">Doesn't Smoke</span>
|
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||||
</div>
|
<path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
|
||||||
<div className="flex">
|
</svg>
|
||||||
<span className="text-gray-600 w-40">Drinking Habits</span>
|
</div>
|
||||||
<span className="text-gray-400">:</span>
|
<h3 className="font-semibold text-lg">Educational Details</h3>
|
||||||
<span className="ml-3 text-gray-900">Doesn't Drink</span>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="p-5 space-y-3 text-sm">
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Highest Qualification</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{education.education || safeVal(profile.education, 'education_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Field of Study</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{education.study_field || safeVal(profile.study_field, 'study_field_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">College Name</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{profile.college_name || education.college_name || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Occupation</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{education.occupation || safeVal(profile.occupation, 'occupation_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Organization Name</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{education.company_name || profile.company_name || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Employee Type</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{education.employee_type || safeVal(profile.employee_type, 'employee_type_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Annual Income</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{education.annual_income || safeVal(profile.annual_income, 'annual_income_name')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="text-gray-600 w-40">Work Location</span>
|
||||||
|
<span className="text-gray-400">:</span>
|
||||||
|
<span className="ml-3 text-gray-900">{profile.work_location || education.work_location || "N/A"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Check, X } from 'lucide-react';
|
import { Check, X } from 'lucide-react';
|
||||||
|
|
||||||
const PartnerPreferences = () => {
|
const PartnerPreferences = ({ data }) => {
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
const pref = data.preferedDetails;
|
||||||
|
const matchDetails = data.mutual_match.my_preferences_match;
|
||||||
|
const overallMatch = data.mutual_match.overall_match_percentage;
|
||||||
|
|
||||||
const basicPreferences = [
|
const basicPreferences = [
|
||||||
{ label: "Preferred Bride's Age", value: "22-29 yrs", match: true },
|
{ label: "Preferred Groom's Age", value: pref.preferred_age_range || "Any", match: matchDetails.age },
|
||||||
{ label: "Preferred Height", value: "5'0\" - 5'5\"", match: false },
|
{ label: "Preferred Height", value: (pref.preferred_height_from && pref.preferred_height_to) ? `${pref.preferred_height_from} - ${pref.preferred_height_to} ft` : "Any", match: matchDetails.height },
|
||||||
{ label: "Preferred Marital Status", value: "Never Married", match: true },
|
{ label: "Preferred Marital Status", value: pref.preferred_marital_statuses?.join(", ") || "Any", match: matchDetails.marital_status },
|
||||||
{ label: "Preferred Mother Tongue", value: "Tamil", match: true },
|
{ label: "Preferred Mother Tongue", value: pref.preferred_mother_tongues?.join(", ") || "Any", match: matchDetails.mother_tongue },
|
||||||
{ label: "Preferred Physical Status", value: "Normal", match: true },
|
{ label: "Preferred Education", value: pref.preferred_educations?.join(", ") || "Any", match: matchDetails.education },
|
||||||
{ label: "Preferred Eating Habits", value: "Vegetarian", match: false },
|
{ label: "Preferred Employee Type", value: pref.preferred_employee_types?.join(", ") || "Any", match: true }, // Not in matchDetails?
|
||||||
{ label: "Preferred Smoking Habits", value: "Doesn't Matter", match: true },
|
|
||||||
{ label: "Preferred Drinking Habits", value: "Doesn't Matter", match: true },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const religiousPreferences = [
|
const religiousPreferences = [
|
||||||
{ label: "Preferred Religion", value: "Hindu", match: true },
|
{ label: "Preferred Caste", value: pref.preferred_castes?.join(", ") || "Any", match: matchDetails.caste },
|
||||||
{ label: "Preferred Caste", value: "Brahmin - Iyer", match: false },
|
{ label: "Preferred Sub-caste", value: pref.preferred_sub_castes?.join(", ") || "Any", match: matchDetails.sub_caste },
|
||||||
{ label: "Preferred Subcaste", value: "Any", match: false },
|
{ label: "Preferred State", value: pref.preferred_states?.join(", ") || "Any", match: true },
|
||||||
{ label: "Preferred Star", value: "Any", match: true },
|
{ label: "Preferred City", value: pref.preferred_districts?.join(", ") || "Any", match: true },
|
||||||
{ label: "Preferred Dosham", value: "No Dosham", match: true },
|
{ label: "Preferred Occupation", value: pref.preferred_occupations?.join(", ") || "Any", match: matchDetails.occupation },
|
||||||
|
{ label: "Preferred Annual Income", value: pref.preferred_annual_income || "Any", match: matchDetails.annual_income },
|
||||||
];
|
];
|
||||||
|
|
||||||
const PreferenceItem = ({ label, value, match }) => (
|
const PreferenceItem = ({ label, value, match }) => (
|
||||||
@ -46,7 +51,7 @@ const PartnerPreferences = () => {
|
|||||||
<div className="text-center mb-6 ">
|
<div className="text-center mb-6 ">
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold text-gray-800 mb-2 flex items-center justify-center gap-2">
|
<h1 className="text-2xl sm:text-3xl font-bold text-gray-800 mb-2 flex items-center justify-center gap-2">
|
||||||
<span className="text-pink-400">✨</span>
|
<span className="text-pink-400">✨</span>
|
||||||
His Partner Preferences
|
Partner Preferences
|
||||||
<span className="text-pink-400">✨</span>
|
<span className="text-pink-400">✨</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@ -56,65 +61,52 @@ const PartnerPreferences = () => {
|
|||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<img
|
<img
|
||||||
src="https://api.dicebear.com/7.x/avataaars/svg?seed=male1"
|
src={data.my_profile || "https://api.dicebear.com/7.x/avataaars/svg?seed=male1"}
|
||||||
alt="Profile"
|
alt="Your Profile"
|
||||||
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-pink-100"
|
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-pink-100 object-cover"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-600 text-sm mb-1">You match</p>
|
<p className="text-gray-600 text-sm mb-1">Overall Match Score</p>
|
||||||
<p className="text-2xl sm:text-3xl font-bold text-red-600">
|
<p className="text-2xl sm:text-3xl font-bold text-red-600">
|
||||||
14<span className="text-[#034E08]">/20</span>
|
{overallMatch}<span className="text-[#034E08]">%</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">of his preferences</p>
|
<p className="text-xs text-gray-500">of preferences match</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
src="https://api.dicebear.com/7.x/avataaars/svg?seed=female1"
|
src={data.profile.profile_picture || "https://api.dicebear.com/7.x/avataaars/svg?seed=female1"}
|
||||||
alt="Your Profile"
|
alt="Partner Profile"
|
||||||
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-purple-100"
|
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-purple-100 object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='grid grid-cols-1 gap-2 md:grid-cols-2 mb-8 pt-4'>
|
<div className='grid grid-cols-1 gap-2 md:grid-cols-2 mb-8 pt-4'>
|
||||||
|
{/* Basic Preferences Section */}
|
||||||
{/* Basic Preferences Section */}
|
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||||||
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
|
<div className="flex items-center justify-between mb-4 bg-[#f5fbff] pt-4 pb-4 px-6">
|
||||||
<div className="flex items-center justify-between mb-4 bg-[#f5fbff] pt-4 pb-4 px-6">
|
<h2 className="text-lg font-bold text-gray-800">Basic Preferences</h2>
|
||||||
<h2 className="text-lg font-bold text-gray-800">Basic Preferences</h2>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="space-y-1 p-6">
|
||||||
<span className="text-sm text-gray-600">You match</span>
|
{basicPreferences.map((pref, index) => (
|
||||||
<div className="w-6 h-6 rounded-full bg-green-100 flex items-center justify-center">
|
<PreferenceItem key={index} {...pref} />
|
||||||
<Check className="w-4 h-4 text-green-600" />
|
))}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 p-6">
|
|
||||||
{basicPreferences.map((pref, index) => (
|
{/* Other Preferences Section */}
|
||||||
<PreferenceItem key={index} {...pref} />
|
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||||||
))}
|
<div className="flex items-center justify-between mb-4 bg-[#f5fbff] pt-4 pb-4 px-6">
|
||||||
|
<h2 className="text-lg font-bold text-gray-800">Professional & Location</h2>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1 p-6">
|
||||||
|
{religiousPreferences.map((pref, index) => (
|
||||||
|
<PreferenceItem key={index} {...pref} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Religious Preferences Section */}
|
|
||||||
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-4 bg-[#f5fbff] pt-4 pb-4 px-6">
|
|
||||||
<h2 className="text-lg font-bold text-gray-800">Religious Preferences</h2>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm text-gray-600">You match</span>
|
|
||||||
<div className="w-6 h-6 rounded-full bg-green-100 flex items-center justify-center">
|
|
||||||
<Check className="w-4 h-4 text-green-600" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1 p-6">
|
|
||||||
{religiousPreferences.map((pref, index) => (
|
|
||||||
<PreferenceItem key={index} {...pref} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Footer Note */}
|
{/* Footer Note */}
|
||||||
<div className="text-center mt-6 text-sm text-gray-500">
|
<div className="text-center mt-6 text-sm text-gray-500">
|
||||||
<p>Preferences are used to find compatible matches</p>
|
<p>Preferences are used to find compatible matches</p>
|
||||||
|
|||||||
@ -1,73 +1,222 @@
|
|||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react";
|
import { Heart, X, Crown, Bookmark, Eye, Clock, ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
// Import your images
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import Profile1 from "../../assets/images/bride1.jpg";
|
import { Navigation, Pagination } from "swiper/modules";
|
||||||
import Profile2 from "../../assets/images/bride2.jpg";
|
import "swiper/css";
|
||||||
import Profile3 from "../../assets/images/bride3.jpg";
|
import "swiper/css/navigation";
|
||||||
import Profile4 from "../../assets/images/bride4.jpg";
|
import "swiper/css/pagination";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const ProfileCardItem = ({ profile }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
|
||||||
|
|
||||||
export default function ProfileCard() {
|
useEffect(() => {
|
||||||
// Sample data for multiple cards with image paths
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
const profiles = [
|
}, [profile?.is_shortlisted]);
|
||||||
{
|
|
||||||
id: 1,
|
const shortlistMutation = useMutation({
|
||||||
name: "Jerome Bell",
|
mutationFn: shortlistProfile,
|
||||||
idNumber: "KI2847596",
|
onMutate: () => {
|
||||||
lastSeen: "4 Nov 2025",
|
setIsShortlisted((prev) => !prev);
|
||||||
salary: "5-10",
|
|
||||||
age: "22 yrs",
|
|
||||||
height: "5'2\"",
|
|
||||||
location: "Chennai",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image: Profile1,
|
|
||||||
},
|
},
|
||||||
{
|
onSuccess: (data) => {
|
||||||
id: 2,
|
toast.success(data.message || "Profile shortlisted successfully.");
|
||||||
name: "Neha Singh",
|
queryClient.invalidateQueries();
|
||||||
idNumber: "KI2847597",
|
|
||||||
lastSeen: "5 Nov 2025",
|
|
||||||
salary: "8-12",
|
|
||||||
age: "26 yrs",
|
|
||||||
height: "5'6\"",
|
|
||||||
location: "hyderabad",
|
|
||||||
caste: "Brahmin",
|
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image: Profile2,
|
|
||||||
},
|
},
|
||||||
{
|
onError: (error) => {
|
||||||
id: 3,
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
name: "Priya Sharma",
|
toast.error(error.message || "Failed to update shortlist status.");
|
||||||
idNumber: "KI2847598",
|
}
|
||||||
lastSeen: "3 Nov 2025",
|
});
|
||||||
salary: "6-11",
|
|
||||||
age: "24 yrs",
|
const interestMutation = useMutation({
|
||||||
height: "5'4\"",
|
mutationFn: sendInterest,
|
||||||
location: "Mumbai",
|
onSuccess: (data) => {
|
||||||
caste: "Brahmin",
|
toast.success(data.message || "Interest sent successfully.");
|
||||||
zodiac1: "Aries",
|
queryClient.invalidateQueries();
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image: Profile3,
|
|
||||||
},
|
},
|
||||||
{
|
onError: (error) => {
|
||||||
id: 4,
|
toast.error(error.message || "Failed to send interest.");
|
||||||
name: "Kavya Iyer",
|
}
|
||||||
idNumber: "KI2847599",
|
});
|
||||||
lastSeen: "2 Nov 2025",
|
|
||||||
salary: "7-10",
|
const declineMutation = useMutation({
|
||||||
age: "23 yrs",
|
mutationFn: declineProfile,
|
||||||
height: "5'3\"",
|
onSuccess: (data) => {
|
||||||
location: "Bangalore",
|
toast.success(data.message || "Profile declined.");
|
||||||
caste: "Brahmin",
|
queryClient.invalidateQueries();
|
||||||
zodiac1: "Aries",
|
|
||||||
zodiac2: "Scorpio",
|
|
||||||
image: Profile4,
|
|
||||||
},
|
},
|
||||||
];
|
onError: (error) => {
|
||||||
|
toast.error(error.message || "Failed to decline profile.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = profile.id;
|
||||||
|
const image = profile.photo || profile.image;
|
||||||
|
const name = profile.name || "Unknown";
|
||||||
|
const idNumber = profile.member_id || profile.idNumber || "N/A";
|
||||||
|
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
|
||||||
|
const age = profile.age ? `${profile.age} yrs` : null;
|
||||||
|
const height = profile.height || null;
|
||||||
|
const salary = profile.annual_income_name || (profile.salary ? `${profile.salary} LPA` : null);
|
||||||
|
const location = profile.district_name || profile.location || null;
|
||||||
|
const caste = profile.caste_name || profile.caste || null;
|
||||||
|
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
|
||||||
|
const zodiac2 = profile.star_name || profile.zodiac2 || null;
|
||||||
|
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
|
||||||
|
onClick={() => navigate(`/profile-details/${id}`)}
|
||||||
|
>
|
||||||
|
{/* IMAGE SECTION */}
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
alt="profile"
|
||||||
|
className="w-full h-[320px] object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isPremium && (
|
||||||
|
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
||||||
|
<Crown size={18} color="#fff" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!shortlistMutation.isPending) shortlistMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||||
|
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CONTENT */}
|
||||||
|
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||||
|
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||||
|
{name}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||||
|
<p>ID: {idNumber}</p>
|
||||||
|
<p className="flex items-center gap-0.5">
|
||||||
|
<Eye size={12} /> {lastSeen}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
|
{[
|
||||||
|
age,
|
||||||
|
height,
|
||||||
|
salary,
|
||||||
|
location,
|
||||||
|
caste,
|
||||||
|
zodiac1,
|
||||||
|
zodiac2,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((v, i) => (
|
||||||
|
<span
|
||||||
|
key={i}
|
||||||
|
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||||
|
>
|
||||||
|
{v}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4 mt-[15px] justify-center">
|
||||||
|
<button
|
||||||
|
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
disabled={declineMutation.isPending}
|
||||||
|
>
|
||||||
|
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
disabled={interestMutation.isPending}
|
||||||
|
>
|
||||||
|
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ProfileCard({ profiles }) {
|
||||||
|
const displayProfiles = profiles || [];
|
||||||
|
|
||||||
|
const [showTimer, setShowTimer] = useState(false);
|
||||||
|
const [timeLeft, setTimeLeft] = useState(24 * 60 * 60); // 24 hours in seconds
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedEndTime = localStorage.getItem("profileTimerEnd");
|
||||||
|
if (storedEndTime) {
|
||||||
|
const endTime = parseInt(storedEndTime, 10);
|
||||||
|
const now = Date.now();
|
||||||
|
if (endTime > now) {
|
||||||
|
setShowTimer(true);
|
||||||
|
setTimeLeft(Math.floor((endTime - now) / 1000));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem("profileTimerEnd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer;
|
||||||
|
if (showTimer && timeLeft > 0) {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
setTimeLeft((prev) => {
|
||||||
|
if (prev <= 1) {
|
||||||
|
setShowTimer(false);
|
||||||
|
localStorage.removeItem("profileTimerEnd");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return prev - 1;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [showTimer, timeLeft]);
|
||||||
|
|
||||||
|
const handleShowTimer = () => {
|
||||||
|
const endTime = Date.now() + 24 * 60 * 60 * 1000; // 24 hours from now
|
||||||
|
localStorage.setItem("profileTimerEnd", endTime.toString());
|
||||||
|
setTimeLeft(24 * 60 * 60);
|
||||||
|
setShowTimer(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (seconds) => {
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = seconds % 60;
|
||||||
|
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-auto py-8 px-4">
|
<div className="h-auto py-8 px-4">
|
||||||
@ -84,78 +233,79 @@ export default function ProfileCard() {
|
|||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-900 text-[12px]">
|
<p className="text-gray-900 text-[12px]">
|
||||||
Find your perfect match today
|
Find your perfect match today
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* CARDS GRID */}
|
{/* CARDS GRID */}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="w-full max-w-[1400px] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
<div className="w-full max-w-[1400px] relative px-2 md:px-12">
|
||||||
{profiles.map((profile) => (
|
{!showTimer ? (
|
||||||
<div
|
<>
|
||||||
key={profile.id}
|
{/* Custom Navigation Arrows */}
|
||||||
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md"
|
<button className="profile-swiper-prev absolute left-0 md:left-2 top-[40%] -translate-y-1/2 z-20 w-10 h-10 bg-white shadow-xl border border-gray-100 rounded-full flex items-center justify-center text-[#8b0000] hover:bg-[#8b0000] hover:text-white transition-all disabled:opacity-30 disabled:cursor-not-allowed">
|
||||||
|
<ChevronLeft size={24} />
|
||||||
|
</button>
|
||||||
|
<button className="profile-swiper-next absolute right-0 md:right-2 top-[40%] -translate-y-1/2 z-20 w-10 h-10 bg-white shadow-xl border border-gray-100 rounded-full flex items-center justify-center text-[#8b0000] hover:bg-[#8b0000] hover:text-white transition-all disabled:opacity-30 disabled:cursor-not-allowed">
|
||||||
|
<ChevronRight size={24} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Swiper
|
||||||
|
modules={[Navigation, Pagination]}
|
||||||
|
spaceBetween={24}
|
||||||
|
slidesPerView={1}
|
||||||
|
navigation={{
|
||||||
|
prevEl: '.profile-swiper-prev',
|
||||||
|
nextEl: '.profile-swiper-next',
|
||||||
|
}}
|
||||||
|
pagination={{ clickable: true }}
|
||||||
|
breakpoints={{
|
||||||
|
768: { slidesPerView: 2 },
|
||||||
|
1024: { slidesPerView: 3 },
|
||||||
|
1280: { slidesPerView: 4 },
|
||||||
|
}}
|
||||||
|
onReachEnd={() => {
|
||||||
|
if (displayProfiles.length > 0) {
|
||||||
|
setTimeout(handleShowTimer, 2500); // Wait 2.5s on the last slide before hiding
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="pb-12"
|
||||||
>
|
>
|
||||||
{/* IMAGE SECTION */}
|
{displayProfiles.map((profile, index) => (
|
||||||
<div className="relative">
|
<SwiperSlide key={profile.id || index}>
|
||||||
<img
|
<ProfileCardItem profile={profile} />
|
||||||
src={profile.image}
|
</SwiperSlide>
|
||||||
alt="profile"
|
))}
|
||||||
className="w-full h-[320px] object-cover"
|
{/* END SLIDE (BUFFER) */}
|
||||||
/>
|
<SwiperSlide>
|
||||||
|
<div className="w-full h-full min-h-[400px] flex flex-col items-center justify-center bg-white rounded-[28px] shadow-md border border-gray-100 p-6">
|
||||||
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
<h3 className="text-xl font-bold mb-4 text-gray-800 text-center">You've seen all matches!</h3>
|
||||||
<Crown size={18} color="#fff" />
|
<button
|
||||||
</div>
|
onClick={handleShowTimer}
|
||||||
|
className="px-6 py-2 bg-[#8b0000] text-white rounded-full font-semibold shadow-lg hover:bg-red-800 transition"
|
||||||
<div className="absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg">
|
>
|
||||||
<Bookmark size={14} /> Shortlist
|
View Next Batch Timer
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* CONTENT */}
|
|
||||||
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
|
||||||
<h2 className="text-center text-[22px] font-semibold mb-1">
|
|
||||||
{profile.name}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
|
||||||
<p>ID: {profile.idNumber}</p>
|
|
||||||
<p className="flex items-center gap-0.5">
|
|
||||||
<Eye size={12} /> {profile.lastSeen}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-2">
|
|
||||||
{[
|
|
||||||
profile.age,
|
|
||||||
profile.height,
|
|
||||||
profile.salary + " LPA",
|
|
||||||
profile.location,
|
|
||||||
profile.caste,
|
|
||||||
profile.zodiac1,
|
|
||||||
profile.zodiac2,
|
|
||||||
].map((v, i) => (
|
|
||||||
<span
|
|
||||||
key={i}
|
|
||||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
|
||||||
>
|
|
||||||
{v}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-4 mt-[15px] justify-center">
|
|
||||||
<button className="px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900">
|
|
||||||
<X size={18} /> Decline
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button className="px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold">
|
|
||||||
<Heart size={18} /> Interest
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
</Swiper>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
className="flex flex-col items-center justify-center bg-white rounded-[28px] shadow-xl max-w-lg mx-auto py-12 px-6 border border-gray-100"
|
||||||
|
>
|
||||||
|
<Clock size={48} className="text-[#8b0000] mb-4" />
|
||||||
|
<h2 className="text-2xl font-bold mb-2 text-gray-800 text-center">Next Recommendations In</h2>
|
||||||
|
<div className="text-5xl font-extrabold text-[#8b0000] tracking-widest my-4 tabular-nums">
|
||||||
|
{formatTime(timeLeft)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p className="text-gray-500 text-center mb-6">
|
||||||
))}
|
We are curating the best matches for you. Please check back when the timer ends!
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -75,12 +75,12 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
|||||||
<>
|
<>
|
||||||
<Dropzone
|
<Dropzone
|
||||||
onChange={updateFiles}
|
onChange={updateFiles}
|
||||||
minHeight="195px"
|
minHeight="200px"
|
||||||
value={extFiles}
|
value={extFiles}
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
maxFiles={3}
|
maxFiles={3}
|
||||||
maxFileSize={10 * 1024 * 1024}
|
maxFileSize={10 * 1024 * 1024}
|
||||||
label="Drag'n drop up to 3 images (max 10 MB each)"
|
label={<span style={{ fontSize: "16px" }}>Drag'n drop up to 3 images (max 10 MB each)</span>}
|
||||||
uploadConfig={{
|
uploadConfig={{
|
||||||
url: BASE_URL + "/file",
|
url: BASE_URL + "/file",
|
||||||
cleanOnUpload: true,
|
cleanOnUpload: true,
|
||||||
@ -155,9 +155,9 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
|||||||
}}
|
}}
|
||||||
aria-label="Move left"
|
aria-label="Move left"
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon fontSize="small" />
|
<ArrowLeftIcon fontSize="small" sx={{fontSize:"30px"}} />
|
||||||
</button>
|
</button>
|
||||||
<span
|
{/* <span
|
||||||
style={{
|
style={{
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@ -170,7 +170,7 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
|||||||
title={file.name}
|
title={file.name}
|
||||||
>
|
>
|
||||||
{file.name}
|
{file.name}
|
||||||
</span>
|
</span> */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -197,7 +197,7 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
|||||||
}}
|
}}
|
||||||
aria-label="Move right"
|
aria-label="Move right"
|
||||||
>
|
>
|
||||||
<ArrowRightIcon fontSize="small" />
|
<ArrowRightIcon fontSize="small" sx={{fontSize:"30px"}} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { updateEducationalDetails } from "../redux/registrationFormSlice";
|
import { updateEducationalDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
||||||
import {
|
import {
|
||||||
TextField,
|
TextField,
|
||||||
Button,
|
Button,
|
||||||
@ -9,454 +9,412 @@ import {
|
|||||||
InputLabel,
|
InputLabel,
|
||||||
Select,
|
Select,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
FormHelperText,
|
||||||
|
InputAdornment,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useEducationMasters, useEducationList } from "../hooks/useMasters";
|
import { useEducationMasters, useEducationList } from "../hooks/useMasters";
|
||||||
|
import { useCityMasters } from "../hooks/useDependentMasters";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
const EducationalDetailsForm = ({
|
const EducationalDetailsForm = ({
|
||||||
onSubmitStep,
|
onSubmitStep,
|
||||||
onSkipStep,
|
onSkipStep,
|
||||||
errors,
|
errors: externalErrors,
|
||||||
onFieldChange,
|
isEditMode,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const data = useSelector((state) => state.registerform.educationalDetails);
|
const data = useSelector((state) => state.registerform.educationalDetails);
|
||||||
const inputRef = useRef(null);
|
const [localErrors, setLocalErrors] = useState({});
|
||||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||||
|
|
||||||
const { data: educationMasters, isLoading: isEducationMastersLoading } =
|
const { data: educationMasters, isLoading: isEducationMastersLoading } =
|
||||||
useEducationMasters();
|
useEducationMasters();
|
||||||
const educationListQuery = useEducationList(data.fieldOfStudy);
|
const educationListQuery = useEducationList(data.study_field);
|
||||||
|
const districtQuery = useCityMasters(data.work_state);
|
||||||
|
|
||||||
const studyFieldOptions = useMemo(() => {
|
const studyFieldOptions = educationMasters?.studyFields || [];
|
||||||
const raw = educationMasters;
|
const qualificationOptions = educationListQuery.data?.education || educationListQuery.data?.data || [];
|
||||||
if (!raw) return [];
|
const occupationOptions = educationMasters?.occupation || [];
|
||||||
if (Array.isArray(raw)) return raw;
|
const employeeTypeOptions = educationMasters?.employeeType || [];
|
||||||
return raw.studyFields || raw.study_fields || raw.fieldOfStudy || [];
|
const countryOptions = educationMasters?.country || [];
|
||||||
}, [educationMasters]);
|
const stateOptions = educationMasters?.state || [];
|
||||||
|
const districtOptions = districtQuery.data?.districts || districtQuery.data || [];
|
||||||
|
|
||||||
const qualificationOptions = useMemo(() => {
|
const isUnemployed = data.employee_type === 11;
|
||||||
const raw = educationListQuery.data;
|
const isIndia = Number(data.work_country) === 1;
|
||||||
if (!raw) return [];
|
|
||||||
if (Array.isArray(raw)) return raw;
|
|
||||||
return raw.education || raw.data || [];
|
|
||||||
}, [educationListQuery.data]);
|
|
||||||
|
|
||||||
const occupationOptions = useMemo(() => {
|
|
||||||
const raw = educationMasters;
|
|
||||||
if (!raw) return [];
|
|
||||||
if (Array.isArray(raw)) return raw;
|
|
||||||
return raw.occupation || raw.occupations || [];
|
|
||||||
}, [educationMasters]);
|
|
||||||
|
|
||||||
const employeeTypeOptions = useMemo(() => {
|
|
||||||
const raw = educationMasters;
|
|
||||||
if (!raw) return [];
|
|
||||||
if (Array.isArray(raw)) return raw;
|
|
||||||
return raw.employeeType || raw.employee_type || [];
|
|
||||||
}, [educationMasters]);
|
|
||||||
|
|
||||||
const annualIncomeOptions = useMemo(() => {
|
|
||||||
const raw = educationMasters;
|
|
||||||
if (!raw) return [];
|
|
||||||
if (Array.isArray(raw)) return raw;
|
|
||||||
return raw.annualIncome || raw.annual_income || [];
|
|
||||||
}, [educationMasters]);
|
|
||||||
|
|
||||||
const workLocationOptions = useMemo(() => {
|
|
||||||
const raw = educationMasters;
|
|
||||||
if (!raw) return [];
|
|
||||||
if (Array.isArray(raw)) return [];
|
|
||||||
return raw.workLocation || raw.work_location || raw.workLocations || [];
|
|
||||||
}, [educationMasters]);
|
|
||||||
|
|
||||||
const getOptionLabel = (item, fallback = "") => {
|
|
||||||
if (!item) return fallback;
|
|
||||||
if (typeof item === "string") return item;
|
|
||||||
return (
|
|
||||||
item.study_field_name ||
|
|
||||||
item.education_name ||
|
|
||||||
item.occupation_name ||
|
|
||||||
item.employee_type_name ||
|
|
||||||
item.annual_income_name ||
|
|
||||||
item.work_location_name ||
|
|
||||||
item.name ||
|
|
||||||
fallback
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
const updates = { [field]: value };
|
const updates = { [field]: value };
|
||||||
const fieldsToClear = [field];
|
|
||||||
if (field === "fieldOfStudy") {
|
if (field === "study_field") {
|
||||||
updates.qualification = "";
|
updates.education = "";
|
||||||
fieldsToClear.push("qualification");
|
|
||||||
}
|
}
|
||||||
|
if (field === "work_country") {
|
||||||
|
updates.work_state = "";
|
||||||
|
updates.work_district = "";
|
||||||
|
updates.work_city = "";
|
||||||
|
}
|
||||||
|
if (field === "work_state") {
|
||||||
|
updates.work_district = "";
|
||||||
|
}
|
||||||
|
if (field === "employee_type" && value === 11) {
|
||||||
|
// Clear fields that will be hidden
|
||||||
|
updates.occupation = "";
|
||||||
|
updates.occupation_detail = "";
|
||||||
|
updates.company_name = "";
|
||||||
|
updates.annual_income = "";
|
||||||
|
updates.work_country = "";
|
||||||
|
updates.work_state = "";
|
||||||
|
updates.work_district = "";
|
||||||
|
updates.work_city = "";
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(updateEducationalDetails(updates));
|
dispatch(updateEducationalDetails(updates));
|
||||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
setLocalErrors((prev) => ({ ...prev, [field]: "" }));
|
||||||
|
|
||||||
|
if (!isEditMode) {
|
||||||
|
dispatch(clearAllStepsFrom(3));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
const newErrors = {};
|
||||||
|
if (!data.study_field) newErrors.study_field = "Required";
|
||||||
|
if (!data.education) newErrors.education = "Required";
|
||||||
|
if (!data.education_detail) newErrors.education_detail = "Required";
|
||||||
|
if (!data.employee_type) newErrors.employee_type = "Required";
|
||||||
|
|
||||||
|
if (!isUnemployed) {
|
||||||
|
if (!data.occupation) newErrors.occupation = "Required";
|
||||||
|
if (!data.occupation_detail) newErrors.occupation_detail = "Required";
|
||||||
|
if (!data.income_currency) newErrors.income_currency = "Required";
|
||||||
|
if (!data.annual_income) newErrors.annual_income = "Required";
|
||||||
|
if (!data.work_country) newErrors.work_country = "Required";
|
||||||
|
|
||||||
|
if (isIndia) {
|
||||||
|
if (!data.work_state) newErrors.work_state = "Required";
|
||||||
|
if (!data.work_district) newErrors.work_district = "Required";
|
||||||
|
} else {
|
||||||
|
if (!data.work_city) newErrors.work_city = "Required";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.address) newErrors.address = "Required";
|
||||||
|
|
||||||
|
setLocalErrors(newErrors);
|
||||||
|
return newErrors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToError = (errorMap) => {
|
||||||
|
const errorFields = Object.keys(errorMap);
|
||||||
|
if (errorFields.length > 0) {
|
||||||
|
const fieldId = errorFields[0];
|
||||||
|
const element = document.getElementById(fieldId);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
setTimeout(() => {
|
||||||
|
const focusable = element.querySelector('[role="combobox"]') ||
|
||||||
|
element.querySelector('[role="button"]') ||
|
||||||
|
element.querySelector("input") ||
|
||||||
|
element.querySelector("select") ||
|
||||||
|
element;
|
||||||
|
if (focusable && typeof focusable.focus === "function") {
|
||||||
|
focusable.focus();
|
||||||
|
}
|
||||||
|
}, 300); // Reduced delay slightly for snappier feel
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
console.log("Submitting educational details:", data);
|
const freshErrors = validateForm();
|
||||||
|
if (Object.keys(freshErrors).length > 0) {
|
||||||
|
toast.error("Please fill all mandatory fields");
|
||||||
|
scrollToError(freshErrors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
onSubmitStep();
|
onSubmitStep();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
{/* 1. Field of Study */}
|
||||||
{/* Field of Study */}
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col gap-4">
|
<label className="text-gray-900 text-[15px]">Field of Study{requiredMark}</label>
|
||||||
<label className="text-gray-900 text-[15px]">
|
<FormControl fullWidth error={Boolean(localErrors.study_field)} id="study_field">
|
||||||
Field of Study{requiredMark}
|
<InputLabel>Select Field of Study</InputLabel>
|
||||||
</label>
|
<Select
|
||||||
<FormControl
|
value={data.study_field}
|
||||||
fullWidth
|
label="Select Field of Study"
|
||||||
variant="outlined"
|
onChange={(e) => handleChange("study_field", e.target.value)}
|
||||||
error={Boolean(errors.fieldOfStudy)}
|
|
||||||
>
|
>
|
||||||
<InputLabel id="fieldOfStudy-label">
|
{studyFieldOptions.map((opt) => (
|
||||||
Select Field of Study
|
<MenuItem key={opt.id} value={opt.id}>{opt.study_field_name}</MenuItem>
|
||||||
</InputLabel>
|
))}
|
||||||
<Select
|
</Select>
|
||||||
labelId="fieldOfStudy-label"
|
{localErrors.study_field && <FormHelperText>{localErrors.study_field}</FormHelperText>}
|
||||||
label="Select Field of Study"
|
</FormControl>
|
||||||
name="fieldOfStudy"
|
|
||||||
value={data.fieldOfStudy}
|
|
||||||
onChange={(e) => handleChange("fieldOfStudy", e.target.value)}
|
|
||||||
inputRef={inputRef}
|
|
||||||
disabled={isEducationMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{studyFieldOptions.map((field) => (
|
|
||||||
<MenuItem key={field.id ?? field} value={field.id ?? field}>
|
|
||||||
{getOptionLabel(field, "Field of Study")}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.fieldOfStudy && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.fieldOfStudy}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Highest Qualification */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Highest Educational Qualification{requiredMark}
|
|
||||||
</label>
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.qualification)}
|
|
||||||
>
|
|
||||||
<InputLabel id="qualification-label">
|
|
||||||
Select Highest Qualification
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="qualification-label"
|
|
||||||
label="Select Highest Qualification"
|
|
||||||
name="qualification"
|
|
||||||
value={data.qualification}
|
|
||||||
onChange={(e) => handleChange("qualification", e.target.value)}
|
|
||||||
disabled={
|
|
||||||
!data.fieldOfStudy || educationListQuery.isLoading
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{qualificationOptions.map((item) => (
|
|
||||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
|
||||||
{getOptionLabel(item, "Qualification")}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.qualification && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.qualification}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* College Name */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
College Name
|
|
||||||
</label>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
name="collegeName"
|
|
||||||
label="College Name"
|
|
||||||
value={data.collegeName}
|
|
||||||
onChange={(e) => handleChange("collegeName", e.target.value)}
|
|
||||||
error={Boolean(errors.collegeName)}
|
|
||||||
helperText={errors.collegeName}
|
|
||||||
placeholder="Enter College Name"
|
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Occupation */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Occupation{requiredMark}
|
|
||||||
</label>
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.occupation)}
|
|
||||||
>
|
|
||||||
<InputLabel id="occupation-label">Select Occupation</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="occupation-label"
|
|
||||||
label="Select Occupation"
|
|
||||||
name="occupation"
|
|
||||||
value={data.occupation}
|
|
||||||
onChange={(e) => handleChange("occupation", e.target.value)}
|
|
||||||
disabled={isEducationMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{occupationOptions.map((item) => (
|
|
||||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
|
||||||
{getOptionLabel(item, "Occupation")}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.occupation && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.occupation}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Company / Organization Name */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Company / Organization Name{requiredMark}
|
|
||||||
</label>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Employee Type */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Employee Type{requiredMark}
|
|
||||||
</label>
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.employeeType)}
|
|
||||||
>
|
|
||||||
<InputLabel id="employeeType-label">
|
|
||||||
Select Employee Type
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="employeeType-label"
|
|
||||||
label="Select Employee Type"
|
|
||||||
name="employeeType"
|
|
||||||
value={data.employeeType}
|
|
||||||
onChange={(e) => handleChange("employeeType", e.target.value)}
|
|
||||||
disabled={isEducationMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{employeeTypeOptions.map((item) => (
|
|
||||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
|
||||||
{getOptionLabel(item, "Employee Type")}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.employeeType && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.employeeType}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Annual Income */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Annual Income{requiredMark}
|
|
||||||
</label>
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.income)}
|
|
||||||
>
|
|
||||||
<InputLabel id="income-label">Select Annual Income</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="income-label"
|
|
||||||
label="Select Annual Income"
|
|
||||||
name="income"
|
|
||||||
value={data.income}
|
|
||||||
onChange={(e) => handleChange("income", e.target.value)}
|
|
||||||
disabled={isEducationMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{annualIncomeOptions.map((item) => (
|
|
||||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
|
||||||
{getOptionLabel(item, "Annual Income")}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.income && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.income}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Work Location */}
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Work Location
|
|
||||||
</label>
|
|
||||||
{workLocationOptions.length > 0 ? (
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.workLocation)}
|
|
||||||
>
|
|
||||||
<InputLabel id="workLocation-label">
|
|
||||||
Select Work Location
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="workLocation-label"
|
|
||||||
label="Select Work Location"
|
|
||||||
name="workLocation"
|
|
||||||
value={data.workLocation}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleChange("workLocation", e.target.value)
|
|
||||||
}
|
|
||||||
disabled={isEducationMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{workLocationOptions.map((item) => (
|
|
||||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
|
||||||
{getOptionLabel(item, "Work Location")}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.workLocation && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.workLocation}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
) : (
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
name="workLocation"
|
|
||||||
label="Work Location"
|
|
||||||
value={data.workLocation}
|
|
||||||
onChange={(e) => handleChange("workLocation", e.target.value)}
|
|
||||||
error={Boolean(errors.workLocation)}
|
|
||||||
helperText={errors.workLocation}
|
|
||||||
placeholder="Enter Work Location"
|
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Grid
|
{/* 2. Highest Qualification */}
|
||||||
item
|
<div className="flex flex-col gap-2">
|
||||||
xs={12}
|
<label className="text-gray-900 text-[15px]">Highest Qualification{requiredMark}</label>
|
||||||
style={{
|
<FormControl fullWidth error={Boolean(localErrors.education)} id="education" disabled={!data.study_field || educationListQuery.isLoading}>
|
||||||
marginTop: "40px",
|
<InputLabel>Select Qualification</InputLabel>
|
||||||
display: "flex",
|
<Select
|
||||||
gap: 16,
|
value={data.education}
|
||||||
justifyContent: "center",
|
label="Select Qualification"
|
||||||
}}
|
onChange={(e) => handleChange("education", e.target.value)}
|
||||||
>
|
>
|
||||||
<Button variant="outlined" onClick={onSkipStep}>
|
{qualificationOptions.map((opt) => (
|
||||||
Next
|
<MenuItem key={opt.id} value={opt.id}>{opt.education_name || opt.name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.education && <FormHelperText>{localErrors.education}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3. Education in Detail */}
|
||||||
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Education in Detail{requiredMark}</label>
|
||||||
|
<TextField
|
||||||
|
id="education_detail"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
placeholder="Enter your education details"
|
||||||
|
value={data.education_detail}
|
||||||
|
onChange={(e) => handleChange("education_detail", e.target.value)}
|
||||||
|
error={Boolean(localErrors.education_detail)}
|
||||||
|
helperText={localErrors.education_detail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 4. College Name */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Enter College Name</label>
|
||||||
|
<TextField
|
||||||
|
id="college_name"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter College Name"
|
||||||
|
value={data.college_name}
|
||||||
|
onChange={(e) => handleChange("college_name", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 5. Employee Type */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Employee type{requiredMark}</label>
|
||||||
|
<FormControl fullWidth error={Boolean(localErrors.employee_type)} id="employee_type">
|
||||||
|
<InputLabel>Select Employee Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={data.employee_type}
|
||||||
|
label="Select Employee Type"
|
||||||
|
onChange={(e) => handleChange("employee_type", e.target.value)}
|
||||||
|
>
|
||||||
|
{employeeTypeOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.employee_type_name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.employee_type && <FormHelperText>{localErrors.employee_type}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isUnemployed && (
|
||||||
|
<>
|
||||||
|
{/* 6. Occupation */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Occupation{requiredMark}</label>
|
||||||
|
<FormControl fullWidth error={Boolean(localErrors.occupation)} id="occupation">
|
||||||
|
<InputLabel>Select Occupation</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={data.occupation}
|
||||||
|
label="Select Occupation"
|
||||||
|
onChange={(e) => handleChange("occupation", e.target.value)}
|
||||||
|
>
|
||||||
|
{occupationOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.occupation_name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.occupation && <FormHelperText>{localErrors.occupation}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 7. Occupation in Detail */}
|
||||||
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Occupation in Detail{requiredMark}</label>
|
||||||
|
<TextField
|
||||||
|
id="occupation_detail"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
placeholder="Enter your occupation details"
|
||||||
|
value={data.occupation_detail}
|
||||||
|
onChange={(e) => handleChange("occupation_detail", e.target.value)}
|
||||||
|
error={Boolean(localErrors.occupation_detail)}
|
||||||
|
helperText={localErrors.occupation_detail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 8. Company / Organization Name */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Company / Organization Name</label>
|
||||||
|
<TextField
|
||||||
|
id="company_name"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter Company Name"
|
||||||
|
value={data.company_name}
|
||||||
|
onChange={(e) => handleChange("company_name", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 9. Income Currency Type */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Income Currency Type{requiredMark}</label>
|
||||||
|
<FormControl fullWidth error={Boolean(localErrors.income_currency)} id="income_currency">
|
||||||
|
<InputLabel>Select Currency</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={data.income_currency}
|
||||||
|
label="Select Currency"
|
||||||
|
onChange={(e) => handleChange("income_currency", e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="INR">INR</MenuItem>
|
||||||
|
<MenuItem value="USD">USD</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{localErrors.income_currency && <FormHelperText>{localErrors.income_currency}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 10. Annual Income */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Annual Income{requiredMark}</label>
|
||||||
|
<TextField
|
||||||
|
id="annual_income"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter Annual Income"
|
||||||
|
value={data.annual_income}
|
||||||
|
onChange={(e) => handleChange("annual_income", e.target.value)}
|
||||||
|
error={Boolean(localErrors.annual_income)}
|
||||||
|
helperText={localErrors.annual_income}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
{data.income_currency === "USD" ? "$" : "₹"}
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 11. Country */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Country{requiredMark}</label>
|
||||||
|
<FormControl fullWidth error={Boolean(localErrors.work_country)} id="work_country">
|
||||||
|
<InputLabel>Select Country</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={data.work_country}
|
||||||
|
label="Select Country"
|
||||||
|
onChange={(e) => handleChange("work_country", e.target.value)}
|
||||||
|
>
|
||||||
|
{countryOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.country_name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.work_country && <FormHelperText>{localErrors.work_country}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 12. City/Town (Only if NOT India) */}
|
||||||
|
{!isIndia && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">City / Town{requiredMark}</label>
|
||||||
|
<TextField
|
||||||
|
id="work_city"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter City / Town"
|
||||||
|
value={data.work_city}
|
||||||
|
onChange={(e) => handleChange("work_city", e.target.value)}
|
||||||
|
error={Boolean(localErrors.work_city)}
|
||||||
|
helperText={localErrors.work_city}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 13. State (Only if India) */}
|
||||||
|
{isIndia && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">State{requiredMark}</label>
|
||||||
|
<FormControl fullWidth error={Boolean(localErrors.work_state)} id="work_state">
|
||||||
|
<InputLabel>Select State</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={data.work_state}
|
||||||
|
label="Select State"
|
||||||
|
onChange={(e) => handleChange("work_state", e.target.value)}
|
||||||
|
>
|
||||||
|
{stateOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.state_name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.work_state && <FormHelperText>{localErrors.work_state}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 14. City (District) (Only if India) */}
|
||||||
|
{isIndia && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">City{requiredMark}</label>
|
||||||
|
<FormControl fullWidth error={Boolean(localErrors.work_district)} id="work_district" disabled={!data.work_state || districtQuery.isLoading}>
|
||||||
|
<InputLabel>Select City</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={data.work_district}
|
||||||
|
label="Select City"
|
||||||
|
onChange={(e) => handleChange("work_district", e.target.value)}
|
||||||
|
>
|
||||||
|
{districtOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.district_name || opt.name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.work_district && <FormHelperText>{localErrors.work_district}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 15. Address */}
|
||||||
|
<div className="flex flex-col gap-2 md:col-span-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Address{requiredMark}</label>
|
||||||
|
<TextField
|
||||||
|
id="address"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
placeholder="Enter your address"
|
||||||
|
value={data.address}
|
||||||
|
onChange={(e) => handleChange("address", e.target.value)}
|
||||||
|
error={Boolean(localErrors.address)}
|
||||||
|
helperText={localErrors.address}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 5, display: "flex", gap: 2, justifyContent: "center" }}>
|
||||||
|
{onSkipStep && (
|
||||||
|
<Button variant="outlined" size="large" onClick={onSkipStep} sx={{ minWidth: 120 }}>
|
||||||
|
Skip
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
)}
|
||||||
Submit
|
<Button variant="contained" size="large" onClick={handleSubmit} sx={{ minWidth: 120 }}>
|
||||||
</Button>
|
{onSkipStep ? "Next" : "Update"}
|
||||||
</Grid>
|
</Button>
|
||||||
</form>
|
</Box>
|
||||||
</div>
|
</form>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useEffect, useMemo, useRef } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { updateFamilyDetails } from "../redux/registrationFormSlice";
|
import { updateFamilyDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
||||||
import {
|
import {
|
||||||
Grid,
|
Grid,
|
||||||
TextField,
|
TextField,
|
||||||
@ -12,46 +12,55 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useFamilyMasters } from "../hooks/useMasters";
|
import { useFamilyMasters } from "../hooks/useMasters";
|
||||||
|
import { useCityMasters } from "../hooks/useDependentMasters";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange }) => {
|
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange, isEditMode }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const data = useSelector((state) => state.registerform.familyDetails);
|
const data = useSelector((state) => state.registerform.familyDetails);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
const brotherSectionRef = useRef(null);
|
||||||
|
const sisterSectionRef = useRef(null);
|
||||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||||
|
|
||||||
const { data: familyMasters, isLoading: isFamilyMastersLoading } =
|
const { data: familyMasters, isLoading: isFamilyMastersLoading } = useFamilyMasters();
|
||||||
useFamilyMasters();
|
|
||||||
|
// District query for India
|
||||||
|
const districtQuery = useCityMasters(data.familyState);
|
||||||
|
|
||||||
const occupationOptions = useMemo(() => {
|
const occupationOptions = useMemo(() => {
|
||||||
const raw = familyMasters;
|
const raw = familyMasters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
return raw.occupation || [];
|
||||||
return raw.occupation || raw.occupations || [];
|
|
||||||
}, [familyMasters]);
|
}, [familyMasters]);
|
||||||
|
|
||||||
const maritalStatusOptions = useMemo(() => {
|
const maritalStatusOptions = useMemo(() => {
|
||||||
const raw = familyMasters;
|
const raw = familyMasters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
return raw.maritalStatus || [];
|
||||||
return raw.maritalStatus || raw.marital_status || [];
|
|
||||||
}, [familyMasters]);
|
}, [familyMasters]);
|
||||||
|
|
||||||
const familyStatusOptions = useMemo(() => {
|
const familyStatusOptions = useMemo(() => {
|
||||||
const raw = familyMasters;
|
const raw = familyMasters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return [];
|
return raw.familyStatus || [];
|
||||||
return raw.familyStatus || raw.family_status || [];
|
|
||||||
}, [familyMasters]);
|
}, [familyMasters]);
|
||||||
|
|
||||||
|
const countryOptions = useMemo(() => familyMasters?.country || [], [familyMasters]);
|
||||||
|
const stateOptions = useMemo(() => familyMasters?.state || [], [familyMasters]);
|
||||||
|
const districtOptions = useMemo(() => districtQuery.data?.districts || districtQuery.data || [], [districtQuery.data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const createSibling = () => ({
|
const createSibling = () => ({
|
||||||
|
type: "",
|
||||||
name: "",
|
name: "",
|
||||||
occupation: "",
|
occupation: "",
|
||||||
maritalStatus: "",
|
maritalStatus: "",
|
||||||
haveChildrens: "",
|
hasChildren: "",
|
||||||
|
details: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncSiblingArray = (arr, count) => {
|
const syncSiblingArray = (arr, count) => {
|
||||||
@ -72,20 +81,48 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
|
|
||||||
if (field === "brotherCount") {
|
if (field === "brotherCount") {
|
||||||
const count = Number(value) || 0;
|
const count = Number(value) || 0;
|
||||||
updates.brotherCount = count;
|
updates.brotherCount = value;
|
||||||
updates.brothers = syncSiblingArray(data.brothers, count);
|
updates.brothers = syncSiblingArray(data.brothers, count);
|
||||||
fieldsToClear.push("brothers");
|
fieldsToClear.push("brothers");
|
||||||
|
if (count > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
brotherSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
const firstInput = brotherSectionRef.current?.querySelector('.first-sibling-name');
|
||||||
|
if (firstInput) firstInput.focus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === "sisterCount") {
|
if (field === "sisterCount") {
|
||||||
const count = Number(value) || 0;
|
const count = Number(value) || 0;
|
||||||
updates.sisterCount = count;
|
updates.sisterCount = value;
|
||||||
updates.sisters = syncSiblingArray(data.sisters, count);
|
updates.sisters = syncSiblingArray(data.sisters, count);
|
||||||
fieldsToClear.push("sisters");
|
fieldsToClear.push("sisters");
|
||||||
|
if (count > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
sisterSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
const firstInput = sisterSectionRef.current?.querySelector('.first-sibling-name');
|
||||||
|
if (firstInput) firstInput.focus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field === "familyCountry") {
|
||||||
|
updates.familyState = "";
|
||||||
|
updates.familyDistrict = "";
|
||||||
|
updates.familyCity = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field === "familyState") {
|
||||||
|
updates.familyDistrict = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updateFamilyDetails(updates));
|
dispatch(updateFamilyDetails(updates));
|
||||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||||
|
|
||||||
|
if (!isEditMode) {
|
||||||
|
dispatch(clearAllStepsFrom(4));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSiblingChange = (type, index, field, value) => {
|
const handleSiblingChange = (type, index, field, value) => {
|
||||||
@ -94,14 +131,40 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
list[index] = { ...list[index], [field]: value };
|
list[index] = { ...list[index], [field]: value };
|
||||||
dispatch(updateFamilyDetails({ [type]: list }));
|
dispatch(updateFamilyDetails({ [type]: list }));
|
||||||
if (onFieldChange) onFieldChange(type);
|
if (onFieldChange) onFieldChange(type);
|
||||||
|
|
||||||
|
if (!isEditMode) {
|
||||||
|
dispatch(clearAllStepsFrom(4));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const scrollToError = (errorMap) => {
|
||||||
console.log("Submitting family details:", data);
|
const errorFields = Object.keys(errorMap);
|
||||||
|
if (errorFields.length > 0) {
|
||||||
|
const fieldId = errorFields[0];
|
||||||
|
const element = document.getElementById(fieldId);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
setTimeout(() => {
|
||||||
|
const focusable = element.querySelector('[role="combobox"]') ||
|
||||||
|
element.querySelector('[role="button"]') ||
|
||||||
|
element.querySelector("input") ||
|
||||||
|
element.querySelector("select") ||
|
||||||
|
element;
|
||||||
|
if (focusable && typeof focusable.focus === "function") {
|
||||||
|
focusable.focus();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
if (e) e.preventDefault();
|
||||||
onSubmitStep();
|
onSubmitStep();
|
||||||
};
|
};
|
||||||
|
|
||||||
const countOptions = Array.from({ length: 11 }, (_, i) => i);
|
const countOptions = Array.from({ length: 11 }, (_, i) => i);
|
||||||
|
const isIndia = Number(data.familyCountry) === 1;
|
||||||
|
|
||||||
const renderSiblingCard = (type, index) => {
|
const renderSiblingCard = (type, index) => {
|
||||||
const sibling = (data[type] || [])[index] || createSibling();
|
const sibling = (data[type] || [])[index] || createSibling();
|
||||||
@ -114,95 +177,88 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
padding: 2,
|
padding: 2,
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#fff",
|
||||||
|
mb: 4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-gray-900 text-[14px] font-semibold mb-3">
|
<div className="text-gray-900 text-[14px] font-semibold mb-3">
|
||||||
{labelPrefix} {index + 1}
|
{labelPrefix} {index + 1}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||||
|
<FormControl fullWidth variant="outlined">
|
||||||
|
<InputLabel>Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
label="Type"
|
||||||
|
value={sibling.type}
|
||||||
|
onChange={(e) => handleSiblingChange(type, index, "type", e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value=""><em>Select</em></MenuItem>
|
||||||
|
<MenuItem value="Elder">Elder</MenuItem>
|
||||||
|
<MenuItem value="Younger">Younger</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Name"
|
label="Name"
|
||||||
|
placeholder="Enter Name"
|
||||||
value={sibling.name}
|
value={sibling.name}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleSiblingChange(type, index, "name", e.target.value)}
|
||||||
handleSiblingChange(type, index, "name", e.target.value)
|
|
||||||
}
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
inputProps={{ className: index === 0 ? "first-sibling-name" : "" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl fullWidth variant="outlined">
|
<FormControl fullWidth variant="outlined">
|
||||||
<InputLabel id={`${type}-${index}-occupation-label`}>
|
<InputLabel>Occupation</InputLabel>
|
||||||
Occupation
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
<Select
|
||||||
labelId={`${type}-${index}-occupation-label`}
|
|
||||||
label="Occupation"
|
label="Occupation"
|
||||||
value={sibling.occupation}
|
value={sibling.occupation}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleSiblingChange(type, index, "occupation", e.target.value)}
|
||||||
handleSiblingChange(type, index, "occupation", e.target.value)
|
|
||||||
}
|
|
||||||
disabled={isFamilyMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<MenuItem value=""><em>Select</em></MenuItem>
|
||||||
{occupationOptions.map((opt) => (
|
{occupationOptions.map((opt) => (
|
||||||
<MenuItem key={opt} value={opt}>
|
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
|
||||||
{opt}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth variant="outlined">
|
<FormControl fullWidth variant="outlined">
|
||||||
<InputLabel id={`${type}-${index}-marital-label`}>
|
<InputLabel>Marital Status</InputLabel>
|
||||||
Marital Status
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
<Select
|
||||||
labelId={`${type}-${index}-marital-label`}
|
|
||||||
label="Marital Status"
|
label="Marital Status"
|
||||||
value={sibling.maritalStatus}
|
value={sibling.maritalStatus}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleSiblingChange(type, index, "maritalStatus", e.target.value)}
|
||||||
handleSiblingChange(type, index, "maritalStatus", e.target.value)
|
|
||||||
}
|
|
||||||
disabled={isFamilyMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<MenuItem value=""><em>Select</em></MenuItem>
|
||||||
{maritalStatusOptions.map((opt) => (
|
{maritalStatusOptions.map((opt) => (
|
||||||
<MenuItem key={opt} value={opt}>
|
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
|
||||||
{opt}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth variant="outlined">
|
<FormControl fullWidth variant="outlined">
|
||||||
<InputLabel id={`${type}-${index}-children-label`}>
|
<InputLabel>Have Children</InputLabel>
|
||||||
Have Children
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
<Select
|
||||||
labelId={`${type}-${index}-children-label`}
|
|
||||||
label="Have Children"
|
label="Have Children"
|
||||||
value={sibling.haveChildrens}
|
value={sibling.hasChildren}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleSiblingChange(type, index, "hasChildren", e.target.value)}
|
||||||
handleSiblingChange(
|
|
||||||
type,
|
|
||||||
index,
|
|
||||||
"haveChildrens",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<MenuItem value={1}>Yes</MenuItem>
|
<MenuItem value=""><em>Select</em></MenuItem>
|
||||||
<MenuItem value={0}>No</MenuItem>
|
<MenuItem value="Yes">Yes</MenuItem>
|
||||||
|
<MenuItem value="No">No</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
label="Additional Details"
|
||||||
|
value={sibling.details}
|
||||||
|
onChange={(e) => handleSiblingChange(type, index, "details", e.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -212,20 +268,18 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4" id="fatherName">
|
||||||
<label className="text-gray-900 text-[15px]">
|
<label className="text-gray-900 text-[15px]">
|
||||||
Father Name{requiredMark}
|
Father Name{requiredMark}
|
||||||
</label>
|
</label>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
name="fatherName"
|
placeholder="Enter Father Name"
|
||||||
label="Father Name"
|
|
||||||
value={data.fatherName}
|
value={data.fatherName}
|
||||||
onChange={(e) => handleChange("fatherName", e.target.value)}
|
onChange={(e) => handleChange("fatherName", e.target.value)}
|
||||||
error={Boolean(errors.fatherName)}
|
error={Boolean(errors.fatherName)}
|
||||||
helperText={errors.fatherName}
|
helperText={errors.fatherName}
|
||||||
placeholder="Enter Father Name"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -236,30 +290,24 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
</label>
|
</label>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
name="fatherOccupation"
|
placeholder="Enter Father Occupation"
|
||||||
label="Father Occupation"
|
|
||||||
value={data.fatherOccupation}
|
value={data.fatherOccupation}
|
||||||
onChange={(e) => handleChange("fatherOccupation", e.target.value)}
|
onChange={(e) => handleChange("fatherOccupation", e.target.value)}
|
||||||
error={Boolean(errors.fatherOccupation)}
|
|
||||||
helperText={errors.fatherOccupation}
|
|
||||||
placeholder="Enter Father Occupation"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4" id="motherName">
|
||||||
<label className="text-gray-900 text-[15px]">
|
<label className="text-gray-900 text-[15px]">
|
||||||
Mother Name{requiredMark}
|
Mother Name{requiredMark}
|
||||||
</label>
|
</label>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
name="motherName"
|
placeholder="Enter Mother Name"
|
||||||
label="Mother Name"
|
|
||||||
value={data.motherName}
|
value={data.motherName}
|
||||||
onChange={(e) => handleChange("motherName", e.target.value)}
|
onChange={(e) => handleChange("motherName", e.target.value)}
|
||||||
error={Boolean(errors.motherName)}
|
error={Boolean(errors.motherName)}
|
||||||
helperText={errors.motherName}
|
helperText={errors.motherName}
|
||||||
placeholder="Enter Mother Name"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -270,13 +318,9 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
</label>
|
</label>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
name="motherOccupation"
|
placeholder="Enter Mother Occupation"
|
||||||
label="Mother Occupation"
|
|
||||||
value={data.motherOccupation}
|
value={data.motherOccupation}
|
||||||
onChange={(e) => handleChange("motherOccupation", e.target.value)}
|
onChange={(e) => handleChange("motherOccupation", e.target.value)}
|
||||||
error={Boolean(errors.motherOccupation)}
|
|
||||||
helperText={errors.motherOccupation}
|
|
||||||
placeholder="Enter Mother Occupation"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -286,14 +330,13 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
Brother Count
|
Brother Count
|
||||||
</label>
|
</label>
|
||||||
<FormControl fullWidth variant="outlined">
|
<FormControl fullWidth variant="outlined">
|
||||||
<InputLabel id="brotherCount-label">Select Brother Count</InputLabel>
|
<InputLabel>Select Brother Count</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId="brotherCount-label"
|
|
||||||
label="Select Brother Count"
|
label="Select Brother Count"
|
||||||
name="brotherCount"
|
|
||||||
value={data.brotherCount}
|
value={data.brotherCount}
|
||||||
onChange={(e) => handleChange("brotherCount", e.target.value)}
|
onChange={(e) => handleChange("brotherCount", e.target.value)}
|
||||||
>
|
>
|
||||||
|
<MenuItem value=""><em>Select</em></MenuItem>
|
||||||
{countOptions.map((count) => (
|
{countOptions.map((count) => (
|
||||||
<MenuItem key={count} value={count}>
|
<MenuItem key={count} value={count}>
|
||||||
{count}
|
{count}
|
||||||
@ -303,19 +346,27 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{Number(data.brotherCount) > 0 && (
|
||||||
|
<div className="md:col-span-2 mt-2" ref={brotherSectionRef}>
|
||||||
|
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
||||||
|
Brother Details
|
||||||
|
</div>
|
||||||
|
{data.brothers.map((_, i) => renderSiblingCard("brothers", i))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<label className="text-gray-900 text-[15px]">
|
<label className="text-gray-900 text-[15px]">
|
||||||
Sister Count
|
Sister Count
|
||||||
</label>
|
</label>
|
||||||
<FormControl fullWidth variant="outlined">
|
<FormControl fullWidth variant="outlined">
|
||||||
<InputLabel id="sisterCount-label">Select Sister Count</InputLabel>
|
<InputLabel>Select Sister Count</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId="sisterCount-label"
|
|
||||||
label="Select Sister Count"
|
label="Select Sister Count"
|
||||||
name="sisterCount"
|
|
||||||
value={data.sisterCount}
|
value={data.sisterCount}
|
||||||
onChange={(e) => handleChange("sisterCount", e.target.value)}
|
onChange={(e) => handleChange("sisterCount", e.target.value)}
|
||||||
>
|
>
|
||||||
|
<MenuItem value=""><em>Select</em></MenuItem>
|
||||||
{countOptions.map((count) => (
|
{countOptions.map((count) => (
|
||||||
<MenuItem key={count} value={count}>
|
<MenuItem key={count} value={count}>
|
||||||
{count}
|
{count}
|
||||||
@ -325,103 +376,183 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
{Number(data.sisterCount) > 0 && (
|
||||||
|
<div className="md:col-span-2 mt-2" ref={sisterSectionRef}>
|
||||||
|
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
||||||
|
Sister Details
|
||||||
|
</div>
|
||||||
|
{data.sisters.map((_, i) => renderSiblingCard("sisters", i))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4" id="familyStatus">
|
||||||
<label className="text-gray-900 text-[15px]">
|
<label className="text-gray-900 text-[15px]">
|
||||||
Family Status{requiredMark}
|
Family Status{requiredMark}
|
||||||
</label>
|
</label>
|
||||||
<FormControl
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.familyStatus)}>
|
||||||
fullWidth
|
<InputLabel>Select Family Status</InputLabel>
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.familyStatus)}
|
|
||||||
>
|
|
||||||
<InputLabel id="familyStatus-label">Select Family Status</InputLabel>
|
|
||||||
<Select
|
<Select
|
||||||
labelId="familyStatus-label"
|
|
||||||
label="Select Family Status"
|
label="Select Family Status"
|
||||||
name="familyStatus"
|
|
||||||
value={data.familyStatus}
|
value={data.familyStatus}
|
||||||
onChange={(e) => handleChange("familyStatus", e.target.value)}
|
onChange={(e) => handleChange("familyStatus", e.target.value)}
|
||||||
disabled={isFamilyMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{familyStatusOptions.map((item) => (
|
{familyStatusOptions.map((item) => (
|
||||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
<MenuItem key={item.id} value={item.id}>
|
||||||
{item.family_type_name || item.name || item}
|
{item.family_type_name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
{errors.familyStatus && (
|
{errors.familyStatus && (
|
||||||
<p
|
<p className="text-[#d32f2f] text-[0.75rem] mt-1 ml-3">{errors.familyStatus}</p>
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.familyStatus}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4" id="nativePlace">
|
||||||
<label className="text-gray-900 text-[15px]">Native Place</label>
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Native Place{requiredMark}
|
||||||
|
</label>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
name="nativePlace"
|
placeholder="Enter Native Place"
|
||||||
label="Native Place"
|
|
||||||
value={data.nativePlace}
|
value={data.nativePlace}
|
||||||
onChange={(e) => handleChange("nativePlace", e.target.value)}
|
onChange={(e) => handleChange("nativePlace", e.target.value)}
|
||||||
error={Boolean(errors.nativePlace)}
|
error={Boolean(errors.nativePlace)}
|
||||||
helperText={errors.nativePlace}
|
helperText={errors.nativePlace}
|
||||||
placeholder="Enter Native Place"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Country Living
|
||||||
|
</label>
|
||||||
|
<FormControl fullWidth variant="outlined">
|
||||||
|
<InputLabel>Select Country</InputLabel>
|
||||||
|
<Select
|
||||||
|
label="Select Country"
|
||||||
|
value={data.familyCountry}
|
||||||
|
onChange={(e) => handleChange("familyCountry", e.target.value)}
|
||||||
|
>
|
||||||
|
{countryOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.country_name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isIndia ? (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Residing State
|
||||||
|
</label>
|
||||||
|
<FormControl fullWidth variant="outlined" disabled={!data.familyCountry}>
|
||||||
|
<InputLabel>Select State</InputLabel>
|
||||||
|
<Select
|
||||||
|
label="Select State"
|
||||||
|
value={data.familyState}
|
||||||
|
onChange={(e) => handleChange("familyState", e.target.value)}
|
||||||
|
>
|
||||||
|
{stateOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.state_name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Residing City
|
||||||
|
</label>
|
||||||
|
<FormControl fullWidth variant="outlined" disabled={!data.familyState || districtQuery.isLoading}>
|
||||||
|
<InputLabel>Select City</InputLabel>
|
||||||
|
<Select
|
||||||
|
label="Select City"
|
||||||
|
value={data.familyDistrict}
|
||||||
|
onChange={(e) => handleChange("familyDistrict", e.target.value)}
|
||||||
|
>
|
||||||
|
{districtOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>{opt.district_name || opt.name}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : data.familyCountry ? (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
City / Town
|
||||||
|
</label>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter City / Town"
|
||||||
|
value={data.familyCity}
|
||||||
|
onChange={(e) => handleChange("familyCity", e.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 md:col-span-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Address
|
||||||
|
</label>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
placeholder="Enter complete address"
|
||||||
|
value={data.address}
|
||||||
|
onChange={(e) => handleChange("address", e.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 md:col-span-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Expectations / Requirements Details
|
||||||
|
</label>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
placeholder="Describe your expectations"
|
||||||
|
value={data.expectationDetails}
|
||||||
|
onChange={(e) => handleChange("expectationDetails", e.target.value)}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className="text-gray-900 text-[15px]">
|
||||||
|
Willing to go abroad
|
||||||
|
</label>
|
||||||
|
<FormControl fullWidth variant="outlined">
|
||||||
|
<InputLabel>Select Option</InputLabel>
|
||||||
|
<Select
|
||||||
|
label="Select Option"
|
||||||
|
value={data.willingToGoAbroad}
|
||||||
|
onChange={(e) => handleChange("willingToGoAbroad", e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="Yes">Yes</MenuItem>
|
||||||
|
<MenuItem value="No">No</MenuItem>
|
||||||
|
<MenuItem value="Any">Any</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Number(data.brotherCount) > 0 && (
|
<div className="mt-10 flex gap-4 justify-center">
|
||||||
<div className="mt-6">
|
{onSkipStep && (
|
||||||
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
<Button variant="outlined" onClick={onSkipStep} sx={{ minWidth: 120 }}>
|
||||||
Brother Details
|
Skip
|
||||||
</div>
|
</Button>
|
||||||
<div className="grid grid-cols-1 gap-4">
|
)}
|
||||||
{Array.from({ length: Number(data.brotherCount) }).map(
|
<Button variant="contained" color="primary" onClick={handleSubmit} sx={{ minWidth: 120 }}>
|
||||||
(_, index) => renderSiblingCard("brothers", index)
|
{onSkipStep ? "Next" : "Update"}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{Number(data.sisterCount) > 0 && (
|
|
||||||
<div className="mt-6">
|
|
||||||
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
|
||||||
Sister Details
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-4">
|
|
||||||
{Array.from({ length: Number(data.sisterCount) }).map(
|
|
||||||
(_, index) => renderSiblingCard("sisters", index)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Grid
|
|
||||||
item
|
|
||||||
xs={12}
|
|
||||||
sx={{ marginTop: 10, display: "flex", gap: 4, justifyContent: "center" }}
|
|
||||||
>
|
|
||||||
<Button variant="outlined" onClick={onSkipStep}>
|
|
||||||
Skip
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
</div>
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
setAge,
|
setAge,
|
||||||
setHeight,
|
setHeight,
|
||||||
setMaritalStatus,
|
|
||||||
setMotherTongue,
|
|
||||||
setReligion,
|
|
||||||
setMatchesWithHoroscope,
|
|
||||||
setCaste,
|
setCaste,
|
||||||
setSubCaste,
|
setSubCaste,
|
||||||
updateFilter,
|
updateFilter,
|
||||||
|
resetFilters,
|
||||||
} from "../redux/filterSlice";
|
} from "../redux/filterSlice";
|
||||||
import {
|
import {
|
||||||
Slider,
|
Slider,
|
||||||
@ -26,8 +23,12 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
|
CircularProgress,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown, Lock, Crown, RotateCcw, Check } from "lucide-react";
|
||||||
|
import { useProfilesFilterMasters } from "../hooks/useProfiles";
|
||||||
|
import { useCityMasters } from "../hooks/useDependentMasters";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const FilterForm = () => {
|
const FilterForm = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -40,34 +41,61 @@ const FilterForm = () => {
|
|||||||
location: false,
|
location: false,
|
||||||
lifestyle: false,
|
lifestyle: false,
|
||||||
family: false,
|
family: false,
|
||||||
|
paidBenefit: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: filterMasters, isLoading, isError } = useProfilesFilterMasters();
|
||||||
|
|
||||||
|
const { data: cityData } = useCityMasters(
|
||||||
|
filters.state && filters.state.length > 0 ? filters.state : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const cityOptions = useMemo(() => {
|
||||||
|
const raw = cityData;
|
||||||
|
if (!raw) return [];
|
||||||
|
if (Array.isArray(raw)) return raw;
|
||||||
|
return raw.subCaste || raw.district || raw.data || [];
|
||||||
|
}, [cityData]);
|
||||||
|
|
||||||
const handleAccordionChange = (section) => (event, isExpanded) => {
|
const handleAccordionChange = (section) => (event, isExpanded) => {
|
||||||
setExpandedSections((prev) => ({ ...prev, [section]: isExpanded }));
|
setExpandedSections((prev) => ({ ...prev, [section]: isExpanded }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const casteOptions = ["Agamudayar", "Pillai", "Vellalar"];
|
|
||||||
const motherTongueOptions = [
|
|
||||||
"Tamil",
|
|
||||||
"Telugu",
|
|
||||||
"Malayalam",
|
|
||||||
"Kannada",
|
|
||||||
"Hindi",
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
console.log("Filter Values:", filters);
|
console.log("Filter Values:", filters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
dispatch(resetFilters());
|
||||||
|
toast.success("Filters cleared");
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectionChange = (field, value) => {
|
const handleSelectionChange = (field, value) => {
|
||||||
console.log(`${field} selected:`, value);
|
console.log(`${field} selected:`, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[300px]">
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[300px]">
|
||||||
|
<Typography color="error">Failed to load filter options</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto p-4 px-0 md:p-6 ">
|
<div className="max-w-6xl mx-auto p-4 px-0 md:p-6 ">
|
||||||
<div className="bg-white rounded-lg shadow-sm">
|
<div className="bg-white rounded-lg shadow-sm">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b border-pink-200 p-4 bg-[#fff5ed]">
|
<div className="border-b border-pink-200 p-4 bg-[#f2f2f2]">
|
||||||
<Typography variant="h5" className="font-semibold text-center">
|
<Typography variant="h5" className="font-semibold text-center">
|
||||||
Partner Filter preference
|
Partner Filter preference
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -98,7 +126,7 @@ const FilterForm = () => {
|
|||||||
<Typography gutterBottom sx={{marginBottom:"30px"}}>Age</Typography>
|
<Typography gutterBottom sx={{marginBottom:"30px"}}>Age</Typography>
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<Slider
|
<Slider
|
||||||
value={filters.age}
|
value={[filters.from_age, filters.to_age]}
|
||||||
onChange={(e, newValue) => {
|
onChange={(e, newValue) => {
|
||||||
dispatch(setAge(newValue));
|
dispatch(setAge(newValue));
|
||||||
handleSelectionChange("Age", newValue);
|
handleSelectionChange("Age", newValue);
|
||||||
@ -119,7 +147,7 @@ const FilterForm = () => {
|
|||||||
<Typography gutterBottom sx={{marginBottom:"30px"}}>Height</Typography>
|
<Typography gutterBottom sx={{marginBottom:"30px"}}>Height</Typography>
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<Slider
|
<Slider
|
||||||
value={filters.height}
|
value={[filters.from_height, filters.to_height]}
|
||||||
onChange={(e, newValue) => {
|
onChange={(e, newValue) => {
|
||||||
dispatch(setHeight(newValue));
|
dispatch(setHeight(newValue));
|
||||||
handleSelectionChange("Height", newValue);
|
handleSelectionChange("Height", newValue);
|
||||||
@ -141,46 +169,40 @@ const FilterForm = () => {
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Marital Status</InputLabel>
|
<InputLabel>Marital Status</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.maritalStatus}
|
multiple
|
||||||
|
value={filters.marital_status || []}
|
||||||
label="Marital Status"
|
label="Marital Status"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(setMaritalStatus(e.target.value));
|
dispatch(updateFilter({ marital_status: e.target.value }));
|
||||||
handleSelectionChange("Marital Status", 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) => (
|
renderValue={(selected) => (
|
||||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
{selected.map((value) => (
|
{selected.map((value) => {
|
||||||
<Chip key={value} label={value} size="small" />
|
const label = filterMasters?.marital_statuses?.find((s) => s.id === value)?.marital_status_name || value;
|
||||||
))}
|
return (
|
||||||
|
<Chip
|
||||||
|
key={value}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onDelete={() => dispatch(updateFilter({ marital_status: filters.marital_status.filter((v) => v !== value) }))}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{motherTongueOptions.map((lang) => (
|
{filterMasters?.marital_statuses?.map((status) => (
|
||||||
<MenuItem key={lang} value={lang}>
|
<MenuItem key={status.id} value={status.id}>
|
||||||
{lang}
|
{status.marital_status_name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -207,61 +229,69 @@ const FilterForm = () => {
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Religion</InputLabel>
|
<InputLabel>Religion</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.religion}
|
multiple
|
||||||
|
value={filters.religion || []}
|
||||||
label="Religion"
|
label="Religion"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(setReligion(e.target.value));
|
dispatch(updateFilter({ religion: e.target.value }));
|
||||||
handleSelectionChange("Religion", e.target.value);
|
handleSelectionChange("Religion", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.religions?.find((r) => r.id === value)?.religion_name || value;
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={value}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onDelete={() => dispatch(updateFilter({ religion: filters.religion.filter((v) => v !== value) }))}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Hindu">Hindu</MenuItem>
|
{filterMasters?.religions?.map((rel) => (
|
||||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
<MenuItem key={rel.id} value={rel.id}>
|
||||||
<MenuItem value="Christian">Christian</MenuItem>
|
{rel.religion_name}
|
||||||
<MenuItem value="Sikh">Sikh</MenuItem>
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</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 */}
|
{/* Caste */}
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Caste (Multi Select)</InputLabel>
|
<InputLabel>Caste</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={filters.caste}
|
value={filters.caste}
|
||||||
label="Caste (Multi Select)"
|
label="Caste"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(setCaste(e.target.value));
|
dispatch(setCaste(e.target.value));
|
||||||
handleSelectionChange("Caste", e.target.value);
|
handleSelectionChange("Caste", e.target.value);
|
||||||
}}
|
}}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<Box
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
|
{selected.map((value) => {
|
||||||
>
|
const label = filterMasters?.castes?.find((c) => c.id === value)?.caste_name || value;
|
||||||
{selected.map((value) => (
|
return (
|
||||||
<Chip key={value} label={value} size="small" />
|
<Chip
|
||||||
))}
|
key={value}
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
onDelete={() => dispatch(setCaste(filters.caste.filter((v) => v !== value)))}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{["Agamudayar", "Pillai", "Vellalar"].map((caste) => (
|
{filterMasters?.castes?.map((caste) => (
|
||||||
<MenuItem key={caste} value={caste}>
|
<MenuItem key={caste.id} value={caste.id}>
|
||||||
{caste}
|
{caste.caste_name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@ -269,21 +299,19 @@ const FilterForm = () => {
|
|||||||
|
|
||||||
{/* Sub-Caste */}
|
{/* Sub-Caste */}
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Sub-Caste (Multi Select)</InputLabel>
|
<InputLabel>Sub-Caste</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={filters.subCaste}
|
value={filters.sub_caste}
|
||||||
label="Sub-Caste (Multi Select)"
|
label="Sub-Caste"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(setSubCaste(e.target.value));
|
dispatch(setSubCaste(e.target.value));
|
||||||
handleSelectionChange("Sub-Caste", e.target.value);
|
handleSelectionChange("Sub-Caste", e.target.value);
|
||||||
}}
|
}}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<Box
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
|
|
||||||
>
|
|
||||||
{selected.map((value) => (
|
{selected.map((value) => (
|
||||||
<Chip key={value} label={value} size="small" />
|
<Chip key={value} label={value} size="small" onDelete={() => dispatch(setSubCaste(filters.sub_caste.filter((v) => v !== value)))} onMouseDown={(e) => e.stopPropagation()} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -296,39 +324,7 @@ const FilterForm = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</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>
|
</div>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -356,66 +352,108 @@ const FilterForm = () => {
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Occupation</InputLabel>
|
<InputLabel>Occupation</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.occupation}
|
multiple
|
||||||
|
value={filters.occupation || []}
|
||||||
label="Occupation"
|
label="Occupation"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ occupation: e.target.value }));
|
dispatch(updateFilter({ occupation: e.target.value }));
|
||||||
handleSelectionChange("Occupation", e.target.value);
|
handleSelectionChange("Occupation", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.occupations?.find((o) => o.id === value)?.occupation_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ occupation: filters.occupation.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.occupations?.map((occ) => (
|
||||||
<MenuItem value="Software Engineer">
|
<MenuItem key={occ.id} value={occ.id}>
|
||||||
Software Engineer
|
{occ.occupation_name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem value="Doctor">Doctor</MenuItem>
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Annual Income</InputLabel>
|
<InputLabel>Annual Income</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.annualIncome}
|
multiple
|
||||||
|
value={filters.annual_income || []}
|
||||||
label="Annual Income"
|
label="Annual Income"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ annualIncome: e.target.value }));
|
dispatch(updateFilter({ annual_income: e.target.value }));
|
||||||
handleSelectionChange("Annual Income", e.target.value);
|
handleSelectionChange("Annual Income", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.annual_incomes?.find((i) => i.id === value)?.annual_income_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ annual_income: filters.annual_income.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.annual_incomes?.map((inc) => (
|
||||||
<MenuItem value="0-5 Lakhs">0-5 Lakhs</MenuItem>
|
<MenuItem key={inc.id} value={inc.id}>
|
||||||
<MenuItem value="5-10 Lakhs">5-10 Lakhs</MenuItem>
|
{inc.annual_income_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Employee Type</InputLabel>
|
<InputLabel>Employee Type</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.employeeType}
|
multiple
|
||||||
|
value={filters.employee_type || []}
|
||||||
label="Employee Type"
|
label="Employee Type"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ employeeType: e.target.value }));
|
dispatch(updateFilter({ employee_type: e.target.value }));
|
||||||
handleSelectionChange("Employee Type", e.target.value);
|
handleSelectionChange("Employee Type", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.employee_types?.find((et) => et.id === value)?.employee_type_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ employee_type: filters.employee_type.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.employee_types?.map((emp) => (
|
||||||
<MenuItem value="Government">Government</MenuItem>
|
<MenuItem key={emp.id} value={emp.id}>
|
||||||
<MenuItem value="Private">Private</MenuItem>
|
{emp.employee_type_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Education</InputLabel>
|
<InputLabel>Education</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.education}
|
multiple
|
||||||
|
value={filters.education || []}
|
||||||
label="Education"
|
label="Education"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ education: e.target.value }));
|
dispatch(updateFilter({ education: e.target.value }));
|
||||||
handleSelectionChange("Education", e.target.value);
|
handleSelectionChange("Education", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.educations?.find((ed) => ed.id === value)?.education_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ education: filters.education.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.educations?.map((edu) => (
|
||||||
<MenuItem value="Bachelor">Bachelor</MenuItem>
|
<MenuItem key={edu.id} value={edu.id}>
|
||||||
<MenuItem value="Master">Master</MenuItem>
|
{edu.education_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
@ -443,50 +481,59 @@ const FilterForm = () => {
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>State</InputLabel>
|
<InputLabel>State</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.state}
|
multiple
|
||||||
|
value={filters.state || []}
|
||||||
label="State"
|
label="State"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ state: e.target.value }));
|
dispatch(updateFilter({ state: e.target.value, district: [] }));
|
||||||
handleSelectionChange("State", e.target.value);
|
handleSelectionChange("State", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.states?.find((s) => s.id === value)?.state_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ state: filters.state.filter((v) => v !== value), district: [] }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.states?.map((state) => (
|
||||||
<MenuItem value="Tamil Nadu">Tamil Nadu</MenuItem>
|
<MenuItem key={state.id} value={state.id}>
|
||||||
<MenuItem value="Karnataka">Karnataka</MenuItem>
|
{state.state_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Country</InputLabel>
|
<InputLabel>City</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.country}
|
multiple
|
||||||
label="Country"
|
value={filters.district || []}
|
||||||
|
label="City"
|
||||||
|
disabled={!filters.state || filters.state.length === 0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ country: e.target.value }));
|
dispatch(updateFilter({ district: e.target.value }));
|
||||||
handleSelectionChange("Country", e.target.value);
|
handleSelectionChange("City", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = cityOptions.find((c) => c.id === value)?.district_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ district: filters.district.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{cityOptions.map((city) => (
|
||||||
<MenuItem value="India">India</MenuItem>
|
<MenuItem key={city.id} value={city.id}>
|
||||||
<MenuItem value="USA">USA</MenuItem>
|
{city.district_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</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>
|
</div>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -512,54 +559,22 @@ const FilterForm = () => {
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Eating Habits</InputLabel>
|
<InputLabel>Eating Habits</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.eatingHabits}
|
value={filters.diet}
|
||||||
label="Eating Habits"
|
label="Eating Habits"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ eatingHabits: e.target.value }));
|
dispatch(updateFilter({ diet: e.target.value }));
|
||||||
handleSelectionChange("Eating Habits", e.target.value);
|
handleSelectionChange("Eating Habits", e.target.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.diets?.map((diet) => (
|
||||||
<MenuItem value="Vegetarian">Vegetarian</MenuItem>
|
<MenuItem key={diet.id} value={diet.id}>
|
||||||
<MenuItem value="Non-Vegetarian">Non-Vegetarian</MenuItem>
|
{diet.diet_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</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>
|
</div>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -585,64 +600,116 @@ const FilterForm = () => {
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Family Type</InputLabel>
|
<InputLabel>Family Type</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.familyType}
|
multiple
|
||||||
|
value={filters.family_type || []}
|
||||||
label="Family Type"
|
label="Family Type"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ familyType: e.target.value }));
|
dispatch(updateFilter({ family_type: e.target.value }));
|
||||||
handleSelectionChange("Family Type", e.target.value);
|
handleSelectionChange("Family Type", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.family_types?.find((t) => t.id === value)?.family_type_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ family_type: filters.family_type.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.family_types?.map((type) => (
|
||||||
<MenuItem value="Nuclear">Nuclear</MenuItem>
|
<MenuItem key={type.id} value={type.id}>
|
||||||
<MenuItem value="Joint">Joint</MenuItem>
|
{type.family_type_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
</div>
|
||||||
<InputLabel>Family Status</InputLabel>
|
</AccordionDetails>
|
||||||
<Select
|
</Accordion>
|
||||||
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>
|
|
||||||
|
|
||||||
|
{/* Paid Membership Benefit Section */}
|
||||||
|
<Accordion
|
||||||
|
expanded={expandedSections.paidBenefit && filters.isPaidMember}
|
||||||
|
onChange={(e, isExpanded) => {
|
||||||
|
if (!filters.isPaidMember) {
|
||||||
|
toast.error("This feature is locked for paid members only", {
|
||||||
|
icon: "🔒",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleAccordionChange("paidBenefit")(e, isExpanded);
|
||||||
|
}}
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={filters.isPaidMember ? <ChevronDown /> : <Lock size={18} className="text-gray-400" />}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#f5fbff",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#fff6f0",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 -mx-4 -my-2 px-4 py-2 w-full">
|
||||||
|
<Crown size={20} className="text-yellow-600" />
|
||||||
|
<Typography className="font-semibold">
|
||||||
|
Paid Membership Benefit
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||||
|
{/* Star Filter */}
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Family Value</InputLabel>
|
<InputLabel>Star</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.familyValue}
|
multiple
|
||||||
label="Family Value"
|
value={filters.star || []}
|
||||||
|
label="Star"
|
||||||
|
disabled={!filters.isPaidMember}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
dispatch(updateFilter({ familyValue: e.target.value }));
|
dispatch(updateFilter({ star: e.target.value }));
|
||||||
handleSelectionChange("Family Value", e.target.value);
|
handleSelectionChange("Star", e.target.value);
|
||||||
}}
|
}}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
|
{selected.map((value) => {
|
||||||
|
const label = filterMasters?.stars?.find((s) => s.id === value)?.star_name || value;
|
||||||
|
return <Chip key={value} label={label} size="small" onDelete={() => dispatch(updateFilter({ star: filters.star.filter((v) => v !== value) }))} onMouseDown={(e) => e.stopPropagation()} />;
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<MenuItem value="Any">Any</MenuItem>
|
{filterMasters?.stars?.map((star) => (
|
||||||
<MenuItem value="Traditional">Traditional</MenuItem>
|
<MenuItem key={star.id} value={star.id}>
|
||||||
<MenuItem value="Moderate">Moderate</MenuItem>
|
{star.star_name}
|
||||||
<MenuItem value="Liberal">Liberal</MenuItem>
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Sticky Footer Actions */}
|
||||||
<div className="mt-6 flex justify-center">
|
<div className="sticky bottom-0 bg-white p-4 border-t border-gray-200 flex justify-between gap-4 mt-4 z-10 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="large"
|
||||||
|
onClick={handleClear}
|
||||||
|
className="px-8"
|
||||||
|
color="inherit"
|
||||||
|
startIcon={<RotateCcw size={20} />}
|
||||||
|
>
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
className="px-12"
|
className="px-12"
|
||||||
|
startIcon={<Check size={20} />}
|
||||||
>
|
>
|
||||||
Apply Filters
|
Apply Filters
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -27,7 +27,7 @@ const FilterModal = () => {
|
|||||||
open={open}
|
open={open}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth="lg" // adjust: "sm" | "md" | "lg"
|
maxWidth="md" // adjust: "sm" | "md" | "lg"
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{background:"#f5fbff"}}>
|
<DialogTitle sx={{background:"#f5fbff"}}>
|
||||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { updateLifestyleDetails } from "../redux/registrationFormSlice";
|
import { updateLifestyleDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
||||||
import {
|
import {
|
||||||
Grid,
|
Grid,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -11,18 +11,26 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogActions,
|
||||||
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||||
import { useLifestyleMasters } from "../hooks/useMasters";
|
import { useLifestyleMasters } from "../hooks/useMasters";
|
||||||
|
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||||
|
|
||||||
const LifestyleDetailsForm = ({
|
const LifestyleDetailsForm = ({
|
||||||
onSubmitStep,
|
onSubmitStep,
|
||||||
onSkipStep,
|
onSkipStep,
|
||||||
errors,
|
errors,
|
||||||
onFieldChange,
|
onFieldChange,
|
||||||
|
isEditMode,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
||||||
@ -53,6 +61,15 @@ const LifestyleDetailsForm = ({
|
|||||||
return raw.grahas || raw.graha || [];
|
return raw.grahas || raw.graha || [];
|
||||||
}, [lifestyleMasters]);
|
}, [lifestyleMasters]);
|
||||||
|
|
||||||
|
const [confirmDialog, setConfirmDialog] = useState({
|
||||||
|
open: false,
|
||||||
|
type: "",
|
||||||
|
house: null,
|
||||||
|
item: "",
|
||||||
|
sourceHouse: null,
|
||||||
|
pendingValue: [],
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
@ -60,6 +77,10 @@ const LifestyleDetailsForm = ({
|
|||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||||
if (onFieldChange) onFieldChange(field);
|
if (onFieldChange) onFieldChange(field);
|
||||||
|
|
||||||
|
if (!isEditMode) {
|
||||||
|
dispatch(clearAllStepsFrom(5));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMultiChange = (field, value) => {
|
const handleMultiChange = (field, value) => {
|
||||||
@ -68,23 +89,74 @@ const LifestyleDetailsForm = ({
|
|||||||
if (onFieldChange) onFieldChange(field);
|
if (onFieldChange) onFieldChange(field);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGrahaChange = (house, value) => {
|
const handleChartChange = (type, house, newValue) => {
|
||||||
const next = { ...(data.graha || {}) };
|
const currentChart = type === "graha" ? data.graha : data.amsam;
|
||||||
next[house] = value;
|
const currentHouseValue = currentChart?.[house] || [];
|
||||||
dispatch(updateLifestyleDetails({ graha: next }));
|
|
||||||
if (onFieldChange) onFieldChange("graha");
|
// Find added items
|
||||||
|
const addedItems = newValue.filter((item) => !currentHouseValue.includes(item));
|
||||||
|
|
||||||
|
if (addedItems.length > 0) {
|
||||||
|
for (const addedItem of addedItems) {
|
||||||
|
for (const [otherHouse, items] of Object.entries(currentChart || {})) {
|
||||||
|
if (String(otherHouse) !== String(house) && items && items.includes(addedItem)) {
|
||||||
|
setConfirmDialog({
|
||||||
|
open: true,
|
||||||
|
type,
|
||||||
|
house,
|
||||||
|
item: addedItem,
|
||||||
|
sourceHouse: otherHouse,
|
||||||
|
pendingValue: newValue,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No duplicates, proceed with update
|
||||||
|
const updates = {};
|
||||||
|
const nextChart = { ...(currentChart || {}) };
|
||||||
|
nextChart[house] = newValue;
|
||||||
|
|
||||||
|
if (type === "graha") {
|
||||||
|
updates.graha = nextChart;
|
||||||
|
} else {
|
||||||
|
updates.amsam = nextChart;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(updateLifestyleDetails(updates));
|
||||||
|
if (onFieldChange) onFieldChange(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAmsamChange = (house, value) => {
|
const handleConfirmMove = () => {
|
||||||
const next = { ...(data.amsam || {}) };
|
const { type, house, item, sourceHouse, pendingValue } = confirmDialog;
|
||||||
next[house] = value;
|
const currentChart = type === "graha" ? data.graha : data.amsam;
|
||||||
dispatch(updateLifestyleDetails({ amsam: next }));
|
const nextChart = { ...(currentChart || {}) };
|
||||||
if (onFieldChange) onFieldChange("amsam");
|
|
||||||
|
// Remove from source house
|
||||||
|
if (nextChart[sourceHouse]) {
|
||||||
|
nextChart[sourceHouse] = nextChart[sourceHouse].filter((i) => i !== item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to target house
|
||||||
|
nextChart[house] = pendingValue;
|
||||||
|
|
||||||
|
const updates = type === "graha" ? { graha: nextChart } : { amsam: nextChart };
|
||||||
|
dispatch(updateLifestyleDetails(updates));
|
||||||
|
if (onFieldChange) onFieldChange(type);
|
||||||
|
|
||||||
|
setConfirmDialog({ ...confirmDialog, open: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelMove = () => {
|
||||||
|
setConfirmDialog({ ...confirmDialog, open: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
console.log("Submitting lifestyle details:", data);
|
console.log("Submitting lifestyle details:", data);
|
||||||
onSubmitStep();
|
onSubmitStep();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseDateValue = (value) => {
|
const parseDateValue = (value) => {
|
||||||
@ -118,6 +190,7 @@ const LifestyleDetailsForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderChartCell = (label, value, onChange) => (
|
const renderChartCell = (label, value, onChange) => (
|
||||||
|
<Tooltip title={value && value.length > 0 ? value.join(", ") : ""} arrow placement="top">
|
||||||
<div className="bg-white border border-gray-300 rounded-lg p-2 flex flex-col items-center justify-center min-h-[70px]">
|
<div className="bg-white border border-gray-300 rounded-lg p-2 flex flex-col items-center justify-center min-h-[70px]">
|
||||||
<span className="text-[10px] font-medium text-gray-700 text-center">
|
<span className="text-[10px] font-medium text-gray-700 text-center">
|
||||||
{label}
|
{label}
|
||||||
@ -130,7 +203,17 @@ const LifestyleDetailsForm = ({
|
|||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
renderValue={(selected) => {
|
renderValue={(selected) => {
|
||||||
if (!selected || selected.length === 0) return `+ Add ${label}`;
|
if (!selected || selected.length === 0) return `+ Add ${label}`;
|
||||||
return selected.join(", ");
|
return (
|
||||||
|
<div style={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
maxWidth: "60px",
|
||||||
|
fontSize: "11px"
|
||||||
|
}}>
|
||||||
|
{selected.join(", ")}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
MenuProps={{
|
MenuProps={{
|
||||||
PaperProps: { style: { maxHeight: 280 } },
|
PaperProps: { style: { maxHeight: 280 } },
|
||||||
@ -145,15 +228,14 @@ const LifestyleDetailsForm = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderChartGrid = (type) => {
|
const renderChartGrid = (type) => {
|
||||||
const getValue = (house) =>
|
const getValue = (house) =>
|
||||||
(type === "graha" ? data.graha : data.amsam)?.[house] || [];
|
(type === "graha" ? data.graha : data.amsam)?.[house] || [];
|
||||||
const onChange = (house, value) =>
|
const onChange = (house, value) =>
|
||||||
type === "graha"
|
handleChartChange(type, house, value);
|
||||||
? handleGrahaChange(house, value)
|
|
||||||
: handleAmsamChange(house, value);
|
|
||||||
const label = type === "graha" ? "Rasi" : "Navamsam";
|
const label = type === "graha" ? "Rasi" : "Navamsam";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -165,7 +247,9 @@ const LifestyleDetailsForm = ({
|
|||||||
|
|
||||||
{renderChartCell(label, getValue(5), (value) => onChange(5, value))}
|
{renderChartCell(label, getValue(5), (value) => onChange(5, value))}
|
||||||
<div className="col-span-2 row-span-2 flex items-center justify-center">
|
<div className="col-span-2 row-span-2 flex items-center justify-center">
|
||||||
<div className="w-full aspect-square rounded-full overflow-hidden border-4 border-gray-200 shadow-lg bg-white" />
|
<div className="w-full aspect-square rounded-full overflow-hidden bg-white">
|
||||||
|
<img src={horoscopeImg} alt="Horoscope" className="w-full h-full object-contain" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{renderChartCell(label, getValue(6), (value) => onChange(6, value))}
|
{renderChartCell(label, getValue(6), (value) => onChange(6, value))}
|
||||||
|
|
||||||
@ -186,7 +270,7 @@ const LifestyleDetailsForm = ({
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label className="text-gray-900 text-[15px]">
|
<label className="text-gray-900 text-[15px]">
|
||||||
Diet (Multi-select){requiredMark}
|
Diet{requiredMark}
|
||||||
</label>
|
</label>
|
||||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
||||||
<InputLabel id="diets-label">Select Diet</InputLabel>
|
<InputLabel id="diets-label">Select Diet</InputLabel>
|
||||||
@ -194,21 +278,10 @@ const LifestyleDetailsForm = ({
|
|||||||
labelId="diets-label"
|
labelId="diets-label"
|
||||||
label="Select Diet"
|
label="Select Diet"
|
||||||
name="diets"
|
name="diets"
|
||||||
multiple
|
|
||||||
value={data.diets}
|
value={data.diets}
|
||||||
onChange={(e) => handleMultiChange("diets", e.target.value)}
|
onChange={(e) => handleChange("diets", e.target.value)}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
disabled={isLifestyleMastersLoading}
|
disabled={isLifestyleMastersLoading}
|
||||||
renderValue={(selected) =>
|
|
||||||
selected
|
|
||||||
.map((id) => {
|
|
||||||
const item = dietOptions.find(
|
|
||||||
(opt) => (opt.id ?? opt) === id
|
|
||||||
);
|
|
||||||
return item?.diet_name || item?.name || id;
|
|
||||||
})
|
|
||||||
.join(", ")
|
|
||||||
}
|
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
"& .MuiSelect-select.Mui-disabled": {
|
||||||
cursor: "not-allowed",
|
cursor: "not-allowed",
|
||||||
@ -220,8 +293,7 @@ const LifestyleDetailsForm = ({
|
|||||||
const label = opt.diet_name || opt.name || String(opt);
|
const label = opt.diet_name || opt.name || String(opt);
|
||||||
return (
|
return (
|
||||||
<MenuItem key={value} value={value}>
|
<MenuItem key={value} value={value}>
|
||||||
<Checkbox checked={data.diets.indexOf(value) > -1} />
|
{label}
|
||||||
<ListItemText primary={label} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -406,13 +478,33 @@ const LifestyleDetailsForm = ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button variant="outlined" onClick={onSkipStep}>
|
{onSkipStep && (
|
||||||
Skip
|
<Button variant="outlined" onClick={onSkipStep}>
|
||||||
</Button>
|
Skip
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||||
Next
|
{onSkipStep ? "Next" : "Update"}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={confirmDialog.open}
|
||||||
|
onClose={handleCancelMove}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">{"Duplicate Selection"}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
{confirmDialog.item} is already selected in House {confirmDialog.sourceHouse}. Do you want to move it to House {confirmDialog.house}?
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleCancelMove}>Cancel</Button>
|
||||||
|
<Button onClick={handleConfirmMove} autoFocus>Move</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -417,11 +417,13 @@ const PartnerPreferencesForm = ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button variant="outlined" onClick={onSkipStep}>
|
{onSkipStep && (
|
||||||
Skip
|
<Button variant="outlined" onClick={onSkipStep}>
|
||||||
</Button>
|
Skip
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||||
Next
|
{onSkipStep ? "Next" : "Update"}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
0
src/feature/PreviewProfile.jsx
Normal file
0
src/feature/PreviewProfile.jsx
Normal file
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Navigation, Pagination } from "swiper/modules";
|
import { Navigation, Pagination } from "swiper/modules";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
@ -17,9 +17,16 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Grid,
|
Grid,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogActions,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { usePreviewDetails } from "../hooks/usePreview";
|
import { usePreviewDetails } from "../hooks/usePreview";
|
||||||
import { isAuthenticated } from "../utills/auth";
|
import { isAuthenticated } from "../utills/auth";
|
||||||
|
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||||
|
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||||
|
|
||||||
const PreviewScreen = ({ onEdit, onSubmit }) => {
|
const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||||
const formData = useSelector((state) => state.registerform);
|
const formData = useSelector((state) => state.registerform);
|
||||||
@ -28,6 +35,37 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
const isLoading = isAuth && apiLoading;
|
const isLoading = isAuth && apiLoading;
|
||||||
const isError = isAuth && apiError;
|
const isError = isAuth && apiError;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const pd = previewData?.personal_details;
|
||||||
|
if (pd) {
|
||||||
|
const images = pd.images || pd.profile_images;
|
||||||
|
if (images && images.length > 0) {
|
||||||
|
// Map images to ensure they have a consistent format for Redux
|
||||||
|
const formattedImages = images.map(img =>
|
||||||
|
typeof img === 'string' ? { url: img, preview: img } : img
|
||||||
|
);
|
||||||
|
dispatch(updatePersonalDetails({ profiles: formattedImages }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [previewData, dispatch]);
|
||||||
|
|
||||||
|
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||||
|
|
||||||
|
const handleConfirmSubmit = () => {
|
||||||
|
setOpenConfirmDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProceedSubmit = () => {
|
||||||
|
setOpenConfirmDialog(false);
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelSubmit = () => {
|
||||||
|
setOpenConfirmDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
const sections = previewData?.personal_details
|
const sections = previewData?.personal_details
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
@ -183,8 +221,8 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0.5, maxWidth: '300px', mx: 'auto', p: 1, bgcolor: '#fff3e0', borderRadius: 2 }}>
|
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0.5, maxWidth: '300px', mx: 'auto', p: 1, bgcolor: '#fff3e0', borderRadius: 2 }}>
|
||||||
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
|
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
|
||||||
{renderCell(5)}
|
{renderCell(5)}
|
||||||
<Box sx={{ gridColumn: 'span 2', gridRow: 'span 2', bgcolor: '#fff', border: '1px solid #eee', display:'flex', alignItems:'center', justifyContent:'center', fontSize:'0.7rem', fontWeight:'bold', color:'#aaa' }}>
|
<Box sx={{ gridColumn: 'span 2', gridRow: 'span 2', bgcolor: '#fff', border: '1px solid #eee', display:'flex', alignItems:'center', justifyContent:'center', fontSize:'0.7rem', fontWeight:'bold', color:'#aaa', overflow: 'hidden' }}>
|
||||||
{title.toUpperCase()}
|
<img src={horoscopeImg} alt={title} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||||
</Box>
|
</Box>
|
||||||
{renderCell(6)}
|
{renderCell(6)}
|
||||||
{renderCell(7)} {renderCell(8)}
|
{renderCell(7)} {renderCell(8)}
|
||||||
@ -224,7 +262,10 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
sections.map((section) => (
|
sections.map((section) => (
|
||||||
|
|
||||||
<Card key={section.title} variant="outlined" sx={{ borderRadius: 2, background:"#fff5ed" }}>
|
<Card key={section.title} variant="outlined"
|
||||||
|
sx={{ borderRadius: 2,
|
||||||
|
// background:"#fff5ed"
|
||||||
|
}}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
@ -243,11 +284,25 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
}
|
}
|
||||||
sx={{padding:"15px 15px", background:"#f5fbff" }}
|
sx={{padding:"15px 15px", background:"#f5fbff" }}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
<CardContent sx={{ pt: 1, }}>
|
<CardContent sx={{ pt: 1, }}>
|
||||||
{Object.entries(section.data || {}).map(([key, value]) => {
|
{Object.entries(section.data || {}).map(([key, value]) => {
|
||||||
// Filter out ID fields
|
// Filter out ID fields
|
||||||
if (key === 'id' || key.endsWith('_id') || key.endsWith('Id') || key.endsWith('_ids') || key.endsWith('Ids') || key === 'created_at' || key === 'updated_at') {
|
const hiddenFields = [
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"phone_number_visibility",
|
||||||
|
"chat_alert_notification",
|
||||||
|
"chat_protection",
|
||||||
|
"profile_photo_protect",
|
||||||
|
"call_protection",
|
||||||
|
"match_alert_preference",
|
||||||
|
"who_can_message",
|
||||||
|
"who_can_message_categories",
|
||||||
|
"user_status",
|
||||||
|
];
|
||||||
|
if (hiddenFields.includes(key) || key.endsWith('_id') || key.endsWith('Id') || key.endsWith('_ids') || key.endsWith('Ids')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,14 +359,21 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
color: #d32f2f;
|
color: #d32f2f;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
|
overflow: 'hidden';
|
||||||
|
padding:5px;
|
||||||
|
display: 'flex';
|
||||||
|
align-items: 'center';
|
||||||
|
justify-content: 'center';
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
.custom-swiper-${key} .swiper-button-next::after,
|
.custom-swiper-${key} .swiper-button-next::after,
|
||||||
.custom-swiper-${key} .swiper-button-prev::after {
|
.custom-swiper-${key} .swiper-button-prev::after {
|
||||||
font-size: 12px;
|
font-size: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
width:20px;
|
||||||
|
|
||||||
}
|
}
|
||||||
.custom-swiper-${key} .swiper-pagination-bullet-active {
|
.custom-swiper-${key} .swiper-pagination-bullet-active {
|
||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
@ -329,15 +391,16 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
>
|
>
|
||||||
{value.map((sibling, idx) => (
|
{value.map((sibling, idx) => (
|
||||||
<SwiperSlide key={idx}>
|
<SwiperSlide key={idx}>
|
||||||
<Card variant="outlined" sx={{ bgcolor: '#fff', height: '100%' }}>
|
<Card variant="outlined"
|
||||||
|
sx={{ bgcolor: '#fff', height: '100%' }}>
|
||||||
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
||||||
{Object.entries(sibling).map(([sKey, sVal]) => {
|
{Object.entries(sibling).map(([sKey, sVal]) => {
|
||||||
if (sKey === 'id' || sKey.endsWith('_id') || sKey.endsWith('Id') || sKey === 'created_at' || sKey === 'updated_at') return null;
|
if (sKey === 'id' || sKey.endsWith('_id') || sKey.endsWith('Id') || sKey === 'created_at' || sKey === 'updated_at') return null;
|
||||||
let displayValue = sVal;
|
let displayValue = sVal;
|
||||||
if (sKey === 'haveChildrens' || sKey === 'have_childrens') {
|
if (sKey === 'haveChildrens' || sKey === 'have_childrens' || sKey === 'hasChildren' || sKey === 'has_children') {
|
||||||
if (sVal === true || sVal === 1 || sVal === '1') {
|
if (sVal === true || sVal === 1 || sVal === '1' || sVal === 'Yes') {
|
||||||
displayValue = 'Yes';
|
displayValue = 'Yes';
|
||||||
} else if (sVal === false || sVal === 0 || sVal === '0') {
|
} else if (sVal === false || sVal === 0 || sVal === '0' || sVal === 'No') {
|
||||||
displayValue = 'No';
|
displayValue = 'No';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +438,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
<Box
|
<Box
|
||||||
key={key}
|
key={key}
|
||||||
py={0.7}
|
py={0.7}
|
||||||
borderBottom="1px solid #e0e0e0"
|
// borderBottom="1px solid #e0e0e0"
|
||||||
sx={{
|
sx={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: {
|
gridTemplateColumns: {
|
||||||
@ -434,12 +497,34 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
color="success"
|
color="success"
|
||||||
|
|
||||||
size="large"
|
size="large"
|
||||||
onClick={onSubmit}
|
onClick={handleConfirmSubmit}
|
||||||
sx={{ borderRadius: 2 }}
|
sx={{ borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
Submit full Completed Data
|
Submit full Completed Data
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={openConfirmDialog}
|
||||||
|
onClose={handleCancelSubmit}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
{"Confirm Submission"}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
Once you submit your details, you will not be able to edit the following fields: Place of Birth, Date of Birth, Rasi and Navamsam.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleCancelSubmit}>Cancel</Button>
|
||||||
|
<Button onClick={handleProceedSubmit} autoFocus>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Navigation, Autoplay } from "swiper/modules";
|
import { Navigation, Autoplay } from "swiper/modules";
|
||||||
import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react";
|
import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import LazyImage from "../components/common/LazyImage";
|
import LazyImage from "../components/common/LazyImage";
|
||||||
@ -13,9 +13,8 @@ import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
|
|||||||
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
||||||
|
|
||||||
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
|
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
|
||||||
|
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||||
import ProfileCompletion from "../components/profiledashboard/ProfileCompletion";
|
import ProfileCompletion from "../components/profiledashboard/ProfileCompletion";
|
||||||
import { preloadDummyProfile } from "../redux/registrationFormSlice";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -25,8 +24,12 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
Typography,
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Info } from "@mui/icons-material";
|
import { Info } from "@mui/icons-material";
|
||||||
|
import { usePreviewDetails } from "../hooks/usePreview";
|
||||||
|
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||||
|
|
||||||
const images = [
|
const images = [
|
||||||
weddingpromo1, // bride in saree
|
weddingpromo1, // bride in saree
|
||||||
@ -42,20 +45,95 @@ const images = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const ProfilePreviewPage = () => {
|
const ProfilePreviewPage = () => {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const dispatch = useDispatch();
|
||||||
personalDetails,
|
const { data, isLoading } = usePreviewDetails();
|
||||||
educationalDetails,
|
|
||||||
familyDetails,
|
|
||||||
lifestyleDetails,
|
|
||||||
partnerPreferences,
|
|
||||||
} = useSelector((state) => state.registerform);
|
|
||||||
|
|
||||||
// For dummy data on first visit
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(preloadDummyProfile());
|
if (data?.personal_details) {
|
||||||
}, [dispatch]);
|
const pd = data.personal_details;
|
||||||
|
const images = pd.images || pd.profile_images;
|
||||||
|
if (images && images.length > 0) {
|
||||||
|
const formattedImages = images.map((img) =>
|
||||||
|
typeof img === "string" ? { url: img, preview: img } : img
|
||||||
|
);
|
||||||
|
dispatch(updatePersonalDetails({ profiles: formattedImages }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data, dispatch]);
|
||||||
|
|
||||||
|
const personalDetails = data?.personal_details
|
||||||
|
? {
|
||||||
|
name: data.personal_details.name,
|
||||||
|
mobileNumber: data.personal_details.mobile,
|
||||||
|
gender: data.personal_details.gender,
|
||||||
|
dob: data.personal_details.dob
|
||||||
|
? data.personal_details.dob.split("T")[0]
|
||||||
|
: "",
|
||||||
|
height: data.personal_details.height,
|
||||||
|
weight: data.personal_details.weight,
|
||||||
|
maritalStatus: data.personal_details.marital_status,
|
||||||
|
religion: data.personal_details.religion,
|
||||||
|
caste: data.personal_details.caste,
|
||||||
|
email: data.personal_details.email,
|
||||||
|
state: data.personal_details.state,
|
||||||
|
city: data.personal_details.district,
|
||||||
|
pincode: data.personal_details.pincode,
|
||||||
|
profiles:
|
||||||
|
data.personal_details.images?.map((url) => ({ preview: url })) || [],
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const educationalDetails = data?.educational_details
|
||||||
|
? {
|
||||||
|
qualification: data.educational_details.education,
|
||||||
|
fieldOfStudy: data.educational_details.study_field,
|
||||||
|
collegeName: data.educational_details.college_name,
|
||||||
|
occupation: data.educational_details.occupation,
|
||||||
|
organization: data.educational_details.company_name,
|
||||||
|
employeeType: data.educational_details.employee_type,
|
||||||
|
income: data.educational_details.annual_income,
|
||||||
|
workLocation: data.educational_details.work_location,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const familyDetails = data?.family_details
|
||||||
|
? {
|
||||||
|
fatherName: data.family_details.father_name,
|
||||||
|
fatherOccupation: data.family_details.father_occupation,
|
||||||
|
motherName: data.family_details.mother_name,
|
||||||
|
motherOccupation: data.family_details.mother_occupation,
|
||||||
|
brotherCount: data.family_details.brother_count,
|
||||||
|
sisterCount: data.family_details.sister_count,
|
||||||
|
familyStatus: data.family_details.family_status,
|
||||||
|
nativePlace: data.family_details.native_place,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const lifestyleDetails = data?.lifestyle_details
|
||||||
|
? {
|
||||||
|
diets: data.lifestyle_details.diet,
|
||||||
|
hobbies: data.lifestyle_details.hobbies,
|
||||||
|
dob: data.lifestyle_details.date_of_birth_formated,
|
||||||
|
tob: data.lifestyle_details.time_of_birth_formated,
|
||||||
|
placeOfBirth: data.lifestyle_details.place_of_birth,
|
||||||
|
horoscope: data.lifestyle_details.horoscope,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const partnerPreferences = data?.partner_preferences
|
||||||
|
? {
|
||||||
|
ageRange: data.partner_preferences.preferred_age_range,
|
||||||
|
castes: data.partner_preferences.preferred_castes,
|
||||||
|
subCastes: data.partner_preferences.preferred_sub_castes,
|
||||||
|
occupations: data.partner_preferences.preferred_occupations,
|
||||||
|
educations: data.partner_preferences.preferred_educations,
|
||||||
|
hobbies: data.partner_preferences.preferred_hobbies,
|
||||||
|
annualIncome: data.partner_preferences.preferred_annual_income,
|
||||||
|
states: data.partner_preferences.preferred_states,
|
||||||
|
districts: data.partner_preferences.preferred_districts,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
const handleEditSection = (stepNum) => {
|
const handleEditSection = (stepNum) => {
|
||||||
navigate("/profile-edit", { state: { step: stepNum } });
|
navigate("/profile-edit", { state: { step: stepNum } });
|
||||||
@ -64,7 +142,7 @@ const ProfilePreviewPage = () => {
|
|||||||
const renderField = (label, value, stepNum = 1) => (
|
const renderField = (label, value, stepNum = 1) => (
|
||||||
<Box
|
<Box
|
||||||
py={0.7}
|
py={0.7}
|
||||||
borderBottom="1px solid #e0e0e0"
|
// borderBottom="1px solid #e0e0e0"
|
||||||
sx={{
|
sx={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: {
|
gridTemplateColumns: {
|
||||||
@ -102,8 +180,119 @@ const ProfilePreviewPage = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderChartGrid = (getDataForCell, title) => {
|
||||||
|
const renderCell = (i) => {
|
||||||
|
const items = getDataForCell(i);
|
||||||
|
const label = Array.isArray(items) ? items.join(", ") : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={i}
|
||||||
|
title={label}
|
||||||
|
arrow
|
||||||
|
placement="top"
|
||||||
|
disableHoverListener={!items || items.length === 0}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
bgcolor: "#fff",
|
||||||
|
height: "60px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "0.65rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
overflow: "hidden",
|
||||||
|
p: 0.5,
|
||||||
|
wordBreak: "break-word",
|
||||||
|
lineHeight: 1.1,
|
||||||
|
cursor: items && items.length > 0 ? "help" : "default",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items && items.length > 2 ? (
|
||||||
|
<Box>
|
||||||
|
{items.slice(0, 2).join(", ")}
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{ color: "primary.main", display: "block", fontSize: "0.6rem" }}
|
||||||
|
>
|
||||||
|
+{items.length - 2}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
label
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="subtitle2"
|
||||||
|
align="center"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(4, 1fr)",
|
||||||
|
gap: 0.5,
|
||||||
|
maxWidth: "300px",
|
||||||
|
mx: "auto",
|
||||||
|
p: 1,
|
||||||
|
bgcolor: "#fff3e0",
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
|
||||||
|
{renderCell(5)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
gridColumn: "span 2",
|
||||||
|
gridRow: "span 2",
|
||||||
|
bgcolor: "#fff",
|
||||||
|
border: "1px solid #eee",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
fontSize: "0.7rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#aaa",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={horoscopeImg}
|
||||||
|
alt={title}
|
||||||
|
sx={{
|
||||||
|
width: "70%",
|
||||||
|
height: "70%",
|
||||||
|
objectFit: "cover",
|
||||||
|
animation: "spin 20s linear infinite",
|
||||||
|
"@keyframes spin": {
|
||||||
|
"0%": { transform: "rotate(0deg)" },
|
||||||
|
"100%": { transform: "rotate(360deg)" },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{renderCell(6)}
|
||||||
|
{renderCell(7)} {renderCell(8)}
|
||||||
|
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderPersonalSection = () => (
|
const renderPersonalSection = () => (
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
@ -120,9 +309,9 @@ const ProfilePreviewPage = () => {
|
|||||||
<Edit2 size={20} />
|
<Edit2 size={20} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
<CardContent sx={{ pt: 1 }}>
|
||||||
{renderField("Name", personalDetails.name, 1)}
|
{renderField("Name", personalDetails.name, 1)}
|
||||||
@ -142,7 +331,7 @@ const ProfilePreviewPage = () => {
|
|||||||
{/* Profile Images */}
|
{/* Profile Images */}
|
||||||
<Box
|
<Box
|
||||||
py={0.7}
|
py={0.7}
|
||||||
borderBottom="1px solid #e0e0e0"
|
// borderBottom="1px solid #e0e0e0"
|
||||||
sx={{
|
sx={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
||||||
@ -210,7 +399,7 @@ const ProfilePreviewPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderEducationalSection = () => (
|
const renderEducationalSection = () => (
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
@ -227,9 +416,9 @@ const ProfilePreviewPage = () => {
|
|||||||
<Edit2 size={20} />
|
<Edit2 size={20} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
<CardContent sx={{ pt: 1 }}>
|
||||||
{renderField("Qualification", educationalDetails.qualification, 2)}
|
{renderField("Qualification", educationalDetails.qualification, 2)}
|
||||||
@ -245,7 +434,7 @@ const ProfilePreviewPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderFamilySection = () => (
|
const renderFamilySection = () => (
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
@ -256,15 +445,15 @@ const ProfilePreviewPage = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
aria-label="edit"
|
aria-label="edit"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => handleEditSection(2)}
|
onClick={() => handleEditSection(3)}
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<Edit2 size={20} />
|
<Edit2 size={20} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
<CardContent sx={{ pt: 1 }}>
|
||||||
{renderField("Father Name", familyDetails.fatherName)}
|
{renderField("Father Name", familyDetails.fatherName)}
|
||||||
@ -280,7 +469,7 @@ const ProfilePreviewPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderLifestyleSection = () => (
|
const renderLifestyleSection = () => (
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
@ -291,15 +480,15 @@ const ProfilePreviewPage = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
aria-label="edit"
|
aria-label="edit"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => handleEditSection(2)}
|
onClick={() => handleEditSection(4)}
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<Edit2 size={20} />
|
<Edit2 size={20} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
<CardContent sx={{ pt: 1 }}>
|
||||||
{renderField(
|
{renderField(
|
||||||
@ -317,32 +506,61 @@ const ProfilePreviewPage = () => {
|
|||||||
{renderField("Date of Birth", lifestyleDetails.dob)}
|
{renderField("Date of Birth", lifestyleDetails.dob)}
|
||||||
{renderField("Time of Birth", lifestyleDetails.tob)}
|
{renderField("Time of Birth", lifestyleDetails.tob)}
|
||||||
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
|
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
|
||||||
|
{lifestyleDetails.horoscope && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
mt: 2,
|
||||||
|
mb: 2,
|
||||||
|
borderTop: "1px solid #e0e0e0",
|
||||||
|
pt: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
Horoscope Details
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={4} justifyContent="center">
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
{renderChartGrid((i) => {
|
||||||
|
const val = lifestyleDetails.horoscope[`graha_${i}`];
|
||||||
|
return val ? val.split(",") : [];
|
||||||
|
}, "Rasi")}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
{renderChartGrid((i) => {
|
||||||
|
const val = lifestyleDetails.horoscope[`amsam_${i}`];
|
||||||
|
return val ? val.split(",") : [];
|
||||||
|
}, "Navamsam")}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const renderPreferenceSection = () => (
|
const renderPreferenceSection = () => (
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
Lifestyle Details
|
Partner Preferences
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="edit"
|
aria-label="edit"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => handleEditSection(2)}
|
onClick={() => handleEditSection(5)}
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<Edit2 size={20} />
|
<Edit2 size={20} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
{/* <Divider /> */}
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
<CardContent sx={{ pt: 1 }}>
|
||||||
{renderField("Age Range", partnerPreferences.ageRange)}
|
{renderField("Age Range", partnerPreferences.ageRange)}
|
||||||
@ -394,6 +612,14 @@ const ProfilePreviewPage = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="50vh">
|
||||||
|
<Typography>Loading Profile...</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="custom-swiper-hero flex items-center justify-center p-4">
|
<div className="custom-swiper-hero flex items-center justify-center p-4">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
14
src/hooks/useDashboardQuery.js
Normal file
14
src/hooks/useDashboardQuery.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getDashboardDetails } from "../api/dashboard.api";
|
||||||
|
|
||||||
|
export const useDashboardQuery = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["dashboardDetails"],
|
||||||
|
queryFn: getDashboardDetails,
|
||||||
|
// Performance settings:
|
||||||
|
staleTime: 1000 * 60 * 5, // Data is considered fresh for 5 minutes (reduces API calls)
|
||||||
|
gcTime: 1000 * 60 * 10, // Unused data is garbage collected after 10 minutes
|
||||||
|
refetchOnWindowFocus: false, // Prevents refetching when user switches tabs
|
||||||
|
retry: 1, // Retry failed requests once
|
||||||
|
});
|
||||||
|
};
|
||||||
14
src/hooks/useDebounce.js
Normal file
14
src/hooks/useDebounce.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export function useDebounce(value, delay=500) {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
useEffect(()=>{
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
},delay);
|
||||||
|
return () => clearTimeout(handler);
|
||||||
|
},[value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
};
|
||||||
19
src/hooks/useDebounce.jsx
Normal file
19
src/hooks/useDebounce.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const useDebounce = (value, delay) => {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDebounce;
|
||||||
67
src/hooks/useProfiles.js
Normal file
67
src/hooks/useProfiles.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||||
|
import {
|
||||||
|
getProfilesFilterList,
|
||||||
|
getProfilesFilterMasters,
|
||||||
|
} from "../api/masters.api";
|
||||||
|
|
||||||
|
export const useProfiles = (filters = {}) => {
|
||||||
|
|
||||||
|
// Remove empty filters
|
||||||
|
const cleanFilters = Object.entries(filters).reduce((acc, [key, value]) => {
|
||||||
|
if (
|
||||||
|
value !== "" &&
|
||||||
|
value !== null &&
|
||||||
|
value !== undefined &&
|
||||||
|
!(Array.isArray(value) && value.length === 0)
|
||||||
|
) {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return useInfiniteQuery({
|
||||||
|
queryKey: ["profiles-filter-list", cleanFilters],
|
||||||
|
|
||||||
|
queryFn: ({ pageParam = 1 }) =>
|
||||||
|
getProfilesFilterList({
|
||||||
|
...cleanFilters,
|
||||||
|
page: pageParam,
|
||||||
|
}),
|
||||||
|
|
||||||
|
staleTime: 1000 * 60 * 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
|
||||||
|
|
||||||
|
getNextPageParam: (lastPage, allPages) => {
|
||||||
|
|
||||||
|
const currentPageData =
|
||||||
|
lastPage?.data?.data || lastPage?.data || [];
|
||||||
|
|
||||||
|
const totalLoaded = allPages.reduce((acc, page) => {
|
||||||
|
const pageData = page?.data?.data || page?.data || [];
|
||||||
|
return acc + pageData.length;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const totalRecords =
|
||||||
|
lastPage?.data?.recordsFiltered ||
|
||||||
|
lastPage?.recordsFiltered ||
|
||||||
|
0;
|
||||||
|
|
||||||
|
console.log("totalLoaded:", totalLoaded);
|
||||||
|
console.log("totalRecords:", totalRecords);
|
||||||
|
|
||||||
|
if (totalLoaded < totalRecords) {
|
||||||
|
return allPages.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProfilesFilterMasters = () =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: ["profiles-filter-masters"],
|
||||||
|
queryFn: getProfilesFilterMasters,
|
||||||
|
});
|
||||||
168
src/hooks/useWebSocket.js
Normal file
168
src/hooks/useWebSocket.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const WS_URL = "wss://www.thirukalyanam.amrithaa.net/backend/reverb/app/xk30gjh2ggmel5szmm5w?protocol=7&client=js&version=1.0";
|
||||||
|
|
||||||
|
// SINGLETON state to share across all components
|
||||||
|
let globalSocket = null;
|
||||||
|
let globalMessages = [];
|
||||||
|
let globalIsConnected = false;
|
||||||
|
let globalActiveChannels = new Set();
|
||||||
|
let subscribers = new Set();
|
||||||
|
let reconnectTimer = null;
|
||||||
|
let heartbeatTimer = null;
|
||||||
|
|
||||||
|
const notifySubscribers = () => {
|
||||||
|
subscribers.forEach(callback => callback({
|
||||||
|
messages: [...globalMessages],
|
||||||
|
isConnected: globalIsConnected
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
if (globalSocket?.readyState === WebSocket.OPEN) return;
|
||||||
|
if (globalSocket?.readyState === WebSocket.CONNECTING) return;
|
||||||
|
|
||||||
|
console.log("[WS] Connecting to:", WS_URL);
|
||||||
|
const socket = new WebSocket(WS_URL);
|
||||||
|
globalSocket = socket;
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
console.log("[WS] Open - Waiting for handshake...");
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
console.log("[WS] RAW:", data);
|
||||||
|
|
||||||
|
// 1. Handshake
|
||||||
|
if (data.event === "pusher:connection_established") {
|
||||||
|
console.log("[WS] Handshake Complete | Socket ID:", data.data ? JSON.parse(data.data).socket_id : "N/A");
|
||||||
|
globalIsConnected = true;
|
||||||
|
|
||||||
|
// Auto-subscribe to all pending channels
|
||||||
|
if (globalActiveChannels.size > 0) {
|
||||||
|
console.log(`[WS] Re-subscribing to ${globalActiveChannels.size} channels...`);
|
||||||
|
globalActiveChannels.forEach(channel => {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
event: "pusher:subscribe",
|
||||||
|
data: { channel }
|
||||||
|
}));
|
||||||
|
console.log(`[WS] Subscribing to: ${channel}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySubscribers();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Heartbeat
|
||||||
|
if (data.event === "pusher:ping") {
|
||||||
|
socket.send(JSON.stringify({ event: "pusher:pong", data: {} }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.event === "pusher:pong") return;
|
||||||
|
|
||||||
|
// 3. Subscription Succeeded
|
||||||
|
if (data.event === "pusher_internal:subscription_succeeded") {
|
||||||
|
console.log(`[WS] ✅ Subscribed to ${data.channel}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Subscription Error
|
||||||
|
if (data.event === "pusher:subscription_error") {
|
||||||
|
console.error(`[WS] ❌ Subscription failed for ${data.channel}`, data.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Regular Events
|
||||||
|
console.log(`[WS] 📩 Event: ${data.event} | Channel: ${data.channel}`);
|
||||||
|
globalMessages = [...globalMessages, data];
|
||||||
|
notifySubscribers();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[WS] Parse Error:", err, event.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = (event) => {
|
||||||
|
console.log(`[WS] Disconnected (Code: ${event.code}) - Reconnecting in 5s...`);
|
||||||
|
globalIsConnected = false;
|
||||||
|
notifySubscribers();
|
||||||
|
|
||||||
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = setTimeout(connect, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = (err) => {
|
||||||
|
console.error("[WS] Error:", err);
|
||||||
|
socket.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Heartbeat
|
||||||
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
||||||
|
heartbeatTimer = setInterval(() => {
|
||||||
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.send(JSON.stringify({ event: "pusher:ping", data: {} }));
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWebSocket = (channels = []) => {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
messages: globalMessages,
|
||||||
|
isConnected: globalIsConnected
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Add this component's listener
|
||||||
|
const callback = (newState) => setState(newState);
|
||||||
|
subscribers.add(callback);
|
||||||
|
|
||||||
|
// Initialize connection if needed
|
||||||
|
if (!globalSocket) {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscribers.delete(callback);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle Dynamic Subscriptions
|
||||||
|
useEffect(() => {
|
||||||
|
if (!channels || channels.length === 0) return;
|
||||||
|
|
||||||
|
channels.forEach(channel => {
|
||||||
|
if (channel && !globalActiveChannels.has(channel)) {
|
||||||
|
globalActiveChannels.add(channel);
|
||||||
|
|
||||||
|
if (globalIsConnected && globalSocket?.readyState === WebSocket.OPEN) {
|
||||||
|
globalSocket.send(JSON.stringify({
|
||||||
|
event: "pusher:subscribe",
|
||||||
|
data: { channel }
|
||||||
|
}));
|
||||||
|
console.log(`[WS] Dynamic Subscribe: ${channel}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optional: Cleanup old channels if they are no longer in the provided array
|
||||||
|
// But for a chat app, we often want to keep listening to notification channels.
|
||||||
|
// So we'll leave them for now unless we implement a more complex cleanup.
|
||||||
|
}, [channels]);
|
||||||
|
|
||||||
|
const unsubscribe = (channel) => {
|
||||||
|
if (channel && globalActiveChannels.has(channel)) {
|
||||||
|
globalActiveChannels.delete(channel);
|
||||||
|
if (globalIsConnected && globalSocket?.readyState === WebSocket.OPEN) {
|
||||||
|
globalSocket.send(JSON.stringify({
|
||||||
|
event: "pusher:unsubscribe",
|
||||||
|
data: { channel }
|
||||||
|
}));
|
||||||
|
console.log(`[WS] Unsubscribed from: ${channel}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...state, unsubscribe };
|
||||||
|
};
|
||||||
17
src/main.jsx
17
src/main.jsx
@ -7,8 +7,9 @@ import { ThemeProvider } from "@mui/material/styles";
|
|||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
|
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "./redux/store.js";
|
import { persistor, store } from "./redux/store.js";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { PersistGate } from "redux-persist/integration/react";
|
||||||
// Disable noisy logs in production while keeping warnings/errors.
|
// Disable noisy logs in production while keeping warnings/errors.
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
console.log = () => {};
|
console.log = () => {};
|
||||||
@ -29,12 +30,14 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||||
|
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<App />
|
<QueryClientProvider client={queryClient}>
|
||||||
</QueryClientProvider>
|
<App />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,42 +1,57 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Tabs, Tab, Box, Chip } from '@mui/material';
|
import { Tabs, Tab, Box, CircularProgress } from '@mui/material';
|
||||||
import { CheckCircle, Phone, ExpandMore } from '@mui/icons-material';
|
import { CheckCircle, Phone } from '@mui/icons-material';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
import { getBlockedProfiles, getReportedProfiles, unblockProfile } from '../services/profileActionApi';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
const BlockedProfile = ({ profile }) => (
|
const BlockedProfile = ({ profile, onUnblock }) => (
|
||||||
<div className="bg-white border border-1 border-red-100 rounded-lg shadow-sm p-6 mb-4">
|
<div className="bg-white border border-1 border-red-100 rounded-lg shadow-sm p-6 mb-4">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="relative flex-shrink-0">
|
<div className="relative flex-shrink-0">
|
||||||
<img
|
<img
|
||||||
src={profile.image}
|
src={profile.photo || 'https://via.placeholder.com/150'}
|
||||||
alt={profile.name}
|
alt={profile.name}
|
||||||
className="w-32 h-32 rounded-2xl object-cover border-2 border-[#A70710]"
|
className="w-32 h-32 rounded-2xl object-cover border-2 border-[#A70710]"
|
||||||
/>
|
/>
|
||||||
<button className="w-8 h-8 flex justify-center items-center absolute bottom-0 right-0 bg-[#A70710] text-white rounded-full shadow-lg">
|
{/* <button className="w-8 h-8 flex justify-center items-center absolute bottom-0 right-0 bg-[#A70710] text-white rounded-full shadow-lg">
|
||||||
<Phone className="w-4 h-4" />
|
<Phone className="w-4 h-4" />
|
||||||
</button>
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
{/* <div className="flex items-center gap-2 mb-1">
|
||||||
<CheckCircle className="text-green-500 w-5 h-5" />
|
<CheckCircle className="text-green-500 w-5 h-5" />
|
||||||
<span className="text-green-500 font-medium">Verified</span>
|
<span className="text-green-500 font-medium">Verified</span>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-1">{profile.name}</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-1">{profile.name}</h2>
|
||||||
<p className="text-gray-500 text-sm mb-3">{profile.id} | Profile Created by Parent</p>
|
<p className="text-gray-500 text-sm mb-3">{profile.member_id} | Profile Created by Parent</p>
|
||||||
|
|
||||||
<div className="space-y-1 text-gray-700">
|
<div className="space-y-1 text-gray-700">
|
||||||
<p className="font-medium">{profile.age} yrs, {profile.height}, {profile.language},</p>
|
<p className="font-medium">{profile.age ? `${profile.age} yrs` : ''}{profile.age && profile.height ? ', ' : ''}{profile.height || ''}</p>
|
||||||
<p className="font-medium">{profile.location},</p>
|
|
||||||
<p className="font-medium">{profile.education}, {profile.occupation}, ₹ {profile.income}, {profile.state}, India</p>
|
<p className="font-medium">
|
||||||
|
{[profile.district_name, profile.state_name].filter(Boolean).join(', ')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="font-medium">
|
||||||
|
{[
|
||||||
|
profile.education,
|
||||||
|
profile.occupation,
|
||||||
|
profile.annual_income_name ? `₹ ${profile.annual_income_name}` : null
|
||||||
|
].filter(Boolean).join(', ')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex items-center justify-between border-t border-[#A70710] pt-4">
|
<div className="mt-6 flex items-center justify-between border-t border-[#A70710] pt-4">
|
||||||
<p className="text-gray-600">You have blocked this profile</p>
|
<p className="text-gray-600">You have blocked this profile</p>
|
||||||
<button className="bg-[#A70710] hover:bg-red-600 text-white px-8 py-2 rounded-full font-medium transition-colors">
|
<button
|
||||||
|
onClick={() => onUnblock(profile.id)}
|
||||||
|
className="bg-[#A70710] hover:bg-red-600 text-white px-8 py-2 rounded-full font-medium transition-colors"
|
||||||
|
>
|
||||||
UnBlock
|
UnBlock
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -49,7 +64,7 @@ const ReportedProfile = ({ profile, onViewReason }) => {
|
|||||||
<div className="flex flex-col sm:flex-row items-start gap-4">
|
<div className="flex flex-col sm:flex-row items-start gap-4">
|
||||||
<div className='overflow-hidden w-[100%] h-[100%] max-w-50 max-h-45 rounded-lg flex-shrink-0'>
|
<div className='overflow-hidden w-[100%] h-[100%] max-w-50 max-h-45 rounded-lg flex-shrink-0'>
|
||||||
<img
|
<img
|
||||||
src={profile.image}
|
src={profile.photo || 'https://via.placeholder.com/150'}
|
||||||
alt={profile.name}
|
alt={profile.name}
|
||||||
className="w-full h-full object-cover "
|
className="w-full h-full object-cover "
|
||||||
/>
|
/>
|
||||||
@ -57,25 +72,39 @@ const ReportedProfile = ({ profile, onViewReason }) => {
|
|||||||
<div className="flex-1 w-full">
|
<div className="flex-1 w-full">
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-1">{profile.name}</h3>
|
<h3 className="text-lg font-bold text-gray-900 mb-1">{profile.name}</h3>
|
||||||
<p className="text-sm text-gray-500 mb-2">
|
<p className="text-sm text-gray-500 mb-2">
|
||||||
ID : {profile.id} <span className="text-xs ml-1">Last seen {profile.lastSeen}</span>
|
ID : {profile.member_id} {profile.last_seen_at && <span className="text-xs ml-1">{profile.last_seen_at}</span>}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-1.5 text-sm">
|
<div className="space-y-1.5 text-sm">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span className="text-gray-600">Profile created by Parent</span>
|
<span className="text-gray-600">Profile created by Parent</span>
|
||||||
<span className="text-gray-400">•</span>
|
{profile.age && (
|
||||||
<span className="text-gray-600">{profile.age} yrs</span>
|
<>
|
||||||
</div>
|
<span className="text-gray-400">•</span>
|
||||||
<div className="flex items-center gap-2">
|
<span className="text-gray-600">{profile.age} yrs</span>
|
||||||
<span className="text-gray-400">•</span>
|
</>
|
||||||
<span className="text-gray-600">{profile.caste}</span>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{profile.caste_name && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-gray-400">•</span>
|
||||||
|
<span className="text-gray-600">{profile.caste_name}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<span className="text-gray-400">•</span>
|
{profile.occupation && (
|
||||||
<span className="text-gray-600">{profile.occupation}</span>
|
<>
|
||||||
<span className="text-gray-400">•</span>
|
<span className="text-gray-400">•</span>
|
||||||
<span className="text-gray-600">{profile.location}</span>
|
<span className="text-gray-600">{profile.occupation}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{profile.district_name && (
|
||||||
|
<>
|
||||||
|
<span className="text-gray-400">•</span>
|
||||||
|
<span className="text-gray-600">{profile.district_name}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -100,20 +129,20 @@ const ReportReasonModal = ({ profile, onClose }) => {
|
|||||||
<div className="bg-white rounded-lg shadow-2xl max-w-md w-full p-6 animate-slideUp">
|
<div className="bg-white rounded-lg shadow-2xl max-w-md w-full p-6 animate-slideUp">
|
||||||
<div className="flex items-start gap-4 mb-4">
|
<div className="flex items-start gap-4 mb-4">
|
||||||
<img
|
<img
|
||||||
src={profile.image}
|
src={profile.photo || 'https://via.placeholder.com/150'}
|
||||||
alt={profile.name}
|
alt={profile.name}
|
||||||
className="w-16 h-20 rounded-lg object-cover flex-shrink-0"
|
className="w-16 h-20 rounded-lg object-cover flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900">{profile.name}</h3>
|
<h3 className="text-lg font-bold text-gray-900">{profile.name}</h3>
|
||||||
<p className="text-sm text-gray-500">ID : {profile.id}</p>
|
<p className="text-sm text-gray-500">ID : {profile.member_id}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
<h4 className="font-bold text-gray-900 mb-3">Reason For Report</h4>
|
<h4 className="font-bold text-gray-900 mb-3">Reason For Report</h4>
|
||||||
<p className="text-sm text-gray-600 leading-relaxed bg-gray-50 p-3 rounded-lg mb-4">
|
<p className="text-sm text-gray-600 leading-relaxed bg-gray-50 p-3 rounded-lg mb-4">
|
||||||
{profile.reportReason}
|
{profile.reason}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full flex justify-center'>
|
<div className='w-full flex justify-center'>
|
||||||
@ -134,117 +163,63 @@ const ReportReasonModal = ({ profile, onClose }) => {
|
|||||||
|
|
||||||
function BlockedProfileListPage() {
|
function BlockedProfileListPage() {
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const [selectedReport, setSelectedReport] = useState(null);
|
const [selectedReport, setSelectedReport] = useState(null);
|
||||||
const blockedProfiles = [
|
const [blockedProfiles, setBlockedProfiles] = useState([]);
|
||||||
{
|
const [reportedProfiles, setReportedProfiles] = useState([]);
|
||||||
id: 'M6075010',
|
const [loading, setLoading] = useState(true);
|
||||||
name: 'Aravindh Vinayak M',
|
|
||||||
age: 37,
|
|
||||||
height: "5'6\"",
|
|
||||||
language: 'Tamil',
|
|
||||||
location: 'Karuneegar',
|
|
||||||
education: 'BE',
|
|
||||||
occupation: 'Clerk',
|
|
||||||
income: '9 - 10 Lakhs',
|
|
||||||
state: 'Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'M6075010',
|
|
||||||
name: 'Aravindh Vinayak M',
|
|
||||||
age: 37,
|
|
||||||
height: "5'6\"",
|
|
||||||
language: 'Tamil',
|
|
||||||
location: 'Karuneegar',
|
|
||||||
education: 'BE',
|
|
||||||
occupation: 'Clerk',
|
|
||||||
income: '9 - 10 Lakhs',
|
|
||||||
state: 'Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'M6075010',
|
|
||||||
name: 'Aravindh Vinayak M',
|
|
||||||
age: 37,
|
|
||||||
height: "5'6\"",
|
|
||||||
language: 'Tamil',
|
|
||||||
location: 'Karuneegar',
|
|
||||||
education: 'BE',
|
|
||||||
occupation: 'Clerk',
|
|
||||||
income: '9 - 10 Lakhs',
|
|
||||||
state: 'Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'M6075010',
|
|
||||||
name: 'Aravindh Vinayak M',
|
|
||||||
age: 37,
|
|
||||||
height: "5'6\"",
|
|
||||||
language: 'Tamil',
|
|
||||||
location: 'Karuneegar',
|
|
||||||
education: 'BE',
|
|
||||||
occupation: 'Clerk',
|
|
||||||
income: '9 - 10 Lakhs',
|
|
||||||
state: 'Tamil Nadu',
|
|
||||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const reportedProfiles = [
|
useEffect(() => {
|
||||||
{
|
fetchData();
|
||||||
id: 'TK52586A',
|
}, []);
|
||||||
name: 'Pavilash . P',
|
|
||||||
age: 23,
|
const fetchData = async () => {
|
||||||
lastSeen: 'Nov 25',
|
setLoading(true);
|
||||||
caste: 'Agamudayar / Arcot / Thuluva vellala',
|
try {
|
||||||
occupation: 'Engineer-non – IT',
|
const [blockedRes, reportedRes] = await Promise.all([
|
||||||
location: 'Chennai',
|
getBlockedProfiles(),
|
||||||
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=400&fit=crop',
|
getReportedProfiles()
|
||||||
showReason: true,
|
]);
|
||||||
reportReason: 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.'
|
|
||||||
},
|
if (blockedRes.status === "success") {
|
||||||
{
|
setBlockedProfiles(blockedRes.data);
|
||||||
id: 'TK52586A',
|
}
|
||||||
name: 'Pavilash . P',
|
if (reportedRes.status === "success") {
|
||||||
age: 23,
|
setReportedProfiles(reportedRes.data);
|
||||||
lastSeen: 'Nov 25',
|
}
|
||||||
caste: 'Agamudayar / Arcot / Thuluva vellala',
|
} catch (error) {
|
||||||
occupation: 'Engineer-non – IT',
|
console.error("Error fetching data:", error);
|
||||||
location: 'Chennai',
|
toast.error("Failed to load profiles");
|
||||||
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=400&fit=crop',
|
} finally {
|
||||||
showReason: true,
|
setLoading(false);
|
||||||
reportReason: 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'TK52586A',
|
|
||||||
name: 'Pavilash . P',
|
|
||||||
age: 23,
|
|
||||||
lastSeen: 'Nov 25',
|
|
||||||
caste: 'Agamudayar / Arcot / Thuluva vellala',
|
|
||||||
occupation: 'Engineer-non – IT',
|
|
||||||
location: 'Chennai',
|
|
||||||
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=400&fit=crop',
|
|
||||||
showReason: true,
|
|
||||||
reportReason: 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'TK52586A',
|
|
||||||
name: 'Pavilash . P',
|
|
||||||
age: 23,
|
|
||||||
lastSeen: 'Nov 25',
|
|
||||||
caste: 'Agamudayar / Arcot / Thuluva vellala',
|
|
||||||
occupation: 'Engineer-non – IT',
|
|
||||||
location: 'Chennai',
|
|
||||||
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=400&fit=crop',
|
|
||||||
showReason: true,
|
|
||||||
reportReason: 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.'
|
|
||||||
}
|
}
|
||||||
];
|
};
|
||||||
|
|
||||||
|
const handleUnblock = async (profileId) => {
|
||||||
|
try {
|
||||||
|
const res = await unblockProfile(profileId);
|
||||||
|
if (res.status === "success") {
|
||||||
|
toast.success(res.message || "Profile unblocked successfully");
|
||||||
|
setBlockedProfiles(prev => prev.filter(p => p.id !== profileId));
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || "Failed to unblock profile");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Something went wrong");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleTabChange = (event, newValue) => {
|
const handleTabChange = (event, newValue) => {
|
||||||
setActiveTab(newValue);
|
setActiveTab(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '60vh' }}>
|
||||||
|
<CircularProgress color="error" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" py-4 md:py-8">
|
<div className=" py-4 md:py-8">
|
||||||
<div className="max-w-[1400px] mx-auto">
|
<div className="max-w-[1400px] mx-auto">
|
||||||
@ -259,14 +234,14 @@ function BlockedProfileListPage() {
|
|||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
minWidth: 120,
|
minWidth: 150,
|
||||||
},
|
},
|
||||||
'& .Mui-selected': {
|
'& .Mui-selected': {
|
||||||
color: '#fff !important',
|
color: '#fff !important',
|
||||||
background:"#A70710"
|
background:"#A70710"
|
||||||
},
|
},
|
||||||
'& .MuiTabs-indicator': {
|
'& .MuiTabs-indicator': {
|
||||||
backgroundColor: '#A70710',
|
backgroundColor: 'transparent',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -277,28 +252,32 @@ function BlockedProfileListPage() {
|
|||||||
|
|
||||||
<div className="transition-all duration-300">
|
<div className="transition-all duration-300">
|
||||||
{activeTab === 0 && (
|
{activeTab === 0 && (
|
||||||
<div className='w-[100%] max-w-[1400px] mx-auto grid grid-cols-1 md:grid-cols-2 gap-2'>
|
<div className='w-[100%] max-w-[1400px] mx-auto grid grid-cols-1 md:grid-cols-2 gap-4 px-4'>
|
||||||
{blockedProfiles.map((profile, index) => (
|
{blockedProfiles.length > 0 ? (
|
||||||
<BlockedProfile key={index} profile={profile} />
|
blockedProfiles.map((profile, index) => (
|
||||||
))}
|
<BlockedProfile key={profile.id || index} profile={profile} onUnblock={handleUnblock} />
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="col-span-full text-center py-10 text-gray-500">No blocked profiles found.</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 1 && (
|
{activeTab === 1 && (
|
||||||
<div className='w-[100%] max-w-[1400px] mx-auto grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-2'>
|
<div className='w-[100%] max-w-[1400px] mx-auto grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-4 px-4'>
|
||||||
{reportedProfiles.map((profile, index) => (
|
{reportedProfiles.length > 0 ? (
|
||||||
<ReportedProfile
|
reportedProfiles.map((profile, index) => (
|
||||||
key={index}
|
<ReportedProfile
|
||||||
profile={profile}
|
key={profile.id || index}
|
||||||
onViewReason={setSelectedReport}
|
profile={profile}
|
||||||
/>
|
onViewReason={setSelectedReport}
|
||||||
))}
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="col-span-full text-center py-10 text-gray-500">No reported profiles found.</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* Report Reason Modal */}
|
{/* Report Reason Modal */}
|
||||||
<ReportReasonModal
|
<ReportReasonModal
|
||||||
@ -307,7 +286,7 @@ function BlockedProfileListPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style jsx>{`
|
<style>{`
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { getContactUs } from '../api/contact.api';
|
import { getContactUs } from '../api/contact.api';
|
||||||
import LazyImage from '../components/common/LazyImage';
|
import LazyImage from '../components/common/LazyImage';
|
||||||
import InstagramIcon from '@mui/icons-material/Instagram';
|
import InstagramIcon from '@mui/icons-material/Instagram';
|
||||||
|
import WhatsAppIcon from '@mui/icons-material/WhatsApp';
|
||||||
import FacebookIcon from '@mui/icons-material/Facebook';
|
import FacebookIcon from '@mui/icons-material/Facebook';
|
||||||
import SvgIcon from '@mui/material/SvgIcon';
|
import SvgIcon from '@mui/material/SvgIcon';
|
||||||
import { Phone, Mail, ChevronRight } from 'lucide-react';
|
import { Phone, Mail, ChevronRight } from 'lucide-react';
|
||||||
@ -39,7 +40,13 @@ const ContactUsPage = () => {
|
|||||||
icon: <XIcon fontSize="large" />,
|
icon: <XIcon fontSize="large" />,
|
||||||
color: "from-gray-800 to-black",
|
color: "from-gray-800 to-black",
|
||||||
url: contact.x_url
|
url: contact.x_url
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: "WhatsApp",
|
||||||
|
icon: <WhatsAppIcon fontSize="large" />,
|
||||||
|
color: "from-green-500 to-green-600",
|
||||||
|
url: contact.whatsapp_mobile ? `https://wa.me/${contact.whatsapp_mobile}` : null
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,58 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import MatrimonyProfile from "../components/profiledetail/MatrimonyProfile"
|
import MatrimonyProfile from "../components/profiledetail/MatrimonyProfile"
|
||||||
import PartnerPreferences from "../components/profiledetail/PartnerPreferences"
|
import PartnerPreferences from "../components/profiledetail/PartnerPreferences"
|
||||||
import MatchingList from "../components/profiledashboard/MatchingList";
|
import MatchingList from "../components/profiledashboard/MatchingList";
|
||||||
|
import { getProfileDetail } from "../services/profileActionApi";
|
||||||
|
import { CircularProgress, Box } from "@mui/material";
|
||||||
|
|
||||||
const ProfileDetailPage = () => {
|
const ProfileDetailPage = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDetail = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getProfileDetail(id);
|
||||||
|
setData(res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch profile details:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (id) {
|
||||||
|
fetchDetail();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
|
||||||
|
<CircularProgress color="error" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-20 text-gray-500 text-xl">
|
||||||
|
Profile details not found.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-[100%] max-w-[1400px] mx-auto my-10">
|
<div className="w-[100%] max-w-[1400px] mx-auto my-10">
|
||||||
<MatrimonyProfile/>
|
<MatrimonyProfile data={data} />
|
||||||
<PartnerPreferences/>
|
<PartnerPreferences data={data} />
|
||||||
<MatchingList/>
|
<MatchingList matches={data.all_matches} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProfileDetailPage
|
export default ProfileDetailPage;
|
||||||
@ -11,7 +11,8 @@ import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
|
|||||||
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
||||||
|
|
||||||
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
|
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
|
||||||
|
import { useDashboardQuery } from "../hooks/useDashboardQuery";
|
||||||
|
const NewJoinedProfile = lazy(() => import("../components/profiledashboard/NewJoinedProfile"));
|
||||||
const ProfileCompletion = lazy(() => import("../components/profiledashboard/ProfileCompletion"));
|
const ProfileCompletion = lazy(() => import("../components/profiledashboard/ProfileCompletion"));
|
||||||
const MatrimonyArticles = lazy(() => import("../components/profiledashboard/MatrimonyArticles"));
|
const MatrimonyArticles = lazy(() => import("../components/profiledashboard/MatrimonyArticles"));
|
||||||
const MatchingList = lazy(() => import("../components/profiledashboard/MatchingList"));
|
const MatchingList = lazy(() => import("../components/profiledashboard/MatchingList"));
|
||||||
@ -48,6 +49,15 @@ const SectionFallback = ({ height = 280 }) => (
|
|||||||
|
|
||||||
|
|
||||||
const UserDashboardHome = () => {
|
const UserDashboardHome = () => {
|
||||||
|
const { data, isLoading } = useDashboardQuery();
|
||||||
|
const dashboardData = data?.data;
|
||||||
|
|
||||||
|
const sliderImages = dashboardData?.image_sliders?.length > 0
|
||||||
|
? dashboardData.image_sliders
|
||||||
|
: images.map((img, index) => ({ id: index, image: img }));
|
||||||
|
|
||||||
|
if (isLoading) return <SectionFallback height={600} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="custom-swiper-hero flex items-center justify-center p-4">
|
<div className="custom-swiper-hero flex items-center justify-center p-4">
|
||||||
@ -92,11 +102,11 @@ const UserDashboardHome = () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{images.map((img, idx) => (
|
{sliderImages.map((slide, idx) => (
|
||||||
<SwiperSlide key={idx}>
|
<SwiperSlide key={slide.id || idx}>
|
||||||
<div className="relative overflow-hidden rounded-3xl w-[100%]">
|
<div className="relative overflow-hidden rounded-3xl w-[100%]">
|
||||||
<img
|
<img
|
||||||
src={img}
|
src={slide.image}
|
||||||
alt={`Slide ${idx + 1}`}
|
alt={`Slide ${idx + 1}`}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
loading={idx === 0 ? "eager" : "lazy"}
|
loading={idx === 0 ? "eager" : "lazy"}
|
||||||
@ -206,28 +216,32 @@ const UserDashboardHome = () => {
|
|||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={<SectionFallback height={320} />}>
|
<Suspense fallback={<SectionFallback height={320} />}>
|
||||||
<Profilecardemo />
|
<Profilecardemo profiles={dashboardData?.daily_recommended} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
{/* <DailyRecommendedCard/> */}
|
{/* <DailyRecommendedCard/> */}
|
||||||
|
|
||||||
<Suspense fallback={<SectionFallback height={220} />}>
|
<Suspense fallback={<SectionFallback height={220} />}>
|
||||||
<ProfileCompletion />
|
<ProfileCompletion
|
||||||
|
percentage={dashboardData?.profile_complete_percentage}
|
||||||
|
missingDetails={dashboardData?.non_filled_sections}
|
||||||
|
becomePaidMember={dashboardData?.become_paid_member}
|
||||||
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{/* <DailyRecommendedCard/> */}
|
{/* <DailyRecommendedCard/> */}
|
||||||
|
|
||||||
<Suspense fallback={<SectionFallback height={280} />}>
|
<Suspense fallback={<SectionFallback height={280} />}>
|
||||||
<MatrimonyArticles />
|
<MatrimonyArticles articles={dashboardData?.blogs} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Suspense fallback={<SectionFallback height={320} />}>
|
<Suspense fallback={<SectionFallback height={320} />}>
|
||||||
<MatchingList />
|
<MatchingList matches={dashboardData?.all_matches} />
|
||||||
|
</Suspense>
|
||||||
|
<Suspense fallback={<SectionFallback height={320} />}>
|
||||||
|
<NewJoinedProfile profiles={dashboardData?.new_joined} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{/* <PaidMemberCard/> */}
|
|
||||||
|
|
||||||
{/* <NewJoinedProfile/> */}
|
|
||||||
{/* <CustomerSupportCard/> */}
|
{/* <CustomerSupportCard/> */}
|
||||||
<Suspense fallback={<SectionFallback height={240} />}>
|
<Suspense fallback={<SectionFallback height={240} />}>
|
||||||
<VideoSwiperGallery />
|
<VideoSwiperGallery videos={dashboardData?.youtube_videos} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -74,6 +74,13 @@ const LoginPage = () => {
|
|||||||
localStorage.setItem("access_token", token);
|
localStorage.setItem("access_token", token);
|
||||||
setAccessToken(token);
|
setAccessToken(token);
|
||||||
|
|
||||||
|
// Store profile_id and user_id for WebSocket channels
|
||||||
|
const profileId = data?.profile_id || data?.data?.profile_id;
|
||||||
|
const userId = data?.user_id || data?.data?.user_id;
|
||||||
|
|
||||||
|
if (profileId) localStorage.setItem("profile_id", profileId);
|
||||||
|
if (userId) localStorage.setItem("user_id", userId);
|
||||||
|
|
||||||
toast.success("Login Successful!");
|
toast.success("Login Successful!");
|
||||||
navigate("/dashboard-home");
|
navigate("/dashboard-home");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,29 +1,27 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
age: [18, 70],
|
from_age: 18,
|
||||||
height: [4.0, 7.11],
|
to_age: 70,
|
||||||
maritalStatus: "All Profile",
|
from_height: 4.0,
|
||||||
motherTongue: [],
|
to_height: 7.11,
|
||||||
religion: "Hindu",
|
marital_status: [],
|
||||||
matchesWithHoroscope: false,
|
religion: [],
|
||||||
caste: [],
|
caste: [],
|
||||||
subCaste: [],
|
sub_caste: [],
|
||||||
star: "Any",
|
star: [],
|
||||||
dasham: "Doesn't matter",
|
occupation: [],
|
||||||
occupation: "Any",
|
annual_income: [],
|
||||||
annualIncome: "Any",
|
employee_type: [],
|
||||||
employeeType: "Any",
|
education: [],
|
||||||
education: "Any",
|
state: [],
|
||||||
state: "Any",
|
district: [],
|
||||||
country: "Any",
|
diet: "",
|
||||||
citizenship: "Any",
|
family_type: [],
|
||||||
eatingHabits: "Any",
|
filter_type: "all_matches",
|
||||||
smokingHabits: "Doesn't matter",
|
page: 1,
|
||||||
drinkingHabits: "Doesn't matter",
|
isPaidMember: false,
|
||||||
familyType: "Any",
|
search: "",
|
||||||
familyStatus: "Any",
|
|
||||||
familyValue: "Any",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterSlice = createSlice({
|
const filterSlice = createSlice({
|
||||||
@ -31,73 +29,24 @@ const filterSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setAge: (state, action) => {
|
setAge: (state, action) => {
|
||||||
state.age = action.payload;
|
state.from_age = action.payload[0];
|
||||||
|
state.to_age = action.payload[1];
|
||||||
},
|
},
|
||||||
setHeight: (state, action) => {
|
setHeight: (state, action) => {
|
||||||
state.height = action.payload;
|
state.from_height = action.payload[0];
|
||||||
|
state.to_height = action.payload[1];
|
||||||
},
|
},
|
||||||
setMaritalStatus: (state, action) => {
|
setMaritalStatus: (state, action) => {
|
||||||
state.maritalStatus = action.payload;
|
state.marital_status = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||||
},
|
|
||||||
setMotherTongue: (state, action) => {
|
|
||||||
state.motherTongue = action.payload;
|
|
||||||
},
|
},
|
||||||
setReligion: (state, action) => {
|
setReligion: (state, action) => {
|
||||||
state.religion = action.payload;
|
state.religion = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||||
},
|
|
||||||
setMatchesWithHoroscope: (state, action) => {
|
|
||||||
state.matchesWithHoroscope = action.payload;
|
|
||||||
},
|
},
|
||||||
setCaste: (state, action) => {
|
setCaste: (state, action) => {
|
||||||
state.caste = action.payload;
|
state.caste = action.payload;
|
||||||
},
|
},
|
||||||
setSubCaste: (state, action) => {
|
setSubCaste: (state, action) => {
|
||||||
state.subCaste = action.payload;
|
state.sub_caste = 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
|
// universal update
|
||||||
@ -105,7 +54,16 @@ const filterSlice = createSlice({
|
|||||||
return { ...state, ...action.payload };
|
return { ...state, ...action.payload };
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters: () => initialState,
|
resetFilters: (state) => {
|
||||||
|
// Reset all filters but preserve the paid member status
|
||||||
|
return { ...initialState, isPaidMember: state.isPaidMember };
|
||||||
|
},
|
||||||
|
setPage: (state, action) => {
|
||||||
|
state.page = action.payload;
|
||||||
|
},
|
||||||
|
setPaidMemberStatus: (state, action) => {
|
||||||
|
state.isPaidMember = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,28 +71,13 @@ export const {
|
|||||||
setAge,
|
setAge,
|
||||||
setHeight,
|
setHeight,
|
||||||
setMaritalStatus,
|
setMaritalStatus,
|
||||||
setMotherTongue,
|
|
||||||
setReligion,
|
setReligion,
|
||||||
setMatchesWithHoroscope,
|
|
||||||
setCaste,
|
setCaste,
|
||||||
setSubCaste,
|
setSubCaste,
|
||||||
setStar,
|
|
||||||
setDasham,
|
|
||||||
setOccupation,
|
|
||||||
setAnnualIncome,
|
|
||||||
setEmployeeType,
|
|
||||||
setEducation,
|
|
||||||
setState,
|
|
||||||
setCountry,
|
|
||||||
setCitizenship,
|
|
||||||
setEatingHabits,
|
|
||||||
setSmokingHabits,
|
|
||||||
setDrinkingHabits,
|
|
||||||
setFamilyType,
|
|
||||||
setFamilyStatus,
|
|
||||||
setFamilyValue,
|
|
||||||
updateFilter,
|
updateFilter,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
|
setPage,
|
||||||
|
setPaidMemberStatus,
|
||||||
} = filterSlice.actions;
|
} = filterSlice.actions;
|
||||||
|
|
||||||
export default filterSlice.reducer;
|
export default filterSlice.reducer;
|
||||||
|
|||||||
@ -5,49 +5,72 @@ const registrationformSlice = createSlice({
|
|||||||
initialState: {
|
initialState: {
|
||||||
personalDetails: {
|
personalDetails: {
|
||||||
name: "",
|
name: "",
|
||||||
mobileNumber: "",
|
mobile: "",
|
||||||
|
email: "",
|
||||||
gender: "",
|
gender: "",
|
||||||
dob: "",
|
|
||||||
height: "",
|
height: "",
|
||||||
weight: "",
|
weight: "",
|
||||||
maritalStatus: "",
|
marital_status: "",
|
||||||
religion: "",
|
religion: 1, // Default Hindu
|
||||||
profileFor: "",
|
profile_for: "",
|
||||||
caste: "",
|
caste: 1, // Default Naidu
|
||||||
subCaste: "",
|
sub_caste: "",
|
||||||
|
willing_to_marry: "",
|
||||||
|
inter_caste_parents: "",
|
||||||
|
inter_caste_parents_details: "",
|
||||||
gothram: "",
|
gothram: "",
|
||||||
raasi: "",
|
do_you_speak_telugu: "",
|
||||||
star: "",
|
about_us: "",
|
||||||
bloodGroup: "",
|
known_languages: [],
|
||||||
email: "",
|
mother_language: "",
|
||||||
|
complexion: "",
|
||||||
|
physical_status: "",
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
|
dob: "",
|
||||||
|
raasi: "",
|
||||||
|
star: "",
|
||||||
state: "",
|
state: "",
|
||||||
city: "",
|
city: "",
|
||||||
pincode: "",
|
pincode: "",
|
||||||
profiles: [],
|
profiles: [],
|
||||||
|
verifiedMobileNumber: "",
|
||||||
},
|
},
|
||||||
educationalDetails: {
|
educationalDetails: {
|
||||||
collegeName: "",
|
study_field: "",
|
||||||
employeeType: "",
|
education: "",
|
||||||
qualification: "",
|
education_detail: "",
|
||||||
fieldOfStudy: "",
|
college_name: "",
|
||||||
|
employee_type: "",
|
||||||
occupation: "",
|
occupation: "",
|
||||||
organization: "",
|
occupation_detail: "",
|
||||||
income: "",
|
company_name: "",
|
||||||
workLocation: "",
|
income_currency: "INR",
|
||||||
|
annual_income: "",
|
||||||
|
work_country: 1,
|
||||||
|
work_city: "",
|
||||||
|
work_state: "",
|
||||||
|
work_district: "",
|
||||||
|
address: "",
|
||||||
},
|
},
|
||||||
familyDetails: {
|
familyDetails: {
|
||||||
fatherName: "",
|
fatherName: "",
|
||||||
fatherOccupation: "",
|
fatherOccupation: "",
|
||||||
motherName: "",
|
motherName: "",
|
||||||
motherOccupation: "",
|
motherOccupation: "",
|
||||||
brotherCount: 0,
|
brotherCount: "",
|
||||||
sisterCount: 0,
|
sisterCount: "",
|
||||||
brothers: [],
|
brothers: [],
|
||||||
sisters: [],
|
sisters: [],
|
||||||
familyStatus: "",
|
familyStatus: "",
|
||||||
nativePlace: "",
|
nativePlace: "",
|
||||||
|
familyCountry: "",
|
||||||
|
familyState: "",
|
||||||
|
familyDistrict: "",
|
||||||
|
familyCity: "",
|
||||||
|
address: "",
|
||||||
|
expectationDetails: "",
|
||||||
|
willingToGoAbroad: "",
|
||||||
},
|
},
|
||||||
lifestyleDetails: {
|
lifestyleDetails: {
|
||||||
diets: [],
|
diets: [],
|
||||||
@ -114,14 +137,38 @@ const registrationformSlice = createSlice({
|
|||||||
state.partnerPreferences = { ...state.partnerPreferences, ...action.payload };
|
state.partnerPreferences = { ...state.partnerPreferences, ...action.payload };
|
||||||
},
|
},
|
||||||
|
|
||||||
submitForm: (state) => {
|
clearAllStepsFrom: (state, action) => {
|
||||||
console.log("Form Submitted:", {
|
const step = action.payload;
|
||||||
personalDetails: state.personalDetails,
|
if (step <= 2) {
|
||||||
educationalDetails: state.educationalDetails,
|
state.educationalDetails = {
|
||||||
familyDetails: state.familyDetails,
|
study_field: "", education: "", education_detail: "", college_name: "",
|
||||||
lifestyleDetails: state.lifestyleDetails,
|
employee_type: "", occupation: "", occupation_detail: "", company_name: "",
|
||||||
partnerPreferences: state.partnerPreferences,
|
income_currency: "INR", annual_income: "", work_country: "", work_state: "",
|
||||||
});
|
work_district: "", work_city: "", address: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (step <= 3) {
|
||||||
|
state.familyDetails = {
|
||||||
|
fatherName: "", fatherOccupation: "", motherName: "", motherOccupation: "",
|
||||||
|
brotherCount: "", sisterCount: "", brothers: [], sisters: [],
|
||||||
|
familyStatus: "", nativePlace: "", familyCountry: "", familyState: "",
|
||||||
|
familyDistrict: "", familyCity: "", address: "", expectationDetails: "",
|
||||||
|
willingToGoAbroad: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (step <= 4) {
|
||||||
|
state.lifestyleDetails = {
|
||||||
|
diets: "", hobbies: [], dob: "", tob: "", placeOfBirth: "",
|
||||||
|
graha: { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [] },
|
||||||
|
amsam: { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [] },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (step <= 5) {
|
||||||
|
state.partnerPreferences = {
|
||||||
|
ageRange: "", castes: [], subCastes: [], occupations: [], educations: [],
|
||||||
|
hobbies: [], annualIncome: "", states: [], districts: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@ -279,6 +326,7 @@ export const {
|
|||||||
updateFamilyDetails,
|
updateFamilyDetails,
|
||||||
updateLifestyleDetails,
|
updateLifestyleDetails,
|
||||||
updatePartnerPreferences,
|
updatePartnerPreferences,
|
||||||
|
clearAllStepsFrom,
|
||||||
submitForm,
|
submitForm,
|
||||||
preloadDummyProfile,
|
preloadDummyProfile,
|
||||||
} = registrationformSlice.actions;
|
} = registrationformSlice.actions;
|
||||||
|
|||||||
@ -1,9 +1,36 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
import registerformReducer from "./registrationFormSlice";
|
import registerformReducer from "./registrationFormSlice";
|
||||||
import filtersReducer from "./filterSlice";
|
import filtersReducer from "./filterSlice";
|
||||||
|
import {
|
||||||
|
persistStore,
|
||||||
|
persistReducer,
|
||||||
|
FLUSH,
|
||||||
|
REHYDRATE,
|
||||||
|
PAUSE,
|
||||||
|
PERSIST,
|
||||||
|
PURGE,
|
||||||
|
REGISTER,
|
||||||
|
} from "redux-persist";
|
||||||
|
import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
|
||||||
|
|
||||||
|
const persistConfig = {
|
||||||
|
key: "filters",
|
||||||
|
storage,
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistedFiltersReducer = persistReducer(persistConfig, filtersReducer);
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
registerform:registerformReducer,
|
registerform: registerformReducer,
|
||||||
filters:filtersReducer,
|
filters: persistedFiltersReducer,
|
||||||
},
|
},
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware({
|
||||||
|
serializableCheck: {
|
||||||
|
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const persistor = persistStore(store);
|
||||||
|
|||||||
@ -3,18 +3,10 @@ import { Route, Routes } from 'react-router-dom';
|
|||||||
import UserRoutes from './UserRoutes';
|
import UserRoutes from './UserRoutes';
|
||||||
import PublicRoutes from './PublicRoutes';
|
import PublicRoutes from './PublicRoutes';
|
||||||
import ScrollToTop from '../components/common/ScrollToTop';
|
import ScrollToTop from '../components/common/ScrollToTop';
|
||||||
import Skeleton from "../components/common/Skeleton";
|
import { SkeletonPage } from "../components/common/Skeleton";
|
||||||
|
|
||||||
const RouteFallback = () => (
|
const RouteFallback = () => (
|
||||||
<div className="min-h-[60vh] flex items-center justify-center px-6">
|
<SkeletonPage />
|
||||||
<div className="w-full max-w-3xl space-y-4">
|
|
||||||
<Skeleton height={32} rounded={12} />
|
|
||||||
<Skeleton height={220} rounded={16} />
|
|
||||||
<Skeleton height={16} />
|
|
||||||
<Skeleton height={16} width="80%" />
|
|
||||||
<Skeleton height={16} width="60%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const AppRoutes = () => {
|
const AppRoutes = () => {
|
||||||
|
|||||||
@ -19,9 +19,10 @@ const PublicRoutes = () => {
|
|||||||
<>
|
<>
|
||||||
<Route element={<HomeLayout />}>
|
<Route element={<HomeLayout />}>
|
||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route element={<PublicGuard />}>
|
{/* <Route element={<PublicGuard />}>
|
||||||
<Route path="/registration" element={<StepperForm />} />
|
</Route> */}
|
||||||
</Route>
|
<Route path="/registration" element={<StepperForm />} />
|
||||||
|
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route element={<PublicGuard />}>
|
<Route element={<PublicGuard />}>
|
||||||
|
|||||||
34
src/services/chatApi.js
Normal file
34
src/services/chatApi.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import axiosInstance from "../api/axiosInstance";
|
||||||
|
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||||
|
|
||||||
|
export const getChatList = async (searchValue = "") => {
|
||||||
|
try {
|
||||||
|
// Add timestamp to prevent caching
|
||||||
|
const response = await axiosInstance.get(`${API_ENDPOINTS.CHAT_LIST}?search_value=${searchValue}&_t=${Date.now()}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching chat list:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getChatMessages = async (chatId, page = 1) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(`${API_ENDPOINTS.CHAT_MESSAGES(chatId)}?page=${page}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching chat messages:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendMessage = async (chatId, message) => {
|
||||||
|
try {
|
||||||
|
// Correct endpoint based on user request: chat/message/send?chat_id={id}&message={text}
|
||||||
|
const response = await axiosInstance.post(`chat/message/send?chat_id=${chatId}&message=${encodeURIComponent(message)}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending message:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
65
src/services/profileActionApi.js
Normal file
65
src/services/profileActionApi.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import axiosInstance from "../api/axiosInstance";
|
||||||
|
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||||
|
|
||||||
|
export const getBlockedProfiles = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(API_ENDPOINTS.BLOCK_PROFILE_LIST);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching blocked profiles:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getReportedProfiles = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(API_ENDPOINTS.REPORT_PROFILE_LIST);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching reported profiles:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unblockProfile = async (profileId) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.post(`unblock_profile?profile_id=${profileId}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error unblocking profile:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfileDetail = async (profile_id) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(`${API_ENDPOINTS.PROFILE_DETAIL}?profile_id=${profile_id}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching profile detail:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInterestList = async (tab, type) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(`${API_ENDPOINTS.INTEREST_LIST}?tab=${tab}&type=${type}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching interest list:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateInterestStatus = async (profile_id, status) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.post(API_ENDPOINTS.UPDATE_INTEREST_STATUS, {
|
||||||
|
profile_id,
|
||||||
|
status
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating interest status:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
28
src/services/shortlistapi.js
Normal file
28
src/services/shortlistapi.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import axiosInstance from "../api/axiosInstance";
|
||||||
|
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||||
|
|
||||||
|
export const shortlistProfile = async (profileId) => {
|
||||||
|
const response = await axiosInstance.post(`${API_ENDPOINTS.SHORTLIST_API}?profile_id=${profileId}`);
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to shortlist");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendInterest = async (profileId) => {
|
||||||
|
const response = await axiosInstance.post(`interest_send`, {
|
||||||
|
profile_id: profileId // ✅ sent in request body
|
||||||
|
});
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to send interest");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const declineProfile = async (profileId) => {
|
||||||
|
const response = await axiosInstance.post(`decline?profile_id=${profileId}`);
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to decline profile");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@ -53,7 +53,7 @@ class ErrorBoundary extends Component {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Show error details in development */}
|
{/* Show error details in development */}
|
||||||
{process.env.NODE_ENV === "development" && this.state.error && (
|
{import.meta.env.DEV && this.state.error && (
|
||||||
<details className="mb-4 text-sm text-gray-500">
|
<details className="mb-4 text-sm text-gray-500">
|
||||||
<summary className="cursor-pointer font-medium">
|
<summary className="cursor-pointer font-medium">
|
||||||
Error Details (Development)
|
Error Details (Development)
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss(),],
|
plugins: [react(), tailwindcss(),],
|
||||||
@ -11,3 +16,4 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user