mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 15:17:43 -05:00
Banner on calendar page to notify about reporting
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -17,7 +17,7 @@ Calendar
|
||||
- [x] Add winners page
|
||||
- [ ] E2E test add winners page
|
||||
- [x] Winners on the event info page
|
||||
- [ ] There should be a banner on the list page if you have past tournaments to report
|
||||
- [x] There should be a banner on the list page if you have past tournaments to report
|
||||
- [x] On the user page tab showing past results
|
||||
- [ ] Translations
|
||||
|
||||
|
||||
16
app/components/Alert.tsx
Normal file
16
app/components/Alert.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import type * as React from "react";
|
||||
import { AlertIcon } from "./icons/Alert";
|
||||
|
||||
export function Alert({
|
||||
children,
|
||||
textClassName,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
textClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="alert">
|
||||
<AlertIcon /> <div className={textClassName}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -502,3 +502,36 @@ export function findBadgesById(eventId: CalendarEvent["id"]) {
|
||||
Pick<Badge, "id" | "code" | "hue" | "displayName">
|
||||
>;
|
||||
}
|
||||
|
||||
const eventsToReportStm = sql.prepare(`
|
||||
select
|
||||
"CalendarEvent"."id",
|
||||
"CalendarEvent"."name",
|
||||
(select max("startTime")
|
||||
from "CalendarEventDate"
|
||||
where "eventId" = "CalendarEvent"."id") as "startTime"
|
||||
from "CalendarEvent"
|
||||
where
|
||||
"CalendarEvent"."authorId" = $authorId
|
||||
and
|
||||
"startTime" > $lowerLimitTime
|
||||
and
|
||||
"startTime" < $upperLimitTime
|
||||
and
|
||||
"CalendarEvent"."participantCount" is null
|
||||
`);
|
||||
|
||||
export function eventsToReport(authorId?: CalendarEvent["authorId"]) {
|
||||
if (!authorId) return [];
|
||||
|
||||
const oneMonthAgo = new Date();
|
||||
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
||||
|
||||
return (
|
||||
eventsToReportStm.all({
|
||||
authorId,
|
||||
upperLimitTime: dateToDatabaseTimestamp(new Date()),
|
||||
lowerLimitTime: dateToDatabaseTimestamp(oneMonthAgo),
|
||||
}) as Array<Pick<CalendarEvent, "id" | "name">>
|
||||
).map((row) => ({ id: row.id, name: row.name }));
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import React from "react";
|
||||
import { Flipped, Flipper } from "react-flip-toolkit";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { z } from "zod";
|
||||
import { Alert } from "~/components/Alert";
|
||||
import { LinkButton } from "~/components/Button";
|
||||
import { db } from "~/db";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { useUser } from "~/modules/auth";
|
||||
import { getUser, useUser } from "~/modules/auth";
|
||||
import { i18next } from "~/modules/i18n";
|
||||
import styles from "~/styles/calendar.css";
|
||||
import { joinListToNaturalString } from "~/utils/arrays";
|
||||
@@ -22,7 +23,7 @@ import {
|
||||
} from "~/utils/dates";
|
||||
import { discordFullName, makeTitle } from "~/utils/strings";
|
||||
import type { Unpacked } from "~/utils/types";
|
||||
import { resolveBaseUrl } from "~/utils/urls";
|
||||
import { calendarReportWinnersPage, resolveBaseUrl } from "~/utils/urls";
|
||||
import { actualNumber } from "~/utils/zod";
|
||||
import { Tags } from "./components/Tags";
|
||||
|
||||
@@ -55,6 +56,7 @@ const loaderSearchParamsSchema = z.object({
|
||||
});
|
||||
|
||||
export const loader = async ({ request }: LoaderArgs) => {
|
||||
const user = await getUser(request);
|
||||
const t = await i18next.getFixedT(request);
|
||||
const url = new URL(request.url);
|
||||
const parsedParams = loaderSearchParamsSchema.safeParse({
|
||||
@@ -87,6 +89,7 @@ export const loader = async ({ request }: LoaderArgs) => {
|
||||
}),
|
||||
weeks: closeByWeeks({ week: displayedWeek, year: displayedYear }),
|
||||
events: fetchEventsOfWeek({ week: displayedWeek, year: displayedYear }),
|
||||
eventsToReport: db.calendarEvents.eventsToReport(user?.id),
|
||||
title: makeTitle([`Week ${displayedWeek}`, t("pages.calendar")]),
|
||||
});
|
||||
};
|
||||
@@ -143,6 +146,7 @@ export default function CalendarPage() {
|
||||
return (
|
||||
<main className="stack lg">
|
||||
<WeekLinks />
|
||||
<EventsToReport />
|
||||
<div className="stack md">
|
||||
{user && (
|
||||
<LinkButton to="new" className="calendar__add-new-button" tiny>
|
||||
@@ -287,6 +291,28 @@ function getEventsCountPerWeek(
|
||||
return result;
|
||||
}
|
||||
|
||||
function EventsToReport() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
if (data.eventsToReport.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Alert textClassName="calendar__events-to-report">
|
||||
You can report results for{" "}
|
||||
{data.eventsToReport.map((event, i) => (
|
||||
<React.Fragment key={event.id}>
|
||||
<Link to={calendarReportWinnersPage(event.id)}>{event.name}</Link>
|
||||
{i === data.eventsToReport.length - 1
|
||||
? ""
|
||||
: i === data.eventsToReport.length - 2
|
||||
? " and "
|
||||
: ", "}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
function EventsList({
|
||||
events,
|
||||
}: {
|
||||
|
||||
@@ -85,6 +85,10 @@
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
.calendar__events-to-report {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.calendar__events-container {
|
||||
background-color: var(--bg-lighter);
|
||||
}
|
||||
|
||||
@@ -480,12 +480,35 @@ dialog::backdrop {
|
||||
background-color: var(--theme-transparent);
|
||||
}
|
||||
|
||||
.alert {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--theme-info-transparent);
|
||||
border-radius: var(--rounded);
|
||||
color: var(--text);
|
||||
font-size: var(--fonts-sm);
|
||||
font-weight: var(--semi-bold);
|
||||
gap: var(--s-2);
|
||||
line-height: 0.75;
|
||||
margin-inline: auto;
|
||||
padding-block: var(--s-1-5);
|
||||
padding-inline-end: var(--s-4);
|
||||
padding-inline-start: var(--s-3);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
background-color: var(--bg-lighter);
|
||||
background-image: url("/svg/background-pattern.svg");
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.alert > svg {
|
||||
height: 1.75rem;
|
||||
fill: var(--theme-info);
|
||||
}
|
||||
|
||||
.form-errors {
|
||||
font-size: var(--fonts-sm);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
--theme-warning: #c9c900;
|
||||
--theme-success: #00a514;
|
||||
--theme-success-transparent: #00a51452;
|
||||
--theme-info: #1fb0d0;
|
||||
--theme-info-transparent: #1fb0d052;
|
||||
--theme: hsl(255deg 64% 63%);
|
||||
--theme-vibrant: hsl(255deg 100% 81%);
|
||||
--theme-transparent: hsl(255deg 66.7% 75% / 40%);
|
||||
@@ -90,6 +92,8 @@
|
||||
--theme-warning: #f5f587;
|
||||
--theme-success: #a3ffae;
|
||||
--theme-success-transparent: #a3ffae52;
|
||||
--theme-info: #87cddc;
|
||||
--theme-info-transparent: #87cddc52;
|
||||
--theme: hsl(255deg 66.7% 75%);
|
||||
--theme-vibrant: hsl(255deg 78% 65%);
|
||||
--theme-transparent: hsl(255deg 66.7% 75% / 40%);
|
||||
|
||||
Reference in New Issue
Block a user