-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
442 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
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
220
apps/web/src/app/infonaytto/(loopthrough)/TAPAHTUMAT/events-list.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.