Show Plus Voting % to patrons

This commit is contained in:
Kalle 2022-11-15 18:04:37 +02:00
parent dd3377e7f3
commit 7bce94d97e
6 changed files with 93 additions and 52 deletions

View File

@ -61,14 +61,14 @@ export interface PlusVotingResultByMonthYear {
passed: PlusVotingResultUser[];
failed: PlusVotingResultUser[];
}[];
ownScores?: Pick<
scores: (Pick<
PlusVotingResult,
"score" | "tier" | "wasSuggested" | "passedVoting"
>[];
> & { userId: User["id"] })[];
}
export function resultsByMontYear(
args: MonthYear & { userId?: User["id"] }
args: MonthYear
): PlusVotingResultByMonthYear {
const results = resultsByMonthYearStm.all(
args
@ -76,7 +76,7 @@ export function resultsByMontYear(
return {
results: groupPlusVotingResults(results),
ownScores: ownScoresFromPlusVotingResults(results, args.userId),
scores: scoresFromPlusVotingResults(results),
};
}
@ -114,26 +114,18 @@ function groupPlusVotingResults(
.sort((a, b) => a.tier - b.tier);
}
function ownScoresFromPlusVotingResults(
rows: PlusVotingResultsByMonthYearDatabaseResult,
userId?: User["id"]
function scoresFromPlusVotingResults(
rows: PlusVotingResultsByMonthYearDatabaseResult
) {
if (!userId) return;
const result: PlusVotingResultByMonthYear["ownScores"] = [];
for (const row of rows) {
if (row.id === userId) {
result.push({
tier: row.tier,
score: row.score,
wasSuggested: row.wasSuggested,
passedVoting: row.passedVoting,
});
}
}
return result.sort((a, b) => a.tier - b.tier);
return rows
.map((row) => ({
userId: row.id,
tier: row.tier,
score: row.score,
wasSuggested: row.wasSuggested,
passedVoting: row.passedVoting,
}))
.sort((a, b) => a.tier - b.tier);
}
const plusServerMembersStm = sql.prepare(plusServerMembersSql);

View File

@ -15,4 +15,4 @@ where
"PlusVotingResult"."month" = @month
and "PlusVotingResult"."year" = @year
order by
"User"."discordName" collate nocase asc
"User"."discordName" collate nocase asc

View File

@ -15,7 +15,7 @@ import { databaseTimestampToDate } from "./utils/dates";
// TODO: 1) move "root checkers" to one file and utils to one file 2) make utils const for more terseness
type IsAdminUser = Pick<User, "discordId">;
function isAdmin(user?: IsAdminUser) {
export function isAdmin(user?: IsAdminUser) {
return user?.discordId === ADMIN_DISCORD_ID;
}

View File

@ -8,15 +8,15 @@ import { Link, useLoaderData } from "@remix-run/react";
import { lastCompletedVoting } from "~/modules/plus-server";
import { db } from "~/db";
import type { PlusVotingResultByMonthYear } from "~/db/models/plusVotes/queries.server";
import type { PlusVotingResult } from "~/db/types";
import type { PlusVotingResult, UserWithPlusTier } from "~/db/types";
import { roundToNDecimalPlaces } from "~/utils/number";
import { makeTitle } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
import styles from "~/styles/plus-history.css";
import { discordFullName } from "~/utils/strings";
import { PLUS_SERVER_DISCORD_URL, userPage } from "~/utils/urls";
import clsx from "clsx";
import { getUser } from "~/modules/auth";
import { isAtLeastFiveDollarTierPatreon } from "~/utils/users";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
@ -37,22 +37,76 @@ interface PlusVotingResultsLoaderData {
score?: PlusVotingResult["score"];
tier: PlusVotingResult["tier"];
passedVoting: PlusVotingResult["passedVoting"];
betterThan?: number;
}[];
}
export const loader: LoaderFunction = async ({ request }) => {
const user = await getUser(request);
const { results, ownScores } = db.plusVotes.resultsByMontYear({
...lastCompletedVoting(new Date()),
userId: user?.id,
});
const { results, scores } = db.plusVotes.resultsByMontYear(
lastCompletedVoting(new Date())
);
return json<PlusVotingResultsLoaderData>({
results,
ownScores: ownScores?.map(scoreForDisplaying),
ownScores: ownScores({ scores, user }),
});
};
function databaseAvgToPercentage(score: number) {
const scoreNormalized = score + 1;
return roundToNDecimalPlaces((scoreNormalized / 2) * 100);
}
function ownScores({
scores,
user,
}: {
scores: PlusVotingResultByMonthYear["scores"];
user?: UserWithPlusTier;
}) {
return scores
.filter((score) => {
return score.userId === user?.id;
})
.map((score) => {
const showScore =
(score.wasSuggested && !score.passedVoting) ||
isAtLeastFiveDollarTierPatreon(user);
const sameTierButNotOwn = (
filteredScore: Pick<PlusVotingResult, "tier"> & { userId: number }
) =>
filteredScore.tier === score.tier && filteredScore.userId !== user?.id;
const result: {
tier: number;
score?: number;
passedVoting: number;
betterThan?: number;
} = {
tier: score.tier,
score: databaseAvgToPercentage(score.score),
passedVoting: score.passedVoting,
betterThan: roundToNDecimalPlaces(
(scores
.filter(sameTierButNotOwn)
.filter((otherScore) => otherScore.score <= score.score).length /
scores.filter(sameTierButNotOwn).length) *
100
),
};
if (!showScore) result.score = undefined;
if (!isAtLeastFiveDollarTierPatreon(user) || !result.passedVoting) {
result.betterThan = undefined;
}
return result;
});
}
export default function PlusVotingResultsPage() {
const data = useLoaderData<PlusVotingResultsLoaderData>();
@ -76,7 +130,11 @@ export default function PlusVotingResultsPage() {
)}{" "}
the +{result.tier} voting
{typeof result.score === "number"
? `, your score was ${result.score}% (at least 50% required to pass)`
? `, your score was ${result.score}% ${
result.betterThan
? `(better than ${result.betterThan}% others)`
: "(at least 50% required to pass)"
}`
: ""}
</li>
))}
@ -143,21 +201,3 @@ function Results({
</div>
);
}
function databaseAvgToPercentage(score: number) {
const scoreNormalized = score + 1;
return roundToNDecimalPlaces((scoreNormalized / 2) * 100);
}
function scoreForDisplaying(
score: Unpacked<NonNullable<PlusVotingResultByMonthYear["ownScores"]>>
) {
const showScore = score.wasSuggested && !score.passedVoting;
return {
tier: score.tier,
score: showScore ? databaseAvgToPercentage(score.score) : undefined,
passedVoting: score.passedVoting,
};
}

View File

@ -1,5 +1,4 @@
.plus-history__own-scores {
width: max-content;
padding: var(--s-2);
border-radius: var(--rounded);
margin: 0 auto;

10
app/utils/users.ts Normal file
View File

@ -0,0 +1,10 @@
import type { User } from "~/db/types";
import { isAdmin } from "~/permissions";
export function isAtLeastFiveDollarTierPatreon(
user?: Pick<User, "patronTier" | "discordId">
) {
if (!user) return false;
return isAdmin(user) || (user.patronTier && user.patronTier >= 2);
}