update
This commit is contained in:
399
gnx-react/app/policy/page.tsx
Normal file
399
gnx-react/app/policy/page.tsx
Normal file
@@ -0,0 +1,399 @@
|
||||
"use client";
|
||||
import { useSearchParams } 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';
|
||||
|
||||
const PolicyContent = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const typeParam = searchParams.get('type') || 'privacy';
|
||||
const type = typeParam as 'privacy' | 'terms' | 'support';
|
||||
|
||||
const { data: policy, isLoading, error } = usePolicy(type);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<section className="policy-section section-padding">
|
||||
<div className="container">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12 col-lg-10">
|
||||
<div className="loading-state">
|
||||
<div className="spinner"></div>
|
||||
<p>Loading policy...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.policy-section {
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
|
||||
min-height: 100vh;
|
||||
padding: 120px 0 80px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 0 auto 1rem;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #daa520;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
color: #64748b;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !policy) {
|
||||
return (
|
||||
<section className="policy-section section-padding">
|
||||
<div className="container">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12 col-lg-10">
|
||||
<div className="error-state">
|
||||
<i className="fa-solid fa-exclamation-circle"></i>
|
||||
<h2>Unable to Load Policy</h2>
|
||||
<p>{error?.message || 'The requested policy could not be found.'}</p>
|
||||
<a href="/support-center" className="btn btn-primary">Return to Support Center</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.policy-section {
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
|
||||
min-height: 100vh;
|
||||
padding: 120px 0 80px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.error-state i {
|
||||
font-size: 4rem;
|
||||
color: #ef4444;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-state h2 {
|
||||
font-size: 2rem;
|
||||
color: #1e293b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-state p {
|
||||
color: #64748b;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
display: inline-block;
|
||||
padding: 0.875rem 2rem;
|
||||
background: linear-gradient(135deg, #daa520, #d4af37);
|
||||
color: #0f172a;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 25px rgba(218, 165, 32, 0.3);
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="policy-section section-padding">
|
||||
<div className="container">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12 col-lg-10">
|
||||
{/* Policy Header */}
|
||||
<div className="policy-header">
|
||||
<h1 className="policy-title">{policy.title}</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>
|
||||
</div>
|
||||
{policy.description && (
|
||||
<p className="policy-description">{policy.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 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')
|
||||
}} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contact Section */}
|
||||
<div className="policy-footer">
|
||||
<div className="contact-box">
|
||||
<h3>Questions?</h3>
|
||||
<p>If you have any questions about this policy, please don't hesitate to contact us.</p>
|
||||
<a href="/contact-us" className="btn btn-primary">Contact Us</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.policy-section {
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
|
||||
min-height: 100vh;
|
||||
padding: 120px 0 80px;
|
||||
}
|
||||
|
||||
.policy-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.policy-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.policy-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.policy-meta p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.policy-updated,
|
||||
.policy-version,
|
||||
.policy-effective {
|
||||
color: #64748b;
|
||||
font-size: 0.95rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.policy-version {
|
||||
color: #daa520;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.policy-description {
|
||||
color: #475569;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 1rem;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.policy-content {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.policy-section-item {
|
||||
margin-bottom: 2.5rem;
|
||||
padding: 2rem;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.policy-section-item:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.policy-heading {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.policy-text {
|
||||
color: #475569;
|
||||
font-size: 1rem;
|
||||
line-height: 1.8;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.policy-text strong {
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.policy-text :global(br) {
|
||||
content: "";
|
||||
display: block;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.policy-footer {
|
||||
margin-top: 4rem;
|
||||
padding-top: 3rem;
|
||||
border-top: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.contact-box {
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
||||
padding: 3rem;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.contact-box h3 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.contact-box p {
|
||||
font-size: 1.1rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
display: inline-block;
|
||||
padding: 0.875rem 2rem;
|
||||
background: linear-gradient(135deg, #daa520, #d4af37);
|
||||
color: #0f172a;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 25px rgba(218, 165, 32, 0.3);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.policy-section {
|
||||
padding: 100px 0 60px;
|
||||
}
|
||||
|
||||
.policy-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.policy-meta {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.policy-section-item {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.policy-heading {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.contact-box {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.contact-box h3 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const PolicyPage = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<PolicyContent />
|
||||
</Suspense>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PolicyPage;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
"use client";
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import SupportCenterHero from "@/components/pages/support/SupportCenterHero";
|
||||
import SupportCenterContent from "@/components/pages/support/SupportCenterContent";
|
||||
import { useState } from "react";
|
||||
|
||||
export const metadata = {
|
||||
title: "Enterprise Support Center | GNX Software Solutions",
|
||||
description: "Get expert support with our comprehensive Enterprise Support Center. Submit tickets, browse knowledge base, and track your support requests.",
|
||||
};
|
||||
type ModalType = 'create' | 'knowledge' | 'status' | null;
|
||||
|
||||
const SupportCenterPage = () => {
|
||||
const [activeModal, setActiveModal] = useState<ModalType>(null);
|
||||
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<SupportCenterHero />
|
||||
<SupportCenterContent />
|
||||
<SupportCenterHero onFeatureClick={setActiveModal} />
|
||||
<SupportCenterContent activeModal={activeModal} onClose={() => setActiveModal(null)} />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
Binary file not shown.
@@ -12,16 +12,23 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write('Creating sample about us data...')
|
||||
|
||||
# Clear existing data first
|
||||
self.stdout.write('Clearing existing about data...')
|
||||
AboutBanner.objects.all().delete()
|
||||
AboutService.objects.all().delete()
|
||||
AboutProcess.objects.all().delete()
|
||||
AboutJourney.objects.all().delete()
|
||||
|
||||
# Create About Banner
|
||||
banner, created = AboutBanner.objects.get_or_create(
|
||||
title="Powering Enterprise Digital Transformation",
|
||||
title="Enterprise Software Solutions for Mission-Critical Industries",
|
||||
defaults={
|
||||
'subtitle': "Leading Enterprise Software Solutions",
|
||||
'description': "We are a leading enterprise software company that empowers Fortune 500 companies and growing businesses with cutting-edge technology solutions. Our mission is to accelerate digital transformation through innovative software platforms, cloud infrastructure, and data-driven insights.",
|
||||
'subtitle': "GNX Soft Ltd - Your Trusted Enterprise Technology Partner",
|
||||
'description': "GNX Soft Ltd is a leading Bulgarian enterprise software company delivering cutting-edge technology solutions to mission-critical industries worldwide. We empower organizations in Defense & Aerospace, Healthcare & Medical, Telecommunication, Banking, Public Sector, E-commerce, Food & Beverages, and Oil & Energy sectors with innovative, secure, and scalable software platforms that drive digital transformation and operational excellence.",
|
||||
'badge_text': "Enterprise Software Solutions",
|
||||
'badge_icon': "fa-solid fa-building",
|
||||
'cta_text': "Discover Enterprise Solutions",
|
||||
'cta_text': "Explore Enterprise Solutions",
|
||||
'cta_link': "services",
|
||||
'cta_icon': "fa-solid fa-arrow-trend-up",
|
||||
'is_active': True
|
||||
@@ -33,10 +40,10 @@ class Command(BaseCommand):
|
||||
|
||||
# Create Banner Stats
|
||||
stats_data = [
|
||||
{'number': '500+', 'label': 'Enterprise Clients', 'order': 1},
|
||||
{'number': '8', 'label': 'Industry Verticals', 'order': 1},
|
||||
{'number': '99.9%', 'label': 'Uptime SLA', 'order': 2},
|
||||
{'number': '24/7', 'label': 'Enterprise Support', 'order': 3},
|
||||
{'number': '15+', 'label': 'Years Experience', 'order': 4},
|
||||
{'number': '2020', 'label': 'Founded', 'order': 4},
|
||||
]
|
||||
|
||||
for stat_data in stats_data:
|
||||
@@ -46,30 +53,30 @@ class Command(BaseCommand):
|
||||
social_links_data = [
|
||||
{
|
||||
'platform': 'LinkedIn',
|
||||
'url': 'https://www.linkedin.com/company/enterprisesoft-solutions',
|
||||
'url': 'https://www.linkedin.com/company/gnxsoft',
|
||||
'icon': 'fa-brands fa-linkedin-in',
|
||||
'aria_label': 'Connect with us on LinkedIn',
|
||||
'aria_label': 'Connect with GNX Soft on LinkedIn',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'platform': 'GitHub',
|
||||
'url': 'https://github.com/enterprisesoft',
|
||||
'url': 'https://github.com/gnxsoft',
|
||||
'icon': 'fa-brands fa-github',
|
||||
'aria_label': 'Follow us on GitHub',
|
||||
'aria_label': 'Follow GNX Soft on GitHub',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'platform': 'Twitter',
|
||||
'url': 'https://www.twitter.com/enterprisesoft',
|
||||
'url': 'https://twitter.com/gnxsoft',
|
||||
'icon': 'fa-brands fa-twitter',
|
||||
'aria_label': 'Follow us on Twitter',
|
||||
'aria_label': 'Follow GNX Soft on Twitter',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'platform': 'Stack Overflow',
|
||||
'url': 'https://stackoverflow.com/teams/enterprisesoft',
|
||||
'icon': 'fa-brands fa-stack-overflow',
|
||||
'aria_label': 'Visit our Stack Overflow team',
|
||||
'platform': 'Email',
|
||||
'url': 'mailto:info@gnxsoft.com',
|
||||
'icon': 'fa-solid fa-envelope',
|
||||
'aria_label': 'Contact GNX Soft via email',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
@@ -79,14 +86,14 @@ class Command(BaseCommand):
|
||||
|
||||
# Create About Service
|
||||
service, created = AboutService.objects.get_or_create(
|
||||
title="Enterprise Technology Leaders",
|
||||
title="Enterprise Technology Excellence Across Critical Industries",
|
||||
defaults={
|
||||
'subtitle': "About Our Company",
|
||||
'description': "Founded in 2008, EnterpriseSoft Solutions has emerged as a premier enterprise software company, serving Fortune 500 companies and innovative startups worldwide. Our team of 200+ engineers, architects, and consultants specializes in delivering mission-critical software solutions that drive digital transformation and business growth.",
|
||||
'badge_text': "About Our Company",
|
||||
'subtitle': "About GNX Soft Ltd",
|
||||
'description': "Founded in 2020 and headquartered in Burgas, Bulgaria, GNX Soft Ltd is a premier enterprise software development company specializing in mission-critical solutions for highly regulated industries. Our expert team delivers secure, scalable, and compliant software solutions to Defense & Aerospace, Healthcare & Medical, Telecommunication, Banking & Finance, Public Sector, E-commerce, Food & Beverages, and Oil & Energy sectors. With EU-based infrastructure, we provide enterprise-grade solutions that meet the highest security and regulatory standards.",
|
||||
'badge_text': "About GNX Soft Ltd",
|
||||
'badge_icon': "fa-solid fa-users",
|
||||
'cta_text': "Explore Our Solutions",
|
||||
'cta_link': "service-single",
|
||||
'cta_link': "services",
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
@@ -97,27 +104,27 @@ class Command(BaseCommand):
|
||||
# Create Service Features
|
||||
features_data = [
|
||||
{
|
||||
'title': 'Enterprise Security',
|
||||
'description': 'SOC 2 Type II Certified',
|
||||
'icon': 'fa-solid fa-shield-halved',
|
||||
'title': 'EU-Based Company',
|
||||
'description': 'Headquartered in Bulgaria',
|
||||
'icon': 'fa-solid fa-building',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'title': 'Cloud Native',
|
||||
'description': 'AWS, Azure, GCP Partners',
|
||||
'icon': 'fa-solid fa-cloud',
|
||||
'title': 'EU Infrastructure',
|
||||
'description': 'Bulgaria, Germany, Netherlands',
|
||||
'icon': 'fa-solid fa-server',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'title': 'Certified Experts',
|
||||
'description': 'Microsoft, AWS, Google Certified',
|
||||
'icon': 'fa-solid fa-certificate',
|
||||
'title': '8 Industry Verticals',
|
||||
'description': 'Specialized Expertise',
|
||||
'icon': 'fa-solid fa-industry',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'title': 'Global Reach',
|
||||
'description': 'Offices in 5 Countries',
|
||||
'icon': 'fa-solid fa-globe',
|
||||
'title': '24/7 Support',
|
||||
'description': 'Enterprise Support Team',
|
||||
'icon': 'fa-solid fa-headset',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
@@ -127,14 +134,14 @@ class Command(BaseCommand):
|
||||
|
||||
# Create About Process
|
||||
process, created = AboutProcess.objects.get_or_create(
|
||||
title="Enterprise Development Process",
|
||||
title="Enterprise-Grade Development Methodology",
|
||||
defaults={
|
||||
'subtitle': "Our Methodology",
|
||||
'description': "Our proven enterprise development methodology combines agile practices with enterprise-grade security, scalability, and compliance requirements. We follow industry best practices including DevOps, CI/CD, and microservices architecture to deliver robust, scalable solutions.",
|
||||
'description': "GNX Soft Ltd employs a proven enterprise development methodology that combines agile practices with defense-grade security, regulatory compliance, and enterprise scalability. We follow industry best practices including DevOps, CI/CD, microservices architecture, and privacy-by-design principles to deliver robust, secure, and compliant solutions for highly regulated industries. Every project undergoes rigorous security assessments, Data Protection Impact Assessments (DPIAs), and compliance verification to meet the strictest industry standards.",
|
||||
'badge_text': "Our Methodology",
|
||||
'badge_icon': "fa-solid fa-cogs",
|
||||
'cta_text': "View Our Services",
|
||||
'cta_link': "service-single",
|
||||
'cta_link': "services",
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
@@ -146,26 +153,26 @@ class Command(BaseCommand):
|
||||
steps_data = [
|
||||
{
|
||||
'step_number': '01',
|
||||
'title': 'Discovery & Planning',
|
||||
'description': 'Comprehensive analysis and architecture design',
|
||||
'title': 'Discovery & Compliance',
|
||||
'description': 'Requirements analysis, regulatory assessment, and DPIA',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'step_number': '02',
|
||||
'title': 'Development & Testing',
|
||||
'description': 'Agile development with continuous testing',
|
||||
'title': 'Secure Development',
|
||||
'description': 'Privacy-by-design, secure coding, continuous testing',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'step_number': '03',
|
||||
'title': 'Deployment & Integration',
|
||||
'description': 'Seamless deployment and system integration',
|
||||
'description': 'EU infrastructure deployment and system integration',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'step_number': '04',
|
||||
'title': 'Support & Maintenance',
|
||||
'description': '24/7 enterprise support and maintenance',
|
||||
'title': 'Support & Monitoring',
|
||||
'description': '24/7 enterprise support with breach response',
|
||||
'order': 4
|
||||
},
|
||||
]
|
||||
@@ -175,10 +182,10 @@ class Command(BaseCommand):
|
||||
|
||||
# Create About Journey
|
||||
journey, created = AboutJourney.objects.get_or_create(
|
||||
title="From Startup to Enterprise Leader",
|
||||
title="Building Enterprise Excellence Since 2020",
|
||||
defaults={
|
||||
'subtitle': "Our Journey",
|
||||
'description': "Founded in 2008 by three visionary engineers, Itify Technologies began as a small startup with a big dream: to revolutionize how enterprises approach software development. What started as a passion project has grown into a global enterprise software company serving Fortune 500 clients worldwide.",
|
||||
'description': "Founded in 2020 in Burgas, Bulgaria, GNX Soft Ltd was established with a clear mission: to deliver world-class enterprise software solutions for mission-critical industries while maintaining the highest standards of security, compliance, and data protection. From our inception, we focused exclusively on enterprise clients in highly regulated sectors including Defense & Aerospace, Healthcare & Medical, Banking, Public Sector, Telecommunication, E-commerce, Food & Beverages, and Oil & Energy. Our commitment to EU-based infrastructure and privacy-by-design principles has positioned us as a trusted technology partner for organizations that demand the highest levels of security and regulatory adherence.",
|
||||
'badge_text': "Our Journey",
|
||||
'badge_icon': "fa-solid fa-rocket",
|
||||
'cta_text': "Explore Solutions",
|
||||
@@ -193,23 +200,35 @@ class Command(BaseCommand):
|
||||
# Create Journey Milestones
|
||||
milestones_data = [
|
||||
{
|
||||
'year': '2008',
|
||||
'year': '2020',
|
||||
'title': 'Company Founded',
|
||||
'description': 'Started with 3 engineers',
|
||||
'description': 'GNX Soft Ltd established in Burgas, Bulgaria',
|
||||
'order': 1
|
||||
},
|
||||
{
|
||||
'year': '2015',
|
||||
'title': 'Enterprise Focus',
|
||||
'description': 'Pivoted to enterprise solutions',
|
||||
'year': '2021',
|
||||
'title': 'Industry Focus',
|
||||
'description': 'Specialized in 8 mission-critical industries',
|
||||
'order': 2
|
||||
},
|
||||
{
|
||||
'year': '2020',
|
||||
'title': 'Global Expansion',
|
||||
'description': 'Opened offices in 5 countries',
|
||||
'year': '2022',
|
||||
'title': 'Industry Expansion',
|
||||
'description': 'Expanded to 8 specialized industry verticals',
|
||||
'order': 3
|
||||
},
|
||||
{
|
||||
'year': '2023',
|
||||
'title': 'EU Infrastructure',
|
||||
'description': 'Deployed EU-wide data centers across 3 countries',
|
||||
'order': 4
|
||||
},
|
||||
{
|
||||
'year': '2024',
|
||||
'title': 'Enterprise Leader',
|
||||
'description': 'Recognized as leading Bulgarian enterprise software provider',
|
||||
'order': 5
|
||||
},
|
||||
]
|
||||
|
||||
for milestone_data in milestones_data:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -54,6 +54,7 @@ INSTALLED_APPS = [
|
||||
'support',
|
||||
'blog',
|
||||
'case_studies',
|
||||
'policies',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -53,6 +53,7 @@ urlpatterns = [
|
||||
path('support/', include('support.urls')),
|
||||
path('blog/', include('blog.urls')),
|
||||
path('case-studies/', include('case_studies.urls')),
|
||||
path('policies/', include('policies.urls')),
|
||||
])),
|
||||
]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
0
gnx-react/backend/policies/__init__.py
Normal file
0
gnx-react/backend/policies/__init__.py
Normal file
BIN
gnx-react/backend/policies/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
gnx-react/backend/policies/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gnx-react/backend/policies/__pycache__/admin.cpython-312.pyc
Normal file
BIN
gnx-react/backend/policies/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gnx-react/backend/policies/__pycache__/apps.cpython-312.pyc
Normal file
BIN
gnx-react/backend/policies/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gnx-react/backend/policies/__pycache__/models.cpython-312.pyc
Normal file
BIN
gnx-react/backend/policies/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
gnx-react/backend/policies/__pycache__/urls.cpython-312.pyc
Normal file
BIN
gnx-react/backend/policies/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gnx-react/backend/policies/__pycache__/views.cpython-312.pyc
Normal file
BIN
gnx-react/backend/policies/__pycache__/views.cpython-312.pyc
Normal file
Binary file not shown.
38
gnx-react/backend/policies/admin.py
Normal file
38
gnx-react/backend/policies/admin.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from django.contrib import admin
|
||||
from .models import Policy, PolicySection
|
||||
|
||||
|
||||
class PolicySectionInline(admin.TabularInline):
|
||||
model = PolicySection
|
||||
extra = 1
|
||||
fields = ['heading', 'content', 'order', 'is_active']
|
||||
|
||||
|
||||
@admin.register(Policy)
|
||||
class PolicyAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'type', 'version', 'last_updated', 'effective_date', 'is_active']
|
||||
list_filter = ['type', 'is_active', 'last_updated']
|
||||
search_fields = ['title', 'type', 'description']
|
||||
prepopulated_fields = {'slug': ('type',)}
|
||||
inlines = [PolicySectionInline]
|
||||
|
||||
fieldsets = (
|
||||
('Basic Information', {
|
||||
'fields': ('type', 'title', 'slug', 'description')
|
||||
}),
|
||||
('Version & Dates', {
|
||||
'fields': ('version', 'effective_date', 'last_updated')
|
||||
}),
|
||||
('Status', {
|
||||
'fields': ('is_active',)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(PolicySection)
|
||||
class PolicySectionAdmin(admin.ModelAdmin):
|
||||
list_display = ['policy', 'heading', 'order', 'is_active']
|
||||
list_filter = ['policy__type', 'is_active']
|
||||
search_fields = ['heading', 'content']
|
||||
list_editable = ['order', 'is_active']
|
||||
|
||||
8
gnx-react/backend/policies/apps.py
Normal file
8
gnx-react/backend/policies/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PoliciesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'policies'
|
||||
verbose_name = 'Policies Management'
|
||||
|
||||
0
gnx-react/backend/policies/management/__init__.py
Normal file
0
gnx-react/backend/policies/management/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
54
gnx-react/backend/policies/migrations/0001_initial.py
Normal file
54
gnx-react/backend/policies/migrations/0001_initial.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Generated by Django 4.2.7 on 2025-10-08 13:54
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Policy',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('type', models.CharField(choices=[('privacy', 'Privacy Policy'), ('terms', 'Terms of Use'), ('support', 'Support Policy')], help_text='Type of policy document', max_length=50, unique=True)),
|
||||
('title', models.CharField(help_text='Title of the policy', max_length=200)),
|
||||
('slug', models.SlugField(blank=True, max_length=100, unique=True)),
|
||||
('description', models.TextField(blank=True, help_text='Brief description of the policy')),
|
||||
('last_updated', models.DateField(auto_now=True, help_text='Last update date')),
|
||||
('version', models.CharField(default='1.0', help_text='Policy version number', max_length=20)),
|
||||
('is_active', models.BooleanField(default=True, help_text='Whether this policy is currently active')),
|
||||
('effective_date', models.DateField(help_text='Date when this policy becomes effective')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Policy',
|
||||
'verbose_name_plural': 'Policies',
|
||||
'ordering': ['type'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PolicySection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('heading', models.CharField(help_text='Section heading', max_length=300)),
|
||||
('content', models.TextField(help_text='Section content')),
|
||||
('order', models.IntegerField(default=0, help_text='Display order of sections')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Whether this section is currently active')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('policy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='policies.policy')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Policy Section',
|
||||
'verbose_name_plural': 'Policy Sections',
|
||||
'ordering': ['policy', 'order'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
gnx-react/backend/policies/migrations/__init__.py
Normal file
0
gnx-react/backend/policies/migrations/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
100
gnx-react/backend/policies/models.py
Normal file
100
gnx-react/backend/policies/models.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
|
||||
class Policy(models.Model):
|
||||
"""
|
||||
Model to store various policy documents (Privacy, Terms, Support, etc.)
|
||||
"""
|
||||
POLICY_TYPES = [
|
||||
('privacy', 'Privacy Policy'),
|
||||
('terms', 'Terms of Use'),
|
||||
('support', 'Support Policy'),
|
||||
]
|
||||
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=POLICY_TYPES,
|
||||
unique=True,
|
||||
help_text="Type of policy document"
|
||||
)
|
||||
title = models.CharField(
|
||||
max_length=200,
|
||||
help_text="Title of the policy"
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True,
|
||||
blank=True
|
||||
)
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Brief description of the policy"
|
||||
)
|
||||
last_updated = models.DateField(
|
||||
auto_now=True,
|
||||
help_text="Last update date"
|
||||
)
|
||||
version = models.CharField(
|
||||
max_length=20,
|
||||
default="1.0",
|
||||
help_text="Policy version number"
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Whether this policy is currently active"
|
||||
)
|
||||
effective_date = models.DateField(
|
||||
help_text="Date when this policy becomes effective"
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Policy"
|
||||
verbose_name_plural = "Policies"
|
||||
ordering = ['type']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.type)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_type_display()} (v{self.version})"
|
||||
|
||||
|
||||
class PolicySection(models.Model):
|
||||
"""
|
||||
Individual sections within a policy document
|
||||
"""
|
||||
policy = models.ForeignKey(
|
||||
Policy,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='sections'
|
||||
)
|
||||
heading = models.CharField(
|
||||
max_length=300,
|
||||
help_text="Section heading"
|
||||
)
|
||||
content = models.TextField(
|
||||
help_text="Section content"
|
||||
)
|
||||
order = models.IntegerField(
|
||||
default=0,
|
||||
help_text="Display order of sections"
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Whether this section is currently active"
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Policy Section"
|
||||
verbose_name_plural = "Policy Sections"
|
||||
ordering = ['policy', 'order']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.policy.type} - {self.heading}"
|
||||
|
||||
47
gnx-react/backend/policies/serializers.py
Normal file
47
gnx-react/backend/policies/serializers.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Policy, PolicySection
|
||||
|
||||
|
||||
class PolicySectionSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for policy sections"""
|
||||
|
||||
class Meta:
|
||||
model = PolicySection
|
||||
fields = ['id', 'heading', 'content', 'order']
|
||||
|
||||
|
||||
class PolicySerializer(serializers.ModelSerializer):
|
||||
"""Serializer for policies with their sections"""
|
||||
|
||||
sections = PolicySectionSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Policy
|
||||
fields = [
|
||||
'id',
|
||||
'type',
|
||||
'title',
|
||||
'slug',
|
||||
'description',
|
||||
'last_updated',
|
||||
'version',
|
||||
'effective_date',
|
||||
'sections'
|
||||
]
|
||||
|
||||
|
||||
class PolicyListSerializer(serializers.ModelSerializer):
|
||||
"""Simplified serializer for policy listing"""
|
||||
|
||||
class Meta:
|
||||
model = Policy
|
||||
fields = [
|
||||
'id',
|
||||
'type',
|
||||
'title',
|
||||
'slug',
|
||||
'description',
|
||||
'last_updated',
|
||||
'version'
|
||||
]
|
||||
|
||||
4
gnx-react/backend/policies/tests.py
Normal file
4
gnx-react/backend/policies/tests.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
11
gnx-react/backend/policies/urls.py
Normal file
11
gnx-react/backend/policies/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import PolicyViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'', PolicyViewSet, basename='policy')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
||||
52
gnx-react/backend/policies/views.py
Normal file
52
gnx-react/backend/policies/views.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django.shortcuts import get_object_or_404
|
||||
from .models import Policy, PolicySection
|
||||
from .serializers import PolicySerializer, PolicyListSerializer
|
||||
|
||||
|
||||
class PolicyViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for viewing policies.
|
||||
Provides list and retrieve actions.
|
||||
"""
|
||||
queryset = Policy.objects.filter(is_active=True)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return PolicyListSerializer
|
||||
return PolicySerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Policy.objects.filter(is_active=True)
|
||||
policy_type = self.request.query_params.get('type', None)
|
||||
|
||||
if policy_type:
|
||||
queryset = queryset.filter(type=policy_type)
|
||||
|
||||
return queryset
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
"""
|
||||
Retrieve a policy by ID or type
|
||||
"""
|
||||
# Try to get by ID first
|
||||
if pk.isdigit():
|
||||
policy = get_object_or_404(Policy, pk=pk, is_active=True)
|
||||
else:
|
||||
# Otherwise try by type (slug)
|
||||
policy = get_object_or_404(Policy, type=pk, is_active=True)
|
||||
|
||||
serializer = self.get_serializer(policy)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='by-type/(?P<policy_type>[^/.]+)')
|
||||
def by_type(self, request, policy_type=None):
|
||||
"""
|
||||
Get a specific policy by its type
|
||||
"""
|
||||
policy = get_object_or_404(Policy, type=policy_type, is_active=True)
|
||||
serializer = PolicySerializer(policy)
|
||||
return Response(serializer.data)
|
||||
|
||||
@@ -62,10 +62,10 @@ const AboutBanner = () => {
|
||||
const bannerData = data?.banner;
|
||||
|
||||
const metrics = [
|
||||
{ value: "500+", label: "Fortune 500 Clients", icon: "fa-building", color: "#3b82f6" },
|
||||
{ value: "99.9%", label: "Uptime Guarantee", icon: "fa-shield-halved", color: "#10b981" },
|
||||
{ value: "8", label: "Industry Verticals", icon: "fa-industry", color: "#3b82f6" },
|
||||
{ value: "99.9%", label: "Uptime SLA", icon: "fa-shield-halved", color: "#10b981" },
|
||||
{ value: "24/7", label: "Enterprise Support", icon: "fa-headset", color: "#f59e0b" },
|
||||
{ value: "15+", label: "Years Experience", icon: "fa-award", color: "#8b5cf6" }
|
||||
{ value: "5+", label: "Years of Operation", icon: "fa-award", color: "#8b5cf6" }
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -187,7 +187,7 @@ const AboutBanner = () => {
|
||||
|
||||
{/* Description */}
|
||||
<p className="hero-description">
|
||||
{bannerData?.description || "Trusted by Fortune 500 companies worldwide, we deliver enterprise-grade software solutions with 99.9% uptime SLA, SOC 2 Type II certification, and 24/7 dedicated support. Our mission-critical platforms power digital transformation across industries."}
|
||||
{bannerData?.description || "GNX Soft Ltd delivers enterprise-grade software solutions for mission-critical industries with 99.9% uptime SLA and 24/7 dedicated support. Our platforms power digital transformation across Defense & Aerospace, Healthcare, Banking, Telecommunication, and other highly regulated sectors."}
|
||||
</p>
|
||||
|
||||
{/* Key Metrics */}
|
||||
@@ -220,30 +220,6 @@ const AboutBanner = () => {
|
||||
<i className="fa-solid fa-calendar-check"></i>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Trust Badges */}
|
||||
<div className="trust-badges">
|
||||
<div className="trust-badge">
|
||||
<i className="fa-solid fa-shield-check"></i>
|
||||
<span>SOC 2 Type II</span>
|
||||
</div>
|
||||
<div className="trust-badge">
|
||||
<i className="fa-solid fa-lock"></i>
|
||||
<span>ISO 27001</span>
|
||||
</div>
|
||||
<div className="trust-badge">
|
||||
<i className="fa-solid fa-certificate"></i>
|
||||
<span>GDPR Compliant</span>
|
||||
</div>
|
||||
<div className="trust-badge">
|
||||
<i className="fa-solid fa-globe"></i>
|
||||
<span>Global Operations</span>
|
||||
</div>
|
||||
<div className="trust-badge">
|
||||
<i className="fa-solid fa-award"></i>
|
||||
<span>Microsoft Partner</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -111,7 +111,7 @@ const AboutServiceComponent = () => {
|
||||
</div>
|
||||
<div className="feature-content">
|
||||
<h6>Enterprise Security</h6>
|
||||
<p>SOC 2 Type II Certified</p>
|
||||
<p>Defense-Grade Protection</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,53 +1,165 @@
|
||||
"use client";
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import CreateTicketForm from './CreateTicketForm';
|
||||
import KnowledgeBase from './KnowledgeBase';
|
||||
import TicketStatusCheck from './TicketStatusCheck';
|
||||
|
||||
type TabType = 'create' | 'knowledge' | 'status';
|
||||
type ModalType = 'create' | 'knowledge' | 'status' | null;
|
||||
|
||||
const SupportCenterContent = () => {
|
||||
const [activeTab, setActiveTab] = useState<TabType>('create');
|
||||
interface SupportCenterContentProps {
|
||||
activeModal: ModalType;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const SupportCenterContent = ({ activeModal, onClose }: SupportCenterContentProps) => {
|
||||
// Close modal on escape key
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && activeModal) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
return () => document.removeEventListener('keydown', handleEscape);
|
||||
}, [activeModal, onClose]);
|
||||
|
||||
// Prevent body scroll when modal is open
|
||||
useEffect(() => {
|
||||
if (activeModal) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}, [activeModal]);
|
||||
|
||||
if (!activeModal) return null;
|
||||
|
||||
return (
|
||||
<section className="support-center-content section-padding">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
{/* Tab Navigation */}
|
||||
<div className="support-tabs">
|
||||
<ul className="support-tabs__nav">
|
||||
<li className={activeTab === 'create' ? 'active' : ''}>
|
||||
<button onClick={() => setActiveTab('create')}>
|
||||
<i className="fa-solid fa-ticket me-2"></i>
|
||||
Submit a Ticket
|
||||
</button>
|
||||
</li>
|
||||
<li className={activeTab === 'knowledge' ? 'active' : ''}>
|
||||
<button onClick={() => setActiveTab('knowledge')}>
|
||||
<i className="fa-solid fa-book me-2"></i>
|
||||
Knowledge Base
|
||||
</button>
|
||||
</li>
|
||||
<li className={activeTab === 'status' ? 'active' : ''}>
|
||||
<button onClick={() => setActiveTab('status')}>
|
||||
<i className="fa-solid fa-search me-2"></i>
|
||||
Check Ticket Status
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<>
|
||||
{/* Modal Overlay */}
|
||||
<div
|
||||
className="support-modal-overlay"
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
zIndex: 9998,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '20px',
|
||||
backdropFilter: 'blur(5px)',
|
||||
}}
|
||||
>
|
||||
{/* Modal Content */}
|
||||
<div
|
||||
className="support-modal-content"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '12px',
|
||||
maxWidth: '1000px',
|
||||
width: '100%',
|
||||
maxHeight: '90vh',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
animation: 'modalSlideIn 0.3s ease-out',
|
||||
}}
|
||||
>
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="support-modal-close"
|
||||
aria-label="Close modal"
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: '20px',
|
||||
right: '20px',
|
||||
float: 'right',
|
||||
background: '#f3f4f6',
|
||||
border: 'none',
|
||||
borderRadius: '50%',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
fontSize: '20px',
|
||||
color: '#374151',
|
||||
transition: 'all 0.2s',
|
||||
zIndex: 10,
|
||||
marginBottom: '-40px',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = '#e5e7eb';
|
||||
e.currentTarget.style.transform = 'scale(1.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = '#f3f4f6';
|
||||
e.currentTarget.style.transform = 'scale(1)';
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-times"></i>
|
||||
</button>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="support-tabs__content">
|
||||
{activeTab === 'create' && <CreateTicketForm />}
|
||||
{activeTab === 'knowledge' && <KnowledgeBase />}
|
||||
{activeTab === 'status' && <TicketStatusCheck />}
|
||||
</div>
|
||||
</div>
|
||||
{/* Modal Body */}
|
||||
<div className="support-modal-body" style={{ padding: '40px' }}>
|
||||
{activeModal === 'create' && <CreateTicketForm />}
|
||||
{activeModal === 'knowledge' && <KnowledgeBase />}
|
||||
{activeModal === 'status' && <TicketStatusCheck />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Modal Animation Keyframes */}
|
||||
<style jsx>{`
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.support-modal-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.support-modal-content::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.support-modal-content::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.support-modal-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.support-modal-body {
|
||||
padding: 20px !important;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
"use client";
|
||||
|
||||
const SupportCenterHero = () => {
|
||||
type ModalType = 'create' | 'knowledge' | 'status' | null;
|
||||
|
||||
interface SupportCenterHeroProps {
|
||||
onFeatureClick: (type: ModalType) => void;
|
||||
}
|
||||
|
||||
const SupportCenterHero = ({ onFeatureClick }: SupportCenterHeroProps) => {
|
||||
return (
|
||||
<section className="support-hero">
|
||||
{/* Animated Background */}
|
||||
@@ -49,9 +55,15 @@ const SupportCenterHero = () => {
|
||||
</p>
|
||||
|
||||
<div className="support-hero__features">
|
||||
<div className="row g-4 justify-content-center">
|
||||
<div className="col-md-4">
|
||||
<div className="feature-item">
|
||||
<div className="row g-3 g-md-4 g-lg-5 justify-content-center">
|
||||
<div className="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<div
|
||||
className="feature-item clickable"
|
||||
onClick={() => onFeatureClick('create')}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onFeatureClick('create')}
|
||||
>
|
||||
<div className="feature-icon">
|
||||
<i className="fa-solid fa-ticket"></i>
|
||||
</div>
|
||||
@@ -59,8 +71,14 @@ const SupportCenterHero = () => {
|
||||
<p>Create and track support requests</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="feature-item">
|
||||
<div className="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<div
|
||||
className="feature-item clickable"
|
||||
onClick={() => onFeatureClick('knowledge')}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onFeatureClick('knowledge')}
|
||||
>
|
||||
<div className="feature-icon">
|
||||
<i className="fa-solid fa-book"></i>
|
||||
</div>
|
||||
@@ -68,8 +86,14 @@ const SupportCenterHero = () => {
|
||||
<p>Find answers to common questions</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="feature-item">
|
||||
<div className="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<div
|
||||
className="feature-item clickable"
|
||||
onClick={() => onFeatureClick('status')}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onFeatureClick('status')}
|
||||
>
|
||||
<div className="feature-icon">
|
||||
<i className="fa-solid fa-search"></i>
|
||||
</div>
|
||||
@@ -77,6 +101,42 @@ const SupportCenterHero = () => {
|
||||
<p>Monitor your ticket progress</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<a
|
||||
href="/policy?type=privacy"
|
||||
className="feature-item clickable link-item"
|
||||
>
|
||||
<div className="feature-icon">
|
||||
<i className="fa-solid fa-shield-halved"></i>
|
||||
</div>
|
||||
<h3>Privacy Policy</h3>
|
||||
<p>Learn about data protection</p>
|
||||
</a>
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<a
|
||||
href="/policy?type=terms"
|
||||
className="feature-item clickable link-item"
|
||||
>
|
||||
<div className="feature-icon">
|
||||
<i className="fa-solid fa-file-contract"></i>
|
||||
</div>
|
||||
<h3>Terms of Use</h3>
|
||||
<p>Review our service terms</p>
|
||||
</a>
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<a
|
||||
href="/policy?type=support"
|
||||
className="feature-item clickable link-item"
|
||||
>
|
||||
<div className="feature-icon">
|
||||
<i className="fa-solid fa-headset"></i>
|
||||
</div>
|
||||
<h3>Support Policy</h3>
|
||||
<p>Understand our support coverage</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
118
gnx-react/lib/api/policyService.ts
Normal file
118
gnx-react/lib/api/policyService.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { API_BASE_URL } from '../config/api';
|
||||
|
||||
export interface PolicySection {
|
||||
id: number;
|
||||
heading: string;
|
||||
content: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface Policy {
|
||||
id: number;
|
||||
type: 'privacy' | 'terms' | 'support';
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
last_updated: string;
|
||||
version: string;
|
||||
effective_date: string;
|
||||
sections: PolicySection[];
|
||||
}
|
||||
|
||||
export interface PolicyListItem {
|
||||
id: number;
|
||||
type: 'privacy' | 'terms' | 'support';
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
last_updated: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
class PolicyServiceAPI {
|
||||
private baseUrl = `${API_BASE_URL}/api/policies`;
|
||||
|
||||
/**
|
||||
* Get all policies
|
||||
*/
|
||||
async getPolicies(): Promise<PolicyListItem[]> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.results || data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching policies:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific policy by type
|
||||
*/
|
||||
async getPolicyByType(type: 'privacy' | 'terms' | 'support'): Promise<Policy> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/${type}/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching policy ${type}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific policy by ID
|
||||
*/
|
||||
async getPolicyById(id: number): Promise<Policy> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/${id}/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching policy ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton instance
|
||||
export const policyService = new PolicyServiceAPI();
|
||||
|
||||
// Export individual functions for convenience
|
||||
export const getPolicies = () => policyService.getPolicies();
|
||||
export const getPolicyByType = (type: 'privacy' | 'terms' | 'support') => policyService.getPolicyByType(type);
|
||||
export const getPolicyById = (id: number) => policyService.getPolicyById(id);
|
||||
|
||||
export default policyService;
|
||||
|
||||
|
||||
131
gnx-react/lib/hooks/usePolicy.ts
Normal file
131
gnx-react/lib/hooks/usePolicy.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Policy, PolicyListItem, getPolicies, getPolicyByType, getPolicyById } from '../api/policyService';
|
||||
|
||||
interface UsePoliciesReturn {
|
||||
data: PolicyListItem[] | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface UsePolicyReturn {
|
||||
data: Policy | null;
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch all policies
|
||||
*/
|
||||
export const usePolicies = (): UsePoliciesReturn => {
|
||||
const [data, setData] = useState<PolicyListItem[] | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const result = await getPolicies();
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
console.error('Error fetching policies:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
refetch: fetchData,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to fetch a policy by type
|
||||
*/
|
||||
export const usePolicy = (type: 'privacy' | 'terms' | 'support' | null): UsePolicyReturn => {
|
||||
const [data, setData] = useState<Policy | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!type) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const result = await getPolicyByType(type);
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err : new Error('An error occurred'));
|
||||
console.error('Error fetching policy:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [type]);
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
refetch: fetchData,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to fetch a policy by ID
|
||||
*/
|
||||
export const usePolicyById = (id: number | null): UsePolicyReturn => {
|
||||
const [data, setData] = useState<Policy | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!id) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
const result = await getPolicyById(id);
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err : new Error('An error occurred'));
|
||||
console.error('Error fetching policy:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [id]);
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
refetch: fetchData,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -192,14 +192,84 @@
|
||||
&__features {
|
||||
margin-top: 4rem;
|
||||
|
||||
.row {
|
||||
row-gap: 1.5rem !important;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
row-gap: 2rem !important;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
row-gap: 2.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all columns have the same height
|
||||
[class*="col-"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
padding: 2rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
|
||||
// Ensure consistent sizing on all screens
|
||||
@media (max-width: 575px) {
|
||||
min-height: 180px;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) and (max-width: 767px) {
|
||||
min-height: 190px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
min-height: 210px;
|
||||
}
|
||||
|
||||
&.link-item {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-color: rgba(218, 165, 32, 0.5);
|
||||
box-shadow: 0 10px 30px rgba(218, 165, 32, 0.2);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-6px) scale(1.01);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
@@ -210,7 +280,8 @@
|
||||
.feature-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin: 0 auto 1.5rem;
|
||||
margin: 0 0 1.5rem 0;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(135deg, var(--enterprise-gold), #d4af37);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
@@ -218,19 +289,37 @@
|
||||
justify-content: center;
|
||||
font-size: 1.8rem;
|
||||
color: #0f172a;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
@media (max-width: 575px) {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.5rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
|
||||
@media (max-width: 575px) {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
|
||||
@media (max-width: 575px) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user