Skip to content
Draft
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
100 changes: 100 additions & 0 deletions apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import { Button, type ButtonProps } from "@tietokilta/ui";
import React, { useEffect, useState } from "react";
import {
I18nProviderClient,
useCurrentLocale,
useScopedI18n,
} from "@/locales/client";

export function AutoEnableButton({
startDate,
endDate,
...props
}: React.PropsWithChildren<
Omit<ButtonProps, "disabled"> & { startDate: string; endDate: string }
>) {
const [isDisabled, setIsDisabled] = useState(false);

useEffect(() => {
const tick = () => {
console.log("button tick", props.children);

Check warning on line 22 in apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Check types & Build

Unexpected console statement
const hasStarted = new Date(startDate) < new Date();
const hasEnded = new Date(endDate) < new Date();
setIsDisabled(!hasStarted || hasEnded);
};

if (new Date(startDate) < new Date()) {
setIsDisabled(false);
return;
}

tick();
const interval = setInterval(tick, 500);

return () => {
clearInterval(interval);
};
}, [startDate, endDate, props.children]);

return <Button disabled={isDisabled} {...props} />;
}

const oneMinuteMs = 1000 * 60;

function CountdownUnwrapped({ startDate }: { startDate: string }) {
const t = useScopedI18n("ilmomasiina.countdown");

Check warning on line 47 in apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Check types & Build

Unsafe assignment of an error typed value

Check warning on line 47 in apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Check types & Build

Unsafe call of an `error` type typed value
const [timeToStart, setTimeToStart] = useState<number | null>(null);

useEffect(() => {
const tick = () => {
console.log("countdown tick");

Check warning on line 52 in apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Check types & Build

Unexpected console statement
const hasStarted = new Date(startDate) < new Date();
if (hasStarted) {
setTimeToStart(null);
return;
}

const newTimeToStart =
new Date(startDate).getTime() - new Date().getTime();

setTimeToStart(newTimeToStart);
};

if (new Date(startDate) < new Date()) {
return;
}

tick();
const interval = setInterval(tick, 500);

return () => {
clearInterval(interval);
};
}, [startDate]);

if (!timeToStart || timeToStart < 0 || timeToStart > oneMinuteMs) {
return null;
}

return (
<div>
{t("Starting in {seconds} seconds", {

Check warning on line 83 in apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Check types & Build

Unsafe call of an `error` type typed value
seconds: Math.floor(timeToStart / 1000),
})}
</div>
);
}

export function Countdown(
props: React.ComponentProps<typeof CountdownUnwrapped>,
) {
const locale = useCurrentLocale();

Check warning on line 93 in apps/web/src/app/[locale]/(main)/events/[slug]/client-components.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Check types & Build

Unsafe assignment of an error typed value

return (
<I18nProviderClient locale={locale}>
<CountdownUnwrapped {...props} />
</I18nProviderClient>
);
}
47 changes: 29 additions & 18 deletions apps/web/src/app/[locale]/(main)/events/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Card, Progress } from "@tietokilta/ui";
import { type Metadata } from "next";
import { Suspense } from "react";
import {
type IlmomasiinaEvent,
fetchEvent,
Expand All @@ -17,7 +18,6 @@ import {
} from "@lib/api/external/ilmomasiina";
import { signUp } from "@lib/api/external/ilmomasiina/actions";
import {
cn,
formatDateTimeSeconds,
formatDateTimeSecondsOptions,
formatDatetimeYear,
Expand All @@ -30,7 +30,7 @@ import { getCurrentLocale, getScopedI18n } from "@locales/server";
import { DateTime } from "@components/datetime";
import { remarkI18n } from "@lib/plugins/remark-i18n";
import { openGraphImage } from "../../../../shared-metadata";
import { SignUpButton } from "./signup-button";
import { AutoEnableButton, Countdown } from "./client-components";

async function SignUpText({
startDate,
Expand Down Expand Up @@ -82,28 +82,28 @@ async function SignupButtons({ event }: { event: IlmomasiinaEvent }) {
return null;
}

const t = await getScopedI18n("action");
const startDate = event.registrationStartDate;
const endDate = event.registrationEndDate;

const hasStarted = new Date(event.registrationStartDate) < new Date();
const hasEnded = new Date(event.registrationEndDate) < new Date();
const t = await getScopedI18n("action");

return (
<ul className="flex flex-col gap-2">
{event.quotas.map((quota) => (
<li key={quota.id} className="contents">
<SignUpButton
quotaId={quota.id}
isDisabled={!hasStarted || hasEnded}
signUpAction={signUp}
>
<span>
<span className={cn(event.quotas.length > 1 && "sr-only")}>
{t("Sign up")}
{event.quotas.length === 1 ? "" : `: `}
</span>
{event.quotas.length > 1 ? <span>{quota.title}</span> : null}
</span>
</SignUpButton>
{/* eslint-disable-next-line @typescript-eslint/no-misused-promises -- server actions can be ignored promises */}
<form action={signUp} className="contents">
<input type="hidden" name="quotaId" value={quota.id} />
<AutoEnableButton
type="submit"
variant="secondary"
startDate={startDate}
endDate={endDate}
>
{t("Sign up")}
{event.quotas.length === 1 ? null : `: ${quota.title}`}
</AutoEnableButton>
</form>
</li>
))}
</ul>
Expand Down Expand Up @@ -391,6 +391,14 @@ async function SignUpQuotas({ event }: { event: IlmomasiinaEvent }) {
);
}

export function LiveCountdown(props: React.ComponentProps<typeof Countdown>) {
return (
<Suspense fallback={null}>
<Countdown {...props} />
</Suspense>
);
}

async function SignUpActions({ event }: { event: IlmomasiinaEvent }) {
const t = await getScopedI18n("ilmomasiina");
return (
Expand All @@ -404,6 +412,9 @@ async function SignUpActions({ event }: { event: IlmomasiinaEvent }) {
endDate={event.registrationEndDate}
/>
<SignupButtons event={event} />
{event.registrationStartDate ? (
<LiveCountdown startDate={event.registrationStartDate} />
) : null}
</div>
);
}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ const en = {
"ilmomasiina.path.events": "events",
"ilmomasiina.path.all-events": "all-events",
"ilmomasiina.all-events.Kaikki tapahtumat": "All events",
"ilmomasiina.countdown.Starting in {seconds} seconds":
"Starting in {seconds} seconds",
"ilmomasiina.status.Ei ilmoittautuneita vielä": "No sign ups yet.",
"ilmomasiina.status.Ilmoittautuminen alkaa": "Sign ups start on {startDate}",
"ilmomasiina.status.Ilmo alkaa": "Sign ups start on {startDate}",
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/locales/fi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ const fi = {
"ilmomasiina.path.events": "tapahtumat",
"ilmomasiina.path.all-events": "kaikki-tapahtumat",
"ilmomasiina.all-events.Kaikki tapahtumat": "Kaikki tapahtumat",
"ilmomasiina.countdown.Starting in {seconds} seconds":
"Alkaa {seconds} sekunnin kuluttua",
"ilmomasiina.status.Ei ilmoittautuneita vielä": "Ei ilmoittautuneita vielä.",
"ilmomasiina.status.Ilmoittautuminen alkaa":
"Ilmoittautuminen alkaa {startDate}",
Expand Down
Loading