Skip to content

Commit

Permalink
merge events to infonaytto
Browse files Browse the repository at this point in the history
  • Loading branch information
lanttu1243 committed Oct 23, 2024
2 parents d8397b5 + b934775 commit 2ff86e4
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 1 deletion.
73 changes: 73 additions & 0 deletions apps/cms/src/hooks/revalidate-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type {
AfterChangeHook,
TypeWithID,
} from "payload/dist/collections/config/types";
import type { PayloadRequest } from "payload/types";

// revalidate the page in the background, so the user doesn't have to wait
// notice that the hook itself is not async and we are not awaiting `revalidate`
// only revalidate existing docs that are published (not drafts)
export function revalidatePage<T extends TypeWithID>(
collectionSlug: string,
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- is needed for promise
getFetchData: (doc: T, req: PayloadRequest) => Promise<unknown> | unknown,
): AfterChangeHook<T> {
return ({ doc, req, operation }): T => {
const isPage = collectionSlug === "pages";
const isPublished = "_status" in doc && doc._status === "published";
if (isPage && (operation !== "update" || !isPublished)) {
req.payload.logger.info(
`Not revalidating collection page because it's not published or not an update`,
);
return doc;
}

const revalidationKey = process.env.PAYLOAD_REVALIDATION_KEY;
if (!revalidationKey) {
req.payload.logger.error(
"PAYLOAD_REVALIDATION_KEY not set, cannot revalidate",
);
return doc;
}

const revalidate = async (): Promise<void> => {
try {
const fetchData = JSON.stringify(await getFetchData(doc, req));
const fetchUrl = `${process.env.PUBLIC_FRONTEND_URL ?? ""}/next_api/revalidate-page?${new URLSearchParams(
{
secret: encodeURIComponent(revalidationKey),
collection: encodeURIComponent(collectionSlug),
fetchData: encodeURIComponent(fetchData),
},
).toString()}`;
req.payload.logger.info(
`sending revalidate request ${fetchUrl.replace(revalidationKey, "REDACTED")}`,
);
const res = await fetch(fetchUrl, { method: "POST" });
if (res.ok) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- is ok
const thing = await res.json();
req.payload.logger.info(
`revalidate response ${JSON.stringify(thing)}`,
);
req.payload.logger.info(
`Revalidated collection ${collectionSlug} with data ${fetchData}`,
);
} else {
req.payload.logger.error(
`Error revalidating collection ${collectionSlug} with data ${fetchData}`,
);
}
} catch (err: unknown) {
req.payload.logger.error(
`Error hitting revalidate collection ${collectionSlug}`,
);
req.payload.logger.error(err);
}
};

void revalidate();

return doc;
};
}
3 changes: 3 additions & 0 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const gitSha = process.env.GIT_COMMIT_SHA ?? "dev";
/** @type {import("next").NextConfig} */
module.exports = {
reactStrictMode: true,
env: {
PUBLIC_FRONTEND_URL: process.env.PUBLIC_FRONTEND_URL,
},
images: {
// TODO: only for dev:
remotePatterns: !isProd
Expand Down
47 changes: 47 additions & 0 deletions apps/web/src/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { Button } from "@tietokilta/ui";

export const metadata = {
title: "Tietokilta - Jotain meni pieleen",
description: "Hups, jotain meni pieleen.",
};

// TODO: add i18n when next.js supports params in global-error pages https://github.com/vercel/next.js/discussions/43179

function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="fi">
<body className="font-sans">
<div className="flex min-h-screen flex-col">
<main className="relative mb-8 flex flex-col items-center gap-2 md:gap-6">
<header className="flex h-[15svh] w-full items-center justify-center bg-gray-900 text-gray-100 md:h-[25svh]">
<h1 className="font-mono text-4xl md:text-5xl">
Jotain meni pieleen
</h1>
</header>

<div className="relative m-auto flex max-w-prose flex-col gap-8 p-4 md:p-6">
<p className="shadow-solid max-w-prose rounded-md border-2 border-gray-900 p-4 md:p-6">
Oho, nyt meni jotain pahasti pieleen. Ota yhteyttä sivuston
ylläpitäjään. Virheen tunniste on{" "}
<code className="font-mono">{error.digest}</code>.
</p>
<Button onClick={reset} type="button">
Yritä uudelleen
</Button>
</div>
</main>
</div>
</body>
</html>
);
}

export default GlobalError;
5 changes: 5 additions & 0 deletions apps/web/src/app/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
220 changes: 220 additions & 0 deletions apps/web/src/app/infonaytto/(loopthrough)/TAPAHTUMAT/events-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import Link from "next/link";
import {
type EventQuota,
fetchEvents,
type IlmomasiinaEvent,
} from "../../../../lib/api/external/ilmomasiina";
import {
formatDateTime,
formatDatetimeYear,
getQuotasWithOpenAndQueue,
} from "../../../../lib/utils";

function OpenSignup({
startDate,
endDate,
className,
}: {
startDate?: string | null;
endDate?: string | null;
className?: string;
}) {
if (!startDate || !endDate) {
return <span className={className}>Tapahtumaan ei voi ilmoittautua</span>;
}
const hasStarted = new Date(startDate) < new Date();
const hasEnded = new Date(endDate) < new Date();

if (hasStarted && hasEnded) {
return <span className={className}>Ilmoittautuminen on päättynyt</span>;
}

if (hasStarted && !hasEnded) {
return (
<span className={className}>
Ilmo auki {formatDatetimeYear(endDate, "fi")} asti.
</span>
);
}

return <span> Ilmo aukeaa {formatDatetimeYear(startDate, "fi")}. </span>;
}

