172 lines
4.8 KiB
TypeScript
172 lines
4.8 KiB
TypeScript
import { useState } from 'react'
|
|
import { useLanguage, type Translations } from '../contexts/LanguageContext'
|
|
import styles from './PortfolioView.module.css'
|
|
|
|
interface Tag {
|
|
label: string
|
|
color: string
|
|
}
|
|
|
|
interface Project {
|
|
nameKey: keyof Translations
|
|
descKey: keyof Translations
|
|
url: string
|
|
sourceUrl?: string
|
|
tags: Tag[]
|
|
}
|
|
|
|
const customerProjects: Project[] = [
|
|
{
|
|
nameKey: 'portfolioProject1Name',
|
|
descKey: 'portfolioProject1Desc',
|
|
url: 'https://prosinervo.com/',
|
|
tags: [
|
|
{ label: 'WordPress', color: '#21759b' },
|
|
{ label: 'PHP', color: '#7a86b8' },
|
|
{ label: 'MySQL', color: '#4479a1' },
|
|
],
|
|
},
|
|
{
|
|
nameKey: 'portfolioProject2Name',
|
|
descKey: 'portfolioProject2Desc',
|
|
url: 'https://www.runosaari.fi/',
|
|
sourceUrl: 'https://gitea.tietokonepaja.fi/tietokonepaja/runosaari',
|
|
tags: [
|
|
{ label: 'Next.js', color: '#3d3d3d' },
|
|
{ label: 'React', color: '#087ea4' },
|
|
{ label: 'TypeScript', color: '#3178c6' },
|
|
{ label: 'SCSS', color: '#c6538c' },
|
|
],
|
|
},
|
|
{
|
|
nameKey: 'portfolioProject3Name',
|
|
descKey: 'portfolioProject3Desc',
|
|
url: 'https://www.livonsaarenosuuskauppa.fi/',
|
|
sourceUrl: 'https://gitea.tietokonepaja.fi/tietokonepaja/osuuskauppa',
|
|
tags: [
|
|
{ label: 'Next.js', color: '#3d3d3d' },
|
|
{ label: 'TypeScript', color: '#3178c6' },
|
|
{ label: 'SCSS', color: '#c6538c' },
|
|
{ label: 'Klapi API', color: '#2e7d32' },
|
|
],
|
|
},
|
|
]
|
|
|
|
const maintenanceProjects: Project[] = [
|
|
{
|
|
nameKey: 'portfolioMaint1Name',
|
|
descKey: 'portfolioMaint1Desc',
|
|
url: 'https://klapi.tietokonepaja.fi/',
|
|
sourceUrl: 'https://gitea.tietokonepaja.fi/tietokonepaja/klapi',
|
|
tags: [
|
|
{ label: 'ASP.NET Core', color: '#512bd4' },
|
|
{ label: 'C#', color: '#7b4fa6' },
|
|
{ label: 'TypeScript', color: '#3178c6' },
|
|
{ label: 'React', color: '#087ea4' },
|
|
{ label: 'SQLite', color: '#0f7b6c' },
|
|
],
|
|
},
|
|
{
|
|
nameKey: 'portfolioMaint2Name',
|
|
descKey: 'portfolioMaint2Desc',
|
|
url: 'https://mail.tietokonepaja.fi/',
|
|
tags: [
|
|
{ label: 'Mailcow', color: '#e65100' },
|
|
{ label: 'Docker', color: '#2496ed' },
|
|
{ label: 'Postfix', color: '#b71c1c' },
|
|
{ label: 'Dovecot', color: '#1565c0' },
|
|
{ label: 'Nginx', color: '#009900' },
|
|
],
|
|
},
|
|
{
|
|
nameKey: 'portfolioMaint3Name',
|
|
descKey: 'portfolioMaint3Desc',
|
|
url: 'https://gitea.tietokonepaja.fi/',
|
|
tags: [
|
|
{ label: 'Gitea', color: '#609926' },
|
|
{ label: 'Go', color: '#00add8' },
|
|
{ label: 'Docker', color: '#2496ed' },
|
|
],
|
|
},
|
|
{
|
|
nameKey: 'portfolioMaint4Name',
|
|
descKey: 'portfolioMaint4Desc',
|
|
url: 'https://hattara.tietokonepaja.fi/',
|
|
tags: [
|
|
{ label: 'Nextcloud', color: '#0082c9' },
|
|
{ label: 'PHP', color: '#7a86b8' },
|
|
{ label: 'Docker', color: '#2496ed' },
|
|
],
|
|
},
|
|
]
|
|
|
|
function ProjectCard({ project }: { project: Project }) {
|
|
const { t } = useLanguage()
|
|
const [open, setOpen] = useState(false)
|
|
|
|
return (
|
|
<div className={styles.projectCard}>
|
|
<button
|
|
className={styles.projectHeader}
|
|
onClick={() => setOpen((prev) => !prev)}
|
|
aria-expanded={open}
|
|
>
|
|
<h4>{t[project.nameKey]}</h4>
|
|
<div className={styles.tagList}>
|
|
{project.tags.map((tag) => (
|
|
<span
|
|
key={tag.label}
|
|
className={styles.tag}
|
|
style={{ backgroundColor: tag.color }}
|
|
>
|
|
{tag.label}
|
|
</span>
|
|
))}
|
|
</div>
|
|
<span className={`${styles.chevron}${open ? ` ${styles.open}` : ''}`}>▾</span>
|
|
</button>
|
|
<div className={`${styles.expandWrapper}${open ? ` ${styles.expanded}` : ''}`}>
|
|
<div className={styles.expandContent}>
|
|
<p>{t[project.descKey]}</p>
|
|
<div className={styles.projectLinks}>
|
|
<a href={project.url} target="_blank" rel="noopener noreferrer">
|
|
{t.portfolioVisitSite}
|
|
</a>
|
|
{project.sourceUrl && (
|
|
<a href={project.sourceUrl} target="_blank" rel="noopener noreferrer">
|
|
{t.portfolioSourceCode}
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function PortfolioView() {
|
|
const { t } = useLanguage()
|
|
|
|
return (
|
|
<div className={styles.portfolio}>
|
|
<section>
|
|
<h3>{t.portfolioCustomerHeading}</h3>
|
|
<div className={styles.projectList}>
|
|
{customerProjects.map((project) => (
|
|
<ProjectCard key={project.url} project={project} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h3>{t.portfolioMaintenanceHeading}</h3>
|
|
<div className={styles.projectList}>
|
|
{maintenanceProjects.map((project) => (
|
|
<ProjectCard key={project.url} project={project} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
)
|
|
}
|