Merge remote-tracking branch 'origin/feat/home-banners-real-data' into fix/home-banner-dismiss-cookie
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
@@ -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 }}",
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user