Give each service its own page
A service now has its address, like a project: icon, tagline, what the collective does and how, the projects it was delivered on, what the people it was for said, and the technologies behind it. The landing's service cards link to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
128
landing/src/app/ui/service-detail.ts
Normal file
128
landing/src/app/ui/service-detail.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
PiChipComponent,
|
||||
PiIconComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextLargeDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
} from '@pi/ui';
|
||||
|
||||
import type { Locale } from '../core/api';
|
||||
import type { PublicService } from '../core/api.types';
|
||||
import type { Copy } from '../core/copy';
|
||||
import { resolveIcon } from '../core/icons';
|
||||
import { ContactCta } from './contact-cta';
|
||||
import { ProjectCard } from './project-card';
|
||||
import { TestimonialCard } from './testimonial-card';
|
||||
|
||||
/**
|
||||
* A service on its own address, and the published work that backs it: the
|
||||
* projects it was delivered on and what the people it was for said.
|
||||
*
|
||||
* The description comes from a textarea, so it is printed as text with its
|
||||
* line breaks kept.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-service-detail',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
ContactCta,
|
||||
PiChipComponent,
|
||||
PiIconComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextLargeDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
ProjectCard,
|
||||
TestimonialCard,
|
||||
],
|
||||
styleUrl: './service-detail.scss',
|
||||
template: `
|
||||
<header class="content-hero">
|
||||
<div class="landing-shell">
|
||||
<pi-icon
|
||||
class="service-icon"
|
||||
[icon]="icon()"
|
||||
size="x-large"
|
||||
color="primary"
|
||||
/>
|
||||
<p class="landing-eyebrow">{{ copy().service.eyebrow }}</p>
|
||||
<h1 bold class="content-hero__title">{{ service().title }}</h1>
|
||||
|
||||
@if (service().tagline; as tagline) {
|
||||
<p l pi-color color="grey" class="content-hero__lead">
|
||||
{{ tagline }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="landing-shell content-page__layout">
|
||||
<article class="content-page__main">
|
||||
@if (service().description; as description) {
|
||||
<p class="service-text">{{ description }}</p>
|
||||
}
|
||||
|
||||
@if ((service().projects ?? []).length > 0) {
|
||||
<section class="content-page__section">
|
||||
<h2>{{ copy().service.projects }}</h2>
|
||||
<div class="service-grid">
|
||||
@for (project of service().projects; track project.id) {
|
||||
<app-project-card [project]="project" [copy]="copy()" />
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@if ((service().testimonials ?? []).length > 0) {
|
||||
<section class="content-page__section">
|
||||
<h2>{{ copy().service.testimonials }}</h2>
|
||||
<div class="service-grid">
|
||||
@for (
|
||||
testimonial of service().testimonials;
|
||||
track testimonial.id
|
||||
) {
|
||||
<app-testimonial-card [testimonial]="testimonial" />
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</article>
|
||||
|
||||
<aside class="content-page__aside">
|
||||
@if ((service().technologies ?? []).length > 0) {
|
||||
<div class="landing-card content-page__panel">
|
||||
<h2>{{ copy().service.technologies }}</h2>
|
||||
<ul class="content-hero__chips service-technologies">
|
||||
@for (technology of service().technologies; track technology.id) {
|
||||
<li>
|
||||
<pi-chip
|
||||
size="small"
|
||||
appearance="outline"
|
||||
[label]="technology.name"
|
||||
/>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
<app-contact-cta [copy]="copy()" [locale]="locale()" />
|
||||
</aside>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class ServiceDetail {
|
||||
readonly service = input.required<PublicService>();
|
||||
readonly copy = input.required<Copy>();
|
||||
readonly locale = input.required<Locale>();
|
||||
|
||||
/** The panel lets anyone type anything here, emoji included. */
|
||||
protected readonly icon = computed(() =>
|
||||
resolveIcon(this.service().icon, 'support'),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user