From f210842a4a3bf32aba743e227e2584835188a9e3 Mon Sep 17 00:00:00 2001 From: Benno Tielen Date: Tue, 16 Dec 2025 12:29:38 +0100 Subject: [PATCH] feat: announcements and calendar on homepage --- src/app/(home)/page.tsx | 9 +++ .../PopupButton/PopupButton.stories.tsx | 42 +++++++++++ src/components/PopupButton/PopupButton.tsx | 72 +++++++++++++++++++ src/components/PopupButton/styles.module.scss | 63 ++++++++++++++++ src/fetch/announcement.ts | 40 ++++++++++- src/fetch/calendar.ts | 40 +++++++++++ src/pageComponents/Home/Home.stories.tsx | 36 +++++++++- src/pageComponents/Home/Home.tsx | 28 +++++++- src/pageComponents/Home/styles.module.scss | 5 +- src/utils/dto/perParish.ts | 38 ++++++++++ 10 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 src/components/PopupButton/PopupButton.stories.tsx create mode 100644 src/components/PopupButton/PopupButton.tsx create mode 100644 src/components/PopupButton/styles.module.scss create mode 100644 src/utils/dto/perParish.ts diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index c9259e5..8f43789 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -4,6 +4,9 @@ import { fetchBlogPosts } from '@/fetch/blog' import { fetchHighlights } from '@/fetch/highlights' import { Home } from '@/pageComponents/Home/Home' import moment from 'moment' +import { fetchLastAnnouncement, fetchLastAnnouncements } from '@/fetch/announcement' +import { perParish } from '@/utils/dto/perParish' +import { fetchLastCalendars } from '@/fetch/calendar' export const dynamic = 'force-dynamic' @@ -18,6 +21,10 @@ export default async function HomePage() { }); const blog = await fetchBlogPosts(true) const highlights = await fetchHighlights() + const announcements = await fetchLastAnnouncements(); + const announcementsLinks = announcements ? perParish(announcements) : []; + const calendars = await fetchLastCalendars(); + const calendarsLinks = calendars ? perParish(calendars) : []; return ( @@ -26,6 +33,8 @@ export default async function HomePage() { worship={worship?.docs || []} blog={blog?.docs || []} highlights={highlights?.docs || []} + announcements={announcementsLinks} + calendars={calendarsLinks} /> ) } diff --git a/src/components/PopupButton/PopupButton.stories.tsx b/src/components/PopupButton/PopupButton.stories.tsx new file mode 100644 index 0000000..5b189c5 --- /dev/null +++ b/src/components/PopupButton/PopupButton.stories.tsx @@ -0,0 +1,42 @@ +import { Meta, StoryObj } from '@storybook/react' +import { PopupButton } from './PopupButton' + +const meta: Meta = { + component: PopupButton, + decorators: [ + (Story) => ( +
+
+ +
+
+ ) + ] +} + +type Story = StoryObj; +export default meta + +export const Default: Story = { + args: { + text: "Vermeldungen", + title: "Vermeldungen", + links: [ + { + id: "link_1", + text: "St. Clara", + href: "https://disney.com" + }, + { + id: "link_2", + text: "St. Anna", + href: "https://disney.com" + }, + { + id: "link_3", + text: "St. Eduard", + href: "https://disney.com" + } + ] + }, +} \ No newline at end of file diff --git a/src/components/PopupButton/PopupButton.tsx b/src/components/PopupButton/PopupButton.tsx new file mode 100644 index 0000000..07f9bec --- /dev/null +++ b/src/components/PopupButton/PopupButton.tsx @@ -0,0 +1,72 @@ +"use client" + +import { useEffect, useState } from 'react' +import { Button } from '@/components/Button/Button' +import styles from "./styles.module.scss" + +type PopupButtonProps = { + text: React.ReactNode, + title: string, + links: Link[], + size?: 'lg' | 'md' + schema?: 'base' | 'shade' | 'contrast' +} + +export type Link = { + id: string, + text: string, + href: string +} + +export const PopupButton = ({size = "md", schema, text, links, title}: PopupButtonProps) => { + + const [isPopupOpen, setIsPopupOpen] = useState(false) + + useEffect(() => { + if (!isPopupOpen) return + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setIsPopupOpen(false) + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [isPopupOpen]) + + return ( +
+ +
+ +
+ + {isPopupOpen && ( +
+ +
{title}
+ {links.map(link => ( + + ))} +
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/components/PopupButton/styles.module.scss b/src/components/PopupButton/styles.module.scss new file mode 100644 index 0000000..fd1e26c --- /dev/null +++ b/src/components/PopupButton/styles.module.scss @@ -0,0 +1,63 @@ +.container { + display: inline-grid; + place-items: center; + grid-template-areas: "a"; + grid-template-rows: fit-content(0); +} + +.button { + grid-area: a; +} + +.popup { + grid-area: a; + background-color: white; + box-shadow: 0 0 21px 0 rgba(0,0,0,0.4); + border-radius: 7px; + min-width: 200px; + position: relative; + overflow: auto; +} + +.popupTitle { + font-weight: bold; + text-align: center; + padding: 10px 0; + border-bottom: 1px solid #e1e1e1; +} + +.link { + color: inherit; + text-decoration: none; + padding: 5px 15px; + display: block; + font-size: 17px; + transition: background-color 0.2s ease-in; +} + +.link:hover { + background-color: #e1e1e1; +} + +.closeButton { + position: absolute; + top: 10px; + right: 5px; + border: none; + background: white; + color: inherit; + cursor: pointer; + font-size: 16px; + font-weight: bold; + line-height: 1; + padding: 4px; + width: 30px; + height: 30px; + border-radius: 15px; +} + +.closeButton:hover, +.closeButton:focus-visible { + background-color: #f1f1f1; + outline: none; +} \ No newline at end of file diff --git a/src/fetch/announcement.ts b/src/fetch/announcement.ts index 443632b..e09cd88 100644 --- a/src/fetch/announcement.ts +++ b/src/fetch/announcement.ts @@ -30,7 +30,6 @@ export const fetchLastAnnouncement = async (parishId: string): Promise return announcements.docs[0] +} + +/** + * Fetch the last few announcements + */ +export const fetchLastAnnouncements = async (): Promise | undefined> => { + const date = new Date(); + date.setDate(date.getDate() - 14) + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setHours(23,59,59,59); + + const query: any = { + and: [ + { + date: { + greater_than_equal: date.toISOString(), + } + }, + { + date: { + less_than_equal: tomorrow.toISOString() + } + } + ] + } + + const stringifiedQuery = stringify( + { + sort: "-date", + where: query, + limit: 3, + }, + { addQueryPrefix: true }, + ) + + const response = await fetch(`http://localhost:3000/api/announcement${stringifiedQuery}`) + if (!response.ok) return undefined + return await response.json() as PaginatedDocs } \ No newline at end of file diff --git a/src/fetch/calendar.ts b/src/fetch/calendar.ts index f018e40..ed42859 100644 --- a/src/fetch/calendar.ts +++ b/src/fetch/calendar.ts @@ -46,4 +46,44 @@ export const fetchLastCalendar = async (parishId: string): Promise return announcements.docs[0] +} + +/** + * Fetch last calendars + */ +export const fetchLastCalendars = async (): Promise | undefined> => { + const date = new Date(); + date.setDate(date.getDate() - 14); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setHours(23,59,59,59); + + const query: any = { + and: [ + { + date: { + greater_than_equal: date.toISOString(), + } + }, + { + date: { + less_than_equal: tomorrow.toISOString() + } + } + ] + + } + + const stringifiedQuery = stringify( + { + sort: "-date", + where: query, + limit: 3, + }, + { addQueryPrefix: true }, + ) + + const response = await fetch(`http://localhost:3000/api/calendar${stringifiedQuery}`) + if (!response.ok) return undefined + return await response.json() as PaginatedDocs } \ No newline at end of file diff --git a/src/pageComponents/Home/Home.stories.tsx b/src/pageComponents/Home/Home.stories.tsx index 724f890..e98cc73 100644 --- a/src/pageComponents/Home/Home.stories.tsx +++ b/src/pageComponents/Home/Home.stories.tsx @@ -137,5 +137,39 @@ export const Default: Story = { }, ], highlights: [], - }, + announcements: [ + { + id: "link_1", + text: "St. Clara", + href: "https://disney.com" + }, + { + id: "link_2", + text: "St. Anna", + href: "https://disney.com" + }, + { + id: "link_3", + text: "St. Eduard", + href: "https://disney.com" + } + ], + calendars: [ + { + id: "link_1", + text: "St. Clara", + href: "https://disney.com" + }, + { + id: "link_2", + text: "St. Anna", + href: "https://disney.com" + }, + { + id: "link_3", + text: "St. Eduard", + href: "https://disney.com" + } + ], + } } \ No newline at end of file diff --git a/src/pageComponents/Home/Home.tsx b/src/pageComponents/Home/Home.tsx index d9e76fb..938efae 100644 --- a/src/pageComponents/Home/Home.tsx +++ b/src/pageComponents/Home/Home.tsx @@ -21,12 +21,15 @@ import { MoreInformation } from '@/pageComponents/Home/MoreInformation' import { Button } from '@/components/Button/Button' import styles from "./styles.module.scss" import { PublicationAndNewsletter } from '@/compositions/PublicationAndNewsletter/PublicationAndNewsletter' +import { Link, PopupButton } from '@/components/PopupButton/PopupButton' type HomeProps = { events: Event[], worship: Worship[], blog: Blog[], highlights: Highlight[], + announcements: Link[], + calendars: Link[] } const sortWorship = (worship: Worship[]) => { @@ -52,9 +55,11 @@ export const Home = ({ events, worship, blog, - highlights + highlights, + announcements, + calendars }: HomeProps) => { - const worshipPerLocation = Array.from( + const worshipPerLocation = Array.from( sortWorship(worship).entries(), ).sort( (a, b) => { @@ -111,6 +116,25 @@ export const Home = ({
+ + { announcements.length > 0 && + + } + + { calendars.length > 0 && + + } +