Reformer Class: bottone consulenza conoscitiva gratuita (#22)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 20:30:09 +00:00
parent 9126af28ce
commit 67dc554810
4 changed files with 63 additions and 2 deletions
+15
View File
@@ -0,0 +1,15 @@
// Consulenza conoscitiva gratuita con Nicola (prenotazione su Google Calendar).
//
// Il link vive nel pannello contenuti (tag `global.consulenza.url`): qui c'è il valore di
// partenza e la guardia. Il tag è modificabile da admin, quindi si accetta solo https.
export const CONSULENZA_URL_DEFAULT = 'https://calendar.app.google/nWXMGD2S5g2pVeeD7';
/** Slug delle pagine servizio che mostrano il bottone di prenotazione. */
export const PAGINE_CON_CONSULENZA = ['reformer-class'];
/** Link da usare: il valore del pannello se è https, altrimenti quello di default
* (niente javascript:, http:, vuoto). */
export function linkConsulenza(valore: string): string {
const v = (valore ?? '').trim();
return v.startsWith('https://') ? v : CONSULENZA_URL_DEFAULT;
}
+4
View File
@@ -7,6 +7,7 @@ import { partners } from './partners';
import { disciplines, days, slots } from './agenda';
import { services } from './services';
import { performanceClassPricing } from './pricing';
import { CONSULENZA_URL_DEFAULT } from './consulenza';
export type ContentType = 'text' | 'html' | 'image';
export interface SeedEntry { tag: string; type: ContentType; value: string; styleOptions?: StyleGroup[] }
@@ -270,6 +271,9 @@ export const contentSeed: SeedEntry[] = [
T('service.pricing.entries-plural', 'ingressi settimanali', []),
T('service.pricing.buy-cta', 'Acquista', []),
T('service.contact-cta', 'Contattaci', []),
T('service.consulenza-cta', 'Prenota una consulenza conoscitiva gratuita', []),
T('service.consulenza-note', '30-45 minuti con Nicola', []),
T('global.consulenza.url', CONSULENZA_URL_DEFAULT, []),
// --- Pricing performance class (data-driven da pricing.ts; monthly/entries restano nel data file) ---
T('pricing.heading', performanceClassPricing.heading),
+16 -2
View File
@@ -10,6 +10,7 @@ import TImg from '../../components/content/TImg.astro';
import CalendarioModal from '../../components/CalendarioModal.astro';
import { calendariDaMostrare, PAGINE_CON_CALENDARIO } from '../../data/calendari';
import { t } from '../../lib/content';
import { PAGINE_CON_CONSULENZA, linkConsulenza } from '../../data/consulenza';
export const prerender = false;
const service = getService(Astro.params.slug!);
@@ -38,8 +39,16 @@ const tones = ['tan', 'taupe', 'dark'];
<div>
<h2>{t(`service.${slug}.title`).toUpperCase()}</h2>
<T tag={`service.${slug}.long-description`} as="p" class="prog__desc" />
{slug === 'reformer-class' && (
<p class="prog__cta"><a class="btn" href="/contact">{t('service.contact-cta')}</a></p>
{(slug === 'reformer-class' || PAGINE_CON_CONSULENZA.includes(slug)) && (
<div class="prog__cta">
<div class="prog__cta-row">
{slug === 'reformer-class' && <a class="btn" href="/contact">{t('service.contact-cta')}</a>}
{PAGINE_CON_CONSULENZA.includes(slug) && (
<a class="btn btn--consulenza" href={linkConsulenza(t('global.consulenza.url'))} target="_blank" rel="noopener noreferrer">{t('service.consulenza-cta')}</a>
)}
</div>
{PAGINE_CON_CONSULENZA.includes(slug) && <T tag="service.consulenza-note" as="p" class="prog__cta-note" />}
</div>
)}
{service.features.length > 0 && (
<ul class="prog__features">
@@ -149,6 +158,11 @@ const tones = ['tan', 'taupe', 'dark'];
.prog__calendario { display: flex; align-items: center; gap: 14px; width: 100%; margin-top: 36px; padding: 10px 16px 10px 10px; border: 0; cursor: pointer; text-align: left; background: #6b5d5b; color: #fff; font-family: var(--font-heading); font-size: .82rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; transition: background .2s; }
.prog__calendario:hover { background: var(--c-accent-dark); }
.prog__cta { margin: 10px 0 30px; }
.prog__cta-row { display: flex; flex-wrap: wrap; gap: 12px; }
.btn--consulenza { background: #6b5d5b; }
.btn--consulenza:hover { background: var(--c-accent-dark); }
.prog__cta-note { font-size: .8rem; color: var(--c-text-light); margin: 8px 0 0; }
@media (max-width: 600px) { .prog__cta-row .btn { flex: 1 1 100%; justify-content: center; text-align: center; } }
.prog__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: var(--c-accent); color: #fff; flex-shrink: 0; }
.price__group { border-top: 1px solid #ddd5ca; padding-block: 18px; }
.price__group summary { cursor: pointer; font-family: var(--font-heading); letter-spacing: .12em; text-transform: uppercase; font-size: .85rem; color: var(--c-heading); }
+28
View File
@@ -0,0 +1,28 @@
import { describe, it, expect } from 'vitest';
import { linkConsulenza, CONSULENZA_URL_DEFAULT, PAGINE_CON_CONSULENZA } from '../src/data/consulenza';
import { contentSeed } from '../src/data/content-seed';
import { services } from '../src/data/services';
describe('consulenza conoscitiva', () => {
it('accetta un link https, anche con spazi attorno', () => {
expect(linkConsulenza('https://example.com/x')).toBe('https://example.com/x');
expect(linkConsulenza(' https://example.com/x ')).toBe('https://example.com/x');
});
it.each(['javascript:alert(1)', 'http://x.it', '', ' '])('scarta %j e usa il default', (v) => {
expect(linkConsulenza(v)).toBe(CONSULENZA_URL_DEFAULT);
});
it('il seed ha i tre tag', () => {
const v = (tag: string) => contentSeed.find((e) => e.tag === tag)?.value;
expect(v('service.consulenza-cta')).toBe('Prenota una consulenza conoscitiva gratuita');
expect(v('service.consulenza-note')).toBe('30-45 minuti con Nicola');
expect(v('global.consulenza.url')).toBe(CONSULENZA_URL_DEFAULT);
});
it('le pagine con consulenza esistono', () => {
expect(PAGINE_CON_CONSULENZA).toContain('reformer-class');
for (const slug of PAGINE_CON_CONSULENZA) {
const s = services.find((x) => x.slug === slug);
expect(s, slug).toBeDefined();
expect(s!.page).not.toBe(false);
}
});
});