feat: add hero section and service cards to the hub page
CVE Watch card resolves its href per-locale (cve-en/cve-fr), Convert resolves to the same URL on both locales.
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
import { resolveServiceUrl } from '../i18n/services';
|
||||||
|
import type { Service } from '../i18n/services';
|
||||||
|
import type { Locale } from '../i18n/locale';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
service: Service;
|
||||||
|
locale: Locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { service, locale } = Astro.props;
|
||||||
|
const url = resolveServiceUrl(service, locale);
|
||||||
|
---
|
||||||
|
<a class="service-card" href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<span class="service-card__tag">{service.tag[locale]}</span>
|
||||||
|
<h3 class="service-card__name">{service.name[locale]}</h3>
|
||||||
|
<p class="service-card__description">{service.description[locale]}</p>
|
||||||
|
</a>
|
||||||
@@ -1,10 +1,25 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
import Header from '../../components/Header.astro';
|
import Header from '../../components/Header.astro';
|
||||||
|
import ServiceCard from '../../components/ServiceCard.astro';
|
||||||
|
import { getDictionary } from '../../i18n';
|
||||||
|
import { services } from '../../i18n/services';
|
||||||
|
|
||||||
const locale = 'en';
|
const locale = 'en';
|
||||||
|
const t = getDictionary(locale);
|
||||||
---
|
---
|
||||||
<BaseLayout locale={locale}>
|
<BaseLayout locale={locale}>
|
||||||
<Header locale={locale} />
|
<Header locale={locale} />
|
||||||
<h1>Ombrora</h1>
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<h1>{t.hero.title}</h1>
|
||||||
|
<p>{t.hero.subtitle}</p>
|
||||||
|
</section>
|
||||||
|
<section class="services">
|
||||||
|
<h2>{t.services.heading}</h2>
|
||||||
|
<div class="services-grid">
|
||||||
|
{services.map((service) => <ServiceCard service={service} locale={locale} />)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
import Header from '../../components/Header.astro';
|
import Header from '../../components/Header.astro';
|
||||||
|
import ServiceCard from '../../components/ServiceCard.astro';
|
||||||
|
import { getDictionary } from '../../i18n';
|
||||||
|
import { services } from '../../i18n/services';
|
||||||
|
|
||||||
const locale = 'fr';
|
const locale = 'fr';
|
||||||
|
const t = getDictionary(locale);
|
||||||
---
|
---
|
||||||
<BaseLayout locale={locale}>
|
<BaseLayout locale={locale}>
|
||||||
<Header locale={locale} />
|
<Header locale={locale} />
|
||||||
<h1>Ombrora</h1>
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<h1>{t.hero.title}</h1>
|
||||||
|
<p>{t.hero.subtitle}</p>
|
||||||
|
</section>
|
||||||
|
<section class="services">
|
||||||
|
<h2>{t.services.heading}</h2>
|
||||||
|
<div class="services-grid">
|
||||||
|
{services.map((service) => <ServiceCard service={service} locale={locale} />)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, it, expect, beforeAll } from 'vitest';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
|
describe('hero and service cards', () => {
|
||||||
|
let en: string;
|
||||||
|
let fr: string;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
execSync('npm run build', { stdio: 'inherit' });
|
||||||
|
en = readFileSync('dist/en/index.html', 'utf-8');
|
||||||
|
fr = readFileSync('dist/fr/index.html', 'utf-8');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the localized hero copy', () => {
|
||||||
|
expect(en).toContain('The Ombrora hub');
|
||||||
|
expect(fr).toContain('Le hub des outils Ombrora');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the CVE Watch card with a locale-specific link', () => {
|
||||||
|
expect(en).toContain('href="https://cve-en.ombrora.com"');
|
||||||
|
expect(fr).toContain('href="https://cve-fr.ombrora.com"');
|
||||||
|
expect(en).toContain('CVE Watch');
|
||||||
|
expect(fr).toContain('Veille CVE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the Convert card with the same link on both locales', () => {
|
||||||
|
const enMatches = en.match(/href="https:\/\/convert\.ombrora\.com"/g) ?? [];
|
||||||
|
const frMatches = fr.match(/href="https:\/\/convert\.ombrora\.com"/g) ?? [];
|
||||||
|
expect(enMatches.length).toBeGreaterThan(0);
|
||||||
|
expect(frMatches.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user