updates
This commit is contained in:
@@ -14,6 +14,7 @@ import { CompanySettingsProvider } from './contexts/CompanySettingsContext';
|
||||
import { AuthModalProvider } from './contexts/AuthModalContext';
|
||||
import OfflineIndicator from './components/common/OfflineIndicator';
|
||||
import CookieConsentBanner from './components/common/CookieConsentBanner';
|
||||
import CookiePreferencesModal from './components/common/CookiePreferencesModal';
|
||||
import AnalyticsLoader from './components/common/AnalyticsLoader';
|
||||
import Loading from './components/common/Loading';
|
||||
import ScrollToTop from './components/common/ScrollToTop';
|
||||
@@ -53,6 +54,12 @@ const InvoicePage = lazy(() => import('./pages/customer/InvoicePage'));
|
||||
const ProfilePage = lazy(() => import('./pages/customer/ProfilePage'));
|
||||
const AboutPage = lazy(() => import('./pages/AboutPage'));
|
||||
const ContactPage = lazy(() => import('./pages/ContactPage'));
|
||||
const PrivacyPolicyPage = lazy(() => import('./pages/PrivacyPolicyPage'));
|
||||
const TermsPage = lazy(() => import('./pages/TermsPage'));
|
||||
const RefundsPolicyPage = lazy(() => import('./pages/RefundsPolicyPage'));
|
||||
const CancellationPolicyPage = lazy(() => import('./pages/CancellationPolicyPage'));
|
||||
const AccessibilityPage = lazy(() => import('./pages/AccessibilityPage'));
|
||||
const FAQPage = lazy(() => import('./pages/FAQPage'));
|
||||
|
||||
const AdminDashboardPage = lazy(() => import('./pages/admin/DashboardPage'));
|
||||
const InvoiceManagementPage = lazy(() => import('./pages/admin/InvoiceManagementPage'));
|
||||
@@ -188,6 +195,30 @@ function App() {
|
||||
path="contact"
|
||||
element={<ContactPage />}
|
||||
/>
|
||||
<Route
|
||||
path="privacy"
|
||||
element={<PrivacyPolicyPage />}
|
||||
/>
|
||||
<Route
|
||||
path="terms"
|
||||
element={<TermsPage />}
|
||||
/>
|
||||
<Route
|
||||
path="refunds"
|
||||
element={<RefundsPolicyPage />}
|
||||
/>
|
||||
<Route
|
||||
path="cancellation"
|
||||
element={<CancellationPolicyPage />}
|
||||
/>
|
||||
<Route
|
||||
path="accessibility"
|
||||
element={<AccessibilityPage />}
|
||||
/>
|
||||
<Route
|
||||
path="faq"
|
||||
element={<FAQPage />}
|
||||
/>
|
||||
|
||||
{}
|
||||
<Route
|
||||
@@ -381,6 +412,7 @@ function App() {
|
||||
/>
|
||||
<OfflineIndicator />
|
||||
<CookieConsentBanner />
|
||||
<CookiePreferencesModal />
|
||||
<AnalyticsLoader />
|
||||
<AuthModalManager />
|
||||
</Suspense>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useCookieConsent } from '../../contexts/CookieConsentContext';
|
||||
|
||||
const CookiePreferencesLink: React.FC = () => {
|
||||
const { hasDecided } = useCookieConsent();
|
||||
|
||||
if (!hasDecided) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
|
||||
window.dispatchEvent(new CustomEvent('open-cookie-preferences'));
|
||||
};
|
||||
|
||||
@@ -17,9 +9,9 @@ const CookiePreferencesLink: React.FC = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className="text-xs font-medium text-gray-500 underline hover:text-gray-700"
|
||||
className="hover:text-[#d4af37] transition-colors cursor-pointer font-light tracking-wide text-gray-700"
|
||||
>
|
||||
Cookie preferences
|
||||
Cookie Preferences
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
223
Frontend/src/components/common/CookiePreferencesModal.tsx
Normal file
223
Frontend/src/components/common/CookiePreferencesModal.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import { useCookieConsent } from '../../contexts/CookieConsentContext';
|
||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||
|
||||
const CookiePreferencesModal: React.FC = () => {
|
||||
const { consent, updateConsent } = useCookieConsent();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [analyticsChecked, setAnalyticsChecked] = useState(false);
|
||||
const [marketingChecked, setMarketingChecked] = useState(false);
|
||||
const [preferencesChecked, setPreferencesChecked] = useState(false);
|
||||
const modalRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
useClickOutside(modalRef, () => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (consent) {
|
||||
setAnalyticsChecked(consent.categories.analytics);
|
||||
setMarketingChecked(consent.categories.marketing);
|
||||
setPreferencesChecked(consent.categories.preferences);
|
||||
}
|
||||
}, [consent]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOpenPreferences = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
window.addEventListener('open-cookie-preferences', handleOpenPreferences);
|
||||
return () => {
|
||||
window.removeEventListener('open-cookie-preferences', handleOpenPreferences);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleAcceptAll = async () => {
|
||||
await updateConsent({
|
||||
analytics: true,
|
||||
marketing: true,
|
||||
preferences: true,
|
||||
});
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleRejectNonEssential = async () => {
|
||||
await updateConsent({
|
||||
analytics: false,
|
||||
marketing: false,
|
||||
preferences: false,
|
||||
});
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleSaveSelection = async () => {
|
||||
await updateConsent({
|
||||
analytics: analyticsChecked,
|
||||
marketing: marketingChecked,
|
||||
preferences: preferencesChecked,
|
||||
});
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/70 backdrop-blur-sm transition-opacity"
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="relative transform overflow-hidden rounded-2xl bg-gradient-to-br from-zinc-950/95 via-zinc-900/95 to-black/95 text-left shadow-2xl border border-[#d4af37]/30 transition-all sm:my-8 sm:w-full sm:max-w-2xl"
|
||||
>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="absolute right-4 top-4 z-10 rounded-full p-2 text-gray-400 hover:bg-zinc-800/50 hover:text-white transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div className="px-6 py-6 sm:px-8 sm:py-8">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-black/60 px-3 py-1 text-[11px] font-medium uppercase tracking-[0.16em] text-[#d4af37]/90 ring-1 ring-[#d4af37]/30 mb-4">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-[#d4af37]" />
|
||||
Privacy Suite
|
||||
</div>
|
||||
<h2 className="text-2xl font-elegant font-bold text-white mb-2">
|
||||
Cookie Preferences
|
||||
</h2>
|
||||
<p className="text-sm text-zinc-300">
|
||||
Manage your cookie preferences. You can enable or disable different types of cookies below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Cookie Categories */}
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="rounded-xl bg-black/40 p-4 ring-1 ring-zinc-800/80 backdrop-blur-md">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="mt-0.5 h-5 w-5 rounded border border-[#d4af37]/50 bg-[#d4af37]/20 flex items-center justify-center">
|
||||
<span className="text-[#d4af37] text-xs">✓</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-zinc-50 mb-1">Strictly necessary</p>
|
||||
<p className="text-xs text-zinc-400">
|
||||
Essential for security, authentication, and core booking flows. These are always enabled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl bg-black/40 p-4 ring-1 ring-zinc-800/80 backdrop-blur-md">
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
id="cookie-analytics-modal"
|
||||
type="checkbox"
|
||||
className="mt-0.5 h-5 w-5 rounded border-zinc-500 bg-black/40 text-[#d4af37] focus:ring-[#d4af37] cursor-pointer"
|
||||
checked={analyticsChecked}
|
||||
onChange={(e) => setAnalyticsChecked(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="cookie-analytics-modal" className="flex-1 cursor-pointer">
|
||||
<p className="font-semibold text-zinc-50 mb-1">Analytics</p>
|
||||
<p className="text-xs text-zinc-400">
|
||||
Anonymous insights that help us refine performance and guest experience throughout the site.
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl bg-black/40 p-4 ring-1 ring-zinc-800/80 backdrop-blur-md">
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
id="cookie-marketing-modal"
|
||||
type="checkbox"
|
||||
className="mt-0.5 h-5 w-5 rounded border-zinc-500 bg-black/40 text-[#d4af37] focus:ring-[#d4af37] cursor-pointer"
|
||||
checked={marketingChecked}
|
||||
onChange={(e) => setMarketingChecked(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="cookie-marketing-modal" className="flex-1 cursor-pointer">
|
||||
<p className="font-semibold text-zinc-50 mb-1">Tailored offers</p>
|
||||
<p className="text-xs text-zinc-400">
|
||||
Allow us to present bespoke promotions and experiences aligned with your interests.
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl bg-black/40 p-4 ring-1 ring-zinc-800/80 backdrop-blur-md">
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
id="cookie-preferences-modal"
|
||||
type="checkbox"
|
||||
className="mt-0.5 h-5 w-5 rounded border-zinc-500 bg-black/40 text-[#d4af37] focus:ring-[#d4af37] cursor-pointer"
|
||||
checked={preferencesChecked}
|
||||
onChange={(e) => setPreferencesChecked(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="cookie-preferences-modal" className="flex-1 cursor-pointer">
|
||||
<p className="font-semibold text-zinc-50 mb-1">Comfort preferences</p>
|
||||
<p className="text-xs text-zinc-400">
|
||||
Remember your choices such as language, currency, and layout for a smoother return visit.
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 pt-4 border-t border-zinc-800/50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAcceptAll}
|
||||
className="flex-1 inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#d4af37] via-[#f2cf74] to-[#d4af37] px-6 py-3 text-sm font-semibold uppercase tracking-[0.16em] text-black shadow-lg transition hover:from-[#f8e4a6] hover:via-[#ffe6a3] hover:to-[#f2cf74] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#d4af37]/70"
|
||||
>
|
||||
Accept all
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRejectNonEssential}
|
||||
className="flex-1 inline-flex items-center justify-center rounded-full border border-zinc-600/80 bg-black/40 px-6 py-3 text-sm font-semibold uppercase tracking-[0.16em] text-zinc-100 shadow-lg transition hover:border-zinc-400 hover:bg-black/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-500/70"
|
||||
>
|
||||
Essential only
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveSelection}
|
||||
className="flex-1 inline-flex items-center justify-center rounded-full border border-[#d4af37]/60 bg-zinc-900/80 px-6 py-3 text-sm font-semibold uppercase tracking-[0.16em] text-[#d4af37] shadow-lg transition hover:border-[#d4af37] hover:bg-zinc-800/80 hover:text-[#f5e9c6] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#d4af37]/70"
|
||||
>
|
||||
Save selection
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CookiePreferencesModal;
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useCompanySettings } from '../../contexts/CompanySettingsContext';
|
||||
const Footer: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [enabledPages, setEnabledPages] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
@@ -44,11 +45,41 @@ const Footer: React.FC = () => {
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching footer content:', err);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const checkEnabledPages = async () => {
|
||||
const enabled = new Set<string>();
|
||||
const policyPages = [
|
||||
{ type: 'privacy', url: '/privacy', service: () => pageContentService.getPrivacyContent() },
|
||||
{ type: 'terms', url: '/terms', service: () => pageContentService.getTermsContent() },
|
||||
{ type: 'refunds', url: '/refunds', service: () => pageContentService.getRefundsContent() },
|
||||
{ type: 'cancellation', url: '/cancellation', service: () => pageContentService.getCancellationContent() },
|
||||
{ type: 'accessibility', url: '/accessibility', service: () => pageContentService.getAccessibilityContent() },
|
||||
{ type: 'faq', url: '/faq', service: () => pageContentService.getFAQContent() },
|
||||
];
|
||||
|
||||
await Promise.all(
|
||||
policyPages.map(async (page) => {
|
||||
try {
|
||||
const response = await page.service();
|
||||
if (response.status === 'success' && response.data?.page_content?.is_active) {
|
||||
enabled.add(page.url);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// If 404, page is disabled, don't add to enabled set
|
||||
if (err.response?.status !== 404) {
|
||||
console.error(`Error checking ${page.type} page:`, err);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
setEnabledPages(enabled);
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
checkEnabledPages();
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -97,6 +128,9 @@ const Footer: React.FC = () => {
|
||||
{ label: 'FAQ', url: '/faq' },
|
||||
{ label: 'Terms of Service', url: '/terms' },
|
||||
{ label: 'Privacy Policy', url: '/privacy' },
|
||||
{ label: 'Refunds Policy', url: '/refunds' },
|
||||
{ label: 'Cancellation Policy', url: '/cancellation' },
|
||||
{ label: 'Accessibility', url: '/accessibility' },
|
||||
{ label: 'Contact Us', url: '/contact' }
|
||||
];
|
||||
|
||||
@@ -104,10 +138,18 @@ const Footer: React.FC = () => {
|
||||
? pageContent.footer_links.quick_links
|
||||
: defaultQuickLinks;
|
||||
|
||||
const supportLinks = pageContent?.footer_links?.support_links && pageContent.footer_links.support_links.length > 0
|
||||
const allSupportLinks = pageContent?.footer_links?.support_links && pageContent.footer_links.support_links.length > 0
|
||||
? pageContent.footer_links.support_links
|
||||
: defaultSupportLinks;
|
||||
|
||||
// Filter support links to only show enabled policy pages
|
||||
const supportLinks = allSupportLinks.filter((link) => {
|
||||
// Always show Contact Us
|
||||
if (link.url === '/contact') return true;
|
||||
// Only show policy pages if they are enabled
|
||||
return enabledPages.has(link.url);
|
||||
});
|
||||
|
||||
return (
|
||||
<footer className="relative bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-black text-gray-300 overflow-hidden">
|
||||
{/* Top Gold Accent Line */}
|
||||
@@ -370,9 +412,11 @@ const Footer: React.FC = () => {
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 sm:space-x-6 text-xs sm:text-sm text-gray-600">
|
||||
<span className="hover:text-[#d4af37] transition-colors cursor-pointer font-light tracking-wide">Privacy</span>
|
||||
<Link to="/privacy" className="hover:text-[#d4af37] transition-colors cursor-pointer font-light tracking-wide">Privacy</Link>
|
||||
<span className="text-gray-700">•</span>
|
||||
<span className="hover:text-[#d4af37] transition-colors cursor-pointer font-light tracking-wide">Terms</span>
|
||||
<Link to="/terms" className="hover:text-[#d4af37] transition-colors cursor-pointer font-light tracking-wide">Terms</Link>
|
||||
<span className="text-gray-700">•</span>
|
||||
<Link to="/refunds" className="hover:text-[#d4af37] transition-colors cursor-pointer font-light tracking-wide">Refunds</Link>
|
||||
<span className="text-gray-700">•</span>
|
||||
<CookiePreferencesLink />
|
||||
</div>
|
||||
|
||||
178
Frontend/src/pages/AccessibilityPage.tsx
Normal file
178
Frontend/src/pages/AccessibilityPage.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Accessibility, ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pageContentService } from '../services/api';
|
||||
import type { PageContent } from '../services/api/pageContentService';
|
||||
import { useCompanySettings } from '../contexts/CompanySettingsContext';
|
||||
import Loading from '../components/common/Loading';
|
||||
|
||||
const AccessibilityPage: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await pageContentService.getAccessibilityContent();
|
||||
if (response.status === 'success' && response.data?.page_content) {
|
||||
const content = response.data.page_content;
|
||||
|
||||
if (content.content) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.content;
|
||||
|
||||
const allElements = tempDiv.querySelectorAll('*');
|
||||
allElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const tagName = htmlEl.tagName.toLowerCase();
|
||||
const currentColor = htmlEl.style.color;
|
||||
|
||||
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
|
||||
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||
htmlEl.style.color = '#ffffff';
|
||||
} else if (['strong', 'b'].includes(tagName)) {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else if (tagName === 'a') {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else {
|
||||
htmlEl.style.color = '#d1d5db';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.content = tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
setPageContent(content);
|
||||
|
||||
if (content.meta_title) {
|
||||
document.title = content.meta_title;
|
||||
}
|
||||
if (content.meta_description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', content.meta_description);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching page content:', err);
|
||||
// If page is disabled (404), set pageContent to null to show disabled message
|
||||
if (err.response?.status === 404) {
|
||||
setPageContent(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!pageContent) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Accessibility className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
|
||||
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Accessibility</h1>
|
||||
<p className="text-gray-400">This page is currently unavailable.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
<div className="mb-8 sm:mb-12 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
|
||||
<Accessibility className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
|
||||
{pageContent.title || 'Accessibility'}
|
||||
</h1>
|
||||
{pageContent.subtitle && (
|
||||
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
|
||||
{pageContent.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none text-gray-300
|
||||
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
|
||||
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
|
||||
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
|
||||
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
|
||||
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
|
||||
prose-strong:text-[#d4af37] prose-strong:font-medium
|
||||
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
|
||||
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
|
||||
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
|
||||
style={{ color: '#d1d5db' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{settings.company_email && (
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-sm text-gray-400 font-light">
|
||||
For accessibility inquiries, contact us at{' '}
|
||||
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
|
||||
{settings.company_email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessibilityPage;
|
||||
|
||||
179
Frontend/src/pages/CancellationPolicyPage.tsx
Normal file
179
Frontend/src/pages/CancellationPolicyPage.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { XCircle, ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pageContentService } from '../services/api';
|
||||
import type { PageContent } from '../services/api/pageContentService';
|
||||
import { useCompanySettings } from '../contexts/CompanySettingsContext';
|
||||
import Loading from '../components/common/Loading';
|
||||
|
||||
const CancellationPolicyPage: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await pageContentService.getCancellationContent();
|
||||
if (response.status === 'success' && response.data?.page_content) {
|
||||
const content = response.data.page_content;
|
||||
|
||||
// Process HTML content to ensure text is visible
|
||||
if (content.content) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.content;
|
||||
|
||||
const allElements = tempDiv.querySelectorAll('*');
|
||||
allElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const tagName = htmlEl.tagName.toLowerCase();
|
||||
const currentColor = htmlEl.style.color;
|
||||
|
||||
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
|
||||
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||
htmlEl.style.color = '#ffffff';
|
||||
} else if (['strong', 'b'].includes(tagName)) {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else if (tagName === 'a') {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else {
|
||||
htmlEl.style.color = '#d1d5db';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.content = tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
setPageContent(content);
|
||||
|
||||
if (content.meta_title) {
|
||||
document.title = content.meta_title;
|
||||
}
|
||||
if (content.meta_description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', content.meta_description);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching page content:', err);
|
||||
// If page is disabled (404), set pageContent to null to show disabled message
|
||||
if (err.response?.status === 404) {
|
||||
setPageContent(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!pageContent) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<XCircle className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
|
||||
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Cancellation Policy</h1>
|
||||
<p className="text-gray-400">This page is currently unavailable.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
<div className="mb-8 sm:mb-12 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
|
||||
<XCircle className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
|
||||
{pageContent.title || 'Cancellation Policy'}
|
||||
</h1>
|
||||
{pageContent.subtitle && (
|
||||
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
|
||||
{pageContent.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none text-gray-300
|
||||
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
|
||||
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
|
||||
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
|
||||
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
|
||||
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
|
||||
prose-strong:text-[#d4af37] prose-strong:font-medium
|
||||
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
|
||||
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
|
||||
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
|
||||
style={{ color: '#d1d5db' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{settings.company_email && (
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-sm text-gray-400 font-light">
|
||||
For questions about cancellations, contact us at{' '}
|
||||
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
|
||||
{settings.company_email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CancellationPolicyPage;
|
||||
|
||||
178
Frontend/src/pages/FAQPage.tsx
Normal file
178
Frontend/src/pages/FAQPage.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { HelpCircle, ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pageContentService } from '../services/api';
|
||||
import type { PageContent } from '../services/api/pageContentService';
|
||||
import { useCompanySettings } from '../contexts/CompanySettingsContext';
|
||||
import Loading from '../components/common/Loading';
|
||||
|
||||
const FAQPage: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await pageContentService.getFAQContent();
|
||||
if (response.status === 'success' && response.data?.page_content) {
|
||||
const content = response.data.page_content;
|
||||
|
||||
if (content.content) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.content;
|
||||
|
||||
const allElements = tempDiv.querySelectorAll('*');
|
||||
allElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const tagName = htmlEl.tagName.toLowerCase();
|
||||
const currentColor = htmlEl.style.color;
|
||||
|
||||
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
|
||||
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||
htmlEl.style.color = '#ffffff';
|
||||
} else if (['strong', 'b'].includes(tagName)) {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else if (tagName === 'a') {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else {
|
||||
htmlEl.style.color = '#d1d5db';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.content = tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
setPageContent(content);
|
||||
|
||||
if (content.meta_title) {
|
||||
document.title = content.meta_title;
|
||||
}
|
||||
if (content.meta_description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', content.meta_description);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching page content:', err);
|
||||
// If page is disabled (404), set pageContent to null to show disabled message
|
||||
if (err.response?.status === 404) {
|
||||
setPageContent(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!pageContent) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<HelpCircle className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
|
||||
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Frequently Asked Questions</h1>
|
||||
<p className="text-gray-400">This page is currently unavailable.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
<div className="mb-8 sm:mb-12 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
|
||||
<HelpCircle className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
|
||||
{pageContent.title || 'Frequently Asked Questions'}
|
||||
</h1>
|
||||
{pageContent.subtitle && (
|
||||
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
|
||||
{pageContent.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none text-gray-300
|
||||
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
|
||||
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
|
||||
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
|
||||
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
|
||||
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
|
||||
prose-strong:text-[#d4af37] prose-strong:font-medium
|
||||
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
|
||||
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
|
||||
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
|
||||
style={{ color: '#d1d5db' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{settings.company_email && (
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-sm text-gray-400 font-light">
|
||||
Still have questions? Contact us at{' '}
|
||||
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
|
||||
{settings.company_email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FAQPage;
|
||||
|
||||
187
Frontend/src/pages/PrivacyPolicyPage.tsx
Normal file
187
Frontend/src/pages/PrivacyPolicyPage.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Shield, ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pageContentService } from '../services/api';
|
||||
import type { PageContent } from '../services/api/pageContentService';
|
||||
import { useCompanySettings } from '../contexts/CompanySettingsContext';
|
||||
import Loading from '../components/common/Loading';
|
||||
|
||||
const PrivacyPolicyPage: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await pageContentService.getPrivacyContent();
|
||||
if (response.status === 'success' && response.data?.page_content) {
|
||||
const content = response.data.page_content;
|
||||
|
||||
// Process HTML content to ensure text is visible
|
||||
if (content.content) {
|
||||
// Create a temporary div to parse HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.content;
|
||||
|
||||
// Add color styles to elements that don't have them
|
||||
const allElements = tempDiv.querySelectorAll('*');
|
||||
allElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const tagName = htmlEl.tagName.toLowerCase();
|
||||
const currentColor = htmlEl.style.color;
|
||||
|
||||
// Only add color if not already set
|
||||
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
|
||||
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||
htmlEl.style.color = '#ffffff';
|
||||
} else if (['strong', 'b'].includes(tagName)) {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else if (tagName === 'a') {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else {
|
||||
htmlEl.style.color = '#d1d5db';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update content with processed HTML
|
||||
content.content = tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
setPageContent(content);
|
||||
|
||||
if (content.meta_title) {
|
||||
document.title = content.meta_title;
|
||||
}
|
||||
if (content.meta_description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', content.meta_description);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching page content:', err);
|
||||
// If page is disabled (404), set pageContent to null to show disabled message
|
||||
if (err.response?.status === 404) {
|
||||
setPageContent(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!pageContent) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Shield className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
|
||||
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Privacy Policy</h1>
|
||||
<p className="text-gray-400">This page is currently unavailable.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
|
||||
{/* Back Link */}
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8 sm:mb-12 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
|
||||
<Shield className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
|
||||
{pageContent.title || 'Privacy Policy'}
|
||||
</h1>
|
||||
{pageContent.subtitle && (
|
||||
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
|
||||
{pageContent.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none text-gray-300
|
||||
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
|
||||
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
|
||||
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
|
||||
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
|
||||
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
|
||||
prose-strong:text-[#d4af37] prose-strong:font-medium
|
||||
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
|
||||
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
|
||||
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
|
||||
style={{ color: '#d1d5db' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Footer Note */}
|
||||
{settings.company_email && (
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-sm text-gray-400 font-light">
|
||||
For questions about this policy, contact us at{' '}
|
||||
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
|
||||
{settings.company_email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivacyPolicyPage;
|
||||
|
||||
187
Frontend/src/pages/RefundsPolicyPage.tsx
Normal file
187
Frontend/src/pages/RefundsPolicyPage.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { RefreshCw, ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pageContentService } from '../services/api';
|
||||
import type { PageContent } from '../services/api/pageContentService';
|
||||
import { useCompanySettings } from '../contexts/CompanySettingsContext';
|
||||
import Loading from '../components/common/Loading';
|
||||
|
||||
const RefundsPolicyPage: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await pageContentService.getRefundsContent();
|
||||
if (response.status === 'success' && response.data?.page_content) {
|
||||
const content = response.data.page_content;
|
||||
|
||||
// Process HTML content to ensure text is visible
|
||||
if (content.content) {
|
||||
// Create a temporary div to parse HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.content;
|
||||
|
||||
// Add color styles to elements that don't have them
|
||||
const allElements = tempDiv.querySelectorAll('*');
|
||||
allElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const tagName = htmlEl.tagName.toLowerCase();
|
||||
const currentColor = htmlEl.style.color;
|
||||
|
||||
// Only add color if not already set
|
||||
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
|
||||
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||
htmlEl.style.color = '#ffffff';
|
||||
} else if (['strong', 'b'].includes(tagName)) {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else if (tagName === 'a') {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else {
|
||||
htmlEl.style.color = '#d1d5db';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update content with processed HTML
|
||||
content.content = tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
setPageContent(content);
|
||||
|
||||
if (content.meta_title) {
|
||||
document.title = content.meta_title;
|
||||
}
|
||||
if (content.meta_description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', content.meta_description);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching page content:', err);
|
||||
// If page is disabled (404), set pageContent to null to show disabled message
|
||||
if (err.response?.status === 404) {
|
||||
setPageContent(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!pageContent) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<RefreshCw className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
|
||||
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Refunds Policy</h1>
|
||||
<p className="text-gray-400">This page is currently unavailable.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
|
||||
{/* Back Link */}
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8 sm:mb-12 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
|
||||
<RefreshCw className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
|
||||
{pageContent.title || 'Refunds Policy'}
|
||||
</h1>
|
||||
{pageContent.subtitle && (
|
||||
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
|
||||
{pageContent.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none text-gray-300
|
||||
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
|
||||
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
|
||||
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
|
||||
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
|
||||
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
|
||||
prose-strong:text-[#d4af37] prose-strong:font-medium
|
||||
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
|
||||
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
|
||||
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
|
||||
style={{ color: '#d1d5db' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Footer Note */}
|
||||
{settings.company_email && (
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-sm text-gray-400 font-light">
|
||||
For refund inquiries, contact us at{' '}
|
||||
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
|
||||
{settings.company_email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RefundsPolicyPage;
|
||||
|
||||
187
Frontend/src/pages/TermsPage.tsx
Normal file
187
Frontend/src/pages/TermsPage.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Scale, ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { pageContentService } from '../services/api';
|
||||
import type { PageContent } from '../services/api/pageContentService';
|
||||
import { useCompanySettings } from '../contexts/CompanySettingsContext';
|
||||
import Loading from '../components/common/Loading';
|
||||
|
||||
const TermsPage: React.FC = () => {
|
||||
const { settings } = useCompanySettings();
|
||||
const [pageContent, setPageContent] = useState<PageContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPageContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await pageContentService.getTermsContent();
|
||||
if (response.status === 'success' && response.data?.page_content) {
|
||||
const content = response.data.page_content;
|
||||
|
||||
// Process HTML content to ensure text is visible
|
||||
if (content.content) {
|
||||
// Create a temporary div to parse HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.content;
|
||||
|
||||
// Add color styles to elements that don't have them
|
||||
const allElements = tempDiv.querySelectorAll('*');
|
||||
allElements.forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
const tagName = htmlEl.tagName.toLowerCase();
|
||||
const currentColor = htmlEl.style.color;
|
||||
|
||||
// Only add color if not already set
|
||||
if (!currentColor || currentColor === 'black' || currentColor === '#000' || currentColor === '#000000') {
|
||||
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||
htmlEl.style.color = '#ffffff';
|
||||
} else if (['strong', 'b'].includes(tagName)) {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else if (tagName === 'a') {
|
||||
htmlEl.style.color = '#d4af37';
|
||||
} else {
|
||||
htmlEl.style.color = '#d1d5db';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update content with processed HTML
|
||||
content.content = tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
setPageContent(content);
|
||||
|
||||
if (content.meta_title) {
|
||||
document.title = content.meta_title;
|
||||
}
|
||||
if (content.meta_description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', content.meta_description);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching page content:', err);
|
||||
// If page is disabled (404), set pageContent to null to show disabled message
|
||||
if (err.response?.status === 404) {
|
||||
setPageContent(null);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageContent();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!pageContent) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6 flex items-center justify-center"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Scale className="w-16 h-16 text-[#d4af37]/50 mx-auto mb-4" />
|
||||
<h1 className="text-2xl font-elegant font-bold text-white mb-2">Terms & Conditions</h1>
|
||||
<p className="text-gray-400">This page is currently unavailable.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mt-6 transition-all duration-300"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f] w-screen relative -mt-6 -mb-6"
|
||||
style={{
|
||||
marginLeft: 'calc(50% - 50vw)',
|
||||
marginRight: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
paddingTop: '2rem',
|
||||
paddingBottom: '2rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<div className="w-full px-3 sm:px-4 md:px-6 lg:px-8 py-8 sm:py-12 max-w-5xl mx-auto">
|
||||
{/* Back Link */}
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-[#d4af37]/80 hover:text-[#d4af37] mb-6 transition-all duration-300 group font-light tracking-wide text-xs sm:text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||
<span>Back to Home</span>
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8 sm:mb-12 text-center">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 mb-4 sm:mb-6 bg-gradient-to-br from-[#d4af37]/20 to-[#c9a227]/10 rounded-full border border-[#d4af37]/30">
|
||||
<Scale className="w-8 h-8 sm:w-10 sm:h-10 text-[#d4af37]" />
|
||||
</div>
|
||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-elegant font-bold text-white mb-3 sm:mb-4 tracking-tight leading-tight bg-gradient-to-r from-white via-[#d4af37] to-white bg-clip-text text-transparent">
|
||||
{pageContent.title || 'Terms & Conditions'}
|
||||
</h1>
|
||||
{pageContent.subtitle && (
|
||||
<p className="text-base sm:text-lg text-gray-300 font-light tracking-wide">
|
||||
{pageContent.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="bg-gradient-to-br from-[#1a1a1a] to-[#0a0a0a] rounded-xl border border-[#d4af37]/20 backdrop-blur-xl shadow-2xl shadow-[#d4af37]/5 p-6 sm:p-8 lg:p-12">
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none text-gray-300
|
||||
prose-headings:text-white prose-headings:font-elegant prose-headings:font-semibold
|
||||
prose-h2:text-2xl prose-h2:mt-8 prose-h2:mb-4 prose-h2:border-b prose-h2:border-[#d4af37]/20 prose-h2:pb-2
|
||||
prose-p:text-gray-300 prose-p:font-light prose-p:leading-relaxed prose-p:mb-4
|
||||
prose-ul:text-gray-300 prose-ul:font-light prose-ul:my-4
|
||||
prose-li:text-gray-300 prose-li:mb-2 prose-li:ml-4
|
||||
prose-strong:text-[#d4af37] prose-strong:font-medium
|
||||
prose-a:text-[#d4af37] prose-a:no-underline hover:prose-a:underline
|
||||
[&_*]:text-gray-300 [&_h1]:text-white [&_h2]:text-white [&_h3]:text-white [&_h4]:text-white [&_h5]:text-white [&_h6]:text-white
|
||||
[&_strong]:text-[#d4af37] [&_b]:text-[#d4af37] [&_a]:text-[#d4af37]"
|
||||
style={{ color: '#d1d5db' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: pageContent.content || pageContent.description || '<p style="color: #d1d5db;">No content available.</p>'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Footer Note */}
|
||||
{settings.company_email && (
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-sm text-gray-400 font-light">
|
||||
For questions about these terms, contact us at{' '}
|
||||
<a href={`mailto:${settings.company_email}`} className="text-[#d4af37] hover:underline">
|
||||
{settings.company_email}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsPage;
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
Check,
|
||||
XCircle,
|
||||
Award,
|
||||
Shield
|
||||
Shield,
|
||||
RefreshCw,
|
||||
Accessibility,
|
||||
HelpCircle
|
||||
} from 'lucide-react';
|
||||
import { pageContentService, PageContent, PageType, UpdatePageContentData, bannerService, Banner } from '../../services/api';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -26,7 +29,7 @@ import Loading from '../../components/common/Loading';
|
||||
import { ConfirmationDialog } from '../../components/common';
|
||||
import IconPicker from '../../components/admin/IconPicker';
|
||||
|
||||
type ContentTab = 'overview' | 'home' | 'contact' | 'about' | 'footer' | 'seo';
|
||||
type ContentTab = 'overview' | 'home' | 'contact' | 'about' | 'footer' | 'seo' | 'privacy' | 'terms' | 'refunds' | 'cancellation' | 'accessibility' | 'faq';
|
||||
|
||||
const PageContentDashboard: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<ContentTab>('overview');
|
||||
@@ -38,6 +41,12 @@ const PageContentDashboard: React.FC = () => {
|
||||
about: null,
|
||||
footer: null,
|
||||
seo: null,
|
||||
privacy: null,
|
||||
terms: null,
|
||||
refunds: null,
|
||||
cancellation: null,
|
||||
accessibility: null,
|
||||
faq: null,
|
||||
});
|
||||
|
||||
// Form states for each page
|
||||
@@ -46,6 +55,12 @@ const PageContentDashboard: React.FC = () => {
|
||||
const [aboutData, setAboutData] = useState<UpdatePageContentData>({});
|
||||
const [footerData, setFooterData] = useState<UpdatePageContentData>({});
|
||||
const [seoData, setSeoData] = useState<UpdatePageContentData>({});
|
||||
const [privacyData, setPrivacyData] = useState<UpdatePageContentData>({ is_active: true });
|
||||
const [termsData, setTermsData] = useState<UpdatePageContentData>({ is_active: true });
|
||||
const [refundsData, setRefundsData] = useState<UpdatePageContentData>({ is_active: true });
|
||||
const [cancellationData, setCancellationData] = useState<UpdatePageContentData>({ is_active: true });
|
||||
const [accessibilityData, setAccessibilityData] = useState<UpdatePageContentData>({ is_active: true });
|
||||
const [faqData, setFaqData] = useState<UpdatePageContentData>({ is_active: true });
|
||||
|
||||
// Banner management state
|
||||
const [banners, setBanners] = useState<Banner[]>([]);
|
||||
@@ -88,6 +103,12 @@ const PageContentDashboard: React.FC = () => {
|
||||
about: null,
|
||||
footer: null,
|
||||
seo: null,
|
||||
privacy: null,
|
||||
terms: null,
|
||||
refunds: null,
|
||||
cancellation: null,
|
||||
accessibility: null,
|
||||
faq: null,
|
||||
};
|
||||
|
||||
contents.forEach((content) => {
|
||||
@@ -241,6 +262,84 @@ const PageContentDashboard: React.FC = () => {
|
||||
canonical_url: contents.seo.canonical_url || '',
|
||||
});
|
||||
}
|
||||
|
||||
// Privacy
|
||||
if (contents.privacy) {
|
||||
setPrivacyData({
|
||||
title: contents.privacy.title || '',
|
||||
subtitle: contents.privacy.subtitle || '',
|
||||
description: contents.privacy.description || '',
|
||||
content: contents.privacy.content || '',
|
||||
meta_title: contents.privacy.meta_title || '',
|
||||
meta_description: contents.privacy.meta_description || '',
|
||||
is_active: contents.privacy.is_active ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
// Terms
|
||||
if (contents.terms) {
|
||||
setTermsData({
|
||||
title: contents.terms.title || '',
|
||||
subtitle: contents.terms.subtitle || '',
|
||||
description: contents.terms.description || '',
|
||||
content: contents.terms.content || '',
|
||||
meta_title: contents.terms.meta_title || '',
|
||||
meta_description: contents.terms.meta_description || '',
|
||||
is_active: contents.terms.is_active ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
// Refunds
|
||||
if (contents.refunds) {
|
||||
setRefundsData({
|
||||
title: contents.refunds.title || '',
|
||||
subtitle: contents.refunds.subtitle || '',
|
||||
description: contents.refunds.description || '',
|
||||
content: contents.refunds.content || '',
|
||||
meta_title: contents.refunds.meta_title || '',
|
||||
meta_description: contents.refunds.meta_description || '',
|
||||
is_active: contents.refunds.is_active ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
// Cancellation
|
||||
if (contents.cancellation) {
|
||||
setCancellationData({
|
||||
title: contents.cancellation.title || '',
|
||||
subtitle: contents.cancellation.subtitle || '',
|
||||
description: contents.cancellation.description || '',
|
||||
content: contents.cancellation.content || '',
|
||||
meta_title: contents.cancellation.meta_title || '',
|
||||
meta_description: contents.cancellation.meta_description || '',
|
||||
is_active: contents.cancellation.is_active ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
// Accessibility
|
||||
if (contents.accessibility) {
|
||||
setAccessibilityData({
|
||||
title: contents.accessibility.title || '',
|
||||
subtitle: contents.accessibility.subtitle || '',
|
||||
description: contents.accessibility.description || '',
|
||||
content: contents.accessibility.content || '',
|
||||
meta_title: contents.accessibility.meta_title || '',
|
||||
meta_description: contents.accessibility.meta_description || '',
|
||||
is_active: contents.accessibility.is_active ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
// FAQ
|
||||
if (contents.faq) {
|
||||
setFaqData({
|
||||
title: contents.faq.title || '',
|
||||
subtitle: contents.faq.subtitle || '',
|
||||
description: contents.faq.description || '',
|
||||
content: contents.faq.content || '',
|
||||
meta_title: contents.faq.meta_title || '',
|
||||
meta_description: contents.faq.meta_description || '',
|
||||
is_active: contents.faq.is_active ?? true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async (pageType: PageType, data: UpdatePageContentData) => {
|
||||
@@ -449,6 +548,12 @@ const PageContentDashboard: React.FC = () => {
|
||||
{ id: 'home' as ContentTab, label: 'Home', icon: Home },
|
||||
{ id: 'contact' as ContentTab, label: 'Contact', icon: Mail },
|
||||
{ id: 'about' as ContentTab, label: 'About', icon: Info },
|
||||
{ id: 'privacy' as ContentTab, label: 'Privacy', icon: Shield },
|
||||
{ id: 'terms' as ContentTab, label: 'Terms', icon: FileText },
|
||||
{ id: 'refunds' as ContentTab, label: 'Refunds', icon: RefreshCw },
|
||||
{ id: 'cancellation' as ContentTab, label: 'Cancellation', icon: XCircle },
|
||||
{ id: 'accessibility' as ContentTab, label: 'Accessibility', icon: Accessibility },
|
||||
{ id: 'faq' as ContentTab, label: 'FAQ', icon: HelpCircle },
|
||||
{ id: 'footer' as ContentTab, label: 'Footer', icon: FileText },
|
||||
{ id: 'seo' as ContentTab, label: 'SEO', icon: Search },
|
||||
];
|
||||
@@ -528,6 +633,12 @@ const PageContentDashboard: React.FC = () => {
|
||||
{ id: 'home' as PageType, label: 'Home Page', icon: Home, color: 'blue', description: 'Manage hero section, featured content' },
|
||||
{ id: 'contact' as PageType, label: 'Contact Page', icon: Mail, color: 'green', description: 'Manage contact information and form' },
|
||||
{ id: 'about' as PageType, label: 'About Page', icon: Info, color: 'amber', description: 'Manage story, values, and features' },
|
||||
{ id: 'privacy' as PageType, label: 'Privacy Policy', icon: Shield, color: 'red', description: 'Manage privacy policy content' },
|
||||
{ id: 'terms' as PageType, label: 'Terms & Conditions', icon: FileText, color: 'teal', description: 'Manage terms and conditions' },
|
||||
{ id: 'refunds' as PageType, label: 'Refunds Policy', icon: RefreshCw, color: 'orange', description: 'Manage refunds policy content' },
|
||||
{ id: 'cancellation' as PageType, label: 'Cancellation Policy', icon: XCircle, color: 'pink', description: 'Manage cancellation policy content' },
|
||||
{ id: 'accessibility' as PageType, label: 'Accessibility', icon: Accessibility, color: 'cyan', description: 'Manage accessibility information' },
|
||||
{ id: 'faq' as PageType, label: 'FAQ', icon: HelpCircle, color: 'violet', description: 'Manage frequently asked questions' },
|
||||
{ id: 'footer' as PageType, label: 'Footer', icon: FileText, color: 'purple', description: 'Manage footer links and social media' },
|
||||
{ id: 'seo' as PageType, label: 'SEO Settings', icon: Search, color: 'indigo', description: 'Manage meta tags and SEO optimization' },
|
||||
].map((page) => {
|
||||
@@ -542,6 +653,9 @@ const PageContentDashboard: React.FC = () => {
|
||||
page.color === 'green' ? 'border-green-100/50 hover:border-green-300/60' :
|
||||
page.color === 'amber' ? 'border-amber-100/50 hover:border-amber-300/60' :
|
||||
page.color === 'purple' ? 'border-purple-100/50 hover:border-purple-300/60' :
|
||||
page.color === 'red' ? 'border-red-100/50 hover:border-red-300/60' :
|
||||
page.color === 'teal' ? 'border-teal-100/50 hover:border-teal-300/60' :
|
||||
page.color === 'orange' ? 'border-orange-100/50 hover:border-orange-300/60' :
|
||||
'border-indigo-100/50 hover:border-indigo-300/60'
|
||||
}`}
|
||||
>
|
||||
@@ -550,6 +664,9 @@ const PageContentDashboard: React.FC = () => {
|
||||
page.color === 'green' ? 'from-green-400' :
|
||||
page.color === 'amber' ? 'from-amber-400' :
|
||||
page.color === 'purple' ? 'from-purple-400' :
|
||||
page.color === 'red' ? 'from-red-400' :
|
||||
page.color === 'teal' ? 'from-teal-400' :
|
||||
page.color === 'orange' ? 'from-orange-400' :
|
||||
'from-indigo-400'
|
||||
}`}></div>
|
||||
<div className="relative space-y-5">
|
||||
@@ -560,6 +677,9 @@ const PageContentDashboard: React.FC = () => {
|
||||
page.color === 'green' ? 'bg-gradient-to-br from-green-500 to-green-600 border-green-400/50' :
|
||||
page.color === 'amber' ? 'bg-gradient-to-br from-amber-500 to-amber-600 border-amber-400/50' :
|
||||
page.color === 'purple' ? 'bg-gradient-to-br from-purple-500 to-purple-600 border-purple-400/50' :
|
||||
page.color === 'red' ? 'bg-gradient-to-br from-red-500 to-red-600 border-red-400/50' :
|
||||
page.color === 'teal' ? 'bg-gradient-to-br from-teal-500 to-teal-600 border-teal-400/50' :
|
||||
page.color === 'orange' ? 'bg-gradient-to-br from-orange-500 to-orange-600 border-orange-400/50' :
|
||||
'bg-gradient-to-br from-indigo-500 to-indigo-600 border-indigo-400/50'
|
||||
}`}>
|
||||
<Icon className="w-6 h-6 text-white" />
|
||||
@@ -568,6 +688,9 @@ const PageContentDashboard: React.FC = () => {
|
||||
<h3 className="font-bold text-gray-900 text-xl mb-1">{page.label}</h3>
|
||||
<div className={`h-1 w-12 rounded-full ${
|
||||
page.color === 'blue' ? 'bg-gradient-to-r from-blue-500 to-blue-600' :
|
||||
page.color === 'red' ? 'bg-gradient-to-r from-red-500 to-red-600' :
|
||||
page.color === 'teal' ? 'bg-gradient-to-r from-teal-500 to-teal-600' :
|
||||
page.color === 'orange' ? 'bg-gradient-to-r from-orange-500 to-orange-600' :
|
||||
page.color === 'green' ? 'bg-gradient-to-r from-green-500 to-green-600' :
|
||||
page.color === 'amber' ? 'bg-gradient-to-r from-amber-500 to-amber-600' :
|
||||
page.color === 'purple' ? 'bg-gradient-to-r from-purple-500 to-purple-600' :
|
||||
@@ -3607,6 +3730,570 @@ const PageContentDashboard: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Privacy Tab */}
|
||||
{activeTab === 'privacy' && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900">Privacy Policy Content</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-700">Enable Page</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPrivacyData({ ...privacyData, is_active: !privacyData.is_active })}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
|
||||
(privacyData.is_active ?? true) ? 'bg-purple-600' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
(privacyData.is_active ?? true) ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={privacyData.title || ''}
|
||||
onChange={(e) => setPrivacyData({ ...privacyData, title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Privacy Policy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={privacyData.subtitle || ''}
|
||||
onChange={(e) => setPrivacyData({ ...privacyData, subtitle: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Your privacy is important to us"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Content (HTML)</label>
|
||||
<textarea
|
||||
value={privacyData.content || ''}
|
||||
onChange={(e) => setPrivacyData({ ...privacyData, content: e.target.value })}
|
||||
rows={20}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 font-mono text-sm"
|
||||
placeholder="Enter HTML content for privacy policy..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">SEO Settings</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={privacyData.meta_title || ''}
|
||||
onChange={(e) => setPrivacyData({ ...privacyData, meta_title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Description</label>
|
||||
<textarea
|
||||
value={privacyData.meta_description || ''}
|
||||
onChange={(e) => setPrivacyData({ ...privacyData, meta_description: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-200">
|
||||
<button
|
||||
onClick={() => handleSave('privacy', privacyData)}
|
||||
disabled={saving}
|
||||
className="px-8 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl font-semibold hover:from-purple-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-5 h-5" />
|
||||
{saving ? 'Saving...' : 'Save Privacy Policy'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terms Tab */}
|
||||
{activeTab === 'terms' && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900 mb-6">Terms & Conditions Content</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={termsData.title || ''}
|
||||
onChange={(e) => setTermsData({ ...termsData, title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Terms & Conditions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={termsData.subtitle || ''}
|
||||
onChange={(e) => setTermsData({ ...termsData, subtitle: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Please read these terms carefully"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Content (HTML)</label>
|
||||
<textarea
|
||||
value={termsData.content || ''}
|
||||
onChange={(e) => setTermsData({ ...termsData, content: e.target.value })}
|
||||
rows={20}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 font-mono text-sm"
|
||||
placeholder="Enter HTML content for terms & conditions..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">SEO Settings</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={termsData.meta_title || ''}
|
||||
onChange={(e) => setTermsData({ ...termsData, meta_title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Description</label>
|
||||
<textarea
|
||||
value={termsData.meta_description || ''}
|
||||
onChange={(e) => setTermsData({ ...termsData, meta_description: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-200">
|
||||
<button
|
||||
onClick={() => handleSave('terms', termsData)}
|
||||
disabled={saving}
|
||||
className="px-8 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl font-semibold hover:from-purple-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-5 h-5" />
|
||||
{saving ? 'Saving...' : 'Save Terms & Conditions'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Refunds Tab */}
|
||||
{activeTab === 'refunds' && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900">Refunds Policy Content</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-700">Enable Page</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRefundsData({ ...refundsData, is_active: !refundsData.is_active })}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
|
||||
(refundsData.is_active ?? true) ? 'bg-purple-600' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
(refundsData.is_active ?? true) ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={refundsData.title || ''}
|
||||
onChange={(e) => setRefundsData({ ...refundsData, title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Refunds Policy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={refundsData.subtitle || ''}
|
||||
onChange={(e) => setRefundsData({ ...refundsData, subtitle: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Our commitment to fair refunds"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Content (HTML)</label>
|
||||
<textarea
|
||||
value={refundsData.content || ''}
|
||||
onChange={(e) => setRefundsData({ ...refundsData, content: e.target.value })}
|
||||
rows={20}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 font-mono text-sm"
|
||||
placeholder="Enter HTML content for refunds policy..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-4">SEO Settings</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={refundsData.meta_title || ''}
|
||||
onChange={(e) => setRefundsData({ ...refundsData, meta_title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Description</label>
|
||||
<textarea
|
||||
value={refundsData.meta_description || ''}
|
||||
onChange={(e) => setRefundsData({ ...refundsData, meta_description: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-200">
|
||||
<button
|
||||
onClick={() => handleSave('refunds', refundsData)}
|
||||
disabled={saving}
|
||||
className="px-8 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl font-semibold hover:from-purple-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-5 h-5" />
|
||||
{saving ? 'Saving...' : 'Save Refunds Policy'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Cancellation Tab */}
|
||||
{activeTab === 'cancellation' && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900">Cancellation Policy Content</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-700">Enable Page</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCancellationData({ ...cancellationData, is_active: !cancellationData.is_active })}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
|
||||
(cancellationData.is_active ?? true) ? 'bg-purple-600' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
(cancellationData.is_active ?? true) ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={cancellationData.title || ''}
|
||||
onChange={(e) => setCancellationData({ ...cancellationData, title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Cancellation Policy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={cancellationData.subtitle || ''}
|
||||
onChange={(e) => setCancellationData({ ...cancellationData, subtitle: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Flexible cancellation options for your peace of mind"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Content (HTML)</label>
|
||||
<textarea
|
||||
value={cancellationData.content || ''}
|
||||
onChange={(e) => setCancellationData({ ...cancellationData, content: e.target.value })}
|
||||
rows={20}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 font-mono text-sm"
|
||||
placeholder="Enter HTML content for cancellation policy..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={cancellationData.meta_title || ''}
|
||||
onChange={(e) => setCancellationData({ ...cancellationData, meta_title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Cancellation Policy - Luxury Hotel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Description</label>
|
||||
<textarea
|
||||
value={cancellationData.meta_description || ''}
|
||||
onChange={(e) => setCancellationData({ ...cancellationData, meta_description: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Review our cancellation policy..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-gray-200">
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => handleSave('cancellation', cancellationData)}
|
||||
disabled={saving}
|
||||
className="px-8 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl font-semibold hover:from-purple-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-5 h-5" />
|
||||
{saving ? 'Saving...' : 'Save Cancellation Policy'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Accessibility Tab */}
|
||||
{activeTab === 'accessibility' && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900">Accessibility Content</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-700">Enable Page</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAccessibilityData({ ...accessibilityData, is_active: !accessibilityData.is_active })}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
|
||||
(accessibilityData.is_active ?? true) ? 'bg-purple-600' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
(accessibilityData.is_active ?? true) ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={accessibilityData.title || ''}
|
||||
onChange={(e) => setAccessibilityData({ ...accessibilityData, title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Accessibility"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={accessibilityData.subtitle || ''}
|
||||
onChange={(e) => setAccessibilityData({ ...accessibilityData, subtitle: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Committed to providing an inclusive experience for all guests"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Content (HTML)</label>
|
||||
<textarea
|
||||
value={accessibilityData.content || ''}
|
||||
onChange={(e) => setAccessibilityData({ ...accessibilityData, content: e.target.value })}
|
||||
rows={20}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 font-mono text-sm"
|
||||
placeholder="Enter HTML content for accessibility page..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={accessibilityData.meta_title || ''}
|
||||
onChange={(e) => setAccessibilityData({ ...accessibilityData, meta_title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Accessibility - Luxury Hotel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Description</label>
|
||||
<textarea
|
||||
value={accessibilityData.meta_description || ''}
|
||||
onChange={(e) => setAccessibilityData({ ...accessibilityData, meta_description: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Discover our commitment to accessibility..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-gray-200">
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => handleSave('accessibility', accessibilityData)}
|
||||
disabled={saving}
|
||||
className="px-8 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl font-semibold hover:from-purple-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-5 h-5" />
|
||||
{saving ? 'Saving...' : 'Save Accessibility Content'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* FAQ Tab */}
|
||||
{activeTab === 'faq' && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-3xl font-extrabold text-gray-900">FAQ Content</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-700">Enable Page</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFaqData({ ...faqData, is_active: !faqData.is_active })}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
|
||||
(faqData.is_active ?? true) ? 'bg-purple-600' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
(faqData.is_active ?? true) ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={faqData.title || ''}
|
||||
onChange={(e) => setFaqData({ ...faqData, title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Frequently Asked Questions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
value={faqData.subtitle || ''}
|
||||
onChange={(e) => setFaqData({ ...faqData, subtitle: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Find answers to common questions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Content (HTML)</label>
|
||||
<textarea
|
||||
value={faqData.content || ''}
|
||||
onChange={(e) => setFaqData({ ...faqData, content: e.target.value })}
|
||||
rows={20}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200 font-mono text-sm"
|
||||
placeholder="Enter HTML content for FAQ page..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={faqData.meta_title || ''}
|
||||
onChange={(e) => setFaqData({ ...faqData, meta_title: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="FAQ - Luxury Hotel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Meta Description</label>
|
||||
<textarea
|
||||
value={faqData.meta_description || ''}
|
||||
onChange={(e) => setFaqData({ ...faqData, meta_description: e.target.value })}
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white border-2 border-gray-200 rounded-xl focus:border-purple-400 focus:ring-4 focus:ring-purple-100 transition-all duration-200"
|
||||
placeholder="Find answers to common questions..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-gray-200">
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => handleSave('faq', faqData)}
|
||||
disabled={saving}
|
||||
className="px-8 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white rounded-xl font-semibold hover:from-purple-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
<Save className="w-5 h-5" />
|
||||
{saving ? 'Saving...' : 'Save FAQ Content'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SEO Tab */}
|
||||
{activeTab === 'seo' && (
|
||||
<div className="space-y-8">
|
||||
|
||||
@@ -1787,12 +1787,12 @@ const BookingPage: React.FC = () => {
|
||||
}`}
|
||||
>
|
||||
{paymentMethod === 'cash' ? (
|
||||
<div className="flex items-start gap-2">
|
||||
<Shield className="w-3.5 h-3.5 text-orange-400 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-[10px] sm:text-xs text-orange-300 font-light tracking-wide leading-relaxed">
|
||||
<div className="flex items-start gap-2">
|
||||
<Shield className="w-3.5 h-3.5 text-orange-400 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-[10px] sm:text-xs text-orange-300 font-light tracking-wide leading-relaxed">
|
||||
<strong className="text-orange-200">20% deposit required</strong> to secure your booking. Pay the remaining balance ({formatPrice(totalPrice * 0.8)}) on arrival at the hotel.
|
||||
</p>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-2">
|
||||
<Sparkles className="w-3.5 h-3.5 text-[#d4af37] mt-0.5 flex-shrink-0" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import apiClient from './apiClient';
|
||||
|
||||
export type PageType = 'home' | 'contact' | 'about' | 'footer' | 'seo';
|
||||
export type PageType = 'home' | 'contact' | 'about' | 'footer' | 'seo' | 'privacy' | 'terms' | 'refunds' | 'cancellation' | 'accessibility' | 'faq';
|
||||
|
||||
export interface PageContent {
|
||||
id?: number;
|
||||
@@ -228,6 +228,36 @@ const pageContentService = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getPrivacyContent: async (): Promise<PageContentResponse> => {
|
||||
const response = await apiClient.get<PageContentResponse>('/privacy');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTermsContent: async (): Promise<PageContentResponse> => {
|
||||
const response = await apiClient.get<PageContentResponse>('/terms');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getRefundsContent: async (): Promise<PageContentResponse> => {
|
||||
const response = await apiClient.get<PageContentResponse>('/refunds');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getCancellationContent: async (): Promise<PageContentResponse> => {
|
||||
const response = await apiClient.get<PageContentResponse>('/cancellation');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getAccessibilityContent: async (): Promise<PageContentResponse> => {
|
||||
const response = await apiClient.get<PageContentResponse>('/accessibility');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getFAQContent: async (): Promise<PageContentResponse> => {
|
||||
const response = await apiClient.get<PageContentResponse>('/faq');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
updatePageContent: async (
|
||||
pageType: PageType,
|
||||
|
||||
@@ -543,3 +543,37 @@ img[loading="lazy"].loaded,
|
||||
img[loading="lazy"]:not([src]) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Policy pages content styling - ensure all text is visible */
|
||||
.prose.prose-invert {
|
||||
color: #d1d5db !important;
|
||||
}
|
||||
|
||||
.prose.prose-invert * {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.prose.prose-invert p,
|
||||
.prose.prose-invert li,
|
||||
.prose.prose-invert span,
|
||||
.prose.prose-invert div {
|
||||
color: #d1d5db !important;
|
||||
}
|
||||
|
||||
.prose.prose-invert h1,
|
||||
.prose.prose-invert h2,
|
||||
.prose.prose-invert h3,
|
||||
.prose.prose-invert h4,
|
||||
.prose.prose-invert h5,
|
||||
.prose.prose-invert h6 {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.prose.prose-invert strong,
|
||||
.prose.prose-invert b {
|
||||
color: #d4af37 !important;
|
||||
}
|
||||
|
||||
.prose.prose-invert a {
|
||||
color: #d4af37 !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user