update
This commit is contained in:
35
frontEnd/app/about-us/page.tsx
Normal file
35
frontEnd/app/about-us/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
import { useEffect } from 'react';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import AboutBanner from "@/components/pages/about/AboutBanner";
|
||||
import AboutServiceComponent from "@/components/pages/about/AboutService";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import AboutScrollProgressButton from "@/components/pages/about/AboutScrollProgressButton";
|
||||
import AboutInitAnimations from "@/components/pages/about/AboutInitAnimations";
|
||||
import AboutStarter from "@/components/pages/about/AboutStarter";
|
||||
|
||||
// Note: Since this is a client component, we'll set metadata via useEffect
|
||||
const AboutUsPage = () => {
|
||||
useEffect(() => {
|
||||
document.title = "About Us - Enterprise Software Development Company | GNX Soft";
|
||||
const metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (metaDescription) {
|
||||
metaDescription.setAttribute('content', 'Learn about GNX Soft - a leading enterprise software development company with expertise in custom software, data replication, AI business intelligence, and comprehensive IT solutions.');
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<div className="enterprise-about-page">
|
||||
<Header />
|
||||
<main>
|
||||
<AboutBanner />
|
||||
<AboutServiceComponent />
|
||||
<AboutStarter />
|
||||
</main>
|
||||
<Footer />
|
||||
<AboutScrollProgressButton />
|
||||
<AboutInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUsPage;
|
||||
79
frontEnd/app/career/[slug]/page.tsx
Normal file
79
frontEnd/app/career/[slug]/page.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import JobSingle from "@/components/pages/career/JobSingle";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import CareerScrollProgressButton from "@/components/pages/career/CareerScrollProgressButton";
|
||||
import CareerInitAnimations from "@/components/pages/career/CareerInitAnimations";
|
||||
import { useJob } from "@/lib/hooks/useCareer";
|
||||
|
||||
const JobPage = () => {
|
||||
const params = useParams();
|
||||
const slug = params?.slug as string;
|
||||
const { job, loading, error } = useJob(slug);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<section className="pt-120 pb-120">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-12 text-center">
|
||||
<h2>Loading job details...</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
<CareerScrollProgressButton />
|
||||
<CareerInitAnimations />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !job) {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<section className="pt-120 pb-120">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-12 text-center">
|
||||
<h2 className="text-danger">Job Not Found</h2>
|
||||
<p className="mt-24">
|
||||
The job position you are looking for does not exist or is no longer available.
|
||||
</p>
|
||||
<a href="/career" className="btn mt-40">
|
||||
View All Positions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
<CareerScrollProgressButton />
|
||||
<CareerInitAnimations />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<JobSingle job={job} />
|
||||
</main>
|
||||
<Footer />
|
||||
<CareerScrollProgressButton />
|
||||
<CareerInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobPage;
|
||||
41
frontEnd/app/career/page.tsx
Normal file
41
frontEnd/app/career/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Metadata } from 'next';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import CareerBanner from "@/components/pages/career/CareerBanner";
|
||||
import OpenPosition from "@/components/pages/career/OpenPosition";
|
||||
import Thrive from "@/components/pages/career/Thrive";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import CareerScrollProgressButton from "@/components/pages/career/CareerScrollProgressButton";
|
||||
import CareerInitAnimations from "@/components/pages/career/CareerInitAnimations";
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
|
||||
export const metadata: Metadata = createMetadata({
|
||||
title: "Careers - Join Our Team",
|
||||
description: "Explore career opportunities at GNX Soft. Join our team of talented professionals working on cutting-edge enterprise software solutions. View open positions and apply today.",
|
||||
keywords: [
|
||||
"Careers",
|
||||
"Job Openings",
|
||||
"Software Development Jobs",
|
||||
"Join Our Team",
|
||||
"Tech Careers",
|
||||
"Employment Opportunities",
|
||||
],
|
||||
url: "/career",
|
||||
});
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<CareerBanner />
|
||||
<OpenPosition />
|
||||
<Thrive />
|
||||
</main>
|
||||
<Footer />
|
||||
<CareerScrollProgressButton />
|
||||
<CareerInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
33
frontEnd/app/case-study/[slug]/page.tsx
Normal file
33
frontEnd/app/case-study/[slug]/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import CaseSingle from "@/components/pages/case-study/CaseSingle";
|
||||
import Process from "@/components/pages/case-study/Process";
|
||||
import RelatedCase from "@/components/pages/case-study/RelatedCase";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import CaseStudyScrollProgressButton from "@/components/pages/case-study/CaseStudyScrollProgressButton";
|
||||
import CaseStudyInitAnimations from "@/components/pages/case-study/CaseStudyInitAnimations";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
slug: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const page = async ({ params }: PageProps) => {
|
||||
const { slug } = await params;
|
||||
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<CaseSingle slug={slug} />
|
||||
<Process slug={slug} />
|
||||
<RelatedCase slug={slug} />
|
||||
</main>
|
||||
<Footer />
|
||||
<CaseStudyScrollProgressButton />
|
||||
<CaseStudyInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
37
frontEnd/app/case-study/page.tsx
Normal file
37
frontEnd/app/case-study/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Metadata } from 'next';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import CaseItems from "@/components/pages/case-study/CaseItems";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import CaseStudyScrollProgressButton from "@/components/pages/case-study/CaseStudyScrollProgressButton";
|
||||
import CaseStudyInitAnimations from "@/components/pages/case-study/CaseStudyInitAnimations";
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
|
||||
export const metadata: Metadata = createMetadata({
|
||||
title: "Case Studies - Success Stories & Client Projects",
|
||||
description: "Explore our case studies showcasing successful enterprise software development projects, client success stories, and real-world implementations of our technology solutions.",
|
||||
keywords: [
|
||||
"Case Studies",
|
||||
"Success Stories",
|
||||
"Client Projects",
|
||||
"Software Development Portfolio",
|
||||
"Enterprise Solutions Examples",
|
||||
"Client Testimonials",
|
||||
],
|
||||
url: "/case-study",
|
||||
});
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<CaseItems />
|
||||
</main>
|
||||
<Footer />
|
||||
<CaseStudyScrollProgressButton />
|
||||
<CaseStudyInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
36
frontEnd/app/contact-us/page.tsx
Normal file
36
frontEnd/app/contact-us/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Metadata } from 'next';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import ContactSection from "@/components/pages/contact/ContactSection";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import ContactScrollProgressButton from "@/components/pages/contact/ContactScrollProgressButton";
|
||||
import ContactInitAnimations from "@/components/pages/contact/ContactInitAnimations";
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
|
||||
export const metadata: Metadata = createMetadata({
|
||||
title: "Contact Us - Get in Touch with Our Team",
|
||||
description: "Contact GNX Soft for enterprise software development solutions. Get a free consultation, discuss your project requirements, or request a quote for our services.",
|
||||
keywords: [
|
||||
"Contact GNX Soft",
|
||||
"Software Development Quote",
|
||||
"Enterprise Solutions Consultation",
|
||||
"Custom Software Inquiry",
|
||||
"Get in Touch",
|
||||
],
|
||||
url: "/contact-us",
|
||||
});
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<ContactSection />
|
||||
</main>
|
||||
<Footer />
|
||||
<ContactScrollProgressButton />
|
||||
<ContactInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
23
frontEnd/app/insights/[slug]/page.tsx
Normal file
23
frontEnd/app/insights/[slug]/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import BlogSingle from "@/components/pages/blog/BlogSingle";
|
||||
import LatestPost from "@/components/pages/blog/LatestPost";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import BlogScrollProgressButton from "@/components/pages/blog/BlogScrollProgressButton";
|
||||
import BlogInitAnimations from "@/components/pages/blog/BlogInitAnimations";
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<BlogSingle />
|
||||
<LatestPost />
|
||||
</main>
|
||||
<Footer />
|
||||
<BlogScrollProgressButton />
|
||||
<BlogInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
37
frontEnd/app/insights/page.tsx
Normal file
37
frontEnd/app/insights/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Metadata } from 'next';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import BlogItems from "@/components/pages/blog/BlogItems";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import BlogScrollProgressButton from "@/components/pages/blog/BlogScrollProgressButton";
|
||||
import BlogInitAnimations from "@/components/pages/blog/BlogInitAnimations";
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
|
||||
export const metadata: Metadata = createMetadata({
|
||||
title: "Insights & Blog - Technology Trends & Best Practices",
|
||||
description: "Stay updated with the latest insights on enterprise software development, technology trends, best practices, and industry news from GNX Soft's expert team.",
|
||||
keywords: [
|
||||
"Technology Blog",
|
||||
"Software Development Insights",
|
||||
"Tech Trends",
|
||||
"Enterprise Software Blog",
|
||||
"Development Best Practices",
|
||||
"Industry News",
|
||||
],
|
||||
url: "/insights",
|
||||
});
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<BlogItems />
|
||||
</main>
|
||||
<Footer />
|
||||
<BlogScrollProgressButton />
|
||||
<BlogInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
166
frontEnd/app/layout.tsx
Normal file
166
frontEnd/app/layout.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter, Montserrat } from "next/font/google";
|
||||
import "@/public/styles/main.scss";
|
||||
import { CookieConsentProvider } from "@/components/shared/layout/CookieConsentContext";
|
||||
import { CookieConsent } from "@/components/shared/layout/CookieConsent";
|
||||
import LayoutWrapper from "@/components/shared/layout/LayoutWrapper";
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
import { OrganizationSchema, WebsiteSchema, LocalBusinessSchema } from "@/components/shared/seo/StructuredData";
|
||||
|
||||
const montserrat = Montserrat({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
|
||||
variable: "--mont",
|
||||
fallback: [
|
||||
"-apple-system",
|
||||
"Segoe UI",
|
||||
"Roboto",
|
||||
"Ubuntu",
|
||||
"Fira Sans",
|
||||
"Arial",
|
||||
"sans-serif",
|
||||
],
|
||||
});
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
|
||||
variable: "--inter",
|
||||
fallback: [
|
||||
"-apple-system",
|
||||
"Segoe UI",
|
||||
"Roboto",
|
||||
"Ubuntu",
|
||||
"Fira Sans",
|
||||
"Arial",
|
||||
"sans-serif",
|
||||
],
|
||||
});
|
||||
|
||||
// Enhanced SEO metadata for root layout
|
||||
export const metadata: Metadata = createMetadata({
|
||||
title: "Enterprise Software Development & IT Solutions",
|
||||
description: "Leading enterprise software development company specializing in custom software, data replication, incident management, AI business intelligence, and comprehensive system integrations for modern businesses.",
|
||||
keywords: [
|
||||
"Enterprise Software Development",
|
||||
"Custom Software Solutions",
|
||||
"Data Replication Services",
|
||||
"Incident Management SaaS",
|
||||
"AI Business Intelligence",
|
||||
"Backend Engineering",
|
||||
"Frontend Engineering",
|
||||
"Systems Integration",
|
||||
],
|
||||
url: "/",
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" style={{ scrollBehavior: 'auto', overflow: 'auto' }}>
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
if ('scrollRestoration' in history) {
|
||||
history.scrollRestoration = 'manual';
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
{/* Content Protection Script */}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Wait for DOM to be ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Disable right-click
|
||||
document.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable keyboard shortcuts
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Ctrl+C, Ctrl+X, Ctrl+S, Ctrl+A, Ctrl+P, Ctrl+U, Ctrl+I, Ctrl+J
|
||||
if ((e.ctrlKey || e.metaKey) && ['c','x','s','a','p','u','i','j','k'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// F12
|
||||
if (e.key === 'F12' || e.keyCode === 123) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+Shift+C
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && ['I','J','C'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Disable text selection
|
||||
document.addEventListener('selectstart', function(e) {
|
||||
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Disable image dragging
|
||||
document.addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable copy/cut
|
||||
document.addEventListener('copy', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
document.addEventListener('cut', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
})();
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body className={`${inter.variable} ${montserrat.variable} content-protected`} style={{ scrollBehavior: 'auto', overflow: 'auto' }}>
|
||||
{/* Structured Data for SEO */}
|
||||
<OrganizationSchema />
|
||||
<WebsiteSchema />
|
||||
<LocalBusinessSchema />
|
||||
|
||||
<CookieConsentProvider
|
||||
config={{
|
||||
companyName: "GNX Soft",
|
||||
privacyPolicyUrl: "/policy?type=privacy",
|
||||
cookiePolicyUrl: "/policy?type=privacy",
|
||||
dataControllerEmail: "privacy@gnxsoft.com",
|
||||
retentionPeriod: 365,
|
||||
enableAuditLog: true,
|
||||
enableDetailedSettings: true,
|
||||
showPrivacyNotice: true,
|
||||
}}
|
||||
>
|
||||
<LayoutWrapper>
|
||||
{children}
|
||||
</LayoutWrapper>
|
||||
<CookieConsent />
|
||||
</CookieConsentProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
31
frontEnd/app/not-found.tsx
Normal file
31
frontEnd/app/not-found.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import Link from "next/link";
|
||||
import HomeScrollProgressButton from "@/components/pages/home/HomeScrollProgressButton";
|
||||
import HomeInitAnimations from "@/components/pages/home/HomeInitAnimations";
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<div className="tp-error pt-120 pb-120 text-center">
|
||||
<div className="container">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12 col-lg-8">
|
||||
<h1 className="fw-7 text-uppercase mt-8">404 ERROR</h1>
|
||||
<p className="text-xl fw-5 mt-12">Page Not Found</p>
|
||||
<div className="mt-40 d-flex justify-content-center">
|
||||
<Link href="/" className="btn-anim btn-anim-light">
|
||||
Go Home
|
||||
<i className="fa-solid fa-arrow-trend-up"></i>
|
||||
<span></span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HomeScrollProgressButton />
|
||||
<HomeInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
29
frontEnd/app/page.tsx
Normal file
29
frontEnd/app/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import HomeBanner from "@/components/pages/home/HomeBanner";
|
||||
import Overview from "@/components/pages/home/Overview";
|
||||
import Story from "@/components/pages/home/Story";
|
||||
import ServiceIntro from "@/components/pages/home/ServiceIntro";
|
||||
import HomeLatestPost from "@/components/pages/home/HomeLatestPost";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import HomeScrollProgressButton from "@/components/pages/home/HomeScrollProgressButton";
|
||||
import HomeInitAnimations from "@/components/pages/home/HomeInitAnimations";
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<HomeBanner />
|
||||
<Overview />
|
||||
<Story />
|
||||
<ServiceIntro />
|
||||
<HomeLatestPost />
|
||||
</main>
|
||||
<Footer />
|
||||
<HomeScrollProgressButton />
|
||||
<HomeInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
399
frontEnd/app/policy/page.tsx
Normal file
399
frontEnd/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;
|
||||
|
||||
35
frontEnd/app/robots.ts
Normal file
35
frontEnd/app/robots.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://gnxsoft.com';
|
||||
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: [
|
||||
'/api/',
|
||||
'/admin/',
|
||||
'/_next/',
|
||||
'/private/',
|
||||
'/*.json$',
|
||||
'/*?*',
|
||||
],
|
||||
},
|
||||
{
|
||||
userAgent: 'Googlebot',
|
||||
allow: '/',
|
||||
disallow: ['/api/', '/admin/', '/private/'],
|
||||
},
|
||||
{
|
||||
userAgent: 'Bingbot',
|
||||
allow: '/',
|
||||
disallow: ['/api/', '/admin/', '/private/'],
|
||||
},
|
||||
],
|
||||
sitemap: `${baseUrl}/sitemap.xml`,
|
||||
host: baseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
88
frontEnd/app/services/[slug]/page.tsx
Normal file
88
frontEnd/app/services/[slug]/page.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import ServiceDetailsBanner from "@/components/shared/banners/ServiceDetailsBanner";
|
||||
import ServiceDetails from "@/components/pages/services/ServiceDetails";
|
||||
import ServiceFeatures from "@/components/pages/services/ServiceFeatures";
|
||||
import ServiceDeliverables from "@/components/pages/services/ServiceDeliverables";
|
||||
import ServiceProcess from "@/components/pages/services/ServiceProcess";
|
||||
import Transform from "@/components/pages/services/Transform";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import ServicesScrollProgressButton from "@/components/pages/services/ServicesScrollProgressButton";
|
||||
import ServicesInitAnimations from "@/components/pages/services/ServicesInitAnimations";
|
||||
import { serviceService, Service } from "@/lib/api/serviceService";
|
||||
import { generateServiceMetadata } from "@/lib/seo/metadata";
|
||||
import { ServiceSchema, BreadcrumbSchema } from "@/components/shared/seo/StructuredData";
|
||||
|
||||
interface ServicePageProps {
|
||||
params: Promise<{
|
||||
slug: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Generate static params for all services (optional - for better performance)
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const services = await serviceService.getServices();
|
||||
return services.results.map((service: Service) => ({
|
||||
slug: service.slug,
|
||||
}));
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Generate enhanced metadata for each service page
|
||||
export async function generateMetadata({ params }: ServicePageProps) {
|
||||
try {
|
||||
const { slug } = await params;
|
||||
const service = await serviceService.getServiceBySlug(slug);
|
||||
|
||||
return generateServiceMetadata(service);
|
||||
} catch (error) {
|
||||
return {
|
||||
title: 'Service Not Found - GNX',
|
||||
description: 'The requested service could not be found.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const ServicePage = async ({ params }: ServicePageProps) => {
|
||||
let service: Service;
|
||||
|
||||
try {
|
||||
const { slug } = await params;
|
||||
service = await serviceService.getServiceBySlug(slug);
|
||||
} catch (error) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Breadcrumb data for structured data
|
||||
const breadcrumbItems = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Services', url: '/services' },
|
||||
{ name: service.title, url: `/services/${service.slug}` },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="enterprise-app">
|
||||
{/* SEO Structured Data */}
|
||||
<ServiceSchema service={service} />
|
||||
<BreadcrumbSchema items={breadcrumbItems} />
|
||||
|
||||
<Header />
|
||||
<main className="enterprise-main">
|
||||
<ServiceDetailsBanner service={service} />
|
||||
<ServiceDetails service={service} />
|
||||
<ServiceFeatures service={service} />
|
||||
<ServiceDeliverables service={service} />
|
||||
<Transform service={service} />
|
||||
<ServiceProcess service={service} />
|
||||
</main>
|
||||
<Footer />
|
||||
<ServicesScrollProgressButton />
|
||||
<ServicesInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicePage;
|
||||
41
frontEnd/app/services/page.tsx
Normal file
41
frontEnd/app/services/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Metadata } from 'next';
|
||||
import Header from "@/components/shared/layout/header/Header";
|
||||
import ServicesBanner from "@/components/pages/services/ServicesBanner";
|
||||
import ServiceMain from "@/components/pages/services/ServiceMain";
|
||||
import Footer from "@/components/shared/layout/footer/Footer";
|
||||
import ServicesScrollProgressButton from "@/components/pages/services/ServicesScrollProgressButton";
|
||||
import ServicesInitAnimations from "@/components/pages/services/ServicesInitAnimations";
|
||||
import { generateMetadata as createMetadata } from "@/lib/seo/metadata";
|
||||
|
||||
export const metadata: Metadata = createMetadata({
|
||||
title: "Our Services - Enterprise Software Development",
|
||||
description: "Explore our comprehensive range of enterprise software development services including custom software, data replication, incident management, AI business intelligence, backend & frontend engineering, and systems integration.",
|
||||
keywords: [
|
||||
"Software Development Services",
|
||||
"Custom Software Development",
|
||||
"Data Replication",
|
||||
"Incident Management SaaS",
|
||||
"AI Business Intelligence",
|
||||
"Backend Engineering Services",
|
||||
"Frontend Development",
|
||||
"Systems Integration Services",
|
||||
],
|
||||
url: "/services",
|
||||
});
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<div className="enterprise-app">
|
||||
<Header />
|
||||
<main className="enterprise-main">
|
||||
<ServicesBanner />
|
||||
<ServiceMain />
|
||||
</main>
|
||||
<Footer />
|
||||
<ServicesScrollProgressButton />
|
||||
<ServicesInitAnimations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
136
frontEnd/app/sitemap.ts
Normal file
136
frontEnd/app/sitemap.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://gnxsoft.com';
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api';
|
||||
|
||||
// Static pages
|
||||
const staticPages: MetadataRoute.Sitemap = [
|
||||
{
|
||||
url: baseUrl,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'daily',
|
||||
priority: 1.0,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/about-us`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/services`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/case-study`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/insights`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'daily',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/career`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/contact-us`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/support-center`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/policy`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.5,
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
// Fetch dynamic services
|
||||
const servicesResponse = await fetch(`${apiUrl}/services/`, {
|
||||
next: { revalidate: 3600 }, // Revalidate every hour
|
||||
});
|
||||
|
||||
let servicePages: MetadataRoute.Sitemap = [];
|
||||
if (servicesResponse.ok) {
|
||||
const services = await servicesResponse.json();
|
||||
servicePages = services.map((service: any) => ({
|
||||
url: `${baseUrl}/services/${service.slug}`,
|
||||
lastModified: new Date(service.updated_at || service.created_at),
|
||||
changeFrequency: 'weekly' as const,
|
||||
priority: service.featured ? 0.9 : 0.7,
|
||||
}));
|
||||
}
|
||||
|
||||
// Fetch dynamic blog posts
|
||||
const blogResponse = await fetch(`${apiUrl}/blog/`, {
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
|
||||
let blogPages: MetadataRoute.Sitemap = [];
|
||||
if (blogResponse.ok) {
|
||||
const posts = await blogResponse.json();
|
||||
blogPages = posts.map((post: any) => ({
|
||||
url: `${baseUrl}/insights/${post.slug}`,
|
||||
lastModified: new Date(post.updated_at || post.published_at),
|
||||
changeFrequency: 'weekly' as const,
|
||||
priority: 0.7,
|
||||
}));
|
||||
}
|
||||
|
||||
// Fetch dynamic case studies
|
||||
const caseStudiesResponse = await fetch(`${apiUrl}/case-studies/`, {
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
|
||||
let caseStudyPages: MetadataRoute.Sitemap = [];
|
||||
if (caseStudiesResponse.ok) {
|
||||
const caseStudies = await caseStudiesResponse.json();
|
||||
caseStudyPages = caseStudies.map((study: any) => ({
|
||||
url: `${baseUrl}/case-study/${study.slug}`,
|
||||
lastModified: new Date(study.updated_at || study.created_at),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.7,
|
||||
}));
|
||||
}
|
||||
|
||||
// Fetch dynamic career postings
|
||||
const careerResponse = await fetch(`${apiUrl}/career/jobs`, {
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
|
||||
let careerPages: MetadataRoute.Sitemap = [];
|
||||
if (careerResponse.ok) {
|
||||
const positions = await careerResponse.json();
|
||||
careerPages = positions.map((position: any) => ({
|
||||
url: `${baseUrl}/career/${position.slug}`,
|
||||
lastModified: new Date(position.updated_at || position.created_at),
|
||||
changeFrequency: 'weekly' as const,
|
||||
priority: 0.6,
|
||||
}));
|
||||
}
|
||||
|
||||
return [...staticPages, ...servicePages, ...blogPages, ...caseStudyPages, ...careerPages];
|
||||
} catch (error) {
|
||||
// Return at least static pages if API fails
|
||||
return staticPages;
|
||||
}
|
||||
}
|
||||
|
||||
30
frontEnd/app/support-center/page.tsx
Normal file
30
frontEnd/app/support-center/page.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
"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";
|
||||
|
||||
type ModalType = 'create' | 'knowledge' | 'status' | null;
|
||||
|
||||
const SupportCenterPage = () => {
|
||||
const [activeModal, setActiveModal] = useState<ModalType>(null);
|
||||
|
||||
return (
|
||||
<div className="tp-app">
|
||||
<Header />
|
||||
<main>
|
||||
<SupportCenterHero onFeatureClick={setActiveModal} />
|
||||
<SupportCenterContent
|
||||
activeModal={activeModal}
|
||||
onClose={() => setActiveModal(null)}
|
||||
onOpenModal={setActiveModal}
|
||||
/>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupportCenterPage;
|
||||
|
||||
Reference in New Issue
Block a user