feat: Add Billings & Payments and Testimonials settings pages for agent and user roles, and refactor notification settings form.
3
public/assets/icons/gem-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.625 7.875L10.5 18.375L18.375 7.875M2.625 7.875L5.25 2.625H15.75L18.375 7.875M2.625 7.875H18.375M7 2.625L5.25 7.875L10.5 18.375L15.75 7.875L14 2.625" stroke="#e58625" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 349 B |
5
public/assets/icons/question-circle-icon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.5 3.5C9.768 3.5 3.5 9.768 3.5 17.5C3.5 25.232 9.768 31.5 17.5 31.5C25.232 31.5 31.5 25.232 31.5 17.5C31.5 9.768 25.232 3.5 17.5 3.5Z" stroke="#e58625" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.125 13.125C13.125 10.709 15.084 8.75 17.5 8.75C19.916 8.75 21.875 10.709 21.875 13.125C21.875 15.541 19.916 17.5 17.5 17.5V20.125" stroke="#e58625" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="17.5" cy="24.5" r="1.5" fill="#e58625"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 613 B |
5
public/assets/icons/settings-billing-icon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.5 4.16667H2.5C1.80964 4.16667 1.25 4.72631 1.25 5.41667V14.5833C1.25 15.2737 1.80964 15.8333 2.5 15.8333H17.5C18.1904 15.8333 18.75 15.2737 18.75 14.5833V5.41667C18.75 4.72631 18.1904 4.16667 17.5 4.16667Z" stroke="#00293D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M1.25 8.33333H18.75" stroke="#00293D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5 12.5H8.33333" stroke="#00293D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 634 B |
3
public/assets/icons/settings-testimonials-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 1.66667L12.575 6.88334L18.3333 7.72501L14.1667 11.7833L15.15 17.5167L10 14.8083L4.85 17.5167L5.83333 11.7833L1.66667 7.72501L7.425 6.88334L10 1.66667Z" stroke="#00293D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 353 B |
4
public/assets/icons/tick-circle-icon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5 1.75C5.67 1.75 1.75 5.67 1.75 10.5C1.75 15.33 5.67 19.25 10.5 19.25C15.33 19.25 19.25 15.33 19.25 10.5C19.25 5.67 15.33 1.75 10.5 1.75Z" fill="#e58625"/>
|
||||||
|
<path d="M7 10.5L9.333 12.833L14 8.167" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 395 B |
BIN
public/assets/images/need-help-illustration.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/assets/images/subscription-plan-illustration.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
18
src/app/(agent)/agent/settings/billings/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { SettingsSidebar } from '@/components/settings';
|
||||||
|
import { SubscriptionForm } from '@/components/settings/SubscriptionForm';
|
||||||
|
|
||||||
|
export default function BillingsPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
|
{/* Left Sidebar */}
|
||||||
|
<SettingsSidebar basePath="/agent/settings" />
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<SubscriptionForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,21 +2,7 @@
|
|||||||
|
|
||||||
import { SettingsSidebar, NotificationsForm } from '@/components/settings';
|
import { SettingsSidebar, NotificationsForm } from '@/components/settings';
|
||||||
|
|
||||||
interface NotificationSetting {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NotificationsPage() {
|
export default function NotificationsPage() {
|
||||||
const handleSave = (data: {
|
|
||||||
emailNotifications: NotificationSetting[];
|
|
||||||
pushNotifications: NotificationSetting[];
|
|
||||||
}) => {
|
|
||||||
console.log('Saving agent notification settings:', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col lg:flex-row gap-6">
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
{/* Left Sidebar */}
|
{/* Left Sidebar */}
|
||||||
@@ -24,14 +10,7 @@ export default function NotificationsPage() {
|
|||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{/* Header Card */}
|
<NotificationsForm />
|
||||||
<div className="border border-[#00293d]/10 rounded-[15px] px-6 py-4 mb-4 bg-white">
|
|
||||||
<h1 className="font-fractul font-bold text-[15px] leading-[18px] text-[#00293D]">
|
|
||||||
Notifications
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NotificationsForm onSave={handleSave} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
25
src/app/(agent)/agent/settings/testimonials/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { SettingsSidebar } from '@/components/settings';
|
||||||
|
|
||||||
|
export default function TestimonialsPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
|
{/* Left Sidebar */}
|
||||||
|
<SettingsSidebar basePath="/agent/settings" />
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="border border-[#00293d]/10 rounded-[15px] bg-white p-6">
|
||||||
|
<h2 className="font-fractul font-bold text-[20px] text-[#00293D] mb-4">
|
||||||
|
Add Testimonials
|
||||||
|
</h2>
|
||||||
|
<div className="border-b border-[#00293D]/10 mb-6" />
|
||||||
|
<p className="font-serif text-[14px] text-[#00293D]/70">
|
||||||
|
Testimonials management coming soon.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,21 +2,7 @@
|
|||||||
|
|
||||||
import { SettingsSidebar, NotificationsForm } from '@/components/settings';
|
import { SettingsSidebar, NotificationsForm } from '@/components/settings';
|
||||||
|
|
||||||
interface NotificationSetting {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function UserNotificationsPage() {
|
export default function UserNotificationsPage() {
|
||||||
const handleSave = (data: {
|
|
||||||
emailNotifications: NotificationSetting[];
|
|
||||||
pushNotifications: NotificationSetting[];
|
|
||||||
}) => {
|
|
||||||
console.log('Saving user notification settings:', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-6">
|
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-6">
|
||||||
<div className="flex flex-col lg:flex-row gap-6">
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
@@ -25,14 +11,7 @@ export default function UserNotificationsPage() {
|
|||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{/* Header Card */}
|
<NotificationsForm />
|
||||||
<div className="border border-[#00293d]/10 rounded-[15px] px-6 py-4 mb-4 bg-white">
|
|
||||||
<h1 className="font-fractul font-bold text-[15px] leading-[18px] text-[#00293D]">
|
|
||||||
Notifications
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NotificationsForm onSave={handleSave} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,72 +4,44 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import api from '@/services/api';
|
import api from '@/services/api';
|
||||||
|
|
||||||
interface NotificationSetting {
|
type DigestFrequency = 'instant' | 'daily' | 'weekly' | 'off';
|
||||||
|
|
||||||
|
interface NotificationCategory {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
enabled: boolean;
|
email: boolean;
|
||||||
|
inApp: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationsFormProps {
|
interface NotificationsFormProps {
|
||||||
onSave?: (data: {
|
onSave?: (data: {
|
||||||
emailNotifications: NotificationSetting[];
|
notifications: NotificationCategory[];
|
||||||
pushNotifications: NotificationSetting[];
|
digestFrequency: DigestFrequency;
|
||||||
}) => void;
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default notification settings (all disabled by default)
|
const DEFAULT_NOTIFICATIONS: NotificationCategory[] = [
|
||||||
const DEFAULT_EMAIL_NOTIFICATIONS: NotificationSetting[] = [
|
|
||||||
{
|
{
|
||||||
id: 'new_leads',
|
id: 'new_lead_alerts',
|
||||||
label: 'New Leads',
|
label: 'New Lead Alerts',
|
||||||
description: 'Get notified when you receive new leads',
|
description: 'Choose what you want to be notified about and how.',
|
||||||
enabled: false,
|
email: true,
|
||||||
|
inApp: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'messages',
|
id: 'message_updates',
|
||||||
label: 'Messages',
|
label: 'Message Updates',
|
||||||
description: 'Get notified when you receive new messages',
|
description: 'Notices about new messages from clients or other agents.',
|
||||||
enabled: false,
|
email: false,
|
||||||
|
inApp: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'connection_requests',
|
id: 'urgent_requests_tours',
|
||||||
label: 'Connection Requests',
|
label: 'Urgent Requests & Tours',
|
||||||
description: 'Get notified about new connection requests',
|
description: 'Immediate notifications for tour requests and time-sensitive items.',
|
||||||
enabled: false,
|
email: true,
|
||||||
},
|
inApp: true,
|
||||||
{
|
|
||||||
id: 'property_updates',
|
|
||||||
label: 'Property Updates',
|
|
||||||
description: 'Get notified about property status changes',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'marketing',
|
|
||||||
label: 'Marketing & Promotions',
|
|
||||||
description: 'Receive marketing emails and promotions',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const DEFAULT_PUSH_NOTIFICATIONS: NotificationSetting[] = [
|
|
||||||
{
|
|
||||||
id: 'push_messages',
|
|
||||||
label: 'Messages',
|
|
||||||
description: 'Push notifications for new messages',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'push_leads',
|
|
||||||
label: 'New Leads',
|
|
||||||
description: 'Push notifications for new leads',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'push_reminders',
|
|
||||||
label: 'Reminders',
|
|
||||||
description: 'Push notifications for scheduled reminders',
|
|
||||||
enabled: false,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -80,10 +52,9 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
const [emailNotifications, setEmailNotifications] = useState<NotificationSetting[]>(DEFAULT_EMAIL_NOTIFICATIONS);
|
const [notifications, setNotifications] = useState<NotificationCategory[]>(DEFAULT_NOTIFICATIONS);
|
||||||
const [pushNotifications, setPushNotifications] = useState<NotificationSetting[]>(DEFAULT_PUSH_NOTIFICATIONS);
|
const [digestFrequency, setDigestFrequency] = useState<DigestFrequency>('instant');
|
||||||
|
|
||||||
// Fetch saved preferences on mount
|
|
||||||
const fetchPreferences = useCallback(async () => {
|
const fetchPreferences = useCallback(async () => {
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
|
|
||||||
@@ -95,26 +66,26 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
const response = await api.get(endpoint);
|
const response = await api.get(endpoint);
|
||||||
const savedPrefs = response.data?.data || response.data || {};
|
const savedPrefs = response.data?.data || response.data || {};
|
||||||
|
|
||||||
// Merge saved preferences with defaults
|
if (savedPrefs.notifications) {
|
||||||
if (savedPrefs.email) {
|
setNotifications((prev) =>
|
||||||
setEmailNotifications((prev) =>
|
prev.map((item) => {
|
||||||
prev.map((item) => ({
|
const saved = savedPrefs.notifications[item.id];
|
||||||
...item,
|
if (saved) {
|
||||||
enabled: savedPrefs.email[item.id] ?? item.enabled,
|
return {
|
||||||
}))
|
...item,
|
||||||
|
email: saved.email ?? item.email,
|
||||||
|
inApp: saved.inApp ?? item.inApp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedPrefs.push) {
|
if (savedPrefs.digestFrequency) {
|
||||||
setPushNotifications((prev) =>
|
setDigestFrequency(savedPrefs.digestFrequency);
|
||||||
prev.map((item) => ({
|
|
||||||
...item,
|
|
||||||
enabled: savedPrefs.push[item.id] ?? item.enabled,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
// If no saved preferences, use defaults (all disabled)
|
|
||||||
console.log('No saved notification preferences found, using defaults');
|
console.log('No saved notification preferences found, using defaults');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -125,18 +96,10 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
fetchPreferences();
|
fetchPreferences();
|
||||||
}, [fetchPreferences]);
|
}, [fetchPreferences]);
|
||||||
|
|
||||||
const toggleEmailNotification = (id: string) => {
|
const toggleNotification = (id: string, type: 'email' | 'inApp') => {
|
||||||
setEmailNotifications((prev) =>
|
setNotifications((prev) =>
|
||||||
prev.map((item) =>
|
prev.map((item) =>
|
||||||
item.id === id ? { ...item, enabled: !item.enabled } : item
|
item.id === id ? { ...item, [type]: !item[type] } : item
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const togglePushNotification = (id: string) => {
|
|
||||||
setPushNotifications((prev) =>
|
|
||||||
prev.map((item) =>
|
|
||||||
item.id === id ? { ...item, enabled: !item.enabled } : item
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -150,10 +113,15 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
const role = (session?.user as any)?.role;
|
const role = (session?.user as any)?.role;
|
||||||
const endpoint = role === 'AGENT' ? '/agents/preferences/notifications' : '/users/preferences/notifications';
|
const endpoint = role === 'AGENT' ? '/agents/preferences/notifications' : '/users/preferences/notifications';
|
||||||
|
|
||||||
// Transform settings to API format
|
|
||||||
const preferences = {
|
const preferences = {
|
||||||
email: emailNotifications.reduce((acc, item) => ({ ...acc, [item.id]: item.enabled }), {}),
|
notifications: notifications.reduce(
|
||||||
push: pushNotifications.reduce((acc, item) => ({ ...acc, [item.id]: item.enabled }), {}),
|
(acc, item) => ({
|
||||||
|
...acc,
|
||||||
|
[item.id]: { email: item.email, inApp: item.inApp },
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
digestFrequency,
|
||||||
};
|
};
|
||||||
|
|
||||||
await api.put(endpoint, preferences);
|
await api.put(endpoint, preferences);
|
||||||
@@ -161,10 +129,9 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
setSuccess('Notification preferences saved successfully!');
|
setSuccess('Notification preferences saved successfully!');
|
||||||
|
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
onSave({ emailNotifications, pushNotifications });
|
onSave({ notifications, digestFrequency });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear success message after 3 seconds
|
|
||||||
setTimeout(() => setSuccess(null), 3000);
|
setTimeout(() => setSuccess(null), 3000);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const error = err as { response?: { data?: { message?: string } } };
|
const error = err as { response?: { data?: { message?: string } } };
|
||||||
@@ -174,27 +141,55 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToggleSwitch = ({
|
const handleCancel = () => {
|
||||||
enabled,
|
setNotifications(DEFAULT_NOTIFICATIONS);
|
||||||
|
setDigestFrequency('instant');
|
||||||
|
setError(null);
|
||||||
|
setSuccess(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Checkbox = ({
|
||||||
|
checked,
|
||||||
onToggle,
|
onToggle,
|
||||||
|
label,
|
||||||
}: {
|
}: {
|
||||||
enabled: boolean;
|
checked: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
|
label: string;
|
||||||
}) => (
|
}) => (
|
||||||
<button
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
onClick={onToggle}
|
<button
|
||||||
className={`relative w-[44px] h-[24px] rounded-full transition-colors cursor-pointer ${
|
type="button"
|
||||||
enabled ? 'bg-[#e58625]' : 'bg-[#00293D]/20'
|
onClick={onToggle}
|
||||||
}`}
|
className={`w-[16px] h-[16px] rounded-[2px] border flex items-center justify-center transition-colors ${
|
||||||
>
|
checked
|
||||||
<div
|
? 'bg-[#00293D] border-[#00293D]'
|
||||||
className={`absolute top-[2px] w-[20px] h-[20px] rounded-full bg-white shadow transition-transform ${
|
: 'bg-white border-[#00293D]/30'
|
||||||
enabled ? 'translate-x-[22px]' : 'translate-x-[2px]'
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
>
|
||||||
</button>
|
{checked && (
|
||||||
|
<svg width="10" height="8" viewBox="0 0 10 8" fill="none">
|
||||||
|
<path
|
||||||
|
d="M1 4L3.5 6.5L9 1"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<span className="font-serif text-[14px] text-[#00293D]">{label}</span>
|
||||||
|
</label>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const frequencyOptions: { value: DigestFrequency; label: string }[] = [
|
||||||
|
{ value: 'instant', label: 'Instant' },
|
||||||
|
{ value: 'daily', label: 'Daily' },
|
||||||
|
{ value: 'weekly', label: 'Weekly' },
|
||||||
|
{ value: 'off', label: 'Off' },
|
||||||
|
];
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
@@ -219,84 +214,103 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Email Notifications */}
|
{/* Notification Preferences Card */}
|
||||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white p-6 mb-4">
|
<div className="border border-[#00293d]/10 rounded-[15px] bg-white p-6">
|
||||||
<div className="mb-6">
|
{/* Title */}
|
||||||
<h2 className="font-fractul font-bold text-[16px] text-[#00293D] mb-2">
|
<h2 className="font-fractul font-bold text-[20px] text-[#00293D] mb-4">
|
||||||
Email Notifications
|
Notification Preferences
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-serif text-[12px] text-[#00293D]/60">
|
|
||||||
Manage your email notification preferences
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
{/* Divider */}
|
||||||
{emailNotifications.map((item) => (
|
<div className="border-b border-[#00293D]/10 mb-6" />
|
||||||
<div
|
|
||||||
key={item.id}
|
{/* Notification Categories */}
|
||||||
className="flex items-center justify-between py-3 border-b border-[#00293d]/10 last:border-0"
|
<div className="space-y-0">
|
||||||
>
|
{notifications.map((item, index) => (
|
||||||
<div>
|
<div key={item.id}>
|
||||||
<h3 className="font-serif font-medium text-[14px] text-[#00293D]">
|
<div className="flex items-start justify-between py-4">
|
||||||
{item.label}
|
<div className="flex-1 pr-4">
|
||||||
</h3>
|
<h3 className="font-fractul font-bold text-[14px] text-[#00293D] mb-1">
|
||||||
<p className="font-serif text-[12px] text-[#00293D]/60">
|
{item.label}
|
||||||
{item.description}
|
</h3>
|
||||||
</p>
|
<p className="font-serif text-[14px] text-[#00293D]/70">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-8 flex-shrink-0">
|
||||||
|
<Checkbox
|
||||||
|
checked={item.email}
|
||||||
|
onToggle={() => toggleNotification(item.id, 'email')}
|
||||||
|
label="Email"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
checked={item.inApp}
|
||||||
|
onToggle={() => toggleNotification(item.id, 'inApp')}
|
||||||
|
label="In-App"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ToggleSwitch
|
{index < notifications.length - 1 && (
|
||||||
enabled={item.enabled}
|
<div className="border-b border-[#00293D]/10" />
|
||||||
onToggle={() => toggleEmailNotification(item.id)}
|
)}
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Push Notifications */}
|
{/* Divider before Email Digest */}
|
||||||
<div className="border border-[#00293d]/10 rounded-[15px] bg-white p-6 mb-4">
|
<div className="border-b border-[#00293D]/10 mt-2 mb-6" />
|
||||||
<div className="mb-6">
|
|
||||||
<h2 className="font-fractul font-bold text-[16px] text-[#00293D] mb-2">
|
{/* Email Digest Frequency */}
|
||||||
Push Notifications
|
<div className="mb-8">
|
||||||
</h2>
|
<h3 className="font-fractul font-bold text-[14px] text-[#00293D] mb-1">
|
||||||
<p className="font-serif text-[12px] text-[#00293D]/60">
|
Email Digest Frequency
|
||||||
Manage your push notification preferences
|
</h3>
|
||||||
|
<p className="font-serif text-[14px] text-[#00293D]/70 mb-4">
|
||||||
|
Choose how often you receive non-urgent email summaries
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Frequency Tabs */}
|
||||||
|
<div className="inline-flex border border-[#00293D]/10 rounded-[7px] overflow-hidden">
|
||||||
|
{frequencyOptions.map((option, index) => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setDigestFrequency(option.value)}
|
||||||
|
className={`px-5 py-2 text-[14px] font-fractul transition-colors ${
|
||||||
|
digestFrequency === option.value
|
||||||
|
? 'bg-[#e58625] text-[#00293D] font-medium'
|
||||||
|
: 'bg-white text-[#00293D] hover:bg-[#00293D]/5'
|
||||||
|
} ${
|
||||||
|
index < frequencyOptions.length - 1
|
||||||
|
? 'border-r border-[#00293D]/10'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
{/* Action Buttons */}
|
||||||
{pushNotifications.map((item) => (
|
<div className="flex justify-end gap-4 pt-4 border-t border-[#00293D]/10">
|
||||||
<div
|
<button
|
||||||
key={item.id}
|
type="button"
|
||||||
className="flex items-center justify-between py-3 border-b border-[#00293d]/10 last:border-0"
|
onClick={handleCancel}
|
||||||
>
|
className="px-10 py-3 border border-[#00293D]/10 rounded-[15px] font-fractul font-bold text-[14px] text-[#00293D] hover:bg-[#00293D]/5 transition-colors"
|
||||||
<div>
|
>
|
||||||
<h3 className="font-serif font-medium text-[14px] text-[#00293D]">
|
Cancel
|
||||||
{item.label}
|
</button>
|
||||||
</h3>
|
<button
|
||||||
<p className="font-serif text-[12px] text-[#00293D]/60">
|
type="button"
|
||||||
{item.description}
|
onClick={handleSave}
|
||||||
</p>
|
disabled={isSaving}
|
||||||
</div>
|
className="px-10 py-3 bg-[#e58625] rounded-[15px] font-fractul font-bold text-[14px] text-[#00293D] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
<ToggleSwitch
|
>
|
||||||
enabled={item.enabled}
|
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||||
onToggle={() => togglePushNotification(item.id)}
|
</button>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Save Button */}
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<button
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={isSaving}
|
|
||||||
className="px-8 py-3 bg-[#e58625] text-white rounded-[15px] font-fractul font-medium text-[14px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,18 +67,15 @@ export function SettingsSidebar({
|
|||||||
setProfileData({ name, title, avatarUrl });
|
setProfileData({ name, title, avatarUrl });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch profile for sidebar:', err);
|
console.error('Failed to fetch profile for sidebar:', err);
|
||||||
// Keep session data as fallback
|
|
||||||
}
|
}
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
// Fetch profile on mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session) {
|
if (session) {
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
}
|
}
|
||||||
}, [session, fetchProfile]);
|
}, [session, fetchProfile]);
|
||||||
|
|
||||||
// Listen for profile update events to refresh the sidebar
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleProfileUpdate = () => {
|
const handleProfileUpdate = () => {
|
||||||
if (session) {
|
if (session) {
|
||||||
@@ -92,6 +89,8 @@ export function SettingsSidebar({
|
|||||||
};
|
};
|
||||||
}, [session, fetchProfile]);
|
}, [session, fetchProfile]);
|
||||||
|
|
||||||
|
const isAgent = basePath === '/agent/settings';
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Profile Settings',
|
label: 'Profile Settings',
|
||||||
@@ -99,7 +98,7 @@ export function SettingsSidebar({
|
|||||||
icon: '/assets/icons/settings-profile-icon.svg',
|
icon: '/assets/icons/settings-profile-icon.svg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Password Security',
|
label: 'Change Password',
|
||||||
href: `${basePath}/password`,
|
href: `${basePath}/password`,
|
||||||
icon: '/assets/icons/settings-lock-icon.svg',
|
icon: '/assets/icons/settings-lock-icon.svg',
|
||||||
},
|
},
|
||||||
@@ -113,10 +112,24 @@ export function SettingsSidebar({
|
|||||||
href: `${basePath}/privacy`,
|
href: `${basePath}/privacy`,
|
||||||
icon: '/assets/icons/settings-privacy-icon.svg',
|
icon: '/assets/icons/settings-privacy-icon.svg',
|
||||||
},
|
},
|
||||||
|
...(isAgent
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'Billings & Payments',
|
||||||
|
href: `${basePath}/billings`,
|
||||||
|
icon: '/assets/icons/settings-billing-icon.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Add Testimonials',
|
||||||
|
href: `${basePath}/testimonials`,
|
||||||
|
icon: '/assets/icons/settings-testimonials-icon.svg',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full lg:w-[300px] space-y-4">
|
<div className="w-full lg:w-[300px] space-y-4 flex-shrink-0">
|
||||||
{/* Profile Card */}
|
{/* Profile Card */}
|
||||||
<div className="bg-white rounded-[15px] border border-[#00293D]/20 p-5">
|
<div className="bg-white rounded-[15px] border border-[#00293D]/20 p-5">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
@@ -148,12 +161,12 @@ export function SettingsSidebar({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Categories Card */}
|
{/* Settings Navigation Card */}
|
||||||
<div className="bg-white rounded-[15px] border border-[#00293D]/20 overflow-hidden">
|
<div className="bg-white rounded-[15px] border border-[#00293D]/20 overflow-hidden">
|
||||||
{/* Categories Header */}
|
{/* Settings Header */}
|
||||||
<div className="flex items-center justify-between px-5 py-4 border-b border-[#00293D]/10">
|
<div className="flex items-center justify-between px-5 py-4 border-b border-[#00293D]/10">
|
||||||
<h4 className="font-fractul font-bold text-[16px] leading-[19px] text-[#00293D]">
|
<h4 className="font-fractul font-bold text-[16px] leading-[19px] text-[#00293D]">
|
||||||
Categories
|
Settings
|
||||||
</h4>
|
</h4>
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/chevron-right-icon.svg"
|
src="/assets/icons/chevron-right-icon.svg"
|
||||||
|
|||||||
167
src/components/settings/SubscriptionForm.tsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
interface PlanFeature {
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLAN_FEATURES: PlanFeature[] = [
|
||||||
|
{ icon: '/assets/icons/gem-icon.svg', label: 'Agent -Co-Marketing' },
|
||||||
|
{ icon: '/assets/icons/tick-circle-icon.svg', label: 'Priority 24/7 Support' },
|
||||||
|
{ icon: '/assets/icons/tick-circle-icon.svg', label: 'Whitelabel Client Portals' },
|
||||||
|
{ icon: '/assets/icons/tick-circle-icon.svg', label: 'Advanced CRM Tools & Analytics' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SubscriptionForm() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Page Title */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h1 className="font-fractul font-bold text-[20px] text-[#00293D] mb-2">
|
||||||
|
Subscription & Payments
|
||||||
|
</h1>
|
||||||
|
<p className="font-serif text-[14px] text-[#00293D]/70">
|
||||||
|
Manage Your billing cycles , upgrade your plan , or boost individual listings
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Annual Membership Plans */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="font-fractul font-bold text-[20px] text-[#00293D] mb-4">
|
||||||
|
Annual Membership Plans
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Plan Card */}
|
||||||
|
<div className="border border-[#00293D] rounded-[10px] p-8 relative overflow-hidden">
|
||||||
|
{/* Best Value Ribbon */}
|
||||||
|
<div className="absolute top-0 right-0 w-[100px] h-[100px] overflow-hidden">
|
||||||
|
<div className="absolute top-[18px] right-[-28px] bg-[#e58625] text-white text-[12px] font-fractul py-1 px-8 rotate-45 transform origin-center shadow-sm">
|
||||||
|
Best Value
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col lg:flex-row gap-8">
|
||||||
|
{/* Left Side - Plan Details */}
|
||||||
|
<div className="flex-1">
|
||||||
|
{/* Annual Subscription Badge */}
|
||||||
|
<div className="inline-block bg-[#e58625] rounded-[15px] px-4 py-2 mb-6">
|
||||||
|
<span className="font-serif text-[14px] text-white">
|
||||||
|
Annual Subscription
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Plan Name */}
|
||||||
|
<h3 className="font-fractul font-bold text-[20px] text-[#00293D] mb-3">
|
||||||
|
Professional Annual Plan
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Plan Description */}
|
||||||
|
<p className="font-serif text-[14px] text-[#00293D] mb-6 max-w-[230px] leading-[22px]">
|
||||||
|
Complete solutions for real estate agents and lenders to grow their business.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<span className="font-fractul font-bold text-[35px] text-black">$499</span>
|
||||||
|
<span className="font-fractul font-bold text-[20px] text-black"> / </span>
|
||||||
|
<span className="font-fractul font-bold text-[20px] text-black/50">Year</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Button */}
|
||||||
|
<button className="bg-[#e58625] border border-[#e58625] text-white font-fractul font-bold text-[14px] px-10 py-4 rounded-[15px] hover:bg-[#d47920] transition-colors">
|
||||||
|
Get Premium Access
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side - Features + Illustration */}
|
||||||
|
<div className="flex-1 flex flex-col lg:flex-row gap-6">
|
||||||
|
{/* Features */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h4 className="font-fractul font-bold text-[20px] text-[#00293D] mb-6">
|
||||||
|
Include Features
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
{PLAN_FEATURES.map((feature) => (
|
||||||
|
<div key={feature.label} className="flex items-center gap-3">
|
||||||
|
<Image
|
||||||
|
src={feature.icon}
|
||||||
|
alt=""
|
||||||
|
width={21}
|
||||||
|
height={21}
|
||||||
|
className="flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<span className="font-serif text-[14px] text-black">
|
||||||
|
{feature.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Illustration */}
|
||||||
|
<div className="hidden lg:flex items-end justify-end">
|
||||||
|
<div className="w-[165px] h-[165px] rounded-full overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="/assets/images/subscription-plan-illustration.png"
|
||||||
|
alt="Professional plan"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Need Help Section */}
|
||||||
|
<div className="border border-[#00293D]/70 rounded-[15px] p-8 flex flex-col lg:flex-row items-center gap-8">
|
||||||
|
{/* Left Side - Help Content */}
|
||||||
|
<div className="flex-1 text-center lg:text-left">
|
||||||
|
{/* Question Icon */}
|
||||||
|
<div className="flex justify-center lg:justify-start mb-3">
|
||||||
|
<Image
|
||||||
|
src="/assets/icons/question-circle-icon.svg"
|
||||||
|
alt="Help"
|
||||||
|
width={35}
|
||||||
|
height={35}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h3 className="font-fractul font-bold text-[20px] text-[#00293D] mb-3">
|
||||||
|
Need Help ?
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="font-serif text-[14px] text-[#00293D] mb-4 max-w-[257px] mx-auto lg:mx-0 leading-[22px]">
|
||||||
|
Our billing experts are here to help you with any questions about your plan.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Support Email */}
|
||||||
|
<Link
|
||||||
|
href="mailto:support@example.com"
|
||||||
|
className="font-serif font-bold text-[14px] text-[#e58625] underline hover:text-[#d47920] transition-colors"
|
||||||
|
>
|
||||||
|
support@example.com
|
||||||
|
<span className="inline-block ml-1 rotate-45">↗</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side - Illustration */}
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<div className="w-[261px] h-[261px] rounded-[57px] overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="/assets/images/need-help-illustration.png"
|
||||||
|
alt="Need help illustration"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,3 +5,4 @@ export { PasswordSecurityForm } from './PasswordSecurityForm';
|
|||||||
export { NotificationsForm } from './NotificationsForm';
|
export { NotificationsForm } from './NotificationsForm';
|
||||||
export { PrivacyForm } from './PrivacyForm';
|
export { PrivacyForm } from './PrivacyForm';
|
||||||
export { TwoFactorSettings } from './TwoFactorSettings';
|
export { TwoFactorSettings } from './TwoFactorSettings';
|
||||||
|
export { SubscriptionForm } from './SubscriptionForm';
|
||||||
|
|||||||