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

@@ -57,11 +57,6 @@ export default function ServicesList() {
)}
</div>
<p className="card-text">{service.description}</p>
{service.price && (
<p className="text-muted">
<strong>Starting at: ${service.price}</strong>
</p>
)}
<div className="mt-auto">
<a
href={`/services/${service.slug}`}

View File

@@ -227,18 +227,12 @@ const AboutBanner = () => {
{/* Social Links */}
<div className="social-links">
<Link href="https://www.linkedin.com/company/enterprisesoft-solutions" target="_blank" className="social-link">
<Link href="https://www.linkedin.com/company/gnxtech" target="_blank" className="social-link">
<i className="fa-brands fa-linkedin-in"></i>
</Link>
<Link href="https://github.com/enterprisesoft" target="_blank" className="social-link">
<Link href="https://github.com/gnxtech" target="_blank" className="social-link">
<i className="fa-brands fa-github"></i>
</Link>
<Link href="https://www.twitter.com/enterprisesoft" target="_blank" className="social-link">
<i className="fa-brands fa-twitter"></i>
</Link>
<Link href="https://stackoverflow.com/teams/enterprisesoft" target="_blank" className="social-link">
<i className="fa-brands fa-stack-overflow"></i>
</Link>
</div>
</section>
);

View File

@@ -81,10 +81,10 @@ const AboutServiceComponent = () => {
{serviceData?.badge_text || "About Our Company"}
</div>
<h2 className="title-anim">
{serviceData?.title || "Enterprise Technology Leaders"}
{serviceData?.title || "GNX Soft Ltd. - Software Excellence"}
</h2>
<p>
{serviceData?.description || "Founded 2008, EnterpriseSoft Solutions has emerged as a premier enterprise software company, serving Fortune 500 companies and innovative startups worldwide. Our team of 200+ engineers, architects, and consultants specializes in delivering mission-critical software solutions that drive digital transformation and business growth."}
{serviceData?.description || "Founded in 2020, GNX Soft Ltd. has emerged as a premier enterprise software company, delivering mission-critical software solutions across various industries. Our team of expert engineers, architects, and consultants specializes in custom software development, data replication, incident management, and comprehensive system integrations that drive digital transformation and business growth."}
</p>
<div className="enterprise-features">
<div className="row">
@@ -144,7 +144,7 @@ const AboutServiceComponent = () => {
</div>
<div className="feature-content">
<h6>Global Reach</h6>
<p>Offices in 5 Countries</p>
<p>Based in Bulgaria, Serving Worldwide</p>
</div>
</div>
</div>

View File

@@ -208,26 +208,6 @@ const BlogSingle = () => {
<i className="fa-brands fa-linkedin-in"></i>
<span>LinkedIn</span>
</Link>
<Link
href={`https://twitter.com/intent/tweet?url=${typeof window !== 'undefined' ? encodeURIComponent(window.location.href) : ''}&text=${encodeURIComponent(post.title)}`}
target="_blank"
rel="noopener noreferrer"
className="share-btn share-twitter"
aria-label="Share on Twitter"
>
<i className="fa-brands fa-twitter"></i>
<span>Twitter</span>
</Link>
<Link
href={`https://www.facebook.com/sharer/sharer.php?u=${typeof window !== 'undefined' ? encodeURIComponent(window.location.href) : ''}`}
target="_blank"
rel="noopener noreferrer"
className="share-btn share-facebook"
aria-label="Share on Facebook"
>
<i className="fa-brands fa-facebook-f"></i>
<span>Facebook</span>
</Link>
<button
onClick={() => {
if (typeof window !== 'undefined' && navigator.clipboard) {

View File

@@ -114,38 +114,20 @@ const CareerBanner = () => {
<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

@@ -59,7 +59,7 @@ const ServiceDetails = ({ service }: ServiceDetailsProps) => {
<div className="enterprise-stats mb-4">
<div className="row g-3">
<div className="col-6">
<div className="col-12">
<div className="enterprise-stat-card">
<div className="stat-icon">
<i className="fa-solid fa-star"></i>
@@ -72,19 +72,6 @@ const ServiceDetails = ({ service }: ServiceDetailsProps) => {
</div>
</div>
</div>
<div className="col-6">
<div className="enterprise-stat-card">
<div className="stat-icon">
<i className="fa-solid fa-dollar-sign"></i>
</div>
<div className="stat-content">
<div className="stat-number">
{service.formatted_price}
</div>
<div className="stat-label">Starting Price</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -137,9 +137,6 @@ const ServiceMain = () => {
</p>
<div className="service-footer">
<div className="service-price">
{service.formatted_price}
</div>
<Link href={`/services/${service.slug}`} className="service-link">
<span>View Details</span>
<i className="fa-solid fa-arrow-right"></i>

View File

@@ -61,11 +61,8 @@ const ServicePricing = ({ service }: ServicePricingProps) => {
{service.title}
</h3>
<div className="price-display">
<span className="price-amount">
{service.formatted_price}
</span>
<span className="price-period">
Starting Price
Contact Us for Pricing
</span>
</div>
</div>

View File

@@ -85,38 +85,20 @@ const ServicesBanner = () => {
<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

@@ -48,14 +48,6 @@ const Transform = ({ service }: TransformProps) => {
<p className="enterprise-section-description">
{service.description}
</p>
{service.formatted_price && (
<div className="mt-4">
<div className="price-highlight">
<span className="price-label">Starting from</span>
<span className="price-value">{service.formatted_price}</span>
</div>
</div>
)}
</div>
</div>
</div>

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