Files
InsanityLab-web/.superpowers/sdd/review-b079ded..f4c350a.diff
Adriano b913a2bf90 registri dei piani in git (AI-OS#309)
Refs gitea:Adriano/AI-OS#310

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 10:36:26 +02:00

229 lines
11 KiB
Diff

# Review package: b079ded..f4c350a
## Commits
f4c350a feat: tag contenuti header, footer e form contatti
## Files changed
src/components/ContactForm.astro | 28 +++++++++++++++++++---------
src/components/Footer.astro | 27 +++++++++++++++------------
src/components/Header.astro | 7 ++++---
src/data/content-seed.ts | 40 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 24 deletions(-)
## Diff
diff --git a/src/components/ContactForm.astro b/src/components/ContactForm.astro
index fec0282..4616694 100644
--- a/src/components/ContactForm.astro
+++ b/src/components/ContactForm.astro
@@ -1,42 +1,52 @@
---
+import { t } from '../lib/content';
+import T from './content/T.astro';
interface Props { variant?: 'full' | 'footer' }
const { variant = 'full' } = Astro.props;
---
-<form class:list={['cform', `cform--${variant}`]} method="post" action="/api/contact" novalidate>
- <input class="field" name="firstName" placeholder="Nome" required maxlength="100" />
- {variant === 'full' && <input class="field" name="lastName" placeholder="Cognome" maxlength="100" />}
- {variant === 'full' && <input class="field" name="phone" placeholder="Telefono" maxlength="40" />}
- <input class="field" type="email" name="email" placeholder="E-mail" required maxlength="200" />
- <textarea class="field" name="message" placeholder="Messaggio" required maxlength="5000"></textarea>
+<form
+ class:list={['cform', `cform--${variant}`]}
+ method="post"
+ action="/api/contact"
+ novalidate
+ data-msg-success={t('form.msg.success')}
+ data-msg-error-generic={t('form.msg.error-generic')}
+ data-msg-error-network={t('form.msg.error-network')}
+>
+ <input class="field" name="firstName" placeholder={t('form.field.first-name')} required maxlength="100" />
+ {variant === 'full' && <input class="field" name="lastName" placeholder={t('form.field.last-name')} maxlength="100" />}
+ {variant === 'full' && <input class="field" name="phone" placeholder={t('form.field.phone')} maxlength="40" />}
+ <input class="field" type="email" name="email" placeholder={t('form.field.email')} required maxlength="200" />
+ <textarea class="field" name="message" placeholder={t('form.field.message')} required maxlength="5000"></textarea>
<input class="hp" type="text" name="website" tabindex="-1" autocomplete="off" />
- <button class="btn" type="submit">Invia messaggio</button>
+ <button class="btn" type="submit"><T tag="form.submit" as="span" /></button>
<p class="form-msg" hidden></p>
</form>
<script>
document.querySelectorAll<HTMLFormElement>('.cform').forEach((form) => {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const msg = form.querySelector<HTMLParagraphElement>('.form-msg')!;
const btn = form.querySelector<HTMLButtonElement>('button')!;
btn.disabled = true;
msg.hidden = true;
try {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(new FormData(form))),
});
const data = await res.json();
- msg.textContent = res.ok ? 'Messaggio inviato, ti ricontatteremo al più presto.' : (data.error ?? 'Errore di invio, riprova più tardi.');
+ msg.textContent = res.ok ? form.dataset.msgSuccess! : (data.error ?? form.dataset.msgErrorGeneric);
msg.className = `form-msg ${res.ok ? 'form-msg--ok' : 'form-msg--err'}`;
if (res.ok) form.reset();
} catch {
- msg.textContent = 'Errore di rete, riprova più tardi.';
+ msg.textContent = form.dataset.msgErrorNetwork!;
msg.className = 'form-msg form-msg--err';
}
msg.hidden = false;
btn.disabled = false;
});
});
</script>
diff --git a/src/components/Footer.astro b/src/components/Footer.astro
index 1546466..dfbf28b 100644
--- a/src/components/Footer.astro
+++ b/src/components/Footer.astro
@@ -1,41 +1,44 @@
---
import { site } from '../data/site';
import ContactForm from './ContactForm.astro';
+import { t, contentImageUrl } from '../lib/content';
+import T from './content/T.astro';
const year = new Date().getFullYear();
---
<footer class="ftr">
<div class="container ftr__grid">
<div>
- <img src="/img/logo-light.png" alt="InsanityLab" width="140" loading="lazy" />
- <p class="ftr__tag">PERFORMANCE.&nbsp; BALANCE.&nbsp; LONGEVITY.</p>
- <p>Promuoviamo il movimento come cura di sé. Abitudini sostenibili per benessere fisico e mentale.</p>
- <p>{site.phone}<br />{site.address}, {site.cityLine}</p>
- {site.hours.map((h) => <p class="ftr__hours">{h}</p>)}
+ <img src={contentImageUrl('footer.logo.image', '/img/logo-light.png')} alt="InsanityLab" width="140" loading="lazy" />
+ <T tag="footer.tagline" as="p" class="ftr__tag" />
+ <T tag="footer.about" as="p" />
+ <p>{t('global.contact.phone')}<br />{t('global.contact.address')}, {t('global.contact.city-line')}</p>
+ <p class="ftr__hours">{t('global.hours.1')}</p>
+ <p class="ftr__hours">{t('global.hours.2')}</p>
</div>
<nav aria-label="training">
- <h4>Training</h4>
- {site.footerTraining.map((l) => <a href={l.href}>{l.label}</a>)}
+ <T tag="footer.col.training-title" as="h4" />
+ {site.footerTraining.map((l, i) => <a href={l.href}>{t(`footer.training.${i + 1}.label`)}</a>)}
</nav>
<nav aria-label="programmi">
- <h4>Programmi</h4>
- {site.footerPrograms.map((l) => <a href={l.href}>{l.label}</a>)}
+ <T tag="footer.col.programs-title" as="h4" />
+ {site.footerPrograms.map((l, i) => <a href={l.href}>{t(`footer.programs.${i + 1}.label`)}</a>)}
</nav>
<div>
- <h4>Contact</h4>
+ <T tag="footer.col.contact-title" as="h4" />
<ContactForm variant="footer" />
</div>
</div>
<div class="ftr__bar">
<div class="container ftr__bar-in">
- <p>© {year} <strong>Insanitylab</strong>, All Rights Reserved</p>
- <p>Seguici {site.socials.map((s) => <a href={s.url} rel="noopener" target="_blank">{s.label}</a>)}</p>
+ <p>© {year} <T tag="footer.copyright" as="span" /></p>
+ <p>{t('footer.social-label')} {site.socials.map((s) => <a href={s.url} rel="noopener" target="_blank">{s.label}</a>)}</p>
</div>
</div>
</footer>
<style>
.ftr { background: var(--c-dark); color: #b9aea4; font-size: .88rem; }
.ftr a { color: #b9aea4; text-decoration: none; display: block; padding: 4px 0; }
.ftr a:hover { color: #fff; }
.ftr h4 { color: #fff; text-transform: uppercase; letter-spacing: .18em; font-size: .78rem; margin-bottom: 18px; }
.ftr__grid { display: grid; grid-template-columns: 1.3fr 1fr 1fr 1.3fr; gap: 40px; padding-block: 70px; }
diff --git a/src/components/Header.astro b/src/components/Header.astro
index 6e722bb..33a0b4c 100644
--- a/src/components/Header.astro
+++ b/src/components/Header.astro
@@ -1,20 +1,21 @@
---
import { site } from '../data/site';
+import { t, contentImageUrl } from '../lib/content';
const path = Astro.url.pathname;
---
<header class="hdr" id="site-header">
<div class="container hdr__in">
- <a href="/" class="hdr__logo"><img src="/img/logo-dark.png" alt="InsanityLab" width="150" /></a>
+ <a href="/" class="hdr__logo"><img src={contentImageUrl('header.logo.image', '/img/logo-dark.png')} alt={t('header.logo.alt')} width="150" /></a>
<nav class="hdr__nav" id="site-nav" aria-label="principale">
- {site.navigation.map((item) => (
- <a href={item.href} class:list={['hdr__link', { 'is-active': path === item.href || (item.href !== '/' && path.startsWith(item.href)) }]}>{item.label}</a>
+ {site.navigation.map((item, i) => (
+ <a href={item.href} class:list={['hdr__link', { 'is-active': path === item.href || (item.href !== '/' && path.startsWith(item.href)) }]}>{t(`global.nav.${i + 1}.label`)}</a>
))}
</nav>
<button class="hdr__burger" id="nav-toggle" aria-label="Apri menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
</header>
<style>
.hdr { position: sticky; top: 0; z-index: 100; background: #fff; transition: box-shadow .2s; }
diff --git a/src/data/content-seed.ts b/src/data/content-seed.ts
index 428ef76..93deb38 100644
--- a/src/data/content-seed.ts
+++ b/src/data/content-seed.ts
@@ -11,14 +11,54 @@ const H = (tag: string, value: string, styleOptions: StyleGroup[] = ['size', 'co
const I = (tag: string): SeedEntry => ({ tag, type: 'image', value: '' });
export const contentSeed: SeedEntry[] = [
// --- Globali (site.ts) ---
T('global.contact.phone', site.phone, []),
T('global.contact.email', site.email, []),
T('global.contact.address', site.address, []),
T('global.contact.city-line', site.cityLine, []),
T('global.hours.1', site.hours[0], []),
T('global.hours.2', site.hours[1], []),
+ T('global.nav.1.label', site.navigation[0].label, []),
+ T('global.nav.2.label', site.navigation[1].label, []),
+ T('global.nav.3.label', site.navigation[2].label, []),
+ T('global.nav.4.label', site.navigation[3].label, []),
+ T('global.nav.5.label', site.navigation[4].label, []),
+
+ // --- Header ---
+ T('header.logo.alt', 'InsanityLab', []),
+ I('header.logo.image'),
+
+ // --- Footer ---
+ I('footer.logo.image'),
+ H('footer.tagline', 'PERFORMANCE.&nbsp; BALANCE.&nbsp; LONGEVITY.'),
+ T('footer.about', 'Promuoviamo il movimento come cura di sé. Abitudini sostenibili per benessere fisico e mentale.'),
+ T('footer.col.training-title', 'Training'),
+ T('footer.col.programs-title', 'Programmi'),
+ T('footer.col.contact-title', 'Contact'),
+ H('footer.copyright', '<strong>Insanitylab</strong>, All Rights Reserved'),
+ T('footer.social-label', 'Seguici', []),
+ T('footer.training.1.label', site.footerTraining[0].label, []),
+ T('footer.training.2.label', site.footerTraining[1].label, []),
+ T('footer.training.3.label', site.footerTraining[2].label, []),
+ T('footer.training.4.label', site.footerTraining[3].label, []),
+ T('footer.programs.1.label', site.footerPrograms[0].label, []),
+ T('footer.programs.2.label', site.footerPrograms[1].label, []),
+ T('footer.programs.3.label', site.footerPrograms[2].label, []),
+ T('footer.programs.4.label', site.footerPrograms[3].label, []),
+ T('footer.programs.5.label', site.footerPrograms[4].label, []),
+ T('footer.programs.6.label', site.footerPrograms[5].label, []),
+
+ // --- Contact form ---
+ T('form.field.first-name', 'Nome', []),
+ T('form.field.last-name', 'Cognome', []),
+ T('form.field.phone', 'Telefono', []),
+ T('form.field.email', 'E-mail', []),
+ T('form.field.message', 'Messaggio', []),
+ T('form.submit', 'Invia messaggio'),
+ T('form.msg.success', 'Messaggio inviato, ti ricontatteremo al più presto.', []),
+ T('form.msg.error-generic', 'Errore di invio, riprova più tardi.', []),
+ T('form.msg.error-network', 'Errore di rete, riprova più tardi.', []),
// I task di estrazione aggiungono qui le entry di header/footer/pagine/data file.
];
export const seedByTag = new Map(contentSeed.map((e) => [e.tag, e]));