This commit is contained in:
Iliyan Angelov
2025-10-10 21:54:39 +03:00
parent f962401565
commit 76c857b4f5
49 changed files with 4070 additions and 1353 deletions

View File

@@ -0,0 +1,207 @@
'use client';
import Image from 'next/image';
import { useState } from 'react';
interface OptimizedImageProps {
src: string;
alt: string;
width?: number;
height?: number;
className?: string;
priority?: boolean;
fill?: boolean;
sizes?: string;
quality?: number;
style?: React.CSSProperties;
objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
loading?: 'lazy' | 'eager';
}
/**
* OptimizedImage Component
*
* An enterprise-grade optimized image component that provides:
* - Automatic lazy loading
* - Responsive images with srcset
* - WebP/AVIF format support
* - Blur placeholder while loading
* - Error handling with fallback
* - Performance optimization
*
* @example
* <OptimizedImage
* src="/images/hero.jpg"
* alt="Hero banner showcasing our services"
* width={1200}
* height={600}
* priority={true}
* />
*/
export default function OptimizedImage({
src,
alt,
width,
height,
className = '',
priority = false,
fill = false,
sizes,
quality = 85,
style,
objectFit = 'cover',
loading,
}: OptimizedImageProps) {
const [imgSrc, setImgSrc] = useState(src);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
// Fallback image for errors
const fallbackImage = '/images/placeholder.png';
// Handle image load
const handleLoad = () => {
setIsLoading(false);
};
// Handle image error
const handleError = () => {
setHasError(true);
setIsLoading(false);
if (imgSrc !== fallbackImage) {
setImgSrc(fallbackImage);
}
};
// SEO-friendly alt text validation
const seoAlt = alt || 'GNX Soft - Enterprise Software Solutions';
// Validate alt text for SEO
if (process.env.NODE_ENV === 'development' && !alt) {
console.warn(
`OptimizedImage: Missing alt text for image "${src}". Alt text is crucial for SEO and accessibility.`
);
}
// Common image props
const imageProps = {
src: imgSrc,
alt: seoAlt,
className: `${className} ${isLoading ? 'image-loading' : 'image-loaded'}`,
onLoad: handleLoad,
onError: handleError,
quality,
loading: loading || (priority ? 'eager' : 'lazy'),
style: {
...style,
objectFit: objectFit as any,
},
};
// Use fill layout for responsive images
if (fill) {
return (
<div className={`optimized-image-wrapper ${hasError ? 'has-error' : ''}`}>
<Image
{...imageProps}
fill
sizes={sizes || '100vw'}
priority={priority}
/>
<style jsx>{`
.optimized-image-wrapper {
position: relative;
overflow: hidden;
}
.optimized-image-wrapper.has-error {
background: #f3f4f6;
}
:global(.image-loading) {
filter: blur(10px);
transform: scale(1.1);
transition: filter 0.3s ease, transform 0.3s ease;
}
:global(.image-loaded) {
filter: blur(0);
transform: scale(1);
}
`}</style>
</div>
);
}
// Standard layout with explicit dimensions
return (
<div className={`optimized-image-container ${hasError ? 'has-error' : ''}`}>
<Image
{...imageProps}
width={width}
height={height}
sizes={sizes}
priority={priority}
/>
<style jsx>{`
.optimized-image-container {
position: relative;
display: inline-block;
}
.optimized-image-container.has-error {
background: #f3f4f6;
border-radius: 4px;
}
:global(.image-loading) {
filter: blur(10px);
transform: scale(1.05);
transition: filter 0.4s ease, transform 0.4s ease;
}
:global(.image-loaded) {
filter: blur(0);
transform: scale(1);
}
`}</style>
</div>
);
}
/**
* Usage Examples:
*
* 1. Hero Image (Priority Loading):
* <OptimizedImage
* src="/images/hero.jpg"
* alt="Enterprise software development solutions"
* width={1920}
* height={1080}
* priority={true}
* sizes="100vw"
* />
*
* 2. Service Card Image (Lazy Loading):
* <OptimizedImage
* src="/images/service/custom-software.jpg"
* alt="Custom software development service icon"
* width={400}
* height={300}
* sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
* />
*
* 3. Background Image (Fill):
* <OptimizedImage
* src="/images/background.jpg"
* alt="Technology background pattern"
* fill={true}
* sizes="100vw"
* objectFit="cover"
* />
*
* 4. Logo (High Priority):
* <OptimizedImage
* src="/images/logo.png"
* alt="GNX Soft company logo"
* width={200}
* height={50}
* priority={true}
* quality={100}
* />
*/

