Deploy stack su Proxmox / deploy (push) Failing after 3s
Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA), apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/; infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e workflow Gitea Actions per il deploy via SSH a ogni push su master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import { ChangeDetectionStrategy, Component, computed, effect, inject, input } from '@angular/core'
|
|
import { RouterLink } from '@angular/router'
|
|
import { findSolution, solutionsOf } from '../../content/catalog'
|
|
import { SITE_CONFIG } from '../core/app-config'
|
|
import { SeoService } from '../core/seo.service'
|
|
import { breadcrumbJsonLd, faqJsonLd } from '../core/structured-data'
|
|
import { PageHeader } from '../shared/page-header'
|
|
import { PlaceholderBadge } from '../shared/placeholder-badge'
|
|
import { SolutionBody } from '../shared/solution-body'
|
|
|
|
/**
|
|
* /soluzioni/:area/:soluzione, generata da src/content/solutions/<area>/<soluzione>.ts.
|
|
* Coppie sconosciute → 404 (canMatch).
|
|
*/
|
|
@Component({
|
|
selector: 'app-solution-page',
|
|
imports: [RouterLink, PageHeader, PlaceholderBadge, SolutionBody],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: `
|
|
@if (data(); as d) {
|
|
<app-page-header
|
|
[heading]="d.solution.title"
|
|
[lead]="d.solution.tagline"
|
|
[crumbs]="[{ label: d.area.label, path: '/soluzioni/' + d.area.slug }, { label: d.solution.title }]"
|
|
[area]="d.area.slug"
|
|
>
|
|
<app-placeholder-badge badge [show]="d.solution.placeholder" />
|
|
</app-page-header>
|
|
<div class="container body">
|
|
<app-solution-body [content]="d.solution" />
|
|
@if (siblings().length) {
|
|
<nav class="siblings" aria-labelledby="siblings-title">
|
|
<h2 id="siblings-title">Altre soluzioni {{ d.area.label }}</h2>
|
|
<ul>
|
|
@for (s of siblings(); track s.slug) {
|
|
<li><a [routerLink]="['/soluzioni', d.area.slug, s.slug]">{{ s.title }}</a></li>
|
|
}
|
|
</ul>
|
|
</nav>
|
|
}
|
|
</div>
|
|
}
|
|
`,
|
|
styles: `
|
|
.body {
|
|
padding-block: var(--space-6) var(--space-4);
|
|
}
|
|
.siblings {
|
|
margin-block: var(--space-7);
|
|
|
|
h2 {
|
|
font-size: var(--font-size-xl);
|
|
}
|
|
ul {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-3);
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
a {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-height: 44px;
|
|
padding: var(--space-2) var(--space-4);
|
|
border: 1px solid var(--color-line);
|
|
border-radius: var(--radius-pill);
|
|
text-decoration: none;
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
export default class SolutionPage {
|
|
readonly area = input.required<string>()
|
|
readonly soluzione = input.required<string>()
|
|
protected readonly data = computed(() => findSolution(this.area(), this.soluzione()))
|
|
protected readonly siblings = computed(() => solutionsOf(this.area()).filter((s) => s.slug !== this.soluzione()))
|
|
|
|
constructor() {
|
|
const seo = inject(SeoService)
|
|
const { siteUrl } = inject(SITE_CONFIG)
|
|
effect(() => {
|
|
const d = this.data()
|
|
if (!d) return
|
|
const areaPath = `/soluzioni/${d.area.slug}`
|
|
const path = `${areaPath}/${d.solution.slug}`
|
|
seo.setPage({ title: `${d.solution.title} - ${d.area.label}`, description: d.solution.description, path, image: d.solution.image.src })
|
|
seo.setJsonLd(
|
|
'breadcrumb',
|
|
breadcrumbJsonLd(siteUrl, [
|
|
{ name: d.area.label, path: areaPath },
|
|
{ name: d.solution.title, path },
|
|
]),
|
|
)
|
|
if (d.solution.faq.length) seo.setJsonLd('faq', faqJsonLd(d.solution.faq))
|
|
})
|
|
}
|
|
}
|