Files
dzanan.net/src/app/features/portfolio/portfolio-page.component.ts

117 lines
5.5 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 mono" aria-label="Breadcrumb"><a routerLink="/">Work</a><span>/</span><span>{{ project.index }}</span></nav>
<div class="case-heading">
<div>
<p class="eyebrow mono">{{ project.category }} · {{ project.period }}</p>
<h1>{{ project.title }}</h1>
</div>
<div class="case-intro">
<p>{{ project.summary }}</p>
<dl><div><dt>Role</dt><dd>{{ project.role }}</dd></div><div><dt>Period</dt><dd>{{ project.period }}</dd></div></dl>
</div>
</div>
</header>
<section class="case-section shell narrative" aria-labelledby="challenge-title">
<p class="case-index mono">01 / Context</p>
<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">
<p class="case-index mono">02 / Delivery</p>
<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 class="mono">{{ contributionNumber(index) }}</span><p>{{ contribution }}</p></li>
}
</ol>
</div>
</section>
<section class="diagram-wrap">
<div class="shell"><app-system-diagram [kind]="project.diagram" [title]="project.title" /></div>
</section>
<section class="case-section shell" aria-labelledby="decisions-title">
<p class="case-index mono">03 / Architecture</p>
<div>
<h2 id="decisions-title">Key decisions</h2>
<div class="decision-grid">
@for (decision of project.decisions; track decision.title; let index = $index) {
<article><span class="mono">{{ contributionNumber(index) }}</span><h3>{{ decision.title }}</h3><p>{{ decision.detail }}</p></article>
}
</div>
</div>
</section>
<section class="case-section shell narrative outcome" aria-labelledby="outcome-title">
<p class="case-index mono">04 / Result</p>
<div><h2 id="outcome-title">The outcome</h2><p class="large-copy">{{ project.outcome }}</p></div>
</section>
<section class="case-stack shell" aria-label="Technology stack">
<p class="case-index mono">Technology</p>
<ul>@for (technology of project.stack; track technology) { <li>{{ technology }}</li> }</ul>
</section>
<nav class="case-pagination shell" aria-label="Case studies">
<a [routerLink]="['/work', previous.slug]"><span class="mono">← Previous</span><strong>{{ previous.title }}</strong></a>
<a [routerLink]="['/work', next.slug]"><span class="mono">Next →</span><strong>{{ next.title }}</strong></a>
</nav>
<section class="contact-block shell case-contact" aria-labelledby="case-contact-title">
<p class="eyebrow mono">Have a complex system?</p>
<h2 id="case-contact-title">Let’s make the next change safer and more useful.</h2>
<div class="contact-actions"><a class="button button-light" href="mailto:amar@dzanan.net">Start a conversation <span aria-hidden="true">↗</span></a><a class="text-link light" routerLink="/cv">View full CV →</a></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');
}
}