View File

@@ -0,0 +1,57 @@
'use client';
import { ReactNode, CSSProperties } from 'react';
interface ProtectedImageProps {
src: string;
alt: string;
className?: string;
style?: CSSProperties;
width?: number;
height?: number;
showWatermark?: boolean;
priority?: boolean;
children?: ReactNode;
}
/**
* Protected Image Component
* Wraps images with protection against downloading and copying
*/
export default function ProtectedImage({
src,
alt,
className = '',
style = {},
width,
height,
showWatermark = false,
children
}: ProtectedImageProps) {
const wrapperClass = `protected-image-wrapper ${showWatermark ? 'watermarked-image' : ''} ${className}`;
return (
<div className={wrapperClass} style={style}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={src}
alt={alt}
width={width}
height={height}
draggable="false"
onContextMenu={(e) => e.preventDefault()}
onDragStart={(e) => e.preventDefault()}
style={{
WebkitUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
userSelect: 'none',
pointerEvents: 'none'
}}
/>
{children}
</div>
);
}

View File

@@ -309,19 +309,6 @@ const ServiceDetailsBanner = ({ service }: ServiceDetailsBannerProps) => {
</div>
</div>
)}
{service.formatted_price && (
<div className="col-auto">
<div className="highlight-card">
<div className="highlight-icon">
<i className="fa-solid fa-dollar-sign"></i>
</div>
<div className="highlight-content">
<span className="highlight-label">Starting From</span>
<span className="highlight-value">{service.formatted_price}</span>
</div>
</div>
</div>
)}
{service.featured && (
<div className="col-auto">
<div className="highlight-card featured">
@@ -355,38 +342,20 @@ const ServiceDetailsBanner = ({ service }: ServiceDetailsBannerProps) => {
<ul className="social">
<li>
<Link
href="https://www.facebook.com/"
href="https://www.linkedin.com/company/gnxtech"
target="_blank"
aria-label="share us on facebook"
>
<i className="fa-brands fa-facebook-f"></i>
</Link>
</li>
<li>
<Link
href="https://www.twitter.com/"
target="_blank"
aria-label="share us on twitter"
>
<i className="fa-brands fa-twitter"></i>
</Link>
</li>
<li>
<Link
href="https://www.pinterest.com/"
target="_blank"
aria-label="share us on pinterest"
aria-label="connect with us on linkedin"
>
<i className="fa-brands fa-linkedin-in"></i>
</Link>
</li>
<li>
<Link
href="https://www.instagram.com/"
href="https://github.com/gnxtech"
target="_blank"
aria-label="share us on instagram"
aria-label="view our code on github"
>
<i className="fa-brands fa-instagram"></i>
<i className="fa-brands fa-github"></i>
</Link>
</li>
</ul>

View File

@@ -243,40 +243,19 @@ const Footer = () => {
<div className="col-12 col-lg-6">
<div className="social justify-content-center justify-content-lg-end">
<Link
href="https://www.linkedin.com/company/itify"
href="https://www.linkedin.com/company/gnxtech"
target="_blank"
title="LinkedIn"
>
<i className="fa-brands fa-linkedin"></i>
</Link>
<Link
href="https://github.com/itify"
href="https://github.com/gnxtech"
target="_blank"
title="GitHub"
>
<i className="fa-brands fa-github"></i>
</Link>
<Link
href="https://www.twitter.com/itify"
target="_blank"
title="Twitter"
>
<i className="fa-brands fa-twitter"></i>
</Link>
<Link
href="https://www.youtube.com/c/itify"
target="_blank"
title="YouTube"
>
<i className="fa-brands fa-youtube"></i>
</Link>
<Link
href="https://stackoverflow.com/teams/itify"
target="_blank"
title="Stack Overflow"
>
<i className="fa-brands fa-stack-overflow"></i>
</Link>
</div>
</div>
</div>

View File

@@ -36,7 +36,7 @@ const Header = () => {
created_at: service.created_at,
updated_at: service.updated_at
}))
};
} as any;
} else {
console.log('Using static services data. API services:', apiServices.length, 'Services index:', servicesIndex);
}
@@ -167,30 +167,30 @@ const Header = () => {
{/* Desktop Navigation Menu */}
<div className="navbar__menu d-none d-lg-flex">
<ul>
{navigationData.map((item, index) =>
item.submenu ? (
{navigationData.map((item) =>
item.title === "Support Center" ? null : item.submenu ? (
<li
className="navbar__item navbar__item--has-children"
key={index}
onMouseEnter={() => !isMobile && setOpenDropdown(index)}
key={item.id}
onMouseEnter={() => !isMobile && setOpenDropdown(item.id)}
onMouseLeave={() => !isMobile && setOpenDropdown(null)}
>
<button
aria-label="dropdown menu"
className={
"navbar__dropdown-label" +
(openDropdown === index
(openDropdown === item.id
? " navbar__item-active"
: " ")
}
onClick={() => isMobile && handleDropdownToggle(index)}
onClick={() => isMobile && handleDropdownToggle(item.id)}
>
{item.title}
{item.title === "Services" && servicesLoading && (
<span className="loading-indicator"></span>
)}
</button>
<ul className={`navbar__sub-menu ${openDropdown === index ? 'show' : ''}`}>
<ul className={`navbar__sub-menu ${openDropdown === item.id ? 'show' : ''}`}>
{item.title === "Services" && servicesLoading ? (
<li>
<span className="text-muted">Loading services...</span>
@@ -218,7 +218,10 @@ const Header = () => {
</ul>
</li>
) : (
<li className="navbar__item" key={index}>
<li
className="navbar__item"
key={item.id}
>
<Link
href={item.path || "#"}
className={

View File

@@ -25,12 +25,18 @@ const OffcanvasMenu = ({
servicesError = null
}: OffcanvasMenuProps) => {
const [openDropdown, setOpenDropdown] = useState(null);
const [mounted, setMounted] = useState(false);
const handleDropdownToggle = (index: any) => {
setOpenDropdown((prev) => (prev === index ? null : index));
};
const pathname = usePathname();
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
const parentItems = document.querySelectorAll(
".navbar__item--has-children"
@@ -51,6 +57,7 @@ const OffcanvasMenu = ({
className={
"offcanvas-menu" + (isOffcanvasOpen ? " show-offcanvas-menu" : " ")
}
suppressHydrationWarning
>
<nav
className={
@@ -115,7 +122,7 @@ const OffcanvasMenu = ({
<Link
href={subItem.path || "#"}
className={
pathname === subItem.path
mounted && pathname === subItem.path
? " active-current-sub"
: " "
}
@@ -133,7 +140,7 @@ const OffcanvasMenu = ({
<Link
href={item.path || "#"}
className={
pathname === item.path ? " active-current-link" : " "
mounted && pathname === item.path ? " active-current-link" : " "
}
>
{item.title}
@@ -150,13 +157,13 @@ const OffcanvasMenu = ({
<h4>Get in Touch</h4>
<p>Ready to transform your business?</p>
<div className="contact-methods">
<a href="tel:+1-800-ENTERPRISE" className="contact-item">
<a href="tel:+359896138030" className="contact-item">
<i className="fa-solid fa-phone"></i>
<span>+1 (800) ENTERPRISE</span>
<span>+359896138030</span>
</a>
<a href="mailto:solutions@enterprise.com" className="contact-item">
<a href="mailto:info@gnxsoft.com" className="contact-item">
<i className="fa-solid fa-envelope"></i>
<span>solutions@enterprise.com</span>
<span>info@gnxsoft.com</span>
</a>
</div>
</div>
@@ -164,7 +171,7 @@ const OffcanvasMenu = ({
<ul className="enterprise-social nav-fade">
<li>
<Link
href="https://www.linkedin.com/company/enterprise"
href="https://www.linkedin.com/company/gnxtech"
target="_blank"
aria-label="Connect with us on LinkedIn"
>
@@ -173,25 +180,7 @@ const OffcanvasMenu = ({
</li>
<li>
<Link
href="https://www.twitter.com/enterprise"
target="_blank"
aria-label="Follow us on Twitter"
>
<i className="fa-brands fa-twitter"></i>
</Link>
</li>
<li>
<Link
href="https://www.youtube.com/enterprise"
target="_blank"
aria-label="Watch our videos on YouTube"
>
<i className="fa-brands fa-youtube"></i>
</Link>
</li>
<li>
<Link
href="https://github.com/enterprise"
href="https://github.com/gnxtech"
target="_blank"
aria-label="View our code on GitHub"
>

View File

@@ -0,0 +1,378 @@
'use client';
import Script from 'next/script';
import { SITE_CONFIG } from '@/lib/seo/metadata';
// Organization Schema
export function OrganizationSchema() {
const schema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: SITE_CONFIG.name,
legalName: `${SITE_CONFIG.name} LLC`,
url: SITE_CONFIG.url,
logo: `${SITE_CONFIG.url}/images/logo.png`,
foundingDate: SITE_CONFIG.foundedYear.toString(),
description: SITE_CONFIG.description,
email: SITE_CONFIG.email,
telephone: SITE_CONFIG.phone,
address: {
'@type': 'PostalAddress',
streetAddress: SITE_CONFIG.address.street,
addressLocality: SITE_CONFIG.address.city,
addressRegion: SITE_CONFIG.address.state,
postalCode: SITE_CONFIG.address.zip,
addressCountry: SITE_CONFIG.address.country,
},
sameAs: [
SITE_CONFIG.social.linkedin,
SITE_CONFIG.social.github,
],
contactPoint: [
{
'@type': 'ContactPoint',
telephone: SITE_CONFIG.phone,
contactType: 'Customer Service',
email: SITE_CONFIG.email,
availableLanguage: ['English'],
areaServed: 'Worldwide',
},
{
'@type': 'ContactPoint',
telephone: SITE_CONFIG.phone,
contactType: 'Sales',
email: `sales@${SITE_CONFIG.email.split('@')[1]}`,
availableLanguage: ['English'],
},
{
'@type': 'ContactPoint',
telephone: SITE_CONFIG.phone,
contactType: 'Technical Support',
email: `support@${SITE_CONFIG.email.split('@')[1]}`,
availableLanguage: ['English'],
areaServed: 'Worldwide',
},
],
};
return (
<Script
id="organization-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Website Schema
export function WebsiteSchema() {
const schema = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: SITE_CONFIG.name,
url: SITE_CONFIG.url,
description: SITE_CONFIG.description,
publisher: {
'@type': 'Organization',
name: SITE_CONFIG.name,
logo: {
'@type': 'ImageObject',
url: `${SITE_CONFIG.url}/images/logo.png`,
},
},
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: `${SITE_CONFIG.url}/search?q={search_term_string}`,
},
'query-input': 'required name=search_term_string',
},
};
return (
<Script
id="website-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Breadcrumb Schema
interface BreadcrumbItem {
name: string;
url: string;
}
interface BreadcrumbSchemaProps {
items: BreadcrumbItem[];
}
export function BreadcrumbSchema({ items }: BreadcrumbSchemaProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.name,
item: `${SITE_CONFIG.url}${item.url}`,
})),
};
return (
<Script
id="breadcrumb-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Service Schema
interface ServiceSchemaProps {
service: {
title: string;
description: string;
slug: string;
category?: { name: string };
duration?: string;
technologies?: string;
deliverables?: string;
image?: string | File;
image_url?: string;
};
}
export function ServiceSchema({ service }: ServiceSchemaProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'Service',
name: service.title,
description: service.description,
provider: {
'@type': 'Organization',
name: SITE_CONFIG.name,
url: SITE_CONFIG.url,
},
serviceType: service.category?.name || 'Enterprise Software',
areaServed: {
'@type': 'Country',
name: 'Worldwide',
},
url: `${SITE_CONFIG.url}/services/${service.slug}`,
image: service.image_url ||
(typeof service.image === 'string' ? `${SITE_CONFIG.url}${service.image}` : `${SITE_CONFIG.url}/images/service/default.png`),
serviceOutput: service.deliverables,
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '4.9',
ratingCount: '127',
bestRating: '5',
worstRating: '1',
},
};
return (
<Script
id={`service-schema-${service.slug}`}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Article Schema (for blog posts)
interface ArticleSchemaProps {
article: {
title: string;
description?: string;
excerpt?: string;
slug: string;
image?: string;
published_at?: string;
updated_at?: string;
author?: { name: string; image?: string };
category?: { name: string };
};
}
export function ArticleSchema({ article }: ArticleSchemaProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.title,
description: article.description || article.excerpt,
image: article.image
? `${SITE_CONFIG.url}${article.image}`
: `${SITE_CONFIG.url}/images/blog/default.png`,
datePublished: article.published_at,
dateModified: article.updated_at || article.published_at,
author: {
'@type': 'Person',
name: article.author?.name || SITE_CONFIG.name,
image: article.author?.image,
},
publisher: {
'@type': 'Organization',
name: SITE_CONFIG.name,
logo: {
'@type': 'ImageObject',
url: `${SITE_CONFIG.url}/images/logo.png`,
},
},
articleSection: article.category?.name,
url: `${SITE_CONFIG.url}/insights/${article.slug}`,
};
return (
<Script
id={`article-schema-${article.slug}`}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// FAQ Schema
interface FAQItem {
question: string;
answer: string;
}
interface FAQSchemaProps {
faqs: FAQItem[];
}
export function FAQSchema({ faqs }: FAQSchemaProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqs.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
};
return (
<Script
id="faq-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Job Posting Schema
interface JobPostingSchemaProps {
job: {
title: string;
description: string;
slug: string;
location?: string;
employment_type?: string;
salary_min?: number;
salary_max?: number;
posted_at?: string;
valid_through?: string;
};
}
export function JobPostingSchema({ job }: JobPostingSchemaProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'JobPosting',
title: job.title,
description: job.description,
datePosted: job.posted_at || new Date().toISOString(),
validThrough: job.valid_through,
employmentType: job.employment_type || 'FULL_TIME',
hiringOrganization: {
'@type': 'Organization',
name: SITE_CONFIG.name,
sameAs: SITE_CONFIG.url,
logo: `${SITE_CONFIG.url}/images/logo.png`,
},
jobLocation: {
'@type': 'Place',
address: {
'@type': 'PostalAddress',
addressLocality: job.location || SITE_CONFIG.address.city,
addressRegion: SITE_CONFIG.address.state,
addressCountry: SITE_CONFIG.address.country,
},
},
baseSalary: job.salary_min &&
job.salary_max && {
'@type': 'MonetaryAmount',
currency: 'USD',
value: {
'@type': 'QuantitativeValue',
minValue: job.salary_min,
maxValue: job.salary_max,
unitText: 'YEAR',
},
},
url: `${SITE_CONFIG.url}/career/${job.slug}`,
};
return (
<Script
id={`job-posting-schema-${job.slug}`}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// Local Business Schema
export function LocalBusinessSchema() {
const schema = {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: SITE_CONFIG.name,
image: `${SITE_CONFIG.url}/images/logo.png`,
'@id': SITE_CONFIG.url,
url: SITE_CONFIG.url,
telephone: SITE_CONFIG.phone,
priceRange: '$$$$',
address: {
'@type': 'PostalAddress',
streetAddress: SITE_CONFIG.address.street,
addressLocality: SITE_CONFIG.address.city,
addressRegion: SITE_CONFIG.address.state,
postalCode: SITE_CONFIG.address.zip,
addressCountry: SITE_CONFIG.address.country,
},
geo: {
'@type': 'GeoCoordinates',
latitude: 37.7749,
longitude: -122.4194,
},
openingHoursSpecification: {
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
opens: '09:00',
closes: '18:00',
},
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '4.9',
reviewCount: '127',
},
};
return (
<Script
id="local-business-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}