Compare commits
16
Commits
65bd6c646b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2e8276d5f | ||
|
|
5e1afc5ccc | ||
|
|
10477e0932 | ||
|
|
11cc3c58b6 | ||
|
|
30bea864e5 | ||
|
|
b3d33aca9c | ||
|
|
4ba4ce1e1b | ||
|
|
c467271927 | ||
|
|
68f97c40dc | ||
|
|
cd880e10e5 | ||
|
|
ccf638f7f3 | ||
|
|
9427677a72 | ||
|
|
5392a4211e | ||
|
|
805c93b3f3 | ||
|
|
8f6ddbcb2c | ||
|
|
135f6bba48 |
No files matched your search
@@ -22,3 +22,12 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist.zip
|
||||||
|
|
||||||
@@ -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.
|
||||||
Binary file not shown.
+2
-1
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
<link rel="icon" type="image/png" href="/logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<!-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> -->
|
<!-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> -->
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<title>thirukalyanam</title>
|
<title>thirukalyanam</title>
|
||||||
|
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
Generated
+88
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
+71
-43
@@ -1,57 +1,85 @@
|
|||||||
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",
|
PATHAM_MASTER: "get_patham_masters",
|
||||||
MOBILE_VERIFY_OTP: "verify_otp",
|
MOBILE_SEND_OTP: "send_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: "interest_status_update",
|
||||||
|
CHAT_LIST: "chat/lists",
|
||||||
|
CHAT_MESSAGES: (id) => `chat/${id}/messages`,
|
||||||
|
UNREAD_CHAT_COUNT: "chat/un_read_chat_count",
|
||||||
|
INTEREST_SEND: "interest_send",
|
||||||
|
DAILY_RECOMMENDED_DONT_SHOW: "daily_recomended-dont_show",
|
||||||
|
VIEW_CONTACT: "profiles/view_contact",
|
||||||
|
CHAT_CREATE: "chat/create",
|
||||||
|
SUBSCRIPTION_PLANS: "subscription-plans",
|
||||||
|
SUBSCRIPTION_PURCHASE_RAZORPAY: "subscription-purchase-razorpay",
|
||||||
|
SUBSCRIPTION_HISTORY: "subscription-history",
|
||||||
|
|
||||||
|
//Report and Block profile
|
||||||
|
REPORT_PROFILE : "report_profile",
|
||||||
|
BLOCK_PROFILE: "block_profile",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://www.thirukalyanam.amrithaa.net/backend/api/report_profile?profile_id=151&reason=test
|
||||||
@@ -7,8 +7,9 @@ import { API_ENDPOINTS } from "./apiEndpoints";
|
|||||||
* and default headers for JSON communication.
|
* and default headers for JSON communication.
|
||||||
*/
|
*/
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL ||
|
baseURL: (import.meta.env.DEV)
|
||||||
"https://www.thirukalyanam.amrithaa.net/backend/api/" ,
|
? "/backend/api/"
|
||||||
|
: (import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL || "https://www.thirukalyanam.amrithaa.net/backend/api/"),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
@@ -19,7 +20,9 @@ const axiosInstance = axios.create({
|
|||||||
* while sharing the same base URL and authorization mechanism.
|
* while sharing the same base URL and authorization mechanism.
|
||||||
*/
|
*/
|
||||||
const apiForFiles = axios.create({
|
const apiForFiles = axios.create({
|
||||||
baseURL: import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL,
|
baseURL: (import.meta.env.DEV)
|
||||||
|
? "/backend/api/"
|
||||||
|
: (import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL || "https://www.thirukalyanam.amrithaa.net/backend/api/"),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
},
|
},
|
||||||
@@ -123,8 +126,13 @@ export const clearUserData = () => {
|
|||||||
|
|
||||||
export const urlToFile = async (url, filename = "file") => {
|
export const urlToFile = async (url, filename = "file") => {
|
||||||
try {
|
try {
|
||||||
|
// Rewrite URL to use proxy in development to avoid CORS
|
||||||
|
const finalUrl = (import.meta.env.DEV && url && url.startsWith('https://www.thirukalyanam.amrithaa.net/backend'))
|
||||||
|
? url.replace('https://www.thirukalyanam.amrithaa.net/backend', '/backend')
|
||||||
|
: url;
|
||||||
|
|
||||||
// Use your axios instance to request the URL as a Blob
|
// Use your axios instance to request the URL as a Blob
|
||||||
const response = await apiForFiles.get(url, {
|
const response = await apiForFiles.get(finalUrl, {
|
||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -35,6 +35,13 @@ export const getStarMasters = async (raasi_id) => {
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPathamMasters = async (star_id) => {
|
||||||
|
const res = await axiosInstance.get(API_ENDPOINTS.PATHAM_MASTER, {
|
||||||
|
params: { star_id },
|
||||||
|
});
|
||||||
|
return res.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const getEducationMasters = async () => {
|
export const getEducationMasters = async () => {
|
||||||
const res = await axiosInstance.get(API_ENDPOINTS.EDUCATION_DETAILS_MASTER);
|
const res = await axiosInstance.get(API_ENDPOINTS.EDUCATION_DETAILS_MASTER);
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -63,3 +70,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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import axiosInstance from "./axiosInstance";
|
||||||
|
import { API_ENDPOINTS } from "./apiEndpoints";
|
||||||
|
|
||||||
|
export const getSubscriptionPlans = async () => {
|
||||||
|
const response = await axiosInstance.get(API_ENDPOINTS.SUBSCRIPTION_PLANS);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const purchaseSubscription = async (planId, paymentData = null) => {
|
||||||
|
if (!paymentData) {
|
||||||
|
// Step 1: Initiate purchase to get order details
|
||||||
|
const response = await axiosInstance.post(`${API_ENDPOINTS.SUBSCRIPTION_PURCHASE_RAZORPAY}?supscription_plan_id=${planId}`);
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
// Step 2: Verify payment
|
||||||
|
const response = await axiosInstance.post(API_ENDPOINTS.SUBSCRIPTION_PURCHASE_RAZORPAY, {
|
||||||
|
...paymentData,
|
||||||
|
supscription_plan_id: planId
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSubscriptionHistory = async () => {
|
||||||
|
const response = await axiosInstance.get(API_ENDPOINTS.SUBSCRIPTION_HISTORY);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.8716 6.28503V4.88837C11.8716 4.51795 11.7245 4.1627 11.4626 3.90077C11.2006 3.63885 10.8454 3.4917 10.475 3.4917H3.49164C3.12122 3.4917 2.76597 3.63885 2.50404 3.90077C2.24212 4.1627 2.09497 4.51795 2.09497 4.88837V9.07837C2.09497 9.44878 2.24212 9.80403 2.50404 10.066C2.76597 10.3279 3.12122 10.475 3.49164 10.475H4.8883M6.28497 13.2684H13.2683C13.6387 13.2684 13.994 13.1212 14.2559 12.8593C14.5178 12.5974 14.665 12.2421 14.665 11.8717V7.6817C14.665 7.31128 14.5178 6.95603 14.2559 6.69411C13.994 6.43218 13.6387 6.28503 13.2683 6.28503H6.28497C5.91455 6.28503 5.5593 6.43218 5.29738 6.69411C5.03545 6.95603 4.8883 7.31128 4.8883 7.6817V11.8717C4.8883 12.2421 5.03545 12.5974 5.29738 12.8593C5.5593 13.1212 5.91455 13.2684 6.28497 13.2684ZM11.1733 9.7767C11.1733 10.1471 11.0262 10.5024 10.7642 10.7643C10.5023 11.0262 10.1471 11.1734 9.77664 11.1734C9.40622 11.1734 9.05097 11.0262 8.78905 10.7643C8.52712 10.5024 8.37997 10.1471 8.37997 9.7767C8.37997 9.40628 8.52712 9.05103 8.78905 8.78911C9.05097 8.52718 9.40622 8.38003 9.77664 8.38003C10.1471 8.38003 10.5023 8.52718 10.7642 8.78911C11.0262 9.05103 11.1733 9.40628 11.1733 9.7767Z" stroke="#AA5806" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 788 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 569 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.86372 15.4545C7.86372 15.4545 2.79321 11.1842 2.79321 6.98228C2.79321 5.50084 3.38171 4.08007 4.42926 3.03253C5.4768 1.98499 6.89756 1.39648 8.37901 1.39648C9.86046 1.39648 11.2812 1.98499 12.3288 3.03253C13.3763 4.08007 13.9648 5.50084 13.9648 6.98228C13.9648 11.1842 8.8943 15.4545 8.8943 15.4545C8.61222 15.7143 8.1479 15.7115 7.86372 15.4545ZM8.37901 9.42607C8.69993 9.42607 9.01771 9.36286 9.31421 9.24005C9.6107 9.11724 9.8801 8.93723 10.107 8.7103C10.334 8.48338 10.514 8.21397 10.6368 7.91748C10.7596 7.62099 10.8228 7.30321 10.8228 6.98228C10.8228 6.66136 10.7596 6.34358 10.6368 6.04709C10.514 5.75059 10.334 5.48119 10.107 5.25427C9.8801 5.02734 9.6107 4.84733 9.31421 4.72452C9.01771 4.60171 8.69993 4.5385 8.37901 4.5385C7.73088 4.5385 7.10929 4.79597 6.65099 5.25427C6.19269 5.71256 5.93522 6.33415 5.93522 6.98228C5.93522 7.63042 6.19269 8.252 6.65099 8.7103C7.10929 9.1686 7.73088 9.42607 8.37901 9.42607Z" fill="#DF1D46"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.79346 12.5679C2.79346 11.8272 3.08771 11.1168 3.61148 10.593C4.13525 10.0693 4.84563 9.77502 5.58636 9.77502H11.1722C11.9129 9.77502 12.6233 10.0693 13.147 10.593C13.6708 11.1168 13.9651 11.8272 13.9651 12.5679C13.9651 12.9383 13.8179 13.2935 13.556 13.5554C13.2942 13.8172 12.939 13.9644 12.5686 13.9644H4.18991C3.81954 13.9644 3.46435 13.8172 3.20247 13.5554C2.94058 13.2935 2.79346 12.9383 2.79346 12.5679Z" stroke="#0E69BE" stroke-width="1.57101" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.3791 6.9822C9.53596 6.9822 10.4738 6.04438 10.4738 4.88752C10.4738 3.73066 9.53596 2.79285 8.3791 2.79285C7.22224 2.79285 6.28442 3.73066 6.28442 4.88752C6.28442 6.04438 7.22224 6.9822 8.3791 6.9822Z" stroke="#0E69BE" stroke-width="1.57101"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 847 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.18966 15.361C3.80564 15.361 3.47701 15.2244 3.20377 14.9511C2.93053 14.6779 2.79368 14.349 2.79321 13.9645V2.79293C2.79321 2.40891 2.93006 2.08028 3.20377 1.80704C3.47747 1.5338 3.8061 1.39695 4.18966 1.39648H12.5684C12.9524 1.39648 13.2812 1.53334 13.5549 1.80704C13.8287 2.08074 13.9653 2.40938 13.9648 2.79293V13.9645C13.9648 14.3486 13.8282 14.6774 13.5549 14.9511C13.2817 15.2248 12.9528 15.3614 12.5684 15.361H4.18966ZM4.18966 13.9645H12.5684V2.79293H11.1719V7.68051L9.42635 6.63317L7.68079 7.68051V2.79293H4.18966V13.9645Z" fill="#D00768"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 663 B |
@@ -0,0 +1,254 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { ArrowLeft, MoreVertical, Loader2 } from 'lucide-react';
|
||||||
|
import { getCallHistoryMyList, getCallHistoryOthersList, deleteCallHistory } from '../../services/chatApi';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getHeaderDetails } from '../../api/preview.api';
|
||||||
|
|
||||||
|
const CallHistory = ({ onBack }) => {
|
||||||
|
const [activeTab, setActiveTab] = useState('me');
|
||||||
|
const [historyList, setHistoryList] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const { data: headerData } = useQuery({
|
||||||
|
queryKey: ["headerDetails"],
|
||||||
|
queryFn: getHeaderDetails,
|
||||||
|
});
|
||||||
|
// console.log("headerData", headerData);
|
||||||
|
|
||||||
|
const myDetails = headerData?.myDetails || {};
|
||||||
|
|
||||||
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
|
const [selectedIds, setSelectedIds] = useState([]);
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setHistoryList([]);
|
||||||
|
try {
|
||||||
|
let res;
|
||||||
|
if (activeTab === 'me') {
|
||||||
|
res = await getCallHistoryMyList();
|
||||||
|
} else {
|
||||||
|
res = await getCallHistoryOthersList();
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = res?.call_list || res?.data || res || [];
|
||||||
|
setHistoryList(Array.isArray(data) ? data : []);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to load call history.");
|
||||||
|
setHistoryList([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
setIsEditMode(false);
|
||||||
|
setSelectedIds([]);
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
|
const toggleSelection = (id) => {
|
||||||
|
if (selectedIds.includes(id)) {
|
||||||
|
setSelectedIds(selectedIds.filter(item => item !== id));
|
||||||
|
} else {
|
||||||
|
setSelectedIds([...selectedIds, id]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (selectedIds.length === 0) return;
|
||||||
|
|
||||||
|
setIsDeleting(true);
|
||||||
|
try {
|
||||||
|
const res = await deleteCallHistory(selectedIds);
|
||||||
|
toast.success(res?.message || "History deleted successfully.");
|
||||||
|
setIsEditMode(false);
|
||||||
|
setSelectedIds([]);
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to delete history.");
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSkeletons = () => {
|
||||||
|
return Array(5).fill(0).map((_, idx) => (
|
||||||
|
<div key={`skel-${idx}`} className="flex items-center gap-3 p-4 border-b border-gray-100 animate-pulse">
|
||||||
|
<div className="w-12 h-12 bg-gray-200 rounded-full"></div>
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<div className="h-4 bg-gray-200 rounded w-1/3"></div>
|
||||||
|
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full bg-white relative">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between p-4 border-b border-gray-200 bg-white min-h-[64px]">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
onClick={onBack}
|
||||||
|
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-6 h-6 text-gray-800" />
|
||||||
|
</button>
|
||||||
|
<h2 className="text-xl font-bold text-[#1a237e]">Call History</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowMenu(!showMenu)}
|
||||||
|
className="p-2 rounded-full border border-gray-200 hover:bg-gray-50 flex items-center justify-center transition-colors shadow-sm"
|
||||||
|
>
|
||||||
|
<MoreVertical className="w-5 h-5 text-gray-700" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showMenu && (
|
||||||
|
<div className="absolute right-0 mt-2 w-48 bg-white rounded-xl shadow-xl border border-gray-100 z-50 overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setIsEditMode(!isEditMode);
|
||||||
|
setShowMenu(false);
|
||||||
|
if (isEditMode) setSelectedIds([]);
|
||||||
|
}}
|
||||||
|
className="w-full px-4 py-3 text-left hover:bg-gray-50 text-gray-700 font-medium text-sm transition-colors"
|
||||||
|
>
|
||||||
|
{isEditMode ? "Cancel Deletion" : "Delete History"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User Info Banner */}
|
||||||
|
<div className="p-4 flex items-center gap-4 bg-white border-b border-gray-100">
|
||||||
|
<div className="w-14 h-14 rounded-full overflow-hidden border border-gray-200 shadow-sm relative">
|
||||||
|
<img
|
||||||
|
src={myDetails.profile || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png"}
|
||||||
|
alt="Profile"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<h3 className="font-bold text-[#df1d46] text-[17px]">{myDetails.name || "User"}</h3>
|
||||||
|
{myDetails.is_paid && (
|
||||||
|
<span className="bg-[#0c8626] text-white text-[10px] font-bold px-2 py-0.5 rounded uppercase tracking-wider">Free</span> // Or Paid dynamically
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-semibold text-gray-800">
|
||||||
|
ID : <span className="font-bold">{myDetails.member_id || myDetails.profile_id || "N/A"}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<div className="flex p-4 gap-4 bg-white sticky top-0 z-10 border-b border-gray-100 shadow-sm">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('me')}
|
||||||
|
className={`flex-1 py-3 text-sm font-bold rounded-lg border transition-all ${activeTab === 'me'
|
||||||
|
? 'bg-[#df1d46] text-white border-[#df1d46]'
|
||||||
|
: 'bg-white text-gray-600 border-gray-200 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Viewed By Me
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('others')}
|
||||||
|
className={`flex-1 py-3 text-sm font-bold rounded-lg border transition-all ${activeTab === 'others'
|
||||||
|
? 'bg-[#df1d46] text-white border-[#df1d46]'
|
||||||
|
: 'bg-white text-gray-600 border-gray-200 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Viewed By Others
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content List */}
|
||||||
|
<div className="flex-1 overflow-y-auto pb-20 relative bg-[#fcfcfc]">
|
||||||
|
{loading ? (
|
||||||
|
getSkeletons()
|
||||||
|
) : historyList.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8 mt-10">
|
||||||
|
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||||
|
<span className="text-gray-400">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" /><line x1="22" y1="2" x2="2" y2="22" /></svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-500 font-medium">No Call History found</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="divide-y divide-gray-100 border-t border-gray-100">
|
||||||
|
{historyList.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={item.id || idx}
|
||||||
|
onClick={() => isEditMode && toggleSelection(item.id)}
|
||||||
|
className={`flex items-center gap-4 p-4 transition-colors cursor-pointer hover:bg-gray-50 ${isEditMode && selectedIds.includes(item.id) ? 'bg-red-50/50' : 'bg-white'}`}
|
||||||
|
>
|
||||||
|
{/* Checkbox for Edit Mode */}
|
||||||
|
{isEditMode && (
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className={`w-5 h-5 rounded border flex items-center justify-center transition-colors ${selectedIds.includes(item.id)
|
||||||
|
? 'bg-[#df1d46] border-[#df1d46]'
|
||||||
|
: 'border-gray-300 bg-white'
|
||||||
|
}`}>
|
||||||
|
{selectedIds.includes(item.id) && (
|
||||||
|
<svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="3" d="M5 13l4 4L19 7" /></svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Avatar */}
|
||||||
|
<img
|
||||||
|
src={item.profile || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png"}
|
||||||
|
alt={item.name}
|
||||||
|
className="w-14 h-14 rounded-full object-cover border border-gray-100 shadow-sm flex-shrink-0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Details */}
|
||||||
|
<div className="flex-1 min-w-0 flex flex-col justify-center">
|
||||||
|
<div className="flex justify-between items-start mb-1.5">
|
||||||
|
<h4 className="font-semibold text-gray-900 text-[16px] truncate pr-2">{item.name}</h4>
|
||||||
|
<span className="text-xs text-gray-500 whitespace-nowrap mt-1">{item.date || item.created_at || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{item.mobile ? (
|
||||||
|
<p className="text-sm font-semibold text-gray-800 truncate">
|
||||||
|
Mobile No : <span className="font-bold">{item.mobile}</span>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm font-semibold text-gray-800 truncate">
|
||||||
|
ID : <span className="font-bold uppercase">{item.member_id || "N/A"}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Delete Button (Sticky Bottom) */}
|
||||||
|
{isEditMode && selectedIds.length > 0 && (
|
||||||
|
<div className="absolute bottom-4 left-4 right-4 z-20">
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="w-full bg-[#A80000] hover:bg-red-900 text-white font-bold py-4 rounded-xl shadow-lg shadow-red-200 transition-all flex items-center justify-center gap-2 text-lg active:scale-[0.98] disabled:opacity-70 disabled:active:scale-100"
|
||||||
|
>
|
||||||
|
{isDeleting ? <Loader2 className="w-6 h-6 animate-spin" /> : "Delete"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CallHistory;
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,620 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Crown, Bookmark, X, Heart, Eye, Phone, MessageSquare, ChevronLeft } from "lucide-react";
|
||||||
|
|
||||||
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
|
import axiosInstance, { apiForFiles } from "../../api/axiosInstance";
|
||||||
|
|
||||||
|
import { API_ENDPOINTS } from "../../api/apiEndpoints";
|
||||||
|
import UpgradeModal from "./UpgradeModal";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
// Custom Icons
|
||||||
|
import personIcon from "../../assets/images/personicon.svg";
|
||||||
|
import religionIcon from "../../assets/images/religonicon.svg";
|
||||||
|
import locationIcon from "../../assets/images/locationicon.svg";
|
||||||
|
import cashIcon from "../../assets/images/cashicon.svg";
|
||||||
|
|
||||||
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
|
||||||
|
import { sendMessage } from "../../services/chatApi";
|
||||||
|
|
||||||
|
import { getHeaderDetails } from "../../api/preview.api";
|
||||||
|
|
||||||
|
export default function ProfileCardUI({ profile }) {
|
||||||
|
const [isLiked, setIsLiked] = useState(false);
|
||||||
|
const [isShortlisted, setIsShortlisted] = useState(profile.is_shortlisted === 1);
|
||||||
|
const [isLimitModalOpen, setIsLimitModalOpen] = useState(false);
|
||||||
|
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
|
||||||
|
const [isViewContactModalOpen, setIsViewContactModalOpen] = useState(false);
|
||||||
|
const [isInterestStatusModalOpen, setIsInterestStatusModalOpen] = useState(false);
|
||||||
|
const [isInterestRejectedModalOpen, setIsInterestRejectedModalOpen] = useState(false);
|
||||||
|
const [isContactSuccessModalOpen, setIsContactSuccessModalOpen] = useState(false);
|
||||||
|
const [isChatConfirmModalOpen, setIsChatConfirmModalOpen] = useState(false);
|
||||||
|
const [isCreatingChat, setIsCreatingChat] = useState(false);
|
||||||
|
const [modalTitle, setModalTitle] = useState("");
|
||||||
|
|
||||||
|
|
||||||
|
const [modalMessage, setModalMessage] = useState("");
|
||||||
|
const [unlockedMobile, setUnlockedMobile] = useState(null);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { data: headerData } = useQuery({
|
||||||
|
queryKey: ["headerDetails"],
|
||||||
|
queryFn: getHeaderDetails,
|
||||||
|
staleTime: 60000, // Reuse data for 1 minute
|
||||||
|
});
|
||||||
|
|
||||||
|
const isUserPaid = headerData?.myDetails?.is_paid_member === true;
|
||||||
|
|
||||||
|
const handleInterest = async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isUserPaid) {
|
||||||
|
setIsUpgradeModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await sendInterest(profile.id);
|
||||||
|
setIsLiked(true);
|
||||||
|
toast.success(`Interest sent to ${profile.name}!`, {
|
||||||
|
icon: '❤️',
|
||||||
|
style: { borderRadius: '10px', background: '#333', color: '#fff' }
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const msg = error.message || "";
|
||||||
|
if (msg.toLowerCase().includes("limit") || msg.toLowerCase().includes("reached")) {
|
||||||
|
setIsLimitModalOpen(true);
|
||||||
|
} else {
|
||||||
|
toast.error(msg || "Failed to send interest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDecline = async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
await axiosInstance.post(`${API_ENDPOINTS.UPDATE_INTEREST_STATUS}?profile_id=${profile.id}&status=reject`);
|
||||||
|
toast.success("Profile declined");
|
||||||
|
// Optionally hide card or trigger refresh
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to decline profile.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleCall = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isUserPaid) {
|
||||||
|
setIsUpgradeModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const interestStatus = (profile.is_send_interest_status || "").toLowerCase();
|
||||||
|
|
||||||
|
if (profile.call_protection === 1) {
|
||||||
|
if (profile.is_send_interest) {
|
||||||
|
if (interestStatus === 'pending') {
|
||||||
|
setModalTitle("Note");
|
||||||
|
setModalMessage("An interest request has already been sent for this profile. Please wait for their response");
|
||||||
|
setIsInterestStatusModalOpen(true);
|
||||||
|
return;
|
||||||
|
} else if (interestStatus === 'reject' || interestStatus === 'rejected') {
|
||||||
|
setIsInterestRejectedModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleCallTap();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _handleCallTap = () => {
|
||||||
|
const currentMobile = unlockedMobile || profile.mobile_number || "";
|
||||||
|
const mobile = currentMobile.toLowerCase();
|
||||||
|
if (mobile.includes("upgrade to view")) {
|
||||||
|
setIsUpgradeModalOpen(true);
|
||||||
|
} else if (mobile.includes("view contact")) {
|
||||||
|
setIsViewContactModalOpen(true);
|
||||||
|
} else {
|
||||||
|
// It's a real number - Show the success modal with the number
|
||||||
|
setUnlockedMobile(currentMobile);
|
||||||
|
setIsContactSuccessModalOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleMessage = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// 1. Check if chat already exists
|
||||||
|
if (profile.chat_id) {
|
||||||
|
navigate(`/chat/${profile.chat_id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check membership
|
||||||
|
if (!isUserPaid) {
|
||||||
|
setIsUpgradeModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const interestStatus = (profile.is_send_interest_status || "").toLowerCase();
|
||||||
|
|
||||||
|
// 3. Check protection & interest status
|
||||||
|
if (profile.chat_protection === 1 || profile.call_protection === 1) {
|
||||||
|
if (profile.is_send_interest) {
|
||||||
|
if (interestStatus === 'pending') {
|
||||||
|
setModalTitle("Note");
|
||||||
|
setModalMessage("An interest request has already been sent for this profile. Please wait for their response");
|
||||||
|
setIsInterestStatusModalOpen(true);
|
||||||
|
return;
|
||||||
|
} else if (interestStatus === 'reject' || interestStatus === 'rejected') {
|
||||||
|
setIsInterestRejectedModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Show confirmation dialog
|
||||||
|
setIsChatConfirmModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _handleCreateChat = async () => {
|
||||||
|
if (isCreatingChat) return;
|
||||||
|
|
||||||
|
setIsChatConfirmModalOpen(false);
|
||||||
|
setIsCreatingChat(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.post(API_ENDPOINTS.CHAT_CREATE, { profile_id: profile.id });
|
||||||
|
|
||||||
|
if (response.data?.status === true || response.data?.status === 'success') {
|
||||||
|
const newChatId = response.data?.chat_id;
|
||||||
|
|
||||||
|
// Send default initiation message manually
|
||||||
|
try {
|
||||||
|
await sendMessage(newChatId, "This profile has initiated a chat with you");
|
||||||
|
} catch (msgErr) {
|
||||||
|
console.error("Error sending initial message:", msgErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Chat initiated!");
|
||||||
|
navigate(`/chat/${newChatId}`);
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.error(response.data?.message || "Could not create chat.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating chat", error);
|
||||||
|
toast.error("Failed to start conversation");
|
||||||
|
} finally {
|
||||||
|
setIsCreatingChat(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _handleViewContact = async () => {
|
||||||
|
setIsViewContactModalOpen(false);
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("profile_id", profile.id);
|
||||||
|
const response = await apiForFiles.post(API_ENDPOINTS.VIEW_CONTACT, formData);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (response.data?.status === 'success') {
|
||||||
|
const newMobile = response.data?.mobile_number;
|
||||||
|
setUnlockedMobile(newMobile);
|
||||||
|
setIsContactSuccessModalOpen(true);
|
||||||
|
toast.success("Contact details unlocked!");
|
||||||
|
// Refresh all queries to update the UI globally
|
||||||
|
queryClient.invalidateQueries();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
setIsUpgradeModalOpen(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
console.error("Error viewing contact", error);
|
||||||
|
toast.error("Failed to view contact");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShortlistClick = async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
const res = await shortlistProfile(profile.id);
|
||||||
|
if (res.status === "success") {
|
||||||
|
setIsShortlisted(!isShortlisted);
|
||||||
|
toast.success(res.data.message || "Updated shortlist status");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to update shortlist");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<>
|
||||||
|
<UpgradeModal
|
||||||
|
isOpen={isUpgradeModalOpen}
|
||||||
|
onClose={() => setIsUpgradeModalOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* View Contact Confirmation Modal */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isViewContactModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center relative"
|
||||||
|
>
|
||||||
|
<button onClick={() => setIsViewContactModalOpen(false)} className="absolute top-6 left-6 text-gray-400 hover:text-gray-600">
|
||||||
|
<ChevronLeft size={24} />
|
||||||
|
</button>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-6">Note</h3>
|
||||||
|
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||||
|
You need to view this profile's contact details. If you choose to <span className="text-pink-600 font-bold">"Proceed"</span> one count will be deducted from your subscription.
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<button onClick={() => setIsViewContactModalOpen(false)} className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500 hover:bg-gray-50">Cancel</button>
|
||||||
|
<button onClick={_handleViewContact} className="flex-1 py-4 bg-[#DF1D46] text-white rounded-xl font-bold hover:bg-red-700 shadow-lg shadow-red-200">Proceed</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Interest Status Modal (Pending) */}
|
||||||
|
{isInterestStatusModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||||
|
>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-6">{modalTitle}</h3>
|
||||||
|
<p className="text-gray-600 mb-8 leading-relaxed">{modalMessage}</p>
|
||||||
|
<button onClick={() => setIsInterestStatusModalOpen(false)} className="w-full py-4 bg-[#DF1D46] text-white rounded-xl font-bold hover:bg-red-700">OK</button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Interest Rejected Modal */}
|
||||||
|
{isInterestRejectedModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||||
|
>
|
||||||
|
<div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<X size={40} className="text-[#DF1D46]" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-4">Note</h3>
|
||||||
|
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||||
|
Your previous interest request was declined. You may resend your interest to this profile
|
||||||
|
</p>
|
||||||
|
<button onClick={() => setIsInterestRejectedModalOpen(false)} className="w-full py-4 bg-[#DF1D46] text-white rounded-xl font-bold hover:bg-red-700">OK</button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Contact Success Modal */}
|
||||||
|
{isContactSuccessModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||||
|
>
|
||||||
|
<div className="w-20 h-20 bg-green-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<Phone size={40} className="text-[#0C8626]" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-2">Success!</h3>
|
||||||
|
<p className="text-gray-500 mb-6">The contact number has been unlocked.</p>
|
||||||
|
|
||||||
|
<div className="bg-gray-50 rounded-2xl p-6 mb-8 border border-gray-100 min-h-[80px] flex items-center justify-center">
|
||||||
|
<span className="text-3xl font-black text-gray-900 tracking-wider">
|
||||||
|
{unlockedMobile || (!profile.mobile_number?.toLowerCase().includes("view") ? profile.mobile_number : "Fetching...")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsContactSuccessModalOpen(false)}
|
||||||
|
className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500 hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const finalMobile = unlockedMobile || profile.mobile_number;
|
||||||
|
if (finalMobile) {
|
||||||
|
const cleanMobile = finalMobile.replace(/[^0-9+]/g, '');
|
||||||
|
window.location.href = `tel:${cleanMobile}`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex-1 py-4 bg-[#0C8626] text-white rounded-xl font-bold hover:bg-green-700 shadow-lg shadow-green-200 flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<Phone size={18} /> Call Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Chat Confirmation Modal */}
|
||||||
|
{isChatConfirmModalOpen && (() => {
|
||||||
|
const currentMobile = profile.mobile_number || "";
|
||||||
|
const mobileLower = currentMobile.toLowerCase();
|
||||||
|
const isMobileVisible = currentMobile !== "" &&
|
||||||
|
!mobileLower.includes("upgrade") &&
|
||||||
|
!mobileLower.includes("view contact");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9 }}
|
||||||
|
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||||
|
>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-6">
|
||||||
|
{isMobileVisible ? "Ready to Chat?" : "Note"}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||||
|
{isMobileVisible
|
||||||
|
? `Are you ready to chat with ${currentMobile}?`
|
||||||
|
: "Starting a conversation will use 1 chat count. Are you ready to proceed?"}
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsChatConfirmModalOpen(false)}
|
||||||
|
className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500 hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={_handleCreateChat}
|
||||||
|
className={`flex-1 py-4 text-white rounded-xl font-bold transition-all shadow-lg ${isCreatingChat ? "bg-gray-400" : "bg-[#DF1D46] hover:bg-red-700 shadow-red-200"}`}
|
||||||
|
>
|
||||||
|
{isCreatingChat ? "Starting..." : "Proceed"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<LimitReachedModal
|
||||||
|
isOpen={isLimitModalOpen}
|
||||||
|
onClose={() => setIsLimitModalOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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={handleShortlistClick}
|
||||||
|
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 transition-colors ${isShortlisted ? "text-black fill-black" : "text-gray-600"}`} />
|
||||||
|
<span className="text-[12px] font-medium text-gray-700">
|
||||||
|
{isShortlisted ? "Shortlisted" : "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-5 py-5 -mt-8 bg-white rounded-t-[32px] relative z-[2] shadow-[0_-12px_40px_rgba(0,0,0,0.1)] min-h-[280px] flex flex-col">
|
||||||
|
|
||||||
|
<div className="text-left mb-4">
|
||||||
|
|
||||||
|
<h2 className="text-[20px] font-bold text-gray-900 leading-tight">
|
||||||
|
{profile.name}
|
||||||
|
</h2>
|
||||||
|
<div className="flex items-center justify-start gap-3 mt-1.5 text-[11px] font-semibold text-gray-400 uppercase tracking-wider">
|
||||||
|
<span>ID: {profile.member_id || profile.id}</span>
|
||||||
|
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<VisibilityIcon sx={{ fontSize: 14 }} />
|
||||||
|
{profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3 mt-2 flex-1">
|
||||||
|
{(profile.age || profile.height) && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||||
|
<img src={personIcon} alt="Person" className="w-4 h-4 opacity-80" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[13px] font-bold text-gray-700">
|
||||||
|
{profile.age ? `${profile.age} yrs` : ""}
|
||||||
|
{profile.age && profile.height ? ", " : ""}
|
||||||
|
{profile.height ? `${profile.height} cm` : ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(profile.religion_name || profile.caste_name) && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-pink-50 flex items-center justify-center flex-shrink-0">
|
||||||
|
<img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-80" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
|
{profile.religion_name}
|
||||||
|
{profile.religion_name && profile.caste_name ? " / " : ""}
|
||||||
|
{profile.caste_name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(profile.annual_income_name || profile.income) && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-green-50 flex items-center justify-center flex-shrink-0">
|
||||||
|
<img src={cashIcon} alt="Cash" className="w-4 h-4 opacity-80" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
|
{profile.annual_income_name || profile.income}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(profile.district_name || profile.location) && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0">
|
||||||
|
<img src={locationIcon} alt="Location" className="w-4 h-4 opacity-80" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
|
{profile.district_name || profile.location}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
{profile.is_send_interest_received && profile.statusReceived?.toLowerCase() === 'pending' ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleInterest}
|
||||||
|
className="flex-1 flex items-center justify-center gap-2 py-3 bg-[#0C8626] text-white rounded-2xl font-bold text-[13px] hover:bg-green-700 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
<Heart size={16} fill="white" /> Accept
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleDecline}
|
||||||
|
className="flex-1 flex items-center justify-center gap-2 py-3 bg-white border border-red-200 text-red-600 rounded-2xl font-bold text-[13px] hover:bg-red-50 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
<X size={16} /> Reject
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (!(profile.is_send_interest || isLiked) && !profile.is_send_interest_received) ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleInterest}
|
||||||
|
className={`flex-[1.2] flex items-center justify-center gap-2 py-3 rounded-2xl font-bold text-[13px] transition-all active:scale-[0.98] shadow-lg shadow-green-900/10 ${isLiked ? "bg-green-100 text-green-700 border border-green-200" : "bg-[#034E08] text-white hover:bg-green-800"}`}
|
||||||
|
>
|
||||||
|
<Heart size={16} fill={isLiked ? "#034E08" : "white"} />
|
||||||
|
{isLiked ? "Sent" : "Interest"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); navigate(`/profile-details/${profile.id}`); }}
|
||||||
|
className="flex-1 py-3 bg-gray-50 border border-gray-100 text-gray-600 rounded-2xl font-bold text-[13px] flex items-center justify-center gap-2 hover:bg-gray-100 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
<Eye size={16} /> Detail
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleCall}
|
||||||
|
className="flex-1 flex items-center justify-center gap-2 py-3 bg-white border border-green-200 text-green-700 rounded-2xl font-bold text-[13px] hover:bg-green-50 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
<Phone size={16} /> Call
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleMessage}
|
||||||
|
className="flex-1 flex items-center justify-center gap-2 py-3 bg-white border border-red-200 text-red-600 rounded-2xl font-bold text-[13px] hover:bg-red-50 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
<MessageSquare size={16} /> Message
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LimitReachedModal = ({ isOpen, onClose }) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-[10000] p-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
className="bg-white rounded-3xl p-8 max-w-sm w-full text-center shadow-2xl relative"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute top-4 right-4 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
<X className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
<div className="w-20 h-20 bg-amber-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<Clock className="w-10 h-10 text-amber-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-3">Limit Reached!</h3>
|
||||||
|
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||||
|
You've reached your daily limit for sending interests. Please try again tomorrow or upgrade your plan for more daily connections!
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-full py-4 bg-[#034E08] text-white rounded-2xl font-bold hover:bg-green-800 transition-colors"
|
||||||
|
>
|
||||||
|
Got it
|
||||||
|
</button>
|
||||||
|
</motion.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>
|
||||||
} />
|
} />
|
||||||
@@ -326,14 +439,13 @@ const ProfileHeader = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppBar position="sticky" sx={{ backgroundColor: "#fff" }}>
|
<AppBar position="sticky" sx={{ backgroundColor: "#fff", zIndex: 1000 }}>
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Toolbar disableGutters>
|
<Toolbar disableGutters>
|
||||||
<Box onClick={() => navigate("/")} sx={{ display: { xs: "none", md: "flex" }, mr: 1 }}>
|
<Box onClick={() => navigate("/")} sx={{ display: { xs: "none", md: "flex" }, mr: 1 }}>
|
||||||
<LazyImage
|
<LazyImage
|
||||||
src={Logo}
|
src={Logo}
|
||||||
className="w-full h-[70px] my-2 rounded-lg object-cover cursor-pointer"
|
className="h-[50px] w-auto my-1 rounded-lg object-contain cursor-pointer"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -349,8 +461,7 @@ const ProfileHeader = () => {
|
|||||||
>
|
>
|
||||||
<LazyImage
|
<LazyImage
|
||||||
src={Logo}
|
src={Logo}
|
||||||
className="w-full h-[50px] rounded-lg object-cover"
|
className="h-[40px] w-auto rounded-lg object-contain"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -359,7 +470,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 +483,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;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const TinderCard = ({ children, onSwipe, onCardLeftScreen, preventSwipe, className }) => {
|
||||||
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const [startPos, setStartPos] = useState({ x: 0, y: 0 });
|
||||||
|
const [gone, setGone] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPos({ x: 0, y: 0 });
|
||||||
|
setDragging(false);
|
||||||
|
setGone(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleStart = (clientX, clientY) => {
|
||||||
|
if (gone) return;
|
||||||
|
setDragging(true);
|
||||||
|
setStartPos({ x: clientX - pos.x, y: clientY - pos.y });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMove = (clientX, clientY) => {
|
||||||
|
if (!dragging || gone) return;
|
||||||
|
setPos({ x: clientX - startPos.x, y: clientY - startPos.y });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnd = () => {
|
||||||
|
if (gone) return;
|
||||||
|
setDragging(false);
|
||||||
|
if (Math.abs(pos.x) > 120) {
|
||||||
|
const dir = pos.x > 0 ? 'right' : 'left';
|
||||||
|
if (onSwipe) onSwipe(dir);
|
||||||
|
setGone(true);
|
||||||
|
setTimeout(() => onCardLeftScreen && onCardLeftScreen(dir), 300);
|
||||||
|
} else {
|
||||||
|
setPos({ x: 0, y: 0 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (gone) return null;
|
||||||
|
|
||||||
|
const rotation = pos.x / 20;
|
||||||
|
const opacity = Math.min(Math.abs(pos.x) / 100, 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
style={{
|
||||||
|
transform: `translate(${pos.x}px, ${pos.y}px) rotate(${rotation}deg)`,
|
||||||
|
transition: dragging ? 'none' : 'transform 0.3s ease-out',
|
||||||
|
touchAction: 'none'
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => handleStart(e.clientX, e.clientY)}
|
||||||
|
onMouseMove={(e) => handleMove(e.clientX, e.clientY)}
|
||||||
|
onMouseUp={handleEnd}
|
||||||
|
onMouseLeave={() => dragging && handleEnd()}
|
||||||
|
onTouchStart={(e) => handleStart(e.touches[0].clientX, e.touches[0].clientY)}
|
||||||
|
onTouchMove={(e) => handleMove(e.touches[0].clientX, e.touches[0].clientY)}
|
||||||
|
onTouchEnd={handleEnd}
|
||||||
|
>
|
||||||
|
{pos.x > 50 && (
|
||||||
|
<div className="absolute top-8 left-8 z-10 border-4 border-green-500 text-green-500 px-4 py-2 rounded-lg text-2xl font-bold rotate-[-20deg]" style={{ opacity }}>
|
||||||
|
LIKE
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{pos.x < -50 && (
|
||||||
|
<div className="absolute top-8 right-8 z-10 border-4 border-red-500 text-red-500 px-4 py-2 rounded-lg text-2xl font-bold rotate-[20deg]" style={{ opacity }}>
|
||||||
|
NOPE
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TinderCard;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const UpgradeModal = ({ isOpen, onClose }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-4">
|
||||||
|
{/* Backdrop */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Modal Content */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||||
|
className="relative bg-white rounded-[32px] shadow-2xl w-full max-w-sm overflow-hidden p-10 flex flex-col items-center text-center"
|
||||||
|
>
|
||||||
|
<h4 className="text-[22px] font-bold text-gray-900 mb-6">Note</h4>
|
||||||
|
|
||||||
|
<h3 className="text-[20px] font-bold text-gray-900 mb-4 leading-tight">
|
||||||
|
Membership Upgrade Required
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-[#888] text-[15px] leading-relaxed mb-10 px-2 font-medium">
|
||||||
|
You are currently a Free Member. Upgrade to a Premium Plan to start sending interests and connecting with your perfect match.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex w-full gap-4">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="flex-1 py-4 border border-gray-200 text-gray-500 rounded-2xl font-bold text-[16px] hover:bg-gray-50 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
Later
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/subscription-plan');
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
className="flex-1 py-4 bg-[#e91e4a] text-white rounded-2xl font-bold text-[16px] shadow-lg shadow-red-200 hover:bg-[#d81b45] transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
Upgrade Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpgradeModal;
|
||||||
@@ -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,472 +18,306 @@ 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: "All Matches",
|
||||||
description: "View all the profiles that match your preferences",
|
description: "Profiles matching your preferences",
|
||||||
category: "All Matches",
|
category: "Primary Matches",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "shortlisted-by-you",
|
id: "newly_joined",
|
||||||
|
icon: <RockingChair className="w-6 h-6" />,
|
||||||
|
title: "Newly Joined",
|
||||||
|
description: "Joined within the last 30 days",
|
||||||
|
category: "Primary Matches",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: "Your 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: "Profiles who viewed you",
|
||||||
category: "Based on activity",
|
category: "Your 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: "Profiles who shortlisted you",
|
||||||
category: "Based on activity",
|
category: "Your 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: "Profiles you have viewed",
|
||||||
category: "Based on activity",
|
category: "Your Activity",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "newly-joined",
|
id: "location_matches",
|
||||||
icon: <RockingChair className="w-6 h-6" />,
|
|
||||||
title: "Newly Joined",
|
|
||||||
description: "Matches who Joined within the last 30 days",
|
|
||||||
category: "Based on activity",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "location",
|
|
||||||
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: "Smart Matches",
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "education",
|
|
||||||
icon: <School className="w-6 h-6" />,
|
|
||||||
title: "Education matches",
|
|
||||||
description: "Matches near your education match",
|
|
||||||
category: "Based on activity",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "job",
|
id: "education_matches",
|
||||||
|
icon: <School className="w-6 h-6" />,
|
||||||
|
title: "Education matches",
|
||||||
|
description: "Profiles with similar education",
|
||||||
|
category: "Smart Matches",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: "Profiles with similar profession",
|
||||||
category: "Based on activity",
|
category: "Smart Matches",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
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 (
|
||||||
|
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{/* <div className="grid grid-cols-1 md:grid-cols-[300px_auto] md:px-4 gap-2 md:gap-10" > */}
|
{/* <div className="grid grid-cols-1 md:grid-cols-[300px_auto] md:px-4 gap-2 md:gap-10" > */}
|
||||||
<div className="flex flex-col md:flex-row my-6" >
|
<div className="flex flex-col md:flex-row my-6" >
|
||||||
|
|
||||||
|
|
||||||
{/* Left Sidebar - Fixed on desktop, scrollable on mobile */}
|
|
||||||
|
|
||||||
<div className="w-full md:w-80">
|
{/* Left Sidebar - Fixed on desktop, scrollable on mobile */}
|
||||||
<div
|
|
||||||
className="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]"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div className="py-2 px-4 sticky top-0 bg-[#fff] ">
|
<div className="w-full md:w-80">
|
||||||
<h2 className="text-xl font-bold text-green-900 mb-4 mt-6 first:mt-0">
|
<div
|
||||||
Filter Matches </h2>
|
className="relative rounded-[15px] border border-gray-200 bg-white my-6
|
||||||
|
shadow-xl h-[450px] md:h-[calc(100vh-200px)] overflow-y-auto md:sticky md:top-[120px] custom-sidebar-scrollbar"
|
||||||
|
>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="py-6 px-4 h-full" >
|
<div className="py-2 px-4 sticky top-0 bg-[#fff] ">
|
||||||
{tabs.map((tab) => {
|
<h2 className="text-xl font-bold text-green-900 mb-4 mt-6 first:mt-0">
|
||||||
const showCategory = tab.category !== currentCategory;
|
Filter Matches </h2>
|
||||||
if (showCategory) {
|
|
||||||
currentCategory = tab.category;
|
</div>
|
||||||
}
|
|
||||||
|
<div className="py-6 px-4 h-full" >
|
||||||
|
{tabs.map((tab) => {
|
||||||
|
const showCategory = tab.category !== currentCategory;
|
||||||
|
if (showCategory) {
|
||||||
|
currentCategory = tab.category;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={tab.id}>
|
||||||
|
{showCategory && (
|
||||||
|
<h2 className="text-xs font-bold text-green-700 uppercase tracking-widest mb-3 mt-6 first:mt-0 px-2 opacity-80">
|
||||||
|
{tab.category}
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={tab.id}>
|
|
||||||
{showCategory && (
|
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-4 mt-6 first:mt-0">
|
|
||||||
{tab.category}
|
|
||||||
</h2>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
onClick={() => setSelectedTab(tab.id)}
|
|
||||||
className={`p-4 rounded-lg mb-3 cursor-pointer transition-all ${
|
|
||||||
selectedTab === tab.id
|
|
||||||
? "bg-green-50 border-l-4 border-green-600"
|
|
||||||
: "hover:bg-gray-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div
|
<div
|
||||||
className={`mt-1 ${
|
onClick={() => {
|
||||||
selectedTab === tab.id
|
const finalFilterType = tab.id === "all_matches" ? "" : tab.id;
|
||||||
? "text-green-600"
|
dispatch(updateFilter({ filter_type: finalFilterType }));
|
||||||
: "text-gray-600"
|
}}
|
||||||
}`}
|
|
||||||
|
className={`p-4 rounded-lg mb-3 cursor-pointer transition-all ${selectedTab === tab.id
|
||||||
|
? "bg-green-50 border-l-4 border-green-600"
|
||||||
|
: "hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{tab.icon}
|
<div className="flex items-start gap-3">
|
||||||
</div>
|
<div
|
||||||
<div className="flex-1">
|
className={`mt-1 ${selectedTab === tab.id
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h3
|
|
||||||
className={`font-semibold text-base ${
|
|
||||||
selectedTab === tab.id
|
|
||||||
? "text-green-900"
|
|
||||||
: "text-gray-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tab.title}
|
|
||||||
</h3>
|
|
||||||
<svg
|
|
||||||
className={`w-5 h-5 ${
|
|
||||||
selectedTab === tab.id
|
|
||||||
? "text-green-600"
|
? "text-green-600"
|
||||||
: "text-gray-400"
|
: "text-gray-600"
|
||||||
}`}
|
}`}
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
>
|
||||||
<path
|
{tab.icon}
|
||||||
strokeLinecap="round"
|
</div>
|
||||||
strokeLinejoin="round"
|
<div className="flex-1">
|
||||||
strokeWidth={2}
|
<div className="flex items-center justify-between">
|
||||||
d="M9 5l7 7-7 7"
|
<h3
|
||||||
/>
|
className={`font-semibold text-base ${selectedTab === tab.id
|
||||||
</svg>
|
? "text-green-900"
|
||||||
|
: "text-gray-900"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</h3>
|
||||||
|
<svg
|
||||||
|
className={`w-5 h-5 ${selectedTab === tab.id
|
||||||
|
? "text-green-600"
|
||||||
|
: "text-gray-400"
|
||||||
|
}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600 mt-1">
|
||||||
|
{tab.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 mt-1">
|
|
||||||
{tab.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
})}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Right Content Area - Scrollable */}
|
|
||||||
<div className=" px-2 py-8 w-full md:w-9/12 ">
|
|
||||||
<div className="w-[100%] max-w-[1200px] mx-auto">
|
|
||||||
<div className="flex justify-between gap-2 itemes-center mb-8">
|
|
||||||
<h1 className="text-[24px] font-bold text-gray-900 ">
|
|
||||||
{tabs.find((t) => t.id === selectedTab)?.title}
|
|
||||||
</h1>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<img
|
|
||||||
src={horoscope}
|
|
||||||
onClick={() => {
|
|
||||||
navigate("/horoscoper-generate");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<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>
|
||||||
{profiles.map((profile) => (
|
{/* Right Content Area - Scrollable */}
|
||||||
<ProfileCard key={profile.id} profile={profile} />
|
<div className=" px-2 py-8 w-full md:w-9/12 ">
|
||||||
))}
|
<div className="w-[100%] max-w-[1200px] mx-auto">
|
||||||
|
<div className="flex justify-between gap-2 itemes-center mb-8">
|
||||||
|
<h1 className="text-[24px] font-bold text-gray-900 ">
|
||||||
|
{tabs.find((t) => t.id === selectedTab)?.title}
|
||||||
|
</h1>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="relative cursor-pointer" onClick={() => {
|
||||||
|
if (isPaidMember) {
|
||||||
|
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 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
||||||
|
{isLoading && !isFetchingNextPage ? (
|
||||||
|
[...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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -503,10 +332,22 @@ export default function MatchesInterface() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
.custom-sidebar-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.custom-sidebar-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.custom-sidebar-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.custom-sidebar-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #cbd5e1;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -1,405 +1,409 @@
|
|||||||
import { useRef, useState } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion, AnimatePresence } 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 { Crown, Bookmark, X, Ban, ChevronLeft, ChevronRight, RotateCcw, Heart, Timer } from 'lucide-react';
|
||||||
import CakeIcon from "@mui/icons-material/Cake";
|
import { useNavigate } from 'react-router-dom';
|
||||||
import HeightIcon from "@mui/icons-material/Height";
|
import { toast } from 'react-hot-toast';
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
import axiosInstance from "../../api/axiosInstance";
|
||||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
import { API_ENDPOINTS } from "../../api/apiEndpoints";
|
||||||
|
import { sendInterest, shortlistProfile } from '../../services/shortlistapi';
|
||||||
|
import { unblockProfile } from '../../services/profileActionApi';
|
||||||
|
import UpgradeModal from '../common/UpgradeModal';
|
||||||
|
|
||||||
|
// Custom Icons
|
||||||
|
import personIcon from "../../assets/images/personicon.svg";
|
||||||
|
import religionIcon from "../../assets/images/religonicon.svg";
|
||||||
|
import locationIcon from "../../assets/images/locationicon.svg";
|
||||||
|
import cashIcon from "../../assets/images/cashicon.svg";
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
import SchoolIcon from "@mui/icons-material/School";
|
||||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
import { getHeaderDetails } from "../../api/preview.api";
|
||||||
|
|
||||||
// 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';
|
|
||||||
|
|
||||||
const DailyRecommendedCard = () => {
|
/* ─────────────────────────────────────────────
|
||||||
|
Countdown Timer Component (Popup Style)
|
||||||
|
───────────────────────────────────────────── */
|
||||||
|
const CountdownTimer = ({ onContinue }) => {
|
||||||
|
const [timeLeft, setTimeLeft] = useState({ hours: 0, minutes: 0, seconds: 0 });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const calculateTimeLeft = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const endOfDay = new Date();
|
||||||
|
endOfDay.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
const diff = endOfDay - now;
|
||||||
|
if (diff <= 0) return { hours: 0, minutes: 0, seconds: 0 };
|
||||||
|
|
||||||
|
return {
|
||||||
|
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
|
||||||
|
minutes: Math.floor((diff / 1000 / 60) % 60),
|
||||||
|
seconds: Math.floor((diff / 1000) % 60)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeLeft(calculateTimeLeft());
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setTimeLeft(calculateTimeLeft());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fmt = n => String(n).padStart(2, '0');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-md p-4 overflow-y-auto">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.9, y: 40 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
className="bg-white rounded-[40px] shadow-2xl w-full max-w-lg overflow-hidden p-10 flex flex-col items-center text-center my-auto"
|
||||||
|
>
|
||||||
|
<div className="w-24 h-24 bg-blue-50 rounded-full flex items-center justify-center mb-8">
|
||||||
|
<div className="w-16 h-16 rounded-full border-2 border-blue-100 flex items-center justify-center">
|
||||||
|
<Timer className="w-10 h-10 text-blue-900" strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-[28px] font-bold text-[#1e3a8a] mb-4">Daily Limit Exceeded</h3>
|
||||||
|
<p className="text-gray-500 text-[16px] leading-relaxed mb-10 px-4">
|
||||||
|
You've viewed all recommended profiles for today. Your daily limit will reset in:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-start justify-center gap-4 mb-12">
|
||||||
|
{[
|
||||||
|
{ label: 'HOURS', value: timeLeft.hours },
|
||||||
|
{ label: 'MINS', value: timeLeft.minutes },
|
||||||
|
{ label: 'SECS', value: timeLeft.seconds }
|
||||||
|
].map((u, i) => (
|
||||||
|
<div key={i} className="flex flex-col items-center">
|
||||||
|
<div className="w-20 h-20 bg-white rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.06)] border border-gray-50 flex items-center justify-center mb-3">
|
||||||
|
<span className="text-[32px] font-bold text-[#1e3a8a]">{fmt(u.value)}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] font-bold text-gray-400 tracking-widest">{u.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onContinue}
|
||||||
|
className="w-full py-5 bg-[#009944] text-white rounded-2xl font-bold text-[18px] shadow-lg shadow-green-900/20 hover:bg-[#00883d] transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
Continue to Dashboard
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DailyRecommendedCard = ({ profiles: initialProfiles = [] }) => {
|
||||||
const swiperRef = useRef(null);
|
const swiperRef = useRef(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [activeProfiles, setActiveProfiles] = useState(initialProfiles);
|
||||||
|
const [isHidden, setIsHidden] = useState(() => {
|
||||||
|
const hiddenDate = localStorage.getItem('daily_limit_hidden_date');
|
||||||
|
return hiddenDate === new Date().toDateString();
|
||||||
|
});
|
||||||
|
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
|
||||||
|
|
||||||
// Sample profile data
|
const { data: headerData } = useQuery({
|
||||||
const profiles = [
|
queryKey: ["headerDetails"],
|
||||||
{
|
queryFn: getHeaderDetails,
|
||||||
id: 1,
|
staleTime: 60000,
|
||||||
name: 'Selva Kumar . R',
|
});
|
||||||
userId: 'TK52586A',
|
|
||||||
lastSeen: '14 Nov 25',
|
console.log("Header Data:", headerData);
|
||||||
age: 23,
|
const isUserPaid = headerData?.myDetails?.is_paid_member === true;
|
||||||
height: '5\'2"',
|
|
||||||
religion: 'Hindu / Agamudayar / Thuluva Vellal',
|
useEffect(() => {
|
||||||
education: 'BCA, Data Analyst',
|
setActiveProfiles(initialProfiles);
|
||||||
location: 'Vellore, Tamil Nadu',
|
}, [initialProfiles]);
|
||||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=500&fit=crop',
|
|
||||||
isPremium: true
|
|
||||||
},
|
const handleContinueToDashboard = () => {
|
||||||
{
|
localStorage.setItem('daily_limit_hidden_date', new Date().toDateString());
|
||||||
id: 2,
|
setIsHidden(true);
|
||||||
name: 'Priya Sharma',
|
};
|
||||||
userId: 'TK52587B',
|
|
||||||
lastSeen: '15 Nov 25',
|
const handleInterest = async (e, profileId, name) => {
|
||||||
age: 25,
|
e.stopPropagation();
|
||||||
height: '5\'4"',
|
if (!isUserPaid) {
|
||||||
religion: 'Hindu / Brahmin / Iyer',
|
setIsUpgradeModalOpen(true);
|
||||||
education: 'MBA, Marketing Manager',
|
return;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
];
|
try {
|
||||||
|
await sendInterest(profileId);
|
||||||
|
toast.success(`Interest sent to ${name}!`, {
|
||||||
|
icon: '❤️',
|
||||||
|
style: { borderRadius: '10px', background: '#333', color: '#fff' }
|
||||||
|
});
|
||||||
|
setActiveProfiles(prev => prev.filter(p => p.id !== profileId));
|
||||||
|
} catch (error) {
|
||||||
|
const msg = error.message || "";
|
||||||
|
if (msg.toLowerCase().includes("limit") || msg.toLowerCase().includes("reached")) {
|
||||||
|
setIsUpgradeModalOpen(true);
|
||||||
|
} else {
|
||||||
|
toast.error(msg || "Failed to send interest. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDecline = async (e, profileId) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
await axiosInstance.post(`${API_ENDPOINTS.DAILY_RECOMMENDED_DONT_SHOW}?profile_id=${profileId}`);
|
||||||
|
setActiveProfiles(prev => prev.filter(p => p.id !== profileId));
|
||||||
|
toast.success("Profile hidden");
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to hide profile.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnblock = async (e, profileId) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
const res = await unblockProfile(profileId);
|
||||||
|
toast.success(res?.message || "Profile unblocked successfully");
|
||||||
|
setActiveProfiles(prev => prev.map(p => p.id === profileId ? { ...p, is_blocked: 0 } : p));
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error?.message || "Failed to unblock profile.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ProfileCard = ({ profile }) => {
|
const ProfileCard = ({ profile }) => {
|
||||||
const [isLiked, setIsLiked] = useState(false);
|
const image = profile.photo || profile.image;
|
||||||
|
const memberId = profile.member_id || profile.userId;
|
||||||
|
const religion = profile.religion || (profile.religion_name ? `${profile.religion_name}${profile.caste_name ? ' / ' + profile.caste_name : ''}` : null);
|
||||||
|
const education = profile.education || profile.education_name;
|
||||||
|
const occupation = profile.occupation || profile.occupation_name;
|
||||||
|
const location = profile.location || profile.district_name || profile.city_name;
|
||||||
|
const income = profile.income || profile.annual_income || profile.salary;
|
||||||
|
|
||||||
|
const [isShortlisted, setIsShortlisted] = useState(profile.is_shortlisted === 1);
|
||||||
|
|
||||||
|
const handleShortlistClick = async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
try {
|
||||||
|
const res = await shortlistProfile(profile.id);
|
||||||
|
console.log(res);
|
||||||
|
if (res.status === "success" || res.message) {
|
||||||
|
setIsShortlisted(!isShortlisted);
|
||||||
|
toast.success(res.message || "Updated shortlist status");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("Failed to update shortlist");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
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/${profile.id}`)}
|
||||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
|
className="w-full max-w-sm rounded-[24px] shadow-xl overflow-hidden border border-green-100 bg-white group hover:shadow-2xl transition-all"
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative overflow-hidden">
|
||||||
{profile.isPremium && (
|
{profile.isPremium && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0 }}
|
initial={{ scale: 0 }}
|
||||||
animate={{ scale: 1 }}
|
animate={{ scale: 1 }}
|
||||||
transition={{ delay: 0.2, type: 'spring' }}
|
|
||||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||||
>
|
>
|
||||||
<Crown className="w-5 h-5 text-white" />
|
<Crown className="w-5 h-5 text-white" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<motion.button
|
{!profile.is_blocked && (
|
||||||
whileHover={{ scale: 1 }}
|
<motion.button
|
||||||
whileTap={{ scale: 0.9 }}
|
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"
|
className="absolute top-4 right-4 z-10 bg-white/90 backdrop-blur-sm rounded-full px-4 py-2 shadow-md flex items-center space-x-2 hover:bg-white transition-colors"
|
||||||
onClick={(e) => {
|
onClick={handleShortlistClick}
|
||||||
e.stopPropagation();
|
>
|
||||||
}}
|
<Bookmark
|
||||||
>
|
className={`w-4 h-4 transition-colors ${isShortlisted ? "text-black fill-black" : "text-green-700"}`}
|
||||||
<Bookmark className="w-4 h-4" />
|
/>
|
||||||
<span className="text-[12px] font-medium">Shortlist</span>
|
<span className="text-[12px] font-bold text-gray-700">
|
||||||
</motion.button>
|
{isShortlisted ? "Shortlisted" : "Shortlist"}
|
||||||
|
</span>
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div className="bg-gray-200 overflow-hidden w-full h-[300px]">
|
||||||
className=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
|
||||||
style={{ height: "300px" }}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
src={profile.image}
|
src={image}
|
||||||
alt={profile.name}
|
alt={profile.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
className="absolute bottom-0 left-0 right-0 h-32 pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background: "linear-gradient(to top, white 0%, rgba(255,255,255,0.8) 40%, transparent 100%)",
|
||||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
<div className="absolute bottom-0 left-0 right-0 p-6 pb-2">
|
||||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
<h1 className="text-[20px] text-black-900 font-bold mb-1 truncate">
|
||||||
{profile.name}
|
{profile.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
<p className="text-[13px] text-gray-500 font-medium">
|
||||||
Matrimony ID: {profile.userId}
|
ID: {memberId}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="px-6 pb-6 pt-2 space-y-3 bg-white min-h-[160px]">
|
||||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
{(profile.age || profile.height) && (
|
||||||
style={{ background: "rgb(255, 255, 255)" }}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
<img src={personIcon} alt="Person" className="w-5 h-5 opacity-70" />
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span className="text-[14px] font-medium text-gray-700">
|
||||||
Age : {profile.age}
|
{profile.age ? `${profile.age} Yrs` : ''}
|
||||||
|
{profile.age && profile.height ? ', ' : ''}
|
||||||
|
{profile.height || ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{religion && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
<img src={religionIcon} alt="Religion" className="w-5 h-5 opacity-70" />
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
Height: {profile.height}
|
{religion}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{(education || occupation) && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
<SchoolIcon sx={{ fontSize: 18 }} className="text-gray-400" />
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
{profile.religion}
|
{education || occupation}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{location && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
<img src={locationIcon} alt="Location" className="w-5 h-5 opacity-70" />
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
{profile.education}
|
{location}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{income && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
<img src={cashIcon} alt="Income" className="w-5 h-5 opacity-70" />
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
{profile.location}
|
{income}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
<div className="flex gap-3 pt-4 justify-between">
|
||||||
<button
|
{profile.is_blocked ? (
|
||||||
onClick={(e) => {
|
<button
|
||||||
e.stopPropagation();
|
onClick={(e) => handleUnblock(e, profile.id)}
|
||||||
}}
|
className="flex-1 px-4 py-2 bg-[#DF1D46] hover:bg-red-600 text-white font-bold text-sm rounded-full transition-all flex items-center justify-center gap-2 shadow-lg shadow-red-900/20 active:scale-95"
|
||||||
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
|
<Ban size={16} /> Unblock Profile
|
||||||
d="M18 6L6 18M6 6l12 12"
|
</button>
|
||||||
strokeLinecap="round"
|
) : (
|
||||||
strokeLinejoin="round"
|
<>
|
||||||
/>
|
<button
|
||||||
</svg>
|
onClick={(e) => handleDecline(e, profile.id)}
|
||||||
Decline
|
className="flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-600 font-bold text-sm rounded-full transition-all flex items-center justify-center gap-2 active:scale-95"
|
||||||
</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
|
<X size={16} /> Decline
|
||||||
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"
|
</button>
|
||||||
strokeLinecap="round"
|
<button
|
||||||
strokeLinejoin="round"
|
className="flex-1 px-4 py-2 bg-[#034E08] hover:bg-green-800 text-white font-bold text-sm rounded-full transition-all flex items-center justify-center gap-2 shadow-lg shadow-green-900/20 active:scale-95"
|
||||||
/>
|
onClick={(e) => handleInterest(e, profile.id, profile.name)}
|
||||||
</svg>
|
>
|
||||||
)}
|
<Heart size={16} fill="white" /> Interest
|
||||||
Interest
|
</button>
|
||||||
</button>
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (isHidden) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" py-10">
|
<div className="py-12">
|
||||||
<div className="w-[100%] max-w-[1400px] mx-auto">
|
<UpgradeModal
|
||||||
{/* Header */}
|
isOpen={isUpgradeModalOpen}
|
||||||
<motion.div
|
onClose={() => setIsUpgradeModalOpen(false)}
|
||||||
initial={{ opacity: 0, y: -20 }}
|
/>
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
className="text-center mb-12"
|
{activeProfiles.length === 0 ? (
|
||||||
>
|
<CountdownTimer onContinue={handleContinueToDashboard} />
|
||||||
<h1 className="text-[20px] text-[#00000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
) : (
|
||||||
Daily Recommended
|
<div className="w-full max-w-[1400px] mx-auto px-4">
|
||||||
</h1>
|
<motion.div
|
||||||
<p className="text-gray-900 text-[12px]">Find your perfect match today</p>
|
initial={{ opacity: 0, y: -20 }}
|
||||||
</motion.div>
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="text-center mb-12"
|
||||||
{/* Swiper Container */}
|
|
||||||
<div className="relative px-0 sm:px-0">
|
|
||||||
<Swiper
|
|
||||||
ref={swiperRef}
|
|
||||||
modules={[Navigation, Pagination, Autoplay, EffectCoverflow]}
|
|
||||||
spaceBetween={10}
|
|
||||||
slidesPerView={1}
|
|
||||||
autoplay={{
|
|
||||||
delay: 5000,
|
|
||||||
disableOnInteraction: false,
|
|
||||||
pauseOnMouseEnter: true
|
|
||||||
}}
|
|
||||||
// pagination={{
|
|
||||||
// clickable: true,
|
|
||||||
// dynamicBullets: true
|
|
||||||
// }}
|
|
||||||
loop={true}
|
|
||||||
speed={800}
|
|
||||||
breakpoints={{
|
|
||||||
640: {
|
|
||||||
slidesPerView: 2,
|
|
||||||
spaceBetween: 10
|
|
||||||
},
|
|
||||||
1024: {
|
|
||||||
slidesPerView: 3,
|
|
||||||
spaceBetween: 10
|
|
||||||
},
|
|
||||||
1280: {
|
|
||||||
slidesPerView: 4,
|
|
||||||
spaceBetween: 5
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="pb-16"
|
|
||||||
>
|
>
|
||||||
{profiles.map((profile) => (
|
<h2 className="text-3xl font-extrabold text-gray-900 mb-2">
|
||||||
<SwiperSlide key={profile.id}>
|
Daily <span className="text-green-700">Recommended</span>
|
||||||
<ProfileCard profile={profile} />
|
</h2>
|
||||||
</SwiperSlide>
|
<p className="text-gray-500 font-medium">Handpicked matches just for you</p>
|
||||||
))}
|
</motion.div>
|
||||||
</Swiper>
|
|
||||||
|
<div className="relative group">
|
||||||
|
<Swiper
|
||||||
|
ref={swiperRef}
|
||||||
|
modules={[Navigation, Pagination, Autoplay, EffectCoverflow]}
|
||||||
|
spaceBetween={24}
|
||||||
|
slidesPerView={1}
|
||||||
|
autoplay={{ delay: 4000, disableOnInteraction: false }}
|
||||||
|
breakpoints={{
|
||||||
|
640: { slidesPerView: 2 },
|
||||||
|
1024: { slidesPerView: 3 },
|
||||||
|
1280: { slidesPerView: 4 }
|
||||||
|
}}
|
||||||
|
className="!pb-12"
|
||||||
|
>
|
||||||
|
{activeProfiles.map((profile) => (
|
||||||
|
<SwiperSlide key={profile.id}>
|
||||||
|
<ProfileCard profile={profile} />
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => swiperRef.current?.swiper.slidePrev()}
|
||||||
|
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 -translate-x-4 bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all hidden lg:flex border border-gray-100"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-6 h-6 text-green-800" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => swiperRef.current?.swiper.slideNext()}
|
||||||
|
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 translate-x-4 bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all hidden lg:flex border border-gray-100"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-6 h-6 text-green-800" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Custom Navigation Buttons */}
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.1, x: -5 }}
|
|
||||||
whileTap={{ scale: 0.9 }}
|
|
||||||
onClick={() => swiperRef.current?.swiper.slidePrev()}
|
|
||||||
className="hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10
|
|
||||||
bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all
|
|
||||||
w-12 h-12 rounded-full shadow-xl hover:shadow-2xl items-center justify-center "
|
|
||||||
>
|
|
||||||
<ChevronLeft className="w-6 h-6 text-gray-700" />
|
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.1, x: 5 }}
|
|
||||||
whileTap={{ scale: 0.9 }}
|
|
||||||
onClick={() => swiperRef.current?.swiper.slideNext()}
|
|
||||||
className="hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10
|
|
||||||
bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all
|
|
||||||
w-12 h-12 rounded-full shadow-xl hover:shadow-2xl items-center justify-center transition-all"
|
|
||||||
>
|
|
||||||
<ChevronRight className="w-6 h-6 text-gray-700" />
|
|
||||||
</motion.button>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{/* View All Button */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.5 }}
|
|
||||||
className="text-center mt-12"
|
|
||||||
>
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
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"
|
|
||||||
onClick={() => navigate("/matches")}
|
|
||||||
|
|
||||||
>
|
|
||||||
View All Recommendations
|
|
||||||
</motion.button>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Custom Swiper Styles */}
|
|
||||||
<style jsx global>{`
|
|
||||||
.swiper-pagination-bullet {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: #A70710;
|
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-bullet-active {
|
|
||||||
opacity: 1;
|
|
||||||
width: 30px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// .swiper-slide {
|
|
||||||
// // height: auto;
|
|
||||||
// // display: flex;
|
|
||||||
// }
|
|
||||||
|
|
||||||
`}</style>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,91 @@ 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";
|
// Custom Icons
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
import personIcon from "../../assets/images/personicon.svg";
|
||||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
import religionIcon from "../../assets/images/religonicon.svg";
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
import locationIcon from "../../assets/images/locationicon.svg";
|
||||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
import cashIcon from "../../assets/images/cashicon.svg";
|
||||||
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 +102,106 @@ 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"} className={isShortlisted ? "text-black" : ""} />
|
||||||
<span className="text-[12px] font-medium">Shortlist</span>
|
{shortlistMutation.isPending ? "..." : (isShortlisted ? "Shortlisted" : "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>
|
||||||
{/* <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>
|
||||||
|
|
||||||
{/* Stats and Follow Section */}
|
{/* CONTENT */}
|
||||||
<div
|
<div className="px-5 py-5 -mt-8 bg-white rounded-t-[32px] relative z-[2] shadow-[0_-12px_40px_rgba(0,0,0,0.1)] h-[240px] flex flex-col">
|
||||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
|
<div className="text-left mb-4">
|
||||||
style={{
|
<h2 className="text-[20px] font-bold text-gray-900 leading-tight">
|
||||||
background: "rgb(255, 255, 255)",
|
{name}
|
||||||
}}
|
</h2>
|
||||||
>
|
<div className="flex items-center justify-start gap-3 mt-1.5 text-[11px] font-semibold text-gray-400 uppercase tracking-wider">
|
||||||
<div className="flex items-center gap-4">
|
<span>ID: {idNumber}</span>
|
||||||
<div className="flex items-center gap-2">
|
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
<span className="flex items-center gap-1">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<Eye size={12} /> {lastSeen}
|
||||||
Age : {profile.age}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
|
||||||
Height: {profile.height}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<AccountBalanceWalletIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
|
||||||
{profile.salary}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="space-y-3 mt-2">
|
||||||
<div className="flex items-center gap-2">
|
{(age || height) && (
|
||||||
<TempleHinduIcon className="w-4 h-4 text-gray-700" />
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<div className="w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||||
{profile.caste}
|
<img src={personIcon} alt="Person" className="w-4 h-4 opacity-80" />
|
||||||
</span>
|
</div>
|
||||||
</div>
|
<span className="text-[13px] font-bold text-gray-700">
|
||||||
<div className="flex items-center gap-2">
|
{age || ""}
|
||||||
<img src={Image} alt="" className="w-4 h-4 text-gray-700" />
|
{age && height ? ", " : ""}
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
{height || ""}
|
||||||
{profile.zodiac1}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
{(caste || zodiac1) && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<img src={Image1} alt="" className="w-4 h-4 text-gray-700" />
|
<div className="w-8 h-8 rounded-full bg-pink-50 flex items-center justify-center flex-shrink-0">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-80" />
|
||||||
{profile.zodiac2}
|
</div>
|
||||||
</span>
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
</div>
|
{caste}
|
||||||
|
{caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{location && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0">
|
||||||
|
<img src={locationIcon} alt="Location" className="w-4 h-4 opacity-80" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
|
{location}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</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
|
||||||
@@ -297,16 +213,19 @@ const MatchingList = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="text-center mb-12"
|
className="text-center mb-10"
|
||||||
>
|
>
|
||||||
<h1 className="text-[20px] text-[#00000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
<h2 className="text-[28px] font-extrabold text-gray-900 mb-1">
|
||||||
Your Matching List
|
All Matches <span className="text-green-700">List</span>
|
||||||
</h1>
|
</h2>
|
||||||
<p className="text-gray-900 text-[12px]">
|
|
||||||
Find your perfect match today
|
<p className="text-gray-500 font-medium">
|
||||||
|
We've found these profiles that match your preferences
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Swiper Container */}
|
{/* Swiper Container */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Swiper
|
<Swiper
|
||||||
@@ -341,8 +260,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 +302,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 +313,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,95 @@
|
|||||||
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';
|
||||||
|
|
||||||
|
// Custom Icons
|
||||||
|
import personIcon from "../../assets/images/personicon.svg";
|
||||||
|
import religionIcon from "../../assets/images/religonicon.svg";
|
||||||
|
import locationIcon from "../../assets/images/locationicon.svg";
|
||||||
|
import cashIcon from "../../assets/images/cashicon.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';
|
||||||
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 +97,106 @@ 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"} className={isShortlisted ? "text-black" : ""} />
|
||||||
<span className="text-[12px] font-medium">Shortlist</span>
|
{shortlistMutation.isPending ? "..." : (isShortlisted ? "Shortlisted" : "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>
|
||||||
|
|
||||||
<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
|
{/* CONTENT */}
|
||||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
<div className="px-5 py-5 -mt-8 bg-white rounded-t-[32px] relative z-[2] shadow-[0_-12px_40px_rgba(0,0,0,0.1)] h-[240px] flex flex-col">
|
||||||
style={{ background: "rgb(255, 255, 255)" }}
|
<div className="text-left mb-4">
|
||||||
>
|
<h2 className="text-[20px] font-bold text-gray-900 leading-tight">
|
||||||
<div className="flex items-center gap-4">
|
{name}
|
||||||
<div className="flex items-center gap-2">
|
</h2>
|
||||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
<div className="flex items-center justify-start gap-3 mt-1.5 text-[11px] font-semibold text-gray-400 uppercase tracking-wider">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<span>ID: {idNumber}</span>
|
||||||
Age : {profile.age}
|
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||||
</span>
|
<span className="flex items-center gap-1">
|
||||||
</div>
|
<Eye size={12} /> {lastSeen}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
|
||||||
Height: {profile.height}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="space-y-3 mt-2">
|
||||||
<div className="flex items-center gap-2">
|
{(age || height) && (
|
||||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<div className="w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||||
{profile.religion}
|
<img src={personIcon} alt="Person" className="w-4 h-4 opacity-80" />
|
||||||
</span>
|
</div>
|
||||||
</div>
|
<span className="text-[13px] font-bold text-gray-700">
|
||||||
</div>
|
{age || ""}
|
||||||
|
{age && height ? ", " : ""}
|
||||||
|
{height || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{(caste || zodiac1) && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
<div className="w-8 h-8 rounded-full bg-pink-50 flex items-center justify-center flex-shrink-0">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-80" />
|
||||||
{profile.education}
|
</div>
|
||||||
</span>
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
</div>
|
{caste}
|
||||||
</div>
|
{caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{location && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
<div className="w-8 h-8 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0">
|
||||||
<span className="text-[14px] font-600 text-gray-900">
|
<img src={locationIcon} alt="Location" className="w-4 h-4 opacity-80" />
|
||||||
{profile.location}
|
</div>
|
||||||
</span>
|
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||||
</div>
|
{location}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
)}
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
|
||||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
|
||||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M18 6L6 18M6 6l12 12"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Decline
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
|
||||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
|
||||||
transform hover:scale-105 flex items-center justify-center"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsLiked(!isLiked);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isLiked ? (
|
|
||||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
|
||||||
fill="#EF4444"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
Interest
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</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 (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
@@ -299,14 +211,16 @@ const ProfileCard = ({ profile }) => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="text-center mb-12"
|
className="text-center mb-10"
|
||||||
>
|
>
|
||||||
<h1 className="text-[20px] text-[#034E08] sm:text-[22px] lg:text-[24px] font-bold mb-3">
|
<h2 className="text-[28px] font-extrabold text-gray-900 mb-1">
|
||||||
New Joined
|
Newly <span className="text-green-700">Joined</span>
|
||||||
</h1>
|
</h2>
|
||||||
<p className="text-gray-600 text-[12px]">Find your perfect match today</p>
|
<p className="text-gray-500 font-medium">Be the first to connect with our newest members</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Swiper Container */}
|
{/* Swiper Container */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Swiper
|
<Swiper
|
||||||
@@ -319,10 +233,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 +251,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 +293,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;
|
||||||
|
|||||||
@@ -1,47 +1,75 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Crown, Bookmark } from "lucide-react";
|
import { Crown, Bookmark } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import CakeIcon from "@mui/icons-material/Cake";
|
// Custom Icons
|
||||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
import personIcon from "../../assets/images/personicon.svg";
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
import religionIcon from "../../assets/images/religonicon.svg";
|
||||||
|
import locationIcon from "../../assets/images/locationicon.svg";
|
||||||
|
import cashIcon from "../../assets/images/cashicon.svg";
|
||||||
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
import SchoolIcon from "@mui/icons-material/School";
|
||||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
|
||||||
|
|
||||||
const buildDefaultRows = (profile) => [
|
const buildDefaultRows = (profile) => {
|
||||||
[
|
const rows = [];
|
||||||
{
|
|
||||||
icon: <CakeIcon className="w-4 h-4 text-gray-700" />,
|
// Row 1: Age & Height
|
||||||
text: `Age: ${profile?.age ?? "-"}`,
|
if (profile?.age || profile?.height) {
|
||||||
},
|
const row = [];
|
||||||
{
|
if (profile?.age && profile?.age !== "-") {
|
||||||
icon: <AccessibilityNewIcon className="w-4 h-4 text-gray-700" />,
|
row.push({
|
||||||
text: `Height: ${profile?.height ?? "-"}`,
|
icon: <img src={personIcon} alt="Person" className="w-4 h-4 opacity-70" />,
|
||||||
},
|
text: `${profile.age} Yrs`,
|
||||||
],
|
});
|
||||||
[
|
}
|
||||||
{
|
if (profile?.height && profile?.height !== "-") {
|
||||||
icon: <GroupsIcon className="w-4 h-4 text-gray-700" />,
|
row.push({
|
||||||
text:
|
icon: row.length === 0 ? <img src={personIcon} alt="Person" className="w-4 h-4 opacity-70" /> : null,
|
||||||
profile?.religion ||
|
text: profile.height,
|
||||||
profile?.caste ||
|
});
|
||||||
profile?.community ||
|
}
|
||||||
"-",
|
if (row.length > 0) rows.push(row);
|
||||||
},
|
}
|
||||||
],
|
|
||||||
[
|
// Row 2: Religion / Caste
|
||||||
{
|
const religionText = profile?.religion || (profile?.religion_name ? `${profile.religion_name}${profile.caste_name ? ' / ' + profile.caste_name : ''}` : null);
|
||||||
icon: <SchoolIcon className="w-4 h-4 text-gray-700" />,
|
if (religionText && religionText !== "-") {
|
||||||
text: profile?.education || profile?.qualification || "-",
|
rows.push([{
|
||||||
},
|
icon: <img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-70" />,
|
||||||
],
|
text: religionText,
|
||||||
[
|
}]);
|
||||||
{
|
}
|
||||||
icon: <LocationOnIcon className="w-4 h-4 text-gray-700" />,
|
|
||||||
text: profile?.location || "-",
|
// Row 3: Education
|
||||||
},
|
const eduText = profile?.education || profile?.qualification || profile?.education_name;
|
||||||
],
|
if (eduText && eduText !== "-") {
|
||||||
];
|
rows.push([{
|
||||||
|
icon: <SchoolIcon sx={{ fontSize: 16 }} className="text-gray-400" />,
|
||||||
|
text: eduText,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 4: Income
|
||||||
|
const incomeText = profile?.income || profile?.annual_income || profile?.annual_income_name;
|
||||||
|
if (incomeText && incomeText !== "-") {
|
||||||
|
rows.push([{
|
||||||
|
icon: <img src={cashIcon} alt="Cash" className="w-4 h-4 opacity-70" />,
|
||||||
|
text: incomeText,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 5: Location
|
||||||
|
const locText = profile?.location || profile?.district_name;
|
||||||
|
if (locText && locText !== "-") {
|
||||||
|
rows.push([{
|
||||||
|
icon: <img src={locationIcon} alt="Location" className="w-4 h-4 opacity-70" />,
|
||||||
|
text: locText,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const getProfileIdText = (profile) =>
|
const getProfileIdText = (profile) =>
|
||||||
profile?.userId ||
|
profile?.userId ||
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import AstroChatUI from "./AstroChatUI";
|
||||||
import LazyImage from "../common/LazyImage";
|
import LazyImage from "../common/LazyImage";
|
||||||
import ProfileIcon from "../../assets/images/profileicon.png";
|
import ProfileIcon from "../../assets/images/profileicon.png";
|
||||||
import HoroscodeIcon from "../../assets/images/horoscopericon.png";
|
import HoroscodeIcon from "../../assets/images/horoscopericon.png";
|
||||||
import FamilyIcon from "../../assets/images/homeicon.png";
|
import FamilyIcon from "../../assets/images/homeicon.png";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
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 = [
|
||||||
@@ -60,7 +60,9 @@ const ProfileCompletion = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-[1400px] w-[100%] mx-auto my-10 px-2 sm:p-2 lg:p-2">
|
<div className="max-w-[1400px] w-full mx-auto my-10 px-4">
|
||||||
|
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
@@ -95,7 +97,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 +105,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"
|
||||||
/>
|
/>
|
||||||
@@ -112,67 +114,59 @@ const ProfileCompletion = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cards Section */}
|
{/* Cards Section */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 ">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||||||
|
{/* Left Side: Astro Chat */}
|
||||||
|
<div className="w-full">
|
||||||
|
<AstroChatUI />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Desktop Logo */}
|
{/* Right Side: Cards & Membership */}
|
||||||
{/* <Box sx={{ display: { xs: "none", md: "flex" }, mr: 1 }}>
|
<div className="w-full">
|
||||||
<LazyImage
|
<div className="rounded-2xl bg-green-50 border border-green-200 p-6">
|
||||||
src={Logo}
|
<motion.div
|
||||||
className="w-full h-[50px] rounded-lg object-cover"
|
variants={container}
|
||||||
/>
|
initial="hidden"
|
||||||
</Box> */}
|
animate="show"
|
||||||
|
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||||
|
>
|
||||||
|
{cards.map((card, index) => (
|
||||||
|
<div
|
||||||
|
key={card.id}
|
||||||
|
onClick={() => navigate(card.url)}
|
||||||
|
className="border 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 cursor-pointer"
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
initial={{ rotate: -180, opacity: 0 }}
|
||||||
|
animate={{ rotate: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.9 + index * 0.1, duration: 0.5 }}
|
||||||
|
>
|
||||||
|
<LazyImage
|
||||||
|
src={card.icon}
|
||||||
|
alt={card.title}
|
||||||
|
className="w-full h-full"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
<AstroChatUI/>
|
<motion.h3
|
||||||
<div className="my-4 rounded-2xl bg-green-50 border border-1 border-green-200 p-4 ">
|
initial={{ opacity: 0, x: -10 }}
|
||||||
<motion.div
|
animate={{ opacity: 1, x: 0 }}
|
||||||
variants={container}
|
transition={{ delay: 1 + index * 0.1, duration: 0.4 }}
|
||||||
initial="hidden"
|
className="text-[14px] sm:text-[16px] font-semibold text-gray-900"
|
||||||
animate="show"
|
>
|
||||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
{card.title}
|
||||||
>
|
</motion.h3>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
{cards.map((card, index) => (
|
<div className="mt-6">
|
||||||
|
<MembershipCard becomePaidMember={becomePaidMember} />
|
||||||
<div
|
|
||||||
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
|
|
||||||
cursor-pointer ">
|
|
||||||
{/* Icon Container */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ rotate: -180, opacity: 0 }}
|
|
||||||
animate={{ rotate: 0, opacity: 1 }}
|
|
||||||
transition={{ delay: 0.9 + index * 0.1, duration: 0.5 }}
|
|
||||||
// className={`${card.bgColor} p-0 rounded-xl border ${card.borderColor}`}
|
|
||||||
>
|
|
||||||
<LazyImage
|
|
||||||
src={card.icon}
|
|
||||||
alt={card.title}
|
|
||||||
className="w-ful h-full "
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Text */}
|
|
||||||
<motion.h3
|
|
||||||
initial={{ opacity: 0, x: -10 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: 1 + index * 0.1, duration: 0.4 }}
|
|
||||||
className="text-[14px] sm:text-[16px] font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
{card.title}
|
|
||||||
</motion.h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<MembershipCard/>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Additional Info Section */}
|
{/* Additional Info Section */}
|
||||||
{/* <motion.div
|
{/* <motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -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,40 @@ 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='max-w-3xl mx-auto mb-8 pt-4'>
|
||||||
|
{/* Partner Preferences Section */}
|
||||||
{/* Basic Preferences Section */}
|
<div className="bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-100">
|
||||||
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
|
<div className="flex items-center justify-between bg-[#f5fbff] py-4 px-6 border-b border-gray-100">
|
||||||
<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">Partner Preference Details</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, ...religiousPreferences].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 className="space-y-1 p-6">
|
|
||||||
{basicPreferences.map((pref, index) => (
|
|
||||||
<PreferenceItem key={index} {...pref} />
|
|
||||||
))}
|
|
||||||
</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,251 @@
|
|||||||
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
|
// Custom Icons
|
||||||
import Profile1 from "../../assets/images/bride1.jpg";
|
import personIcon from "../../assets/images/personicon.svg";
|
||||||
import Profile2 from "../../assets/images/bride2.jpg";
|
import religionIcon from "../../assets/images/religonicon.svg";
|
||||||
import Profile3 from "../../assets/images/bride3.jpg";
|
import locationIcon from "../../assets/images/locationicon.svg";
|
||||||
import Profile4 from "../../assets/images/bride4.jpg";
|
import cashIcon from "../../assets/images/cashicon.svg";
|
||||||
|
import SchoolIcon from "@mui/icons-material/School";
|
||||||
|
|
||||||
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
|
import { Navigation, Pagination } from "swiper/modules";
|
||||||
|
import "swiper/css";
|
||||||
|
import "swiper/css/navigation";
|
||||||
|
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";
|
||||||
|
|
||||||
|
|
||||||
export default function ProfileCard() {
|
const ProfileCardItem = ({ profile }) => {
|
||||||
// Sample data for multiple cards with image paths
|
const navigate = useNavigate();
|
||||||
const profiles = [
|
const queryClient = useQueryClient();
|
||||||
{
|
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
|
||||||
id: 1,
|
|
||||||
name: "Jerome Bell",
|
useEffect(() => {
|
||||||
idNumber: "KI2847596",
|
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||||
lastSeen: "4 Nov 2025",
|
}, [profile?.is_shortlisted]);
|
||||||
salary: "5-10",
|
|
||||||
age: "22 yrs",
|
const shortlistMutation = useMutation({
|
||||||
height: "5'2\"",
|
mutationFn: shortlistProfile,
|
||||||
location: "Chennai",
|
onMutate: () => {
|
||||||
caste: "Brahmin",
|
setIsShortlisted((prev) => !prev);
|
||||||
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-col gap-2 mt-4 min-h-[140px]">
|
||||||
|
{(age || height) && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img src={personIcon} alt="Person" className="w-5 h-5 opacity-70" />
|
||||||
|
<span className="text-[14px] font-medium text-gray-700">
|
||||||
|
{age || ""}
|
||||||
|
{age && height ? ", " : ""}
|
||||||
|
{height || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(caste || zodiac1) && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img src={religionIcon} alt="Religion" className="w-5 h-5 opacity-70" />
|
||||||
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
|
{caste}
|
||||||
|
{caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{salary && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img src={cashIcon} alt="Cash" className="w-5 h-5 opacity-70" />
|
||||||
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
|
{salary}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{location && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img src={locationIcon} alt="Location" className="w-5 h-5 opacity-70" />
|
||||||
|
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||||
|
{location}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</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 +262,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,408 @@ 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]: "" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
const newErrors = {};
|
||||||
|
if (!data.study_field) newErrors.study_field = "Field of study is required";
|
||||||
|
if (!data.education) newErrors.education = "Highest qualification is required";
|
||||||
|
if (!data.education_detail) newErrors.education_detail = "Education detail is required";
|
||||||
|
if (!data.employee_type) newErrors.employee_type = "Employee type is required";
|
||||||
|
|
||||||
|
if (!isUnemployed) {
|
||||||
|
if (!data.occupation) newErrors.occupation = "Occupation is required";
|
||||||
|
if (!data.occupation_detail) newErrors.occupation_detail = "Occupation detail is required";
|
||||||
|
if (!data.income_currency) newErrors.income_currency = "Income currency is required";
|
||||||
|
if (!data.annual_income) newErrors.annual_income = "Annual income is required";
|
||||||
|
if (!data.work_country) newErrors.work_country = "Work country is required";
|
||||||
|
|
||||||
|
if (isIndia) {
|
||||||
|
if (!data.work_state) newErrors.work_state = "Work state is required";
|
||||||
|
if (!data.work_district) newErrors.work_district = "Work city is required";
|
||||||
|
} else {
|
||||||
|
if (!data.work_city) newErrors.work_city = "Work city is required";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.address) newErrors.address = "Work address is 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+280
-157
@@ -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,16 +81,40 @@ 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));
|
||||||
@@ -96,12 +129,34 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
|||||||
if (onFieldChange) onFieldChange(type);
|
if (onFieldChange) onFieldChange(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
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 +169,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 +260,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 +282,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 +310,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 +322,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 +338,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 +368,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>
|
||||||
);
|
);
|
||||||
|
|||||||
+305
-238
@@ -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,52 +11,98 @@ 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, DatePicker, TimePicker } from "@mui/x-date-pickers";
|
||||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
|
||||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
|
||||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||||
|
import { format } from "date-fns";
|
||||||
import { useLifestyleMasters } from "../hooks/useMasters";
|
import { useLifestyleMasters } from "../hooks/useMasters";
|
||||||
|
import { useStarMasters, usePathamMasters } from "../hooks/useDependentMasters";
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||||
|
|
||||||
const { data: lifestyleMasters, isLoading: isLifestyleMastersLoading } =
|
const { data: lifestyleMasters, isLoading: isLifestyleMastersLoading } =
|
||||||
useLifestyleMasters();
|
useLifestyleMasters();
|
||||||
|
|
||||||
const dietOptions = useMemo(() => {
|
const raasiOptions = useMemo(() => {
|
||||||
const raw = lifestyleMasters;
|
const raw = lifestyleMasters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
return raw.raasis || raw.raasi || raw.rasi || [];
|
||||||
return raw.diet || raw.diets || [];
|
|
||||||
}, [lifestyleMasters]);
|
}, [lifestyleMasters]);
|
||||||
|
|
||||||
const hobbyOptions = useMemo(() => {
|
const panjangamOptions = useMemo(() => {
|
||||||
const raw = lifestyleMasters;
|
const raw = lifestyleMasters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
return raw.panjangam_types || raw.panjangamTypes || [];
|
||||||
return raw.hobbies || raw.hobby || [];
|
|
||||||
}, [lifestyleMasters]);
|
}, [lifestyleMasters]);
|
||||||
|
|
||||||
|
const { data: starData } = useStarMasters(data.raasi);
|
||||||
|
const starOptions = useMemo(() => {
|
||||||
|
if (!starData) return [];
|
||||||
|
return starData.stars || starData.star || starData.data || (Array.isArray(starData) ? starData : []);
|
||||||
|
}, [starData]);
|
||||||
|
|
||||||
|
const { data: pathamData } = usePathamMasters(data.star);
|
||||||
|
const pathamOptions = useMemo(() => {
|
||||||
|
if (!pathamData) return [];
|
||||||
|
return (
|
||||||
|
pathamData.pathams ||
|
||||||
|
pathamData.patham ||
|
||||||
|
pathamData.data ||
|
||||||
|
(Array.isArray(pathamData) ? pathamData : [])
|
||||||
|
);
|
||||||
|
}, [pathamData]);
|
||||||
|
|
||||||
const grahaOptions = useMemo(() => {
|
const grahaOptions = useMemo(() => {
|
||||||
const raw = lifestyleMasters;
|
const raw = lifestyleMasters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
|
||||||
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();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data.dob) {
|
||||||
|
const date = new Date(data.dob);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
const day = format(date, "EEEE");
|
||||||
|
if (data.dayOfBirth !== day) {
|
||||||
|
handleChange("dayOfBirth", day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data.dob]);
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||||
if (onFieldChange) onFieldChange(field);
|
if (onFieldChange) onFieldChange(field);
|
||||||
@@ -68,23 +114,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) => {
|
||||||
@@ -117,7 +214,8 @@ const LifestyleDetailsForm = ({
|
|||||||
return `${h}:${m}`;
|
return `${h}:${m}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 +228,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 } },
|
||||||
@@ -138,22 +246,21 @@ const LifestyleDetailsForm = ({
|
|||||||
>
|
>
|
||||||
{grahaOptions.map((opt) => (
|
{grahaOptions.map((opt) => (
|
||||||
<MenuItem key={opt} value={opt}>
|
<MenuItem key={opt} value={opt}>
|
||||||
<Checkbox checked={value.indexOf(opt) > -1} />
|
<Checkbox checked={Array.isArray(value) && value.indexOf(opt) > -1} />
|
||||||
<ListItemText primary={opt} />
|
<ListItemText primary={opt} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</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 +272,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))}
|
||||||
|
|
||||||
@@ -183,132 +292,13 @@ const LifestyleDetailsForm = ({
|
|||||||
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="flex flex-col gap-2">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Diet (Multi-select){requiredMark}
|
|
||||||
</label>
|
|
||||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
|
||||||
<InputLabel id="diets-label">Select Diet</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="diets-label"
|
|
||||||
label="Select Diet"
|
|
||||||
name="diets"
|
|
||||||
multiple
|
|
||||||
value={data.diets}
|
|
||||||
onChange={(e) => handleMultiChange("diets", e.target.value)}
|
|
||||||
inputRef={inputRef}
|
|
||||||
disabled={isLifestyleMastersLoading}
|
|
||||||
renderValue={(selected) =>
|
|
||||||
selected
|
|
||||||
.map((id) => {
|
|
||||||
const item = dietOptions.find(
|
|
||||||
(opt) => (opt.id ?? opt) === id
|
|
||||||
);
|
|
||||||
return item?.diet_name || item?.name || id;
|
|
||||||
})
|
|
||||||
.join(", ")
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{dietOptions.map((opt) => {
|
|
||||||
const value = opt.id ?? opt;
|
|
||||||
const label = opt.diet_name || opt.name || String(opt);
|
|
||||||
return (
|
|
||||||
<MenuItem key={value} value={value}>
|
|
||||||
<Checkbox checked={data.diets.indexOf(value) > -1} />
|
|
||||||
<ListItemText primary={label} />
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
{errors.diets && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.diets}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Hobbies & Interests (Multi-select){requiredMark}
|
|
||||||
</label>
|
|
||||||
<FormControl
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
error={Boolean(errors.hobbies)}
|
|
||||||
>
|
|
||||||
<InputLabel id="hobbies-label">
|
|
||||||
Select Hobbies & Interests
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="hobbies-label"
|
|
||||||
label="Select Hobbies & Interests"
|
|
||||||
name="hobbies"
|
|
||||||
multiple
|
|
||||||
value={data.hobbies}
|
|
||||||
onChange={(e) => handleMultiChange("hobbies", e.target.value)}
|
|
||||||
disabled={isLifestyleMastersLoading}
|
|
||||||
renderValue={(selected) =>
|
|
||||||
selected
|
|
||||||
.map((id) => {
|
|
||||||
const item = hobbyOptions.find(
|
|
||||||
(opt) => (opt.id ?? opt) === id
|
|
||||||
);
|
|
||||||
return item?.hobby_name || item?.name || id;
|
|
||||||
})
|
|
||||||
.join(", ")
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hobbyOptions.map((opt) => {
|
|
||||||
const value = opt.id ?? opt;
|
|
||||||
const label = opt.hobby_name || opt.name || String(opt);
|
|
||||||
return (
|
|
||||||
<MenuItem key={value} value={value}>
|
|
||||||
<Checkbox checked={data.hobbies.indexOf(value) > -1} />
|
|
||||||
<ListItemText primary={label} />
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
{errors.hobbies && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.hobbies}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center py-4">
|
<div className="text-center py-4">
|
||||||
<h2 className="text-[18px] font-semibold text-gray-800">
|
<h2 className="text-[18px] font-semibold text-gray-800">
|
||||||
Astrology / Horoscope
|
Astrology / Horoscope
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[900px] mx-auto">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 w-full max-w-[1200px] mx-auto">
|
||||||
<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]">
|
||||||
Date of Birth{requiredMark}
|
Date of Birth{requiredMark}
|
||||||
@@ -331,6 +321,19 @@ const LifestyleDetailsForm = ({
|
|||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Day of Birth</label>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
name="dayOfBirth"
|
||||||
|
value={data.dayOfBirth}
|
||||||
|
readOnly
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Day of Week"
|
||||||
|
InputProps={{ readOnly: true }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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]">
|
||||||
Time of Birth{requiredMark}
|
Time of Birth{requiredMark}
|
||||||
@@ -352,7 +355,7 @@ const LifestyleDetailsForm = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label className="text-gray-900 text-[15px]">Place of Birth</label>
|
<label className="text-gray-900 text-[15px]">Place of Birth{requiredMark}</label>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
name="placeOfBirth"
|
name="placeOfBirth"
|
||||||
@@ -366,32 +369,186 @@ const LifestyleDetailsForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[950px] mx-auto mt-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[1200px] mx-auto mt-6">
|
||||||
<div>
|
<div className="flex flex-col gap-2">
|
||||||
<div className="py-4">
|
<label className="text-gray-900 text-[15px]">Select Raasi{requiredMark}</label>
|
||||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.raasi)} id="raasi">
|
||||||
Add Rasi
|
<InputLabel id="raasi-label">Select Raasi</InputLabel>
|
||||||
</h3>
|
<Select
|
||||||
{renderChartGrid("graha")}
|
labelId="raasi-label"
|
||||||
{errors.graha_duplicate && (
|
label="Select Raasi"
|
||||||
<p style={{ color: "#d32f2f", fontSize: "0.75rem", marginTop: "4px" }}>
|
name="raasi"
|
||||||
{errors.graha_duplicate}
|
value={data.raasi}
|
||||||
</p>
|
onChange={(e) => {
|
||||||
)}
|
handleChange("raasi", e.target.value);
|
||||||
</div>
|
handleChange("star", "");
|
||||||
|
handleChange("patham", "");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{raasiOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>
|
||||||
|
{opt.raasi_name || opt.rasi_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="flex flex-col gap-2">
|
||||||
<div className="py-4">
|
<label className="text-gray-900 text-[15px]">Select Birth Star{requiredMark}</label>
|
||||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.star)} id="star">
|
||||||
Add Navamsam
|
<InputLabel id="star-label">Select Birth Star</InputLabel>
|
||||||
</h3>
|
<Select
|
||||||
{renderChartGrid("amsam")}
|
labelId="star-label"
|
||||||
{errors.amsam_duplicate && (
|
label="Select Birth Star"
|
||||||
<p style={{ color: "#d32f2f", fontSize: "0.75rem", marginTop: "4px" }}>
|
name="star"
|
||||||
{errors.amsam_duplicate}
|
value={data.star}
|
||||||
</p>
|
onChange={(e) => {
|
||||||
)}
|
handleChange("star", e.target.value);
|
||||||
|
handleChange("patham", "");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{starOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>
|
||||||
|
{opt.star_name || opt.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Select Padham</label>
|
||||||
|
<FormControl fullWidth variant="outlined" id="patham">
|
||||||
|
<InputLabel id="patham-label">Select Padham</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="patham-label"
|
||||||
|
label="Select Padham"
|
||||||
|
name="patham"
|
||||||
|
value={data.patham}
|
||||||
|
onChange={(e) => handleChange("patham", e.target.value)}
|
||||||
|
>
|
||||||
|
{pathamOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt} value={opt}>
|
||||||
|
{opt}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Select Lagnam</label>
|
||||||
|
<FormControl fullWidth variant="outlined" id="lagnam">
|
||||||
|
<InputLabel id="lagnam-label">Select Lagnam</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="lagnam-label"
|
||||||
|
label="Select Lagnam"
|
||||||
|
name="lagnam"
|
||||||
|
value={data.lagnam}
|
||||||
|
onChange={(e) => handleChange("lagnam", e.target.value)}
|
||||||
|
>
|
||||||
|
{raasiOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt.id} value={opt.id}>
|
||||||
|
{opt.raasi_name || opt.rasi_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Panjangam Type</label>
|
||||||
|
<FormControl fullWidth variant="outlined" id="panjangam_type">
|
||||||
|
<InputLabel id="panjangam-label">Select Panjangam Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="panjangam-label"
|
||||||
|
label="Select Panjangam Type"
|
||||||
|
name="panjangam_type"
|
||||||
|
value={data.panjangam_type}
|
||||||
|
onChange={(e) => handleChange("panjangam_type", e.target.value)}
|
||||||
|
>
|
||||||
|
{panjangamOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt} value={opt}>
|
||||||
|
{opt}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[1200px] mx-auto mt-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h3 className="text-base font-semibold text-gray-800 mb-3">Add Rasi</h3>
|
||||||
|
{renderChartGrid("graha")}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h3 className="text-base font-semibold text-gray-800 mb-3">Add Navamsam</h3>
|
||||||
|
{renderChartGrid("amsam")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 w-full max-w-[1200px] mx-auto p-6 border border-gray-200 rounded-lg bg-gray-50">
|
||||||
|
<h3 className="text-base font-semibold text-gray-800 mb-4">Dasa Details</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Dasa Type</label>
|
||||||
|
<FormControl fullWidth variant="outlined" id="dasa_balance">
|
||||||
|
<InputLabel id="dasa-label">Select Dasa Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="dasa-label"
|
||||||
|
label="Select Dasa Type"
|
||||||
|
name="dasa_balance"
|
||||||
|
value={data.dasa_balance}
|
||||||
|
onChange={(e) => handleChange("dasa_balance", e.target.value)}
|
||||||
|
>
|
||||||
|
{grahaOptions.map((opt) => (
|
||||||
|
<MenuItem key={opt} value={opt}>
|
||||||
|
{opt}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Dasa Year</label>
|
||||||
|
<TextField
|
||||||
|
id="dasa_years"
|
||||||
|
fullWidth
|
||||||
|
name="dasa_years"
|
||||||
|
value={data.dasa_years}
|
||||||
|
onChange={(e) => handleChange("dasa_years", e.target.value)}
|
||||||
|
placeholder="Enter Year"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Dasa Month</label>
|
||||||
|
<TextField
|
||||||
|
id="dasa_months"
|
||||||
|
fullWidth
|
||||||
|
name="dasa_months"
|
||||||
|
value={data.dasa_months}
|
||||||
|
onChange={(e) => handleChange("dasa_months", e.target.value)}
|
||||||
|
placeholder="Enter Month"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Dasa Day</label>
|
||||||
|
<TextField
|
||||||
|
id="dasa_days"
|
||||||
|
fullWidth
|
||||||
|
name="dasa_days"
|
||||||
|
value={data.dasa_days}
|
||||||
|
onChange={(e) => handleChange("dasa_days", e.target.value)}
|
||||||
|
placeholder="Enter Day"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -406,13 +563,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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { usePartnerPreferenceMasters } from "../hooks/useMasters";
|
import { usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||||
import { useCityMasters, useSubCasteMasters } from "../hooks/useDependentMasters";
|
import { useCityMasters, useSubCasteMasters } from "../hooks/useDependentMasters";
|
||||||
@@ -30,11 +31,30 @@ const PartnerPreferencesForm = ({
|
|||||||
const subCasteQuery = useSubCasteMasters(data.castes);
|
const subCasteQuery = useSubCasteMasters(data.castes);
|
||||||
const cityQuery = useCityMasters(data.states);
|
const cityQuery = useCityMasters(data.states);
|
||||||
|
|
||||||
const ageRangeOptions = useMemo(() => {
|
const ageOptions = useMemo(() => Array.from({ length: 53 }, (_, i) => i + 18), []);
|
||||||
|
|
||||||
|
const heightOptions = useMemo(() => {
|
||||||
const raw = masters;
|
const raw = masters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
return raw.heights || raw.height || [];
|
||||||
return raw.ageRange || raw.age_range || [];
|
}, [masters]);
|
||||||
|
|
||||||
|
const maritalStatusOptions = useMemo(() => {
|
||||||
|
const raw = masters;
|
||||||
|
if (!raw) return [];
|
||||||
|
return raw.maritalStatus || raw.marital_status || [];
|
||||||
|
}, [masters]);
|
||||||
|
|
||||||
|
const starOptions = useMemo(() => {
|
||||||
|
const raw = masters;
|
||||||
|
if (!raw) return [];
|
||||||
|
return raw.stars || raw.star || [];
|
||||||
|
}, [masters]);
|
||||||
|
|
||||||
|
const employeeTypeOptions = useMemo(() => {
|
||||||
|
const raw = masters;
|
||||||
|
if (!raw) return [];
|
||||||
|
return raw.employeeTypes || raw.employee_type || [];
|
||||||
}, [masters]);
|
}, [masters]);
|
||||||
|
|
||||||
const casteOptions = useMemo(() => {
|
const casteOptions = useMemo(() => {
|
||||||
@@ -72,6 +92,8 @@ const PartnerPreferencesForm = ({
|
|||||||
return raw.annual_income || raw.annualIncome || [];
|
return raw.annual_income || raw.annualIncome || [];
|
||||||
}, [masters]);
|
}, [masters]);
|
||||||
|
|
||||||
|
const currencyOptions = useMemo(() => ["INR", "USD"], []);
|
||||||
|
|
||||||
const stateOptions = useMemo(() => {
|
const stateOptions = useMemo(() => {
|
||||||
const raw = masters;
|
const raw = masters;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
@@ -96,8 +118,15 @@ const PartnerPreferencesForm = ({
|
|||||||
const cityOptions = useMemo(() => {
|
const cityOptions = useMemo(() => {
|
||||||
const raw = cityQuery.data;
|
const raw = cityQuery.data;
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
if (Array.isArray(raw)) return raw;
|
if (Array.isArray(raw)) {
|
||||||
return raw.subCaste || raw.district || raw.data || [];
|
const merged = raw.flatMap((entry) => {
|
||||||
|
if (!entry) return [];
|
||||||
|
if (Array.isArray(entry)) return entry;
|
||||||
|
return entry.district || entry.districts || entry.data || [];
|
||||||
|
});
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
return raw.district || raw.districts || raw.data || [];
|
||||||
}, [cityQuery.data]);
|
}, [cityQuery.data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -107,12 +136,15 @@ const PartnerPreferencesForm = ({
|
|||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
const arrayFields = new Set([
|
const arrayFields = new Set([
|
||||||
"castes",
|
"castes",
|
||||||
"subCastes",
|
"sub_castes",
|
||||||
"occupations",
|
"occupations",
|
||||||
"educations",
|
"educations",
|
||||||
"hobbies",
|
|
||||||
"states",
|
"states",
|
||||||
"districts",
|
"districts",
|
||||||
|
"marital_statuses",
|
||||||
|
"birth_stars",
|
||||||
|
"employee_types",
|
||||||
|
"currencies",
|
||||||
]);
|
]);
|
||||||
const nextValue =
|
const nextValue =
|
||||||
arrayFields.has(field) && typeof value === "string"
|
arrayFields.has(field) && typeof value === "string"
|
||||||
@@ -122,8 +154,8 @@ const PartnerPreferencesForm = ({
|
|||||||
const fieldsToClear = [field];
|
const fieldsToClear = [field];
|
||||||
|
|
||||||
if (field === "castes") {
|
if (field === "castes") {
|
||||||
updates.subCastes = [];
|
updates.sub_castes = [];
|
||||||
fieldsToClear.push("subCastes");
|
fieldsToClear.push("sub_castes");
|
||||||
}
|
}
|
||||||
if (field === "states") {
|
if (field === "states") {
|
||||||
updates.districts = [];
|
updates.districts = [];
|
||||||
@@ -201,215 +233,297 @@ const PartnerPreferencesForm = ({
|
|||||||
<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">
|
||||||
{/* Age Range */}
|
{/* 1. Age Range */}
|
||||||
<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]">Age Range (From - To)</label>
|
||||||
Age Range{requiredMark}
|
<div className="flex gap-2">
|
||||||
</label>
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.age_from)}>
|
||||||
<FormControl
|
<InputLabel>From Age</InputLabel>
|
||||||
fullWidth
|
<Select
|
||||||
variant="outlined"
|
id="age_from"
|
||||||
error={Boolean(errors.ageRange)}
|
name="age_from"
|
||||||
>
|
value={data.age_from}
|
||||||
<InputLabel id="ageRange-label">Select Age Range</InputLabel>
|
label="From Age"
|
||||||
<Select
|
inputRef={inputRef}
|
||||||
labelId="ageRange-label"
|
onChange={(e) => handleChange("age_from", e.target.value)}
|
||||||
label="Select Age Range"
|
|
||||||
name="ageRange"
|
|
||||||
value={data.ageRange}
|
|
||||||
onChange={(e) => handleChange("ageRange", e.target.value)}
|
|
||||||
inputRef={inputRef}
|
|
||||||
disabled={isPartnerMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ageRangeOptions.map((opt) => (
|
|
||||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
|
||||||
{opt.name || opt}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.ageRange && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{errors.ageRange}
|
{ageOptions.map((age) => (
|
||||||
</p>
|
<MenuItem key={age} value={age}>{age}</MenuItem>
|
||||||
)}
|
))}
|
||||||
</FormControl>
|
</Select>
|
||||||
|
{errors.age_from && (
|
||||||
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||||
|
{errors.age_from}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.age_to)}>
|
||||||
|
<InputLabel>To Age</InputLabel>
|
||||||
|
<Select
|
||||||
|
id="age_to"
|
||||||
|
name="age_to"
|
||||||
|
value={data.age_to}
|
||||||
|
label="To Age"
|
||||||
|
onChange={(e) => handleChange("age_to", e.target.value)}
|
||||||
|
>
|
||||||
|
{ageOptions.map((age) => (
|
||||||
|
<MenuItem key={age} value={age}>{age}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{errors.age_to && (
|
||||||
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||||
|
{errors.age_to}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Caste */}
|
{/* 2. Height Range */}
|
||||||
<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]">Height Range (From - To)</label>
|
||||||
Caste{requiredMark}
|
<div className="flex gap-2">
|
||||||
</label>
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.height_from)}>
|
||||||
|
<InputLabel>From Height</InputLabel>
|
||||||
|
<Select
|
||||||
|
id="height_from"
|
||||||
|
name="height_from"
|
||||||
|
value={data.height_from}
|
||||||
|
label="From Height"
|
||||||
|
onChange={(e) => handleChange("height_from", e.target.value)}
|
||||||
|
>
|
||||||
|
{heightOptions.map((h) => (
|
||||||
|
<MenuItem key={h.id} value={h.id}>{h.height_text}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{errors.height_from && (
|
||||||
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||||
|
{errors.height_from}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.height_to)}>
|
||||||
|
<InputLabel>To Height</InputLabel>
|
||||||
|
<Select
|
||||||
|
id="height_to"
|
||||||
|
name="height_to"
|
||||||
|
value={data.height_to}
|
||||||
|
label="To Height"
|
||||||
|
onChange={(e) => handleChange("height_to", e.target.value)}
|
||||||
|
>
|
||||||
|
{heightOptions.map((h) => (
|
||||||
|
<MenuItem key={h.id} value={h.id}>{h.height_text}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{errors.height_to && (
|
||||||
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||||
|
{errors.height_to}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3. Marital Status */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Marital Status</label>
|
||||||
|
{renderMultiSelect({
|
||||||
|
name: "marital_statuses",
|
||||||
|
label: "Marital Status",
|
||||||
|
options: maritalStatusOptions,
|
||||||
|
value: data.marital_statuses,
|
||||||
|
getLabel: (opt) => opt.marital_status_name || opt.name,
|
||||||
|
getValue: (opt) => opt.id,
|
||||||
|
disabled: isPartnerMastersLoading,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 4. Birth Star */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Birth Star</label>
|
||||||
|
{renderMultiSelect({
|
||||||
|
name: "birth_stars",
|
||||||
|
label: "Birth Star",
|
||||||
|
options: starOptions,
|
||||||
|
value: data.birth_stars,
|
||||||
|
getLabel: (opt) => opt.star_name || opt.name,
|
||||||
|
getValue: (opt) => opt.id,
|
||||||
|
disabled: isPartnerMastersLoading,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 5. Caste */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Caste</label>
|
||||||
{renderMultiSelect({
|
{renderMultiSelect({
|
||||||
name: "castes",
|
name: "castes",
|
||||||
label: "Caste",
|
label: "Caste",
|
||||||
options: casteOptions,
|
options: casteOptions,
|
||||||
value: data.castes,
|
value: data.castes,
|
||||||
getLabel: (opt) => opt.caste_name || opt.name || String(opt),
|
getLabel: (opt) => opt.caste_name || opt.name,
|
||||||
getValue: (opt) => opt.id ?? opt,
|
getValue: (opt) => opt.id,
|
||||||
disabled: isPartnerMastersLoading,
|
disabled: isPartnerMastersLoading,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sub Caste */}
|
{/* 6. Sub-Sect */}
|
||||||
<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]">Sub-Sect</label>
|
||||||
Sub Caste{requiredMark}
|
|
||||||
</label>
|
|
||||||
{renderMultiSelect({
|
{renderMultiSelect({
|
||||||
name: "subCastes",
|
name: "sub_castes",
|
||||||
label: "Sub Caste",
|
label: "Sub-Sect",
|
||||||
options: subCasteOptions,
|
options: subCasteOptions,
|
||||||
value: data.subCastes,
|
value: data.sub_castes,
|
||||||
getLabel: (opt) =>
|
getLabel: (opt) => opt.sub_caste_name || opt.name,
|
||||||
opt.sub_caste_name || opt.subCaste_name || opt.name || String(opt),
|
getValue: (opt) => opt.id,
|
||||||
getValue: (opt) => opt.id ?? opt,
|
|
||||||
disabled: data.castes.length === 0 || subCasteQuery.isLoading,
|
disabled: data.castes.length === 0 || subCasteQuery.isLoading,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Occupation */}
|
{/* 7. Qualification */}
|
||||||
<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]">Qualification</label>
|
||||||
Occupation{requiredMark}
|
|
||||||
</label>
|
|
||||||
{renderMultiSelect({
|
|
||||||
name: "occupations",
|
|
||||||
label: "Occupation",
|
|
||||||
options: occupationOptions,
|
|
||||||
value: data.occupations,
|
|
||||||
getLabel: (opt) => opt.occupation_name || opt.name || String(opt),
|
|
||||||
getValue: (opt) => opt.id ?? opt,
|
|
||||||
disabled: isPartnerMastersLoading,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Qualification */}
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<label className="text-gray-900 text-[15px]">
|
|
||||||
Qualification{requiredMark}
|
|
||||||
</label>
|
|
||||||
{renderMultiSelect({
|
{renderMultiSelect({
|
||||||
name: "educations",
|
name: "educations",
|
||||||
label: "Qualification",
|
label: "Qualification",
|
||||||
options: educationOptions,
|
options: educationOptions,
|
||||||
value: data.educations,
|
value: data.educations,
|
||||||
getLabel: (opt) => opt.education_name || opt.name || String(opt),
|
getLabel: (opt) => opt.education_name || opt.name,
|
||||||
getValue: (opt) => opt.id ?? opt,
|
getValue: (opt) => opt.id,
|
||||||
disabled: isPartnerMastersLoading,
|
disabled: isPartnerMastersLoading,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lifestyle and Hobbies */}
|
{/* 8. Occupation */}
|
||||||
<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]">Occupation</label>
|
||||||
Lifestyle & Hobbies{requiredMark}
|
|
||||||
</label>
|
|
||||||
{renderMultiSelect({
|
{renderMultiSelect({
|
||||||
name: "hobbies",
|
name: "occupations",
|
||||||
label: "Lifestyle & Hobbies",
|
label: "Occupation",
|
||||||
options: hobbyOptions,
|
options: occupationOptions,
|
||||||
value: data.hobbies,
|
value: data.occupations,
|
||||||
getLabel: (opt) => opt.hobby_name || opt.name || String(opt),
|
getLabel: (opt) => opt.occupation_name || opt.name,
|
||||||
getValue: (opt) => opt.id ?? opt,
|
getValue: (opt) => opt.id,
|
||||||
disabled: isPartnerMastersLoading,
|
disabled: isPartnerMastersLoading,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Annual Income */}
|
{/* 9. Employee Type */}
|
||||||
<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]">Employee Type</label>
|
||||||
Annual Income{requiredMark}
|
{renderMultiSelect({
|
||||||
</label>
|
name: "employee_types",
|
||||||
<FormControl
|
label: "Employee Type",
|
||||||
fullWidth
|
options: employeeTypeOptions,
|
||||||
variant="outlined"
|
value: data.employee_types,
|
||||||
error={Boolean(errors.annualIncome)}
|
getLabel: (opt) => opt.employee_type_name || opt.name,
|
||||||
>
|
getValue: (opt) => opt.id,
|
||||||
<InputLabel id="annualIncome-label">
|
disabled: isPartnerMastersLoading,
|
||||||
Select Annual Income
|
})}
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="annualIncome-label"
|
|
||||||
label="Select Annual Income"
|
|
||||||
name="annualIncome"
|
|
||||||
value={data.annualIncome}
|
|
||||||
onChange={(e) => handleChange("annualIncome", e.target.value)}
|
|
||||||
disabled={isPartnerMastersLoading}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.Mui-disabled": {
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{annualIncomeOptions.map((opt) => (
|
|
||||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
|
||||||
{opt.annual_income_name || opt.name || opt}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
{errors.annualIncome && (
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#d32f2f",
|
|
||||||
margin: "3px 14px 0 14px",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.annualIncome}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* State */}
|
{/* 10. Currency Type */}
|
||||||
<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]">Income Currency Type</label>
|
||||||
State{requiredMark}
|
{renderMultiSelect({
|
||||||
</label>
|
name: "currencies",
|
||||||
|
label: "Currency",
|
||||||
|
options: currencyOptions,
|
||||||
|
value: data.currencies,
|
||||||
|
getLabel: (opt) => opt,
|
||||||
|
getValue: (opt) => opt,
|
||||||
|
disabled: isPartnerMastersLoading,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 11. Income Range */}
|
||||||
|
{data.currencies.includes("INR") && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Annual Income (INR Range)</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<TextField
|
||||||
|
id="inr_from"
|
||||||
|
name="inr_from"
|
||||||
|
fullWidth
|
||||||
|
label="From (INR)"
|
||||||
|
value={data.inr_from}
|
||||||
|
error={Boolean(errors.inr_from)}
|
||||||
|
helperText={errors.inr_from}
|
||||||
|
onChange={(e) => handleChange("inr_from", e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="inr_to"
|
||||||
|
name="inr_to"
|
||||||
|
fullWidth
|
||||||
|
label="To (INR)"
|
||||||
|
value={data.inr_to}
|
||||||
|
error={Boolean(errors.inr_to)}
|
||||||
|
helperText={errors.inr_to}
|
||||||
|
onChange={(e) => handleChange("inr_to", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data.currencies.includes("USD") && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">Annual Income (USD Range)</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<TextField
|
||||||
|
id="usd_from"
|
||||||
|
name="usd_from"
|
||||||
|
fullWidth
|
||||||
|
label="From (USD)"
|
||||||
|
value={data.usd_from}
|
||||||
|
error={Boolean(errors.usd_from)}
|
||||||
|
helperText={errors.usd_from}
|
||||||
|
onChange={(e) => handleChange("usd_from", e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="usd_to"
|
||||||
|
name="usd_to"
|
||||||
|
fullWidth
|
||||||
|
label="To (USD)"
|
||||||
|
value={data.usd_to}
|
||||||
|
error={Boolean(errors.usd_to)}
|
||||||
|
helperText={errors.usd_to}
|
||||||
|
onChange={(e) => handleChange("usd_to", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 12. State */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-gray-900 text-[15px]">State</label>
|
||||||
{renderMultiSelect({
|
{renderMultiSelect({
|
||||||
name: "states",
|
name: "states",
|
||||||
label: "State",
|
label: "State",
|
||||||
options: stateOptions,
|
options: stateOptions,
|
||||||
value: data.states,
|
value: data.states,
|
||||||
getLabel: (opt) => opt.state_name || opt.name || String(opt),
|
getLabel: (opt) => opt.state_name || opt.name,
|
||||||
getValue: (opt) => opt.id ?? opt,
|
getValue: (opt) => opt.id,
|
||||||
disabled: isPartnerMastersLoading,
|
disabled: isPartnerMastersLoading,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* City */}
|
{/* 13. City */}
|
||||||
<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]">City</label>
|
||||||
City{requiredMark}
|
|
||||||
</label>
|
|
||||||
{renderMultiSelect({
|
{renderMultiSelect({
|
||||||
name: "districts",
|
name: "districts",
|
||||||
label: "City",
|
label: "City",
|
||||||
options: cityOptions,
|
options: cityOptions,
|
||||||
value: data.districts,
|
value: data.districts,
|
||||||
getLabel: (opt) =>
|
getLabel: (opt) => opt.district_name || opt.city_name || opt.name,
|
||||||
opt.district_name || opt.city_name || opt.name || String(opt),
|
getValue: (opt) => opt.id,
|
||||||
getValue: (opt) => opt.id ?? opt,
|
|
||||||
disabled: data.states.length === 0 || cityQuery.isLoading,
|
disabled: data.states.length === 0 || cityQuery.isLoading,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
item
|
size={12}
|
||||||
xs={12}
|
|
||||||
style={{
|
style={{
|
||||||
marginTop: 40,
|
marginTop: 40,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -417,11 +531,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>
|
||||||
|
|||||||
+640
-1148
File diff suppressed because it is too large.
Load diff
+348
-353
@@ -1,11 +1,11 @@
|
|||||||
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";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
import { Edit2,Info } from 'lucide-react';
|
import { Edit2, Info } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -17,9 +17,17 @@ 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 { usePersonalMasters, usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||||
|
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,124 +36,80 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
const isLoading = isAuth && apiLoading;
|
const isLoading = isAuth && apiLoading;
|
||||||
const isError = isAuth && apiError;
|
const isError = isAuth && apiError;
|
||||||
|
|
||||||
const sections = previewData?.personal_details
|
const dispatch = useDispatch();
|
||||||
? [
|
|
||||||
{
|
|
||||||
title: "Personal Details",
|
|
||||||
step: 1,
|
|
||||||
data: previewData.personal_details,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Educational & Professional Details",
|
|
||||||
step: 2,
|
|
||||||
data: previewData.educational_details,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Family Details",
|
|
||||||
step: 3,
|
|
||||||
data: previewData.family_details,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Lifestyle & Habits",
|
|
||||||
step: 4,
|
|
||||||
data: previewData.lifestyle_details,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Partner Preferences",
|
|
||||||
step: 5,
|
|
||||||
data: previewData.partner_preferences,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
title: 'Personal Details',
|
|
||||||
step: 1,
|
|
||||||
data: formData.personalDetails,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Educational & Professional Details',
|
|
||||||
step: 2,
|
|
||||||
data: formData.educationalDetails,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Family Details',
|
|
||||||
step: 3,
|
|
||||||
data: formData.familyDetails,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lifestyle & Habits',
|
|
||||||
step: 4,
|
|
||||||
data: formData.lifestyleDetails,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Partner Preferences',
|
|
||||||
step: 5,
|
|
||||||
data: formData.partnerPreferences,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const renderValue = (key, value) => {
|
useEffect(() => {
|
||||||
if (key === "profiles" || key === "profile" || key === "profile_images" || key === "images") {
|
const pd = previewData?.personal_details;
|
||||||
const list = Array.isArray(value) ? value : (value ? [value] : []);
|
if (pd) {
|
||||||
return (
|
const images = pd.images || pd.profile_images;
|
||||||
<Box display="flex" gap={1} flexWrap="wrap">
|
if (images && images.length > 0) {
|
||||||
{list.length > 0 ? (
|
const formattedImages = images.map(img =>
|
||||||
list.map((imgObj, index) => {
|
typeof img === 'string' ? { url: img, preview: img } : img
|
||||||
const src =
|
);
|
||||||
typeof imgObj === "string"
|
dispatch(updatePersonalDetails({ profiles: formattedImages }));
|
||||||
? imgObj
|
}
|
||||||
: imgObj?.preview ||
|
|
||||||
imgObj?.url ||
|
|
||||||
(imgObj?.file ? URL.createObjectURL(imgObj.file) : null);
|
|
||||||
if (!src) return null;
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
key={index}
|
|
||||||
src={src}
|
|
||||||
alt="Profile"
|
|
||||||
style={{
|
|
||||||
width: "90px",
|
|
||||||
height: "90px",
|
|
||||||
objectFit: "cover",
|
|
||||||
borderRadius: "50%",
|
|
||||||
border: "1px solid #ccc",
|
|
||||||
// marginLeft: "-20px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<Box display="flex" alignItems="center" gap={1} sx={{ color: "#888" }}>
|
|
||||||
<Info size={16} />
|
|
||||||
<Typography>No photos uploaded</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}, [previewData, dispatch]);
|
||||||
|
|
||||||
|
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||||
|
|
||||||
|
const handleConfirmSubmit = () => {
|
||||||
|
setOpenConfirmDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProceedSubmit = () => {
|
||||||
|
setOpenConfirmDialog(false);
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelSubmit = () => {
|
||||||
|
setOpenConfirmDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderValue = (value) => {
|
||||||
|
if (value === null || value === undefined || value === "") return "-";
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (value.length === 0) return "-";
|
if (value.length === 0) return "-";
|
||||||
if (typeof value[0] === "object") {
|
|
||||||
return value.map((item, idx) => (
|
|
||||||
<div key={idx}>{JSON.stringify(item)}</div>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return value.join(", ");
|
return value.join(", ");
|
||||||
}
|
}
|
||||||
|
if (typeof value === "boolean" || value === 1 || value === "1" || value === 0 || value === "0") {
|
||||||
if (value && typeof value === "object") {
|
if (value === true || value === 1 || value === "1") return "Yes";
|
||||||
return "-";
|
if (value === false || value === 0 || value === "0") return "No";
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
return value || "-";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DetailRow = ({ label, value }) => (
|
||||||
|
<Box display="grid" gridTemplateColumns="45% 55%" gap={1} mb={0.8} alignItems="start">
|
||||||
|
<Typography color="text.secondary" sx={{ fontWeight: 500, fontSize: '0.875rem' }}>
|
||||||
|
{label}:
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" fontWeight={600} sx={{ wordBreak: 'break-word' }}>
|
||||||
|
{renderValue(value)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SectionTitle = ({ title, mt = 2 }) => (
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#111827',
|
||||||
|
mt: mt,
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
const renderChartGrid = (getDataForCell, title) => {
|
const renderChartGrid = (getDataForCell, title) => {
|
||||||
const renderCell = (i) => {
|
const renderCell = (i) => {
|
||||||
const items = getDataForCell(i);
|
const items = getDataForCell(i);
|
||||||
const label = Array.isArray(items) ? items.join(', ') : '';
|
const label = Array.isArray(items) ? items.join(', ') : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip key={i} title={label} arrow placement="top" disableHoverListener={!items || items.length === 0}>
|
<Tooltip key={i} title={label} arrow placement="top" disableHoverListener={!items || items.length === 0}>
|
||||||
<Box
|
<Box
|
||||||
@@ -166,12 +130,12 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
cursor: items && items.length > 0 ? 'help' : 'default'
|
cursor: items && items.length > 0 ? 'help' : 'default'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{items && items.length > 2 ? (
|
{items && items.length > 2 ? (
|
||||||
<Box>
|
<Box>
|
||||||
{items.slice(0, 2).join(', ')}
|
{items.slice(0, 2).join(', ')}
|
||||||
<Box component="span" sx={{ color: 'primary.main', display: 'block', fontSize:'0.6rem' }}>+{items.length - 2}</Box>
|
<Box component="span" sx={{ color: 'primary.main', display: 'block', fontSize: '0.6rem' }}>+{items.length - 2}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
) : label}
|
) : label}
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@@ -179,268 +143,299 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" align="center" gutterBottom sx={{ fontWeight: 600 }}>{title}</Typography>
|
<Typography variant="subtitle2" align="center" gutterBottom sx={{ fontWeight: 600, color: '#CC1F1F' }}>{title}</Typography>
|
||||||
<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>
|
|
||||||
{renderCell(6)}
|
|
||||||
{renderCell(7)} {renderCell(8)}
|
|
||||||
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
{renderCell(6)}
|
||||||
|
{renderCell(7)} {renderCell(8)}
|
||||||
|
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const { data: pm } = usePersonalMasters();
|
||||||
<Box p={3} sx={{maxWidth:"1400px", width:"100%"}} mx="auto" >
|
const { data: ppm } = usePartnerPreferenceMasters();
|
||||||
|
|
||||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 my-10'>
|
|
||||||
|
|
||||||
|
const getNamesFromIds = (ids, options, labelKey = 'name', valueKey = 'id') => {
|
||||||
|
if (!ids || (Array.isArray(ids) && ids.length === 0)) return null;
|
||||||
|
if (!options) return Array.isArray(ids) ? ids.join(", ") : ids;
|
||||||
|
|
||||||
|
const idArray = Array.isArray(ids) ? ids : String(ids).split(",").map(v => v.trim());
|
||||||
|
if (idArray.length === 0) return null;
|
||||||
|
|
||||||
|
// Check if the first element is already a name
|
||||||
|
if (idArray[0] && typeof idArray[0] === 'string' && isNaN(Number(idArray[0]))) {
|
||||||
|
return idArray.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const names = idArray.map(id => {
|
||||||
|
const found = options.find(opt => String(opt[valueKey]) === String(id));
|
||||||
|
if (found) return found[labelKey];
|
||||||
|
return id;
|
||||||
|
});
|
||||||
|
return names.filter(Boolean).join(", ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const pInfo = previewData?.personal_details || formData.personalDetails;
|
||||||
|
const eInfo = previewData?.educational_details || formData.educationalDetails;
|
||||||
|
const fInfo = previewData?.family_details || formData.familyDetails;
|
||||||
|
const lInfo = previewData?.lifestyle_details || formData.lifestyleDetails;
|
||||||
|
// Helper to check if partner preferences from API are actually empty
|
||||||
|
const isPartnerApiDataEmpty = (pp) => {
|
||||||
|
if (!pp) return true;
|
||||||
|
// Check for common fields that should have values if data was saved
|
||||||
|
const hasData = pp.preferred_age_from || pp.age_from || pp.preferred_marital_status_ids?.length > 0 || pp.marital_statuses?.length > 0 || pp.preferred_castes_ids?.length > 0 || pp.castes?.length > 0;
|
||||||
|
return !hasData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const prInfo = (!isPartnerApiDataEmpty(previewData?.partner_preferences)) ? previewData.partner_preferences :
|
||||||
|
(!isPartnerApiDataEmpty(previewData?.preferred_details)) ? previewData.preferred_details :
|
||||||
|
(!isPartnerApiDataEmpty(previewData?.partner_details)) ? previewData.partner_details :
|
||||||
|
(formData.partnerPreferences || {});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={3} sx={{ maxWidth: "1400px", width: "100%" }} mx="auto">
|
||||||
|
<div className='grid grid-cols-1 md:grid-cols-2 gap-6 my-10'>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<Box
|
<Box py={4} display="flex" justifyContent="center" sx={{ color: "#666", gridColumn: 'span 2' }}>
|
||||||
py={4}
|
|
||||||
display="flex"
|
|
||||||
justifyContent="center"
|
|
||||||
sx={{ color: "#666" }}
|
|
||||||
>
|
|
||||||
Loading preview...
|
Loading preview...
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isError && (
|
{isError && (
|
||||||
<Box
|
<Box py={4} display="flex" justifyContent="center" sx={{ color: "#d32f2f", gridColumn: 'span 2' }}>
|
||||||
py={4}
|
|
||||||
display="flex"
|
|
||||||
justifyContent="center"
|
|
||||||
sx={{ color: "#d32f2f" }}
|
|
||||||
>
|
|
||||||
Failed to load preview.
|
Failed to load preview.
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading &&
|
{!isLoading && (
|
||||||
sections.map((section) => (
|
<>
|
||||||
|
{/* 1. Personal Details */}
|
||||||
<Card key={section.title} variant="outlined" sx={{ borderRadius: 2, background:"#fff5ed" }}>
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
title={
|
title={<Typography variant="h6" fontWeight="bold">Personal Details</Typography>}
|
||||||
<Typography variant="h6" fontWeight="bold">
|
action={<IconButton color="primary" onClick={() => onEdit(1)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
{section.title}
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
aria-label="edit"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => onEdit(section.step)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Edit2 size={20} />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
sx={{padding:"15px 15px", background:"#f5fbff" }}
|
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<CardContent>
|
||||||
<CardContent sx={{ pt: 1, }}>
|
<Box mb={2}>
|
||||||
{Object.entries(section.data || {}).map(([key, value]) => {
|
{/* Photos Preview */}
|
||||||
// Filter out ID fields
|
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1, fontSize: '0.75rem' }}>Profile Photos:</Typography>
|
||||||
if (key === 'id' || key.endsWith('_id') || key.endsWith('Id') || key.endsWith('_ids') || key.endsWith('Ids') || key === 'created_at' || key === 'updated_at') {
|
<Box display="flex" gap={1} flexWrap="wrap">
|
||||||
return null;
|
{(pInfo.images || pInfo.profile_images || pInfo.profiles || []).map((imgObj, index) => {
|
||||||
}
|
const src = typeof imgObj === "string" ? imgObj : imgObj?.preview || imgObj?.url;
|
||||||
|
return src ? (
|
||||||
// Handle Horoscope Chart (Server Data)
|
<img key={index} src={src} alt="Profile" style={{ width: "80px", height: "80px", objectFit: "cover", borderRadius: "50%", border: "1px solid #ccc" }} />
|
||||||
if (key === 'horoscope' && value && typeof value === 'object') {
|
) : null;
|
||||||
return (
|
})}
|
||||||
<Box key={key} sx={{ width: '100%', mt: 2, mb: 2, borderTop: '1px solid #e0e0e0', pt: 2, gridColumn: '1 / -1' }}>
|
{!(pInfo.images || pInfo.profile_images || pInfo.profiles || []).length && <Typography variant="caption">No photos</Typography>}
|
||||||
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>Horoscope Details</Typography>
|
</Box>
|
||||||
<Grid container spacing={4} justifyContent="center">
|
</Box>
|
||||||
<Grid item xs={12} md={6}>
|
<DetailRow label="Profile Created For" value={pInfo.profile_for} />
|
||||||
{renderChartGrid((i) => {
|
<DetailRow label="Gender" value={pInfo.gender} />
|
||||||
const val = value[`graha_${i}`];
|
<DetailRow label="Name" value={pInfo.name} />
|
||||||
return val ? val.split(',') : [];
|
<DetailRow label="Mobile Number" value={pInfo.mobile || pInfo.mobileNumber || pInfo.mobile_number} />
|
||||||
}, 'Rasi')}
|
<DetailRow label="Email Id" value={pInfo.email || pInfo.emailId || pInfo.email_id} />
|
||||||
</Grid>
|
<DetailRow label="Marital Status" value={getNamesFromIds(pInfo.marital_status, pm?.marital_status, 'marital_status_name')} />
|
||||||
<Grid item xs={12} md={6}>
|
<DetailRow label="Height" value={getNamesFromIds(pInfo.height, pm?.heights, 'height_text')} />
|
||||||
{renderChartGrid((i) => {
|
<DetailRow label="Weight" value={pInfo.weight} />
|
||||||
const val = value[`amsam_${i}`];
|
<DetailRow label="Complexion" value={getNamesFromIds(pInfo.complexion, pm?.complexion, 'complexion_name')} />
|
||||||
return val ? val.split(',') : [];
|
<DetailRow label="Physical Status" value={getNamesFromIds(pInfo.physical_status, pm?.physicalStatus, 'physical_status_name')} />
|
||||||
}, 'Navamsam')}
|
<DetailRow label="Religion" value={getNamesFromIds(pInfo.religion, pm?.religion, 'religion_name')} />
|
||||||
</Grid>
|
<DetailRow label="Caste / Community" value={getNamesFromIds(pInfo.caste, pm?.caste, 'caste_name')} />
|
||||||
</Grid>
|
<DetailRow label="Sub-Sect" value={getNamesFromIds(pInfo.sub_caste, pm?.subCaste, 'sub_caste_name')} />
|
||||||
</Box>
|
<DetailRow label="Willing to marry from same sub-sect" value={pInfo.willing_to_marry} />
|
||||||
);
|
<DetailRow label="Inter-Caste Parents" value={pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1" ? "Yes" : "No"} />
|
||||||
}
|
{ (pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1") && <DetailRow label="Inter-Caste Parents Details" value={pInfo.inter_caste_parents_details} />}
|
||||||
|
<DetailRow label="Gothram" value={pInfo.gothram} />
|
||||||
// Handle Horoscope Chart (Redux Data)
|
<DetailRow label="Do you speak Telugu" value={pInfo.do_you_speak_telugu === 1 || pInfo.do_you_speak_telugu === "1" ? "Yes" : "No"} />
|
||||||
if (key === 'graha' && value) {
|
<DetailRow label="About" value={pInfo.about_us} />
|
||||||
return (
|
<DetailRow label="Language Spoken" value={pInfo.known_languages} />
|
||||||
<Box key={key} sx={{ width: '100%', mt: 2, mb: 2, gridColumn: '1 / -1' }}>
|
<DetailRow label="Mother Tongue" value={getNamesFromIds(pInfo.mother_language, pm?.languages, 'language')} />
|
||||||
{renderChartGrid((i) => value[i] || [], 'Rasi')}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (key === 'amsam' && value) {
|
|
||||||
return (
|
|
||||||
<Box key={key} sx={{ width: '100%', mt: 2, mb: 2, gridColumn: '1 / -1' }}>
|
|
||||||
{renderChartGrid((i) => value[i] || [], 'Navamsam')}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle brothers and sisters arrays specifically
|
|
||||||
if ((key === 'brothers' || key === 'sisters') && Array.isArray(value) && value.length > 0) {
|
|
||||||
return (
|
|
||||||
<Box key={key} sx={{ py: 2, borderBottom: "1px solid #e0e0e0" }}>
|
|
||||||
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1 }}>
|
|
||||||
{key === 'brothers' ? 'Brothers Details' : 'Sisters Details'}
|
|
||||||
</Typography>
|
|
||||||
<style>
|
|
||||||
{`
|
|
||||||
.custom-swiper-${key} .swiper-button-next,
|
|
||||||
.custom-swiper-${key} .swiper-button-prev {
|
|
||||||
color: #d32f2f;
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.custom-swiper-${key} .swiper-button-next::after,
|
|
||||||
.custom-swiper-${key} .swiper-button-prev::after {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.custom-swiper-${key} .swiper-pagination-bullet-active {
|
|
||||||
background-color: #d32f2f;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
<Swiper
|
|
||||||
className={`custom-swiper-${key}`}
|
|
||||||
modules={[Navigation, Pagination]}
|
|
||||||
spaceBetween={15}
|
|
||||||
slidesPerView={1}
|
|
||||||
navigation
|
|
||||||
pagination={{ clickable: true }}
|
|
||||||
style={{ padding: "4px 4px 30px 4px" }}
|
|
||||||
>
|
|
||||||
{value.map((sibling, idx) => (
|
|
||||||
<SwiperSlide key={idx}>
|
|
||||||
<Card variant="outlined" sx={{ bgcolor: '#fff', height: '100%' }}>
|
|
||||||
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
|
||||||
{Object.entries(sibling).map(([sKey, sVal]) => {
|
|
||||||
if (sKey === 'id' || sKey.endsWith('_id') || sKey.endsWith('Id') || sKey === 'created_at' || sKey === 'updated_at') return null;
|
|
||||||
let displayValue = sVal;
|
|
||||||
if (sKey === 'haveChildrens' || sKey === 'have_childrens') {
|
|
||||||
if (sVal === true || sVal === 1 || sVal === '1') {
|
|
||||||
displayValue = 'Yes';
|
|
||||||
} else if (sVal === false || sVal === 0 || sVal === '0') {
|
|
||||||
displayValue = 'No';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box key={sKey} display="grid" gridTemplateColumns="45% 55%" gap={1} mb={0.8} alignItems="start">
|
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 500 }}>
|
|
||||||
{sKey
|
|
||||||
.replace(/([A-Z])/g, ' $1')
|
|
||||||
.replace(/_/g, ' ')
|
|
||||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
|
||||||
.trim()}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" fontWeight={600} sx={{ wordBreak: 'break-word' }}>
|
|
||||||
{displayValue ?? '-'}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)})}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</SwiperSlide>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Convert camelCase or camel_Snake_case to readable words
|
|
||||||
const formattedKey = key
|
|
||||||
.replace(/([A-Z])/g, ' $1')
|
|
||||||
.replace(/_/g, ' ')
|
|
||||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
|
||||||
.trim();
|
|
||||||
const content = renderValue(key, value);
|
|
||||||
return (
|
|
||||||
|
|
||||||
<Box
|
|
||||||
key={key}
|
|
||||||
py={0.7}
|
|
||||||
borderBottom="1px solid #e0e0e0"
|
|
||||||
sx={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: {
|
|
||||||
xs: "1fr",
|
|
||||||
sm: "1fr 1fr",
|
|
||||||
},
|
|
||||||
gap: "10px",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography color="text.secondary" sx={{ fontWeight: 500 }}>
|
|
||||||
{formattedKey}:
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{/* Special Case: Profiles Image Preview */}
|
|
||||||
{key === "profiles" || key === "profile" || key === "profile_images" || key === "images" ? (
|
|
||||||
content
|
|
||||||
) : value ? (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
wordBreak: "break-word",
|
|
||||||
textAlign: "left",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
gap={1}
|
|
||||||
sx={{ color: "#888", fontStyle: "italic" }}
|
|
||||||
>
|
|
||||||
<Info size={16} />
|
|
||||||
<Typography>No data available</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
})}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
))}
|
|
||||||
|
|
||||||
</div>
|
{/* 2. Educational & Professional Details */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">Educational & Professional Details</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => onEdit(2)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Highest Qualification" value={getNamesFromIds(eInfo.education, ppm?.education || ppm?.educations, 'education_name')} />
|
||||||
|
<DetailRow label="Field of Study" value={getNamesFromIds(eInfo.study_field, ppm?.study_fields || ppm?.studyFields, 'study_field_name')} />
|
||||||
|
<DetailRow label="About" value={eInfo.education_detail || eInfo.educationDetail || eInfo.about} />
|
||||||
|
<DetailRow label="College Name" value={eInfo.college_name} />
|
||||||
|
<DetailRow label="Employee type" value={getNamesFromIds(eInfo.employee_type, ppm?.employeeTypes || ppm?.employee_type, 'employee_type_name')} />
|
||||||
|
<DetailRow label="Occupation" value={getNamesFromIds(eInfo.occupation, ppm?.occupation || ppm?.occupations, 'occupation_name')} />
|
||||||
|
<DetailRow label="Occupation Details" value={eInfo.occupation_detail || eInfo.occupationDetail} />
|
||||||
|
<DetailRow label="Organization Name" value={eInfo.company_name || eInfo.organizationName || eInfo.companyName} />
|
||||||
|
<DetailRow label="Income Currency Type" value={eInfo.income_currency} />
|
||||||
|
<DetailRow label="Annual Income" value={eInfo.annual_income} />
|
||||||
|
|
||||||
|
<SectionTitle title="Work Location" />
|
||||||
|
<DetailRow label="Country" value={eInfo.work_country_name || eInfo.work_country} />
|
||||||
|
<DetailRow label="State" value={eInfo.work_state_name || eInfo.work_state} />
|
||||||
|
<DetailRow label="City" value={eInfo.work_district_name || eInfo.work_city} />
|
||||||
|
<DetailRow label="Address" value={eInfo.work_location || eInfo.address} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Grid item xs={12} sx={{display:"flex", justifyContent:"center"}}>
|
{/* 3. Family Details */}
|
||||||
<Button
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
variant="contained"
|
<CardHeader
|
||||||
color="success"
|
title={<Typography variant="h6" fontWeight="bold">Family Details</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => onEdit(3)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
size="large"
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
onClick={onSubmit}
|
/>
|
||||||
sx={{ borderRadius: 2 }}
|
<CardContent>
|
||||||
>
|
<DetailRow label="Father Name" value={fInfo.father_name || fInfo.fatherName} />
|
||||||
Submit full Completed Data
|
<DetailRow label="Father Occupation" value={fInfo.father_occupation || fInfo.father_occupational || fInfo.fatherOccupation || fInfo.fatherOccupational || fInfo.father_occupation_name} />
|
||||||
</Button>
|
<DetailRow label="Mother Name" value={fInfo.mother_name || fInfo.motherName} />
|
||||||
</Grid>
|
<DetailRow label="Mother Occupation" value={fInfo.mother_occupation || fInfo.mother_occupational || fInfo.motherOccupation || fInfo.motherOccupational || fInfo.mother_occupation_name} />
|
||||||
|
|
||||||
|
{/* Siblings */}
|
||||||
|
{['brothers', 'sisters'].map(type => (
|
||||||
|
(fInfo[type] && fInfo[type].length > 0) && (
|
||||||
|
<Box key={type} mt={2} mb={2}>
|
||||||
|
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1, textTransform: 'capitalize' }}>{type} Details:</Typography>
|
||||||
|
<Swiper modules={[Navigation, Pagination]} spaceBetween={10} slidesPerView={1} navigation pagination={{ clickable: true }} style={{ paddingBottom: '30px' }}>
|
||||||
|
{fInfo[type].map((sib, i) => (
|
||||||
|
<SwiperSlide key={i}>
|
||||||
|
<Card variant="outlined" sx={{ p: 2, bgcolor: '#f9f9f9' }}>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>{type.slice(0, -1)} {i + 1}</Typography>
|
||||||
|
<DetailRow label="Name" value={sib.name} />
|
||||||
|
<DetailRow label={type === 'brothers' ? 'Brother Type' : 'Sister Type'} value={sib.type} />
|
||||||
|
<DetailRow label="Occupation" value={sib.occupation || sib.occupational || sib.occupation_name} />
|
||||||
|
<DetailRow label="Marital Status" value={sib.marital_status || sib.maritalStatus || sib.marital_status_id} />
|
||||||
|
<DetailRow label="Has Children" value={sib.has_children || sib.hasChildren || sib.thereHaveChildren} />
|
||||||
|
<DetailRow label="Additional Details" value={sib.details || sib.additionalDetails || sib.additional_details} />
|
||||||
|
</Card>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
|
||||||
|
<DetailRow label="Family Status" value={fInfo.family_status || fInfo.familyStatus} />
|
||||||
|
<DetailRow label="Native Place" value={fInfo.native_place || fInfo.nativePlace} />
|
||||||
|
|
||||||
|
<SectionTitle title="Location Living" />
|
||||||
|
<DetailRow label="Country Living" value={fInfo.family_country_name || fInfo.living_country_name} />
|
||||||
|
<DetailRow label="Residing State" value={fInfo.family_state_name || fInfo.living_state_name} />
|
||||||
|
<DetailRow label="Residing City" value={fInfo.family_district_name || fInfo.family_city || fInfo.living_city_name} />
|
||||||
|
<DetailRow label="Address" value={fInfo.address} />
|
||||||
|
<DetailRow label="Expectations / Requirements Details" value={fInfo.expectation_details || fInfo.expectationDetails} />
|
||||||
|
<DetailRow label="Willing to go abroad" value={fInfo.willing_to_go_abroad || fInfo.willingToGoAbroad} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 4. Lifestyle & Horoscope */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">Lifestyle & Horoscope</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => onEdit(4)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Time of Birth" value={lInfo.time_of_birth_formated || lInfo.tob} />
|
||||||
|
<DetailRow label="Place of Birth" value={lInfo.place_of_birth || lInfo.placeOfBirth} />
|
||||||
|
<DetailRow label="Birth Star" value={lInfo.star_name || lInfo.star} />
|
||||||
|
<DetailRow label="Padham" value={lInfo.patham} />
|
||||||
|
<DetailRow label="Rasi" value={lInfo.raasi_name || lInfo.raasi} />
|
||||||
|
<DetailRow label="Lagnam" value={lInfo.lagnam_name || lInfo.lagnam} />
|
||||||
|
<DetailRow label="Panjangam" value={lInfo.panjangam_type} />
|
||||||
|
<DetailRow label="Date of Birth" value={lInfo.date_of_birth_formated || lInfo.dob} />
|
||||||
|
<DetailRow label="Dasa Balance" value={lInfo.dasa_balance} />
|
||||||
|
<DetailRow label="Dasa Duration" value={(lInfo.dasa_years || lInfo.dasa_months || lInfo.dasa_days) ? `${lInfo.dasa_years || 0} Years, ${lInfo.dasa_months || 0} Months, ${lInfo.dasa_days || 0} Days` : "-"} />
|
||||||
|
|
||||||
|
{/* Horoscope Charts */}
|
||||||
|
{lInfo.horoscope && (
|
||||||
|
<Box mt={3}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
|
{renderChartGrid((i) => lInfo.horoscope[`graha_${i}`]?.split(',') || (lInfo.graha && lInfo.graha[i]) || [], 'Rasi')}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
|
{renderChartGrid((i) => lInfo.horoscope[`amsam_${i}`]?.split(',') || (lInfo.amsam && lInfo.amsam[i]) || [], 'Navamsam')}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 5. What am I looking for in a Partner */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">What am I looking for in a Partner</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => onEdit(5)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Age Range" value={(prInfo.preferred_age_from || prInfo.age_from) && (prInfo.preferred_age_to || prInfo.age_to) ? `${prInfo.preferred_age_from || prInfo.age_from} - ${prInfo.preferred_age_to || prInfo.age_to}` : "-"} />
|
||||||
|
<DetailRow label="Height" value={(getNamesFromIds(prInfo.preferred_height_from_id || prInfo.height_from, ppm?.heights, 'height_text') && getNamesFromIds(prInfo.preferred_height_to_id || prInfo.height_to, ppm?.heights, 'height_text')) ? `${getNamesFromIds(prInfo.preferred_height_from_id || prInfo.height_from, ppm?.heights, 'height_text')} - ${getNamesFromIds(prInfo.preferred_height_to_id || prInfo.height_to, ppm?.heights, 'height_text')}` : "-"} />
|
||||||
|
<DetailRow label="Marital Status" value={getNamesFromIds(prInfo.preferred_marital_statuses || prInfo.marital_statuses || prInfo.preferred_marital_status || prInfo.marital_status, ppm?.maritalStatus || ppm?.marital_status, 'marital_status_name')} />
|
||||||
|
<DetailRow label="Birth Star" value={getNamesFromIds(prInfo.preferred_birth_stars || prInfo.birth_stars || prInfo.preferred_birth_star || prInfo.birth_star, ppm?.stars || ppm?.star, 'star_name')} />
|
||||||
|
<DetailRow label="Caste" value={getNamesFromIds(prInfo.preferred_castes || prInfo.castes || prInfo.preferred_caste || prInfo.caste, ppm?.caste || ppm?.castes, 'caste_name')} />
|
||||||
|
<DetailRow label="Sub - Sect" value={getNamesFromIds(prInfo.preferred_sub_castes || prInfo.sub_castes || prInfo.preferred_sub_caste || prInfo.sub_caste, ppm?.sub_caste || ppm?.sub_castes, 'sub_caste_name')} />
|
||||||
|
<DetailRow label="Qualification" value={getNamesFromIds(prInfo.preferred_educations || prInfo.educations || prInfo.preferred_education || prInfo.education, ppm?.education || ppm?.educations, 'education_name')} />
|
||||||
|
<DetailRow label="Occupation" value={getNamesFromIds(prInfo.preferred_occupations || prInfo.occupations || prInfo.preferred_occupation || prInfo.occupation, ppm?.occupation || ppm?.occupations, 'occupation_name')} />
|
||||||
|
|
||||||
|
<DetailRow label="Employee Type" value={getNamesFromIds(prInfo.preferred_employee_types || prInfo.employee_types || prInfo.preferred_employee_type || prInfo.employee_type, ppm?.employeeTypes || ppm?.employee_type, 'employee_type_name')} />
|
||||||
|
|
||||||
|
<SectionTitle title="Prefer Annual Income" />
|
||||||
|
{(prInfo.preferred_currencies || prInfo.currencies || []).includes("INR") && (
|
||||||
|
<DetailRow label="Prefer Annual Income (INR)" value={(prInfo.preferred_inr_from || prInfo.inr_from || prInfo.preferred_inr_range) ? `From : ${prInfo.preferred_inr_from || prInfo.inr_from || ""} \nTo : ${prInfo.preferred_inr_to || prInfo.inr_to || ""}` : "-"} />
|
||||||
|
)}
|
||||||
|
{(prInfo.preferred_currencies || prInfo.currencies || []).includes("USD") && (
|
||||||
|
<DetailRow label="Prefer Annual Income (USD)" value={(prInfo.preferred_usd_from || prInfo.usd_from || prInfo.preferred_usd_range) ? `From : ${prInfo.preferred_usd_from || prInfo.usd_from || ""} \nTo : ${prInfo.preferred_usd_to || prInfo.usd_to || ""}` : "-"} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DetailRow label="State" value={getNamesFromIds(prInfo.preferred_states || prInfo.states || prInfo.preferred_state || prInfo.state, ppm?.states, 'state_name')} />
|
||||||
|
<DetailRow label="City" value={getNamesFromIds(prInfo.preferred_districts || prInfo.districts || prInfo.preferred_district || prInfo.district, ppm?.districts, 'district_name')} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Grid size={12} sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
size="large"
|
||||||
|
onClick={handleConfirmSubmit}
|
||||||
|
sx={{ borderRadius: 2 }}
|
||||||
|
>
|
||||||
|
Submit Full Completed Data
|
||||||
|
</Button>
|
||||||
|
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+374
-486
@@ -1,409 +1,214 @@
|
|||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Navigation, Autoplay } from "swiper/modules";
|
import { Navigation, Autoplay, Pagination } from "swiper/modules";
|
||||||
import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react";
|
import { ChevronLeft, ChevronRight, Edit2, Info, Image as ImageIcon } 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, useSelector } from "react-redux";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import LazyImage from "../components/common/LazyImage";
|
import "swiper/css/pagination";
|
||||||
import weddingpromo1 from "../assets/images/weddingpromo1.jpg";
|
import weddingpromo1 from "../assets/images/weddingpromo1.jpg";
|
||||||
import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
|
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,
|
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
Divider,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Typography,
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Info } from "@mui/icons-material";
|
import { usePreviewDetails } from "../hooks/usePreview";
|
||||||
|
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||||
|
import { usePersonalMasters, usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||||
|
|
||||||
const images = [
|
const promoImages = [
|
||||||
weddingpromo1, // bride in saree
|
weddingpromo1,
|
||||||
weddingpromo2, // traditional jewellery
|
weddingpromo2,
|
||||||
weddingpromo3, // flower details
|
weddingpromo3,
|
||||||
weddingpromo4, // couple hands
|
weddingpromo4,
|
||||||
weddingpromo1, // bride close-up
|
weddingpromo1,
|
||||||
weddingpromo3, // groom + bride portrait
|
weddingpromo3,
|
||||||
weddingpromo2, // wedding decor
|
weddingpromo2,
|
||||||
weddingpromo4, // bride in temple
|
weddingpromo4,
|
||||||
weddingpromo1, // traditional ceremony
|
weddingpromo1,
|
||||||
weddingpromo3, // couple full shot
|
weddingpromo3,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ProfilePreviewPage = () => {
|
const ProfilePreviewPage = () => {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const dispatch = useDispatch();
|
||||||
personalDetails,
|
const { data: previewData, isLoading } = usePreviewDetails();
|
||||||
educationalDetails,
|
|
||||||
familyDetails,
|
const { data: pm } = usePersonalMasters();
|
||||||
lifestyleDetails,
|
const { data: ppm } = usePartnerPreferenceMasters();
|
||||||
partnerPreferences,
|
|
||||||
} = useSelector((state) => state.registerform);
|
const getNamesFromIds = (ids, options, labelKey = 'name', valueKey = 'id') => {
|
||||||
|
if (!ids || (Array.isArray(ids) && ids.length === 0)) return null;
|
||||||
|
if (!options) return Array.isArray(ids) ? ids.join(", ") : ids;
|
||||||
|
|
||||||
|
const idArray = Array.isArray(ids) ? ids : String(ids).split(",").map(v => v.trim());
|
||||||
|
if (idArray.length === 0) return null;
|
||||||
|
|
||||||
|
if (idArray[0] && typeof idArray[0] === 'string' && isNaN(Number(idArray[0]))) {
|
||||||
|
return idArray.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const names = idArray.map(id => {
|
||||||
|
const found = options.find(opt => String(opt[valueKey]) === String(id));
|
||||||
|
if (found) return found[labelKey];
|
||||||
|
return id;
|
||||||
|
});
|
||||||
|
return names.filter(Boolean).join(", ");
|
||||||
|
};
|
||||||
|
|
||||||
// For dummy data on first visit
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(preloadDummyProfile());
|
if (previewData?.personal_details) {
|
||||||
}, [dispatch]);
|
const pd = previewData.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 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [previewData, dispatch]);
|
||||||
|
|
||||||
|
const formData = useSelector((state) => state.registerform);
|
||||||
|
|
||||||
|
const renderValue = (value) => {
|
||||||
|
if (value === null || value === undefined || value === "") return "-";
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (value.length === 0) return "-";
|
||||||
|
return value.join(", ");
|
||||||
|
}
|
||||||
|
if (typeof value === "boolean" || value === 1 || value === "1" || value === 0 || value === "0") {
|
||||||
|
if (value === true || value === 1 || value === "1") return "Yes";
|
||||||
|
if (value === false || value === 0 || value === "0") return "No";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetailRow = ({ label, value }) => (
|
||||||
|
<Box display="grid" gridTemplateColumns="45% 55%" gap={1} mb={0.8} alignItems="start">
|
||||||
|
<Typography color="text.secondary" sx={{ fontWeight: 500, fontSize: '0.875rem' }}>
|
||||||
|
{label}:
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" fontWeight={600} sx={{ wordBreak: 'break-word' }}>
|
||||||
|
{renderValue(value)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SectionTitle = ({ title, mt = 2 }) => (
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#111827',
|
||||||
|
mt: mt,
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
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, color: '#CC1F1F' }}>{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', overflow: 'hidden' }}>
|
||||||
|
<img src={horoscopeImg} alt={title} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||||
|
</Box>
|
||||||
|
{renderCell(6)}
|
||||||
|
{renderCell(7)} {renderCell(8)}
|
||||||
|
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditSection = (stepNum) => {
|
const handleEditSection = (stepNum) => {
|
||||||
navigate("/profile-edit", { state: { step: stepNum } });
|
navigate("/profile-edit", { state: { step: stepNum } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderField = (label, value, stepNum = 1) => (
|
const pInfo = previewData?.personal_details || formData.personalDetails;
|
||||||
<Box
|
const eInfo = previewData?.educational_details || formData.educationalDetails;
|
||||||
py={0.7}
|
const fInfo = previewData?.family_details || formData.familyDetails;
|
||||||
borderBottom="1px solid #e0e0e0"
|
const lInfo = previewData?.lifestyle_details || formData.lifestyleDetails;
|
||||||
sx={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: {
|
|
||||||
xs: "1fr",
|
|
||||||
sm: "1fr 1fr",
|
|
||||||
},
|
|
||||||
gap: "10px",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography color="text.secondary" sx={{ fontWeight: 500 }}>
|
|
||||||
{label}:
|
|
||||||
</Typography>
|
|
||||||
{value ? (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
wordBreak: "break-word",
|
|
||||||
textAlign: "left",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
gap={1}
|
|
||||||
sx={{ color: "#888", fontStyle: "italic" }}
|
|
||||||
>
|
|
||||||
<Info size={16} />
|
|
||||||
<Typography>No data available</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderPersonalSection = () => (
|
const isPartnerApiDataEmpty = (pp) => {
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
if (!pp) return true;
|
||||||
<CardHeader
|
const hasData = pp.preferred_age_from || pp.age_from || pp.preferred_marital_status_ids?.length > 0 || pp.marital_statuses?.length > 0 || pp.preferred_castes_ids?.length > 0 || pp.castes?.length > 0;
|
||||||
title={
|
return !hasData;
|
||||||
<Typography variant="h6" fontWeight="bold">
|
};
|
||||||
Personal Details
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
aria-label="edit"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => handleEditSection(1)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Edit2 size={20} />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
const prInfo = (!isPartnerApiDataEmpty(previewData?.partner_preferences)) ? previewData.partner_preferences :
|
||||||
{renderField("Name", personalDetails.name, 1)}
|
(!isPartnerApiDataEmpty(previewData?.preferred_details)) ? previewData.preferred_details :
|
||||||
{renderField("Mobile Number", personalDetails.mobileNumber, 1)}
|
(!isPartnerApiDataEmpty(previewData?.partner_details)) ? previewData.partner_details :
|
||||||
{renderField("Gender", personalDetails.gender, 1)}
|
(formData.partnerPreferences || {});
|
||||||
{renderField("Date of Birth", personalDetails.dob, 1)}
|
|
||||||
{renderField("Height", personalDetails.height, 1)}
|
|
||||||
{renderField("Weight", personalDetails.weight, 1)}
|
|
||||||
{renderField("Marital Status", personalDetails.maritalStatus, 1)}
|
|
||||||
{renderField("Religion", personalDetails.religion, 1)}
|
|
||||||
{renderField("Caste", personalDetails.caste, 1)}
|
|
||||||
{renderField("Email", personalDetails.email, 1)}
|
|
||||||
{renderField("State", personalDetails.state, 1)}
|
|
||||||
{renderField("City", personalDetails.city, 1)}
|
|
||||||
{renderField("Pincode", personalDetails.pincode, 1)}
|
|
||||||
|
|
||||||
{/* Profile Images */}
|
if (isLoading) {
|
||||||
<Box
|
return (
|
||||||
py={0.7}
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="50vh">
|
||||||
borderBottom="1px solid #e0e0e0"
|
<Typography>Loading Profile...</Typography>
|
||||||
sx={{
|
</Box>
|
||||||
display: "grid",
|
);
|
||||||
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
}
|
||||||
gap: "10px",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography color="text.secondary" sx={{ fontWeight: 500 }}>
|
|
||||||
Profile Photos:
|
|
||||||
</Typography>
|
|
||||||
{personalDetails.profiles && personalDetails.profiles.length > 0 ? (
|
|
||||||
<Box display="flex" flexWrap="wrap" gap={1}>
|
|
||||||
{personalDetails.profiles.slice(0, 4).map((profile, index) => (
|
|
||||||
<Box
|
|
||||||
key={index}
|
|
||||||
width={60}
|
|
||||||
height={60}
|
|
||||||
borderRadius={1}
|
|
||||||
overflow="hidden"
|
|
||||||
sx={{ border: "1px solid #ddd", position: "relative" }}
|
|
||||||
>
|
|
||||||
{profile.preview || profile.url ? (
|
|
||||||
<img
|
|
||||||
src={profile.preview || profile.url}
|
|
||||||
alt={`Profile ${index + 1}`}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
height="100%"
|
|
||||||
bgcolor="#f5f5f5"
|
|
||||||
>
|
|
||||||
<ImageIcon size={24} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
{personalDetails.profiles.length > 4 && (
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
|
||||||
+{personalDetails.profiles.length - 4} more
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
gap={1}
|
|
||||||
sx={{ color: "#888", fontStyle: "italic" }}
|
|
||||||
>
|
|
||||||
<Info size={16} />
|
|
||||||
<Typography>No photos uploaded</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderEducationalSection = () => (
|
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
|
||||||
<CardHeader
|
|
||||||
title={
|
|
||||||
<Typography variant="h6" fontWeight="bold">
|
|
||||||
Education Details
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
aria-label="edit"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => handleEditSection(2)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Edit2 size={20} />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
|
||||||
{renderField("Qualification", educationalDetails.qualification, 2)}
|
|
||||||
{renderField("Field of Study", educationalDetails.fieldOfStudy, 2)}
|
|
||||||
{renderField("College Name", educationalDetails.collegeName, 2)}
|
|
||||||
{renderField("Occupation", educationalDetails.occupation, 2)}
|
|
||||||
{renderField("Organization", educationalDetails.organization, 2)}
|
|
||||||
{renderField("Employee Type", educationalDetails.employeeType, 2)}
|
|
||||||
{renderField("Income", educationalDetails.income, 2)}
|
|
||||||
{renderField("Work Location", educationalDetails.workLocation, 2)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderFamilySection = () => (
|
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
|
||||||
<CardHeader
|
|
||||||
title={
|
|
||||||
<Typography variant="h6" fontWeight="bold">
|
|
||||||
Family Details
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
aria-label="edit"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => handleEditSection(2)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Edit2 size={20} />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
|
||||||
{renderField("Father Name", familyDetails.fatherName)}
|
|
||||||
{renderField("Father Occupation", familyDetails.fatherOccupation)}
|
|
||||||
{renderField("Mother Name", familyDetails.motherName)}
|
|
||||||
{renderField("Mother Occupation", familyDetails.motherOccupation)}
|
|
||||||
{renderField("Brother Count", familyDetails.brotherCount)}
|
|
||||||
{renderField("Sister Count", familyDetails.sisterCount)}
|
|
||||||
{renderField("Family Status", familyDetails.familyStatus)}
|
|
||||||
{renderField("Native Place", familyDetails.nativePlace)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderLifestyleSection = () => (
|
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
|
||||||
<CardHeader
|
|
||||||
title={
|
|
||||||
<Typography variant="h6" fontWeight="bold">
|
|
||||||
Lifestyle Details
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
aria-label="edit"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => handleEditSection(2)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Edit2 size={20} />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
|
||||||
{renderField(
|
|
||||||
"Diet",
|
|
||||||
Array.isArray(lifestyleDetails.diets)
|
|
||||||
? lifestyleDetails.diets.join(", ")
|
|
||||||
: lifestyleDetails.diets
|
|
||||||
)}
|
|
||||||
{renderField(
|
|
||||||
"Hobbies",
|
|
||||||
Array.isArray(lifestyleDetails.hobbies)
|
|
||||||
? lifestyleDetails.hobbies.join(", ")
|
|
||||||
: lifestyleDetails.hobbies
|
|
||||||
)}
|
|
||||||
{renderField("Date of Birth", lifestyleDetails.dob)}
|
|
||||||
{renderField("Time of Birth", lifestyleDetails.tob)}
|
|
||||||
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const renderPreferenceSection = () => (
|
|
||||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
|
||||||
<CardHeader
|
|
||||||
title={
|
|
||||||
<Typography variant="h6" fontWeight="bold">
|
|
||||||
Lifestyle Details
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
aria-label="edit"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => handleEditSection(2)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Edit2 size={20} />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<CardContent sx={{ pt: 1 }}>
|
|
||||||
{renderField("Age Range", partnerPreferences.ageRange)}
|
|
||||||
{renderField(
|
|
||||||
"Caste",
|
|
||||||
Array.isArray(partnerPreferences.castes)
|
|
||||||
? partnerPreferences.castes.join(", ")
|
|
||||||
: partnerPreferences.castes
|
|
||||||
)}
|
|
||||||
{renderField(
|
|
||||||
"Sub Caste",
|
|
||||||
Array.isArray(partnerPreferences.subCastes)
|
|
||||||
? partnerPreferences.subCastes.join(", ")
|
|
||||||
: partnerPreferences.subCastes
|
|
||||||
)}
|
|
||||||
{renderField(
|
|
||||||
"Occupation",
|
|
||||||
Array.isArray(partnerPreferences.occupations)
|
|
||||||
? partnerPreferences.occupations.join(", ")
|
|
||||||
: partnerPreferences.occupations
|
|
||||||
)}
|
|
||||||
{renderField(
|
|
||||||
"Qualification",
|
|
||||||
Array.isArray(partnerPreferences.educations)
|
|
||||||
? partnerPreferences.educations.join(", ")
|
|
||||||
: partnerPreferences.educations
|
|
||||||
)}
|
|
||||||
{renderField(
|
|
||||||
"Lifestyle & Hobbies",
|
|
||||||
Array.isArray(partnerPreferences.hobbies)
|
|
||||||
? partnerPreferences.hobbies.join(", ")
|
|
||||||
: partnerPreferences.hobbies
|
|
||||||
)}
|
|
||||||
{renderField("Annual Income", partnerPreferences.annualIncome)}
|
|
||||||
{renderField(
|
|
||||||
"State",
|
|
||||||
Array.isArray(partnerPreferences.states)
|
|
||||||
? partnerPreferences.states.join(", ")
|
|
||||||
: partnerPreferences.states
|
|
||||||
)}
|
|
||||||
{renderField(
|
|
||||||
"City",
|
|
||||||
Array.isArray(partnerPreferences.districts)
|
|
||||||
? partnerPreferences.districts.join(", ")
|
|
||||||
: partnerPreferences.districts
|
|
||||||
)}
|
|
||||||
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
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">
|
||||||
<div className="w-full max-w-7xl relative">
|
<div className="w-full max-w-7xl relative">
|
||||||
{/* Left Arrow */}
|
|
||||||
<button className="swiper-button-prev-custom absolute left-0 top-1/2 -translate-y-1/2 z-10 w-12 h-12 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all">
|
<button className="swiper-button-prev-custom absolute left-0 top-1/2 -translate-y-1/2 z-10 w-12 h-12 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all">
|
||||||
<ChevronLeft className="w-6 h-6 text-white" />
|
<ChevronLeft className="w-6 h-6 text-white" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Right Arrow */}
|
|
||||||
<button className="swiper-button-next-custom absolute right-0 top-1/2 -translate-y-1/2 z-10 w-12 h-12 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all">
|
<button className="swiper-button-next-custom absolute right-0 top-1/2 -translate-y-1/2 z-10 w-12 h-12 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all">
|
||||||
<ChevronRight className="w-6 h-6 text-white" />
|
<ChevronRight className="w-6 h-6 text-white" />
|
||||||
</button>
|
</button>
|
||||||
@@ -425,138 +230,221 @@ const ProfilePreviewPage = () => {
|
|||||||
loop={true}
|
loop={true}
|
||||||
className="mySwiper"
|
className="mySwiper"
|
||||||
>
|
>
|
||||||
{images.map((img, idx) => (
|
{(pInfo.images || pInfo.profile_images || pInfo.profiles || promoImages).map((imgObj, idx) => {
|
||||||
<SwiperSlide key={idx}>
|
const src = typeof imgObj === "string" ? imgObj : imgObj?.preview || imgObj?.url;
|
||||||
<div className="relative overflow-hidden rounded-3xl w-[100%] ">
|
return src ? (
|
||||||
<img
|
<SwiperSlide key={idx}>
|
||||||
src={img}
|
<div className="relative overflow-hidden rounded-3xl w-[100%] h-full">
|
||||||
alt={`Slide ${idx + 1}`}
|
<img src={src} alt={`Slide ${idx + 1}`} className="w-full h-full object-cover" />
|
||||||
className="w-full h-full object-cover"
|
</div>
|
||||||
/>
|
</SwiperSlide>
|
||||||
|
) : null;
|
||||||
{/* <LazyImage
|
})}
|
||||||
src={img}
|
|
||||||
alt={`Slide ${idx + 1}`}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/> */}
|
|
||||||
{/* <div className="absolute bottom-6 left-1/2 -translate-x-1/2 bg-black/50 backdrop-blur-sm px-6 py-2 rounded-full">
|
|
||||||
<span className="text-white font-semibold text-lg">Slide {idx + 1}</span>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</SwiperSlide>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>{`
|
<style>{`
|
||||||
.custom-swiper-hero .swiper {
|
.custom-swiper-hero .swiper { width: 100%; overflow: visible; }
|
||||||
width: 100%;
|
.custom-swiper-hero .swiper-slide { width: 320px; height: 320px; transition: all 0.4s ease; transform: scale(0.85); }
|
||||||
overflow: visible;
|
.custom-swiper-hero .swiper-slide-active { width: 630px !important; height: 320px !important; transform: scale(1); z-index: 2; }
|
||||||
}
|
@media only screen and (max-width: 768px) {
|
||||||
|
.custom-swiper-hero .swiper-slide { width: 300px; height: 150px; }
|
||||||
.custom-swiper-hero .swiper-slide {
|
.custom-swiper-hero .swiper-slide-active { width: 300px !important; height: 150px !important; }
|
||||||
width: 320px;
|
|
||||||
height: 320px;
|
|
||||||
transition: all 0.4s ease;
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(0.85);
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
|
||||||
width: 630px !important;
|
|
||||||
height: 320px !important;
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
|
||||||
.custom-swiper-hero .swiper-slide {
|
|
||||||
width: 300px;
|
|
||||||
height: 150px;
|
|
||||||
}
|
}
|
||||||
|
.custom-swiper-hero .swiper-slide > div { width: 100%; height: 100%; }
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
`}</style>
|
||||||
width: 300px !important;
|
|
||||||
height: 150px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide > div {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-button-prev-custom,
|
|
||||||
.custom-swiper-hero .swiper-button-next-custom {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.custom-swiper-hero .swiper-slide {
|
|
||||||
width: 240px;
|
|
||||||
height: 340px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
|
||||||
width: 380px !important;
|
|
||||||
height: 480px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.custom-swiper-hero .swiper-slide {
|
|
||||||
width: 180px;
|
|
||||||
height: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
|
||||||
width: 300px !important;
|
|
||||||
height: 280px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-button-prev-custom,
|
|
||||||
.custom-swiper-hero .swiper-button-next-custom {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.custom-swiper-hero .swiper-slide {
|
|
||||||
width: 140px;
|
|
||||||
height: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
|
||||||
width: 300px !important;
|
|
||||||
height: 220px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-button-prev-custom,
|
|
||||||
.custom-swiper-hero .swiper-button-next-custom {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProfileCompletion />
|
<ProfileCompletion />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* personal details start */}
|
|
||||||
<Box p={3} sx={{ maxWidth: "1400px", width: "100%" }} mx="auto">
|
<Box p={3} sx={{ maxWidth: "1400px", width: "100%" }} mx="auto">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 my-5">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 my-5">
|
||||||
{renderPersonalSection()}
|
{/* 1. Personal Details */}
|
||||||
{renderEducationalSection()}
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
{renderFamilySection()}
|
<CardHeader
|
||||||
{renderLifestyleSection()}
|
title={<Typography variant="h6" fontWeight="bold">Personal Details</Typography>}
|
||||||
{renderPreferenceSection()}
|
action={<IconButton color="primary" onClick={() => handleEditSection(1)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Box mb={2}>
|
||||||
|
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1, fontSize: '0.75rem' }}>Profile Photos:</Typography>
|
||||||
|
<Box display="flex" gap={1} flexWrap="wrap">
|
||||||
|
{(pInfo.images || pInfo.profile_images || pInfo.profiles || []).map((imgObj, index) => {
|
||||||
|
const src = typeof imgObj === "string" ? imgObj : imgObj?.preview || imgObj?.url;
|
||||||
|
return src ? (
|
||||||
|
<img key={index} src={src} alt="Profile" style={{ width: "80px", height: "80px", objectFit: "cover", borderRadius: "50%", border: "1px solid #ccc" }} />
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
|
{!(pInfo.images || pInfo.profile_images || pInfo.profiles || []).length && <Typography variant="caption">No photos</Typography>}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<DetailRow label="Profile Created For" value={pInfo.profile_for} />
|
||||||
|
<DetailRow label="Gender" value={pInfo.gender} />
|
||||||
|
<DetailRow label="Name" value={pInfo.name} />
|
||||||
|
<DetailRow label="Mobile Number" value={pInfo.mobile || pInfo.mobileNumber || pInfo.mobile_number} />
|
||||||
|
<DetailRow label="Email Id" value={pInfo.email || pInfo.emailId || pInfo.email_id} />
|
||||||
|
<DetailRow label="Marital Status" value={getNamesFromIds(pInfo.marital_status, pm?.marital_status, 'marital_status_name')} />
|
||||||
|
<DetailRow label="Height" value={getNamesFromIds(pInfo.height, pm?.heights, 'height_text')} />
|
||||||
|
<DetailRow label="Weight" value={pInfo.weight} />
|
||||||
|
<DetailRow label="Complexion" value={getNamesFromIds(pInfo.complexion, pm?.complexion, 'complexion_name')} />
|
||||||
|
<DetailRow label="Physical Status" value={getNamesFromIds(pInfo.physical_status, pm?.physicalStatus, 'physical_status_name')} />
|
||||||
|
<DetailRow label="Religion" value={getNamesFromIds(pInfo.religion, pm?.religion, 'religion_name')} />
|
||||||
|
<DetailRow label="Caste / Community" value={getNamesFromIds(pInfo.caste, pm?.caste, 'caste_name')} />
|
||||||
|
<DetailRow label="Sub-Sect" value={getNamesFromIds(pInfo.sub_caste, pm?.subCaste, 'sub_caste_name')} />
|
||||||
|
<DetailRow label="Willing to marry from same sub-sect" value={pInfo.willing_to_marry} />
|
||||||
|
<DetailRow label="Inter-Caste Parents" value={pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1" ? "Yes" : "No"} />
|
||||||
|
{ (pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1") && <DetailRow label="Inter-Caste Parents Details" value={pInfo.inter_caste_parents_details} />}
|
||||||
|
<DetailRow label="Gothram" value={pInfo.gothram} />
|
||||||
|
<DetailRow label="Do you speak Telugu" value={pInfo.do_you_speak_telugu === 1 || pInfo.do_you_speak_telugu === "1" ? "Yes" : "No"} />
|
||||||
|
<DetailRow label="About" value={pInfo.about_us} />
|
||||||
|
<DetailRow label="Language Spoken" value={pInfo.known_languages} />
|
||||||
|
<DetailRow label="Mother Tongue" value={getNamesFromIds(pInfo.mother_language, pm?.languages, 'language')} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 2. Educational & Professional Details */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">Educational & Professional Details</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => handleEditSection(2)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Highest Qualification" value={getNamesFromIds(eInfo.education, ppm?.education || ppm?.educations, 'education_name')} />
|
||||||
|
<DetailRow label="Field of Study" value={getNamesFromIds(eInfo.study_field, ppm?.study_fields || ppm?.studyFields, 'study_field_name')} />
|
||||||
|
<DetailRow label="About" value={eInfo.education_detail || eInfo.educationDetail || eInfo.about} />
|
||||||
|
<DetailRow label="College Name" value={eInfo.college_name} />
|
||||||
|
<DetailRow label="Employee type" value={getNamesFromIds(eInfo.employee_type, ppm?.employeeTypes || ppm?.employee_type, 'employee_type_name')} />
|
||||||
|
<DetailRow label="Occupation" value={getNamesFromIds(eInfo.occupation, ppm?.occupation || ppm?.occupations, 'occupation_name')} />
|
||||||
|
<DetailRow label="Occupation Details" value={eInfo.occupation_detail || eInfo.occupationDetail} />
|
||||||
|
<DetailRow label="Organization Name" value={eInfo.company_name || eInfo.organizationName || eInfo.companyName} />
|
||||||
|
<DetailRow label="Income Currency Type" value={eInfo.income_currency} />
|
||||||
|
<DetailRow label="Annual Income" value={eInfo.annual_income} />
|
||||||
|
|
||||||
|
<SectionTitle title="Work Location" />
|
||||||
|
<DetailRow label="Country" value={eInfo.work_country_name || eInfo.work_country} />
|
||||||
|
<DetailRow label="State" value={eInfo.work_state_name || eInfo.work_state} />
|
||||||
|
<DetailRow label="City" value={eInfo.work_district_name || eInfo.work_city} />
|
||||||
|
<DetailRow label="Address" value={eInfo.work_location || eInfo.address} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 3. Family Details */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">Family Details</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => handleEditSection(3)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Father Name" value={fInfo.father_name || fInfo.fatherName} />
|
||||||
|
<DetailRow label="Father Occupation" value={fInfo.father_occupation || fInfo.father_occupational || fInfo.fatherOccupation || fInfo.fatherOccupational || fInfo.father_occupation_name} />
|
||||||
|
<DetailRow label="Mother Name" value={fInfo.mother_name || fInfo.motherName} />
|
||||||
|
<DetailRow label="Mother Occupation" value={fInfo.mother_occupation || fInfo.mother_occupational || fInfo.motherOccupation || fInfo.motherOccupational || fInfo.mother_occupation_name} />
|
||||||
|
|
||||||
|
{['brothers', 'sisters'].map(type => (
|
||||||
|
(fInfo[type] && fInfo[type].length > 0) && (
|
||||||
|
<Box key={type} mt={2} mb={2}>
|
||||||
|
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1, textTransform: 'capitalize' }}>{type} Details:</Typography>
|
||||||
|
<Swiper modules={[Navigation, Pagination]} spaceBetween={10} slidesPerView={1} navigation pagination={{ clickable: true }} style={{ paddingBottom: '30px' }}>
|
||||||
|
{fInfo[type].map((sib, i) => (
|
||||||
|
<SwiperSlide key={i}>
|
||||||
|
<Card variant="outlined" sx={{ p: 2, bgcolor: '#f9f9f9' }}>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>{type.slice(0, -1)} {i + 1}</Typography>
|
||||||
|
<DetailRow label="Name" value={sib.name} />
|
||||||
|
<DetailRow label={type === 'brothers' ? 'Brother Type' : 'Sister Type'} value={sib.type} />
|
||||||
|
<DetailRow label="Occupation" value={sib.occupation || sib.occupational || sib.occupation_name} />
|
||||||
|
<DetailRow label="Marital Status" value={sib.marital_status || sib.maritalStatus || sib.marital_status_id} />
|
||||||
|
<DetailRow label="Has Children" value={sib.has_children || sib.hasChildren || sib.thereHaveChildren} />
|
||||||
|
<DetailRow label="Additional Details" value={sib.details || sib.additionalDetails || sib.additional_details} />
|
||||||
|
</Card>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
|
||||||
|
<DetailRow label="Family Status" value={fInfo.family_status || fInfo.familyStatus} />
|
||||||
|
<DetailRow label="Native Place" value={fInfo.native_place || fInfo.nativePlace} />
|
||||||
|
|
||||||
|
<SectionTitle title="Location Living" />
|
||||||
|
<DetailRow label="Country Living" value={fInfo.family_country_name || fInfo.living_country_name} />
|
||||||
|
<DetailRow label="Residing State" value={fInfo.family_state_name || fInfo.living_state_name} />
|
||||||
|
<DetailRow label="Residing City" value={fInfo.family_district_name || fInfo.family_city || fInfo.living_city_name} />
|
||||||
|
<DetailRow label="Address" value={fInfo.address} />
|
||||||
|
<DetailRow label="Expectations / Requirements Details" value={fInfo.expectation_details || fInfo.expectationDetails} />
|
||||||
|
<DetailRow label="Willing to go abroad" value={fInfo.willing_to_go_abroad || fInfo.willingToGoAbroad} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 4. Lifestyle & Horoscope */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">Lifestyle & Horoscope</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => handleEditSection(4)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Time of Birth" value={lInfo.time_of_birth_formated || lInfo.tob} />
|
||||||
|
<DetailRow label="Place of Birth" value={lInfo.place_of_birth || lInfo.placeOfBirth} />
|
||||||
|
<DetailRow label="Birth Star" value={lInfo.star_name || lInfo.star} />
|
||||||
|
<DetailRow label="Padham" value={lInfo.patham} />
|
||||||
|
<DetailRow label="Rasi" value={lInfo.raasi_name || lInfo.raasi} />
|
||||||
|
<DetailRow label="Lagnam" value={lInfo.lagnam_name || lInfo.lagnam} />
|
||||||
|
<DetailRow label="Panjangam" value={lInfo.panjangam_type} />
|
||||||
|
<DetailRow label="Date of Birth" value={lInfo.date_of_birth_formated || lInfo.dob} />
|
||||||
|
<DetailRow label="Dasa Balance" value={lInfo.dasa_balance} />
|
||||||
|
<DetailRow label="Dasa Duration" value={(lInfo.dasa_years || lInfo.dasa_months || lInfo.dasa_days) ? `${lInfo.dasa_years || 0} Years, ${lInfo.dasa_months || 0} Months, ${lInfo.dasa_days || 0} Days` : "-"} />
|
||||||
|
|
||||||
|
{lInfo.horoscope && (
|
||||||
|
<Box mt={3}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
|
{renderChartGrid((i) => lInfo.horoscope[`graha_${i}`]?.split(',') || (lInfo.graha && lInfo.graha[i]) || [], 'Rasi')}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
|
{renderChartGrid((i) => lInfo.horoscope[`amsam_${i}`]?.split(',') || (lInfo.amsam && lInfo.amsam[i]) || [], 'Navamsam')}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 5. What am I looking for in a Partner */}
|
||||||
|
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography variant="h6" fontWeight="bold">What am I looking for in a Partner</Typography>}
|
||||||
|
action={<IconButton color="primary" onClick={() => handleEditSection(5)} size="large"><Edit2 size={20} /></IconButton>}
|
||||||
|
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<DetailRow label="Age Range" value={(prInfo.preferred_age_from || prInfo.age_from) && (prInfo.preferred_age_to || prInfo.age_to) ? `${prInfo.preferred_age_from || prInfo.age_from} - ${prInfo.preferred_age_to || prInfo.age_to}` : "-"} />
|
||||||
|
<DetailRow label="Height" value={(getNamesFromIds(prInfo.preferred_height_from_id || prInfo.height_from, ppm?.heights, 'height_text') && getNamesFromIds(prInfo.preferred_height_to_id || prInfo.height_to, ppm?.heights, 'height_text')) ? `${getNamesFromIds(prInfo.preferred_height_from_id || prInfo.height_from, ppm?.heights, 'height_text')} - ${getNamesFromIds(prInfo.preferred_height_to_id || prInfo.height_to, ppm?.heights, 'height_text')}` : "-"} />
|
||||||
|
<DetailRow label="Marital Status" value={getNamesFromIds(prInfo.preferred_marital_statuses || prInfo.marital_statuses || prInfo.preferred_marital_status || prInfo.marital_status, ppm?.maritalStatus || ppm?.marital_status, 'marital_status_name')} />
|
||||||
|
<DetailRow label="Birth Star" value={getNamesFromIds(prInfo.preferred_birth_stars || prInfo.birth_stars || prInfo.preferred_birth_star || prInfo.birth_star, ppm?.stars || ppm?.star, 'star_name')} />
|
||||||
|
<DetailRow label="Caste" value={getNamesFromIds(prInfo.preferred_castes || prInfo.castes || prInfo.preferred_caste || prInfo.caste, ppm?.caste || ppm?.castes, 'caste_name')} />
|
||||||
|
<DetailRow label="Sub - Sect" value={getNamesFromIds(prInfo.preferred_sub_castes || prInfo.sub_castes || prInfo.preferred_sub_caste || prInfo.sub_caste, ppm?.sub_caste || ppm?.sub_castes, 'sub_caste_name')} />
|
||||||
|
<DetailRow label="Qualification" value={getNamesFromIds(prInfo.preferred_educations || prInfo.educations || prInfo.preferred_education || prInfo.education, ppm?.education || ppm?.educations, 'education_name')} />
|
||||||
|
<DetailRow label="Occupation" value={getNamesFromIds(prInfo.preferred_occupations || prInfo.occupations || prInfo.preferred_occupation || prInfo.occupation, ppm?.occupation || ppm?.occupations, 'occupation_name')} />
|
||||||
|
<DetailRow label="Employee Type" value={getNamesFromIds(prInfo.preferred_employee_types || prInfo.employee_types || prInfo.preferred_employee_type || prInfo.employee_type, ppm?.employeeTypes || ppm?.employee_type, 'employee_type_name')} />
|
||||||
|
|
||||||
|
<SectionTitle title="Prefer Annual Income" />
|
||||||
|
{(prInfo.preferred_currencies || prInfo.currencies || []).includes("INR") && (
|
||||||
|
<DetailRow label="Prefer Annual Income (INR)" value={(prInfo.preferred_inr_from || prInfo.inr_from || prInfo.preferred_inr_range) ? `From : ${prInfo.preferred_inr_from || prInfo.inr_from || ""} \nTo : ${prInfo.preferred_inr_to || prInfo.inr_to || ""}` : "-"} />
|
||||||
|
)}
|
||||||
|
{(prInfo.preferred_currencies || prInfo.currencies || []).includes("USD") && (
|
||||||
|
<DetailRow label="Prefer Annual Income (USD)" value={(prInfo.preferred_usd_from || prInfo.usd_from || prInfo.preferred_usd_range) ? `From : ${prInfo.preferred_usd_from || prInfo.usd_from || ""} \nTo : ${prInfo.preferred_usd_to || prInfo.usd_to || ""}` : "-"} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DetailRow label="State" value={getNamesFromIds(prInfo.preferred_states || prInfo.states || prInfo.preferred_state || prInfo.state, ppm?.states, 'state_name')} />
|
||||||
|
<DetailRow label="City" value={getNamesFromIds(prInfo.preferred_districts || prInfo.districts || prInfo.preferred_district || prInfo.district, ppm?.districts, 'district_name')} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* personal details end */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { IconButton, Dialog, DialogTitle, DialogContent, DialogActions, Box, Button, TextField } from "@mui/material";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
|
const ReportModel = ({ open, onClose, onSubmit, actionType }) => {
|
||||||
|
const [reason, setReason] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setReason("");
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
onSubmit(reason);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
fullWidth
|
||||||
|
maxWidth="sm"
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ background: "#f5fbff" }}>
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||||
|
<span className="capitalize" style={{ textTransform: 'capitalize' }}>
|
||||||
|
{actionType === 'block' ? 'Block Profile' : 'Report Profile'}
|
||||||
|
</span>
|
||||||
|
<IconButton onClick={onClose} aria-label="close">
|
||||||
|
<X size={20} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box mt={1}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
variant="outlined"
|
||||||
|
placeholder={`Enter your reason for ${actionType === 'block' ? 'blocking' : 'reporting'} this profile...`}
|
||||||
|
value={reason}
|
||||||
|
onChange={(e) => setReason(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ p: 2, background: "#f5fbff" }}>
|
||||||
|
<Button onClick={onClose} color="inherit">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
variant="contained"
|
||||||
|
disabled={!reason.trim()}
|
||||||
|
sx={{
|
||||||
|
bgcolor: actionType === 'block' ? '#d32f2f' : '#034E08',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: actionType === 'block' ? '#b71c1c' : '#023806',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{actionType === 'block' ? 'Block' : 'Submit Report'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportModel;
|
||||||
+965
-374
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
getSubCasteMasters,
|
getSubCasteMasters,
|
||||||
getCityMasters,
|
getCityMasters,
|
||||||
getStarMasters,
|
getStarMasters,
|
||||||
|
getPathamMasters,
|
||||||
} from "../api/masters.api";
|
} from "../api/masters.api";
|
||||||
|
|
||||||
/** Personal details masters (gender, marital status, religion, gothram, raasi, state, etc.) */
|
/** Personal details masters (gender, marital status, religion, gothram, raasi, state, etc.) */
|
||||||
@@ -43,7 +44,15 @@ export const useSubCasteMasters = (caste_id) =>
|
|||||||
export const useCityMasters = (state_id) =>
|
export const useCityMasters = (state_id) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: ["city-masters", state_id],
|
queryKey: ["city-masters", state_id],
|
||||||
queryFn: () => getCityMasters(state_id),
|
queryFn: async () => {
|
||||||
|
if (Array.isArray(state_id)) {
|
||||||
|
const results = await Promise.all(
|
||||||
|
state_id.map((id) => getCityMasters(id))
|
||||||
|
);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
return getCityMasters(state_id);
|
||||||
|
},
|
||||||
enabled: Array.isArray(state_id) ? state_id.length > 0 : !!state_id,
|
enabled: Array.isArray(state_id) ? state_id.length > 0 : !!state_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,3 +63,11 @@ export const useStarMasters = (raasi_id) =>
|
|||||||
queryFn: () => getStarMasters(raasi_id),
|
queryFn: () => getStarMasters(raasi_id),
|
||||||
enabled: !!raasi_id,
|
enabled: !!raasi_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Patham depends on star */
|
||||||
|
export const usePathamMasters = (star_id) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: ["patham-masters", star_id],
|
||||||
|
queryFn: () => getPathamMasters(star_id),
|
||||||
|
enabled: !!star_id,
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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]) => {
|
||||||
|
// Skip default boundaries to prevent strict filtering on null values
|
||||||
|
if (key === "from_age" && value === 18) return acc;
|
||||||
|
if (key === "to_age" && value === 70) return acc;
|
||||||
|
if (key === "from_height" && value === 4.0) return acc;
|
||||||
|
if (key === "to_height" && value === 7.11) return acc;
|
||||||
|
|
||||||
|
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 }) => {
|
||||||
|
console.log("[API-REQUEST] Calling profiles/lists with filters:", {
|
||||||
|
...cleanFilters,
|
||||||
|
page: pageParam,
|
||||||
|
});
|
||||||
|
return 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,
|
||||||
|
});
|
||||||
@@ -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 };
|
||||||
|
};
|
||||||
+10
-7
@@ -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;
|
||||||
|
|||||||
+624
-501
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) {
|
||||||
|
|||||||
+602
-1015
File diff suppressed because it is too large.
Load diff
@@ -1,19 +1,59 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
const fetchDetail = async (showLoading = true) => {
|
||||||
|
if (showLoading) setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getProfileDetail(id);
|
||||||
|
setData(res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch profile details:", error);
|
||||||
|
} finally {
|
||||||
|
if (showLoading) setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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} onRefresh={fetchDetail} />
|
||||||
<PartnerPreferences/>
|
<PartnerPreferences data={data} />
|
||||||
<MatchingList/>
|
<MatchingList matches={data.all_matches} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProfileDetailPage
|
export default ProfileDetailPage;
|
||||||
+249
-158
@@ -1,177 +1,268 @@
|
|||||||
// src/pages/SubscriptionHistory.jsx
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ArrowBackIosNew, CalendarToday, AccessTime, CreditCard, Person, Visibility } from '@mui/icons-material';
|
import { ArrowBackIosNew, CalendarToday, AccessTime, CreditCard, Person, Visibility, History, WorkspacePremium, ReceiptLong, LocalActivity } from '@mui/icons-material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { motion } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { CrownIcon } from 'lucide-react';
|
import { CrownIcon, ShieldCheck, Zap, ArrowRight, CheckCircle2 } from 'lucide-react';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getSubscriptionHistory } from '../api/subscription.api';
|
||||||
|
import { CircularProgress, Box, Typography, Chip } from '@mui/material';
|
||||||
|
|
||||||
const subscriptions = [
|
const PlanCard = ({ sub, isActive = false, index = 0 }) => {
|
||||||
{
|
if (!sub) return null;
|
||||||
id: "SUB722HSN",
|
|
||||||
plan: "Gold Plan",
|
return (
|
||||||
profileCount: 150,
|
<motion.div
|
||||||
usedCount: 70,
|
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||||
billingCycle: "Monthly",
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
expireDate: "19/11/2025",
|
whileHover={{ y: -8, transition: { duration: 0.3 } }}
|
||||||
startDate: "19/10/2025",
|
transition={{ delay: index * 0.1, duration: 0.5 }}
|
||||||
startTime: "10:00 AM",
|
className={`group relative rounded-[32px] overflow-hidden border transition-all duration-500 ${
|
||||||
paymentMethod: "UPI Mode",
|
isActive
|
||||||
amount: 1800,
|
? 'border-amber-300 shadow-[0_20px_50px_rgba(255,193,7,0.15)] bg-white'
|
||||||
isActive: true
|
: 'border-gray-100 bg-white/80 backdrop-blur-sm shadow-xl'
|
||||||
},
|
}`}
|
||||||
{
|
>
|
||||||
id: "SUB722HSN",
|
{/* Dynamic Background Pattern */}
|
||||||
plan: "Gold Plan",
|
<div className={`absolute inset-0 opacity-[0.03] pointer-events-none ${isActive ? 'bg-[radial-gradient(circle_at_50%_50%,#ffc107_0%,transparent_70%)]' : ''}`} />
|
||||||
profileCount: 150,
|
|
||||||
usedCount: 70,
|
{/* Decorative Gradient Glow */}
|
||||||
billingCycle: "Monthly",
|
{isActive && (
|
||||||
expireDate: "19/11/2025",
|
<div className="absolute -right-20 -top-20 w-40 h-40 bg-amber-400/20 blur-[80px] rounded-full pointer-events-none" />
|
||||||
startDate: "19/10/2025",
|
)}
|
||||||
startTime: "10:00 AM",
|
|
||||||
paymentMethod: "UPI Mode",
|
{/* Header Section */}
|
||||||
amount: 1800,
|
<div className={`relative px-6 pt-12 pb-6 text-center ${isActive ? 'bg-gradient-to-b from-amber-50/50 to-transparent' : ''}`}>
|
||||||
isActive: false
|
{/* Status Badge */}
|
||||||
},
|
<div className="absolute top-4 left-1/2 -translate-x-1/2 flex items-center gap-2">
|
||||||
|
{isActive ? (
|
||||||
{ id: "SUB722HSN",
|
<motion.div
|
||||||
plan: "Gold Plan",
|
animate={{ scale: [1, 1.05, 1] }}
|
||||||
profileCount: 150,
|
transition={{ repeat: Infinity, duration: 2 }}
|
||||||
usedCount: 70,
|
className="bg-gradient-to-r from-amber-500 to-orange-600 text-white px-5 py-1.5 rounded-full text-[10px] font-black tracking-widest shadow-lg flex items-center gap-2"
|
||||||
billingCycle: "Monthly",
|
>
|
||||||
expireDate: "19/11/2025",
|
<Zap size={12} fill="white" /> ACTIVE PLAN
|
||||||
startDate: "19/10/2025",
|
</motion.div>
|
||||||
startTime: "10:00 AM",
|
) : (
|
||||||
paymentMethod: "UPI Mode",
|
<div className="bg-gray-100 text-gray-500 px-4 py-1 rounded-full text-[10px] font-bold tracking-widest flex items-center gap-2">
|
||||||
amount: 1800,
|
<History size={12} /> COMPLETED
|
||||||
isActive: false
|
</div>
|
||||||
}
|
)}
|
||||||
];
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-2xl font-black text-gray-900 mb-1">{sub.plan_name}</h3>
|
||||||
|
<p className="text-[10px] text-gray-400 font-bold uppercase tracking-[0.2em]">Transaction ID: #TK{sub.id}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pb-8">
|
||||||
|
{/* Stats Grid */}
|
||||||
|
<div className="grid grid-cols-2 gap-3 mb-6">
|
||||||
|
<div className="relative group/stat p-4 rounded-2xl bg-gray-50 border border-gray-100 transition-colors hover:bg-amber-50/50 overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 p-2 opacity-10 group-hover/stat:opacity-20 transition-opacity">
|
||||||
|
<Person />
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-black text-gray-900 mb-0.5">
|
||||||
|
{sub.count_of_profile_view === -1 ? '∞' : sub.count_of_profile_view}
|
||||||
|
</p>
|
||||||
|
<p className="text-[9px] font-bold text-gray-400 uppercase tracking-wider">Profile Views</p>
|
||||||
|
</div>
|
||||||
|
<div className="relative group/stat p-4 rounded-2xl bg-gray-50 border border-gray-100 transition-colors hover:bg-green-50/50 overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 p-2 opacity-10 group-hover/stat:opacity-20 transition-opacity">
|
||||||
|
<Visibility />
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-black text-gray-900 mb-0.5">{sub.used_count_of_profile_view || 0}</p>
|
||||||
|
<p className="text-[9px] font-bold text-gray-400 uppercase tracking-wider">Used Count</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Benefits/Details List */}
|
||||||
|
<div className="space-y-2 mb-8">
|
||||||
|
<DetailRow icon={<CalendarToday className="text-amber-500" />} label="Activated" value={new Date(sub.supscription_start_date).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })} />
|
||||||
|
<DetailRow icon={<AccessTime className="text-red-500" />} label="Expiry" value={new Date(sub.supscription_expiry_date).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })} />
|
||||||
|
<DetailRow icon={<ShieldCheck className="text-blue-500" size={16} />} label="Duration" value={sub.plan_validity_days === -1 ? 'Lifetime Access' : `${sub.plan_validity_days} Days`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price Tag */}
|
||||||
|
<div className={`p-4 rounded-2xl flex items-center justify-between ${isActive ? 'bg-amber-50 border border-amber-100' : 'bg-gray-50 border border-gray-100'}`}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className={`p-2 rounded-lg ${isActive ? 'bg-amber-500 text-white' : 'bg-gray-200 text-gray-500'}`}>
|
||||||
|
<CreditCard fontSize="small" />
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold text-gray-500">Total Paid</span>
|
||||||
|
</div>
|
||||||
|
<span className={`text-xl font-black ${isActive ? 'text-amber-600' : 'text-gray-900'}`}>
|
||||||
|
₹{sub.plan_amount?.toLocaleString()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetailRow = ({ icon, label, value }) => (
|
||||||
|
<div className="flex items-center justify-between py-2.5 px-4 rounded-xl hover:bg-gray-50 transition-colors">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="flex-shrink-0 opacity-80">{icon}</span>
|
||||||
|
<span className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">{label}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-bold text-gray-900">{value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export default function SubscriptionHistory() {
|
export default function SubscriptionHistory() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: historyData, isLoading, error } = useQuery({
|
||||||
|
queryKey: ['subscriptionHistory'],
|
||||||
|
queryFn: getSubscriptionHistory,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex flex-col items-center justify-center bg-white">
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [1, 1.1, 1], rotate: 360 }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2 }}
|
||||||
|
className="mb-6"
|
||||||
|
>
|
||||||
|
<WorkspacePremium sx={{ fontSize: 60, color: '#A70710' }} />
|
||||||
|
</motion.div>
|
||||||
|
<div className="w-48 h-1 bg-gray-100 rounded-full overflow-hidden relative">
|
||||||
|
<motion.div
|
||||||
|
animate={{ left: ['-100%', '100%'] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 1.5, ease: "linear" }}
|
||||||
|
className="absolute top-0 bottom-0 w-full bg-[#A70710]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSub = historyData?.current_subscription;
|
||||||
|
const history = historyData?.subscription_history || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen ">
|
<div className="min-h-screen bg-[#FAFAFA] text-black">
|
||||||
{/* Royal Header */}
|
{/* Glass Header */}
|
||||||
<motion.header
|
<motion.header
|
||||||
initial={{ y: -100 }}
|
initial={{ y: -20, opacity: 0 }}
|
||||||
animate={{ y: 0 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
className="bg-[#034E08] text-white shadow-2xl rounded-[10px]"
|
className="sticky top-0 z-[100] bg-white/80 backdrop-blur-xl border-b border-gray-100"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between px-5 py-8 safe-area-top relative">
|
<div className="max-w-2xl mx-auto px-4 h-20 flex items-center justify-between">
|
||||||
|
<button
|
||||||
<h1 className="text-2xl font-bold">Subscription History</h1>
|
onClick={() => navigate(-1)}
|
||||||
<div className="w-12" />
|
className="w-10 h-10 flex items-center justify-center rounded-2xl hover:bg-gray-100 transition-colors text-gray-600"
|
||||||
|
>
|
||||||
|
<ArrowBackIosNew fontSize="small" />
|
||||||
|
</button>
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-lg font-black tracking-tight">Subscriptions</h1>
|
||||||
|
<p className="text-[9px] font-bold text-amber-600 tracking-[0.3em] uppercase">Thirukalyanam Premium</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 flex items-center justify-center rounded-2xl bg-amber-50 text-amber-600">
|
||||||
|
<ReceiptLong size={20} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.header>
|
</motion.header>
|
||||||
|
|
||||||
{/* Current Plan Title */}
|
<main className="max-w-7xl mx-auto px-4 py-10 pb-32">
|
||||||
<div className="px-6 pt-8">
|
{/* Hero Active Section */}
|
||||||
<motion.h2
|
<div className="mb-16">
|
||||||
initial={{ opacity: 0, x: -50 }}
|
<div className="flex items-center justify-between mb-8">
|
||||||
animate={{ opacity: 1, x: 0 }}
|
<div className="flex items-center gap-4">
|
||||||
className="text-3xl font-bold text-gray-900 flex items-center gap-3"
|
<div className="w-14 h-14 rounded-[20px] bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center text-white shadow-lg shadow-orange-100">
|
||||||
>
|
<WorkspacePremium fontSize="large" />
|
||||||
<CrownIcon className="text-amber-500 text-4xl" />
|
</div>
|
||||||
Current Plan
|
<div>
|
||||||
</motion.h2>
|
<h2 className="text-xl font-black text-gray-900">Current Status</h2>
|
||||||
</div>
|
<p className="text-xs font-bold text-gray-400 uppercase tracking-widest">Active Plan Details</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{currentSub && (
|
||||||
|
<div className="bg-green-50 text-green-600 px-4 py-2 rounded-xl hidden sm:flex items-center gap-2 border border-green-100">
|
||||||
|
<CheckCircle2 size={16} />
|
||||||
|
<span className="text-[10px] font-black uppercase tracking-wider">Verified Membership</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className=''>
|
{currentSub ? (
|
||||||
{/* Subscription Cards */}
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
<div className="px-2 py-8 pb-32 grid grid-cols-1 md:grid-cols-3 gap-2">
|
<PlanCard sub={currentSub} isActive={true} />
|
||||||
{subscriptions.map((sub, index) => (
|
<div className="hidden lg:block lg:col-span-2 bg-gradient-to-br from-gray-900 to-black rounded-[40px] p-10 relative overflow-hidden text-white shadow-2xl">
|
||||||
<motion.div
|
<div className="absolute top-0 right-0 p-10 opacity-20 scale-150">
|
||||||
key={index}
|
<CrownIcon size={120} />
|
||||||
initial={{ opacity: 0, y: 50 }}
|
</div>
|
||||||
animate={{ opacity: 1, y: 0 }}
|
<div className="relative z-10 h-full flex flex-col justify-center">
|
||||||
transition={{ delay: index * 0.2 }}
|
<h3 className="text-3xl font-black mb-4 tracking-tight">Premium Benefits Active</h3>
|
||||||
className={`relative rounded-3xl overflow-hidden border border-1 border-gray-200 w-full max-w-md ${
|
<p className="text-gray-400 mb-8 max-w-md leading-relaxed text-sm">
|
||||||
sub.isActive
|
You are currently enjoying the full suite of Thirukalyanam premium features.
|
||||||
? 'border-amber-400 bg-gradient-to-br from-amber-50 to-pink-50'
|
Your account is prioritized in searches and you have direct access to contact details.
|
||||||
: 'border-pink-200 bg-gradient-to-br from-pink-50 to-rose-50 opacity-90'
|
</p>
|
||||||
}`}
|
<div className="flex flex-wrap gap-3">
|
||||||
>
|
{['Verified Profile', 'Priority Search', 'Direct Contact', 'Chat Enabled'].map((item) => (
|
||||||
{/* Crown Badge */}
|
<div key={item} className="px-4 py-2 bg-white/10 backdrop-blur-md rounded-xl text-[10px] font-bold tracking-widest uppercase flex items-center gap-2">
|
||||||
<div className={` text-[12px] absolute z-[999] -top-1 left-1/2 -translate-x-1/2 px-8 py-2 rounded-b-3xl font-bold text-white shadow-xl flex items-center gap-2 ${
|
<CheckCircle2 size={12} className="text-green-400" /> {item}
|
||||||
sub.isActive ? 'bg-gradient-to-r from-amber-500 to-red-600' : 'bg-gradient-to-r from-pink-500 to-rose-600'
|
</div>
|
||||||
}`}>
|
))}
|
||||||
<CrownIcon className="text-[12px]" />
|
</div>
|
||||||
{sub.plan}
|
</div>
|
||||||
{sub.isActive && <span className="ml-2 text-[12px] bg-green-400 text-black px-3 py-1 rounded-full">ACTIVE</span>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-14 pb-8 px-8">
|
|
||||||
{/* Subscription ID */}
|
|
||||||
<div className="text-center mb-6">
|
|
||||||
<p className="text-sm text-gray-600">Subscription ID</p>
|
|
||||||
<p className="text-[18px] font-bold text-gray-900">{sub.id}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats Row */}
|
|
||||||
<div className="grid grid-cols-2 gap-6 mb-4">
|
|
||||||
<div className="bg-white/70 backdrop-blur rounded-2xl p-5 text-center ">
|
|
||||||
<Person className="text-4xl text-amber-600 mx-auto mb-2" />
|
|
||||||
<p className="text-[18px] font-bold text-gray-900">{sub.profileCount}</p>
|
|
||||||
<p className="text-sm text-gray-600">Profile Count</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/70 backdrop-blur rounded-2xl p-5 text-center ">
|
|
||||||
<Visibility className="text-4xl text-green-600 mx-auto mb-2" />
|
|
||||||
<p className="text-[18px] font-bold text-gray-900">{sub.usedCount}</p>
|
|
||||||
<p className="text-sm text-gray-600">Used Count</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Details Grid */}
|
|
||||||
<div className="space-y-2 text-lg">
|
|
||||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
|
||||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
|
||||||
<CalendarToday className="text-amber-600" /> Billing Cycle
|
|
||||||
</span>
|
|
||||||
<span className="font-bold text-gray-900 text-[14px]">{sub.billingCycle}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
|
||||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
|
||||||
<CalendarToday className="text-red-600" /> Expire Date
|
|
||||||
</span>
|
|
||||||
<span className="font-bold text-red-600 text-[14px]">{sub.expireDate}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
|
||||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
|
||||||
<AccessTime className="text-green-600" /> Start Date & Time
|
|
||||||
</span>
|
|
||||||
<span className="font-bold text-gray-900 text-[14px]">{sub.startDate} | {sub.startTime}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
|
||||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
|
||||||
<CreditCard className="text-purple-600" /> Payment Method
|
|
||||||
</span>
|
|
||||||
<span className="font-bold text-purple-700 text-[14px]">{sub.paymentMethod}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Total Amount */}
|
|
||||||
<div className="mt-8 text-center flex gap-4 items-center justify-center flex-wrap">
|
|
||||||
<p className="text-lg text-gray-600">Total Plan Amount</p>
|
|
||||||
<p className="text-[18px] font-bold text-gray-900 bg-white/70 backdrop-blur px-4 py-2 rounded-[10px]">
|
|
||||||
₹{sub.amount.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
) : (
|
||||||
))}
|
<motion.div
|
||||||
</div>
|
initial={{ opacity: 0, y: 20 }}
|
||||||
</div>
|
animate={{ opacity: 1, y: 0 }}
|
||||||
{/* Final Message */}
|
className="p-10 rounded-[40px] bg-white border-2 border-dashed border-gray-100 text-center max-w-xl mx-auto"
|
||||||
{/* <motion.div
|
>
|
||||||
initial={{ opacity: 0 }}
|
<div className="w-20 h-20 bg-gray-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
animate={{ opacity: 1 }}
|
<ShieldCheck size={40} className="text-gray-200" />
|
||||||
transition={{ delay: 0.6 }}
|
</div>
|
||||||
className="fixed bottom-0 left-0 right-0 bg-gradient-to-r from-red-600 to-rose-700 text-white py-8 text-center shadow-2xl"
|
<h3 className="text-lg font-black text-gray-900 mb-2">No Active Membership</h3>
|
||||||
>
|
<p className="text-sm text-gray-400 mb-8 max-w-[240px] mx-auto leading-relaxed">
|
||||||
<p className="text-xl font-bold">Your journey to forever is fully powered</p>
|
Unlock premium matchmaking features to find your soulmate faster.
|
||||||
<p className="text-green-200 mt-2">Enjoy unlimited matches with your Gold Plan</p>
|
</p>
|
||||||
</motion.div> */}
|
<button
|
||||||
|
onClick={() => navigate('/subscription-plan')}
|
||||||
|
className="group relative w-full py-4 bg-gray-900 text-white rounded-2xl font-black flex items-center justify-center gap-2 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-amber-400 to-orange-500 translate-y-full group-hover:translate-y-0 transition-transform duration-500" />
|
||||||
|
<span className="relative z-10">UPGRADE NOW</span>
|
||||||
|
<ArrowRight size={18} className="relative z-10 transition-transform group-hover:translate-x-1" />
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Timeline History Section */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="flex items-center gap-4 mb-8">
|
||||||
|
<div className="w-14 h-14 rounded-[20px] bg-white shadow-xl flex items-center justify-center text-gray-400">
|
||||||
|
<History fontSize="large" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-black text-gray-900">Purchase History</h2>
|
||||||
|
<p className="text-xs font-bold text-gray-400 uppercase tracking-widest">Your payment journey</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{history.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{history.map((sub, index) => (
|
||||||
|
<PlanCard key={sub.id} sub={sub} index={index + 1} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="py-20 text-center">
|
||||||
|
<div className="inline-block p-6 rounded-full bg-gray-50 mb-4">
|
||||||
|
<LocalActivity className="text-gray-200" sx={{ fontSize: 40 }} />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-gray-300 uppercase tracking-widest">No Previous History</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+272
-142
@@ -1,64 +1,177 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ArrowBackIosNew, Check, Star, AutoAwesome } from '@mui/icons-material';
|
import { ArrowBackIosNew, Check, Star, AutoAwesome, InfoOutlined } from '@mui/icons-material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { getSubscriptionPlans, purchaseSubscription } from '../api/subscription.api';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from '@mui/material';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const loadRazorpayScript = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (window.Razorpay) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = "https://checkout.razorpay.com/v1/checkout.js";
|
||||||
|
script.onload = () => resolve(true);
|
||||||
|
script.onerror = () => resolve(false);
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function SubscriptionPlan() {
|
export default function SubscriptionPlan() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isAnnual, setIsAnnual] = useState(true);
|
const queryClient = useQueryClient();
|
||||||
|
const [purchaseLoading, setPurchaseLoading] = useState(false);
|
||||||
|
const [confirmDialog, setConfirmDialog] = useState({ open: false, plan: null });
|
||||||
|
|
||||||
const plans = [
|
const { data: plansData, isLoading, error } = useQuery({
|
||||||
{
|
queryKey: ['subscriptionPlans'],
|
||||||
name: "Gold Plan",
|
queryFn: getSubscriptionPlans,
|
||||||
monthlyPrice: 1800,
|
});
|
||||||
annualPrice: 1299,
|
|
||||||
color: "from-amber-400 to-yellow-600",
|
const plans = plansData?.data || [];
|
||||||
button: "from-amber-500 to-orange-600",
|
|
||||||
features: [
|
const handlePurchase = async (plan) => {
|
||||||
"Unlimited Profile Views",
|
setPurchaseLoading(true);
|
||||||
"Send Personalized Messages",
|
try {
|
||||||
"Featured Profile for 30 Days",
|
console.log("Initiating purchase for plan:", plan.id);
|
||||||
"Priority Customer Support",
|
const res = await purchaseSubscription(plan.id);
|
||||||
"Verified Badge",
|
console.log("Purchase Initiation Response:", res);
|
||||||
"Horoscope Matching",
|
|
||||||
"100+ Daily Matches"
|
// Support both res.success (older API) and res.status === 'success' (newer API)
|
||||||
]
|
if (res.success || res.status === 'success') {
|
||||||
},
|
const data = res.data;
|
||||||
{
|
|
||||||
name: "Diamond Plan",
|
// Ensure Razorpay script is loaded
|
||||||
monthlyPrice: 2800,
|
const isLoaded = await loadRazorpayScript();
|
||||||
annualPrice: 1999,
|
if (!isLoaded || !window.Razorpay) {
|
||||||
color: "from-purple-500 to-pink-600",
|
toast.error("Razorpay SDK failed to load. Please check your internet connection.");
|
||||||
button: "from-purple-600 to-pink-700",
|
setPurchaseLoading(false);
|
||||||
popular: true,
|
return;
|
||||||
features: [
|
}
|
||||||
"Everything in Gold",
|
|
||||||
"Top of Search Results",
|
if (!data.order_id || !data.key_id) {
|
||||||
"Profile Highlight in Gold Border",
|
toast.error("Invalid order details received from server.");
|
||||||
"Personal Relationship Manager",
|
setPurchaseLoading(false);
|
||||||
"Video Call with Matches",
|
return;
|
||||||
"Astro Compatibility Report",
|
}
|
||||||
"200+ Premium Matches Daily"
|
|
||||||
]
|
const options = {
|
||||||
},
|
key: data.key_id,
|
||||||
{
|
amount: data.amount,
|
||||||
name: "Platinum Plan",
|
currency: "INR",
|
||||||
monthlyPrice: 4800,
|
name: "Thirukalyanam",
|
||||||
annualPrice: 3499,
|
description: data.plan_name || plan.plan_name,
|
||||||
color: "from-emerald-500 to-teal-600",
|
order_id: data.order_id,
|
||||||
button: "from-emerald-600 to-teal-700",
|
handler: async (response) => {
|
||||||
features: [
|
try {
|
||||||
"Everything in Diamond",
|
toast.loading("Verifying payment...", { id: "verify-payment" });
|
||||||
"Guaranteed 10+ Interests/Month",
|
const verifyRes = await purchaseSubscription(plan.id, {
|
||||||
"Exclusive VIP Events Invite",
|
razorpay_payment_id: response.razorpay_payment_id,
|
||||||
"Profile Boost Every Week",
|
razorpay_order_id: response.razorpay_order_id,
|
||||||
"Family Member Login Access",
|
razorpay_signature: response.razorpay_signature,
|
||||||
"Lifetime Profile Visibility",
|
});
|
||||||
"Dedicated Matchmaking Expert"
|
|
||||||
]
|
if (verifyRes.success || verifyRes.status === 'success') {
|
||||||
|
toast.success("Subscription activated successfully!", { id: "verify-payment" });
|
||||||
|
queryClient.invalidateQueries(['subscriptionPlans']);
|
||||||
|
queryClient.invalidateQueries(['headerDetails']);
|
||||||
|
queryClient.invalidateQueries(['dashboardDetails']);
|
||||||
|
} else {
|
||||||
|
toast.error(verifyRes.message || "Verification failed", { id: "verify-payment" });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Verification Error:", err);
|
||||||
|
toast.error("Verification failed", { id: "verify-payment" });
|
||||||
|
} finally {
|
||||||
|
setPurchaseLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prefill: {
|
||||||
|
name: data.name || "",
|
||||||
|
email: data.email || "",
|
||||||
|
contact: data.mobile || data.mobile_number || "",
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
color: "#A70710",
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
ondismiss: function() {
|
||||||
|
console.log("Payment modal closed by user");
|
||||||
|
setPurchaseLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Opening Razorpay with options:", options);
|
||||||
|
const rzp = new window.Razorpay(options);
|
||||||
|
rzp.open();
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || "Failed to initiate purchase");
|
||||||
|
setPurchaseLoading(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Purchase Error:", err);
|
||||||
|
toast.error(err.response?.data?.message || "Something went wrong during purchase initiation");
|
||||||
|
setPurchaseLoading(false);
|
||||||
}
|
}
|
||||||
];
|
};
|
||||||
|
|
||||||
|
const getPlanFeatures = (plan) => {
|
||||||
|
const features = [
|
||||||
|
{ text: `Profile Views: ${plan.count_of_profile_view === -1 ? 'Unlimited' : plan.count_of_profile_view}`, available: true },
|
||||||
|
{ text: `Validity: ${plan.plan_validity_days === -1 ? 'Unlimited' : `${plan.plan_validity_days} Days`}`, available: true },
|
||||||
|
{ text: "View Mobile Number", available: plan.can_view_mobile_number },
|
||||||
|
{ text: "View Email ID", available: plan.can_view_email },
|
||||||
|
{ text: "View Raasi", available: plan.can_view_raasi },
|
||||||
|
{ text: "View Star", available: plan.can_view_star },
|
||||||
|
{ text: "View Father Name", available: plan.can_view_father_name },
|
||||||
|
{ text: "View Mother Name", available: plan.can_view_mother_name },
|
||||||
|
{ text: "View DOB", available: plan.can_view_dob },
|
||||||
|
{ text: "View TOB", available: plan.can_view_tob },
|
||||||
|
{ text: "Start Chat", available: plan.can_start_chat },
|
||||||
|
{ text: "Send Interest", available: plan.can_send_interest },
|
||||||
|
{ text: "Accept Interest", available: plan.can_accept_interest },
|
||||||
|
];
|
||||||
|
return features;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPlanColors = (index) => {
|
||||||
|
const colors = [
|
||||||
|
{ color: "from-amber-400 to-yellow-600", button: "bg-[#A70710]" },
|
||||||
|
{ color: "from-purple-500 to-pink-600", button: "bg-[#A70710]" },
|
||||||
|
{ color: "from-emerald-500 to-teal-600", button: "bg-[#A70710]" },
|
||||||
|
{ color: "from-blue-500 to-indigo-600", button: "bg-[#A70710]" },
|
||||||
|
];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<CircularProgress sx={{ color: '#A70710' }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center text-red-500 text-center px-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-xl font-bold mb-2">Error loading plans</p>
|
||||||
|
<p>Please check your connection or try again later.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen mt-4 text-black overflow-hidden relative z-20">
|
<div className="min-h-screen mt-4 text-black overflow-hidden relative z-20">
|
||||||
@@ -70,113 +183,130 @@ export default function SubscriptionPlan() {
|
|||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-[#034E08] rounded-[15px]" />
|
<div className="absolute inset-0 bg-[#034E08] rounded-[15px]" />
|
||||||
<div className="relative flex items-center justify-between px-5 py-8 safe-area-top">
|
<div className="relative flex items-center justify-between px-5 py-8 safe-area-top">
|
||||||
|
<button onClick={() => navigate(-1)} className="text-white">
|
||||||
|
<ArrowBackIosNew />
|
||||||
|
</button>
|
||||||
<h1 className="text-2xl font-bold text-center text-white">Subscription Plan</h1>
|
<h1 className="text-2xl font-bold text-center text-white">Subscription Plan</h1>
|
||||||
<div className="w-12" />
|
<div className="w-12" />
|
||||||
</div>
|
</div>
|
||||||
</motion.header>
|
</motion.header>
|
||||||
|
|
||||||
{/* Annual / Monthly Toggle */}
|
|
||||||
<div className="flex justify-center -mt-6 relative z-10">
|
|
||||||
<motion.div
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
className="bg-[#A70710] backdrop-blur-xl rounded-full p-2 flex gap-2 shadow-2xl"
|
|
||||||
// style={{background:"linear-gradient(98.05deg, #FAFBFF 0%, #FCC4FF 100%)"}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsAnnual(true)}
|
|
||||||
className={`px-8 py-4 rounded-full font-bold text-lg transition-all ${isAnnual ? 'bg-white text-purple-900 shadow-lg' : 'text-white/80'}`}
|
|
||||||
>
|
|
||||||
ANNUAL PLAN
|
|
||||||
<span className="block text-xs font-normal mt-1 text-green-300">Save up to 40%</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsAnnual(false)}
|
|
||||||
className={`px-8 py-4 rounded-full font-bold text-lg transition-all ${!isAnnual ? 'bg-white text-purple-900 shadow-lg' : 'text-white/80'}`}
|
|
||||||
>
|
|
||||||
MONTHLY PLAN
|
|
||||||
</button>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Pricing Cards */}
|
{/* Pricing Cards */}
|
||||||
<div className="px-5 py-10 max-w-7xl mx-auto">
|
<div className="px-5 py-10 max-w-7xl mx-auto">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{plans.map((plan, index) => (
|
{plans.map((plan, index) => {
|
||||||
<motion.div
|
const planColors = getPlanColors(index);
|
||||||
key={index}
|
const features = getPlanFeatures(plan);
|
||||||
initial={{ opacity: 0, y: 50 }}
|
const isBuyable = plan.is_can_buyable === 1 && !plan.is_current_plan;
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: index * 0.2 }}
|
|
||||||
className={`relative backdrop-blur-xl rounded-3xl overflow-hidden border ${
|
|
||||||
plan.popular ? 'border-yellow-400 shadow-2xl shadow-yellow-500/30 scale-105' : 'border-white/30'
|
|
||||||
}`}
|
|
||||||
|
|
||||||
style={{background:"linear-gradient(98.05deg, #FAFBFF 0%, #FCC4FF 100%)"}}
|
return (
|
||||||
>
|
<motion.div
|
||||||
{plan.popular && (
|
key={plan.id}
|
||||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 bg-gradient-to-r from-yellow-400 to-amber-500 text-purple-900 px-8 py-2 rounded-b-2xl font-bold text-sm flex items-center gap-2">
|
initial={{ opacity: 0, y: 50 }}
|
||||||
<AutoAwesome className="text-lg" /> MOST POPULAR <AutoAwesome className="text-lg" />
|
animate={{ opacity: 1, y: 0 }}
|
||||||
</div>
|
transition={{ delay: index * 0.1 }}
|
||||||
)}
|
className={`relative backdrop-blur-xl rounded-3xl overflow-hidden border ${
|
||||||
|
plan.is_current_plan ? 'border-yellow-400 shadow-2xl scale-105' : 'border-white/30'
|
||||||
<div className="mt-6 p-8 text-center">
|
}`}
|
||||||
<h3 className="text-3xl font-bold mb-4">{plan.name}</h3>
|
style={{ background: "linear-gradient(98.05deg, #FAFBFF 0%, #FCC4FF 100%)" }}
|
||||||
<div className="text-[25px] font-bold mb-2">
|
>
|
||||||
₹{isAnnual ? plan.annualPrice.toLocaleString() : plan.monthlyPrice.toLocaleString()}
|
{plan.is_current_plan && (
|
||||||
<span className="text-[18px] font-normal text-black"> /{isAnnual ? 'month' : 'month'}</span>
|
<div className="absolute -top-1 left-1/2 -translate-x-1/2 bg-gradient-to-r from-yellow-400 to-amber-500 text-purple-900 px-8 py-2 rounded-b-2xl font-bold text-sm flex items-center gap-2">
|
||||||
</div>
|
<Star className="text-lg" /> CURRENT PLAN <Star className="text-lg" />
|
||||||
{isAnnual && (
|
</div>
|
||||||
<p className="text-[#00903F] font-bold text-lg mb-6">
|
|
||||||
Billed annually @ ₹{(plan.annualPrice * 12).toLocaleString()}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<motion.button
|
<div className="mt-6 p-8 text-center">
|
||||||
whileHover={{ scale: 1.05 }}
|
<h3 className="text-2xl font-bold mb-4">{plan.plan_name}</h3>
|
||||||
whileTap={{ scale: 0.95 }}
|
<div className="text-[32px] font-bold mb-2">
|
||||||
className={`w-[fit-content] py-4 px-8 rounded-full text-white font-semibold text-[16px] shadow-xl bg-gradient-to-r ${plan.button} hover:shadow-2xl transition-all`}
|
₹{plan.plan_amount}
|
||||||
style={{background:"#A70710"}}
|
<span className="text-[18px] font-normal text-gray-600">
|
||||||
>
|
/{plan.plan_validity_days === -1 ? 'Lifetime' : `${plan.plan_validity_days} days`}
|
||||||
Select Plan
|
</span>
|
||||||
</motion.button>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 text-left space-y-4">
|
<motion.button
|
||||||
<h4 className="text-xl font-bold text-black">Features</h4>
|
disabled={!isBuyable || purchaseLoading}
|
||||||
{plan.features.map((feature, i) => (
|
whileHover={isBuyable ? { scale: 1.05 } : {}}
|
||||||
<div key={i} className="flex items-center gap-3">
|
whileTap={isBuyable ? { scale: 0.95 } : {}}
|
||||||
<Check className="text-green-400 text-2xl" />
|
onClick={() => setConfirmDialog({ open: true, plan })}
|
||||||
<span className="text-black">{feature}</span>
|
className={`w-full py-4 px-8 rounded-xl text-white font-semibold text-[16px] shadow-xl transition-all ${
|
||||||
|
isBuyable ? planColors.button : 'bg-gray-400 cursor-not-allowed opacity-70'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{purchaseLoading ? <CircularProgress size={24} color="inherit" /> :
|
||||||
|
plan.is_current_plan ? "Current Plan" : "Select Plan"}
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<div className="mt-8 text-left space-y-3">
|
||||||
|
<h4 className="text-lg font-bold text-black border-b border-gray-200 pb-2">Features</h4>
|
||||||
|
<div className="max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
|
||||||
|
{features.map((feature, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-3 py-1">
|
||||||
|
<div className={`rounded-full p-0.5 ${feature.available ? 'bg-green-100' : 'bg-red-100'}`}>
|
||||||
|
<Check className={`text-xl ${feature.available ? 'text-green-600' : 'text-red-600'}`}
|
||||||
|
sx={{ fontSize: 16, visibility: feature.available ? 'visible' : 'hidden' }} />
|
||||||
|
{!feature.available && <span className="w-4 h-4 flex items-center justify-center text-red-600 font-bold text-xs">✕</span>}
|
||||||
|
</div>
|
||||||
|
<span className={`text-sm ${feature.available ? 'text-black' : 'text-gray-400 line-through'}`}>
|
||||||
|
{feature.text}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</motion.div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Final CTA */}
|
<div className="text-center mt-12 mb-8">
|
||||||
{/* <motion.div
|
<p className="text-gray-600 max-w-2xl mx-auto italic">
|
||||||
initial={{ opacity: 0 }}
|
Choose the plan that's right for you. Find your perfect life partner with our premium plans.
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.8 }}
|
|
||||||
className="text-center mt-16"
|
|
||||||
>
|
|
||||||
<h2 className="text-3xl font-bold mb-4">Find Your Perfect Life Partner</h2>
|
|
||||||
<p className="text-xl text-white/80 max-w-2xl mx-auto mb-10">
|
|
||||||
Choose the plan that’s right for your sacred journey. Every premium member gets closer to their soulmate.
|
|
||||||
</p>
|
</p>
|
||||||
<motion.button
|
</div>
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
className="bg-gradient-to-r from-red-600 to-rose-700 px-12 py-6 rounded-full text-2xl font-bold shadow-2xl hover:shadow-red-500/50 transition-all"
|
|
||||||
>
|
|
||||||
Choose This Plan
|
|
||||||
</motion.button>
|
|
||||||
</motion.div> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-32" />
|
{/* Confirmation Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={confirmDialog.open}
|
||||||
|
onClose={() => setConfirmDialog({ open: false, plan: null })}
|
||||||
|
PaperProps={{
|
||||||
|
sx: { borderRadius: '20px', padding: '10px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1, color: '#1B5E20', fontWeight: 'bold' }}>
|
||||||
|
<InfoOutlined /> Note
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<p className="text-[#DF1D46] font-semibold text-center text-lg">
|
||||||
|
If you choose a new plan, your current plan will expire and the new plan will be activated.
|
||||||
|
</p>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ justifyContent: 'center', gap: 2, pb: 3 }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setConfirmDialog({ open: false, plan: null })}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ borderRadius: '10px', color: 'gray', borderColor: 'gray' }}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const plan = confirmDialog.plan;
|
||||||
|
setConfirmDialog({ open: false, plan: null });
|
||||||
|
handlePurchase(plan);
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
sx={{ borderRadius: '10px', bgcolor: '#A70710', '&:hover': { bgcolor: '#8e060d' } }}
|
||||||
|
>
|
||||||
|
Ok
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<div className="h-20" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,15 @@ 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"));
|
||||||
const VideoSwiperGallery = lazy(() => import("../components/profiledashboard/VideoSwiperGallery"));
|
const VideoSwiperGallery = lazy(() => import("../components/profiledashboard/VideoSwiperGallery"));
|
||||||
const Profilecardemo = lazy(() => import("../components/ui/ProfileCardDemo"));
|
const Profilecardemo = lazy(() => import("../components/ui/ProfileCardDemo"));
|
||||||
|
const DailyRecommendedCard = lazy(() => import("../components/profiledashboard/DailyRecommendedCard"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const images = [
|
const images = [
|
||||||
@@ -48,6 +51,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 +104,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"}
|
||||||
@@ -120,114 +132,117 @@ const UserDashboardHome = () => {
|
|||||||
|
|
||||||
|
|
||||||
<style>{`
|
<style>{`
|
||||||
.custom-swiper-hero .swiper {
|
.custom-swiper-hero .swiper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= MOBILE (≤768px) ================= */
|
/* ================= MOBILE (≤768px) ================= */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.custom-swiper-hero .swiper {
|
.custom-swiper-hero .swiper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide {
|
.custom-swiper-hero .swiper-slide {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
.custom-swiper-hero .swiper-slide-active {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 200px !important;
|
height: 200px !important;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= TABLET & DESKTOP (≥769px) ================= */
|
/* ================= TABLET & DESKTOP (≥769px) ================= */
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
.custom-swiper-hero .swiper {
|
.custom-swiper-hero .swiper {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-wrapper {
|
.custom-swiper-hero .swiper-wrapper {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide {
|
.custom-swiper-hero .swiper-slide {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 320px;
|
height: 320px;
|
||||||
transform: scale(1); /* ❌ removed zoom out */
|
transform: scale(1); /* ❌ removed zoom out */
|
||||||
opacity: 1; /* ❌ removed fade */
|
opacity: 1; /* ❌ removed fade */
|
||||||
transition: all 0.4s ease;
|
transition: all 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide-active {
|
.custom-swiper-hero .swiper-slide-active {
|
||||||
width: 630px !important; /* center slide bigger */
|
width: 630px !important; /* center slide bigger */
|
||||||
height: 320px !important;
|
height: 320px !important;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= COMMON ================= */
|
/* ================= COMMON ================= */
|
||||||
.custom-swiper-hero .swiper-slide > div {
|
.custom-swiper-hero .swiper-slide > div {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-swiper-hero .swiper-slide img {
|
.custom-swiper-hero .swiper-slide img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Desktop Styles */
|
/* Desktop Styles */
|
||||||
// @media (min-width: 1024px) {
|
// @media (min-width: 1024px) {
|
||||||
// .custom-swiper-hero .swiper-slide {
|
// .custom-swiper-hero .swiper-slide {
|
||||||
// width: 100%;
|
// width: 100%;
|
||||||
// height: 400px; /* Adjust the height for desktop */
|
// height: 400px; /* Adjust the height for desktop */
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// .custom-swiper-hero .swiper-slide-active {
|
// .custom-swiper-hero .swiper-slide-active {
|
||||||
// width: 100% !important;
|
// width: 100% !important;
|
||||||
// height: 400px !important;
|
// height: 400px !important;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={<SectionFallback height={320} />}>
|
<Suspense fallback={<SectionFallback height={320} />}>
|
||||||
<Profilecardemo />
|
<DailyRecommendedCard profiles={dashboardData?.daily_recommended} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
{/* <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/> */}
|
|
||||||
|
|
||||||
|
|
||||||
<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 {
|
||||||
|
|||||||
+40
-96
@@ -1,29 +1,28 @@
|
|||||||
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: "",
|
||||||
smokingHabits: "Doesn't matter",
|
|
||||||
drinkingHabits: "Doesn't matter",
|
page: 1,
|
||||||
familyType: "Any",
|
isPaidMember: false,
|
||||||
familyStatus: "Any",
|
search: "",
|
||||||
familyValue: "Any",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterSlice = createSlice({
|
const filterSlice = createSlice({
|
||||||
@@ -31,73 +30,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 +55,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 +72,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,53 +5,84 @@ 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: [],
|
dayOfBirth: "",
|
||||||
hobbies: [],
|
raasi: "",
|
||||||
|
star: "",
|
||||||
|
patham: "",
|
||||||
|
lagnam: "",
|
||||||
|
panjangam_type: "",
|
||||||
|
dasa_balance: "",
|
||||||
|
dasa_years: "",
|
||||||
|
dasa_months: "",
|
||||||
|
dasa_days: "",
|
||||||
dob: "",
|
dob: "",
|
||||||
tob: "",
|
tob: "",
|
||||||
placeOfBirth: "",
|
placeOfBirth: "",
|
||||||
@@ -85,13 +116,22 @@ const registrationformSlice = createSlice({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
partnerPreferences: {
|
partnerPreferences: {
|
||||||
ageRange: "",
|
age_from: "",
|
||||||
|
age_to: "",
|
||||||
|
height_from: "",
|
||||||
|
height_to: "",
|
||||||
|
marital_statuses: [],
|
||||||
|
birth_stars: [],
|
||||||
castes: [],
|
castes: [],
|
||||||
subCastes: [],
|
sub_castes: [],
|
||||||
occupations: [],
|
|
||||||
educations: [],
|
educations: [],
|
||||||
hobbies: [],
|
occupations: [],
|
||||||
annualIncome: "",
|
employee_types: [],
|
||||||
|
currencies: [],
|
||||||
|
inr_from: "",
|
||||||
|
inr_to: "",
|
||||||
|
usd_from: "",
|
||||||
|
usd_to: "",
|
||||||
states: [],
|
states: [],
|
||||||
districts: [],
|
districts: [],
|
||||||
},
|
},
|
||||||
@@ -114,14 +154,43 @@ 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 = {
|
||||||
|
dayOfBirth: "", raasi: "", star: "", patham: "", lagnam: "", panjangam_type: "",
|
||||||
|
dasa_balance: "", dasa_years: "", dasa_months: "", dasa_days: "",
|
||||||
|
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 = {
|
||||||
|
age_from: "", age_to: "", height_from: "", height_to: "",
|
||||||
|
marital_statuses: [], birth_stars: [], castes: [], sub_castes: [],
|
||||||
|
educations: [], occupations: [], employee_types: [],
|
||||||
|
currencies: [], inr_from: "", inr_to: "", usd_from: "", usd_to: "",
|
||||||
|
states: [], districts: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@@ -219,8 +288,16 @@ preloadDummyProfile: (state) => {
|
|||||||
};
|
};
|
||||||
state.lifestyleDetails = {
|
state.lifestyleDetails = {
|
||||||
...state.lifestyleDetails,
|
...state.lifestyleDetails,
|
||||||
diets: [1],
|
dayOfBirth: "Monday",
|
||||||
hobbies: [1, 3],
|
raasi: 1,
|
||||||
|
star: 1,
|
||||||
|
patham: "1",
|
||||||
|
lagnam: 1,
|
||||||
|
panjangam_type: "Thirukanitham",
|
||||||
|
dasa_balance: "SURIYAN",
|
||||||
|
dasa_years: "5",
|
||||||
|
dasa_months: "2",
|
||||||
|
dasa_days: "10",
|
||||||
dob: "1995-05-01",
|
dob: "1995-05-01",
|
||||||
tob: "09:30",
|
tob: "09:30",
|
||||||
placeOfBirth: "Chennai",
|
placeOfBirth: "Chennai",
|
||||||
@@ -255,13 +332,20 @@ preloadDummyProfile: (state) => {
|
|||||||
};
|
};
|
||||||
state.partnerPreferences = {
|
state.partnerPreferences = {
|
||||||
...state.partnerPreferences,
|
...state.partnerPreferences,
|
||||||
ageRange: 2,
|
age_from: 25,
|
||||||
|
age_to: 30,
|
||||||
|
height_from: 1,
|
||||||
|
height_to: 10,
|
||||||
|
marital_statuses: [11],
|
||||||
|
birth_stars: [1, 2],
|
||||||
castes: [1],
|
castes: [1],
|
||||||
subCastes: [1],
|
sub_castes: [1],
|
||||||
occupations: [57],
|
occupations: [57],
|
||||||
educations: [14],
|
educations: [14],
|
||||||
hobbies: [1, 3],
|
employee_types: [3],
|
||||||
annualIncome: 1,
|
currencies: ["INR"],
|
||||||
|
inr_from: "100000",
|
||||||
|
inr_to: "120000",
|
||||||
states: [31],
|
states: [31],
|
||||||
districts: [1],
|
districts: [1],
|
||||||
};
|
};
|
||||||
@@ -279,6 +363,7 @@ export const {
|
|||||||
updateFamilyDetails,
|
updateFamilyDetails,
|
||||||
updateLifestyleDetails,
|
updateLifestyleDetails,
|
||||||
updatePartnerPreferences,
|
updatePartnerPreferences,
|
||||||
|
clearAllStepsFrom,
|
||||||
submitForm,
|
submitForm,
|
||||||
preloadDummyProfile,
|
preloadDummyProfile,
|
||||||
} = registrationformSlice.actions;
|
} = registrationformSlice.actions;
|
||||||
|
|||||||
+31
-2
@@ -1,9 +1,38 @@
|
|||||||
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, "registerform/updatePersonalDetails"],
|
||||||
|
ignoredActionPaths: ["payload.profiles", "payload.file"],
|
||||||
|
ignoredPaths: ["registerform.personalDetails.profiles"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 />}>
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ const UserRoutes = () => {
|
|||||||
|
|
||||||
<Route element={<ProfileLayout />}>
|
<Route element={<ProfileLayout />}>
|
||||||
<Route path="/chat" element={<ChatUI />} />
|
<Route path="/chat" element={<ChatUI />} />
|
||||||
|
<Route path="/chat/:chatId" element={<ChatUI />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Route element={<ProfileLayout />}>
|
<Route element={<ProfileLayout />}>
|
||||||
<Route path="/horoscoper-generate" element={<HoroscopeGenerator />} />
|
<Route path="/horoscoper-generate" element={<HoroscopeGenerator />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCallHistoryMyList = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(`call_my_list`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching my call history:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCallHistoryOthersList = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get(`call_others_list`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching others call history:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteCallHistory = async (ids) => {
|
||||||
|
try {
|
||||||
|
const queryStr = ids.map((id, index) => `ids[${index}]=${id}`).join('&');
|
||||||
|
const response = await axiosInstance.get(`call_list_delete?${queryStr}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting call history:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteChats = async (ids) => {
|
||||||
|
try {
|
||||||
|
const queryStr = ids.map((id, index) => `ids[${index}]=${id}`).join('&');
|
||||||
|
const response = await axiosInstance.post(`chat/delete?${queryStr}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting chats:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
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(`${API_ENDPOINTS.BLOCK_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 (id, status) => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.post(`${API_ENDPOINTS.UPDATE_INTEREST_STATUS}?id=${id}&status=${status}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating interest status:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reportProfile = async (profileId, reason) => {
|
||||||
|
const response = await axiosInstance.post(`${API_ENDPOINTS.REPORT_PROFILE}?profile_id=${profileId}&reason=${reason}`);
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to report");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const blockProfile = async (profileId, reason) => {
|
||||||
|
const response = await axiosInstance.post(`${API_ENDPOINTS.BLOCK_PROFILE}?profile_id=${profileId}&reason=${reason}`);
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to block");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import axiosInstance, { apiForFiles } from "../api/axiosInstance";
|
||||||
|
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||||
|
|
||||||
|
export const shortlistProfile = async (profileId) => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("profile_id", profileId);
|
||||||
|
const response = await apiForFiles.post(API_ENDPOINTS.SHORTLIST_API, formData);
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to shortlist");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
const message = error.response?.data?.message || error.message || "Failed to shortlist";
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendInterest = async (profileId) => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("profile_id", profileId);
|
||||||
|
|
||||||
|
// Using apiForFiles which is pre-configured for multipart/form-data
|
||||||
|
// This is more robust for PHP backends expecting form data
|
||||||
|
const response = await apiForFiles.post(API_ENDPOINTS.INTEREST_SEND, formData);
|
||||||
|
|
||||||
|
if (response.data?.status === "error") {
|
||||||
|
throw new Error(response.data.message || "Failed to send interest");
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
const message = error.response?.data?.message || error.message || "Failed to send interest";
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),],
|
||||||
@@ -10,4 +15,14 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "src"),
|
"@": path.resolve(__dirname, "src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/backend': {
|
||||||
|
target: 'https://www.thirukalyanam.amrithaa.net',
|
||||||
|
changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/backend/, '/backend') // keep /backend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Reference in new issue
Block a user