This commit is contained in:
Iliyan Angelov
2025-11-24 03:52:08 +02:00
parent dfcaebaf8c
commit 366f28677a
18241 changed files with 865352 additions and 567 deletions

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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>
);
}

View 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
View 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;

View 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
View 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,
};
}

View 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;

View 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
View 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;
}
}

View 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;