mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
172 lines
4.5 KiB
TypeScript
172 lines
4.5 KiB
TypeScript
import clsx from "clsx";
|
|
import { Check } from "lucide-react";
|
|
import * as React from "react";
|
|
import type { MetaFunction } from "react-router";
|
|
import { Form, useLoaderData } from "react-router";
|
|
import { Avatar } from "~/components/Avatar";
|
|
import { SendouButton } from "~/components/elements/Button";
|
|
import { RelativeTime } from "~/components/RelativeTime";
|
|
import styles from "~/features/plus-suggestions/plus.module.css";
|
|
import { usePlusVoting } from "~/features/plus-voting/core";
|
|
import { metaTags } from "~/utils/remix";
|
|
import { assertUnreachable } from "~/utils/types";
|
|
import { PlusSuggestionComments } from "../../plus-suggestions/routes/plus.suggestions";
|
|
|
|
import { action } from "../actions/plus.voting.server";
|
|
import {
|
|
loader,
|
|
type PlusVotingLoaderData,
|
|
} from "../loaders/plus.voting.server";
|
|
|
|
export { action, loader };
|
|
|
|
export const meta: MetaFunction = (args) => {
|
|
return metaTags({
|
|
title: "Plus Server Voting",
|
|
location: args.location,
|
|
});
|
|
};
|
|
|
|
export default function PlusVotingPage() {
|
|
const data = useLoaderData<PlusVotingLoaderData>();
|
|
|
|
switch (data.type) {
|
|
case "noTimeDefinedInfo": {
|
|
return (
|
|
<div className="text-center text-lighter text-sm">
|
|
Next voting date to be announced
|
|
</div>
|
|
);
|
|
}
|
|
case "timeInfo": {
|
|
return <VotingTimingInfo {...data} />;
|
|
}
|
|
case "voting": {
|
|
return <Voting {...data} />;
|
|
}
|
|
default: {
|
|
assertUnreachable(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
function VotingTimingInfo(
|
|
data: Extract<PlusVotingLoaderData, { type: "timeInfo" }>,
|
|
) {
|
|
return (
|
|
<div className="stack md">
|
|
{data.voted ? (
|
|
<div className={styles.votingAlert}>
|
|
<Check /> You have voted
|
|
</div>
|
|
) : null}
|
|
<div className="text-sm text-center">
|
|
{data.timeInfo.timing === "starts"
|
|
? "Next voting starts"
|
|
: "Voting is currently happening. Ends"}{" "}
|
|
<RelativeTime timestamp={data.timeInfo.timestamp}>
|
|
{data.timeInfo.relativeTime}
|
|
</RelativeTime>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const tips = [
|
|
"Voting progress is saved locally",
|
|
"You can use S (-1) and K (+1) keys on desktop to vote",
|
|
"You +1 yourself automatically",
|
|
];
|
|
|
|
function Voting(data: Extract<PlusVotingLoaderData, { type: "voting" }>) {
|
|
const [randomTip] = React.useState(tips[Math.floor(Math.random() * 3)]);
|
|
const { currentUser, previous, votes, addVote, undoLast, isReady, progress } =
|
|
usePlusVoting(data.usersForVoting);
|
|
|
|
if (!isReady) return null;
|
|
|
|
return (
|
|
<div className={clsx(styles.votingContainer, "stack md")}>
|
|
<div className="stack xs">
|
|
<div className="text-sm text-center">
|
|
Voting ends{" "}
|
|
<RelativeTime timestamp={data.votingEnds.timestamp}>
|
|
{data.votingEnds.relativeTime}
|
|
</RelativeTime>
|
|
</div>
|
|
{progress ? (
|
|
<progress
|
|
className={styles.votingProgress}
|
|
value={progress[0]}
|
|
max={progress[1]}
|
|
title={`Voting progress ${progress[0]} out of ${progress[1]}`}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
{previous ? (
|
|
<p className="button-text-paragraph text-sm text-lighter">
|
|
Previously{" "}
|
|
<span className={previous.score > 0 ? "text-success" : "text-error"}>
|
|
{previous.score > 0 ? "+" : ""}
|
|
{previous.score}
|
|
</span>{" "}
|
|
on {previous.user.username}.
|
|
<SendouButton
|
|
className="ml-auto"
|
|
variant="minimal"
|
|
onPress={undoLast}
|
|
>
|
|
Undo?
|
|
</SendouButton>
|
|
</p>
|
|
) : (
|
|
<p className="text-sm text-lighter">Tip: {randomTip}</p>
|
|
)}
|
|
{currentUser ? (
|
|
<div className="stack md items-center">
|
|
<Avatar user={currentUser.user} size="lg" />
|
|
<h2>{currentUser.user.username}</h2>
|
|
<div className="stack horizontal lg">
|
|
<SendouButton
|
|
className={clsx(
|
|
styles.votingVoteButton,
|
|
styles.votingVoteButtonDownvote,
|
|
)}
|
|
variant="outlined"
|
|
onPress={() => addVote("downvote")}
|
|
>
|
|
-1
|
|
</SendouButton>
|
|
<SendouButton
|
|
className={styles.votingVoteButton}
|
|
variant="outlined"
|
|
onPress={() => addVote("upvote")}
|
|
>
|
|
+1
|
|
</SendouButton>
|
|
</div>
|
|
{currentUser.suggestion ? (
|
|
<PlusSuggestionComments
|
|
suggestion={currentUser.suggestion}
|
|
defaultOpen
|
|
/>
|
|
) : null}
|
|
{currentUser.user.bio ? (
|
|
<article className="w-full">
|
|
<h2 className={styles.votingBioHeader}>Bio</h2>
|
|
{currentUser.user.bio}
|
|
</article>
|
|
) : null}
|
|
</div>
|
|
) : (
|
|
<Form method="post">
|
|
<input type="hidden" name="votes" value={JSON.stringify(votes)} />
|
|
<SendouButton className={styles.votingSubmitButton} type="submit">
|
|
Submit votes
|
|
</SendouButton>
|
|
</Form>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|