mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
import clsx from "clsx";
|
|
import type { MetaFunction } from "react-router";
|
|
import { Link, useLoaderData } from "react-router";
|
|
import { metaTags, type SerializeFrom } from "~/utils/remix";
|
|
import { PLUS_SERVER_DISCORD_URL, userPage } from "~/utils/urls";
|
|
|
|
import { loader } from "../loaders/plus.voting.results.server";
|
|
import styles from "../plus-voting-results.module.css";
|
|
|
|
export { loader };
|
|
|
|
export const meta: MetaFunction = (args) => {
|
|
return metaTags({
|
|
title: "Plus Server voting results",
|
|
ogTitle: "Plus Server voting results",
|
|
description:
|
|
"Plus Server (+1, +2 and +3) voting results for the latest season.",
|
|
location: args.location,
|
|
});
|
|
};
|
|
|
|
export default function PlusVotingResultsPage() {
|
|
const data = useLoaderData<typeof loader>();
|
|
|
|
return (
|
|
<div className="stack md">
|
|
<h2 className="text-center">
|
|
Voting results for {data.lastCompletedVoting.month + 1}/
|
|
{data.lastCompletedVoting.year}
|
|
</h2>
|
|
{data.ownScores && data.ownScores.length > 0 ? (
|
|
<>
|
|
<ul className={clsx(styles.ownScores, "stack sm")}>
|
|
{data.ownScores.map((result) => (
|
|
<li key={result.tier}>
|
|
You{" "}
|
|
{result.passedVoting ? (
|
|
<span className={styles.success}>passed</span>
|
|
) : (
|
|
<span className={styles.fail}>didn't pass</span>
|
|
)}{" "}
|
|
the +{result.tier} voting
|
|
{typeof result.score === "number"
|
|
? `, your score was ${result.score}% ${
|
|
result.betterThan
|
|
? `(better than ${result.betterThan}% others)`
|
|
: "(at least 60% required to pass)"
|
|
}`
|
|
: ""}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<div className="text-sm text-center text-lighter">
|
|
Click{" "}
|
|
<a href={PLUS_SERVER_DISCORD_URL} target="_blank" rel="noreferrer">
|
|
here
|
|
</a>{" "}
|
|
to join the Discord server. In some cases you might need to rejoin
|
|
the server to get the correct role.
|
|
</div>
|
|
</>
|
|
) : null}
|
|
{!data.ownScores ? (
|
|
<div className="text-center text-sm">
|
|
You weren't in the voting this month.
|
|
</div>
|
|
) : null}
|
|
{data.results ? <Results results={data.results} /> : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Results({
|
|
results,
|
|
}: {
|
|
results: NonNullable<SerializeFrom<typeof loader>["results"]>;
|
|
}) {
|
|
return (
|
|
<div>
|
|
<div className="text-xs text-lighter">S = Suggested user</div>
|
|
<div className="stack lg">
|
|
{results.map((tiersResults) => (
|
|
<div className="stack md" key={tiersResults.tier}>
|
|
<h3 className={styles.tierHeader}>
|
|
<span>+{tiersResults.tier}</span>
|
|
</h3>
|
|
{(["passed", "failed"] as const).map((status) => (
|
|
<div key={status} className={styles.passedInfoContainer}>
|
|
<h4 className={styles.passedHeader}>
|
|
{status === "passed" ? "Passed" : "Didn't pass"} (
|
|
{tiersResults[status].length})
|
|
</h4>
|
|
{tiersResults[status].map((user) => (
|
|
<Link
|
|
to={userPage(user)}
|
|
className={clsx(styles.userStatus, {
|
|
[styles.userStatusFailed]: status === "failed",
|
|
})}
|
|
key={user.id}
|
|
>
|
|
{user.wasSuggested ? (
|
|
<span className={styles.suggestionS}>S</span>
|
|
) : null}
|
|
{user.username}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|