Initial commit

This commit is contained in:
pigui
2026-08-30 21:11:59 +02:00
commit 3f930bb46a
168 changed files with 35708 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import {
PiAvatarComponent,
PiCardCenterContentDirective,
PiCardComponent,
PiCardTopContentDirective,
PiChipComponent,
PiTextColorDirective,
PiTextSmallDirective,
PiTextViewerLinkComponent,
PiTextWeightBoldDirective,
} from '@pi/ui';
import type { Copy } from '../core/copy';
import type { PublicMember } from '../core/api.types';
@Component({
selector: 'app-member-card',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
PiCardComponent,
PiCardTopContentDirective,
PiCardCenterContentDirective,
PiAvatarComponent,
PiChipComponent,
PiTextViewerLinkComponent,
PiTextSmallDirective,
PiTextColorDirective,
PiTextWeightBoldDirective,
],
template: `
<pi-card
class="landing-card"
[hasShadow]="true"
[isStroked]="true"
gap="m"
padding="l"
innerPadding="none"
alignment="center"
context="public"
>
<pi-card-top-content>
<!-- The avatar falls back to initials on its own, and again to an
icon, so a missing photo never leaves a hole. -->
<pi-avatar
size="x-large"
shape="circle"
color="primary"
icon="user"
[image]="member().avatar?.url"
[alt]="member().avatar?.alt"
[name]="member().name"
/>
</pi-card-top-content>
<pi-card-center-content>
<h3 bold class="landing-card__title">{{ member().name }}</h3>
@if (member().role || member().headline; as subtitle) {
<p s pi-color color="grey">{{ subtitle }}</p>
}
<div class="landing-card__chips landing-card__chips--center">
@if (member().is_founder) {
<pi-chip size="small" color="primary" [label]="copy().labels.founder" />
}
@if (!member().is_current) {
<pi-chip
size="small"
appearance="outline"
[label]="copy().labels.alumni"
/>
}
</div>
@if (links().length > 0) {
<div class="landing-card__links landing-card__links--center">
@for (link of links(); track link.label) {
<pi-text-viewer-link
icon="link"
iconSize="x-small"
[value]="link.label"
[link]="link.url"
target="_blank"
/>
}
</div>
}
</pi-card-center-content>
</pi-card>
`,
})
export class MemberCard {
readonly member = input.required<PublicMember>();
readonly copy = input.required<Copy>();
protected readonly links = computed(() => {
const member = this.member();
const entries = Object.entries(member.links ?? {}).map(([label, url]) => ({
label,
url,
}));
// The username is handed over separately, so build the URL rather than
// hope somebody also filled the links map in.
if (member.github_username && !entries.some((link) => link.label === 'github')) {
entries.unshift({
label: 'github',
url: `https://github.com/${member.github_username}`,
});
}
return entries.slice(0, 3);
});
}