feat: Add professional portfolio images and Hero redesign
- Add 15 optimized WebP images for portfolio (21-53KB each) - Redesign Hero section with full-width background image - Add SSR support for Hero component - Update About section with new portrait image - Update Contact page with professional headshot - Add images to Services/Leistungen page - Add image optimization script (sharp) - Update translations for gallery section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,758 @@
|
||||
import { type Locale } from '@/i18n/config';
|
||||
|
||||
interface JsonLdProps {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export function PersonJsonLd({ locale }: JsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Person',
|
||||
'@id': `${baseUrl}/#person`,
|
||||
name: 'Damjan Savić',
|
||||
givenName: 'Damjan',
|
||||
familyName: 'Savić',
|
||||
jobTitle: locale === 'de'
|
||||
? 'AI & Automation Specialist | Fullstack Entwickler'
|
||||
: locale === 'sr'
|
||||
? 'AI & Automation Specialist | Fullstack Developer'
|
||||
: 'AI & Automation Specialist | Fullstack Developer',
|
||||
description: locale === 'de'
|
||||
? 'Entwicklung von KI-Agenten und Automatisierungslösungen. Von Voice-AI-Plattformen bis zu autonomen Web-Agenten.'
|
||||
: locale === 'sr'
|
||||
? 'Razvoj AI agenata i rešenja za automatizaciju. Od voice AI platformi do autonomnih web agenata.'
|
||||
: 'Remote AI developer building AI agents and automation solutions for clients in USA, UK and Europe. From voice AI platforms to autonomous web agents.',
|
||||
url: baseUrl,
|
||||
image: `${baseUrl}/images/og-image.jpg`,
|
||||
email: 'info@damjan-savic.com',
|
||||
telephone: '+491756950979',
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
addressLocality: locale === 'sr' ? 'Keln' : locale === 'en' ? 'Cologne' : 'Köln',
|
||||
addressRegion: 'NRW',
|
||||
addressCountry: 'DE',
|
||||
},
|
||||
sameAs: [
|
||||
'https://www.linkedin.com/in/damjansavic/',
|
||||
'https://github.com/damjansavic',
|
||||
],
|
||||
knowsAbout: [
|
||||
'Artificial Intelligence',
|
||||
'Machine Learning',
|
||||
'Process Automation',
|
||||
'Voice AI',
|
||||
'Web Development',
|
||||
'Python',
|
||||
'TypeScript',
|
||||
'React',
|
||||
'Next.js',
|
||||
'n8n',
|
||||
'SaaS Development',
|
||||
],
|
||||
alumniOf: [
|
||||
{
|
||||
'@type': 'EducationalOrganization',
|
||||
name: 'FOM Hochschule für Ökonomie & Management',
|
||||
},
|
||||
],
|
||||
hasCredential: [
|
||||
{
|
||||
'@type': 'EducationalOccupationalCredential',
|
||||
name: 'M.A. Software Development',
|
||||
credentialCategory: 'degree',
|
||||
},
|
||||
{
|
||||
'@type': 'EducationalOccupationalCredential',
|
||||
name: 'B.Sc. Wirtschaftsinformatik',
|
||||
credentialCategory: 'degree',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfessionalServiceJsonLd({ locale }: JsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const serviceNames = {
|
||||
de: [
|
||||
'KI-Entwicklung',
|
||||
'Prozessautomatisierung',
|
||||
'Voice AI Entwicklung',
|
||||
'SaaS Entwicklung',
|
||||
'Fullstack Webentwicklung',
|
||||
'n8n Automatisierung',
|
||||
],
|
||||
en: [
|
||||
'AI Development',
|
||||
'Process Automation',
|
||||
'Voice AI Development',
|
||||
'SaaS Development',
|
||||
'Fullstack Web Development',
|
||||
'n8n Automation',
|
||||
],
|
||||
sr: [
|
||||
'AI Razvoj',
|
||||
'Automatizacija procesa',
|
||||
'Voice AI Razvoj',
|
||||
'SaaS Razvoj',
|
||||
'Fullstack Web Razvoj',
|
||||
'n8n Automatizacija',
|
||||
],
|
||||
};
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ProfessionalService',
|
||||
'@id': `${baseUrl}/#business`,
|
||||
name: 'Damjan Savić - AI & Automation Specialist',
|
||||
description: locale === 'de'
|
||||
? 'Freelance AI & Automation Specialist aus Köln. Spezialisiert auf KI-Agenten, Voice AI, Prozessautomatisierung und SaaS-Entwicklung.'
|
||||
: locale === 'sr'
|
||||
? 'Freelance AI & Automation Specialist iz Kelna. Specijalizovan za AI agente, Voice AI, automatizaciju procesa i SaaS razvoj.'
|
||||
: 'Remote AI & Automation Specialist based in Germany, serving clients in USA, UK and Europe. Specialized in AI agents, Voice AI, n8n process automation and custom SaaS development.',
|
||||
url: baseUrl,
|
||||
logo: `${baseUrl}/images/og-image.jpg`,
|
||||
image: `${baseUrl}/images/og-image.jpg`,
|
||||
email: 'info@damjan-savic.com',
|
||||
telephone: '+491756950979',
|
||||
priceRange: '€€€',
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
streetAddress: 'Rotdornallee',
|
||||
addressLocality: locale === 'sr' ? 'Keln' : locale === 'en' ? 'Cologne' : 'Köln',
|
||||
addressRegion: 'NRW',
|
||||
postalCode: '50769',
|
||||
addressCountry: 'DE',
|
||||
},
|
||||
geo: {
|
||||
'@type': 'GeoCoordinates',
|
||||
latitude: 50.9375,
|
||||
longitude: 6.9603,
|
||||
},
|
||||
areaServed: [
|
||||
// Germany
|
||||
{ '@type': 'City', name: 'Cologne' },
|
||||
{ '@type': 'City', name: 'Düsseldorf' },
|
||||
{ '@type': 'City', name: 'Frankfurt' },
|
||||
{ '@type': 'City', name: 'Munich' },
|
||||
{ '@type': 'City', name: 'Berlin' },
|
||||
{ '@type': 'City', name: 'Hamburg' },
|
||||
{ '@type': 'Country', name: 'Germany' },
|
||||
// USA
|
||||
{ '@type': 'City', name: 'New York' },
|
||||
{ '@type': 'City', name: 'Los Angeles' },
|
||||
{ '@type': 'City', name: 'San Francisco' },
|
||||
{ '@type': 'City', name: 'Miami' },
|
||||
{ '@type': 'City', name: 'Chicago' },
|
||||
{ '@type': 'Country', name: 'United States' },
|
||||
// UK
|
||||
{ '@type': 'City', name: 'London' },
|
||||
{ '@type': 'Country', name: 'United Kingdom' },
|
||||
// Europe
|
||||
{ '@type': 'Country', name: 'Austria' },
|
||||
{ '@type': 'Country', name: 'Switzerland' },
|
||||
],
|
||||
hasOfferCatalog: {
|
||||
'@type': 'OfferCatalog',
|
||||
name: locale === 'de' ? 'Dienstleistungen' : locale === 'sr' ? 'Usluge' : 'Services',
|
||||
itemListElement: serviceNames[locale].map((service, index) => ({
|
||||
'@type': 'Offer',
|
||||
itemOffered: {
|
||||
'@type': 'Service',
|
||||
name: service,
|
||||
},
|
||||
position: index + 1,
|
||||
})),
|
||||
},
|
||||
founder: {
|
||||
'@id': `${baseUrl}/#person`,
|
||||
},
|
||||
sameAs: [
|
||||
'https://www.linkedin.com/in/damjansavic/',
|
||||
'https://github.com/damjansavic',
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function BreadcrumbJsonLd({
|
||||
items,
|
||||
locale,
|
||||
}: {
|
||||
items: { name: string; url: string }[];
|
||||
locale: Locale;
|
||||
}) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
itemListElement: items.map((item, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: item.url.startsWith('http') ? item.url : `${baseUrl}${item.url}`,
|
||||
})),
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function WebPageJsonLd({
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
locale,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
locale: Locale;
|
||||
}) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebPage',
|
||||
name: title,
|
||||
description: description,
|
||||
url: url.startsWith('http') ? url : `${baseUrl}${url}`,
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: 'Damjan Savić',
|
||||
url: baseUrl,
|
||||
},
|
||||
author: {
|
||||
'@id': `${baseUrl}/#person`,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface FAQItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export function FAQJsonLd({ items, locale }: { items: FAQItem[]; locale?: Locale }) {
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
inLanguage: locale ? (locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US') : 'de-DE',
|
||||
mainEntity: items.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: item.answer,
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface ServiceJsonLdProps {
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
locale: Locale;
|
||||
areaServed?: string[];
|
||||
}
|
||||
|
||||
export function ServiceJsonLd({
|
||||
name,
|
||||
description,
|
||||
url,
|
||||
locale,
|
||||
areaServed = ['Köln', 'Düsseldorf', 'Frankfurt', 'Deutschland'],
|
||||
}: ServiceJsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Service',
|
||||
name: name,
|
||||
description: description,
|
||||
url: url.startsWith('http') ? url : `${baseUrl}${url}`,
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
availableLanguage: ['de-DE', 'en-US', 'sr-RS'],
|
||||
provider: {
|
||||
'@id': `${baseUrl}/#business`,
|
||||
},
|
||||
areaServed: areaServed.map((area) => ({
|
||||
'@type': 'City',
|
||||
name: area,
|
||||
})),
|
||||
serviceType: name,
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Organization Schema
|
||||
export function OrganizationJsonLd({ locale }: JsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
'@id': `${baseUrl}/#organization`,
|
||||
name: 'Damjan Savić - AI & Automation Specialist',
|
||||
alternateName: 'Damjan Savić',
|
||||
url: baseUrl,
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${baseUrl}/images/og-image.jpg`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
image: `${baseUrl}/images/og-image.jpg`,
|
||||
description: locale === 'de'
|
||||
? 'Freelance KI-Entwickler und Automatisierungsspezialist aus Köln. Spezialisiert auf KI-Agenten, Voice AI, Prozessautomatisierung mit n8n und SaaS-Entwicklung.'
|
||||
: locale === 'sr'
|
||||
? 'Freelance AI developer i specijalista za automatizaciju iz Kelna. Specijalizovan za AI agente, Voice AI, automatizaciju procesa sa n8n i SaaS razvoj.'
|
||||
: 'Remote AI developer and automation specialist based in Germany. Working with clients in USA, UK & Europe. Specialized in AI agents, Voice AI, n8n automation and SaaS development.',
|
||||
email: 'info@damjan-savic.com',
|
||||
telephone: '+491756950979',
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
streetAddress: 'Rotdornallee',
|
||||
addressLocality: locale === 'sr' ? 'Keln' : locale === 'en' ? 'Cologne' : 'Köln',
|
||||
addressRegion: 'NRW',
|
||||
postalCode: '50769',
|
||||
addressCountry: 'DE',
|
||||
},
|
||||
geo: {
|
||||
'@type': 'GeoCoordinates',
|
||||
latitude: 50.9375,
|
||||
longitude: 6.9603,
|
||||
},
|
||||
areaServed: [
|
||||
// DACH Region
|
||||
{ '@type': 'Country', name: 'Germany' },
|
||||
{ '@type': 'Country', name: 'Austria' },
|
||||
{ '@type': 'Country', name: 'Switzerland' },
|
||||
// International (Remote)
|
||||
{ '@type': 'Country', name: 'United States' },
|
||||
{ '@type': 'Country', name: 'United Kingdom' },
|
||||
{ '@type': 'Country', name: 'Serbia' },
|
||||
// Major International Cities
|
||||
{ '@type': 'City', name: 'New York' },
|
||||
{ '@type': 'City', name: 'London' },
|
||||
{ '@type': 'City', name: 'San Francisco' },
|
||||
{ '@type': 'City', name: 'Los Angeles' },
|
||||
],
|
||||
founder: {
|
||||
'@id': `${baseUrl}/#person`,
|
||||
},
|
||||
sameAs: [
|
||||
'https://www.linkedin.com/in/damjansavic/',
|
||||
'https://github.com/damjansavic',
|
||||
],
|
||||
contactPoint: {
|
||||
'@type': 'ContactPoint',
|
||||
contactType: 'customer service',
|
||||
email: 'info@damjan-savic.com',
|
||||
telephone: '+491756950979',
|
||||
availableLanguage: ['German', 'English', 'Serbian'],
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Article Schema for Blog Posts
|
||||
interface ArticleJsonLdProps {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
datePublished: string;
|
||||
dateModified?: string;
|
||||
authorName?: string;
|
||||
tags?: string[];
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export function ArticleJsonLd({
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
imageUrl,
|
||||
datePublished,
|
||||
dateModified,
|
||||
authorName = 'Damjan Savić',
|
||||
tags = [],
|
||||
locale,
|
||||
}: ArticleJsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Article',
|
||||
headline: title,
|
||||
description: description,
|
||||
image: imageUrl.startsWith('http') ? imageUrl : `${baseUrl}${imageUrl}`,
|
||||
url: url.startsWith('http') ? url : `${baseUrl}${url}`,
|
||||
datePublished: datePublished,
|
||||
dateModified: dateModified || datePublished,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
'@id': `${baseUrl}/#person`,
|
||||
name: authorName,
|
||||
url: baseUrl,
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
'@id': `${baseUrl}/#organization`,
|
||||
name: 'Damjan Savić',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${baseUrl}/images/og-image.jpg`,
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': url.startsWith('http') ? url : `${baseUrl}${url}`,
|
||||
},
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
keywords: tags.join(', '),
|
||||
articleSection: 'Technology',
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// HowTo Schema for Tutorials
|
||||
interface HowToStep {
|
||||
name: string;
|
||||
text: string;
|
||||
url?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface HowToJsonLdProps {
|
||||
name: string;
|
||||
description: string;
|
||||
steps: HowToStep[];
|
||||
totalTime?: string; // ISO 8601 duration format, e.g., "PT30M" for 30 minutes
|
||||
estimatedCost?: { currency: string; value: string };
|
||||
tools?: string[];
|
||||
supplies?: string[];
|
||||
image?: string;
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export function HowToJsonLd({
|
||||
name,
|
||||
description,
|
||||
steps,
|
||||
totalTime,
|
||||
estimatedCost,
|
||||
tools,
|
||||
supplies,
|
||||
image,
|
||||
locale,
|
||||
}: HowToJsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema: Record<string, unknown> = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'HowTo',
|
||||
name: name,
|
||||
description: description,
|
||||
step: steps.map((step, index) => ({
|
||||
'@type': 'HowToStep',
|
||||
position: index + 1,
|
||||
name: step.name,
|
||||
text: step.text,
|
||||
...(step.url && { url: step.url.startsWith('http') ? step.url : `${baseUrl}${step.url}` }),
|
||||
...(step.image && { image: step.image.startsWith('http') ? step.image : `${baseUrl}${step.image}` }),
|
||||
})),
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
};
|
||||
|
||||
if (totalTime) {
|
||||
schema.totalTime = totalTime;
|
||||
}
|
||||
|
||||
if (estimatedCost) {
|
||||
schema.estimatedCost = {
|
||||
'@type': 'MonetaryAmount',
|
||||
currency: estimatedCost.currency,
|
||||
value: estimatedCost.value,
|
||||
};
|
||||
}
|
||||
|
||||
if (tools && tools.length > 0) {
|
||||
schema.tool = tools.map((tool) => ({
|
||||
'@type': 'HowToTool',
|
||||
name: tool,
|
||||
}));
|
||||
}
|
||||
|
||||
if (supplies && supplies.length > 0) {
|
||||
schema.supply = supplies.map((supply) => ({
|
||||
'@type': 'HowToSupply',
|
||||
name: supply,
|
||||
}));
|
||||
}
|
||||
|
||||
if (image) {
|
||||
schema.image = image.startsWith('http') ? image : `${baseUrl}${image}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ProfilePage Schema for About Page
|
||||
interface ProfilePageJsonLdProps {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export function ProfilePageJsonLd({ locale }: ProfilePageJsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
dateCreated: '2024-01-01',
|
||||
dateModified: new Date().toISOString().split('T')[0],
|
||||
mainEntity: {
|
||||
'@id': `${baseUrl}/#person`,
|
||||
},
|
||||
name: locale === 'de'
|
||||
? 'Über Damjan Savić - KI-Entwickler & Automatisierungsspezialist'
|
||||
: locale === 'sr'
|
||||
? 'O Damjanu Saviću - AI Developer & Specijalista za automatizaciju'
|
||||
: 'About Damjan Savić - AI Developer & Automation Specialist',
|
||||
description: locale === 'de'
|
||||
? 'Erfahren Sie mehr über Damjan Savić, Freelance KI-Entwickler und Automatisierungsspezialist aus Köln. Expertise in KI-Agenten, Voice AI, n8n und SaaS-Entwicklung.'
|
||||
: locale === 'sr'
|
||||
? 'Saznajte više o Damjanu Saviću, freelance AI developeru i specijalisti za automatizaciju iz Kelna. Ekspertiza u AI agentima, Voice AI, n8n i SaaS razvoju.'
|
||||
: 'Learn more about Damjan Savić, freelance AI developer and automation specialist from Cologne. Expertise in AI agents, Voice AI, n8n and SaaS development.',
|
||||
url: `${baseUrl}/${locale}/about`,
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// VideoObject Schema for embedded videos
|
||||
interface VideoJsonLdProps {
|
||||
name: string;
|
||||
description: string;
|
||||
thumbnailUrl: string;
|
||||
uploadDate: string;
|
||||
duration?: string; // ISO 8601 format, e.g., "PT1M30S"
|
||||
contentUrl?: string;
|
||||
embedUrl?: string;
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export function VideoJsonLd({
|
||||
name,
|
||||
description,
|
||||
thumbnailUrl,
|
||||
uploadDate,
|
||||
duration,
|
||||
contentUrl,
|
||||
embedUrl,
|
||||
locale,
|
||||
}: VideoJsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema: Record<string, unknown> = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'VideoObject',
|
||||
name: name,
|
||||
description: description,
|
||||
thumbnailUrl: thumbnailUrl.startsWith('http') ? thumbnailUrl : `${baseUrl}${thumbnailUrl}`,
|
||||
uploadDate: uploadDate,
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
author: {
|
||||
'@id': `${baseUrl}/#person`,
|
||||
},
|
||||
};
|
||||
|
||||
if (duration) {
|
||||
schema.duration = duration;
|
||||
}
|
||||
|
||||
if (contentUrl) {
|
||||
schema.contentUrl = contentUrl;
|
||||
}
|
||||
|
||||
if (embedUrl) {
|
||||
schema.embedUrl = embedUrl;
|
||||
}
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// SoftwareApplication Schema for tools/projects
|
||||
interface SoftwareAppJsonLdProps {
|
||||
name: string;
|
||||
description: string;
|
||||
applicationCategory: string;
|
||||
operatingSystem?: string;
|
||||
offers?: {
|
||||
price: string;
|
||||
priceCurrency: string;
|
||||
};
|
||||
aggregateRating?: {
|
||||
ratingValue: string;
|
||||
ratingCount: string;
|
||||
};
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export function SoftwareAppJsonLd({
|
||||
name,
|
||||
description,
|
||||
applicationCategory,
|
||||
operatingSystem = 'Web',
|
||||
offers,
|
||||
aggregateRating,
|
||||
locale,
|
||||
}: SoftwareAppJsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema: Record<string, unknown> = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: name,
|
||||
description: description,
|
||||
applicationCategory: applicationCategory,
|
||||
operatingSystem: operatingSystem,
|
||||
author: {
|
||||
'@id': `${baseUrl}/#person`,
|
||||
},
|
||||
inLanguage: locale === 'de' ? 'de-DE' : locale === 'sr' ? 'sr-RS' : 'en-US',
|
||||
};
|
||||
|
||||
if (offers) {
|
||||
schema.offers = {
|
||||
'@type': 'Offer',
|
||||
price: offers.price,
|
||||
priceCurrency: offers.priceCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
if (aggregateRating) {
|
||||
schema.aggregateRating = {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: aggregateRating.ratingValue,
|
||||
ratingCount: aggregateRating.ratingCount,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// WebSite Schema for Search Box
|
||||
export function WebSiteJsonLd({ locale }: JsonLdProps) {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
||||
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
'@id': `${baseUrl}/#website`,
|
||||
name: 'Damjan Savić',
|
||||
alternateName: locale === 'de'
|
||||
? 'Damjan Savić - KI Entwickler Köln'
|
||||
: locale === 'sr'
|
||||
? 'Damjan Savić - AI Developer Keln'
|
||||
: 'Damjan Savić - AI Developer Cologne',
|
||||
url: baseUrl,
|
||||
description: locale === 'de'
|
||||
? 'Portfolio und Blog von Damjan Savić - KI-Entwickler und Automatisierungsspezialist aus Köln'
|
||||
: locale === 'sr'
|
||||
? 'Portfolio i blog Damjana Savića - AI developer i specijalista za automatizaciju iz Kelna'
|
||||
: 'Portfolio and blog of Damjan Savić - AI Developer and Automation Specialist from Cologne',
|
||||
publisher: {
|
||||
'@id': `${baseUrl}/#organization`,
|
||||
},
|
||||
inLanguage: [
|
||||
{ '@type': 'Language', name: 'German', alternateName: 'de' },
|
||||
{ '@type': 'Language', name: 'English', alternateName: 'en' },
|
||||
{ '@type': 'Language', name: 'Serbian', alternateName: 'sr' },
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user