function SignupQuotas({
showQuotas,
quotas,
openQuotaSize,
className,
}: {
showQuotas: boolean;
quotas: EventQuota[];
openQuotaSize: number;
className?: string;
}) {
const isSingleQuota =
(quotas.length === 1 && !openQuotaSize) ||
(quotas.length === 0 && openQuotaSize);

function renderNoSignup() {
return <span>Tapahtumaan ei voi ilmoittautua.</span>;
}

function renderSignup() {
return (
<span>
Ilmoittautuneita: {isSingleQuota ? renderSingle() : renderMultiple()}
</span>
);
}

function renderSingle() {
return (
<div className="flex w-full justify-between">
<span>
{quotas[0].title.length - 3 > 8
? `${quotas[0].title.slice(0, 8)}...`
: quotas[0].title}
</span>
<span>
{quotas[0].signupCount} / {quotas[0].size}
</span>
</div>
);
}

function renderMultiple() {
const allQuotas = getQuotasWithOpenAndQueue(quotas, openQuotaSize);
return (
<ul>
<li className="flex flex-col justify-end">
{allQuotas.map((quota) => (
<div className="flex w-full justify-between" key={quota.id}>
<span>
{quota.title.length - 3 > 8
? `${quota.title.slice(0, 8)}...`
: quota.title}
</span>
<span>
{quota.signupCount} / {quota.size}
</span>
</div>
))}
</li>
</ul>
);
}

return (
<div className={className}>
{showQuotas ? renderSignup() : renderNoSignup()}
</div>
);
}

function EventCard({ event }: { event: IlmomasiinaEvent }) {
let showSignupQuotas = true;
const signupStartDate = event.registrationStartDate;
const signupEndDate = event.registrationEndDate;
const eventDate = event.date ? new Date(event.date) : new Date();

if (event.registrationClosed === true || !signupEndDate || !signupStartDate) {
showSignupQuotas = false;
}

return (
//eslint-disable-next-line tailwindcss/no-custom-classname -- custom classnames is needed
<li className="shadow-solid relative flex max-w-3xl flex-col gap-2 rounded-md border-2 border-gray-900 bg-gray-100 p-4">
<div className="flex flex-row justify-between">
<div className={`flex grow ${showSignupQuotas ? "flex-col" : ""}`}>
<Link
href={`/fi/tapahtumat/${event.slug}`}
className="text-pretty text-lg font-bold group-hover:underline"
>
<h2>{`${event.title}, ${formatDateTime(
eventDate.toISOString(),
).slice(0, eventDate.toISOString.length - 5)}`}</h2>
</Link>

{showSignupQuotas ? (
<OpenSignup endDate={signupEndDate} startDate={signupStartDate} />
) : null}
</div>
<SignupQuotas
showQuotas={showSignupQuotas}
quotas={event.quotas}
openQuotaSize={event.openQuotaSize}
className="ml-5 w-1/3 shrink-0"
/>
</div>
</li>
);
}

function getWeek(date: Date) {
// First day of the year
const firstMondayOfYear = new Date(date.getFullYear(), 0, 1);
// Monday is 0, Sunday is 6
const weekday = (firstMondayOfYear.getDay() + 6) % 7;
// Find first monday of the year
if (weekday < 3) {
// If the first day of the year is a Monday, Tuesday or Wednesday
firstMondayOfYear.setDate(firstMondayOfYear.getDate() - weekday);
} else {
// If the first day of the year is a Thursday, Friday, Saturday or Sunday
firstMondayOfYear.setDate(firstMondayOfYear.getDate() + 7 - weekday);
}

// Calculate how many weeks have passed
const diff = date.getTime() - firstMondayOfYear.getTime();
const days = diff / (1000 * 60 * 60 * 24 * 7);
const weeknumber = Math.ceil(days);
return weeknumber;
}

function groupEventsByWeek(
events: IlmomasiinaEvent[],
): Record<number, IlmomasiinaEvent[]> {
return events.reduce<Record<number, IlmomasiinaEvent[]>>((acc, event) => {
const eventDate = event.date ? new Date(event.date) : new Date();
const weekNumber = getWeek(eventDate);
if (!acc[weekNumber]) {
acc[weekNumber] = [];
}
acc[weekNumber].push(event);
return acc;
}, {});
}

export default async function Page() {
const events = await fetchEvents();
const upcomingEvents = [...(events.data ?? [])];

const upcomingEventsDataByWeek = groupEventsByWeek(upcomingEvents);

return (
<main id="main" className="flex flex-col align-top">
<h1 className="my-6 text-center font-mono text-5xl font-bold">
Tapahtumat
</h1>
<ul className="flex flex-row flex-wrap">
{Object.entries(upcomingEventsDataByWeek)
.slice(0, 3)
.map((entry) => {
const eventsInWeek = entry[1];
return (
<div key={entry[0]} className="flex w-1/2 flex-col p-2 xl:w-1/3">
<span className="text-pretty py-2 text-center text-3xl font-bold">
Week {entry[0]}
</span>
<div className="flex flex-col gap-3">
{eventsInWeek.map((event) => {
return <EventCard event={event} key={event.id} />;
})}
</div>
</div>
);
})}
</ul>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Eventslist from "./events-list";

export function InfoScreen() {
return <div />;
return <Eventslist />;
}

export default InfoScreen;
Loading

0 comments on commit 2ff86e4

Please sign in to comment.