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>
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
# 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. BALANCE. 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. BALANCE. 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]));
|
||||
Reference in New Issue
Block a user