Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions public/locales/de/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
"iframeTitles": {
"howToVolunteer": "Wie Du Geflüchtete in Deiner Nachbarschaft unterstützen können"
},
"events": {
"headline": "Kommende Veranstaltungen",
"button": "Mehr erfahren & anmelden",
"loading": "Kommende Veranstaltungen werden geladen…",
"empty": "Aktuell ist keine kommende Veranstaltung verfügbar."
},
"testimonials": {
"header": "ERFAHRUNGSBERICHTE",
"headerParagraph": "Was Ehrenamtliche und Unterkünfte sagen"
Expand Down
6 changes: 6 additions & 0 deletions public/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
"iframeTitles": {
"howToVolunteer": "How you can support refugees in your community"
},
"events": {
"headline": "Upcoming Events",
"button": "Learn More & Register",
"loading": "Loading upcoming events…",
"empty": "No upcoming event is currently available."
},
"testimonials": {
"header": "TESTIMONIALS",
"headerParagraph": "What Volunteers & Coordinators Say"
Expand Down
166 changes: 165 additions & 1 deletion src/components/EventsSection/EventsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,167 @@
"use client";

import { Button } from "@/components/core/button";
import { ImageWithGradient } from "@/components/core/image";
import { FullWidthContainer, OverlayingSectionContainer } from "@/components/styled/container";
import { CustomHeading, Heading1, Heading4 } from "@/components/styled/text";
import { eventsPublicLandingUrl, ScreenTypes } from "@/config/constants";
import { useScreenType } from "@/context/DeviceContext";
import { useEvents } from "@/hooks/useEvents";
import { getImageUrl } from "@/utils/helpers";
import type { ApiEventN4DGetList } from "need4deed-sdk";
import { EventN4DType } from "need4deed-sdk";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";

const EventsSectionContainer = styled(OverlayingSectionContainer)`
height: var(--homepage-events-section-container-height);
justify-content: center;
align-items: center;
gap: var(--homepage-events-section-content-container-gap);
`;

const ContentContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: var(--homepage-events-section-content-container-gap);
`;

const EventContainer = styled.div`
display: flex;
flex-direction: var(--homepage-events-section-event-container-flex-direction);
gap: var(--homepage-events-section-event-container-gap);
`;

const EventCard = styled.div`
display: flex;
flex-direction: column;
width: var(--homepage-events-section-event-card-width, 100%);
gap: var(--homepage-events-section-event-card-gap);
`;

const EventHeadline = styled.div`
display: flex;
flex-flow: row wrap;
align-items: center;
gap: var(--homepage-events-section-event-card-headline-gap);
`;

const EventTitleTag = styled.div`
padding: var(--homepage-events-section-event-card-event-title-tag-padding);
border-radius: var(--homepage-events-section-event-card-event-title-tag-border-radius);
background-color: var(--color-orchid);
`;

const ButtonContainer = styled.div`
display: flex;
align-items: flex-end;
justify-content: var(--homepage-events-section-button-container-justify-content);
`;

const imageNamesMap: Record<EventN4DType, Record<ScreenTypes, string>> = {
[EventN4DType.PARTY]: {
mobile: "events1-bg-mobile.webp",
tablet: "events1-bg-tablet.webp",
desktop: "events1-bg-desktop.webp",
},
[EventN4DType.WORKSHOP]: {
mobile: "events2-bg-mobile.webp",
tablet: "events2-bg-tablet.webp",
desktop: "events2-bg-desktop.webp",
},
};

function getUpcomingEvent(events?: ApiEventN4DGetList[]) {
const today = new Date();
today.setHours(0, 0, 0, 0);

return events
?.filter((event) => event.active && new Date(event.dateEnd ?? event.date) >= today)
.sort((first, second) => new Date(first.date).getTime() - new Date(second.date).getTime())[0];
}

function formatEventDate(event: ApiEventN4DGetList, locale: string) {
const format = new Intl.DateTimeFormat(locale, { day: "numeric", month: "long", year: "numeric" });
const start = format.format(new Date(event.date));
if (!event.dateEnd) return start;

const end = format.format(new Date(event.dateEnd));
return start === end ? start : `${start} – ${end}`;
}

export function EventsSection() {
return <div style={{ padding: "50px", border: "1px dashed red" }}>Events Section (Coming in #137)</div>;
const { t, i18n } = useTranslation();
const screenType = useScreenType();
const { data: events, isLoading } = useEvents();
const upcomingEvent = useMemo(() => getUpcomingEvent(events), [events]);
const eventType = upcomingEvent?.type ?? EventN4DType.PARTY;
const imageUrl = getImageUrl(imageNamesMap[eventType][screenType]);

return (
<FullWidthContainer id="Events-Section-FWContainer">
<ImageWithGradient
imageUrl={imageUrl}
gradientClass="image-filter-gradient-blue"
height="var(--homepage-events-section-container-height)"
/>

<EventsSectionContainer id="events-section-container">
<ContentContainer>
<Heading1 color="var(--color-white)" margin={0}>
{t("homepage.events.headline")}
</Heading1>

<EventContainer>
<EventCard aria-live="polite">
{isLoading ? (
<Heading4 color="var(--color-white)" margin={0}>
{t("homepage.events.loading")}
</Heading4>
) : upcomingEvent ? (
<>
<EventHeadline>
<EventTitleTag>
<CustomHeading
as="h2"
fontWeight={700}
fontSize="24px"
lineheight="24px"
color="var(--color-midnight)"
margin={0}
>
{upcomingEvent.title}
</CustomHeading>
</EventTitleTag>
<Heading4 color="var(--color-white)" margin={0}>
{formatEventDate(upcomingEvent, i18n.language)}
</Heading4>
</EventHeadline>
<Heading4 color="var(--color-white)" margin={0}>
{upcomingEvent.shortDescription.replace(/\\n/g, "\n")}
</Heading4>
</>
) : (
<Heading4 color="var(--color-white)" margin={0}>
{t("homepage.events.empty")}
</Heading4>
)}
</EventCard>

{upcomingEvent && (
<ButtonContainer>
<Button
text={t("homepage.events.button")}
onClick={() => window.location.assign(eventsPublicLandingUrl)}
/>
</ButtonContainer>
)}
</EventContainer>
</ContentContainer>
</EventsSectionContainer>
</FullWidthContainer>
);
}

export default EventsSection;
2 changes: 1 addition & 1 deletion src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const eventsSectionContainerId = "events-section-container";
export const eventsPublicLandingUrl = "https://www.need4deed.org/event-page";
export const opportunityCardsPublicUrl = "https://www.need4deed.org/opportunity-cards";

export const cloudfrontURL = process.env.NEXT_PUBLIC_CLOUDFRONT_URL;
export const cloudfrontURL = process.env.NEXT_PUBLIC_CLOUDFRONT_URL ?? "https://d2nwrdddg8skub.cloudfront.net/images";

export const minPLZGermany = 1067;
export const maxPLZGermany = 99998;
Expand Down
Loading