setting page api integrated
This commit is contained in:
parent
564b5ed50f
commit
65bd6c646b
@ -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",
|
||||
|
||||
|
||||
};
|
||||
|
||||
@ -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
7
src/api/contact.api.js
Normal 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
7
src/api/safety.api.js
Normal 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;
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user