Compare commits
10 Commits
65bd6c646b
...
4ba4ce1e1b
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ba4ce1e1b | |||
| c467271927 | |||
| 68f97c40dc | |||
| cd880e10e5 | |||
| ccf638f7f3 | |||
| 9427677a72 | |||
| 5392a4211e | |||
| 805c93b3f3 | |||
| 8f6ddbcb2c | |||
| 135f6bba48 |
9
.gitignore
vendored
9
.gitignore
vendored
@ -22,3 +22,12 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Build artifacts
|
||||
dist.zip
|
||||
|
||||
|
||||
62
README.md
62
README.md
@ -1,16 +1,60 @@
|
||||
# React + Vite
|
||||
# Thirukalyanam Web Application
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
A modern matrimonial web application built with React, Vite, and Tailwind CSS.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## Live Application
|
||||
- [Live Site](https://www.thirukalyanam.amrithaa.net/)
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||
- [@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
|
||||
## Features
|
||||
- Multi-step Registration Form (Personal, Educational, Family, Lifestyle, Partner Preferences)
|
||||
- User Dashboard
|
||||
- Real-time Chat (WebSocket)
|
||||
- Profile Filtering and Matching
|
||||
- Notification System
|
||||
|
||||
## React Compiler
|
||||
## Tech Stack
|
||||
- **Frontend**: React 19, Vite
|
||||
- **Styling**: Tailwind CSS v4, Material UI
|
||||
- **State Management**: Redux Toolkit, Redux Persist
|
||||
- **Data Fetching**: React Query (TanStack Query)
|
||||
- **Icons**: Lucide React, Material Icons
|
||||
- **Notifications**: Firebase Cloud Messaging
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
## Getting Started
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
### Prerequisites
|
||||
- Node.js (v18 or higher)
|
||||
- npm or yarn
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
### Installation
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://gitpro.amrithaa.com/mageshwaran/thirukalyanamweb.git
|
||||
```
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. Create a `.env` file and add the API base URL:
|
||||
```env
|
||||
VITE_THIRUKALYANAM_API_BASE_URL=https://www.thirukalyanam.amrithaa.net/backend/api/
|
||||
```
|
||||
|
||||
### Development
|
||||
Run the development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Production Build
|
||||
Generate a production-ready build:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
Preview the build locally:
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## License
|
||||
Proprietary. All rights reserved.
|
||||
|
||||
88
package-lock.json
generated
88
package-lock.json
generated
@ -31,6 +31,9 @@
|
||||
"react-lazy-load-image-component": "^1.6.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"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",
|
||||
"tailwindcss": "^4.1.17"
|
||||
},
|
||||
@ -1719,6 +1722,31 @@
|
||||
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
|
||||
"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": {
|
||||
"version": "1.9.15",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
|
||||
@ -7139,6 +7167,12 @@
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@ -8053,6 +8087,27 @@
|
||||
"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": {
|
||||
"version": "4.0.4",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -8205,6 +8270,15 @@
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||
@ -9069,6 +9143,20 @@
|
||||
"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": {
|
||||
"version": "1.6.0",
|
||||
"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-redux": "^9.2.0",
|
||||
"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",
|
||||
"tailwindcss": "^4.1.17"
|
||||
},
|
||||
|
||||
@ -1,57 +1,71 @@
|
||||
export const API_ENDPOINTS = {
|
||||
LOGOUT: "logout",
|
||||
TERMS_AND_POLICIES_PRIVACY:"terms-and-policies",
|
||||
// registration api's
|
||||
PERSONAL_DETAILS_MASTER :"personal_details_masters",
|
||||
CASTE_MASTER : "get_caste_masters",
|
||||
SUB_CASTE_MASTER : "get_sub_caste_masters",
|
||||
CITY_MASTER : "get_district_masters",
|
||||
STAR_MASTER : "get_star_masters",
|
||||
MOBILE_SEND_OTP: "send_otp",
|
||||
MOBILE_VERIFY_OTP: "verify_otp",
|
||||
TERMS_AND_POLICIES_PRIVACY: "terms-and-policies",
|
||||
// registration api's
|
||||
PERSONAL_DETAILS_MASTER: "personal_details_masters",
|
||||
CASTE_MASTER: "get_caste_masters",
|
||||
SUB_CASTE_MASTER: "get_sub_caste_masters",
|
||||
CITY_MASTER: "get_district_masters",
|
||||
STAR_MASTER: "get_star_masters",
|
||||
MOBILE_SEND_OTP: "send_otp",
|
||||
MOBILE_VERIFY_OTP: "verify_otp",
|
||||
|
||||
EDUCATION_DETAILS_MASTER: "educational_details_masters",
|
||||
EDUCATION_LIST_API:"get_education",
|
||||
EDUCATION_DETAILS_MASTER: "educational_details_masters",
|
||||
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_STEP2:"update_educational_details", // educational details updated api
|
||||
REGSITER_STEP3:"update_family_details", // family details updated api
|
||||
REGISTER_STEP4:"update_lifestyle_details", // lifestyle details updated api
|
||||
REGISTER_STEP5:"update_preferred_details", // partner preference details updated api
|
||||
PREVIEW_DETAILS: "get_preview_details",
|
||||
REVIEWS: "reviews",
|
||||
REGISTER_STEP1: "register", // register api
|
||||
REGISTER_STEP2: "update_educational_details", // educational details updated api
|
||||
REGSITER_STEP3: "update_family_details", // family details updated api
|
||||
REGISTER_STEP4: "update_lifestyle_details", // lifestyle details updated api
|
||||
REGISTER_STEP5: "update_preferred_details", // partner preference details updated api
|
||||
PREVIEW_DETAILS: "get_preview_details",
|
||||
REVIEWS: "reviews",
|
||||
|
||||
// edit api's autopapulated
|
||||
// edit api's autopapulated
|
||||
|
||||
EDIT_PERSONAL_DETAILS: "get_personal_details",
|
||||
EDIT_EDUCATION_DETAILS: "get_educational_details",
|
||||
EDIT_FAMILY_DETAILS: "get_family_details",
|
||||
EDIT_LIFESTYLE_DETAILS: "get_lifestyle_details",
|
||||
EDIT_PREFERED_PARTNER_DETAILS: "get_preferred_details",
|
||||
EDIT_PERSONAL_DETAILS: "get_personal_details",
|
||||
EDIT_EDUCATION_DETAILS: "get_educational_details",
|
||||
EDIT_FAMILY_DETAILS: "get_family_details",
|
||||
EDIT_LIFESTYLE_DETAILS: "get_lifestyle_details",
|
||||
EDIT_PREFERED_PARTNER_DETAILS: "get_preferred_details",
|
||||
|
||||
// delete api
|
||||
// delete api
|
||||
|
||||
DELETE_ACCOUNT: "delete_account",
|
||||
PHONE_NUMBER_VISIBILITY: "get_phone_number_visibility",
|
||||
UPDATE_PHONE_NUMBER_VISIBILITY: "update_phone_number_visibility",
|
||||
CHAT_ALERT_NOTIFICATION:"get_chat_alert_notification",
|
||||
UPDATE_CHAT_ALERT_NOTIFICATION:"update_chat_alert_notification",
|
||||
PROFILE_PROTECT_API:"get_profile_protection",
|
||||
UPDATE_PROFILE_PROTECT_API:"update_profile_protection",
|
||||
MATCH_ALERT:"get_match_alert",
|
||||
UPDATE_MATCH_ALERT:"update_match_alert",
|
||||
WHO_CAN_VIEW_MESSAGE:"get_who_can_message_me",
|
||||
UPDATE_WHO_CAN_VIEW_MESSAGE:"update_who_can_message_me",
|
||||
CONTACT_US:"get_contact_us",
|
||||
BE_SAFE_ONLINE:"get_be_safe_online",
|
||||
NOTIFICATION_LIST:"notification/lists",
|
||||
NOTIFICATION_COUNT:"notification/un_read_count",
|
||||
DELETE_ACCOUNT: "delete_account",
|
||||
PHONE_NUMBER_VISIBILITY: "get_phone_number_visibility",
|
||||
UPDATE_PHONE_NUMBER_VISIBILITY: "update_phone_number_visibility",
|
||||
CHAT_ALERT_NOTIFICATION: "get_chat_alert_notification",
|
||||
UPDATE_CHAT_ALERT_NOTIFICATION: "update_chat_alert_notification",
|
||||
PROFILE_PROTECT_API: "get_profile_protection",
|
||||
UPDATE_PROFILE_PROTECT_API: "update_profile_protection",
|
||||
MATCH_ALERT: "get_match_alert",
|
||||
UPDATE_MATCH_ALERT: "update_match_alert",
|
||||
WHO_CAN_VIEW_MESSAGE: "get_who_can_message_me",
|
||||
UPDATE_WHO_CAN_VIEW_MESSAGE: "update_who_can_message_me",
|
||||
CONTACT_US: "get_contact_us",
|
||||
BE_SAFE_ONLINE: "get_be_safe_online",
|
||||
NOTIFICATION_LIST: "notification/lists",
|
||||
NOTIFICATION_COUNT: "notification/un_read_count",
|
||||
|
||||
// filter with profiles list api's
|
||||
PROFILES_FILTER_LIST: "profiles/lists",
|
||||
PROFILES_FILTER_MASTER: "profiles/filter/masters",
|
||||
|
||||
DASHBOARD_API: "dashboard",
|
||||
HEADER_API: "header_data",
|
||||
SHORTLIST_API: "shortlist_profile",
|
||||
BLOCK_PROFILE_LIST: "block_profile_list",
|
||||
REPORT_PROFILE_LIST: "report_profile_list",
|
||||
PROFILE_DETAIL: "profiles/detail",
|
||||
INTEREST_LIST: "interest_lists",
|
||||
UPDATE_INTEREST_STATUS: "update_interest_status",
|
||||
CHAT_LIST: "chat/lists",
|
||||
CHAT_MESSAGES: (id) => `chat/${id}/messages`,
|
||||
UNREAD_CHAT_COUNT: "chat/un_read_chat_count",
|
||||
};
|
||||
|
||||
7
src/api/dashboard.api.js
Normal file
7
src/api/dashboard.api.js
Normal file
@ -0,0 +1,7 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
import { API_ENDPOINTS } from "./apiEndpoints";
|
||||
|
||||
export const getDashboardDetails = async () => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.DASHBOARD_API);
|
||||
return res.data;
|
||||
};
|
||||
@ -63,3 +63,17 @@ export const getPartnerPreferenceMasters = async () => {
|
||||
);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
|
||||
// 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);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
|
||||
export const getHeaderDetails = async () => {
|
||||
const res = await axiosInstance.get(API_ENDPOINTS.HEADER_API);
|
||||
return res.data;
|
||||
}
|
||||
BIN
src/assets/images/horoscopeimg.png
Normal file
BIN
src/assets/images/horoscopeimg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 788 KiB |
BIN
src/assets/images/kiridam.png
Normal file
BIN
src/assets/images/kiridam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 569 B |
@ -242,7 +242,7 @@ const navigate = useNavigate();
|
||||
>
|
||||
<div onClick={(e) => e.stopPropagation()} className="bg-white rounded-2xl shadow-xl overflow-hidden select-none">
|
||||
<div 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
|
||||
src={profile.image}
|
||||
|
||||
20
src/components/common/ProfileCardSkeleton.jsx
Normal file
20
src/components/common/ProfileCardSkeleton.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
export default function ProfileCardSkeleton() {
|
||||
return (
|
||||
<div className="w-full max-w-sm rounded-[10px] border shadow-lg p-4 animate-pulse">
|
||||
|
||||
<div className="w-full h-[280px] bg-gray-200 rounded mb-4"></div>
|
||||
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
|
||||
|
||||
<div className="h-3 bg-gray-200 rounded w-1/2 mb-3"></div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="h-3 bg-gray-200 rounded"></div>
|
||||
<div className="h-3 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
142
src/components/common/ProfileCardUI.jsx
Normal file
142
src/components/common/ProfileCardUI.jsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { useState } from "react";
|
||||
import { Crown, Bookmark, Receipt, Sparkles, MoonStar, IdCard } from "lucide-react";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import { motion } from "framer-motion";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function ProfileCardUI({ profile }) {
|
||||
const [isLiked, setIsLiked] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Map API fields to UI, handling missing values
|
||||
const imageSrc = profile.photo || profile.image || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200 bg-white cursor-pointer hover:shadow-2xl transition-all duration-300"
|
||||
>
|
||||
<div className="relative">
|
||||
{/* Premium Badge */}
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring" }}
|
||||
className="absolute top-4 left-4 z-10 bg-red-900 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Shortlist logic here
|
||||
}}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Bookmark className="w-4 h-4 text-gray-600" />
|
||||
<span className="text-[12px] font-medium text-gray-700">Shortlist</span>
|
||||
</motion.button>
|
||||
|
||||
<div className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]">
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover bg-gray-200"
|
||||
style={{ objectPosition: "top" }}
|
||||
onError={(e) => {
|
||||
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Gradient Overlay */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-24 pointer-events-none" style={{ background: "linear-gradient(to top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 100%)" }}></div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-2 text-gray-900">
|
||||
<h1 className="text-[18px] text-green-900 font-bold mb-1 truncate">{profile.name}</h1>
|
||||
<p className="text-[14px] text-gray-700 leading-relaxed font-medium">ID: {profile.member_id || profile.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-4 pt-2 pb-4 flex flex-col gap-3 bg-white">
|
||||
<div className="flex items-center gap-2 text-gray-600">
|
||||
<VisibilityIcon sx={{ fontSize: 18 }} />
|
||||
<span className="text-[13px] font-medium">Last seen: {profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"}</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-y-2 gap-x-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon sx={{ fontSize: 18, color: "#374151" }} />
|
||||
<span className="text-[14px] font-semibold text-gray-900">{profile.age ? `${profile.age} yrs` : "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<AccessibilityNewIcon sx={{ fontSize: 18, color: "#374151" }} />
|
||||
<span className="text-[14px] font-semibold text-gray-900">{profile.height ? `${profile.height} cm` : "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 col-span-2">
|
||||
<Receipt className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-semibold text-gray-900 truncate">{profile.annual_income_name || "N/A"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-gray-700">
|
||||
<div className="flex items-center gap-1.5" title="Raasi">
|
||||
<MoonStar className="w-4 h-4" />
|
||||
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.raasi_name || "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5" title="Star">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.star_name || "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5" title="Caste">
|
||||
<IdCard className="w-4 h-4" />
|
||||
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.caste_name || "-"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon sx={{ fontSize: 18, color: "#DC2626" }} />
|
||||
<span className="text-[14px] font-semibold text-gray-900 truncate">
|
||||
{profile.district_name || profile.location || "-"}
|
||||
{profile.state_name ? `, ${profile.state_name}` : ""}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 mt-2">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); }}
|
||||
className="flex-1 flex items-center justify-center gap-2 px-4 py-2 bg-red-50 border border-red-200 text-red-700 rounded-full font-medium text-sm hover:bg-red-100 transition-colors active:scale-95"
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-full font-medium text-sm border transition-colors active:scale-95 ${isLiked ? "bg-green-100 border-green-300 text-green-700" : "bg-green-50 border-green-200 text-green-700 hover:bg-green-100"}`}
|
||||
onClick={(e) =>{ e.stopPropagation(); setIsLiked(!isLiked); }}
|
||||
>
|
||||
{isLiked ? (
|
||||
<>
|
||||
<svg className="w-4 h-4 text-red-500 fill-current" viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
|
||||
Sent
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
Interest
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -4,6 +4,7 @@ import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import SwipeableDrawer from "@mui/material/SwipeableDrawer";
|
||||
import { useWebSocket } from "../../hooks/useWebSocket";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
@ -19,7 +20,7 @@ import Button from "@mui/material/Button";
|
||||
import LazyImage from "./LazyImage";
|
||||
import Logo from "../../assets/images/logo.png";
|
||||
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 { Home, Users, Heart, MessageCircle, Search, Bell } from "lucide-react";
|
||||
import { isAuthenticated } from "../../utills/auth";
|
||||
@ -27,13 +28,15 @@ import userimg from "../../assets/images/bride1.jpg"
|
||||
import axiosInstance, { logoutAPI } from "../../api/axiosInstance";
|
||||
import toast from "react-hot-toast";
|
||||
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 = [
|
||||
// { label: "Home", path: "/" },
|
||||
{ label: "Matches", path: "/matches" },
|
||||
// { label: "ProfileCard", path: "/profile-card" },
|
||||
{ label: "Interest", path: "/interest" },
|
||||
{ label: "Horoscope", path: "/horoscoper-generate" },
|
||||
// { label: "Horoscope", path: "/horoscoper-generate" },
|
||||
{ label: "Messages", path: "/chat" },
|
||||
{ label: "Search", path: "/matches" },
|
||||
{ label: "Notifications", path: "/notifications" }
|
||||
@ -158,6 +161,15 @@ const ProfileHeader = () => {
|
||||
const [profileDrawerOpen, setProfileDrawerOpen] = useState(false);
|
||||
const [deleteModalOpen, setDeleteModalOpen] = 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 isDesktop = useMediaQuery(theme.breakpoints.up("md"));
|
||||
@ -179,11 +191,105 @@ const ProfileHeader = () => {
|
||||
return res.data;
|
||||
},
|
||||
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 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) => {
|
||||
if (item.action === "delete") {
|
||||
@ -200,7 +306,7 @@ const ProfileHeader = () => {
|
||||
|
||||
const deleteAccountMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
return await axiosInstance.delete(API_ENDPOINTS.DELETE_ACCOUNT);
|
||||
return await axiosInstance.post(API_ENDPOINTS.DELETE_ACCOUNT);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
toast.success(response?.data?.message || "Account deleted successfully");
|
||||
@ -310,10 +416,17 @@ const ProfileHeader = () => {
|
||||
{getNavIcon(index)}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={
|
||||
<div className="flex items-center justify-between">
|
||||
{label}
|
||||
<div className="flex items-center justify-between w-full pr-4">
|
||||
<span>{label}</span>
|
||||
{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>
|
||||
} />
|
||||
@ -359,7 +472,10 @@ const ProfileHeader = () => {
|
||||
items={NAV_LINKS.map(link => link.label)}
|
||||
color="#034E08"
|
||||
activeItem={currentLabel}
|
||||
badges={{ "Notifications": notificationCount }}
|
||||
badges={{
|
||||
"Notifications": notificationCount,
|
||||
"Messages": chatCount
|
||||
}}
|
||||
onItemClick={(item) => {
|
||||
setSelectedItem(item);
|
||||
const link = NAV_LINKS.find(l => l.label === item);
|
||||
@ -369,16 +485,16 @@ const ProfileHeader = () => {
|
||||
</Box>
|
||||
|
||||
{(auth ? (
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Box key="user-menu-box" sx={{ flexGrow: 0 }}>
|
||||
<Tooltip title="Account Menu">
|
||||
<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>
|
||||
</Tooltip>
|
||||
</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>))}
|
||||
|
||||
|
||||
|
||||
@ -52,10 +52,6 @@ export const SkeletonText = ({
|
||||
height = 12,
|
||||
className = "",
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
injectSkeletonStyles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className} style={{ display: "grid", gap }}>
|
||||
{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;
|
||||
|
||||
@ -125,7 +125,7 @@ const AppPromoteSection = () => {
|
||||
className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-16 max-w-6xl mx-auto"
|
||||
>
|
||||
{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
|
||||
colorFrom="#ff0000ff"
|
||||
colorTo="#338105ff"
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { Crown, Bookmark, CurrencyIcon, Currency, Wallet, Receipt, Sparkles, MoonStar, IdCard, RockingChair, LocateFixed, School, WorkflowIcon } from "lucide-react";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
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 React, { useEffect } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { RockingChair, LocateFixed, School, WorkflowIcon, Lock } from "lucide-react";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import StarIcon from "@mui/icons-material/Star";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
||||
import { motion } from "framer-motion";
|
||||
import FilterModal from "../../feature/FilterModal";
|
||||
import bride1 from "../../assets/images/bride1.jpg";
|
||||
import bride2 from "../../assets/images/bride2.jpg";
|
||||
@ -23,260 +18,135 @@ import groom4 from "../../assets/images/groom4.jpg";
|
||||
|
||||
import horoscope from "../../assets/images/horoscopeicon.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button, Fab } from "@mui/material";
|
||||
import MessageIcon from "@mui/icons-material/Message";
|
||||
import PhoneIcon from "@mui/icons-material/Phone";
|
||||
// Profile Card Component
|
||||
function ProfileCard({ profile }) {
|
||||
const [isLiked, setIsLiked] = useState(false);
|
||||
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>
|
||||
);
|
||||
}
|
||||
import toast from "react-hot-toast";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { updateFilter } from "../../redux/filterSlice";
|
||||
import { useProfiles } from "../../hooks/useProfiles";
|
||||
import ProfileCardUI from "../common/ProfileCardUI";
|
||||
import ProfileCardSkeleton from "../common/ProfileCardSkeleton";
|
||||
|
||||
// Main Component
|
||||
export default function MatchesInterface() {
|
||||
const [showSkeleton, setShowSkeleton] = React.useState(false);
|
||||
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 = [
|
||||
{
|
||||
id: "your-matches",
|
||||
id: "all_matches",
|
||||
icon: <PersonIcon className="w-6 h-6" />,
|
||||
title: "Your Matches",
|
||||
description: "View all the profiles that match your preferences",
|
||||
category: "All Matches",
|
||||
},
|
||||
{
|
||||
id: "shortlisted-by-you",
|
||||
id: "shorlisted_by_you",
|
||||
icon: <StarIcon className="w-6 h-6" />,
|
||||
title: "Shortlisted by you",
|
||||
description: "Matches you have shortlisted",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "viewed-you",
|
||||
id: "viewed_you",
|
||||
icon: <VisibilityIcon className="w-6 h-6" />,
|
||||
title: "Viewed you",
|
||||
description: "Matches who have viewed your profile",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "shortlisted-you",
|
||||
id: "shorlisted_you",
|
||||
icon: <PersonAddIcon className="w-6 h-6" />,
|
||||
title: "Shortlisted you",
|
||||
description: "Matches who have shortlisted your profile",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "viewed-by-you",
|
||||
id: "viewed_by_you",
|
||||
icon: <VisibilityIcon className="w-6 h-6" />,
|
||||
title: "Viewed by you",
|
||||
description: "Matches you have viewed",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "newly-joined",
|
||||
id: "newly_joined",
|
||||
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",
|
||||
id: "location_matches",
|
||||
icon: <LocateFixed className="w-6 h-6" />,
|
||||
title: "Location matches",
|
||||
description: "Matches near your location",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "education",
|
||||
id: "education_matches",
|
||||
icon: <School className="w-6 h-6" />,
|
||||
title: "Education matches",
|
||||
description: "Matches near your education match",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "job",
|
||||
id: "job_matches",
|
||||
icon: <WorkflowIcon className="w-6 h-6" />,
|
||||
title: "Job matches",
|
||||
description: "Matches near your job",
|
||||
@ -284,89 +154,6 @@ export default function MatchesInterface() {
|
||||
},
|
||||
];
|
||||
|
||||
const profiles = [
|
||||
{
|
||||
id: "JB2847593",
|
||||
name: "Jerome Bell",
|
||||
age: 22,
|
||||
height: "5.2",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "BCA / Data analyst",
|
||||
location: "Chennai",
|
||||
image: bride1,
|
||||
},
|
||||
{
|
||||
id: "SA8392847",
|
||||
name: "Sarah Anderson",
|
||||
age: 24,
|
||||
height: "5.4",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "MBA / Marketing Manager",
|
||||
location: "Bangalore",
|
||||
image: bride4,
|
||||
},
|
||||
{
|
||||
id: "PR9384756",
|
||||
name: "Priya Reddy",
|
||||
age: 23,
|
||||
height: "5.3",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "B.Tech / Software Engineer",
|
||||
location: "Hyderabad",
|
||||
image: bride2,
|
||||
},
|
||||
{
|
||||
id: "AN4758392",
|
||||
name: "Ananya Krishnan",
|
||||
age: 25,
|
||||
height: "5.5",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "MD / Doctor",
|
||||
location: "Kochi",
|
||||
image: bride3,
|
||||
},
|
||||
{
|
||||
id: "AN4758392",
|
||||
name: "Ananya Krishnan",
|
||||
age: 25,
|
||||
height: "5.5",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "MD / Doctor",
|
||||
location: "Kochi",
|
||||
image: groom1,
|
||||
},
|
||||
{
|
||||
id: "AN4758392",
|
||||
name: "Ananya Krishnan",
|
||||
age: 25,
|
||||
height: "5.5",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "MD / Doctor",
|
||||
location: "Kochi",
|
||||
image: groom2,
|
||||
},
|
||||
{
|
||||
id: "AN4758392",
|
||||
name: "Ananya Krishnan",
|
||||
age: 25,
|
||||
height: "5.5",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "MD / Doctor",
|
||||
location: "Kochi",
|
||||
image: groom4,
|
||||
},
|
||||
{
|
||||
id: "AN4758392",
|
||||
name: "Ananya Krishnan",
|
||||
age: 25,
|
||||
height: "5.5",
|
||||
lastseen: "Last seen 14 Nov 2025",
|
||||
education: "MD / Doctor",
|
||||
location: "Kochi",
|
||||
image: groom3,
|
||||
},
|
||||
];
|
||||
|
||||
let currentCategory = "";
|
||||
|
||||
return (
|
||||
@ -381,7 +168,7 @@ export default function MatchesInterface() {
|
||||
|
||||
<div className="w-full md:w-80">
|
||||
<div
|
||||
className="rounded-[10px] border border-gray-200 bg-white my-6
|
||||
className="relative rounded-[10px] border border-gray-200 bg-white my-6
|
||||
shadow-lg h-[400px] md:h-[600px] overflow-y-auto md:sticky md:top-[150px]"
|
||||
>
|
||||
|
||||
@ -406,7 +193,9 @@ export default function MatchesInterface() {
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
onClick={() => setSelectedTab(tab.id)}
|
||||
onClick={() => {
|
||||
dispatch(updateFilter({ filter_type: 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"
|
||||
@ -472,19 +261,58 @@ export default function MatchesInterface() {
|
||||
{tabs.find((t) => t.id === selectedTab)?.title}
|
||||
</h1>
|
||||
<div className="flex gap-2 items-center">
|
||||
<img
|
||||
src={horoscope}
|
||||
onClick={() => {
|
||||
<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">
|
||||
{profiles.map((profile) => (
|
||||
<ProfileCard key={profile.id} profile={profile} />
|
||||
))}
|
||||
{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>
|
||||
|
||||
@ -1,9 +1,23 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from '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() {
|
||||
const dispatch = useDispatch();
|
||||
const searchFromStore = useSelector((state) => state.filters.search);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
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
|
||||
const allSuggestions = [
|
||||
|
||||
@ -379,7 +379,7 @@ const DailyRecommendedCard = () => {
|
||||
</div>
|
||||
|
||||
{/* Custom Swiper Styles */}
|
||||
<style jsx global>{`
|
||||
<style>{`
|
||||
.swiper-pagination-bullet {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import {
|
||||
@ -10,141 +10,84 @@ import {
|
||||
import {
|
||||
Crown,
|
||||
Bookmark,
|
||||
User,
|
||||
Briefcase,
|
||||
MapPin,
|
||||
X,
|
||||
Send,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Heart,
|
||||
Eye,
|
||||
} from "lucide-react";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import HeightIcon from "@mui/icons-material/Height";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
||||
import SchoolIcon from "@mui/icons-material/School";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
|
||||
import profilebg from "../../assets/images/profilebg.jpg";
|
||||
import Image from "../../assets/images/astrology-horoscope-svgrepo-com.svg";
|
||||
import Image1 from "../../assets/images/scorpio-svgrepo-com.svg";
|
||||
// Import Swiper styles
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/effect-coverflow";
|
||||
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 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 ProfileCard = ({ profile }) => {
|
||||
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 (
|
||||
<motion.div
|
||||
@ -152,140 +95,110 @@ const MatchingList = () => {
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-2 border-gray-200"
|
||||
onClick={() => navigate(`/profile-details/${id}`)}
|
||||
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
|
||||
>
|
||||
{/* Profile Image Section */}
|
||||
{/* IMAGE SECTION */}
|
||||
<div className="relative">
|
||||
{/* Premium Badge */}
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: "spring" }}
|
||||
className="absolute top-4 left-4 z-10 bg-red-900 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* Shortlist Button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
<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();
|
||||
// shortlist logic
|
||||
if (!shortlistMutation.isPending) {
|
||||
shortlistMutation.mutate(id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
|
||||
<div
|
||||
classname=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
||||
style={{ height: "300px" }}
|
||||
>
|
||||
<img
|
||||
src={profile.image}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/* <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>
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats and Follow Section */}
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
|
||||
style={{
|
||||
background: "rgb(255, 255, 255)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Age : {profile.age}
|
||||
</span>
|
||||
</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 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 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 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>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
age,
|
||||
height,
|
||||
salary,
|
||||
location,
|
||||
caste,
|
||||
zodiac1,
|
||||
zodiac2,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<TempleHinduIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.caste}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={Image} alt="" className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.zodiac1}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={Image1} alt="" className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.zodiac2}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||
}}
|
||||
disabled={declineMutation.isPending}
|
||||
>
|
||||
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||
}}
|
||||
disabled={interestMutation.isPending}
|
||||
>
|
||||
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</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 (
|
||||
<>
|
||||
<div
|
||||
@ -341,8 +254,8 @@ const MatchingList = () => {
|
||||
}}
|
||||
className="pb-16"
|
||||
>
|
||||
{profiles.map((profile) => (
|
||||
<SwiperSlide key={profile.id}>
|
||||
{displayProfiles.map((profile, index) => (
|
||||
<SwiperSlide key={profile.id || index}>
|
||||
<ProfileCard profile={profile} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
@ -383,7 +296,10 @@ const MatchingList = () => {
|
||||
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")}
|
||||
onClick={() => {
|
||||
dispatch(updateFilter({ filter_type: "all_matches" }));
|
||||
navigate("/matches", { state: { activeTab: "allmatches", filter_type: "all_matches" } });
|
||||
}}
|
||||
>
|
||||
View All Matches
|
||||
</motion.button>
|
||||
@ -391,7 +307,7 @@ const MatchingList = () => {
|
||||
</div>
|
||||
|
||||
{/* Custom Swiper Styles */}
|
||||
<style jsx global>{`
|
||||
<style>{`
|
||||
.swiper-pagination-bullet {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@ -5,48 +5,13 @@ import "swiper/css/navigation";
|
||||
import { motion } from 'framer-motion';
|
||||
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-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 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 MatrimonyArticles = ({ articles }) => {
|
||||
const swiperRef = useRef(null);
|
||||
const displayArticles = articles || [];
|
||||
|
||||
if (displayArticles.length === 0) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="custom-article-swiper py-10 px-2 max-w-[1400px] mx-auto my-10">
|
||||
@ -70,51 +35,19 @@ const MatrimonyArticles = () => {
|
||||
loop={true}
|
||||
className="mySwiper"
|
||||
>
|
||||
{articleData.map((item, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
{displayArticles.map((item) => (
|
||||
<SwiperSlide key={item.id}>
|
||||
<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=" 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
|
||||
src={item.img}
|
||||
className="w-full h-88 object-cover"
|
||||
alt={item.title}
|
||||
src={item.image}
|
||||
className="w-full h-full object-cover"
|
||||
alt={`Article ${item.id}`}
|
||||
/>
|
||||
</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>
|
||||
|
||||
@ -1,7 +1,19 @@
|
||||
|
||||
import { Phone, MessageCircle, ThumbsUp, Eye } from 'lucide-react';
|
||||
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 (
|
||||
|
||||
<>
|
||||
@ -19,50 +31,31 @@ return (
|
||||
|
||||
{/* Subheading with offer */}
|
||||
<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>
|
||||
|
||||
{/* Features List */}
|
||||
<div className="space-y-2 mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
||||
<Phone className="w-5 h-5 text-gray-700" />
|
||||
</div>
|
||||
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
||||
Call/WhatsApp matches
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
||||
<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>
|
||||
{points.map((point, index) => {
|
||||
const Icon = icons[index % icons.length];
|
||||
return (
|
||||
<div key={index} className="flex items-start gap-4">
|
||||
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
|
||||
<Icon className="w-5 h-5 text-gray-700" />
|
||||
</div>
|
||||
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
|
||||
{point}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
</button>
|
||||
</div>
|
||||
@ -86,4 +79,3 @@ return (
|
||||
</>);
|
||||
};
|
||||
export default MembershipCard
|
||||
|
||||
|
||||
@ -1,116 +1,88 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Pagination, Autoplay, EffectCoverflow } from 'swiper/modules';
|
||||
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import HeightIcon from "@mui/icons-material/Height";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
|
||||
import SchoolIcon from "@mui/icons-material/School";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import profilebg from "../../assets/images/profilebg.jpg";
|
||||
import {
|
||||
Crown,
|
||||
Bookmark,
|
||||
X,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Heart,
|
||||
Eye,
|
||||
} from 'lucide-react';
|
||||
// Import Swiper styles
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import 'swiper/css/effect-coverflow';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const NewJoinedProfile = () => {
|
||||
|
||||
|
||||
const swiperRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Sample profile data
|
||||
const profiles = [
|
||||
{
|
||||
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
|
||||
|
||||
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 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 (
|
||||
<motion.div
|
||||
@ -118,173 +90,110 @@ const ProfileCard = ({ profile }) => {
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
|
||||
onClick={() => navigate(`/profile-details/${id}`)}
|
||||
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
|
||||
>
|
||||
{/* IMAGE SECTION */}
|
||||
<div className="relative">
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
</motion.div>
|
||||
<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>
|
||||
)}
|
||||
|
||||
<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"
|
||||
<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 className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
</motion.button>
|
||||
|
||||
<div
|
||||
className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
||||
style={{ height: "300px" }}
|
||||
>
|
||||
<img
|
||||
src={profile.image}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="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>
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
||||
style={{ background: "rgb(255, 255, 255)" }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Age : {profile.age}
|
||||
</span>
|
||||
</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 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 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 items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.religion}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.education}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.location}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M18 6L6 18M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
||||
transform hover:scale-105 flex items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsLiked(!isLiked);
|
||||
}}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||
fill="#EF4444"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
age,
|
||||
height,
|
||||
salary,
|
||||
location,
|
||||
caste,
|
||||
zodiac1,
|
||||
zodiac2,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
<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
|
||||
{v}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
{/* <button
|
||||
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||
}}
|
||||
disabled={declineMutation.isPending}
|
||||
>
|
||||
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||
</button> */}
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||
}}
|
||||
disabled={interestMutation.isPending}
|
||||
>
|
||||
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</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 (
|
||||
<>
|
||||
|
||||
@ -301,10 +210,10 @@ const ProfileCard = ({ profile }) => {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-[20px] text-[#034E08] sm:text-[22px] lg:text-[24px] font-bold mb-3">
|
||||
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||
New Joined
|
||||
</h1>
|
||||
<p className="text-gray-600 text-[12px]">Find your perfect match today</p>
|
||||
<p className="text-gray-900 text-[12px]">Find your perfect match today</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Swiper Container */}
|
||||
@ -319,10 +228,6 @@ const ProfileCard = ({ profile }) => {
|
||||
disableOnInteraction: false,
|
||||
pauseOnMouseEnter: true
|
||||
}}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
dynamicBullets: true
|
||||
}}
|
||||
loop={true}
|
||||
speed={800}
|
||||
breakpoints={{
|
||||
@ -341,8 +246,8 @@ const ProfileCard = ({ profile }) => {
|
||||
}}
|
||||
className="pb-16"
|
||||
>
|
||||
{profiles.map((profile) => (
|
||||
<SwiperSlide key={profile.id}>
|
||||
{displayProfiles.map((profile, index) => (
|
||||
<SwiperSlide key={profile.id || index}>
|
||||
<ProfileCard profile={profile} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
@ -383,14 +288,18 @@ const ProfileCard = ({ profile }) => {
|
||||
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={() => {
|
||||
dispatch(updateFilter({ filter_type: "newly_joined" }));
|
||||
navigate("/matches", { state: { activeTab: "newly_joined", filter_type: "newly_joined" } });
|
||||
}}
|
||||
>
|
||||
View All
|
||||
View All Matches
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Custom Swiper Styles */}
|
||||
<style jsx global>{`
|
||||
<style>{`
|
||||
.swiper-pagination-bullet {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@ -9,8 +9,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import AstroChatUI from "./AstroChatUI";
|
||||
import MembershipCard from "./MembershipCard";
|
||||
|
||||
const ProfileCompletion = () => {
|
||||
const [completeness, setCompleteness] = useState(30);
|
||||
const ProfileCompletion = ({ percentage = 0, missingDetails,becomePaidMember }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const cards = [
|
||||
@ -95,7 +94,7 @@ const ProfileCompletion = () => {
|
||||
transition={{ delay: 0.6, type: "spring", stiffness: 200 }}
|
||||
className="text-lg sm:text-xl font-bold text-gray-900"
|
||||
>
|
||||
{completeness}%
|
||||
{percentage}%
|
||||
</motion.span>
|
||||
</div>
|
||||
|
||||
@ -103,7 +102,7 @@ const ProfileCompletion = () => {
|
||||
<div className="relative h-3 bg-gray-200 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${completeness}%` }}
|
||||
animate={{ width: `${percentage}%` }}
|
||||
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"
|
||||
/>
|
||||
@ -123,22 +122,24 @@ const ProfileCompletion = () => {
|
||||
</Box> */}
|
||||
|
||||
<AstroChatUI/>
|
||||
<div className="my-4 rounded-2xl bg-green-50 border border-1 border-green-200 p-4 ">
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
||||
>
|
||||
|
||||
{cards.map((card, index) => (
|
||||
|
||||
<div
|
||||
onClick={() => navigate(card.url)}
|
||||
|
||||
className=" border border-1 border-red-50 bg-white rounded-3xl hover:bg-red-50 hover:border-2
|
||||
|
||||
<div className="my-4 rounded-2xl bg-green-50 border border-1 border-green-200 p-4 ">
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
||||
>
|
||||
{/* NOTE: The cards are static for now. To make them dynamic,
|
||||
the `missingDetails` prop should be an array of objects to map over. */}
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={card.id}
|
||||
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 ">
|
||||
cursor-pointer "
|
||||
>
|
||||
{/* Icon Container */}
|
||||
<motion.div
|
||||
initial={{ rotate: -180, opacity: 0 }}
|
||||
@ -163,15 +164,12 @@ const ProfileCompletion = () => {
|
||||
{card.title}
|
||||
</motion.h3>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
|
||||
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
<MembershipCard/>
|
||||
|
||||
<MembershipCard becomePaidMember={becomePaidMember} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* Additional Info Section */}
|
||||
{/* <motion.div
|
||||
|
||||
@ -2,113 +2,31 @@ import React, { useState, useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Navigation, Pagination, Autoplay } from 'swiper/modules';
|
||||
import { Play, X, Heart, Share2, Eye, 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 { Play, X, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
// Import Swiper styles
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
|
||||
const VideoSwiperGallery = () => {
|
||||
const VideoSwiperGallery = ({ videos }) => {
|
||||
const [selectedVideo, setSelectedVideo] = useState(null);
|
||||
const swiperRef = useRef(null);
|
||||
|
||||
// Video data with online sources
|
||||
const videos = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Priya & Rahul - Wedding Story',
|
||||
thumbnail: weddingImg1,
|
||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
||||
views: '2.4K',
|
||||
likes: '142',
|
||||
duration: '3:45'
|
||||
},
|
||||
{
|
||||
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'
|
||||
const displayVideos = videos || [];
|
||||
|
||||
if (displayVideos.length === 0) return null;
|
||||
|
||||
const getEmbedUrl = (url) => {
|
||||
if (!url) return '';
|
||||
let embedUrl = url;
|
||||
if (url.includes('youtu.be/')) {
|
||||
embedUrl = url.replace('youtu.be/', 'www.youtube.com/embed/');
|
||||
} else if (url.includes('youtube.com/watch?v=')) {
|
||||
embedUrl = url.replace('youtube.com/watch?v=', 'youtube.com/embed/');
|
||||
}
|
||||
];
|
||||
return embedUrl.split('?')[0] + '?autoplay=1';
|
||||
};
|
||||
|
||||
const VideoCard = ({ video }) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
@ -129,12 +47,12 @@ const VideoSwiperGallery = () => {
|
||||
<div className="relative aspect-video">
|
||||
<img
|
||||
src={video.thumbnail}
|
||||
alt={video.title}
|
||||
alt="Video Thumbnail"
|
||||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
|
||||
{/* 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 */}
|
||||
<motion.div
|
||||
@ -146,28 +64,6 @@ const VideoSwiperGallery = () => {
|
||||
<Play className="w-7 h-7 text-[#034E08] group-hover:text-white ml-1" fill="currentColor" />
|
||||
</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>
|
||||
</motion.div>
|
||||
@ -202,36 +98,13 @@ const VideoSwiperGallery = () => {
|
||||
</button>
|
||||
|
||||
{/* Video Player */}
|
||||
<div className="bg-[#034E08] rounded-2xl overflow-y-scroll h-[100%] max-h-[480px] shadow-2xl">
|
||||
<video
|
||||
src={selectedVideo.videoUrl}
|
||||
controls
|
||||
autoPlay
|
||||
<div className="bg-black rounded-2xl overflow-hidden shadow-2xl">
|
||||
<iframe
|
||||
src={getEmbedUrl(selectedVideo.youtube_url)}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="w-full aspect-video"
|
||||
>
|
||||
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>
|
||||
></iframe>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
@ -290,7 +163,7 @@ const VideoSwiperGallery = () => {
|
||||
}}
|
||||
className="pb-16"
|
||||
>
|
||||
{videos.map((video) => (
|
||||
{displayVideos.map((video) => (
|
||||
<SwiperSlide key={video.id}>
|
||||
<VideoCard video={video} />
|
||||
</SwiperSlide>
|
||||
@ -343,7 +216,7 @@ const VideoSwiperGallery = () => {
|
||||
{selectedVideo && <VideoModal />}
|
||||
|
||||
{/* Custom Swiper Styles */}
|
||||
<style jsx global>{`
|
||||
<style>{`
|
||||
.swiper-pagination-bullet {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import {
|
||||
Heart,
|
||||
X,
|
||||
ChevronRight,
|
||||
SkipForward,
|
||||
Bookmark,
|
||||
MessageCircle,
|
||||
Ban,
|
||||
@ -19,8 +18,10 @@ import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/thumbs";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { sendInterest, shortlistProfile } from "../../services/shortlistapi";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
const MatrimonyProfile = () => {
|
||||
const MatrimonyProfile = ({ data }) => {
|
||||
const navigate = useNavigate();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
@ -28,26 +29,17 @@ const MatrimonyProfile = () => {
|
||||
const mainSwiperRef = useRef(null);
|
||||
const modalSwiperRef = useRef(null);
|
||||
|
||||
const profile = {
|
||||
name: "Sudharshan M",
|
||||
id: "M8355880",
|
||||
verified: true,
|
||||
lastSeen: "Last seen few hour ago",
|
||||
age: "30 yrs",
|
||||
height: "5'5\"",
|
||||
caste: "Brahmin",
|
||||
education: "Engineer - Non IT",
|
||||
location: "Chennai",
|
||||
maritalStatus: "Never Married",
|
||||
createdBy: "Profile created by sibling",
|
||||
images: [
|
||||
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=800&fit=crop",
|
||||
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=600&h=800&fit=crop",
|
||||
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=600&h=800&fit=crop",
|
||||
"https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=600&h=800&fit=crop",
|
||||
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=600&h=800&fit=crop",
|
||||
],
|
||||
};
|
||||
if (!data) return null;
|
||||
|
||||
const profile = data.profile;
|
||||
const personal = data.personalDetails;
|
||||
const family = data.familyDetails;
|
||||
const education = data.educationalDetails;
|
||||
const lifestyle = data.lifestyleDetails;
|
||||
|
||||
const profileImages = personal.images && personal.images.length > 0
|
||||
? personal.images
|
||||
: [profile.profile_picture || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png"];
|
||||
|
||||
const openModal = (index) => {
|
||||
setIsModalOpen(true);
|
||||
@ -58,6 +50,31 @@ const MatrimonyProfile = () => {
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleSendInterest = async () => {
|
||||
try {
|
||||
const res = await sendInterest(profile.id);
|
||||
toast.success(res.message || "Interest sent successfully");
|
||||
} catch (error) {
|
||||
toast.error(error.message || "Failed to send interest");
|
||||
}
|
||||
};
|
||||
|
||||
const handleShortlist = async () => {
|
||||
try {
|
||||
const res = await shortlistProfile(profile.id);
|
||||
toast.success(res.message || "Profile shortlisted successfully");
|
||||
} catch (error) {
|
||||
toast.error(error.message || "Failed to shortlist");
|
||||
}
|
||||
};
|
||||
|
||||
const safeVal = (val, key) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return val[key] || "N/A";
|
||||
}
|
||||
return val || "N/A";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div
|
||||
@ -76,16 +93,12 @@ const MatrimonyProfile = () => {
|
||||
prevEl: ".swiper-button-prev-custom",
|
||||
nextEl: ".swiper-button-next-custom",
|
||||
}}
|
||||
pagination={{
|
||||
type: "fraction",
|
||||
el: ".swiper-pagination-custom",
|
||||
}}
|
||||
onSwiper={(swiper) => {
|
||||
mainSwiperRef.current = swiper;
|
||||
}}
|
||||
className="h-full w-full"
|
||||
>
|
||||
{profile.images.map((img, idx) => (
|
||||
{profileImages.map((img, idx) => (
|
||||
<SwiperSlide key={idx}>
|
||||
<div
|
||||
className="w-[320px] h-[330px] cursor-pointer"
|
||||
@ -95,35 +108,21 @@ const MatrimonyProfile = () => {
|
||||
src={img}
|
||||
alt={`${profile.name} ${idx + 1}`}
|
||||
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
|
||||
onError={(e) => {
|
||||
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
{/* Swiper Navigation Buttons */}
|
||||
<button className="swiper-button-prev-custom absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-2 rounded-full hover:bg-black/70 transition-colors">
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<button className="swiper-button-next-custom absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-2 rounded-full hover:bg-black/70 transition-colors">
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Pagination */}
|
||||
{/* <div className="swiper-pagination-custom absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/50 text-white px-3 py-1 rounded-full text-sm z-10"></div> */}
|
||||
|
||||
{/* Thumbnail Navigation */}
|
||||
{/* <div className="absolute bottom-0 right-3 flex flex-row gap-2 z-10">
|
||||
{profile.images.slice(0, 4).map((img, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="w-12 h-12 rounded-lg overflow-hidden border-2 border-white cursor-pointer hover:scale-110 transition-transform shadow-lg"
|
||||
onClick={() => mainSwiperRef.current?.slideTo(idx)}
|
||||
>
|
||||
<img src={img} alt="" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -144,7 +143,7 @@ const MatrimonyProfile = () => {
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-[#034E08] font-semibold">ID verified</span>
|
||||
<span className="text-[#034E08] font-semibold">{profile.approved ? "ID verified" : "Pending Verification"}</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
@ -161,10 +160,16 @@ const MatrimonyProfile = () => {
|
||||
</button>
|
||||
{showMenu && (
|
||||
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-xl border z-10">
|
||||
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => { setShowMenu(false); handleShortlist(); }}
|
||||
className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3"
|
||||
>
|
||||
<Bookmark className="w-4 h-4" /> Shortlist
|
||||
</button>
|
||||
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => { setShowMenu(false); navigate("/chat"); }}
|
||||
className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3"
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" /> Send Message
|
||||
</button>
|
||||
<button className="w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center gap-3">
|
||||
@ -182,44 +187,47 @@ const MatrimonyProfile = () => {
|
||||
{profile.name}
|
||||
</h1>
|
||||
<p className="text-gray-500 text-sm mb-4">
|
||||
{profile.id} | {profile.lastSeen}
|
||||
{profile.member_id} | {profile.last_seen_at}
|
||||
</p>
|
||||
|
||||
<div className="space-y-2 mb-6 text-gray-700">
|
||||
<p className="flex flex-wrap gap-2">
|
||||
<span className="font-semibold">{profile.maritalStatus}</span>
|
||||
<span>•</span>
|
||||
<p className="flex flex-wrap gap-2 items-center">
|
||||
<span className="text-sm text-gray-500">
|
||||
{profile.createdBy}
|
||||
Profile created by {personal.profile_for || "N/A"}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>{profile.age}</span>
|
||||
<span>•</span>
|
||||
<span>{profile.height}</span>
|
||||
<span>•</span>
|
||||
<span>{profile.caste}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">{profile.education}</span>
|
||||
<span> • </span>
|
||||
<span>{profile.location}</span>
|
||||
{(personal.age || profile.age) && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span className="text-sm">{personal.age || profile.age} yrs</span>
|
||||
</>
|
||||
)}
|
||||
{(profile.religion || profile.caste || profile.sub_caste || profile.college_name) && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span className="text-sm">
|
||||
{[
|
||||
safeVal(profile.religion, 'religion_name'),
|
||||
safeVal(profile.caste, 'caste_name'),
|
||||
safeVal(profile.sub_caste, 'sub_caste_name'),
|
||||
profile.college_name
|
||||
].filter(v => v !== "N/A" && v !== undefined).join(" / ")}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-start gap-3">
|
||||
<button
|
||||
// onClick={()=>{
|
||||
// navigate("/chat")
|
||||
// }}
|
||||
className="w-[fit-content] border-2 border-gray-300 text-gray-700 py-2 px-6 rounded-full hover:bg-gray-50 transition-colors flex items-center justify-center gap-2 text-sm">
|
||||
className="w-[fit-content] border-2 border-gray-300 text-gray-700 py-2 px-6 rounded-full hover:bg-gray-50 transition-colors flex items-center justify-center gap-2 text-sm"
|
||||
>
|
||||
<X className="w-5 h-5" /> Don't Show
|
||||
{/* Message */}
|
||||
</button>
|
||||
{/* <button className="w-[fit-content] border-2 border-orange-500 text-[#034E08] py-2 px-6 rounded-full hover:bg-orange-50 transition-colors flex items-center justify-center gap-2 text-sm">
|
||||
<SkipForward className="w-5 h-5" /> Skip
|
||||
</button> */}
|
||||
<button className="w-[fit-content] bg-[#034E08] text-white py-2 px-6 rounded-full hover:bg-[#A70710] transition-colors flex items-center justify-center gap-2 font-semibold text-sm">
|
||||
<button
|
||||
onClick={handleSendInterest}
|
||||
className="w-[fit-content] bg-[#034E08] text-white py-2 px-6 rounded-full hover:bg-[#A70710] transition-colors flex items-center justify-center gap-2 font-semibold text-sm"
|
||||
>
|
||||
<Heart className="w-5 h-5" /> Send Interest
|
||||
</button>
|
||||
</div>
|
||||
@ -227,7 +235,7 @@ const MatrimonyProfile = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Image Modal with Swiper */}
|
||||
{/* Image Modal */}
|
||||
{isModalOpen && (
|
||||
<div
|
||||
style={{ backdropFilter: "blur(5px)" }}
|
||||
@ -243,7 +251,6 @@ const MatrimonyProfile = () => {
|
||||
<div className="max-w-4xl w-full bg-white p-4 rounded-md">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
{/* Main Modal Swiper */}
|
||||
<div
|
||||
className="relative bg-gray-900 rounded-lg overflow-hidden"
|
||||
style={{ height: "65vh" }}
|
||||
@ -271,20 +278,22 @@ const MatrimonyProfile = () => {
|
||||
}}
|
||||
className="h-full w-full"
|
||||
>
|
||||
{profile.images.map((img, idx) => (
|
||||
{profileImages.map((img, idx) => (
|
||||
<SwiperSlide key={idx}>
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<img
|
||||
src={img}
|
||||
alt={`${profile.name} ${idx + 1}`}
|
||||
className="max-w-full max-h-full object-contain"
|
||||
onError={(e) => {
|
||||
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
{/* Modal Navigation Buttons */}
|
||||
<button className="modal-swiper-button-prev absolute left-4 top-1/2 -translate-y-1/2 z-10 bg-black/50 text-white p-3 rounded-full hover:bg-black/70 transition-colors">
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
</button>
|
||||
@ -295,24 +304,13 @@ const MatrimonyProfile = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* Top Info Bar */}
|
||||
<div className="bg-white rounded-t-lg p-4 mb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="swiper-pagination-modal text-lg font-semibold"></div>
|
||||
<div className="text-right">
|
||||
<h3 className="font-bold text-lg">{profile.name}</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
{profile.id} | {profile.createdBy}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
{profile.age} • {profile.height} • {profile.caste} • BE
|
||||
• {profile.education} • {profile.location}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-bold text-lg">{profile.name}</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
{profile.member_id} | Profile created by {personal.profile_for}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Thumbnail Swiper */}
|
||||
<Swiper
|
||||
modules={[Thumbs]}
|
||||
watchSlidesProgress
|
||||
@ -321,25 +319,28 @@ const MatrimonyProfile = () => {
|
||||
slidesPerView={5}
|
||||
className="mb-2"
|
||||
>
|
||||
{profile.images.map((img, idx) => (
|
||||
{profileImages.map((img, idx) => (
|
||||
<SwiperSlide key={idx}>
|
||||
<div className="w-full h-16 rounded-lg overflow-hidden border-2 border-white cursor-pointer hover:border-orange-500 transition-colors">
|
||||
<img
|
||||
src={img}
|
||||
alt=""
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
{/* Bottom Action Bar */}
|
||||
<div className="bg-white p-4 rounded-b-lg mt-2 text-center">
|
||||
<p className="text-sm text-gray-600 mb-2">
|
||||
Like this member?
|
||||
</p>
|
||||
<button className="bg-[#034E08] text-white px-8 py-2 rounded-full hover:bg-orange-700 transition-colors font-semibold">
|
||||
<p className="text-sm text-gray-600 mb-2">Like this member?</p>
|
||||
<button
|
||||
onClick={handleSendInterest}
|
||||
className="bg-[#034E08] text-white px-8 py-2 rounded-full hover:bg-orange-700 transition-colors font-semibold"
|
||||
>
|
||||
Send Interest
|
||||
</button>
|
||||
</div>
|
||||
@ -351,19 +352,11 @@ const MatrimonyProfile = () => {
|
||||
|
||||
<div className="w-[100%] max-w-[1200px] mx-auto my-10 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{/* Personal Information Section */}
|
||||
<div className=" border border-gray-200 rounded-lg bg-pink-50/30">
|
||||
<div className="border border-gray-200 rounded-lg bg-pink-50/30">
|
||||
<div className="flex items-center gap-2 mb-4 p-3 py-3 bg-green-100">
|
||||
<div className="bg-pink-100 p-2 rounded-full">
|
||||
<svg
|
||||
className="w-5 h-5 text-[#A70710]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg">Personal Information</h3>
|
||||
@ -371,188 +364,103 @@ const MatrimonyProfile = () => {
|
||||
|
||||
<div className="p-5 mb-6 space-y-3 text-sm">
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Age</span>
|
||||
<span className="text-gray-600 w-40">Name</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">30 Years and 8 months</span>
|
||||
<span className="ml-3 text-gray-900">{profile.name}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Gender</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{personal.gender || profile.type || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Date of Birth</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{personal.dob || profile.dob || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Place of Birth</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.place_of_birth || personal.place_of_birth || profile.place_of_birth || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Time of Birth</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.time_of_birth || personal.time_of_birth || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Height</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">5'5"</span>
|
||||
<span className="ml-3 text-gray-900">{profile.height ? `${profile.height} ft` : "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Weight</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">97 Kg</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Body Type</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Average</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Spoken Languages</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">
|
||||
Tamil (Mother Tongue), English, Hindi
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Profile Created By</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Sibling</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Marital Status</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Never Married</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Lives In</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Chennai, Tamil Nadu</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Eating Habits</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Vegetarian</span>
|
||||
<span className="ml-3 text-gray-900">{profile.weight ? `${profile.weight} Kg` : "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Religion</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Hindu</span>
|
||||
<span className="ml-3 text-gray-900">{personal.religion || safeVal(profile.religion, 'religion_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Profile Created By</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{personal.profile_for || safeVal(profile.profile_for, 'profile_for_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Caste</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Brahmin - Iyer</span>
|
||||
<span className="ml-3 text-gray-900">{personal.caste || safeVal(profile.caste, 'caste_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Subcaste</span>
|
||||
<span className="text-gray-600 w-40">Sub Caste</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Brahacharmam</span>
|
||||
<span className="ml-3 text-gray-900">{personal.sub_caste || safeVal(profile.sub_caste, 'sub_caste_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Gothra(m)</span>
|
||||
<span className="text-gray-600 w-40">Gothram</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Kashyapa / Kaashyapa</span>
|
||||
<span className="ml-3 text-gray-900">{personal.gothram || safeVal(profile.gothram, 'gothram_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Dosha(m)</span>
|
||||
<span className="text-gray-600 w-40">Rasi</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Don't know</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-gray-600 w-40">Date Of Birth</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900"> 23-12-1991</span>
|
||||
{/* <button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Upgrade to view
|
||||
</button> */}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-gray-600 w-40">Star</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Piscus</span>
|
||||
{/* <button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Upgrade to view
|
||||
</button> */}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-gray-600 w-40">Rassi</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900"> Revathy</span>
|
||||
{/* <button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Upgrade to view
|
||||
</button> */}
|
||||
</div>
|
||||
{/* <div className="flex items-center">
|
||||
<span className="text-gray-600 w-40">Horoscope</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<button className="ml-3 text-[#034E08] hover:text-orange-700 flex items-center gap-1 text-xs font-medium">
|
||||
<svg
|
||||
className="w-3 h-3"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Upgrade to view
|
||||
</button>
|
||||
</div> */}
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Employment</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Employed in private</span>
|
||||
<span className="ml-3 text-gray-900">{personal.raasi || safeVal(profile.raasi, 'raasi_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Income</span>
|
||||
<span className="text-gray-600 w-40">Birth Star</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">₹ 4 - 5 Lakhs</span>
|
||||
<span className="ml-3 text-gray-900">{personal.star || safeVal(profile.star, 'star_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Education</span>
|
||||
<span className="text-gray-600 w-40">Known Languages</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">BE</span>
|
||||
<span className="ml-3 text-gray-900">{personal.known_languages || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Occupation</span>
|
||||
<span className="text-gray-600 w-40">Speaks Telugu</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Engineer - Non IT</span>
|
||||
<span className="ml-3 text-gray-900">{personal.do_you_speak_telugu === 1 ? "Yes" : personal.do_you_speak_telugu === 0 ? "No" : "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">City</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{personal.district || safeVal(profile.district, 'district_name') || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Pin Code</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{personal.pincode || profile.zip || "N/A"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Family Information Section */}
|
||||
<div className="border border-gray-200 rounded-lg bg-pink-50/30 ">
|
||||
<div className="border border-gray-200 rounded-lg bg-pink-50/30">
|
||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||
<div className="bg-white p-2 rounded-full">
|
||||
<svg
|
||||
className="w-5 h-5 text-[#A70710]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
||||
</svg>
|
||||
</div>
|
||||
@ -561,104 +469,44 @@ const MatrimonyProfile = () => {
|
||||
|
||||
<div className="p-5 space-y-3 text-sm">
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Parents</span>
|
||||
<span className="text-gray-600 w-40">Father Name</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">
|
||||
Father Passed Away, Mother is a Home Maker
|
||||
</span>
|
||||
<span className="ml-3 text-gray-900">{family.father_name || profile.father_name || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Ancestral Origin</span>
|
||||
<span className="text-gray-600 w-40">Father Occupation</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Rameshwaram</span>
|
||||
<span className="ml-3 text-gray-900">{family.father_occupation || profile.father_occupation || "N/A"}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Contact Information Section */}
|
||||
<div className="my-8">
|
||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||
<div className="bg-white p-2 rounded-full">
|
||||
<svg
|
||||
className="w-5 h-5 text-[#A70710]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg">Contact Information</h3>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Mother Name</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{family.mother_name || profile.mother_name || "N/A"}</span>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-3 text-sm">
|
||||
<div className="flex items-center">
|
||||
<span className="text-gray-600 w-40">Mobile Number</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<div className="ml-3 flex items-center gap-2">
|
||||
<svg
|
||||
className="w-3 h-3 text-green-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
|
||||
</svg>
|
||||
<svg
|
||||
className="w-3 h-3 text-red-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-gray-900">+91 99••••••••</span>
|
||||
<button className="text-[#034E08] hover:text-orange-700 text-xs font-medium">
|
||||
Upgrade to view
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Mother Occupation</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{family.mother_occupation || profile.mother_occupation || "N/A"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* About Myself Section */}
|
||||
<div className="my-8">
|
||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||
<div className="bg-white p-2 rounded-full">
|
||||
<svg
|
||||
className="w-5 h-5 text-[#A70710]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg">About Myself</h3>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Siblings</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{family.brother_count || profile.brother_count} Brothers, {family.sister_count || profile.sister_count} Sisters</span>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-4 text-sm">
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-2">
|
||||
About Sudharshan M
|
||||
</h4>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
I am making this profile for my brother. He completed his
|
||||
bachelor's degree and is now working as a project engineer -
|
||||
non IT. We belong to a middle class, nuclear family with
|
||||
traditional values, currently settled in Chennai.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-2">
|
||||
What we are looking for
|
||||
</h4>
|
||||
<p className="text-gray-700">
|
||||
Traditional, homely girl with moderate values
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Family Type</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{family.family_status || safeVal(profile.family_type, 'family_type_name') || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Settled</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{family.settled || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Native Place</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{family.native_place || profile.native_place || "N/A"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -666,11 +514,7 @@ const MatrimonyProfile = () => {
|
||||
<div className="my-8">
|
||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||
<div className="bg-white p-2 rounded-full">
|
||||
<svg
|
||||
className="w-5 h-5 text-[#A70710]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
|
||||
</svg>
|
||||
</div>
|
||||
@ -679,55 +523,108 @@ const MatrimonyProfile = () => {
|
||||
|
||||
<div className="p-5 space-y-3 text-sm">
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Cuisine</span>
|
||||
<span className="text-gray-600 w-40">Diet</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.diet || safeVal(profile.diet, 'diet_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Place of Birth</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.place_of_birth || personal.place_of_birth || profile.place_of_birth || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Time of Birth</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.time_of_birth || personal.time_of_birth || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Panjangam Type</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.panjangam_type || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Dasa Balance</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{lifestyle.dasa_balance || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Dasa Period</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">
|
||||
Chinese, North Indian, South Indian
|
||||
{lifestyle.dasa_years || "0"} Years, {lifestyle.dasa_months || "0"} Months, {lifestyle.dasa_days || "0"} Days
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Books</span>
|
||||
<span className="text-gray-600 w-40">Age</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">
|
||||
History, Philosophy / Spiritual
|
||||
</span>
|
||||
<span className="ml-3 text-gray-900">{(personal.age || profile.age || lifestyle.age) ? `${personal.age || profile.age || lifestyle.age} Years` : "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Hobbies</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Cooking</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Movies</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">
|
||||
Anime, Comedy, Sci-Fi
|
||||
{lifestyle.hobbies && lifestyle.hobbies.length > 0 ? lifestyle.hobbies.join(", ") : "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Sports</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Yoga / Meditation</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Smoking Habits</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Doesn't Smoke</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Drinking Habits</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">Doesn't Drink</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Educational Details Section */}
|
||||
<div className="border border-gray-200 rounded-lg bg-pink-50/30">
|
||||
<div className="flex items-center gap-2 p-3 bg-pink-100">
|
||||
<div className="bg-white p-2 rounded-full">
|
||||
<svg className="w-5 h-5 text-[#A70710]" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg">Educational Details</h3>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-3 text-sm">
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Highest Qualification</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{education.education || safeVal(profile.education, 'education_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Field of Study</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{education.study_field || safeVal(profile.study_field, 'study_field_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">College Name</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{profile.college_name || education.college_name || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Occupation</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{education.occupation || safeVal(profile.occupation, 'occupation_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Organization Name</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{education.company_name || profile.company_name || "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Employee Type</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{education.employee_type || safeVal(profile.employee_type, 'employee_type_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Annual Income</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{education.annual_income || safeVal(profile.annual_income, 'annual_income_name')}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="text-gray-600 w-40">Work Location</span>
|
||||
<span className="text-gray-400">:</span>
|
||||
<span className="ml-3 text-gray-900">{profile.work_location || education.work_location || "N/A"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,24 +1,29 @@
|
||||
import React from '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 = [
|
||||
{ label: "Preferred Bride's Age", value: "22-29 yrs", match: true },
|
||||
{ label: "Preferred Height", value: "5'0\" - 5'5\"", match: false },
|
||||
{ label: "Preferred Marital Status", value: "Never Married", match: true },
|
||||
{ label: "Preferred Mother Tongue", value: "Tamil", match: true },
|
||||
{ label: "Preferred Physical Status", value: "Normal", match: true },
|
||||
{ label: "Preferred Eating Habits", value: "Vegetarian", match: false },
|
||||
{ label: "Preferred Smoking Habits", value: "Doesn't Matter", match: true },
|
||||
{ label: "Preferred Drinking Habits", value: "Doesn't Matter", match: true },
|
||||
{ label: "Preferred Groom's Age", value: pref.preferred_age_range || "Any", match: matchDetails.age },
|
||||
{ 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: pref.preferred_marital_statuses?.join(", ") || "Any", match: matchDetails.marital_status },
|
||||
{ label: "Preferred Mother Tongue", value: pref.preferred_mother_tongues?.join(", ") || "Any", match: matchDetails.mother_tongue },
|
||||
{ label: "Preferred Education", value: pref.preferred_educations?.join(", ") || "Any", match: matchDetails.education },
|
||||
{ label: "Preferred Employee Type", value: pref.preferred_employee_types?.join(", ") || "Any", match: true }, // Not in matchDetails?
|
||||
];
|
||||
|
||||
const religiousPreferences = [
|
||||
{ label: "Preferred Religion", value: "Hindu", match: true },
|
||||
{ label: "Preferred Caste", value: "Brahmin - Iyer", match: false },
|
||||
{ label: "Preferred Subcaste", value: "Any", match: false },
|
||||
{ label: "Preferred Star", value: "Any", match: true },
|
||||
{ label: "Preferred Dosham", value: "No Dosham", match: true },
|
||||
{ label: "Preferred Caste", value: pref.preferred_castes?.join(", ") || "Any", match: matchDetails.caste },
|
||||
{ label: "Preferred Sub-caste", value: pref.preferred_sub_castes?.join(", ") || "Any", match: matchDetails.sub_caste },
|
||||
{ label: "Preferred State", value: pref.preferred_states?.join(", ") || "Any", match: true },
|
||||
{ label: "Preferred City", value: pref.preferred_districts?.join(", ") || "Any", 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 }) => (
|
||||
@ -46,7 +51,7 @@ const PartnerPreferences = () => {
|
||||
<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">
|
||||
<span className="text-pink-400">✨</span>
|
||||
His Partner Preferences
|
||||
Partner Preferences
|
||||
<span className="text-pink-400">✨</span>
|
||||
</h1>
|
||||
</div>
|
||||
@ -56,65 +61,52 @@ const PartnerPreferences = () => {
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/avataaars/svg?seed=male1"
|
||||
alt="Profile"
|
||||
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-pink-100"
|
||||
src={data.my_profile || "https://api.dicebear.com/7.x/avataaars/svg?seed=male1"}
|
||||
alt="Your Profile"
|
||||
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-pink-100 object-cover"
|
||||
/>
|
||||
<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">
|
||||
14<span className="text-[#034E08]">/20</span>
|
||||
{overallMatch}<span className="text-[#034E08]">%</span>
|
||||
</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>
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/avataaars/svg?seed=female1"
|
||||
alt="Your Profile"
|
||||
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-purple-100"
|
||||
src={data.profile.profile_picture || "https://api.dicebear.com/7.x/avataaars/svg?seed=female1"}
|
||||
alt="Partner Profile"
|
||||
className="w-16 h-16 sm:w-20 sm:h-20 rounded-xl border-4 border-purple-100 object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 gap-2 md:grid-cols-2 mb-8 pt-4'>
|
||||
|
||||
{/* Basic 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">Basic 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>
|
||||
{/* Basic 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">Basic Preferences</h2>
|
||||
</div>
|
||||
<div className="space-y-1 p-6">
|
||||
{basicPreferences.map((pref, index) => (
|
||||
<PreferenceItem key={index} {...pref} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 p-6">
|
||||
{basicPreferences.map((pref, index) => (
|
||||
<PreferenceItem key={index} {...pref} />
|
||||
))}
|
||||
|
||||
{/* Other 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">Professional & Location</h2>
|
||||
</div>
|
||||
<div className="space-y-1 p-6">
|
||||
{religiousPreferences.map((pref, index) => (
|
||||
<PreferenceItem key={index} {...pref} />
|
||||
))}
|
||||
</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 */}
|
||||
<div className="text-center mt-6 text-sm text-gray-500">
|
||||
<p>Preferences are used to find compatible matches</p>
|
||||
|
||||
@ -1,73 +1,222 @@
|
||||
import React from "react";
|
||||
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react";
|
||||
// Import your images
|
||||
import Profile1 from "../../assets/images/bride1.jpg";
|
||||
import Profile2 from "../../assets/images/bride2.jpg";
|
||||
import Profile3 from "../../assets/images/bride3.jpg";
|
||||
import Profile4 from "../../assets/images/bride4.jpg";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Heart, X, Crown, Bookmark, Eye, Clock, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
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 { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import toast from "react-hot-toast";
|
||||
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const ProfileCardItem = ({ profile }) => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
|
||||
|
||||
export default function ProfileCard() {
|
||||
// Sample data for multiple cards with image paths
|
||||
const profiles = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Jerome Bell",
|
||||
idNumber: "KI2847596",
|
||||
lastSeen: "4 Nov 2025",
|
||||
salary: "5-10",
|
||||
age: "22 yrs",
|
||||
height: "5'2\"",
|
||||
location: "Chennai",
|
||||
caste: "Brahmin",
|
||||
zodiac1: "Aries",
|
||||
zodiac2: "Scorpio",
|
||||
image: Profile1,
|
||||
useEffect(() => {
|
||||
setIsShortlisted(profile?.is_shortlisted === 1);
|
||||
}, [profile?.is_shortlisted]);
|
||||
|
||||
const shortlistMutation = useMutation({
|
||||
mutationFn: shortlistProfile,
|
||||
onMutate: () => {
|
||||
setIsShortlisted((prev) => !prev);
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Neha Singh",
|
||||
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,
|
||||
onSuccess: (data) => {
|
||||
toast.success(data.message || "Profile shortlisted successfully.");
|
||||
queryClient.invalidateQueries();
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Priya Sharma",
|
||||
idNumber: "KI2847598",
|
||||
lastSeen: "3 Nov 2025",
|
||||
salary: "6-11",
|
||||
age: "24 yrs",
|
||||
height: "5'4\"",
|
||||
location: "Mumbai",
|
||||
caste: "Brahmin",
|
||||
zodiac1: "Aries",
|
||||
zodiac2: "Scorpio",
|
||||
image: Profile3,
|
||||
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();
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Kavya Iyer",
|
||||
idNumber: "KI2847599",
|
||||
lastSeen: "2 Nov 2025",
|
||||
salary: "7-10",
|
||||
age: "23 yrs",
|
||||
height: "5'3\"",
|
||||
location: "Bangalore",
|
||||
caste: "Brahmin",
|
||||
zodiac1: "Aries",
|
||||
zodiac2: "Scorpio",
|
||||
image: Profile4,
|
||||
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.idNumber || "N/A";
|
||||
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
|
||||
const age = profile.age ? `${profile.age} yrs` : null;
|
||||
const height = profile.height || null;
|
||||
const salary = profile.annual_income_name || (profile.salary ? `${profile.salary} LPA` : null);
|
||||
const location = profile.district_name || profile.location || null;
|
||||
const caste = profile.caste_name || profile.caste || null;
|
||||
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
|
||||
const zodiac2 = profile.star_name || profile.zodiac2 || null;
|
||||
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : true;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
|
||||
onClick={() => navigate(`/profile-details/${id}`)}
|
||||
>
|
||||
{/* IMAGE SECTION */}
|
||||
<div className="relative">
|
||||
<img
|
||||
src={image}
|
||||
alt="profile"
|
||||
className="w-full h-[320px] object-cover"
|
||||
onError={(e) => {
|
||||
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
|
||||
}}
|
||||
/>
|
||||
|
||||
{isPremium && (
|
||||
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
||||
<Crown size={18} color="#fff" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!shortlistMutation.isPending) shortlistMutation.mutate(id);
|
||||
}}
|
||||
>
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||
{name}
|
||||
</h2>
|
||||
|
||||
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||
<p>ID: {idNumber}</p>
|
||||
<p className="flex items-center gap-0.5">
|
||||
<Eye size={12} /> {lastSeen}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
age,
|
||||
height,
|
||||
salary,
|
||||
location,
|
||||
caste,
|
||||
zodiac1,
|
||||
zodiac2,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||
}}
|
||||
disabled={declineMutation.isPending}
|
||||
>
|
||||
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||
}}
|
||||
disabled={interestMutation.isPending}
|
||||
>
|
||||
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ProfileCard({ profiles }) {
|
||||
const displayProfiles = profiles || [];
|
||||
|
||||
const [showTimer, setShowTimer] = useState(false);
|
||||
const [timeLeft, setTimeLeft] = useState(24 * 60 * 60); // 24 hours in seconds
|
||||
|
||||
useEffect(() => {
|
||||
const storedEndTime = localStorage.getItem("profileTimerEnd");
|
||||
if (storedEndTime) {
|
||||
const endTime = parseInt(storedEndTime, 10);
|
||||
const now = Date.now();
|
||||
if (endTime > now) {
|
||||
setShowTimer(true);
|
||||
setTimeLeft(Math.floor((endTime - now) / 1000));
|
||||
} else {
|
||||
localStorage.removeItem("profileTimerEnd");
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
if (showTimer && timeLeft > 0) {
|
||||
timer = setInterval(() => {
|
||||
setTimeLeft((prev) => {
|
||||
if (prev <= 1) {
|
||||
setShowTimer(false);
|
||||
localStorage.removeItem("profileTimerEnd");
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
return () => clearInterval(timer);
|
||||
}, [showTimer, timeLeft]);
|
||||
|
||||
const handleShowTimer = () => {
|
||||
const endTime = Date.now() + 24 * 60 * 60 * 1000; // 24 hours from now
|
||||
localStorage.setItem("profileTimerEnd", endTime.toString());
|
||||
setTimeLeft(24 * 60 * 60);
|
||||
setShowTimer(true);
|
||||
};
|
||||
|
||||
const formatTime = (seconds) => {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = seconds % 60;
|
||||
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-auto py-8 px-4">
|
||||
@ -84,78 +233,79 @@ export default function ProfileCard() {
|
||||
</h1>
|
||||
<p className="text-gray-900 text-[12px]">
|
||||
Find your perfect match today
|
||||
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* CARDS GRID */}
|
||||
<div className="flex justify-center">
|
||||
<div className="w-full max-w-[1400px] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{profiles.map((profile) => (
|
||||
<div
|
||||
key={profile.id}
|
||||
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md"
|
||||
<div className="w-full max-w-[1400px] relative px-2 md:px-12">
|
||||
{!showTimer ? (
|
||||
<>
|
||||
{/* Custom Navigation Arrows */}
|
||||
<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 */}
|
||||
<div className="relative">
|
||||
<img
|
||||
src={profile.image}
|
||||
alt="profile"
|
||||
className="w-full h-[320px] object-cover"
|
||||
/>
|
||||
|
||||
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
|
||||
<Crown size={18} color="#fff" />
|
||||
</div>
|
||||
|
||||
<div className="absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg">
|
||||
<Bookmark size={14} /> Shortlist
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||
{profile.name}
|
||||
</h2>
|
||||
|
||||
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||
<p>ID: {profile.idNumber}</p>
|
||||
<p className="flex items-center gap-0.5">
|
||||
<Eye size={12} /> {profile.lastSeen}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
profile.age,
|
||||
profile.height,
|
||||
profile.salary + " LPA",
|
||||
profile.location,
|
||||
profile.caste,
|
||||
profile.zodiac1,
|
||||
profile.zodiac2,
|
||||
].map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
<button className="px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900">
|
||||
<X size={18} /> Decline
|
||||
</button>
|
||||
|
||||
<button className="px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold">
|
||||
<Heart size={18} /> Interest
|
||||
{displayProfiles.map((profile, index) => (
|
||||
<SwiperSlide key={profile.id || index}>
|
||||
<ProfileCardItem profile={profile} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
{/* 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">
|
||||
<h3 className="text-xl font-bold mb-4 text-gray-800 text-center">You've seen all matches!</h3>
|
||||
<button
|
||||
onClick={handleShowTimer}
|
||||
className="px-6 py-2 bg-[#8b0000] text-white rounded-full font-semibold shadow-lg hover:bg-red-800 transition"
|
||||
>
|
||||
View Next Batch Timer
|
||||
</button>
|
||||
</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>
|
||||
))}
|
||||
<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>
|
||||
|
||||
@ -75,12 +75,12 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
||||
<>
|
||||
<Dropzone
|
||||
onChange={updateFiles}
|
||||
minHeight="195px"
|
||||
minHeight="200px"
|
||||
value={extFiles}
|
||||
accept="image/*"
|
||||
maxFiles={3}
|
||||
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={{
|
||||
url: BASE_URL + "/file",
|
||||
cleanOnUpload: true,
|
||||
@ -155,9 +155,9 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
||||
}}
|
||||
aria-label="Move left"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="small" />
|
||||
<ArrowLeftIcon fontSize="small" sx={{fontSize:"30px"}} />
|
||||
</button>
|
||||
<span
|
||||
{/* <span
|
||||
style={{
|
||||
color: "#fff",
|
||||
fontSize: 12,
|
||||
@ -170,7 +170,7 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
||||
title={file.name}
|
||||
>
|
||||
{file.name}
|
||||
</span>
|
||||
</span> */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
@ -197,7 +197,7 @@ const AdvancedDropzone = ({ value, onChange }) => {
|
||||
}}
|
||||
aria-label="Move right"
|
||||
>
|
||||
<ArrowRightIcon fontSize="small" />
|
||||
<ArrowRightIcon fontSize="small" sx={{fontSize:"30px"}} />
|
||||
</button>
|
||||
</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 { updateEducationalDetails } from "../redux/registrationFormSlice";
|
||||
import { updateEducationalDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
@ -9,454 +9,412 @@ import {
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormHelperText,
|
||||
InputAdornment,
|
||||
Box,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useEducationMasters, useEducationList } from "../hooks/useMasters";
|
||||
import { useCityMasters } from "../hooks/useDependentMasters";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
const EducationalDetailsForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
errors: externalErrors,
|
||||
isEditMode,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.educationalDetails);
|
||||
const inputRef = useRef(null);
|
||||
const [localErrors, setLocalErrors] = useState({});
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: educationMasters, isLoading: isEducationMastersLoading } =
|
||||
useEducationMasters();
|
||||
const educationListQuery = useEducationList(data.fieldOfStudy);
|
||||
const educationListQuery = useEducationList(data.study_field);
|
||||
const districtQuery = useCityMasters(data.work_state);
|
||||
|
||||
const studyFieldOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.studyFields || raw.study_fields || raw.fieldOfStudy || [];
|
||||
}, [educationMasters]);
|
||||
const studyFieldOptions = educationMasters?.studyFields || [];
|
||||
const qualificationOptions = educationListQuery.data?.education || educationListQuery.data?.data || [];
|
||||
const occupationOptions = educationMasters?.occupation || [];
|
||||
const employeeTypeOptions = educationMasters?.employeeType || [];
|
||||
const countryOptions = educationMasters?.country || [];
|
||||
const stateOptions = educationMasters?.state || [];
|
||||
const districtOptions = districtQuery.data?.districts || districtQuery.data || [];
|
||||
|
||||
const qualificationOptions = useMemo(() => {
|
||||
const raw = educationListQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.education || raw.data || [];
|
||||
}, [educationListQuery.data]);
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const employeeTypeOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.employeeType || raw.employee_type || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const annualIncomeOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.annualIncome || raw.annual_income || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const workLocationOptions = useMemo(() => {
|
||||
const raw = educationMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return [];
|
||||
return raw.workLocation || raw.work_location || raw.workLocations || [];
|
||||
}, [educationMasters]);
|
||||
|
||||
const getOptionLabel = (item, fallback = "") => {
|
||||
if (!item) return fallback;
|
||||
if (typeof item === "string") return item;
|
||||
return (
|
||||
item.study_field_name ||
|
||||
item.education_name ||
|
||||
item.occupation_name ||
|
||||
item.employee_type_name ||
|
||||
item.annual_income_name ||
|
||||
item.work_location_name ||
|
||||
item.name ||
|
||||
fallback
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
const isUnemployed = data.employee_type === 11;
|
||||
const isIndia = Number(data.work_country) === 1;
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
if (field === "fieldOfStudy") {
|
||||
updates.qualification = "";
|
||||
fieldsToClear.push("qualification");
|
||||
|
||||
if (field === "study_field") {
|
||||
updates.education = "";
|
||||
}
|
||||
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));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
setLocalErrors((prev) => ({ ...prev, [field]: "" }));
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(3));
|
||||
}
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
if (!data.study_field) newErrors.study_field = "Required";
|
||||
if (!data.education) newErrors.education = "Required";
|
||||
if (!data.education_detail) newErrors.education_detail = "Required";
|
||||
if (!data.employee_type) newErrors.employee_type = "Required";
|
||||
|
||||
if (!isUnemployed) {
|
||||
if (!data.occupation) newErrors.occupation = "Required";
|
||||
if (!data.occupation_detail) newErrors.occupation_detail = "Required";
|
||||
if (!data.income_currency) newErrors.income_currency = "Required";
|
||||
if (!data.annual_income) newErrors.annual_income = "Required";
|
||||
if (!data.work_country) newErrors.work_country = "Required";
|
||||
|
||||
if (isIndia) {
|
||||
if (!data.work_state) newErrors.work_state = "Required";
|
||||
if (!data.work_district) newErrors.work_district = "Required";
|
||||
} else {
|
||||
if (!data.work_city) newErrors.work_city = "Required";
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.address) newErrors.address = "Required";
|
||||
|
||||
setLocalErrors(newErrors);
|
||||
return newErrors;
|
||||
};
|
||||
|
||||
const scrollToError = (errorMap) => {
|
||||
const errorFields = Object.keys(errorMap);
|
||||
if (errorFields.length > 0) {
|
||||
const fieldId = errorFields[0];
|
||||
const element = document.getElementById(fieldId);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
setTimeout(() => {
|
||||
const focusable = element.querySelector('[role="combobox"]') ||
|
||||
element.querySelector('[role="button"]') ||
|
||||
element.querySelector("input") ||
|
||||
element.querySelector("select") ||
|
||||
element;
|
||||
if (focusable && typeof focusable.focus === "function") {
|
||||
focusable.focus();
|
||||
}
|
||||
}, 300); // Reduced delay slightly for snappier feel
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
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();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{/* Field of Study */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Field of Study{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.fieldOfStudy)}
|
||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{/* 1. Field of Study */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Field of Study{requiredMark}</label>
|
||||
<FormControl fullWidth error={Boolean(localErrors.study_field)} id="study_field">
|
||||
<InputLabel>Select Field of Study</InputLabel>
|
||||
<Select
|
||||
value={data.study_field}
|
||||
label="Select Field of Study"
|
||||
onChange={(e) => handleChange("study_field", e.target.value)}
|
||||
>
|
||||
<InputLabel id="fieldOfStudy-label">
|
||||
Select Field of Study
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="fieldOfStudy-label"
|
||||
label="Select Field of Study"
|
||||
name="fieldOfStudy"
|
||||
value={data.fieldOfStudy}
|
||||
onChange={(e) => handleChange("fieldOfStudy", e.target.value)}
|
||||
inputRef={inputRef}
|
||||
disabled={isEducationMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{studyFieldOptions.map((field) => (
|
||||
<MenuItem key={field.id ?? field} value={field.id ?? field}>
|
||||
{getOptionLabel(field, "Field of Study")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.fieldOfStudy && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.fieldOfStudy}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Highest Educational Qualification{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.qualification)}
|
||||
>
|
||||
<InputLabel id="qualification-label">
|
||||
Select Highest Qualification
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="qualification-label"
|
||||
label="Select Highest Qualification"
|
||||
name="qualification"
|
||||
value={data.qualification}
|
||||
onChange={(e) => handleChange("qualification", e.target.value)}
|
||||
disabled={
|
||||
!data.fieldOfStudy || educationListQuery.isLoading
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{qualificationOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{getOptionLabel(item, "Qualification")}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.qualification && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.qualification}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* College Name */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
College Name
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="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>
|
||||
{studyFieldOptions.map((opt) => (
|
||||
<MenuItem key={opt.id} value={opt.id}>{opt.study_field_name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{localErrors.study_field && <FormHelperText>{localErrors.study_field}</FormHelperText>}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
marginTop: "40px",
|
||||
display: "flex",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Next
|
||||
{/* 2. Highest Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Highest Qualification{requiredMark}</label>
|
||||
<FormControl fullWidth error={Boolean(localErrors.education)} id="education" disabled={!data.study_field || educationListQuery.isLoading}>
|
||||
<InputLabel>Select Qualification</InputLabel>
|
||||
<Select
|
||||
value={data.education}
|
||||
label="Select Qualification"
|
||||
onChange={(e) => handleChange("education", e.target.value)}
|
||||
>
|
||||
{qualificationOptions.map((opt) => (
|
||||
<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 variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Button variant="contained" size="large" onClick={handleSubmit} sx={{ minWidth: 120 }}>
|
||||
{onSkipStep ? "Next" : "Update"}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateFamilyDetails } from "../redux/registrationFormSlice";
|
||||
import { updateFamilyDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
@ -12,46 +12,55 @@ import {
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
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 data = useSelector((state) => state.registerform.familyDetails);
|
||||
const inputRef = useRef(null);
|
||||
const brotherSectionRef = useRef(null);
|
||||
const sisterSectionRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: familyMasters, isLoading: isFamilyMastersLoading } =
|
||||
useFamilyMasters();
|
||||
const { data: familyMasters, isLoading: isFamilyMastersLoading } = useFamilyMasters();
|
||||
|
||||
// District query for India
|
||||
const districtQuery = useCityMasters(data.familyState);
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
return raw.occupation || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const maritalStatusOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.maritalStatus || raw.marital_status || [];
|
||||
return raw.maritalStatus || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const familyStatusOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return [];
|
||||
return raw.familyStatus || raw.family_status || [];
|
||||
return raw.familyStatus || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const countryOptions = useMemo(() => familyMasters?.country || [], [familyMasters]);
|
||||
const stateOptions = useMemo(() => familyMasters?.state || [], [familyMasters]);
|
||||
const districtOptions = useMemo(() => districtQuery.data?.districts || districtQuery.data || [], [districtQuery.data]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const createSibling = () => ({
|
||||
type: "",
|
||||
name: "",
|
||||
occupation: "",
|
||||
maritalStatus: "",
|
||||
haveChildrens: "",
|
||||
hasChildren: "",
|
||||
details: "",
|
||||
});
|
||||
|
||||
const syncSiblingArray = (arr, count) => {
|
||||
@ -72,20 +81,48 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
|
||||
if (field === "brotherCount") {
|
||||
const count = Number(value) || 0;
|
||||
updates.brotherCount = count;
|
||||
updates.brotherCount = value;
|
||||
updates.brothers = syncSiblingArray(data.brothers, count);
|
||||
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") {
|
||||
const count = Number(value) || 0;
|
||||
updates.sisterCount = count;
|
||||
updates.sisterCount = value;
|
||||
updates.sisters = syncSiblingArray(data.sisters, count);
|
||||
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));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(4));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSiblingChange = (type, index, field, value) => {
|
||||
@ -94,14 +131,40 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
list[index] = { ...list[index], [field]: value };
|
||||
dispatch(updateFamilyDetails({ [type]: list }));
|
||||
if (onFieldChange) onFieldChange(type);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(4));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Submitting family details:", data);
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
if (e) e.preventDefault();
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
const countOptions = Array.from({ length: 11 }, (_, i) => i);
|
||||
const isIndia = Number(data.familyCountry) === 1;
|
||||
|
||||
const renderSiblingCard = (type, index) => {
|
||||
const sibling = (data[type] || [])[index] || createSibling();
|
||||
@ -114,95 +177,88 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
borderRadius: 2,
|
||||
padding: 2,
|
||||
backgroundColor: "#fff",
|
||||
mb: 4
|
||||
}}
|
||||
>
|
||||
<div className="text-gray-900 text-[14px] font-semibold mb-3">
|
||||
{labelPrefix} {index + 1}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||
<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
|
||||
fullWidth
|
||||
label="Name"
|
||||
placeholder="Enter Name"
|
||||
value={sibling.name}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "name", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleSiblingChange(type, index, "name", e.target.value)}
|
||||
variant="outlined"
|
||||
inputProps={{ className: index === 0 ? "first-sibling-name" : "" }}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-occupation-label`}>
|
||||
Occupation
|
||||
</InputLabel>
|
||||
<InputLabel>Occupation</InputLabel>
|
||||
<Select
|
||||
labelId={`${type}-${index}-occupation-label`}
|
||||
label="Occupation"
|
||||
value={sibling.occupation}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "occupation", e.target.value)
|
||||
}
|
||||
disabled={isFamilyMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => handleSiblingChange(type, index, "occupation", e.target.value)}
|
||||
>
|
||||
<MenuItem value=""><em>Select</em></MenuItem>
|
||||
{occupationOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-marital-label`}>
|
||||
Marital Status
|
||||
</InputLabel>
|
||||
<InputLabel>Marital Status</InputLabel>
|
||||
<Select
|
||||
labelId={`${type}-${index}-marital-label`}
|
||||
label="Marital Status"
|
||||
value={sibling.maritalStatus}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "maritalStatus", e.target.value)
|
||||
}
|
||||
disabled={isFamilyMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
onChange={(e) => handleSiblingChange(type, index, "maritalStatus", e.target.value)}
|
||||
>
|
||||
<MenuItem value=""><em>Select</em></MenuItem>
|
||||
{maritalStatusOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-children-label`}>
|
||||
Have Children
|
||||
</InputLabel>
|
||||
<InputLabel>Have Children</InputLabel>
|
||||
<Select
|
||||
labelId={`${type}-${index}-children-label`}
|
||||
label="Have Children"
|
||||
value={sibling.haveChildrens}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(
|
||||
type,
|
||||
index,
|
||||
"haveChildrens",
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
value={sibling.hasChildren}
|
||||
onChange={(e) => handleSiblingChange(type, index, "hasChildren", e.target.value)}
|
||||
>
|
||||
<MenuItem value={1}>Yes</MenuItem>
|
||||
<MenuItem value={0}>No</MenuItem>
|
||||
<MenuItem value=""><em>Select</em></MenuItem>
|
||||
<MenuItem value="Yes">Yes</MenuItem>
|
||||
<MenuItem value="No">No</MenuItem>
|
||||
</Select>
|
||||
</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>
|
||||
</Box>
|
||||
);
|
||||
@ -212,20 +268,18 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4" id="fatherName">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Father Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="fatherName"
|
||||
label="Father Name"
|
||||
placeholder="Enter Father Name"
|
||||
value={data.fatherName}
|
||||
onChange={(e) => handleChange("fatherName", e.target.value)}
|
||||
error={Boolean(errors.fatherName)}
|
||||
helperText={errors.fatherName}
|
||||
placeholder="Enter Father Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
@ -236,30 +290,24 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="fatherOccupation"
|
||||
label="Father Occupation"
|
||||
placeholder="Enter Father Occupation"
|
||||
value={data.fatherOccupation}
|
||||
onChange={(e) => handleChange("fatherOccupation", e.target.value)}
|
||||
error={Boolean(errors.fatherOccupation)}
|
||||
helperText={errors.fatherOccupation}
|
||||
placeholder="Enter Father Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4" id="motherName">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Mother Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherName"
|
||||
label="Mother Name"
|
||||
placeholder="Enter Mother Name"
|
||||
value={data.motherName}
|
||||
onChange={(e) => handleChange("motherName", e.target.value)}
|
||||
error={Boolean(errors.motherName)}
|
||||
helperText={errors.motherName}
|
||||
placeholder="Enter Mother Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
@ -270,13 +318,9 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherOccupation"
|
||||
label="Mother Occupation"
|
||||
placeholder="Enter Mother Occupation"
|
||||
value={data.motherOccupation}
|
||||
onChange={(e) => handleChange("motherOccupation", e.target.value)}
|
||||
error={Boolean(errors.motherOccupation)}
|
||||
helperText={errors.motherOccupation}
|
||||
placeholder="Enter Mother Occupation"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
@ -286,14 +330,13 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
Brother Count
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="brotherCount-label">Select Brother Count</InputLabel>
|
||||
<InputLabel>Select Brother Count</InputLabel>
|
||||
<Select
|
||||
labelId="brotherCount-label"
|
||||
label="Select Brother Count"
|
||||
name="brotherCount"
|
||||
value={data.brotherCount}
|
||||
onChange={(e) => handleChange("brotherCount", e.target.value)}
|
||||
>
|
||||
<MenuItem value=""><em>Select</em></MenuItem>
|
||||
{countOptions.map((count) => (
|
||||
<MenuItem key={count} value={count}>
|
||||
{count}
|
||||
@ -303,19 +346,27 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
</FormControl>
|
||||
</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">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Sister Count
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="sisterCount-label">Select Sister Count</InputLabel>
|
||||
<InputLabel>Select Sister Count</InputLabel>
|
||||
<Select
|
||||
labelId="sisterCount-label"
|
||||
label="Select Sister Count"
|
||||
name="sisterCount"
|
||||
value={data.sisterCount}
|
||||
onChange={(e) => handleChange("sisterCount", e.target.value)}
|
||||
>
|
||||
<MenuItem value=""><em>Select</em></MenuItem>
|
||||
{countOptions.map((count) => (
|
||||
<MenuItem key={count} value={count}>
|
||||
{count}
|
||||
@ -325,103 +376,183 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
|
||||
</FormControl>
|
||||
</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]">
|
||||
Family Status{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.familyStatus)}
|
||||
>
|
||||
<InputLabel id="familyStatus-label">Select Family Status</InputLabel>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.familyStatus)}>
|
||||
<InputLabel>Select Family Status</InputLabel>
|
||||
<Select
|
||||
labelId="familyStatus-label"
|
||||
label="Select Family Status"
|
||||
name="familyStatus"
|
||||
value={data.familyStatus}
|
||||
onChange={(e) => handleChange("familyStatus", e.target.value)}
|
||||
disabled={isFamilyMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{familyStatusOptions.map((item) => (
|
||||
<MenuItem key={item.id ?? item} value={item.id ?? item}>
|
||||
{item.family_type_name || item.name || item}
|
||||
<MenuItem key={item.id} value={item.id}>
|
||||
{item.family_type_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.familyStatus && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.familyStatus}
|
||||
</p>
|
||||
<p className="text-[#d32f2f] text-[0.75rem] mt-1 ml-3">{errors.familyStatus}</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Native Place</label>
|
||||
<div className="flex flex-col gap-4" id="nativePlace">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Native Place{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="nativePlace"
|
||||
label="Native Place"
|
||||
placeholder="Enter Native Place"
|
||||
value={data.nativePlace}
|
||||
onChange={(e) => handleChange("nativePlace", e.target.value)}
|
||||
error={Boolean(errors.nativePlace)}
|
||||
helperText={errors.nativePlace}
|
||||
placeholder="Enter Native Place"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div 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>
|
||||
|
||||
{Number(data.brotherCount) > 0 && (
|
||||
<div className="mt-6">
|
||||
<div className="text-gray-900 text-[16px] font-semibold mb-3">
|
||||
Brother Details
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{Array.from({ length: Number(data.brotherCount) }).map(
|
||||
(_, index) => renderSiblingCard("brothers", index)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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
|
||||
<div className="mt-10 flex gap-4 justify-center">
|
||||
{onSkipStep && (
|
||||
<Button variant="outlined" onClick={onSkipStep} sx={{ minWidth: 120 }}>
|
||||
Skip
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit} sx={{ minWidth: 120 }}>
|
||||
{onSkipStep ? "Next" : "Update"}
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
setAge,
|
||||
setHeight,
|
||||
setMaritalStatus,
|
||||
setMotherTongue,
|
||||
setReligion,
|
||||
setMatchesWithHoroscope,
|
||||
setCaste,
|
||||
setSubCaste,
|
||||
updateFilter,
|
||||
resetFilters,
|
||||
} from "../redux/filterSlice";
|
||||
import {
|
||||
Slider,
|
||||
@ -26,8 +23,12 @@ import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
CircularProgress,
|
||||
} 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 dispatch = useDispatch();
|
||||
@ -40,34 +41,61 @@ const FilterForm = () => {
|
||||
location: false,
|
||||
lifestyle: 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) => {
|
||||
setExpandedSections((prev) => ({ ...prev, [section]: isExpanded }));
|
||||
};
|
||||
|
||||
const casteOptions = ["Agamudayar", "Pillai", "Vellalar"];
|
||||
const motherTongueOptions = [
|
||||
"Tamil",
|
||||
"Telugu",
|
||||
"Malayalam",
|
||||
"Kannada",
|
||||
"Hindi",
|
||||
];
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("Filter Values:", filters);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
dispatch(resetFilters());
|
||||
toast.success("Filters cleared");
|
||||
};
|
||||
|
||||
const handleSelectionChange = (field, 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 (
|
||||
<div className="max-w-6xl mx-auto p-4 px-0 md:p-6 ">
|
||||
<div className="bg-white rounded-lg shadow-sm">
|
||||
{/* 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">
|
||||
Partner Filter preference
|
||||
</Typography>
|
||||
@ -98,7 +126,7 @@ const FilterForm = () => {
|
||||
<Typography gutterBottom sx={{marginBottom:"30px"}}>Age</Typography>
|
||||
<div className="px-2">
|
||||
<Slider
|
||||
value={filters.age}
|
||||
value={[filters.from_age, filters.to_age]}
|
||||
onChange={(e, newValue) => {
|
||||
dispatch(setAge(newValue));
|
||||
handleSelectionChange("Age", newValue);
|
||||
@ -119,7 +147,7 @@ const FilterForm = () => {
|
||||
<Typography gutterBottom sx={{marginBottom:"30px"}}>Height</Typography>
|
||||
<div className="px-2">
|
||||
<Slider
|
||||
value={filters.height}
|
||||
value={[filters.from_height, filters.to_height]}
|
||||
onChange={(e, newValue) => {
|
||||
dispatch(setHeight(newValue));
|
||||
handleSelectionChange("Height", newValue);
|
||||
@ -141,46 +169,40 @@ const FilterForm = () => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Marital Status</InputLabel>
|
||||
<Select
|
||||
value={filters.maritalStatus}
|
||||
multiple
|
||||
value={filters.marital_status || []}
|
||||
label="Marital Status"
|
||||
onChange={(e) => {
|
||||
dispatch(setMaritalStatus(e.target.value));
|
||||
dispatch(updateFilter({ 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) => (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} size="small" />
|
||||
))}
|
||||
{selected.map((value) => {
|
||||
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>
|
||||
)}
|
||||
>
|
||||
{motherTongueOptions.map((lang) => (
|
||||
<MenuItem key={lang} value={lang}>
|
||||
{lang}
|
||||
{filterMasters?.marital_statuses?.map((status) => (
|
||||
<MenuItem key={status.id} value={status.id}>
|
||||
{status.marital_status_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
@ -207,61 +229,69 @@ const FilterForm = () => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Religion</InputLabel>
|
||||
<Select
|
||||
value={filters.religion}
|
||||
multiple
|
||||
value={filters.religion || []}
|
||||
label="Religion"
|
||||
onChange={(e) => {
|
||||
dispatch(setReligion(e.target.value));
|
||||
dispatch(updateFilter({ 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>
|
||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
||||
<MenuItem value="Christian">Christian</MenuItem>
|
||||
<MenuItem value="Sikh">Sikh</MenuItem>
|
||||
{filterMasters?.religions?.map((rel) => (
|
||||
<MenuItem key={rel.id} value={rel.id}>
|
||||
{rel.religion_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Matches with Horoscope */}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={filters.matchesWithHoroscope}
|
||||
onChange={(e) => {
|
||||
dispatch(setMatchesWithHoroscope(e.target.checked));
|
||||
handleSelectionChange(
|
||||
"Matches with Horoscope",
|
||||
e.target.checked
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Matches with horoscope"
|
||||
/>
|
||||
|
||||
{/* Caste */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Caste (Multi Select)</InputLabel>
|
||||
<InputLabel>Caste</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={filters.caste}
|
||||
label="Caste (Multi Select)"
|
||||
label="Caste"
|
||||
onChange={(e) => {
|
||||
dispatch(setCaste(e.target.value));
|
||||
handleSelectionChange("Caste", e.target.value);
|
||||
}}
|
||||
renderValue={(selected) => (
|
||||
<Box
|
||||
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
|
||||
>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} size="small" />
|
||||
))}
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => {
|
||||
const label = filterMasters?.castes?.find((c) => c.id === value)?.caste_name || value;
|
||||
return (
|
||||
<Chip
|
||||
key={value}
|
||||
label={label}
|
||||
size="small"
|
||||
onDelete={() => dispatch(setCaste(filters.caste.filter((v) => v !== value)))}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{["Agamudayar", "Pillai", "Vellalar"].map((caste) => (
|
||||
<MenuItem key={caste} value={caste}>
|
||||
{caste}
|
||||
{filterMasters?.castes?.map((caste) => (
|
||||
<MenuItem key={caste.id} value={caste.id}>
|
||||
{caste.caste_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
@ -269,21 +299,19 @@ const FilterForm = () => {
|
||||
|
||||
{/* Sub-Caste */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sub-Caste (Multi Select)</InputLabel>
|
||||
<InputLabel>Sub-Caste</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={filters.subCaste}
|
||||
label="Sub-Caste (Multi Select)"
|
||||
value={filters.sub_caste}
|
||||
label="Sub-Caste"
|
||||
onChange={(e) => {
|
||||
dispatch(setSubCaste(e.target.value));
|
||||
handleSelectionChange("Sub-Caste", e.target.value);
|
||||
}}
|
||||
renderValue={(selected) => (
|
||||
<Box
|
||||
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{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>
|
||||
)}
|
||||
@ -296,39 +324,7 @@ const FilterForm = () => {
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Star */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Star</InputLabel>
|
||||
<Select
|
||||
value={filters.star}
|
||||
label="Star"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ star: e.target.value }));
|
||||
handleSelectionChange("Star", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Ashwini">Ashwini</MenuItem>
|
||||
<MenuItem value="Bharani">Bharani</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Dasham */}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Dasham</InputLabel>
|
||||
<Select
|
||||
value={filters.dasham}
|
||||
label="Dasham"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ dasham: e.target.value }));
|
||||
handleSelectionChange("Dasham", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Doesn't matter">Doesn't matter</MenuItem>
|
||||
<MenuItem value="Yes">Yes</MenuItem>
|
||||
<MenuItem value="No">No</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
@ -356,66 +352,108 @@ const FilterForm = () => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Occupation</InputLabel>
|
||||
<Select
|
||||
value={filters.occupation}
|
||||
multiple
|
||||
value={filters.occupation || []}
|
||||
label="Occupation"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ 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>
|
||||
<MenuItem value="Software Engineer">
|
||||
Software Engineer
|
||||
</MenuItem>
|
||||
<MenuItem value="Doctor">Doctor</MenuItem>
|
||||
{filterMasters?.occupations?.map((occ) => (
|
||||
<MenuItem key={occ.id} value={occ.id}>
|
||||
{occ.occupation_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Annual Income</InputLabel>
|
||||
<Select
|
||||
value={filters.annualIncome}
|
||||
multiple
|
||||
value={filters.annual_income || []}
|
||||
label="Annual Income"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ annualIncome: e.target.value }));
|
||||
dispatch(updateFilter({ 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>
|
||||
<MenuItem value="0-5 Lakhs">0-5 Lakhs</MenuItem>
|
||||
<MenuItem value="5-10 Lakhs">5-10 Lakhs</MenuItem>
|
||||
{filterMasters?.annual_incomes?.map((inc) => (
|
||||
<MenuItem key={inc.id} value={inc.id}>
|
||||
{inc.annual_income_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Employee Type</InputLabel>
|
||||
<Select
|
||||
value={filters.employeeType}
|
||||
multiple
|
||||
value={filters.employee_type || []}
|
||||
label="Employee Type"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ employeeType: e.target.value }));
|
||||
dispatch(updateFilter({ 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>
|
||||
<MenuItem value="Government">Government</MenuItem>
|
||||
<MenuItem value="Private">Private</MenuItem>
|
||||
{filterMasters?.employee_types?.map((emp) => (
|
||||
<MenuItem key={emp.id} value={emp.id}>
|
||||
{emp.employee_type_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Education</InputLabel>
|
||||
<Select
|
||||
value={filters.education}
|
||||
multiple
|
||||
value={filters.education || []}
|
||||
label="Education"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ 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>
|
||||
<MenuItem value="Bachelor">Bachelor</MenuItem>
|
||||
<MenuItem value="Master">Master</MenuItem>
|
||||
{filterMasters?.educations?.map((edu) => (
|
||||
<MenuItem key={edu.id} value={edu.id}>
|
||||
{edu.education_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
@ -443,50 +481,59 @@ const FilterForm = () => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>State</InputLabel>
|
||||
<Select
|
||||
value={filters.state}
|
||||
multiple
|
||||
value={filters.state || []}
|
||||
label="State"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ state: e.target.value }));
|
||||
dispatch(updateFilter({ state: e.target.value, district: [] }));
|
||||
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>
|
||||
<MenuItem value="Tamil Nadu">Tamil Nadu</MenuItem>
|
||||
<MenuItem value="Karnataka">Karnataka</MenuItem>
|
||||
{filterMasters?.states?.map((state) => (
|
||||
<MenuItem key={state.id} value={state.id}>
|
||||
{state.state_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Country</InputLabel>
|
||||
<InputLabel>City</InputLabel>
|
||||
<Select
|
||||
value={filters.country}
|
||||
label="Country"
|
||||
multiple
|
||||
value={filters.district || []}
|
||||
label="City"
|
||||
disabled={!filters.state || filters.state.length === 0}
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ country: e.target.value }));
|
||||
handleSelectionChange("Country", e.target.value);
|
||||
dispatch(updateFilter({ district: 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>
|
||||
<MenuItem value="India">India</MenuItem>
|
||||
<MenuItem value="USA">USA</MenuItem>
|
||||
{cityOptions.map((city) => (
|
||||
<MenuItem key={city.id} value={city.id}>
|
||||
{city.district_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Citizenship</InputLabel>
|
||||
<Select
|
||||
value={filters.citizenship}
|
||||
label="Citizenship"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ citizenship: e.target.value }));
|
||||
handleSelectionChange("Citizenship", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Indian">Indian</MenuItem>
|
||||
<MenuItem value="US Citizen">US Citizen</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
@ -512,54 +559,22 @@ const FilterForm = () => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Eating Habits</InputLabel>
|
||||
<Select
|
||||
value={filters.eatingHabits}
|
||||
value={filters.diet}
|
||||
label="Eating Habits"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ eatingHabits: e.target.value }));
|
||||
dispatch(updateFilter({ diet: e.target.value }));
|
||||
handleSelectionChange("Eating Habits", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Vegetarian">Vegetarian</MenuItem>
|
||||
<MenuItem value="Non-Vegetarian">Non-Vegetarian</MenuItem>
|
||||
{filterMasters?.diets?.map((diet) => (
|
||||
<MenuItem key={diet.id} value={diet.id}>
|
||||
{diet.diet_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* <FormControl fullWidth>
|
||||
<InputLabel>Smoking Habits</InputLabel>
|
||||
<Select
|
||||
value={filters.smokingHabits}
|
||||
label="Smoking Habits"
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
updateFilter({ smokingHabits: e.target.value })
|
||||
);
|
||||
handleSelectionChange("Smoking Habits", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Doesn't matter">Doesn't matter</MenuItem>
|
||||
<MenuItem value="Non-Smoker">Non-Smoker</MenuItem>
|
||||
<MenuItem value="Smoker">Smoker</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Drinking Habits</InputLabel>
|
||||
<Select
|
||||
value={filters.drinkingHabits}
|
||||
label="Drinking Habits"
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
updateFilter({ drinkingHabits: e.target.value })
|
||||
);
|
||||
handleSelectionChange("Drinking Habits", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Doesn't matter">Doesn't matter</MenuItem>
|
||||
<MenuItem value="Non-Drinker">Non-Drinker</MenuItem>
|
||||
<MenuItem value="Social Drinker">Social Drinker</MenuItem>
|
||||
</Select>
|
||||
</FormControl> */}
|
||||
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
@ -585,64 +600,116 @@ const FilterForm = () => {
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Family Type</InputLabel>
|
||||
<Select
|
||||
value={filters.familyType}
|
||||
multiple
|
||||
value={filters.family_type || []}
|
||||
label="Family Type"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ familyType: e.target.value }));
|
||||
dispatch(updateFilter({ 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>
|
||||
<MenuItem value="Nuclear">Nuclear</MenuItem>
|
||||
<MenuItem value="Joint">Joint</MenuItem>
|
||||
{filterMasters?.family_types?.map((type) => (
|
||||
<MenuItem key={type.id} value={type.id}>
|
||||
{type.family_type_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Family Status</InputLabel>
|
||||
<Select
|
||||
value={filters.familyStatus}
|
||||
label="Family Status"
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ familyStatus: e.target.value }));
|
||||
handleSelectionChange("Family Status", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
<MenuItem value="Middle Class">Middle Class</MenuItem>
|
||||
<MenuItem value="Upper Middle Class">
|
||||
Upper Middle Class
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* 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>
|
||||
<InputLabel>Family Value</InputLabel>
|
||||
<InputLabel>Star</InputLabel>
|
||||
<Select
|
||||
value={filters.familyValue}
|
||||
label="Family Value"
|
||||
multiple
|
||||
value={filters.star || []}
|
||||
label="Star"
|
||||
disabled={!filters.isPaidMember}
|
||||
onChange={(e) => {
|
||||
dispatch(updateFilter({ familyValue: e.target.value }));
|
||||
handleSelectionChange("Family Value", e.target.value);
|
||||
dispatch(updateFilter({ star: 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>
|
||||
<MenuItem value="Traditional">Traditional</MenuItem>
|
||||
<MenuItem value="Moderate">Moderate</MenuItem>
|
||||
<MenuItem value="Liberal">Liberal</MenuItem>
|
||||
{filterMasters?.stars?.map((star) => (
|
||||
<MenuItem key={star.id} value={star.id}>
|
||||
{star.star_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-6 flex justify-center">
|
||||
{/* Sticky Footer Actions */}
|
||||
<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
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={handleSubmit}
|
||||
className="px-12"
|
||||
startIcon={<Check size={20} />}
|
||||
>
|
||||
Apply Filters
|
||||
</Button>
|
||||
|
||||
@ -27,7 +27,7 @@ const FilterModal = () => {
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
fullWidth
|
||||
maxWidth="lg" // adjust: "sm" | "md" | "lg"
|
||||
maxWidth="md" // adjust: "sm" | "md" | "lg"
|
||||
>
|
||||
<DialogTitle sx={{background:"#f5fbff"}}>
|
||||
<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 { updateLifestyleDetails } from "../redux/registrationFormSlice";
|
||||
import { updateLifestyleDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
FormControl,
|
||||
@ -11,18 +11,26 @@ import {
|
||||
TextField,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { useLifestyleMasters } from "../hooks/useMasters";
|
||||
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||
|
||||
const LifestyleDetailsForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
isEditMode,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
||||
@ -53,6 +61,15 @@ const LifestyleDetailsForm = ({
|
||||
return raw.grahas || raw.graha || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const [confirmDialog, setConfirmDialog] = useState({
|
||||
open: false,
|
||||
type: "",
|
||||
house: null,
|
||||
item: "",
|
||||
sourceHouse: null,
|
||||
pendingValue: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
@ -60,6 +77,10 @@ const LifestyleDetailsForm = ({
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(5));
|
||||
}
|
||||
};
|
||||
|
||||
const handleMultiChange = (field, value) => {
|
||||
@ -68,23 +89,74 @@ const LifestyleDetailsForm = ({
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
};
|
||||
|
||||
const handleGrahaChange = (house, value) => {
|
||||
const next = { ...(data.graha || {}) };
|
||||
next[house] = value;
|
||||
dispatch(updateLifestyleDetails({ graha: next }));
|
||||
if (onFieldChange) onFieldChange("graha");
|
||||
const handleChartChange = (type, house, newValue) => {
|
||||
const currentChart = type === "graha" ? data.graha : data.amsam;
|
||||
const currentHouseValue = currentChart?.[house] || [];
|
||||
|
||||
// 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 next = { ...(data.amsam || {}) };
|
||||
next[house] = value;
|
||||
dispatch(updateLifestyleDetails({ amsam: next }));
|
||||
if (onFieldChange) onFieldChange("amsam");
|
||||
const handleConfirmMove = () => {
|
||||
const { type, house, item, sourceHouse, pendingValue } = confirmDialog;
|
||||
const currentChart = type === "graha" ? data.graha : data.amsam;
|
||||
const nextChart = { ...(currentChart || {}) };
|
||||
|
||||
// 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 = () => {
|
||||
console.log("Submitting lifestyle details:", data);
|
||||
onSubmitStep();
|
||||
|
||||
};
|
||||
|
||||
const parseDateValue = (value) => {
|
||||
@ -118,6 +190,7 @@ const LifestyleDetailsForm = ({
|
||||
};
|
||||
|
||||
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]">
|
||||
<span className="text-[10px] font-medium text-gray-700 text-center">
|
||||
{label}
|
||||
@ -130,7 +203,17 @@ const LifestyleDetailsForm = ({
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
renderValue={(selected) => {
|
||||
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={{
|
||||
PaperProps: { style: { maxHeight: 280 } },
|
||||
@ -145,15 +228,14 @@ const LifestyleDetailsForm = ({
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const renderChartGrid = (type) => {
|
||||
const getValue = (house) =>
|
||||
(type === "graha" ? data.graha : data.amsam)?.[house] || [];
|
||||
const onChange = (house, value) =>
|
||||
type === "graha"
|
||||
? handleGrahaChange(house, value)
|
||||
: handleAmsamChange(house, value);
|
||||
handleChartChange(type, house, value);
|
||||
const label = type === "graha" ? "Rasi" : "Navamsam";
|
||||
|
||||
return (
|
||||
@ -165,7 +247,9 @@ const LifestyleDetailsForm = ({
|
||||
|
||||
{renderChartCell(label, getValue(5), (value) => onChange(5, value))}
|
||||
<div className="col-span-2 row-span-2 flex items-center justify-center">
|
||||
<div className="w-full aspect-square rounded-full overflow-hidden border-4 border-gray-200 shadow-lg bg-white" />
|
||||
<div 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>
|
||||
{renderChartCell(label, getValue(6), (value) => onChange(6, value))}
|
||||
|
||||
@ -186,7 +270,7 @@ const LifestyleDetailsForm = ({
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Diet (Multi-select){requiredMark}
|
||||
Diet{requiredMark}
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
||||
<InputLabel id="diets-label">Select Diet</InputLabel>
|
||||
@ -194,21 +278,10 @@ const LifestyleDetailsForm = ({
|
||||
labelId="diets-label"
|
||||
label="Select Diet"
|
||||
name="diets"
|
||||
multiple
|
||||
value={data.diets}
|
||||
onChange={(e) => handleMultiChange("diets", e.target.value)}
|
||||
onChange={(e) => handleChange("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",
|
||||
@ -220,8 +293,7 @@ const LifestyleDetailsForm = ({
|
||||
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} />
|
||||
{label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
@ -406,13 +478,33 @@ const LifestyleDetailsForm = ({
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
{onSkipStep && (
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Next
|
||||
{onSkipStep ? "Next" : "Update"}
|
||||
</Button>
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -417,11 +417,13 @@ const PartnerPreferencesForm = ({
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
{onSkipStep && (
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Next
|
||||
{onSkipStep ? "Next" : "Update"}
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
0
src/feature/PreviewProfile.jsx
Normal file
0
src/feature/PreviewProfile.jsx
Normal file
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Navigation, Pagination } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
@ -17,9 +17,16 @@ import {
|
||||
Button,
|
||||
Grid,
|
||||
Tooltip,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
import { usePreviewDetails } from "../hooks/usePreview";
|
||||
import { isAuthenticated } from "../utills/auth";
|
||||
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||
|
||||
const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
const formData = useSelector((state) => state.registerform);
|
||||
@ -28,6 +35,37 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
const isLoading = isAuth && apiLoading;
|
||||
const isError = isAuth && apiError;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const pd = previewData?.personal_details;
|
||||
if (pd) {
|
||||
const images = pd.images || pd.profile_images;
|
||||
if (images && images.length > 0) {
|
||||
// Map images to ensure they have a consistent format for Redux
|
||||
const formattedImages = images.map(img =>
|
||||
typeof img === 'string' ? { url: img, preview: img } : img
|
||||
);
|
||||
dispatch(updatePersonalDetails({ profiles: formattedImages }));
|
||||
}
|
||||
}
|
||||
}, [previewData, dispatch]);
|
||||
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
|
||||
const handleConfirmSubmit = () => {
|
||||
setOpenConfirmDialog(true);
|
||||
};
|
||||
|
||||
const handleProceedSubmit = () => {
|
||||
setOpenConfirmDialog(false);
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
const handleCancelSubmit = () => {
|
||||
setOpenConfirmDialog(false);
|
||||
};
|
||||
|
||||
const sections = previewData?.personal_details
|
||||
? [
|
||||
{
|
||||
@ -183,8 +221,8 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0.5, maxWidth: '300px', mx: 'auto', p: 1, bgcolor: '#fff3e0', borderRadius: 2 }}>
|
||||
{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' }}>
|
||||
{title.toUpperCase()}
|
||||
<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)}
|
||||
@ -224,7 +262,10 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
{!isLoading &&
|
||||
sections.map((section) => (
|
||||
|
||||
<Card key={section.title} variant="outlined" sx={{ borderRadius: 2, background:"#fff5ed" }}>
|
||||
<Card key={section.title} variant="outlined"
|
||||
sx={{ borderRadius: 2,
|
||||
// background:"#fff5ed"
|
||||
}}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
@ -243,11 +284,25 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
}
|
||||
sx={{padding:"15px 15px", background:"#f5fbff" }}
|
||||
/>
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
<CardContent sx={{ pt: 1, }}>
|
||||
{Object.entries(section.data || {}).map(([key, value]) => {
|
||||
// Filter out ID fields
|
||||
if (key === 'id' || key.endsWith('_id') || key.endsWith('Id') || key.endsWith('_ids') || key.endsWith('Ids') || key === 'created_at' || key === 'updated_at') {
|
||||
const hiddenFields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"phone_number_visibility",
|
||||
"chat_alert_notification",
|
||||
"chat_protection",
|
||||
"profile_photo_protect",
|
||||
"call_protection",
|
||||
"match_alert_preference",
|
||||
"who_can_message",
|
||||
"who_can_message_categories",
|
||||
"user_status",
|
||||
];
|
||||
if (hiddenFields.includes(key) || key.endsWith('_id') || key.endsWith('Id') || key.endsWith('_ids') || key.endsWith('Ids')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -304,14 +359,21 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
color: #d32f2f;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
overflow: 'hidden';
|
||||
padding:5px;
|
||||
display: 'flex';
|
||||
align-items: 'center';
|
||||
justify-content: 'center';
|
||||
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-size: 8px;
|
||||
font-weight: bold;
|
||||
width:20px;
|
||||
|
||||
}
|
||||
.custom-swiper-${key} .swiper-pagination-bullet-active {
|
||||
background-color: #d32f2f;
|
||||
@ -329,15 +391,16 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
>
|
||||
{value.map((sibling, idx) => (
|
||||
<SwiperSlide key={idx}>
|
||||
<Card variant="outlined" sx={{ bgcolor: '#fff', height: '100%' }}>
|
||||
<Card variant="outlined"
|
||||
sx={{ bgcolor: '#fff', height: '100%' }}>
|
||||
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
||||
{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') {
|
||||
if (sKey === 'haveChildrens' || sKey === 'have_childrens' || sKey === 'hasChildren' || sKey === 'has_children') {
|
||||
if (sVal === true || sVal === 1 || sVal === '1' || sVal === 'Yes') {
|
||||
displayValue = 'Yes';
|
||||
} else if (sVal === false || sVal === 0 || sVal === '0') {
|
||||
} else if (sVal === false || sVal === 0 || sVal === '0' || sVal === 'No') {
|
||||
displayValue = 'No';
|
||||
}
|
||||
}
|
||||
@ -375,7 +438,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
<Box
|
||||
key={key}
|
||||
py={0.7}
|
||||
borderBottom="1px solid #e0e0e0"
|
||||
// borderBottom="1px solid #e0e0e0"
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: {
|
||||
@ -434,12 +497,34 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
color="success"
|
||||
|
||||
size="large"
|
||||
onClick={onSubmit}
|
||||
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>
|
||||
);
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Navigation, Autoplay } from "swiper/modules";
|
||||
import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react";
|
||||
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useDispatch } from "react-redux";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import LazyImage from "../components/common/LazyImage";
|
||||
@ -13,9 +13,8 @@ import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
|
||||
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
||||
|
||||
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
|
||||
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||
import ProfileCompletion from "../components/profiledashboard/ProfileCompletion";
|
||||
import { preloadDummyProfile } from "../redux/registrationFormSlice";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -25,8 +24,12 @@ import {
|
||||
Divider,
|
||||
IconButton,
|
||||
Typography,
|
||||
Grid,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { Info } from "@mui/icons-material";
|
||||
import { usePreviewDetails } from "../hooks/usePreview";
|
||||
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||
|
||||
const images = [
|
||||
weddingpromo1, // bride in saree
|
||||
@ -42,20 +45,95 @@ const images = [
|
||||
];
|
||||
|
||||
const ProfilePreviewPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
personalDetails,
|
||||
educationalDetails,
|
||||
familyDetails,
|
||||
lifestyleDetails,
|
||||
partnerPreferences,
|
||||
} = useSelector((state) => state.registerform);
|
||||
const dispatch = useDispatch();
|
||||
const { data, isLoading } = usePreviewDetails();
|
||||
|
||||
// For dummy data on first visit
|
||||
useEffect(() => {
|
||||
dispatch(preloadDummyProfile());
|
||||
}, [dispatch]);
|
||||
if (data?.personal_details) {
|
||||
const pd = data.personal_details;
|
||||
const images = pd.images || pd.profile_images;
|
||||
if (images && images.length > 0) {
|
||||
const formattedImages = images.map((img) =>
|
||||
typeof img === "string" ? { url: img, preview: img } : img
|
||||
);
|
||||
dispatch(updatePersonalDetails({ profiles: formattedImages }));
|
||||
}
|
||||
}
|
||||
}, [data, dispatch]);
|
||||
|
||||
const personalDetails = data?.personal_details
|
||||
? {
|
||||
name: data.personal_details.name,
|
||||
mobileNumber: data.personal_details.mobile,
|
||||
gender: data.personal_details.gender,
|
||||
dob: data.personal_details.dob
|
||||
? data.personal_details.dob.split("T")[0]
|
||||
: "",
|
||||
height: data.personal_details.height,
|
||||
weight: data.personal_details.weight,
|
||||
maritalStatus: data.personal_details.marital_status,
|
||||
religion: data.personal_details.religion,
|
||||
caste: data.personal_details.caste,
|
||||
email: data.personal_details.email,
|
||||
state: data.personal_details.state,
|
||||
city: data.personal_details.district,
|
||||
pincode: data.personal_details.pincode,
|
||||
profiles:
|
||||
data.personal_details.images?.map((url) => ({ preview: url })) || [],
|
||||
}
|
||||
: {};
|
||||
|
||||
const educationalDetails = data?.educational_details
|
||||
? {
|
||||
qualification: data.educational_details.education,
|
||||
fieldOfStudy: data.educational_details.study_field,
|
||||
collegeName: data.educational_details.college_name,
|
||||
occupation: data.educational_details.occupation,
|
||||
organization: data.educational_details.company_name,
|
||||
employeeType: data.educational_details.employee_type,
|
||||
income: data.educational_details.annual_income,
|
||||
workLocation: data.educational_details.work_location,
|
||||
}
|
||||
: {};
|
||||
|
||||
const familyDetails = data?.family_details
|
||||
? {
|
||||
fatherName: data.family_details.father_name,
|
||||
fatherOccupation: data.family_details.father_occupation,
|
||||
motherName: data.family_details.mother_name,
|
||||
motherOccupation: data.family_details.mother_occupation,
|
||||
brotherCount: data.family_details.brother_count,
|
||||
sisterCount: data.family_details.sister_count,
|
||||
familyStatus: data.family_details.family_status,
|
||||
nativePlace: data.family_details.native_place,
|
||||
}
|
||||
: {};
|
||||
|
||||
const lifestyleDetails = data?.lifestyle_details
|
||||
? {
|
||||
diets: data.lifestyle_details.diet,
|
||||
hobbies: data.lifestyle_details.hobbies,
|
||||
dob: data.lifestyle_details.date_of_birth_formated,
|
||||
tob: data.lifestyle_details.time_of_birth_formated,
|
||||
placeOfBirth: data.lifestyle_details.place_of_birth,
|
||||
horoscope: data.lifestyle_details.horoscope,
|
||||
}
|
||||
: {};
|
||||
|
||||
const partnerPreferences = data?.partner_preferences
|
||||
? {
|
||||
ageRange: data.partner_preferences.preferred_age_range,
|
||||
castes: data.partner_preferences.preferred_castes,
|
||||
subCastes: data.partner_preferences.preferred_sub_castes,
|
||||
occupations: data.partner_preferences.preferred_occupations,
|
||||
educations: data.partner_preferences.preferred_educations,
|
||||
hobbies: data.partner_preferences.preferred_hobbies,
|
||||
annualIncome: data.partner_preferences.preferred_annual_income,
|
||||
states: data.partner_preferences.preferred_states,
|
||||
districts: data.partner_preferences.preferred_districts,
|
||||
}
|
||||
: {};
|
||||
|
||||
const handleEditSection = (stepNum) => {
|
||||
navigate("/profile-edit", { state: { step: stepNum } });
|
||||
@ -64,7 +142,7 @@ const ProfilePreviewPage = () => {
|
||||
const renderField = (label, value, stepNum = 1) => (
|
||||
<Box
|
||||
py={0.7}
|
||||
borderBottom="1px solid #e0e0e0"
|
||||
// borderBottom="1px solid #e0e0e0"
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: {
|
||||
@ -102,8 +180,119 @@ const ProfilePreviewPage = () => {
|
||||
</Box>
|
||||
);
|
||||
|
||||
const renderChartGrid = (getDataForCell, title) => {
|
||||
const renderCell = (i) => {
|
||||
const items = getDataForCell(i);
|
||||
const label = Array.isArray(items) ? items.join(", ") : "";
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={i}
|
||||
title={label}
|
||||
arrow
|
||||
placement="top"
|
||||
disableHoverListener={!items || items.length === 0}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #ccc",
|
||||
bgcolor: "#fff",
|
||||
height: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
textAlign: "center",
|
||||
fontSize: "0.65rem",
|
||||
fontWeight: "bold",
|
||||
overflow: "hidden",
|
||||
p: 0.5,
|
||||
wordBreak: "break-word",
|
||||
lineHeight: 1.1,
|
||||
cursor: items && items.length > 0 ? "help" : "default",
|
||||
}}
|
||||
>
|
||||
{items && items.length > 2 ? (
|
||||
<Box>
|
||||
{items.slice(0, 2).join(", ")}
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ color: "primary.main", display: "block", fontSize: "0.6rem" }}
|
||||
>
|
||||
+{items.length - 2}
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
align="center"
|
||||
gutterBottom
|
||||
sx={{ fontWeight: 600 }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(4, 1fr)",
|
||||
gap: 0.5,
|
||||
maxWidth: "300px",
|
||||
mx: "auto",
|
||||
p: 1,
|
||||
bgcolor: "#fff3e0",
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
|
||||
{renderCell(5)}
|
||||
<Box
|
||||
sx={{
|
||||
gridColumn: "span 2",
|
||||
gridRow: "span 2",
|
||||
bgcolor: "#fff",
|
||||
border: "1px solid #eee",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "0.7rem",
|
||||
fontWeight: "bold",
|
||||
color: "#aaa",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={horoscopeImg}
|
||||
alt={title}
|
||||
sx={{
|
||||
width: "70%",
|
||||
height: "70%",
|
||||
objectFit: "cover",
|
||||
animation: "spin 20s linear infinite",
|
||||
"@keyframes spin": {
|
||||
"0%": { transform: "rotate(0deg)" },
|
||||
"100%": { transform: "rotate(360deg)" },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{renderCell(6)}
|
||||
{renderCell(7)} {renderCell(8)}
|
||||
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPersonalSection = () => (
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
@ -120,9 +309,9 @@ const ProfilePreviewPage = () => {
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||
/>
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Name", personalDetails.name, 1)}
|
||||
@ -142,7 +331,7 @@ const ProfilePreviewPage = () => {
|
||||
{/* Profile Images */}
|
||||
<Box
|
||||
py={0.7}
|
||||
borderBottom="1px solid #e0e0e0"
|
||||
// borderBottom="1px solid #e0e0e0"
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
||||
@ -210,7 +399,7 @@ const ProfilePreviewPage = () => {
|
||||
);
|
||||
|
||||
const renderEducationalSection = () => (
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
@ -227,9 +416,9 @@ const ProfilePreviewPage = () => {
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||
/>
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Qualification", educationalDetails.qualification, 2)}
|
||||
@ -245,7 +434,7 @@ const ProfilePreviewPage = () => {
|
||||
);
|
||||
|
||||
const renderFamilySection = () => (
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
@ -256,15 +445,15 @@ const ProfilePreviewPage = () => {
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
color="primary"
|
||||
onClick={() => handleEditSection(2)}
|
||||
onClick={() => handleEditSection(3)}
|
||||
size="large"
|
||||
>
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||
/>
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Father Name", familyDetails.fatherName)}
|
||||
@ -280,7 +469,7 @@ const ProfilePreviewPage = () => {
|
||||
);
|
||||
|
||||
const renderLifestyleSection = () => (
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
@ -291,15 +480,15 @@ const ProfilePreviewPage = () => {
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
color="primary"
|
||||
onClick={() => handleEditSection(2)}
|
||||
onClick={() => handleEditSection(4)}
|
||||
size="large"
|
||||
>
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||
/>
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField(
|
||||
@ -317,32 +506,61 @@ const ProfilePreviewPage = () => {
|
||||
{renderField("Date of Birth", lifestyleDetails.dob)}
|
||||
{renderField("Time of Birth", lifestyleDetails.tob)}
|
||||
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
|
||||
{lifestyleDetails.horoscope && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
mt: 2,
|
||||
mb: 2,
|
||||
borderTop: "1px solid #e0e0e0",
|
||||
pt: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||
Horoscope Details
|
||||
</Typography>
|
||||
<Grid container spacing={4} justifyContent="center">
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderChartGrid((i) => {
|
||||
const val = lifestyleDetails.horoscope[`graha_${i}`];
|
||||
return val ? val.split(",") : [];
|
||||
}, "Rasi")}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderChartGrid((i) => {
|
||||
const val = lifestyleDetails.horoscope[`amsam_${i}`];
|
||||
return val ? val.split(",") : [];
|
||||
}, "Navamsam")}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
|
||||
const renderPreferenceSection = () => (
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}>
|
||||
<Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
Lifestyle Details
|
||||
Partner Preferences
|
||||
</Typography>
|
||||
}
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
color="primary"
|
||||
onClick={() => handleEditSection(2)}
|
||||
onClick={() => handleEditSection(5)}
|
||||
size="large"
|
||||
>
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
sx={{ padding: "15px 15px", background: "#f2f2f2" }}
|
||||
/>
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Age Range", partnerPreferences.ageRange)}
|
||||
@ -394,6 +612,14 @@ const ProfilePreviewPage = () => {
|
||||
</Card>
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="50vh">
|
||||
<Typography>Loading Profile...</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="custom-swiper-hero flex items-center justify-center p-4">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
14
src/hooks/useDashboardQuery.js
Normal file
14
src/hooks/useDashboardQuery.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getDashboardDetails } from "../api/dashboard.api";
|
||||
|
||||
export const useDashboardQuery = () => {
|
||||
return useQuery({
|
||||
queryKey: ["dashboardDetails"],
|
||||
queryFn: getDashboardDetails,
|
||||
// Performance settings:
|
||||
staleTime: 1000 * 60 * 5, // Data is considered fresh for 5 minutes (reduces API calls)
|
||||
gcTime: 1000 * 60 * 10, // Unused data is garbage collected after 10 minutes
|
||||
refetchOnWindowFocus: false, // Prevents refetching when user switches tabs
|
||||
retry: 1, // Retry failed requests once
|
||||
});
|
||||
};
|
||||
14
src/hooks/useDebounce.js
Normal file
14
src/hooks/useDebounce.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
export function useDebounce(value, delay=500) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
useEffect(()=>{
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
},delay);
|
||||
return () => clearTimeout(handler);
|
||||
},[value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
};
|
||||
19
src/hooks/useDebounce.jsx
Normal file
19
src/hooks/useDebounce.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useDebounce = (value, delay) => {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
};
|
||||
|
||||
export default useDebounce;
|
||||
67
src/hooks/useProfiles.js
Normal file
67
src/hooks/useProfiles.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getProfilesFilterList,
|
||||
getProfilesFilterMasters,
|
||||
} from "../api/masters.api";
|
||||
|
||||
export const useProfiles = (filters = {}) => {
|
||||
|
||||
// Remove empty filters
|
||||
const cleanFilters = Object.entries(filters).reduce((acc, [key, value]) => {
|
||||
if (
|
||||
value !== "" &&
|
||||
value !== null &&
|
||||
value !== undefined &&
|
||||
!(Array.isArray(value) && value.length === 0)
|
||||
) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["profiles-filter-list", cleanFilters],
|
||||
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
getProfilesFilterList({
|
||||
...cleanFilters,
|
||||
page: pageParam,
|
||||
}),
|
||||
|
||||
staleTime: 1000 * 60 * 2,
|
||||
refetchOnWindowFocus: false,
|
||||
|
||||
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
|
||||
const currentPageData =
|
||||
lastPage?.data?.data || lastPage?.data || [];
|
||||
|
||||
const totalLoaded = allPages.reduce((acc, page) => {
|
||||
const pageData = page?.data?.data || page?.data || [];
|
||||
return acc + pageData.length;
|
||||
}, 0);
|
||||
|
||||
const totalRecords =
|
||||
lastPage?.data?.recordsFiltered ||
|
||||
lastPage?.recordsFiltered ||
|
||||
0;
|
||||
|
||||
console.log("totalLoaded:", totalLoaded);
|
||||
console.log("totalRecords:", totalRecords);
|
||||
|
||||
if (totalLoaded < totalRecords) {
|
||||
return allPages.length + 1;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
export const useProfilesFilterMasters = () =>
|
||||
useQuery({
|
||||
queryKey: ["profiles-filter-masters"],
|
||||
queryFn: getProfilesFilterMasters,
|
||||
});
|
||||
168
src/hooks/useWebSocket.js
Normal file
168
src/hooks/useWebSocket.js
Normal file
@ -0,0 +1,168 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const WS_URL = "wss://www.thirukalyanam.amrithaa.net/backend/reverb/app/xk30gjh2ggmel5szmm5w?protocol=7&client=js&version=1.0";
|
||||
|
||||
// SINGLETON state to share across all components
|
||||
let globalSocket = null;
|
||||
let globalMessages = [];
|
||||
let globalIsConnected = false;
|
||||
let globalActiveChannels = new Set();
|
||||
let subscribers = new Set();
|
||||
let reconnectTimer = null;
|
||||
let heartbeatTimer = null;
|
||||
|
||||
const notifySubscribers = () => {
|
||||
subscribers.forEach(callback => callback({
|
||||
messages: [...globalMessages],
|
||||
isConnected: globalIsConnected
|
||||
}));
|
||||
};
|
||||
|
||||
const connect = () => {
|
||||
if (globalSocket?.readyState === WebSocket.OPEN) return;
|
||||
if (globalSocket?.readyState === WebSocket.CONNECTING) return;
|
||||
|
||||
console.log("[WS] Connecting to:", WS_URL);
|
||||
const socket = new WebSocket(WS_URL);
|
||||
globalSocket = socket;
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log("[WS] Open - Waiting for handshake...");
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("[WS] RAW:", data);
|
||||
|
||||
// 1. Handshake
|
||||
if (data.event === "pusher:connection_established") {
|
||||
console.log("[WS] Handshake Complete | Socket ID:", data.data ? JSON.parse(data.data).socket_id : "N/A");
|
||||
globalIsConnected = true;
|
||||
|
||||
// Auto-subscribe to all pending channels
|
||||
if (globalActiveChannels.size > 0) {
|
||||
console.log(`[WS] Re-subscribing to ${globalActiveChannels.size} channels...`);
|
||||
globalActiveChannels.forEach(channel => {
|
||||
socket.send(JSON.stringify({
|
||||
event: "pusher:subscribe",
|
||||
data: { channel }
|
||||
}));
|
||||
console.log(`[WS] Subscribing to: ${channel}`);
|
||||
});
|
||||
}
|
||||
|
||||
notifySubscribers();
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Heartbeat
|
||||
if (data.event === "pusher:ping") {
|
||||
socket.send(JSON.stringify({ event: "pusher:pong", data: {} }));
|
||||
return;
|
||||
}
|
||||
if (data.event === "pusher:pong") return;
|
||||
|
||||
// 3. Subscription Succeeded
|
||||
if (data.event === "pusher_internal:subscription_succeeded") {
|
||||
console.log(`[WS] ✅ Subscribed to ${data.channel}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Subscription Error
|
||||
if (data.event === "pusher:subscription_error") {
|
||||
console.error(`[WS] ❌ Subscription failed for ${data.channel}`, data.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Regular Events
|
||||
console.log(`[WS] 📩 Event: ${data.event} | Channel: ${data.channel}`);
|
||||
globalMessages = [...globalMessages, data];
|
||||
notifySubscribers();
|
||||
} catch (err) {
|
||||
console.error("[WS] Parse Error:", err, event.data);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = (event) => {
|
||||
console.log(`[WS] Disconnected (Code: ${event.code}) - Reconnecting in 5s...`);
|
||||
globalIsConnected = false;
|
||||
notifySubscribers();
|
||||
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||
reconnectTimer = setTimeout(connect, 5000);
|
||||
};
|
||||
|
||||
socket.onerror = (err) => {
|
||||
console.error("[WS] Error:", err);
|
||||
socket.close();
|
||||
};
|
||||
|
||||
// Heartbeat
|
||||
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
||||
heartbeatTimer = setInterval(() => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON.stringify({ event: "pusher:ping", data: {} }));
|
||||
}
|
||||
}, 20000);
|
||||
};
|
||||
|
||||
export const useWebSocket = (channels = []) => {
|
||||
const [state, setState] = useState({
|
||||
messages: globalMessages,
|
||||
isConnected: globalIsConnected
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Add this component's listener
|
||||
const callback = (newState) => setState(newState);
|
||||
subscribers.add(callback);
|
||||
|
||||
// Initialize connection if needed
|
||||
if (!globalSocket) {
|
||||
connect();
|
||||
}
|
||||
|
||||
return () => {
|
||||
subscribers.delete(callback);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle Dynamic Subscriptions
|
||||
useEffect(() => {
|
||||
if (!channels || channels.length === 0) return;
|
||||
|
||||
channels.forEach(channel => {
|
||||
if (channel && !globalActiveChannels.has(channel)) {
|
||||
globalActiveChannels.add(channel);
|
||||
|
||||
if (globalIsConnected && globalSocket?.readyState === WebSocket.OPEN) {
|
||||
globalSocket.send(JSON.stringify({
|
||||
event: "pusher:subscribe",
|
||||
data: { channel }
|
||||
}));
|
||||
console.log(`[WS] Dynamic Subscribe: ${channel}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Optional: Cleanup old channels if they are no longer in the provided array
|
||||
// But for a chat app, we often want to keep listening to notification channels.
|
||||
// So we'll leave them for now unless we implement a more complex cleanup.
|
||||
}, [channels]);
|
||||
|
||||
const unsubscribe = (channel) => {
|
||||
if (channel && globalActiveChannels.has(channel)) {
|
||||
globalActiveChannels.delete(channel);
|
||||
if (globalIsConnected && globalSocket?.readyState === WebSocket.OPEN) {
|
||||
globalSocket.send(JSON.stringify({
|
||||
event: "pusher:unsubscribe",
|
||||
data: { channel }
|
||||
}));
|
||||
console.log(`[WS] Unsubscribed from: ${channel}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { ...state, unsubscribe };
|
||||
};
|
||||
17
src/main.jsx
17
src/main.jsx
@ -7,8 +7,9 @@ import { ThemeProvider } from "@mui/material/styles";
|
||||
import theme from "./theme";
|
||||
|
||||
import { 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 { PersistGate } from "redux-persist/integration/react";
|
||||
// Disable noisy logs in production while keeping warnings/errors.
|
||||
if (import.meta.env.PROD) {
|
||||
console.log = () => {};
|
||||
@ -29,12 +30,14 @@ const queryClient = new QueryClient({
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
|
||||
<ThemeProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
</ThemeProvider>
|
||||
|
||||
);
|
||||
|
||||
@ -1,42 +1,57 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Tabs, Tab, Box, Chip } from '@mui/material';
|
||||
import { CheckCircle, Phone, ExpandMore } from '@mui/icons-material';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Tabs, Tab, Box, CircularProgress } from '@mui/material';
|
||||
import { CheckCircle, Phone } from '@mui/icons-material';
|
||||
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="flex items-start gap-4">
|
||||
<div className="relative flex-shrink-0">
|
||||
<img
|
||||
src={profile.image}
|
||||
src={profile.photo || 'https://via.placeholder.com/150'}
|
||||
alt={profile.name}
|
||||
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" />
|
||||
</button>
|
||||
</button> */}
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
<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>
|
||||
<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">
|
||||
<p className="font-medium">{profile.age} yrs, {profile.height}, {profile.language},</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.age ? `${profile.age} yrs` : ''}{profile.age && profile.height ? ', ' : ''}{profile.height || ''}</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 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>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
@ -49,7 +64,7 @@ const ReportedProfile = ({ profile, onViewReason }) => {
|
||||
<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'>
|
||||
<img
|
||||
src={profile.image}
|
||||
src={profile.photo || 'https://via.placeholder.com/150'}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover "
|
||||
/>
|
||||
@ -57,25 +72,39 @@ const ReportedProfile = ({ profile, onViewReason }) => {
|
||||
<div className="flex-1 w-full">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-1">{profile.name}</h3>
|
||||
<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>
|
||||
|
||||
<div className="space-y-1.5 text-sm">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-600">Profile created by Parent</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-600">{profile.age} yrs</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-600">{profile.caste}</span>
|
||||
{profile.age && (
|
||||
<>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-600">{profile.age} yrs</span>
|
||||
</>
|
||||
)}
|
||||
</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">
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-600">{profile.occupation}</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span className="text-gray-600">{profile.location}</span>
|
||||
{profile.occupation && (
|
||||
<>
|
||||
<span className="text-gray-400">•</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>
|
||||
|
||||
@ -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="flex items-start gap-4 mb-4">
|
||||
<img
|
||||
src={profile.image}
|
||||
src={profile.photo || 'https://via.placeholder.com/150'}
|
||||
alt={profile.name}
|
||||
className="w-16 h-20 rounded-lg object-cover flex-shrink-0"
|
||||
/>
|
||||
<div>
|
||||
<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 className="border-t pt-4">
|
||||
<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">
|
||||
{profile.reportReason}
|
||||
{profile.reason}
|
||||
</p>
|
||||
</div>
|
||||
<div className='w-full flex justify-center'>
|
||||
@ -134,117 +163,63 @@ const ReportReasonModal = ({ profile, onClose }) => {
|
||||
|
||||
function BlockedProfileListPage() {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [selectedReport, setSelectedReport] = useState(null);
|
||||
const blockedProfiles = [
|
||||
{
|
||||
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'
|
||||
},
|
||||
{
|
||||
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 [selectedReport, setSelectedReport] = useState(null);
|
||||
const [blockedProfiles, setBlockedProfiles] = useState([]);
|
||||
const [reportedProfiles, setReportedProfiles] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const reportedProfiles = [
|
||||
{
|
||||
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.'
|
||||
},
|
||||
{
|
||||
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.'
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [blockedRes, reportedRes] = await Promise.all([
|
||||
getBlockedProfiles(),
|
||||
getReportedProfiles()
|
||||
]);
|
||||
|
||||
if (blockedRes.status === "success") {
|
||||
setBlockedProfiles(blockedRes.data);
|
||||
}
|
||||
if (reportedRes.status === "success") {
|
||||
setReportedProfiles(reportedRes.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
toast.error("Failed to load profiles");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
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) => {
|
||||
setActiveTab(newValue);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '60vh' }}>
|
||||
<CircularProgress color="error" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" py-4 md:py-8">
|
||||
<div className="max-w-[1400px] mx-auto">
|
||||
@ -259,14 +234,14 @@ function BlockedProfileListPage() {
|
||||
textTransform: 'none',
|
||||
fontSize: '1rem',
|
||||
fontWeight: 600,
|
||||
minWidth: 120,
|
||||
minWidth: 150,
|
||||
},
|
||||
'& .Mui-selected': {
|
||||
color: '#fff !important',
|
||||
background:"#A70710"
|
||||
},
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: '#A70710',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
>
|
||||
@ -277,28 +252,32 @@ function BlockedProfileListPage() {
|
||||
|
||||
<div className="transition-all duration-300">
|
||||
{activeTab === 0 && (
|
||||
<div className='w-[100%] max-w-[1400px] mx-auto grid grid-cols-1 md:grid-cols-2 gap-2'>
|
||||
{blockedProfiles.map((profile, index) => (
|
||||
<BlockedProfile key={index} profile={profile} />
|
||||
))}
|
||||
<div className='w-[100%] max-w-[1400px] mx-auto grid grid-cols-1 md:grid-cols-2 gap-4 px-4'>
|
||||
{blockedProfiles.length > 0 ? (
|
||||
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>
|
||||
)}
|
||||
|
||||
{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'>
|
||||
{reportedProfiles.map((profile, index) => (
|
||||
<ReportedProfile
|
||||
key={index}
|
||||
profile={profile}
|
||||
onViewReason={setSelectedReport}
|
||||
/>
|
||||
))}
|
||||
<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.length > 0 ? (
|
||||
reportedProfiles.map((profile, index) => (
|
||||
<ReportedProfile
|
||||
key={profile.id || index}
|
||||
profile={profile}
|
||||
onViewReason={setSelectedReport}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-full text-center py-10 text-gray-500">No reported profiles found.</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
)}
|
||||
|
||||
|
||||
</div>
|
||||
{/* Report Reason Modal */}
|
||||
<ReportReasonModal
|
||||
@ -307,7 +286,7 @@ function BlockedProfileListPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
<style>{`
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { getContactUs } from '../api/contact.api';
|
||||
import LazyImage from '../components/common/LazyImage';
|
||||
import InstagramIcon from '@mui/icons-material/Instagram';
|
||||
import WhatsAppIcon from '@mui/icons-material/WhatsApp';
|
||||
import FacebookIcon from '@mui/icons-material/Facebook';
|
||||
import SvgIcon from '@mui/material/SvgIcon';
|
||||
import { Phone, Mail, ChevronRight } from 'lucide-react';
|
||||
@ -39,7 +40,13 @@ const ContactUsPage = () => {
|
||||
icon: <XIcon fontSize="large" />,
|
||||
color: "from-gray-800 to-black",
|
||||
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) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,58 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams } from "react-router-dom";
|
||||
import MatrimonyProfile from "../components/profiledetail/MatrimonyProfile"
|
||||
import PartnerPreferences from "../components/profiledetail/PartnerPreferences"
|
||||
import MatchingList from "../components/profiledashboard/MatchingList";
|
||||
import { getProfileDetail } from "../services/profileActionApi";
|
||||
import { CircularProgress, Box } from "@mui/material";
|
||||
|
||||
const ProfileDetailPage = () => {
|
||||
const { id } = useParams();
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDetail = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getProfileDetail(id);
|
||||
setData(res);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch profile details:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (id) {
|
||||
fetchDetail();
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
|
||||
<CircularProgress color="error" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="text-center py-20 text-gray-500 text-xl">
|
||||
Profile details not found.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-[100%] max-w-[1400px] mx-auto my-10">
|
||||
<MatrimonyProfile/>
|
||||
<PartnerPreferences/>
|
||||
<MatchingList/>
|
||||
<MatrimonyProfile data={data} />
|
||||
<PartnerPreferences data={data} />
|
||||
<MatchingList matches={data.all_matches} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileDetailPage
|
||||
export default ProfileDetailPage;
|
||||
@ -11,7 +11,8 @@ import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
|
||||
import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
|
||||
|
||||
import 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 MatrimonyArticles = lazy(() => import("../components/profiledashboard/MatrimonyArticles"));
|
||||
const MatchingList = lazy(() => import("../components/profiledashboard/MatchingList"));
|
||||
@ -48,6 +49,15 @@ const SectionFallback = ({ height = 280 }) => (
|
||||
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="custom-swiper-hero flex items-center justify-center p-4">
|
||||
@ -92,11 +102,11 @@ const UserDashboardHome = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{images.map((img, idx) => (
|
||||
<SwiperSlide key={idx}>
|
||||
{sliderImages.map((slide, idx) => (
|
||||
<SwiperSlide key={slide.id || idx}>
|
||||
<div className="relative overflow-hidden rounded-3xl w-[100%]">
|
||||
<img
|
||||
src={img}
|
||||
src={slide.image}
|
||||
alt={`Slide ${idx + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
loading={idx === 0 ? "eager" : "lazy"}
|
||||
@ -206,28 +216,32 @@ const UserDashboardHome = () => {
|
||||
`}</style>
|
||||
</div>
|
||||
<Suspense fallback={<SectionFallback height={320} />}>
|
||||
<Profilecardemo />
|
||||
<Profilecardemo profiles={dashboardData?.daily_recommended} />
|
||||
</Suspense>
|
||||
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
<Suspense fallback={<SectionFallback height={220} />}>
|
||||
<ProfileCompletion />
|
||||
<ProfileCompletion
|
||||
percentage={dashboardData?.profile_complete_percentage}
|
||||
missingDetails={dashboardData?.non_filled_sections}
|
||||
becomePaidMember={dashboardData?.become_paid_member}
|
||||
/>
|
||||
</Suspense>
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
<Suspense fallback={<SectionFallback height={280} />}>
|
||||
<MatrimonyArticles />
|
||||
<MatrimonyArticles articles={dashboardData?.blogs} />
|
||||
</Suspense>
|
||||
<Suspense fallback={<SectionFallback height={320} />}>
|
||||
<MatchingList />
|
||||
<MatchingList matches={dashboardData?.all_matches} />
|
||||
</Suspense>
|
||||
<Suspense fallback={<SectionFallback height={320} />}>
|
||||
<NewJoinedProfile profiles={dashboardData?.new_joined} />
|
||||
</Suspense>
|
||||
{/* <PaidMemberCard/> */}
|
||||
|
||||
{/* <NewJoinedProfile/> */}
|
||||
{/* <CustomerSupportCard/> */}
|
||||
<Suspense fallback={<SectionFallback height={240} />}>
|
||||
<VideoSwiperGallery />
|
||||
<VideoSwiperGallery videos={dashboardData?.youtube_videos} />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -74,6 +74,13 @@ const LoginPage = () => {
|
||||
localStorage.setItem("access_token", 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!");
|
||||
navigate("/dashboard-home");
|
||||
} else {
|
||||
|
||||
@ -1,29 +1,27 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
age: [18, 70],
|
||||
height: [4.0, 7.11],
|
||||
maritalStatus: "All Profile",
|
||||
motherTongue: [],
|
||||
religion: "Hindu",
|
||||
matchesWithHoroscope: false,
|
||||
from_age: 18,
|
||||
to_age: 70,
|
||||
from_height: 4.0,
|
||||
to_height: 7.11,
|
||||
marital_status: [],
|
||||
religion: [],
|
||||
caste: [],
|
||||
subCaste: [],
|
||||
star: "Any",
|
||||
dasham: "Doesn't matter",
|
||||
occupation: "Any",
|
||||
annualIncome: "Any",
|
||||
employeeType: "Any",
|
||||
education: "Any",
|
||||
state: "Any",
|
||||
country: "Any",
|
||||
citizenship: "Any",
|
||||
eatingHabits: "Any",
|
||||
smokingHabits: "Doesn't matter",
|
||||
drinkingHabits: "Doesn't matter",
|
||||
familyType: "Any",
|
||||
familyStatus: "Any",
|
||||
familyValue: "Any",
|
||||
sub_caste: [],
|
||||
star: [],
|
||||
occupation: [],
|
||||
annual_income: [],
|
||||
employee_type: [],
|
||||
education: [],
|
||||
state: [],
|
||||
district: [],
|
||||
diet: "",
|
||||
family_type: [],
|
||||
filter_type: "all_matches",
|
||||
page: 1,
|
||||
isPaidMember: false,
|
||||
search: "",
|
||||
};
|
||||
|
||||
const filterSlice = createSlice({
|
||||
@ -31,73 +29,24 @@ const filterSlice = createSlice({
|
||||
initialState,
|
||||
reducers: {
|
||||
setAge: (state, action) => {
|
||||
state.age = action.payload;
|
||||
state.from_age = action.payload[0];
|
||||
state.to_age = action.payload[1];
|
||||
},
|
||||
setHeight: (state, action) => {
|
||||
state.height = action.payload;
|
||||
state.from_height = action.payload[0];
|
||||
state.to_height = action.payload[1];
|
||||
},
|
||||
setMaritalStatus: (state, action) => {
|
||||
state.maritalStatus = action.payload;
|
||||
},
|
||||
setMotherTongue: (state, action) => {
|
||||
state.motherTongue = action.payload;
|
||||
state.marital_status = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||
},
|
||||
setReligion: (state, action) => {
|
||||
state.religion = action.payload;
|
||||
},
|
||||
setMatchesWithHoroscope: (state, action) => {
|
||||
state.matchesWithHoroscope = action.payload;
|
||||
state.religion = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||
},
|
||||
setCaste: (state, action) => {
|
||||
state.caste = action.payload;
|
||||
},
|
||||
setSubCaste: (state, action) => {
|
||||
state.subCaste = action.payload;
|
||||
},
|
||||
setStar: (state, action) => {
|
||||
state.star = action.payload;
|
||||
},
|
||||
setDasham: (state, action) => {
|
||||
state.dasham = action.payload;
|
||||
},
|
||||
setOccupation: (state, action) => {
|
||||
state.occupation = action.payload;
|
||||
},
|
||||
setAnnualIncome: (state, action) => {
|
||||
state.annualIncome = action.payload;
|
||||
},
|
||||
setEmployeeType: (state, action) => {
|
||||
state.employeeType = action.payload;
|
||||
},
|
||||
setEducation: (state, action) => {
|
||||
state.education = action.payload;
|
||||
},
|
||||
setState: (state, action) => {
|
||||
state.state = action.payload;
|
||||
},
|
||||
setCountry: (state, action) => {
|
||||
state.country = action.payload;
|
||||
},
|
||||
setCitizenship: (state, action) => {
|
||||
state.citizenship = action.payload;
|
||||
},
|
||||
setEatingHabits: (state, action) => {
|
||||
state.eatingHabits = action.payload;
|
||||
},
|
||||
setSmokingHabits: (state, action) => {
|
||||
state.smokingHabits = action.payload;
|
||||
},
|
||||
setDrinkingHabits: (state, action) => {
|
||||
state.drinkingHabits = action.payload;
|
||||
},
|
||||
setFamilyType: (state, action) => {
|
||||
state.familyType = action.payload;
|
||||
},
|
||||
setFamilyStatus: (state, action) => {
|
||||
state.familyStatus = action.payload;
|
||||
},
|
||||
setFamilyValue: (state, action) => {
|
||||
state.familyValue = action.payload;
|
||||
state.sub_caste = action.payload;
|
||||
},
|
||||
|
||||
// universal update
|
||||
@ -105,7 +54,16 @@ const filterSlice = createSlice({
|
||||
return { ...state, ...action.payload };
|
||||
},
|
||||
|
||||
resetFilters: () => initialState,
|
||||
resetFilters: (state) => {
|
||||
// Reset all filters but preserve the paid member status
|
||||
return { ...initialState, isPaidMember: state.isPaidMember };
|
||||
},
|
||||
setPage: (state, action) => {
|
||||
state.page = action.payload;
|
||||
},
|
||||
setPaidMemberStatus: (state, action) => {
|
||||
state.isPaidMember = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -113,28 +71,13 @@ export const {
|
||||
setAge,
|
||||
setHeight,
|
||||
setMaritalStatus,
|
||||
setMotherTongue,
|
||||
setReligion,
|
||||
setMatchesWithHoroscope,
|
||||
setCaste,
|
||||
setSubCaste,
|
||||
setStar,
|
||||
setDasham,
|
||||
setOccupation,
|
||||
setAnnualIncome,
|
||||
setEmployeeType,
|
||||
setEducation,
|
||||
setState,
|
||||
setCountry,
|
||||
setCitizenship,
|
||||
setEatingHabits,
|
||||
setSmokingHabits,
|
||||
setDrinkingHabits,
|
||||
setFamilyType,
|
||||
setFamilyStatus,
|
||||
setFamilyValue,
|
||||
updateFilter,
|
||||
resetFilters,
|
||||
setPage,
|
||||
setPaidMemberStatus,
|
||||
} = filterSlice.actions;
|
||||
|
||||
export default filterSlice.reducer;
|
||||
|
||||
@ -5,49 +5,72 @@ const registrationformSlice = createSlice({
|
||||
initialState: {
|
||||
personalDetails: {
|
||||
name: "",
|
||||
mobileNumber: "",
|
||||
mobile: "",
|
||||
email: "",
|
||||
gender: "",
|
||||
dob: "",
|
||||
height: "",
|
||||
weight: "",
|
||||
maritalStatus: "",
|
||||
religion: "",
|
||||
profileFor: "",
|
||||
caste: "",
|
||||
subCaste: "",
|
||||
marital_status: "",
|
||||
religion: 1, // Default Hindu
|
||||
profile_for: "",
|
||||
caste: 1, // Default Naidu
|
||||
sub_caste: "",
|
||||
willing_to_marry: "",
|
||||
inter_caste_parents: "",
|
||||
inter_caste_parents_details: "",
|
||||
gothram: "",
|
||||
raasi: "",
|
||||
star: "",
|
||||
bloodGroup: "",
|
||||
email: "",
|
||||
do_you_speak_telugu: "",
|
||||
about_us: "",
|
||||
known_languages: [],
|
||||
mother_language: "",
|
||||
complexion: "",
|
||||
physical_status: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
dob: "",
|
||||
raasi: "",
|
||||
star: "",
|
||||
state: "",
|
||||
city: "",
|
||||
pincode: "",
|
||||
profiles: [],
|
||||
verifiedMobileNumber: "",
|
||||
},
|
||||
educationalDetails: {
|
||||
collegeName: "",
|
||||
employeeType: "",
|
||||
qualification: "",
|
||||
fieldOfStudy: "",
|
||||
study_field: "",
|
||||
education: "",
|
||||
education_detail: "",
|
||||
college_name: "",
|
||||
employee_type: "",
|
||||
occupation: "",
|
||||
organization: "",
|
||||
income: "",
|
||||
workLocation: "",
|
||||
occupation_detail: "",
|
||||
company_name: "",
|
||||
income_currency: "INR",
|
||||
annual_income: "",
|
||||
work_country: 1,
|
||||
work_city: "",
|
||||
work_state: "",
|
||||
work_district: "",
|
||||
address: "",
|
||||
},
|
||||
familyDetails: {
|
||||
fatherName: "",
|
||||
fatherOccupation: "",
|
||||
motherName: "",
|
||||
motherOccupation: "",
|
||||
brotherCount: 0,
|
||||
sisterCount: 0,
|
||||
brotherCount: "",
|
||||
sisterCount: "",
|
||||
brothers: [],
|
||||
sisters: [],
|
||||
familyStatus: "",
|
||||
nativePlace: "",
|
||||
familyCountry: "",
|
||||
familyState: "",
|
||||
familyDistrict: "",
|
||||
familyCity: "",
|
||||
address: "",
|
||||
expectationDetails: "",
|
||||
willingToGoAbroad: "",
|
||||
},
|
||||
lifestyleDetails: {
|
||||
diets: [],
|
||||
@ -114,14 +137,38 @@ const registrationformSlice = createSlice({
|
||||
state.partnerPreferences = { ...state.partnerPreferences, ...action.payload };
|
||||
},
|
||||
|
||||
submitForm: (state) => {
|
||||
console.log("Form Submitted:", {
|
||||
personalDetails: state.personalDetails,
|
||||
educationalDetails: state.educationalDetails,
|
||||
familyDetails: state.familyDetails,
|
||||
lifestyleDetails: state.lifestyleDetails,
|
||||
partnerPreferences: state.partnerPreferences,
|
||||
});
|
||||
clearAllStepsFrom: (state, action) => {
|
||||
const step = action.payload;
|
||||
if (step <= 2) {
|
||||
state.educationalDetails = {
|
||||
study_field: "", education: "", education_detail: "", college_name: "",
|
||||
employee_type: "", occupation: "", occupation_detail: "", company_name: "",
|
||||
income_currency: "INR", annual_income: "", work_country: "", work_state: "",
|
||||
work_district: "", work_city: "", address: "",
|
||||
};
|
||||
}
|
||||
if (step <= 3) {
|
||||
state.familyDetails = {
|
||||
fatherName: "", fatherOccupation: "", motherName: "", motherOccupation: "",
|
||||
brotherCount: "", sisterCount: "", brothers: [], sisters: [],
|
||||
familyStatus: "", nativePlace: "", familyCountry: "", familyState: "",
|
||||
familyDistrict: "", familyCity: "", address: "", expectationDetails: "",
|
||||
willingToGoAbroad: "",
|
||||
};
|
||||
}
|
||||
if (step <= 4) {
|
||||
state.lifestyleDetails = {
|
||||
diets: "", hobbies: [], dob: "", tob: "", placeOfBirth: "",
|
||||
graha: { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [] },
|
||||
amsam: { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [] },
|
||||
};
|
||||
}
|
||||
if (step <= 5) {
|
||||
state.partnerPreferences = {
|
||||
ageRange: "", castes: [], subCastes: [], occupations: [], educations: [],
|
||||
hobbies: [], annualIncome: "", states: [], districts: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -279,6 +326,7 @@ export const {
|
||||
updateFamilyDetails,
|
||||
updateLifestyleDetails,
|
||||
updatePartnerPreferences,
|
||||
clearAllStepsFrom,
|
||||
submitForm,
|
||||
preloadDummyProfile,
|
||||
} = registrationformSlice.actions;
|
||||
|
||||
@ -1,9 +1,36 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import registerformReducer from "./registrationFormSlice";
|
||||
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({
|
||||
reducer: {
|
||||
registerform:registerformReducer,
|
||||
filters:filtersReducer,
|
||||
registerform: registerformReducer,
|
||||
filters: persistedFiltersReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: {
|
||||
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
|
||||
@ -3,18 +3,10 @@ import { Route, Routes } from 'react-router-dom';
|
||||
import UserRoutes from './UserRoutes';
|
||||
import PublicRoutes from './PublicRoutes';
|
||||
import ScrollToTop from '../components/common/ScrollToTop';
|
||||
import Skeleton from "../components/common/Skeleton";
|
||||
import { SkeletonPage } from "../components/common/Skeleton";
|
||||
|
||||
const RouteFallback = () => (
|
||||
<div className="min-h-[60vh] flex items-center justify-center px-6">
|
||||
<div className="w-full max-w-3xl space-y-4">
|
||||
<Skeleton height={32} rounded={12} />
|
||||
<Skeleton height={220} rounded={16} />
|
||||
<Skeleton height={16} />
|
||||
<Skeleton height={16} width="80%" />
|
||||
<Skeleton height={16} width="60%" />
|
||||
</div>
|
||||
</div>
|
||||
<SkeletonPage />
|
||||
);
|
||||
|
||||
const AppRoutes = () => {
|
||||
|
||||
@ -19,9 +19,10 @@ const PublicRoutes = () => {
|
||||
<>
|
||||
<Route element={<HomeLayout />}>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route element={<PublicGuard />}>
|
||||
<Route path="/registration" element={<StepperForm />} />
|
||||
</Route>
|
||||
{/* <Route element={<PublicGuard />}>
|
||||
</Route> */}
|
||||
<Route path="/registration" element={<StepperForm />} />
|
||||
|
||||
</Route>
|
||||
|
||||
<Route element={<PublicGuard />}>
|
||||
|
||||
34
src/services/chatApi.js
Normal file
34
src/services/chatApi.js
Normal file
@ -0,0 +1,34 @@
|
||||
import axiosInstance from "../api/axiosInstance";
|
||||
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||
|
||||
export const getChatList = async (searchValue = "") => {
|
||||
try {
|
||||
// Add timestamp to prevent caching
|
||||
const response = await axiosInstance.get(`${API_ENDPOINTS.CHAT_LIST}?search_value=${searchValue}&_t=${Date.now()}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching chat list:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getChatMessages = async (chatId, page = 1) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`${API_ENDPOINTS.CHAT_MESSAGES(chatId)}?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching chat messages:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const sendMessage = async (chatId, message) => {
|
||||
try {
|
||||
// Correct endpoint based on user request: chat/message/send?chat_id={id}&message={text}
|
||||
const response = await axiosInstance.post(`chat/message/send?chat_id=${chatId}&message=${encodeURIComponent(message)}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error sending message:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
65
src/services/profileActionApi.js
Normal file
65
src/services/profileActionApi.js
Normal file
@ -0,0 +1,65 @@
|
||||
import axiosInstance from "../api/axiosInstance";
|
||||
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||
|
||||
export const getBlockedProfiles = async () => {
|
||||
try {
|
||||
const response = await axiosInstance.get(API_ENDPOINTS.BLOCK_PROFILE_LIST);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching blocked profiles:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getReportedProfiles = async () => {
|
||||
try {
|
||||
const response = await axiosInstance.get(API_ENDPOINTS.REPORT_PROFILE_LIST);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching reported profiles:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const unblockProfile = async (profileId) => {
|
||||
try {
|
||||
const response = await axiosInstance.post(`unblock_profile?profile_id=${profileId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error unblocking profile:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getProfileDetail = async (profile_id) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`${API_ENDPOINTS.PROFILE_DETAIL}?profile_id=${profile_id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching profile detail:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getInterestList = async (tab, type) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`${API_ENDPOINTS.INTEREST_LIST}?tab=${tab}&type=${type}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching interest list:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateInterestStatus = async (profile_id, status) => {
|
||||
try {
|
||||
const response = await axiosInstance.post(API_ENDPOINTS.UPDATE_INTEREST_STATUS, {
|
||||
profile_id,
|
||||
status
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error updating interest status:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
28
src/services/shortlistapi.js
Normal file
28
src/services/shortlistapi.js
Normal file
@ -0,0 +1,28 @@
|
||||
import axiosInstance from "../api/axiosInstance";
|
||||
import { API_ENDPOINTS } from "../api/apiEndpoints";
|
||||
|
||||
export const shortlistProfile = async (profileId) => {
|
||||
const response = await axiosInstance.post(`${API_ENDPOINTS.SHORTLIST_API}?profile_id=${profileId}`);
|
||||
if (response.data?.status === "error") {
|
||||
throw new Error(response.data.message || "Failed to shortlist");
|
||||
}
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const sendInterest = async (profileId) => {
|
||||
const response = await axiosInstance.post(`interest_send`, {
|
||||
profile_id: profileId // ✅ sent in request body
|
||||
});
|
||||
if (response.data?.status === "error") {
|
||||
throw new Error(response.data.message || "Failed to send interest");
|
||||
}
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const declineProfile = async (profileId) => {
|
||||
const response = await axiosInstance.post(`decline?profile_id=${profileId}`);
|
||||
if (response.data?.status === "error") {
|
||||
throw new Error(response.data.message || "Failed to decline profile");
|
||||
}
|
||||
return response.data;
|
||||
};
|
||||
@ -53,7 +53,7 @@ class ErrorBoundary extends Component {
|
||||
</p>
|
||||
|
||||
{/* 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">
|
||||
<summary className="cursor-pointer font-medium">
|
||||
Error Details (Development)
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss(),],
|
||||
@ -11,3 +16,4 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user