112 lines
5.3 KiB
TypeScript
112 lines
5.3 KiB
TypeScript
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@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');
|
||
}
|
||
}
|