updates
This commit is contained in:
@@ -1,21 +1,75 @@
|
||||
"use client";
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams, usePathname } from 'next/navigation';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import { Suspense } from 'react';
|
||||
import { usePolicy } from '@/lib/hooks/usePolicy';
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
import { sanitizeHTML } from "@/lib/security/sanitize";
|
||||
|
||||
const PolicyContent = () => {
|
||||
// Component that reads type from URL using Next.js hooks (safe in client components)
|
||||
const PolicyContentClient = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const typeParam = searchParams.get('type') || 'privacy';
|
||||
const type = typeParam as 'privacy' | 'terms' | 'support';
|
||||
const pathname = usePathname();
|
||||
const [type, setType] = useState<'privacy' | 'terms' | 'support'>('privacy');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Only run on client side
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
setMounted(true);
|
||||
|
||||
// Get type from URL search params
|
||||
try {
|
||||
const urlType = searchParams?.get('type');
|
||||
if (urlType && ['privacy', 'terms', 'support'].includes(urlType)) {
|
||||
setType(urlType as 'privacy' | 'terms' | 'support');
|
||||
} else {
|
||||
setType('privacy'); // Default fallback
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading URL type:', error);
|
||||
setType('privacy'); // Fallback to default
|
||||
}
|
||||
}, [searchParams, pathname]);
|
||||
|
||||
// If not mounted yet, show loading state
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div style={{ padding: '4rem', textAlign: 'center', minHeight: '50vh' }}>
|
||||
<div style={{
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
margin: '0 auto 1rem',
|
||||
border: '4px solid #f3f3f3',
|
||||
borderTop: '4px solid #daa520',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 1s linear infinite'
|
||||
}}></div>
|
||||
<p style={{ color: '#64748b' }}>Loading policy...</p>
|
||||
<style jsx>{`
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <PolicyContentInner type={type} />;
|
||||
};
|
||||
|
||||
// Inner component that doesn't use useSearchParams
|
||||
const PolicyContentInner = ({ type }: { type: 'privacy' | 'terms' | 'support' }) => {
|
||||
|
||||
const { data: policy, isLoading, error } = usePolicy(type);
|
||||
|
||||
// Update metadata based on policy type
|
||||
useEffect(() => {
|
||||
// Only run on client side
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
||||
|
||||
const policyTitles = {
|
||||
privacy: 'Privacy Policy - Data Protection & Privacy',
|
||||
terms: 'Terms of Use - Terms & Conditions',
|
||||
@@ -28,30 +82,50 @@ const PolicyContent = () => {
|
||||
support: 'Learn about GNX Soft\'s Support Policy, including support terms, response times, and service level agreements.',
|
||||
};
|
||||
|
||||
const metadata = createMetadata({
|
||||
title: policyTitles[type],
|
||||
description: policyDescriptions[type],
|
||||
keywords: [
|
||||
type === 'privacy' ? 'Privacy Policy' : type === 'terms' ? 'Terms of Use' : 'Support Policy',
|
||||
'Legal Documents',
|
||||
'Company Policies',
|
||||
'Data Protection',
|
||||
'Terms and Conditions',
|
||||
],
|
||||
url: `/policy?type=${type}`,
|
||||
});
|
||||
try {
|
||||
// Dynamically import metadata function to avoid SSR issues
|
||||
import("@/lib/seo/metadata").then(({ generateMetadata: createMetadata }) => {
|
||||
const metadata = createMetadata({
|
||||
title: policyTitles[type],
|
||||
description: policyDescriptions[type],
|
||||
keywords: [
|
||||
type === 'privacy' ? 'Privacy Policy' : type === 'terms' ? 'Terms of Use' : 'Support Policy',
|
||||
'Legal Documents',
|
||||
'Company Policies',
|
||||
'Data Protection',
|
||||
'Terms and Conditions',
|
||||
],
|
||||
url: `/policy?type=${type}`,
|
||||
});
|
||||
|
||||
const titleString = typeof metadata.title === 'string' ? metadata.title : `${policyTitles[type]} | GNX Soft`;
|
||||
document.title = titleString;
|
||||
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
const titleString = typeof metadata.title === 'string' ? metadata.title : `${policyTitles[type]} | GNX Soft`;
|
||||
document.title = titleString;
|
||||
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
const descriptionString = typeof metadata.description === 'string' ? metadata.description : policyDescriptions[type];
|
||||
metaDescription.setAttribute('content', descriptionString);
|
||||
}).catch((error) => {
|
||||
// Fallback to simple title/description update if metadata import fails
|
||||
console.warn('Error loading metadata function:', error);
|
||||
document.title = `${policyTitles[type]} | GNX Soft`;
|
||||
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.setAttribute('name', 'description');
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.setAttribute('content', policyDescriptions[type]);
|
||||
});
|
||||
} catch (error) {
|
||||
// Silently handle metadata errors
|
||||
console.error('Error setting metadata:', error);
|
||||
}
|
||||
const descriptionString = typeof metadata.description === 'string' ? metadata.description : policyDescriptions[type];
|
||||
metaDescription.setAttribute('content', descriptionString);
|
||||
}, [type]);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -178,23 +252,49 @@ const PolicyContent = () => {
|
||||
<div className="col-12 col-lg-10">
|
||||
{/* Policy Header */}
|
||||
<div className="policy-header">
|
||||
<h1 className="policy-title">{policy.title}</h1>
|
||||
<h1 className="policy-title">{policy.title || 'Policy'}</h1>
|
||||
<div className="policy-meta">
|
||||
<p className="policy-updated">
|
||||
Last Updated: {new Date(policy.last_updated).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})}
|
||||
</p>
|
||||
<p className="policy-version">Version {policy.version}</p>
|
||||
<p className="policy-effective">
|
||||
Effective Date: {new Date(policy.effective_date).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})}
|
||||
</p>
|
||||
{policy.last_updated && (
|
||||
<p className="policy-updated">
|
||||
Last Updated: {(() => {
|
||||
try {
|
||||
return new Date(policy.last_updated).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch {
|
||||
return new Date().toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
})()}
|
||||
</p>
|
||||
)}
|
||||
{policy.version && (
|
||||
<p className="policy-version">Version {policy.version}</p>
|
||||
)}
|
||||
{policy.effective_date && (
|
||||
<p className="policy-effective">
|
||||
Effective Date: {(() => {
|
||||
try {
|
||||
return new Date(policy.effective_date).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch {
|
||||
return new Date().toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
})()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{policy.description && (
|
||||
<p className="policy-description">{policy.description}</p>
|
||||
@@ -203,34 +303,42 @@ const PolicyContent = () => {
|
||||
|
||||
{/* Policy Content */}
|
||||
<div className="policy-content">
|
||||
{policy.sections.map((section) => (
|
||||
<div key={section.id} className="policy-section-item">
|
||||
<h2 className="policy-heading">{section.heading}</h2>
|
||||
<div className="policy-text" dangerouslySetInnerHTML={{
|
||||
__html: section.content
|
||||
// First, handle main sections with (a), (b), etc.
|
||||
.replace(/\(a\)/g, '<br/><br/><strong>(a)</strong>')
|
||||
.replace(/\(b\)/g, '<br/><br/><strong>(b)</strong>')
|
||||
.replace(/\(c\)/g, '<br/><br/><strong>(c)</strong>')
|
||||
.replace(/\(d\)/g, '<br/><br/><strong>(d)</strong>')
|
||||
.replace(/\(e\)/g, '<br/><br/><strong>(e)</strong>')
|
||||
.replace(/\(f\)/g, '<br/><br/><strong>(f)</strong>')
|
||||
.replace(/\(g\)/g, '<br/><br/><strong>(g)</strong>')
|
||||
.replace(/\(h\)/g, '<br/><br/><strong>(h)</strong>')
|
||||
.replace(/\(i\)/g, '<br/><br/><strong>(i)</strong>')
|
||||
.replace(/\(j\)/g, '<br/><br/><strong>(j)</strong>')
|
||||
.replace(/\(k\)/g, '<br/><br/><strong>(k)</strong>')
|
||||
.replace(/\(l\)/g, '<br/><br/><strong>(l)</strong>')
|
||||
// Handle pipe separators for contact information
|
||||
.replace(/ \| /g, '<br/><strong>')
|
||||
.replace(/: /g, ':</strong> ')
|
||||
// Handle semicolon with parenthesis
|
||||
.replace(/; \(/g, ';<br/><br/>(')
|
||||
// Add spacing after periods in long sentences
|
||||
.replace(/\. ([A-Z])/g, '.<br/><br/>$1')
|
||||
}} />
|
||||
{policy.sections && Array.isArray(policy.sections) && policy.sections.length > 0 ? (
|
||||
policy.sections.map((section) => (
|
||||
<div key={section.id || Math.random()} className="policy-section-item">
|
||||
<h2 className="policy-heading">{section.heading || ''}</h2>
|
||||
<div className="policy-text" dangerouslySetInnerHTML={{
|
||||
__html: sanitizeHTML(
|
||||
(section.content || '')
|
||||
// First, handle main sections with (a), (b), etc.
|
||||
.replace(/\(a\)/g, '<br/><br/><strong>(a)</strong>')
|
||||
.replace(/\(b\)/g, '<br/><br/><strong>(b)</strong>')
|
||||
.replace(/\(c\)/g, '<br/><br/><strong>(c)</strong>')
|
||||
.replace(/\(d\)/g, '<br/><br/><strong>(d)</strong>')
|
||||
.replace(/\(e\)/g, '<br/><br/><strong>(e)</strong>')
|
||||
.replace(/\(f\)/g, '<br/><br/><strong>(f)</strong>')
|
||||
.replace(/\(g\)/g, '<br/><br/><strong>(g)</strong>')
|
||||
.replace(/\(h\)/g, '<br/><br/><strong>(h)</strong>')
|
||||
.replace(/\(i\)/g, '<br/><br/><strong>(i)</strong>')
|
||||
.replace(/\(j\)/g, '<br/><br/><strong>(j)</strong>')
|
||||
.replace(/\(k\)/g, '<br/><br/><strong>(k)</strong>')
|
||||
.replace(/\(l\)/g, '<br/><br/><strong>(l)</strong>')
|
||||
// Handle pipe separators for contact information
|
||||
.replace(/ \| /g, '<br/><strong>')
|
||||
.replace(/: /g, ':</strong> ')
|
||||
// Handle semicolon with parenthesis
|
||||
.replace(/; \(/g, ';<br/><br/>(')
|
||||
// Add spacing after periods in long sentences
|
||||
.replace(/\. ([A-Z])/g, '.<br/><br/>$1')
|
||||
)
|
||||
}} />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="policy-section-item">
|
||||
<p>No content available.</p>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Contact Section */}
|
||||
@@ -423,14 +531,17 @@ const PolicyContent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Wrapper component (no longer needs Suspense since we're not using useSearchParams)
|
||||
const PolicyContentWrapper = () => {
|
||||
return <PolicyContentClient />;
|
||||
};
|
||||
|
||||
const PolicyPage = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<PolicyContent />
|
||||
</Suspense>
|
||||
<PolicyContentWrapper />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user