setting page api integrated

This commit is contained in:
Meenadeveloper 2026-03-06 14:41:07 +05:30
parent 564b5ed50f
commit 65bd6c646b
9 changed files with 541 additions and 373 deletions

View File

@ -35,4 +35,23 @@ EDIT_FAMILY_DETAILS: "get_family_details",
EDIT_LIFESTYLE_DETAILS: "get_lifestyle_details",
EDIT_PREFERED_PARTNER_DETAILS: "get_preferred_details",
// 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",
};

View File

@ -20,3 +20,5 @@ export const logoutAPI = async () => {
const res = await axiosInstance.post(API_ENDPOINTS.LOGOUT);
return res.data;
};

7
src/api/contact.api.js Normal file
View File

@ -0,0 +1,7 @@
import axiosInstance from "./axiosInstance";
import { API_ENDPOINTS } from "./apiEndpoints";
export const getContactUs = async () => {
const res = await axiosInstance.get(API_ENDPOINTS.CONTACT_US);
return res.data;
};

7
src/api/safety.api.js Normal file
View File

@ -0,0 +1,7 @@
import axiosInstance from "./axiosInstance";
import { API_ENDPOINTS } from "./apiEndpoints";
export const getBeSafeOnline = async () => {
const res = await axiosInstance.get(API_ENDPOINTS.BE_SAFE_ONLINE);
return res.data;
};

View File

