Files
dzanan.net/src/app/features/portfolio/portfolio-page.component.ts
amar.dzanan 4d754f33c1
All checks were successful
Deploy dzanan.net / deploy (push) Successful in 1m0s
redesign complete
2026-09-06 22:16:19 +02:00

112 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { PageShellComponent } from '../../core/layout/page-shell.component';
import { SeoService } from '../../core/services/seo.service';
import { MAIN_NAVIGATION } from '../../shared/config/navigation.config';
import { PortfolioProjectId } from '../work/portfolio-project.model';
import { getPortfolioProject, PORTFOLIO_PROJECTS } from '../work/portfolio-projects.data';
import { SystemDiagramComponent } from '../work/system-diagram.component';
@Component({
selector: 'app-portfolio-page',
standalone: true,
imports: [PageShellComponent, RouterLink, SystemDiagramComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-page-shell [sections]="sections">
<article>
<header class="case-hero shell">
<nav class="breadcrumb" aria-label="Breadcrumb"><a routerLink="/work">~/work/</a><span>{{ project.slug }}</span></nav>
<p class="label">{{ project.category }} · {{ project.period }}</p>
<h1>{{ project.title }}</h1>
<p class="lead">{{ project.summary }}</p>
<dl class="panel case-meta">
<div><dt>role</dt><dd>{{ project.role }}</dd></div>
<div><dt>period</dt><dd>{{ project.period }}</dd></div>
<div><dt>stack</dt><dd class="tag-row">@for (technology of project.stack; track technology) {<span class="tag">{{ technology }}</span>}</dd></div>
</dl>
</header>
<section class="case-section shell" aria-labelledby="challenge-title">
<span class="label">// 01 context</span>
<div><h2 id="challenge-title">The challenge</h2><p class="large-copy">{{ project.challenge }}</p></div>
</section>
<section class="case-section shell" aria-labelledby="contributions-title">
<span class="label">// 02 delivery</span>
<div>
<h2 id="contributions-title">What I contributed</h2>
<ol class="contribution-list">
@for (contribution of project.contributions; track contribution; let index = $index) {
<li><span>{{ contributionNumber(index) }}</span><p>{{ contribution }}</p></li>
}
</ol>
</div>
</section>
<section class="diagram-wrap shell">
<app-system-diagram [kind]="project.diagram" [title]="project.title" />
</section>
<section class="case-section shell" aria-labelledby="decisions-title">
<span class="label">// 03 architecture</span>
<div>
<h2 id="decisions-title">Key decisions</h2>
<div class="decision-grid">
@for (decision of project.decisions; track decision.title; let index = $index) {
<article class="panel decision"><span>{{ contributionNumber(index) }}</span><h3>{{ decision.title }}</h3><p>{{ decision.detail }}</p></article>
}
</div>
</div>
</section>
<section class="case-section shell" aria-labelledby="outcome-title">
<span class="label">// 04 result</span>
<div><h2 id="outcome-title">The outcome</h2><p class="large-copy">{{ project.outcome }}</p></div>
</section>
<nav class="case-pagination shell" aria-label="Case studies">
<a class="panel" [routerLink]="['/work', previous.slug]"><span class="mono">← previous</span><strong>{{ previous.title }}</strong></a>
<a class="panel" [routerLink]="['/work', next.slug]"><span class="mono">next →</span><strong>{{ next.title }}</strong></a>
</nav>
<section class="shell case-contact" aria-labelledby="case-contact-title">
<div class="panel contact">
<span class="label">// contact</span>
<h2 id="case-contact-title">Have a complex system? Let’s make the next change safer and more useful.</h2>
<div class="contact-actions"><a class="btn btn-accent" href="mailto:amar@dzanan.net">amar&#64;dzanan.net</a><a class="text-link" routerLink="/cv">cv.pdf →</a></div>
</div>
</section>
</article>
</app-page-shell>
`
})
export class PortfolioPageComponent {
readonly sections = MAIN_NAVIGATION;
readonly project = getPortfolioProject(inject(ActivatedRoute).snapshot.data['projectId'] as PortfolioProjectId);
readonly projectIndex = PORTFOLIO_PROJECTS.findIndex(item => item.id === this.project.id);
readonly previous = PORTFOLIO_PROJECTS[(this.projectIndex - 1 + PORTFOLIO_PROJECTS.length) % PORTFOLIO_PROJECTS.length];
readonly next = PORTFOLIO_PROJECTS[(this.projectIndex + 1) % PORTFOLIO_PROJECTS.length];
constructor() {
inject(SeoService).update({
title: `${this.project.title} — Amar Džanan`,
description: this.project.seoDescription,
path: `/work/${this.project.slug}`,
type: 'article',
structuredData: {
'@context': 'https://schema.org',
'@type': 'Article',
headline: this.project.title,
description: this.project.seoDescription,
author: { '@type': 'Person', name: 'Amar Džanan', url: 'https://dzanan.net/' },
url: `https://dzanan.net/work/${this.project.slug}`
}
});
}
contributionNumber(index: number): string {
return String(index + 1).padStart(2, '0');
}
}