From d7ff5c71e655633bc81db09ee69b8aa4e387acfe Mon Sep 17 00:00:00 2001 From: Iliyan Angelov Date: Mon, 24 Nov 2025 08:42:03 +0200 Subject: [PATCH] updates --- .../gnx/__pycache__/settings.cpython-312.pyc | Bin 8432 -> 9388 bytes .../__pycache__/api_security.cpython-312.pyc | Bin 0 -> 5420 bytes backEnd/gnx/middleware/api_security.py | 145 +++++++++++++++ backEnd/gnx/settings.py | 64 ++++++- backEnd/logs/django.log | 42 +++++ backEnd/nginx.conf.example | 173 ++++++++++++++++++ backEnd/production.env.example | 11 +- frontEnd/app/about-us/AboutUsClient.tsx | 27 +++ frontEnd/app/about-us/page.tsx | 51 +++--- frontEnd/app/career/[slug]/page.tsx | 30 +++ frontEnd/app/insights/[slug]/page.tsx | 55 +++++- frontEnd/app/page.tsx | 22 +++ frontEnd/app/policy/page.tsx | 40 ++++ frontEnd/app/robots.ts | 50 ++++- frontEnd/app/support-center/page.tsx | 30 +++ 15 files changed, 697 insertions(+), 43 deletions(-) create mode 100644 backEnd/gnx/middleware/__pycache__/api_security.cpython-312.pyc create mode 100644 backEnd/gnx/middleware/api_security.py create mode 100644 backEnd/nginx.conf.example create mode 100644 frontEnd/app/about-us/AboutUsClient.tsx diff --git a/backEnd/gnx/__pycache__/settings.cpython-312.pyc b/backEnd/gnx/__pycache__/settings.cpython-312.pyc index 431ec15eb4a9b1c74c4151ac29fc36391a746df1..0ad5b2316a9afb869fa30c11f124bae02aeb1345 100644 GIT binary patch delta 1737 zcmZ8hTW=dh6yEhUaV~@;ByQp~H&8IG6N#46q@}QpH;!p)yK9@arqtc~Oq@-VwX?gf zFSOXba4R5DttwvfQ0N1o_Mwr5cmjzhD7-9H1Yv&!Bn5#GsA6WE7N;xun>pY4=9@ET z*1OK#3by{x(&A$9GkAYksyuhK)os~tUwL}jQnoP24q?=+T41-#Lgdzwn-(Hun8Y5M z6Chg0Jx{q9456l54L7Zu%Dt40!)|!xGE>Korx`f<37eRp`C~8!$G7D7q4GZEaWpYs ztQplK+IN$!#ViBP#0xN#IFM!%FJj9CwtNYXui}xzxr5aH#3MybQuZ}CSy#TFa>@U> zGCn6y$y1206HcLK#f~ObD~O4JTvMEeRGlI~R{@|pZklqlkVf5gT?kTk4xC$h_A+J5 z(j!$LqV)WOvnOU~eCA<%n8pjRxMhnFWo5|J6%(dh7M8XsBa~Ghu=xDsNgM$chCqWH zoL#m+ese6~c@ave5neAfF$|oecpjGE4Ja==pi-B+@JP-97vU0IUbe!U`1e+t#ZGTy zzjyGQxDLU)nD<_xVP!L{B^{g5dsy)PvLhiBt=`hnKl>~i(DRu5~xf!m^1+uE*M%Vt|J6yt>uHGQ!2C2@t1}4HNeM|5u7mXxZ25S(-sLwW2Q9{k9l3DLrkSmRx3SL8+}MlWk8bCW{a59<}!*@ z5=je6%_({dnolcck!b~4GaWdamgJHvry^zCC#I3Cm6{5^l&Z{XnKMW(wH15{TFN0+ zL*UCx+FYra4 zrBgXfgdqZ90uc;)vbUR!>G|IMY_Bf%c9iiA1^8op)NG#&3(+LGxTDF#d~7=G-^xw% zoSzq>W-}Lw@F9OPI>SY$l_W7fML;BQnm~#G5I_WG38V?k5s(PX6Ij6DR2IoJqc?bl zo4&?V^6^yr?fM?ig0rdBQgN;KR6q9|VcBv0pT1wq8v@I8_82`ozp<~JHV#Rv9rFg$ zw&7s`1BkDVKBOqXZP=3Z+auVuS1+rHsqY+acB+1CG~t=|}U*1KxkweDbgwi`^>+BS;Y d2Mnh7p2JBO-0N(jtFx? delta 1023 zcmZ9L%TE(g6vpSyOk2vMAo3_ssT3D>VK8DKKtU?cN<*QgJO-pz&(QLamT47vXc37E z<3?vm7lti0k<7-#KfnYwg^;LCOwd2T6pafP8t=WWW}r!bbHDF=_uSK)X~L1V8}HQ##xxpK0|mN zh6Pa%(sCZdK!h?1LAVF^<62l$)gGLxX<-SLVI|JND*iqUAl&H@?iXvog0!{38ZJDh zYB8%~RB5wn9oL`4wL;$#XWdYOngb5$l!KyIoWhqiA;U4&l8vAhL;hLbtg|9Xhu2D-(S%UH?5y~MrsTfL`QiX}t(-JN z(z661d9U(9eT3L-Dktd2w<1cBOn|(cI2`(vQ6H18?5v&B^mu b4*B|{igQV(?5Ls|Gp$v=Sl%N(fe-i#+_(iD diff --git a/backEnd/gnx/middleware/__pycache__/api_security.cpython-312.pyc b/backEnd/gnx/middleware/__pycache__/api_security.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc482812a0c1e4b7cc70ec70dd8df974e9d6c500 GIT binary patch literal 5420 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 { + return ( +
+
+
+ + + +
+
+ + +
+ ); +}; + +export default AboutUsClient; + diff --git a/frontEnd/app/about-us/page.tsx b/frontEnd/app/about-us/page.tsx index a21eba79..c16b84ff 100644 --- a/frontEnd/app/about-us/page.tsx +++ b/frontEnd/app/about-us/page.tsx @@ -1,35 +1,26 @@ -"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"; +// Server Component - metadata export is allowed here +import { Metadata } from 'next'; +import { generateMetadata as createMetadata } from "@/lib/seo/metadata"; +import AboutUsClient from "./AboutUsClient"; + +export const metadata: Metadata = createMetadata({ + title: "About Us - Enterprise Software Development Company", + description: "Learn about GNX Soft - a leading enterprise software development company founded in 2020, specializing in custom software solutions, data replication, AI business intelligence, incident management, and comprehensive IT solutions for modern businesses.", + keywords: [ + "About GNX Soft", + "Software Development Company", + "Enterprise Solutions Provider", + "IT Services Company", + "Custom Software Development", + "Technology Company", + "Software Engineering Team", + "Digital Transformation Experts", + ], + url: "/about-us", +}); -// 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 ( -
-
-
- - - -
-
- - -
- ); + return ; }; export default AboutUsPage; diff --git a/frontEnd/app/career/[slug]/page.tsx b/frontEnd/app/career/[slug]/page.tsx index f4143328..c21d0c29 100644 --- a/frontEnd/app/career/[slug]/page.tsx +++ b/frontEnd/app/career/[slug]/page.tsx @@ -1,18 +1,48 @@ "use client"; import { useParams } from "next/navigation"; +import { useEffect } from "react"; 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"; +import { generateCareerMetadata } from "@/lib/seo/metadata"; const JobPage = () => { const params = useParams(); const slug = params?.slug as string; const { job, loading, error } = useJob(slug); + // Update metadata dynamically for client component + useEffect(() => { + if (job) { + const metadata = generateCareerMetadata(job); + const title = typeof metadata.title === 'string' ? metadata.title : `Career - ${job.title} | GNX Soft`; + document.title = title; + + // Update meta description + let metaDescription = document.querySelector('meta[name="description"]'); + if (!metaDescription) { + metaDescription = document.createElement('meta'); + metaDescription.setAttribute('name', 'description'); + document.head.appendChild(metaDescription); + } + const description = typeof metadata.description === 'string' ? metadata.description : `Apply for ${job.title} at GNX Soft. ${job.location || 'Remote'} position.`; + metaDescription.setAttribute('content', description); + + // Update canonical URL + let canonical = document.querySelector('link[rel="canonical"]'); + if (!canonical) { + canonical = document.createElement('link'); + canonical.setAttribute('rel', 'canonical'); + document.head.appendChild(canonical); + } + canonical.setAttribute('href', `${window.location.origin}/career/${job.slug}`); + } + }, [job]); + if (loading) { return (
diff --git a/frontEnd/app/insights/[slug]/page.tsx b/frontEnd/app/insights/[slug]/page.tsx index 161b6754..2224e35f 100644 --- a/frontEnd/app/insights/[slug]/page.tsx +++ b/frontEnd/app/insights/[slug]/page.tsx @@ -1,11 +1,64 @@ +import { Metadata } from 'next'; +import { notFound } from 'next/navigation'; 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"; +import { generateBlogMetadata } from "@/lib/seo/metadata"; +import { API_CONFIG } from "@/lib/config/api"; -const page = () => { +interface PageProps { + params: Promise<{ + slug: string; + }>; +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + + try { + const response = await fetch( + `${API_CONFIG.BASE_URL}/api/blog/posts/${slug}/`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + next: { revalidate: 3600 }, + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const post = await response.json(); + + return generateBlogMetadata({ + title: post.title, + description: post.meta_description || post.excerpt || post.content?.substring(0, 160), + excerpt: post.excerpt, + slug: post.slug, + image: post.featured_image || post.thumbnail, + published_at: post.published_at, + updated_at: post.updated_at, + author: post.author ? { name: post.author.name } : undefined, + category: post.category ? { name: post.category.title } : undefined, + tags: post.tags?.map((tag: any) => tag.name) || [], + }); + } catch (error) { + return { + title: 'Insight Not Found | GNX Soft', + description: 'The requested insight article could not be found.', + }; + } +} + +const page = async ({ params }: PageProps) => { + const { slug } = await params; + return (
diff --git a/frontEnd/app/page.tsx b/frontEnd/app/page.tsx index 2f4a27f3..befe477f 100644 --- a/frontEnd/app/page.tsx +++ b/frontEnd/app/page.tsx @@ -1,3 +1,4 @@ +import { Metadata } from 'next'; import Header from "@/components/shared/layout/header/Header"; import HomeBanner from "@/components/pages/home/HomeBanner"; import Overview from "@/components/pages/home/Overview"; @@ -7,6 +8,27 @@ 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"; +import { generateMetadata as createMetadata } from "@/lib/seo/metadata"; + +export const metadata: Metadata = createMetadata({ + title: "Enterprise Software Development & IT Solutions", + description: "GNX Soft - Leading enterprise software development company specializing in custom software solutions, data replication, incident management, AI business intelligence, backend & frontend engineering, 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 Development", + "Systems Integration", + "Cloud Solutions", + "DevOps Services", + "API Development", + "Digital Transformation", + ], + url: "/", +}); const page = () => { return ( diff --git a/frontEnd/app/policy/page.tsx b/frontEnd/app/policy/page.tsx index 7667f20c..4fbb2552 100644 --- a/frontEnd/app/policy/page.tsx +++ b/frontEnd/app/policy/page.tsx @@ -1,9 +1,11 @@ "use client"; import { useSearchParams } from 'next/navigation'; +import { useEffect } from 'react'; import Header from "@/components/shared/layout/header/Header"; import Footer from "@/components/shared/layout/footer/Footer"; import { Suspense } from 'react'; import { usePolicy } from '@/lib/hooks/usePolicy'; +import { generateMetadata as createMetadata } from "@/lib/seo/metadata"; const PolicyContent = () => { const searchParams = useSearchParams(); @@ -12,6 +14,44 @@ const PolicyContent = () => { const { data: policy, isLoading, error } = usePolicy(type); + // Update metadata based on policy type + useEffect(() => { + const policyTitles = { + privacy: 'Privacy Policy - Data Protection & Privacy', + terms: 'Terms of Use - Terms & Conditions', + support: 'Support Policy - Support Terms & Guidelines', + }; + + const policyDescriptions = { + privacy: 'Read GNX Soft\'s Privacy Policy to understand how we collect, use, and protect your personal information and data.', + terms: 'Review GNX Soft\'s Terms of Use and Conditions for using our software services and platforms.', + support: 'Learn about GNX Soft\'s Support Policy, including support terms, response times, and service level agreements.', + }; + + const metadata = createMetadata({ + title: policyTitles[type], + description: policyDescriptions[type], + keywords: [ + type === 'privacy' ? 'Privacy Policy' : type === 'terms' ? 'Terms of Use' : 'Support Policy', + 'Legal Documents', + 'Company Policies', + 'Data Protection', + 'Terms and Conditions', + ], + url: `/policy?type=${type}`, + }); + + document.title = metadata.title || `${policyTitles[type]} | GNX Soft`; + + let metaDescription = document.querySelector('meta[name="description"]'); + if (!metaDescription) { + metaDescription = document.createElement('meta'); + metaDescription.setAttribute('name', 'description'); + document.head.appendChild(metaDescription); + } + metaDescription.setAttribute('content', metadata.description || policyDescriptions[type]); + }, [type]); + if (isLoading) { return (
diff --git a/frontEnd/app/robots.ts b/frontEnd/app/robots.ts index 65458484..1335c6df 100644 --- a/frontEnd/app/robots.ts +++ b/frontEnd/app/robots.ts @@ -13,19 +13,59 @@ export default function robots(): MetadataRoute.Robots { '/admin/', '/_next/', '/private/', + '/static/', '/*.json$', - '/*?*', ], }, { userAgent: 'Googlebot', - allow: '/', - disallow: ['/api/', '/admin/', '/private/'], + allow: [ + '/', + '/services', + '/services/*', + '/about-us', + '/contact-us', + '/career', + '/career/*', + '/case-study', + '/case-study/*', + '/insights', + '/insights/*', + '/support-center', + '/policy', + ], + disallow: [ + '/api/', + '/admin/', + '/private/', + '/_next/', + '/static/', + ], }, { userAgent: 'Bingbot', - allow: '/', - disallow: ['/api/', '/admin/', '/private/'], + allow: [ + '/', + '/services', + '/services/*', + '/about-us', + '/contact-us', + '/career', + '/career/*', + '/case-study', + '/case-study/*', + '/insights', + '/insights/*', + '/support-center', + '/policy', + ], + disallow: [ + '/api/', + '/admin/', + '/private/', + '/_next/', + '/static/', + ], }, ], sitemap: `${baseUrl}/sitemap.xml`, diff --git a/frontEnd/app/support-center/page.tsx b/frontEnd/app/support-center/page.tsx index 9d9a929c..1feba094 100644 --- a/frontEnd/app/support-center/page.tsx +++ b/frontEnd/app/support-center/page.tsx @@ -1,13 +1,43 @@ "use client"; +import { useEffect } from "react"; 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"; +import { generateMetadata as createMetadata } from "@/lib/seo/metadata"; type ModalType = 'create' | 'knowledge' | 'status' | null; const SupportCenterPage = () => { + // Set metadata for client component + useEffect(() => { + const metadata = createMetadata({ + title: "Support Center - Enterprise Support & Help Desk", + description: "Get 24/7 enterprise support from GNX Soft. Access our knowledge base, create support tickets, check ticket status, and get help with our software solutions and services.", + keywords: [ + "Support Center", + "Customer Support", + "Help Desk", + "Technical Support", + "Knowledge Base", + "Support Tickets", + "Enterprise Support", + "IT Support", + ], + url: "/support-center", + }); + + document.title = metadata.title || "Support Center | GNX Soft"; + + let metaDescription = document.querySelector('meta[name="description"]'); + if (!metaDescription) { + metaDescription = document.createElement('meta'); + metaDescription.setAttribute('name', 'description'); + document.head.appendChild(metaDescription); + } + metaDescription.setAttribute('content', metadata.description || 'Get enterprise support from GNX Soft'); + }, []); const [activeModal, setActiveModal] = useState(null); return (