@ -27,6 +27,7 @@ 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";
const NAV_LINKS = [
// { label: "Home", path: "/" },
{ label: "Matches", path: "/matches" },
@ -58,7 +59,7 @@ const ACCOUNT_SETTINGS = [
{ label: "Logout", action: "logout" }
];
const SparkleNavbar = ({ items, color = "#0fec1eff", onItemClick, activeItem }) => {
const SparkleNavbar = ({ items, color = "#0fec1eff", onItemClick, activeItem, badges = {} }) => {
const [activeIndex, setActiveIndex] = useState(0);
const [indicatorStyle, setIndicatorStyle] = useState({});
const [isAnimating, setIsAnimating] = useState(false);
@ -125,9 +126,14 @@ const SparkleNavbar = ({ items, color = "#0fec1eff", onItemClick, activeItem })
onClick={() => handleClick(index)}
className={`cursor-pointer relative uppercase text-sm font-medium transition-all duration-300 pb-2 ${
activeIndex === index ? "text-black" : "text-gray-900 hover:text-gray-600"
}`}
} flex items-center gap-1`}
>
{item}
{badges[item] > 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">
{badges[item]}
</span>
)}
</button>
))}
@ -166,6 +172,16 @@ const ProfileHeader = () => {
const currentLabel = currentNav?.label ?? NAV_LINKS[0].label;
const { data: notificationData } = useQuery({
queryKey: ["notificationCount"],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.NOTIFICATION_COUNT);
return res.data;
},
enabled: !!auth,
});
const notificationCount = notificationData?.count || 0;
@ -182,11 +198,23 @@ const ProfileHeader = () => {
}
};
const handleDeleteAccount = () => {
// Add your delete account logic here
console.log("Account deleted");
const deleteAccountMutation = useMutation({
mutationFn: async () => {
return await axiosInstance.delete(API_ENDPOINTS.DELETE_ACCOUNT);
},
onSuccess: (response) => {
toast.success(response?.data?.message || "Account deleted successfully");
setDeleteModalOpen(false);
logoutAPI();
navigate("/");
},
onError: (error) => {
toast.error(error?.response?.data?.message || "Failed to delete account");
},
});
const handleDeleteAccount = () => {
deleteAccountMutation.mutate();
};
const handleLogout = () => {
@ -281,7 +309,14 @@ const ProfileHeader = () => {
<ListItemIcon>
{getNavIcon(index)}
</ListItemIcon>
<ListItemText primary={label} />
<ListItemText primary={
<div className="flex items-center justify-between">
{label}
{label === "Notifications" && notificationCount > 0 && (
<span className="bg-red-600 text-white text-xs px-2 py-0.5 rounded-full">{notificationCount}</span>
)}
</div>
} />
</ListItemButton>
</ListItem>
))}
@ -324,6 +359,7 @@ const ProfileHeader = () => {
items={NAV_LINKS.map(link => link.label)}
color="#034E08"
activeItem={currentLabel}
badges={{ "Notifications": notificationCount }}
onItemClick={(item) => {
setSelectedItem(item);
const link = NAV_LINKS.find(l => l.label === item);
@ -402,12 +438,13 @@ const ProfileHeader = () => {
<Button
variant="contained"
onClick={handleDeleteAccount}
disabled={deleteAccountMutation.isPending}
sx={{
bgcolor: '#f44336',
'&:hover': { bgcolor: '#d32f2f' }
}}
>
Delete
{deleteAccountMutation.isPending ? "Deleting..." : "Delete"}
</Button>
</Box>
</Box>

View File

@ -4,10 +4,14 @@ import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Switch from '@mui/material/Switch';
import { Switch, Select, MenuItem } from '@mui/material';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
import { Phone, MessageSquare, Shield, Bell, UserCheck, ChevronLeft } from 'lucide-react';
import { useQuery, useMutation } from '@tanstack/react-query';
import axiosInstance from '../api/axiosInstance';
import { API_ENDPOINTS } from '../api/apiEndpoints';
import toast from 'react-hot-toast';
function TabPanel(props) {
const { children, value, index, ...other } = props;
@ -68,8 +72,33 @@ const SettingItem = ({ title, description, enabled, onToggle }) => (
</Box>
);
const TabContent = ({ title, settings, states, onToggle }) => (
<Box sx={{borderRadius:"10px", flex: 1, bgcolor: '#ebf7ff', height: { xs: 'auto', md: '100vh' }, overflow: 'auto', width:"100%", maxWidth:"1000px", margin:"0 auto" }}>
const SelectSettingItem = ({ title, description, value, onChange, options, disabled }) => (
<Box sx={{ py: 2, borderBottom: '1px solid #e5e7eb', '&:last-child': { borderBottom: 'none' } }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body1" sx={{ fontWeight: 'bold', color: '#111827', fontSize: { xs: '0.875rem', sm: '1rem' } }}>
{title}
</Typography>
<Select
value={value}
onChange={onChange}
disabled={disabled}
sx={{ minWidth: 150, fontSize: { xs: '0.875rem', sm: '1rem' } }}
variant="standard"
>
{options.map(option => (
<MenuItem key={option} value={option}>{option}</MenuItem>
))}
</Select>
</Box>
<Typography variant="body2" sx={{ color: '#6b7280', lineHeight: 1.6, fontSize: { xs: '0.75rem', sm: '0.875rem' } }}>
{description}
</Typography>
</Box>
);
const TabContent = ({ title, settings, states, onToggle, onSelectChange }) => (
<Box sx={{borderRadius:"10px", flex: 1, bgcolor: '#ffff', height: { xs: 'auto', md: '100vh' }, overflow: 'auto', width:"100%", maxWidth:"1000px", margin:"0 auto" }}>
<Box sx={{
position: 'sticky',
top: 0,
@ -88,15 +117,28 @@ const TabContent = ({ title, settings, states, onToggle }) => (
</Typography>
</Box>
<Box sx={{ px: { xs: 2, sm: 3 }, py: 2 }}>
{settings.map((setting, index) => (
<SettingItem
{settings.map((setting, index) => {
if (setting.type === 'select') {
return (
<SelectSettingItem
key={index}
title={setting.title}
description={setting.description}
value={states[setting.key]}
onChange={(e) => onSelectChange(setting.key, e.target.value)}
options={setting.options}
disabled={!states.messagePermission}
/>
);
}
return (<SettingItem
key={index}
title={setting.title}
description={setting.description}
enabled={states[setting.key]}
onToggle={() => onToggle(setting.key)}
/>
))}
onToggle={() => onToggle(setting.key)} />
);
})}
</Box>
</Box>
);
@ -113,16 +155,238 @@ export default function AccountSettingPage() {
callProtection: true,
matchAlerts: true,
profileViews: false,
messagePermission: true,
blockUnverified: false,
messagePermission: false,
messageCategory: 'All Profile',
});
const handleChange = (event, newValue) => {
setValue(newValue);
};
const { data: phoneVisibilityData } = useQuery({
queryKey: ["phoneVisibility"],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.PHONE_NUMBER_VISIBILITY);
return res.data;
},
});
React.useEffect(() => {
if (phoneVisibilityData) {
setSettings((prev) => ({
...prev,
phoneVisibility: phoneVisibilityData.phone_number_visibility === 1,
}));
}
}, [phoneVisibilityData]);
const phoneVisibilityMutation = useMutation({
mutationFn: async (isVisible) => {
return await axiosInstance.post(API_ENDPOINTS.UPDATE_PHONE_NUMBER_VISIBILITY, null, {
params: { phone_number_visibility: isVisible ? 1 : 0 },
});
},
onSuccess: (res) => {
toast.success(res.data.message);
},
onError: () => {
toast.error("Failed to update phone visibility");
setSettings((prev) => ({ ...prev, phoneVisibility: !prev.phoneVisibility }));
},
});
const { data: chatAlertData } = useQuery({
queryKey: ["chatAlerts"],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.CHAT_ALERT_NOTIFICATION);
return res.data;
},
});
React.useEffect(() => {
if (chatAlertData) {
setSettings((prev) => ({
...prev,
chatAlertsNotification: chatAlertData.chat_alert_notification === 1,
chatProtection: chatAlertData.chat_protection === 1,
}));
}
}, [chatAlertData]);
const chatAlertMutation = useMutation({
mutationFn: async (payload) => {
return await axiosInstance.post(API_ENDPOINTS.UPDATE_CHAT_ALERT_NOTIFICATION, null, {
params: {
chat_alert_notification: payload.chatAlertsNotification ? 1 : 0,
chat_protection: payload.chatProtection ? 1 : 0,
},
});
},
onSuccess: (res) => {
toast.success(res.data.message);
},
onError: () => {
toast.error("Failed to update chat settings");
},
});
const { data: profileProtectionData } = useQuery({
queryKey: ["profileProtection"],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.PROFILE_PROTECT_API);
return res.data;
},
});
React.useEffect(() => {
if (profileProtectionData) {
setSettings((prev) => ({
...prev,
protectProfilePhoto: profileProtectionData.profile_photo_protect === 1,
callProtection: profileProtectionData.call_protection === 1,
}));
}
}, [profileProtectionData]);
const profileProtectionMutation = useMutation({
mutationFn: async (payload) => {
return await axiosInstance.post(
API_ENDPOINTS.UPDATE_PROFILE_PROTECT_API,
null,
{
params: {
profile_photo_protect: payload.protectProfilePhoto ? 1 : 0,
call_protection: payload.callProtection ? 1 : 0,
},
}
);
},
onSuccess: (res) => {
toast.success(res.data.message);
},
onError: () => {
toast.error("Failed to update profile protection settings");
},
});
const { data: matchAlertData } = useQuery({
queryKey: ["matchAlerts"],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.MATCH_ALERT);
return res.data;
},
});
React.useEffect(() => {
if (matchAlertData) {
setSettings((prev) => ({
...prev,
matchAlerts: matchAlertData.match_alert_preference === 1,
}));
}
}, [matchAlertData]);
const matchAlertMutation = useMutation({
mutationFn: async (isEnabled) => {
return await axiosInstance.post(API_ENDPOINTS.UPDATE_MATCH_ALERT, null, {
params: { match_alert_preference: isEnabled ? 1 : 0 },
});
},
onSuccess: (res) => {
toast.success(res.data.message);
},
onError: () => {
toast.error("Failed to update match alert settings");
setSettings((prev) => ({ ...prev, matchAlerts: !prev.matchAlerts }));
},
});
const { data: whoCanMessageData } = useQuery({
queryKey: ["whoCanMessage"],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.WHO_CAN_VIEW_MESSAGE);
return res.data;
},
});
React.useEffect(() => {
if (whoCanMessageData) {
setSettings((prev) => ({
...prev,
messagePermission: whoCanMessageData.who_can_message === 1,
messageCategory: whoCanMessageData.who_can_message_categories || 'All Profile',
}));
}
}, [whoCanMessageData]);
const whoCanMessageMutation = useMutation({
mutationFn: async (payload) => {
return await axiosInstance.post(API_ENDPOINTS.UPDATE_WHO_CAN_VIEW_MESSAGE, null, {
params: {
who_can_message: payload.messagePermission ? 1 : 0,
who_can_message_categories: payload.messageCategory,
},
});
},
onSuccess: (res) => {
toast.success(res.data.message);
},
onError: (err, variables) => {
toast.error("Failed to update message settings");
// Revert optimistic update
setSettings(variables.oldSettings);
},
});
const toggleSetting = (key) => {
setSettings(prev => ({ ...prev, [key]: !prev[key] }));
if (key === "phoneVisibility") {
const newValue = !settings.phoneVisibility;
setSettings((prev) => ({ ...prev, [key]: newValue }));
phoneVisibilityMutation.mutate(newValue);
} else if (key === "chatAlertsNotification" || key === "chatProtection") {
const newValue = !settings[key];
setSettings((prev) => ({ ...prev, [key]: newValue }));
const payload = {
chatAlertsNotification: key === "chatAlertsNotification" ? newValue : settings.chatAlertsNotification,
chatProtection: key === "chatProtection" ? newValue : settings.chatProtection,
};
chatAlertMutation.mutate(payload);
} else if (key === "protectProfilePhoto" || key === "callProtection") {
const newValue = !settings[key];
setSettings((prev) => ({ ...prev, [key]: newValue }));
const payload = {
protectProfilePhoto:
key === "protectProfilePhoto" ? newValue : settings.protectProfilePhoto,
callProtection:
key === "callProtection" ? newValue : settings.callProtection,
};
profileProtectionMutation.mutate(payload);
} else if (key === "matchAlerts") {
const newValue = !settings.matchAlerts;
setSettings((prev) => ({ ...prev, [key]: newValue }));
matchAlertMutation.mutate(newValue);
} else if (key === 'messagePermission') {
const oldSettings = { ...settings };
const newValue = !settings.messagePermission;
setSettings(prev => ({ ...prev, [key]: newValue }));
whoCanMessageMutation.mutate({
messagePermission: newValue,
messageCategory: settings.messageCategory,
oldSettings
});
} else {
setSettings((prev) => ({ ...prev, [key]: !prev[key] }));
}
};
const handleSelectChange = (key, value) => {
const oldSettings = { ...settings };
setSettings(prev => ({ ...prev, [key]: value }));
whoCanMessageMutation.mutate({
messagePermission: settings.messagePermission,
messageCategory: value,
oldSettings
});
};
const tabs = [
@ -183,14 +447,14 @@ export default function AccountSettingPage() {
settings: [
{
key: 'matchAlerts',
title: 'Match Alert Notifications',
description: 'Receive notifications when new profiles match your preferences and requirements.',
},
{
key: 'profileViews',
title: 'Profile View Alerts',
description: 'Get notified when someone views your profile or shows interest in connecting with you.',
title: 'Match Alerts Preferences',
description: 'Match Alerts Preferences content" typically refers to the various settings within an application (e.g., sports, finance, or business software)',
},
// {
// key: 'profileViews',
// title: 'Profile View Alerts',
// description: 'Get notified when someone views your profile or shows interest in connecting with you.',
// },
],
},
{
@ -201,14 +465,16 @@ export default function AccountSettingPage() {
settings: [
{
key: 'messagePermission',
title: 'Message Permissions',
description: 'Control who can send you messages. You can restrict messages to only verified profiles or profiles that match your preferences.',
title: 'Who Can Message Me?',
description: 'Control who is able to send you messages on the platform.',
},
{
key: 'blockUnverified',
title: 'Block Unverified Profiles',
description: 'Automatically block messages from profiles that haven\'t completed verification process.',
},
key: 'messageCategory',
title: 'Allow messages from',
description: 'Choose which category of users can message you.',
type: 'select',
options: ['All Profile', 'Matches', 'Premium Users']
}
],
},
];
@ -219,7 +485,7 @@ export default function AccountSettingPage() {
minHeight: '90vh', marginTop:"50px", marginBottom:"50px" }}>
{/* Desktop: Vertical Sidebar */}
{!isMobile && (
<Box sx={{ width: 300, borderRadius:"10px" ,background:"#ffeac9", borderRight: '1px solid #e5e7eb', display: { xs: 'none', md: 'block' } }}>
<Box sx={{ width: 300, borderRadius:"10px" ,background:"#f2f2f2", borderRight: '1px solid #e5e7eb', display: { xs: 'none', md: 'block' } }}>
<Box sx={{
position: 'sticky',
top: 0,
@ -338,6 +604,7 @@ export default function AccountSettingPage() {
settings={tab.settings}
states={settings}
onToggle={toggleSetting}
onSelectChange={handleSelectChange}
/>
</TabPanel>
))}

View File

@ -1,109 +1,116 @@
import { useState } from 'react';
import contactimg from "../assets/images/contactimg.jpg";
import React from 'react';
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 FacebookIcon from '@mui/icons-material/Facebook';
import TwitterIcon from '@mui/icons-material/Twitter';
import CloseIcon from '@mui/icons-material/Close'; // using as X icon
import SvgIcon from "@mui/material/SvgIcon";
import SvgIcon from '@mui/material/SvgIcon';
import { Phone, Mail, ChevronRight } from 'lucide-react';
function XIcon(props) {
return (
<SvgIcon {...props} viewBox="0 0 24 24">
const XIcon = (props) => (
<SvgIcon {...props}>
<path d="M18.3 2H21L13.4 10.5L22 22H15.6L10.7 15.7L5 22H2L9.9 13L2 2H8.5L13.9 8.8L18.3 2Z" />
</SvgIcon>
);
}
const ContactUsPage = () => {
const { data, isLoading } = useQuery({
queryKey: ['contactUs'],
queryFn: getContactUs,
});
const contact = data || {};
const socialLinks = [
{ name: "Instagram", icon: <InstagramIcon fontSize="large" />, color: "from-purple-500 to-[#d93f87]", url: "#" },
{ name: "Facebook", icon: <FacebookIcon fontSize="large" />, color: "from-blue-900 to-blue-800", url: "#" },
{ name: "Twitter", icon: <TwitterIcon fontSize="large" />, color: "from-blue-400 to-blue-500", url: "#" },
{ name: "X", icon: <XIcon fontSize="large" />, color: "from-gray-800 to-black", url: "#" },
const socialMedia = [
{
name: "Instagram",
icon: <InstagramIcon fontSize="large" />,
color: "from-purple-500 to-[#d93f87]",
url: contact.instagram_url
},
{
name: "Facebook",
icon: <FacebookIcon fontSize="large" />,
color: "from-blue-900 to-blue-800",
url: contact.facebook_url
},
{
name: "X",
icon: <XIcon fontSize="large" />,
color: "from-gray-800 to-black",
url: contact.x_url
}
];
if (isLoading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
}
return (
<div className="min-h-screen my-8">
<div className='grid grid-cols-1 md:grid-cols-2 gap-2 items-center' style={{justifyItems:"center"}}>
<LazyImage src={contactimg} className=""/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 items-center" style={{ justifyItems: "center" }}>
<LazyImage
src={contact.image}
className=""
alt="Contact Us"
/>
<div className="max-w-md mx-auto px-4 py-8">
{/* Get in touch section */}
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-[#034E08] mb-3">Get in touch</h2>
<h2 className="text-2xl font-bold text-[#034E08] mb-3">{contact.title}</h2>
<p className="text-gray-600 text-sm leading-relaxed">
If you have inquiries get in touch with us we'll happy to help you
{contact.content}
</p>
</div>
{/* Contact Information Cards */}
<div className="space-y-4 mb-8">
{/* Phone Card */}
<a href="tel:9345656442" className="block bg-white rounded-2xl shadow-md hover:shadow-xl transition-shadow p-5 border border-gray-100">
<a href={`tel:${contact.mobile}`} className="block bg-white rounded-2xl shadow-md hover:shadow-xl transition-shadow p-5 border border-gray-100">
<div className="flex items-center">
<div className="w-12 h-12 bg-[#A70710] rounded-xl flex items-center justify-center mr-4 shadow-lg">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
<Phone className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<p className="text-[14px] text-gray-500 mb-1">Call us</p>
<p className="text-base font-semibold text-gray-800">93456 56442</p>
<p className="text-base font-semibold text-gray-800">{contact.mobile}</p>
</div>
<svg className="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
<ChevronRight className="w-5 h-5 text-gray-400" />
</div>
</a>
{/* Email Card */}
<a href="mailto:ddsmile.03@gmailcom" className="block bg-white rounded-2xl shadow-md hover:shadow-xl transition-shadow p-5 border border-gray-100">
<a href={`mailto:${contact.email}`} className="block bg-white rounded-2xl shadow-md hover:shadow-xl transition-shadow p-5 border border-gray-100">
<div className="flex items-center">
<div className="w-12 h-12 bg-[#A70710] rounded-xl flex items-center justify-center mr-4 shadow-lg">
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<Mail className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<p className="text-[14px] text-gray-500 mb-1">Email us</p>
<p className="text-base font-semibold text-gray-800">ddsmile.03@gmailcom</p>
<p className="text-base font-semibold text-gray-800">{contact.email}</p>
</div>
<svg className="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
<ChevronRight className="w-5 h-5 text-gray-400" />
</div>
</a>
</div>
{/* Social Media Section */}
<div className="mb-8">
<h3 className="text-lg font-bold text-gray-800 mb-4 text-center">Social Media</h3>
<div className="flex justify-center gap-4">
{socialLinks.map((social, idx) => (
{socialMedia.map((item, index) => (
item.url && (
<a
key={idx}
href={social.url}
className={`w-14 h-14 bg-gradient-to-br ${social.color} rounded-2xl flex items-center justify-center text-white text-2xl shadow-lg hover:scale-110 transition-transform`}
key={index}
href={item.url}
target="_blank"
rel="noopener noreferrer"
className={`w-14 h-14 bg-gradient-to-br ${item.color} rounded-2xl flex items-center justify-center text-white text-2xl shadow-lg hover:scale-110 transition-transform`}
>
{social.icon}
{item.icon}
</a>
)
))}
</div>
</div>
</div>
</div>
</div>
);
};

View File

@ -1,102 +1,22 @@
import React, { useState } from 'react';
import { Tabs, Tab, IconButton, Menu, MenuItem } from '@mui/material';
import { MoreVert, Delete } from '@mui/icons-material';
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import axiosInstance from '../api/axiosInstance';
import { API_ENDPOINTS } from '../api/apiEndpoints';
import { Notifications as NotificationsIcon } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
const NotificationPage = () => {
const [activeTab, setActiveTab] = useState(0);
const [anchorEl, setAnchorEl] = useState(null);
const [selectedNotification, setSelectedNotification] = useState(null);
const navigate = useNavigate();
const [notifications, setNotifications] = useState([
{
id: 1,
type: 'message',
user: 'Thangavel',
avatar: 'https://i.pravatar.cc/150?img=1',
message: 'has sent you a message',
time: '1d',
action: 'View message',
category: 'all'
const { data: notificationData, isLoading, isError } = useQuery({
queryKey: ['notifications'],
queryFn: async () => {
const res = await axiosInstance.get(API_ENDPOINTS.NOTIFICATION_LIST);
return res.data;
},
{
id: 2,
type: 'profile',
user: 'Thangavel',
avatar: 'https://i.pravatar.cc/150?img=2',
message: 'and 3 others have viewed your profile',
time: '1d',
action: 'See who viewed',
category: 'interactions'
},
{
id: 3,
type: 'interest',
user: 'Parthiban',
avatar: 'https://i.pravatar.cc/150?img=3',
message: 'has sent you an interest',
time: '1d',
action: 'View interest',
category: 'urgent'
},
{
id: 4,
type: 'message',
user: 'Rajesh',
avatar: 'https://i.pravatar.cc/150?img=4',
message: 'replied to your message',
time: '2d',
action: 'View message',
category: 'all'
},
{
id: 5,
type: 'profile',
user: 'Priya',
avatar: 'https://i.pravatar.cc/150?img=5',
message: 'viewed your profile',
time: '2d',
action: 'See profile',
category: 'interactions'
},
{
id: 6,
type: 'interest',
user: 'Kumar',
avatar: 'https://i.pravatar.cc/150?img=6',
message: 'accepted your interest',
time: '3d',
action: 'View profile',
category: 'urgent'
}
]);
});
const handleTabChange = (event, newValue) => {
setActiveTab(newValue);
};
const handleMenuOpen = (event, notificationId) => {
setAnchorEl(event.currentTarget);
setSelectedNotification(notificationId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedNotification(null);
};
const handleDelete = () => {
setNotifications(notifications.filter(n => n.id !== selectedNotification));
handleMenuClose();
};
const getFilteredNotifications = () => {
if (activeTab === 0) return notifications;
if (activeTab === 1) return notifications.filter(n => n.category === 'interactions');
if (activeTab === 2) return notifications.filter(n => n.category === 'urgent');
return notifications;
};
const filteredNotifications = getFilteredNotifications();
const notifications = notificationData?.data || [];
return (
<div className="min-h-screen my-10">
@ -106,119 +26,50 @@ const NotificationPage = () => {
<h1 className="text-2xl text-center font-semibold text-gray-900">Notifications</h1>
</div>
{/* Tabs */}
<div className="max-w-[350px] w-full mx-auto " >
<Tabs
value={activeTab}
onChange={handleTabChange}
sx={{
'& .MuiTab-root': {
textTransform: 'none',
fontSize: '15px',
fontWeight: 500,
minWidth: 'auto',
px: 3,
border:"1px solid #074201ff ",
display:"flex",
gap:"10px",
flexWrap:"wrap",
},
'& .Mui-selected': {
color: '#fcfcfcff !important',
backgroundColor: '#074201ff',
},
'& .MuiTabs-indicator': {
backgroundColor: '#074201ff',
}
}}
>
<Tab label="All" />
<Tab label="Interactions" sx={{marginLeft:"10px"}} />
<Tab label="Urgent" sx={{marginLeft:"10px"}}/>
</Tabs>
</div>
{/* Notification List */}
<div className="">
{filteredNotifications.length === 0 ? (
<div className="max-w-3xl mx-auto">
{isLoading ? (
<div className="px-4 py-12 text-center text-gray-500">Loading notifications...</div>
) : isError ? (
<div className="px-4 py-12 text-center text-red-500">Failed to load notifications.</div>
) : notifications.length === 0 ? (
<div className="px-4 py-12 text-center text-gray-500">
No notifications found
</div>
) : (
<>
<div className="px-4 py-3 bg-[#edfffa] my-10">
<h2 className="text-[18px] font-semibold text-gray-700">Yesterday</h2>
</div>
{filteredNotifications.map((notification) => (
<div className="space-y-2">
{notifications.map((notification) => (
<div
key={notification.id}
className="px-4 py-4 hover:bg-gray-50 transition-colors bg-[#fff5ed] border-b border-[#A70710]"
className="px-4 py-4 hover:bg-gray-50 transition-colors bg-white border-b border-gray-200 cursor-pointer"
onClick={() => notification.page && navigate(`/${notification.page}`)}
>
<div className="flex items-start gap-3 ">
{/* Avatar */}
<img
src={notification.avatar}
alt={notification.user}
className="w-12 h-12 rounded-full object-cover flex-shrink-0"
/>
{/* Icon */}
<div className="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<NotificationsIcon className="text-green-700" />
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<p className="text-sm text-gray-900">
<span className="font-semibold">{notification.user}</span>{' '}
<span className="text-gray-700">{notification.message}</span>
<span className="font-semibold">{notification.tittle}</span>
</p>
<p className="text-sm text-gray-600 mt-1">
{notification.description}
</p>
<button className="mt-2 px-4 py-1.5 text-sm font-medium text-[#A70710] border-1 border-[#A70710] rounded-full hover:bg-[#A70710] hover:text-[#fff] transition-colors">
{notification.action}
</button>
</div>
{/* Time and Menu */}
{/* Time */}
<div className="flex items-start gap-2 flex-shrink-0">
<span className="text-xs text-gray-500 mt-1">{notification.time}</span>
<IconButton
size="small"
onClick={(e) => handleMenuOpen(e, notification.id)}
sx={{
color: '#6b7280',
'&:hover': { backgroundColor: '#f3f4f6' }
}}
>
<MoreVert fontSize="small" />
</IconButton>
<span className="text-xs text-gray-500 mt-1 whitespace-nowrap">{notification.created_at}</span>
</div>
</div>
</div>
))}
</>
</div>
)}
</div>
{/* Delete Menu */}
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<MenuItem
onClick={handleDelete}
sx={{
color: '#ef4444',
'&:hover': { backgroundColor: '#fee2e2' }
}}
>
<Delete fontSize="small" sx={{ mr: 1 }} />
Delete
</MenuItem>
</Menu>
</div>
</div>
);

View File

@ -1,41 +1,26 @@
import React from 'react';
import { ArrowBackIosNew, Security, Report, Lock, Psychology } from '@mui/icons-material';
import { Security, Psychology } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import LazyImage from '../components/common/LazyImage';
import safegirl from "../assets/images/safegirl.jpg";
const dummyImage = "https://images.unsplash.com/photo-1594736797933-d0501ba2fe65?w=800&q=80&fit=crop";
import { useQuery } from '@tanstack/react-query';
import { getBeSafeOnline } from '../api/safety.api';
export default function SafetyCentre() {
const navigate = useNavigate();
const safetyTips = [
{
icon: <Security className="text-3xl" />,
title: "Safety Tips",
color: "red",
desc: "Follow these practical guidelines to protect yourself while connecting with potential Matches."
},
{
icon: <Report className="text-3xl" />,
title: "Report a Profile",
color: "orange",
desc: "Report any suspicious or inappropriate behavior immediately—especially requests for money, unsolicited contact, or blackmail threats."
},
{
icon: <Lock className="text-3xl" />,
title: "Privacy Settings",
color: "pink",
desc: "Manage who sees your photos, contact info, & profile and always be in control."
},
{
icon: <Psychology className="text-3xl" />,
title: "Mental Wellbeing",
color: "blue",
desc: "Here's how you can prioritize your mental well-being while making this life-changing decision."
const { data, isLoading } = useQuery({
queryKey: ['beSafeOnline'],
queryFn: getBeSafeOnline,
});
if (isLoading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
}
];
const safetyData = data?.data || [];
const heroData = safetyData.length > 0 ? safetyData[0] : null;
const tipsData = safetyData.length > 1 ? safetyData.slice(1) : [];
return (
<div className="min-h-screen relative z-20">
@ -54,52 +39,38 @@ export default function SafetyCentre() {
{/* Hero Section */}
{heroData && (
<div className='grid grid-cols-1 md:grid-cols-2 gap-2 my-4'>
<section className="md:px-6 pt-8 pb-10">
<h2 className="text-3xl font-bold text-gray-900 mb-4">Safety Centre</h2>
<h2 className="text-3xl font-bold text-gray-900 mb-4" style={{ color: heroData.title_color }}>
{heroData.title}
</h2>
<p className="text-xl text-gray-600 text-justify leading-relaxed max-w-[900px]">
Your safety matters deeply at <span className="font-bold text-red-600">Thirukalyanam</span>.
Our team works with advanced tools to ensure your matchmaking journey remains secure and safe.
{heroData.content}
</p>
</section>
<LazyImage src={safegirl} className="rounded-tl-[50px] rounded-tr-[0px] rounded-bl-[50px] rounded-br-[50px]"/>
<LazyImage src={heroData.image} className="rounded-tl-[50px] rounded-tr-[0px] rounded-bl-[50px] rounded-br-[50px]"/>
</div>
)}
{/* Safety Tips Grid */}
<section className="px-6 py-12 max-w-[1400px] mx-auto space-y-4">
{safetyTips.map((tip, index) => (
{tipsData.map((tip, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.15 }}
className="bg-white rounded-3xl p-8 border border-gray-100 flex items-start gap-6"
>
<div className={`p-4 rounded-2xl bg-gradient-to-br ${
tip.color === "red" ? "from-red-500 to-rose-600" :
tip.color === "orange" ? "from-orange-500 to-amber-600" :
tip.color === "pink" ? "from-pink-500 to-rose-500" :
"from-blue-500 to-indigo-600"
} text-white shadow-lg`}>
{tip.icon}
<div className="p-4 rounded-2xl bg-gray-50 shadow-lg">
<LazyImage src={tip.icon} className="w-10 h-10 object-contain" />
</div>
<div className="flex-1">
<h3 className={`text-2xl font-bold mb-3 ${
tip.color === "red" ? "text-red-600" :
tip.color === "orange" ? "text-orange-600" :
tip.color === "pink" ? "text-pink-600" :
"text-blue-600"
}`}>
<h3 className="text-2xl font-bold mb-3" style={{ color: tip.title_color }}>
{tip.title}
</h3>
<p className="text-gray-700 text-lg leading-relaxed">{tip.desc}</p>
<p className="text-gray-700 text-lg leading-relaxed">{tip.content}</p>
</div>
</motion.div>
))}