Merge remote-tracking branch 'origin/feat/home-banners-real-data' into fix/home-banner-dismiss-cookie

This commit is contained in:
Pep Villalonga
2026-09-02 13:36:06 +02:00
5 changed files with 66 additions and 24 deletions
@@ -2,7 +2,7 @@
import { actions } from "astro:actions";
import { locale, t } from "../../../../i18n/store";
import { formatDate } from "../../../../utils/dates";
import { endOfDay, formatDate, startOfDay } from "../../../../utils/dates";
import ActionableButton from "../../../library/buttons/ActionableButton.svelte";
import DateInput from "../../../library/inputs/DateInput.svelte";
import TextArea from "../../../library/inputs/TextArea.svelte";
@@ -23,9 +23,20 @@
let fieldErrors: FieldErrors = $state({});
function handleDateSelect(date: Date) {
function normalizeDate(field: "startsAt" | "endsAt", date: Date) {
const now = new Date();
date.setHours(now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds());
const isToday =
date.getFullYear() === now.getFullYear() &&
date.getMonth() === now.getMonth() &&
date.getDate() === now.getDate();
// Ends always run to the end of their day. Start dates run from their
// midnight, except when the day is today the current time is used so it
// passes the "no earlier than now" validation. DateInput binds the same
// object, so the normalized time is applied in place.
const normalized = field === "endsAt" ? endOfDay(date) : isToday ? now : startOfDay(date);
date.setTime(normalized.getTime());
}
async function submit() {
@@ -110,7 +121,7 @@
placeholder={$t("pages.admin.comm.banners.fields.startDatePlaceholder")}
error={fieldErrors.startsAt &&
$t(fieldErrors.startsAt, { date: formatDate(new Date(), $locale) })}
onInput={handleDateSelect}
onInput={(date) => normalizeDate("startsAt", date)}
/>
<DateInput
@@ -119,7 +130,7 @@
placeholder={$t("pages.admin.comm.banners.fields.endDatePlaceholder")}
error={fieldErrors.endsAt &&
$t(fieldErrors.endsAt, { date: formatDate(new Date(), $locale) })}
onInput={handleDateSelect}
onInput={(date) => normalizeDate("endsAt", date)}
/>
</div>
</div>
+26
View File
@@ -0,0 +1,26 @@
<script lang="ts">
import HomeBanner from "./HomeBanner.svelte";
import { t } from "../../i18n/store";
import type { BannerRecord } from "../../repositories/banners";
interface Props {
banners: BannerRecord[];
}
let { banners }: Props = $props();
</script>
{#if banners.length > 0}
<section class="wrapper flex flex-col gap-4">
{#each banners as banner}
<HomeBanner
title={banner.title}
description={banner.content}
ctaText={banner.ctaText}
ctaLink={banner.ctaLink}
closeAriaLabel={$t("pages.home.banner.closeAriaLabel")}
/>
{/each}
</section>
{/if}
+1 -1
View File
@@ -27,7 +27,7 @@
"expectedUrl": "No es una URL"
},
"date": {
"greaterThan": "La fecha debe ser igual o mayor que el {{ date }}"
"greaterThan": "La fecha debe ser mayor que el {{ date }}"
},
"file": {
"maxAllowedSize": "Tamaño máximo permitido {{ size }}",
+2 -18
View File
@@ -8,7 +8,7 @@ import Stats from "../../components/home/Stats.astro";
import FeaturedPromoters from "../../components/home/FeaturedPromoters.astro";
import Blog from "../../components/home/Blog.astro";
import Matchfunding from "../../components/home/Matchfunding.astro";
import HomeBanner from "../../components/home/HomeBanner.svelte";
import Banners from "../../components/home/HomeBanners.svelte";
// Disabled sections, re-enable the import along with the component below:
// import Stories from "../../components/home/Stories.svelte";
// import Resources from "../../components/home/Resources.astro";
@@ -35,23 +35,7 @@ const activeBanners = (await bannerRepository.getActive()).filter(
<Layout title={t("pages.index.title")} description={t("pages.metadata.description")}>
<App client:load locale={lang} class="mb-20 gap-18">
<Hero />
{
activeBanners.length > 0 && (
<div class="wrapper flex flex-col gap-4">
{activeBanners.map((banner) => (
<HomeBanner
id={banner.id}
title={banner.title}
description={banner.content}
ctaText={banner.ctaText}
ctaLink={banner.ctaLink}
closeAriaLabel={t("pages.home.banner.closeAriaLabel")}
client:load
/>
))}
</div>
)
}
<Banners banners={activeBanners} client:load />
<Campaigns />
<Blog />
<Stats />
+21
View File
@@ -10,6 +10,27 @@ export function formatDate(date: Date, locale: Locale) {
return date.toLocaleDateString(locale, options);
}
/**
* The first instant of the day for the given date (midnight, 00:00).
* @param date The date to normalize
* @returns A new Date set to the start of the given day
*/
export function startOfDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
/**
* The last instant of the day for the given date (23:59:59.999).
* Derived from the start of the following day rather than hardcoding the end-of-day time.
* @param date The date to normalize
* @returns A new Date set to the end of the given day
*/
export function endOfDay(date: Date): Date {
// Start of the next day, minus a single millisecond
const nextDay = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
return new Date(nextDay.getTime() - 1);
}
/**
* Get a date `n` ms from now
* @param offset Milliseconds since now until future date