mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-14 15:00:54 -05:00
* Got something going * Style overwrites * width != height * More playing with lines * Migrations * Start bracket initial * Unhardcode stage generation params * Link to match page * Matches page initial * Support directly adding seed to map list generator * Add docs * Maps in matches page * Add invariant about tie breaker map pool * Fix PICNIC lacking tie breaker maps * Only link in bracket when tournament has started * Styled tournament roster inputs * Prefer IGN in tournament match page * ModeProgressIndicator * Some conditional rendering * Match action initial + better error display * Persist bestOf in DB * Resolve best of ahead of time * Move brackets-manager to core * Score reporting works * Clear winner on score report * ModeProgressIndicator: highlight winners * Fix inconsistent input * Better text when submitting match * mapCountPlayedInSetWithCertainty that works * UNDO_REPORT_SCORE implemented * Permission check when starting tournament * Remove IGN from upsert * View match results page * Source in DB * Match page waiting for teams * Move tournament bracket to feature folder * REOPEN_MATCH initial * Handle proper resetting of match * Inline bracket-manager * Syncify * Transactions * Handle match is locked gracefully * Match page auto refresh * Fix match refresh called "globally" * Bracket autoupdate * Move fillWithNullTillPowerOfTwo to utils with testing * Fix map lists not visible after tournament started * Optimize match events * Show UI while in progress to members * Fix start tournament alert not being responsive * Teams can check in * Fix map list 400 * xxx -> TODO * Seeds page * Remove map icons for team page * Don't display link to seeds after tournament has started * Admin actions initial * Change captain admin action * Make all hooks ts * Admin actions functioning * Fix validate error not displaying in CatchBoundary * Adjust validate args order * Remove admin loader * Make delete team button menancing * Only include checked in teams to bracket * Optimize to.id route loads * Working show map list generator toggle * Update full tournaments flow * Make full tournaments work with many start times * Handle undefined in crud * Dynamic stage banner * Handle default strat if map list generation fails * Fix crash on brackets if less than 2 teams * Add commented out test for reference * Add TODO * Add players from team during register * TrustRelationship * Prefers not to host feature * Last before merge * Rename some vars * More renames
116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
import { json } from "@remix-run/node";
|
||
import type { LoaderFunction } from "@remix-run/node";
|
||
import { Outlet, useLoaderData, useMatches, useParams } from "@remix-run/react";
|
||
import clsx from "clsx";
|
||
import { Badge } from "~/components/Badge";
|
||
import { LinkButton } from "~/components/Button";
|
||
import { Redirect } from "~/components/Redirect";
|
||
import { db } from "~/db";
|
||
import {
|
||
type ManagersByBadgeId,
|
||
type OwnersByBadgeId,
|
||
} from "~/db/models/badges/queries.server";
|
||
import { type Badge as BadgeDBType } from "~/db/types";
|
||
import { useUser } from "~/modules/auth";
|
||
import { canEditBadgeOwners } from "~/permissions";
|
||
import { discordFullName } from "~/utils/strings";
|
||
import { BADGES_PAGE } from "~/utils/urls";
|
||
import { type BadgesLoaderData } from "../badges";
|
||
import { type TFunction } from "react-i18next";
|
||
import { useTranslation } from "~/hooks/useTranslation";
|
||
|
||
export interface BadgeDetailsContext {
|
||
badgeName: string;
|
||
}
|
||
|
||
export interface BadgeDetailsLoaderData {
|
||
owners: OwnersByBadgeId;
|
||
managers: ManagersByBadgeId;
|
||
}
|
||
|
||
export const loader: LoaderFunction = ({ params }) => {
|
||
const badgeId = Number(params["id"]);
|
||
if (Number.isNaN(badgeId)) {
|
||
throw new Response(null, { status: 404 });
|
||
}
|
||
|
||
return json<BadgeDetailsLoaderData>({
|
||
owners: db.badges.ownersByBadgeId(badgeId),
|
||
managers: db.badges.managersByBadgeId(badgeId),
|
||
});
|
||
};
|
||
|
||
export default function BadgeDetailsPage() {
|
||
const user = useUser();
|
||
const [, parentRoute] = useMatches();
|
||
const { badges } = parentRoute.data as BadgesLoaderData;
|
||
const params = useParams();
|
||
const data = useLoaderData<BadgeDetailsLoaderData>();
|
||
const { t } = useTranslation("badges");
|
||
|
||
const badge = badges.find((b) => b.id === Number(params["id"]));
|
||
if (!badge) return <Redirect to={BADGES_PAGE} />;
|
||
|
||
const context: BadgeDetailsContext = { badgeName: badge.displayName };
|
||
|
||
return (
|
||
<div className="stack md items-center">
|
||
<Outlet context={context} />
|
||
<Badge badge={badge} isAnimated size={200} />
|
||
<div>
|
||
<div className="badges__explanation">
|
||
{badgeExplanationText(t, badge)}
|
||
</div>
|
||
<div
|
||
className={clsx("badges__managers", {
|
||
invisible: data.managers.length === 0,
|
||
})}
|
||
>
|
||
{t("managedBy", {
|
||
users: data.managers.map((m) => discordFullName(m)).join(", "),
|
||
})}
|
||
</div>
|
||
</div>
|
||
{canEditBadgeOwners({ user, managers: data.managers }) ? (
|
||
<LinkButton to="edit" variant="outlined" size="tiny">
|
||
Edit
|
||
</LinkButton>
|
||
) : null}
|
||
<div className="badges__owners-container">
|
||
<ul className="badges__owners">
|
||
{data.owners.map((owner) => (
|
||
<li key={owner.id}>
|
||
<span
|
||
className={clsx("badges__count", {
|
||
invisible: owner.count <= 1,
|
||
})}
|
||
>
|
||
×{owner.count}
|
||
</span>
|
||
<span>{discordFullName(owner)}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function badgeExplanationText(
|
||
t: TFunction<"badges", undefined>,
|
||
badge: Pick<BadgeDBType, "displayName" | "code"> & { count?: number }
|
||
) {
|
||
if (badge.code === "patreon") return t("patreon");
|
||
if (badge.code === "patreon_plus") {
|
||
return t("patreon+");
|
||
}
|
||
if (badge.code.startsWith("xp")) {
|
||
return t("xp", { xpText: badge.displayName });
|
||
}
|
||
|
||
return t("tournament", {
|
||
count: badge.count ?? 1,
|
||
tournament: badge.displayName,
|
||
}).replace("'", "'");
|
||
}
|