refactoring
This commit is contained in:
parent
d99c0e2429
commit
2169c2994b
|
|
@ -0,0 +1,58 @@
|
||||||
|
import type { Service } from '../data/Service';
|
||||||
|
|
||||||
|
interface FeatureCardsGridProps {
|
||||||
|
featureCards: Service[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FeatureCardsGrid({ featureCards }: FeatureCardsGridProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="features"
|
||||||
|
className="about-section__features"
|
||||||
|
aria-labelledby="features-heading"
|
||||||
|
>
|
||||||
|
<h3 id="features-heading" className="sr-only">My Areas of Expertise</h3>
|
||||||
|
|
||||||
|
<div className="about-section__features-grid">
|
||||||
|
{featureCards.map((card, index) => {
|
||||||
|
const IconComponent = card.icon;
|
||||||
|
const cardId = card.title.toLowerCase().replaceAll(/\s+/g, '-');
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={cardId}
|
||||||
|
className={`about-section__feature-card about-section__feature-card--${card.colorClass}`}
|
||||||
|
aria-labelledby={`feature-${cardId}-title`}
|
||||||
|
aria-describedby={`feature-${cardId}-description`}
|
||||||
|
>
|
||||||
|
<header className="about-section__feature-header">
|
||||||
|
<IconComponent
|
||||||
|
className={`about-section__feature-icon about-section__feature-icon--${card.colorClass}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<h4
|
||||||
|
id={`feature-${cardId}-title`}
|
||||||
|
className={`about-section__feature-title about-section__feature-title--${card.colorClass}`}
|
||||||
|
>
|
||||||
|
{card.title}
|
||||||
|
</h4>
|
||||||
|
</header>
|
||||||
|
<div className="about-section__feature-content">
|
||||||
|
<p
|
||||||
|
id={`feature-${cardId}-description`}
|
||||||
|
className={`about-section__feature-description about-section__feature-description--${card.colorClass}`}
|
||||||
|
>
|
||||||
|
{card.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status indicator for screen readers */}
|
||||||
|
<div className="sr-only">
|
||||||
|
Expertise area {index + 1} of {featureCards.length}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Code, Palette, ShieldCheck, Eye } from 'lucide-react';
|
import { Code, Palette, ShieldCheck, Eye } from 'lucide-react';
|
||||||
import { useLanguage } from '../../contexts/LanguageContext';
|
import { useLanguage } from '../../contexts/LanguageContext';
|
||||||
|
import FeatureCardsGrid from '../FeatureCardsGrid';
|
||||||
import type { Service } from '../../data/Service';
|
import type { Service } from '../../data/Service';
|
||||||
import saschaImage from '../../assets/sascha.png';
|
import saschaImage from '../../assets/sascha.png';
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ export default function AboutSection(props: AboutSectionProps = {}) {
|
||||||
icon: [Code, Palette, ShieldCheck, Eye][index] || Code,
|
icon: [Code, Palette, ShieldCheck, Eye][index] || Code,
|
||||||
title: card.title,
|
title: card.title,
|
||||||
description: card.description,
|
description: card.description,
|
||||||
colorClass: ["primary", "secondary", "tertiary", "quaternary"][index] || "primary"
|
colorClass: (["primary", "secondary", "tertiary", "quaternary"][index] || "primary") as "primary" | "secondary" | "tertiary" | "quaternary"
|
||||||
}))
|
}))
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
@ -79,54 +80,7 @@ export default function AboutSection(props: AboutSectionProps = {}) {
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Feature Cards Grid */}
|
{/* Feature Cards Grid */}
|
||||||
<div
|
<FeatureCardsGrid featureCards={featureCards} />
|
||||||
id="features"
|
|
||||||
className="about-section__features"
|
|
||||||
aria-labelledby="features-heading"
|
|
||||||
>
|
|
||||||
<h3 id="features-heading" className="sr-only">My Areas of Expertise</h3>
|
|
||||||
|
|
||||||
<div className="about-section__features-grid">
|
|
||||||
{featureCards.map((card, index) => {
|
|
||||||
const IconComponent = card.icon;
|
|
||||||
const cardId = card.title.toLowerCase().replaceAll(/\s+/g, '-');
|
|
||||||
return (
|
|
||||||
<article
|
|
||||||
key={cardId}
|
|
||||||
className={`about-section__feature-card about-section__feature-card--${card.colorClass}`}
|
|
||||||
aria-labelledby={`feature-${cardId}-title`}
|
|
||||||
aria-describedby={`feature-${cardId}-description`}
|
|
||||||
>
|
|
||||||
<header className="about-section__feature-header">
|
|
||||||
<IconComponent
|
|
||||||
className={`about-section__feature-icon about-section__feature-icon--${card.colorClass}`}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<h4
|
|
||||||
id={`feature-${cardId}-title`}
|
|
||||||
className={`about-section__feature-title about-section__feature-title--${card.colorClass}`}
|
|
||||||
>
|
|
||||||
{card.title}
|
|
||||||
</h4>
|
|
||||||
</header>
|
|
||||||
<div className="about-section__feature-content">
|
|
||||||
<p
|
|
||||||
id={`feature-${cardId}-description`}
|
|
||||||
className={`about-section__feature-description about-section__feature-description--${card.colorClass}`}
|
|
||||||
>
|
|
||||||
{card.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status indicator for screen readers */}
|
|
||||||
<div className="sr-only">
|
|
||||||
Expertise area {index + 1} of {featureCards.length}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section >
|
</section >
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Award } from 'lucide-react';
|
import { Award } from 'lucide-react';
|
||||||
import { useLanguage } from '../../contexts/LanguageContext';
|
import { useLanguage } from '../../contexts/LanguageContext';
|
||||||
|
import { certificationsData } from '../../config/certifications-config';
|
||||||
import type { Certification } from '../../data/Certification';
|
import type { Certification } from '../../data/Certification';
|
||||||
|
|
||||||
interface CertificationsSectionProps {
|
interface CertificationsSectionProps {
|
||||||
|
|
@ -17,12 +18,7 @@ export default function CertificationsSection(props: CertificationsSectionProps
|
||||||
const {
|
const {
|
||||||
title = texts.title,
|
title = texts.title,
|
||||||
subtitle = texts.subtitle,
|
subtitle = texts.subtitle,
|
||||||
certifications = texts.certificationItems.map((cert, index) => ({
|
certifications = certificationsData
|
||||||
name: cert.name,
|
|
||||||
issuer: cert.issuer,
|
|
||||||
year: ["2025", "2025", "2020", "2020", "2015"][index] || "2025",
|
|
||||||
icon: ""
|
|
||||||
}))
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// Sort certifications by year in descending order (newest first)
|
// Sort certifications by year in descending order (newest first)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { certifications } from './locales/en/certifications';
|
||||||
|
import type { Certification } from '../data/Certification';
|
||||||
|
|
||||||
|
const getCertificationText = (id: number) => {
|
||||||
|
const item = certifications.certificationItems.find(c => c.id === id);
|
||||||
|
if (!item) throw new Error(`Certification with id ${id} not found`);
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const certificationsData: Certification[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: getCertificationText(1).name,
|
||||||
|
issuer: getCertificationText(1).issuer,
|
||||||
|
year: '2025',
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: getCertificationText(2).name,
|
||||||
|
issuer: getCertificationText(2).issuer,
|
||||||
|
year: '2025',
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: getCertificationText(3).name,
|
||||||
|
issuer: getCertificationText(3).issuer,
|
||||||
|
year: '2020',
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: getCertificationText(4).name,
|
||||||
|
issuer: getCertificationText(4).issuer,
|
||||||
|
year: '2020',
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: getCertificationText(5).name,
|
||||||
|
issuer: getCertificationText(5).issuer,
|
||||||
|
year: '2015',
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
].sort((a, b) => Number.parseInt(b.year) - Number.parseInt(a.year));
|
||||||
|
|
@ -3,22 +3,27 @@ export const certifications = {
|
||||||
subtitle: 'Meine beruflichen Qualifikationen und kontinuierliche Weiterbildung',
|
subtitle: 'Meine beruflichen Qualifikationen und kontinuierliche Weiterbildung',
|
||||||
certificationItems: [
|
certificationItems: [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
name: 'Practical Prompt Engineering Masterclass: Hands-On Learning',
|
name: 'Practical Prompt Engineering Masterclass: Hands-On Learning',
|
||||||
issuer: 'Udemy Kurs - Asif Farooqi, Abdullah Dar',
|
issuer: 'Udemy Kurs - Asif Farooqi, Abdullah Dar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
name: 'Understanding Typescript',
|
name: 'Understanding Typescript',
|
||||||
issuer: 'Udemy Kurs - Maximilian Schwarzmüller',
|
issuer: 'Udemy Kurs - Maximilian Schwarzmüller',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
name: 'Angular Step by Step',
|
name: 'Angular Step by Step',
|
||||||
issuer: 'Udemy Kurs - Shivprasad Koirala',
|
issuer: 'Udemy Kurs - Shivprasad Koirala',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 4,
|
||||||
name: 'Angular and Typescript',
|
name: 'Angular and Typescript',
|
||||||
issuer: 'LinkedIn TestDome',
|
issuer: 'LinkedIn TestDome',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 5,
|
||||||
name: 'Bachelor of Science in Informatik',
|
name: 'Bachelor of Science in Informatik',
|
||||||
issuer: 'TU Darmstadt',
|
issuer: 'TU Darmstadt',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,27 @@ export const certifications = {
|
||||||
subtitle: 'My professional qualifications and continuous learning',
|
subtitle: 'My professional qualifications and continuous learning',
|
||||||
certificationItems: [
|
certificationItems: [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
name: 'Practical Prompt Engineering Masterclass: Hands-On Learning',
|
name: 'Practical Prompt Engineering Masterclass: Hands-On Learning',
|
||||||
issuer: 'Udemy Lecture - Asif Farooqi, Abdullah Dar',
|
issuer: 'Udemy Lecture - Asif Farooqi, Abdullah Dar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
name: 'Understanding Typescript',
|
name: 'Understanding Typescript',
|
||||||
issuer: 'Udemy Lecture - Maximilian Schwarzmüller',
|
issuer: 'Udemy Lecture - Maximilian Schwarzmüller',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
name: 'Angular Step by Step',
|
name: 'Angular Step by Step',
|
||||||
issuer: 'Udemy Lecture - Shivprasad Koirala',
|
issuer: 'Udemy Lecture - Shivprasad Koirala',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 4,
|
||||||
name: 'Angular and Typescript',
|
name: 'Angular and Typescript',
|
||||||
issuer: 'LinkedIn TestDome',
|
issuer: 'LinkedIn TestDome',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 5,
|
||||||
name: 'Bachelor of Science in Computer Science',
|
name: 'Bachelor of Science in Computer Science',
|
||||||
issuer: 'TU Darmstadt',
|
issuer: 'TU Darmstadt',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue