suggestions render

This commit is contained in:
Kalle 2021-02-24 02:30:31 +02:00
parent 271c9ae1fe
commit ca2bdabf0e
7 changed files with 260 additions and 9 deletions

View File

@ -1,19 +1,131 @@
import { Button } from "@chakra-ui/button";
import { Box } from "@chakra-ui/layout";
import { Box, Center, Divider, Flex, Stack } from "@chakra-ui/layout";
import { Radio, RadioGroup } from "@chakra-ui/react";
import { Trans } from "@lingui/macro";
import MyLink from "components/common/MyLink";
import SubText from "components/common/SubText";
import UserAvatar from "components/common/UserAvatar";
import { usePlus } from "hooks/plus";
import { getFullUsername } from "lib/strings";
import { Fragment } from "react";
import SuggestionVouchModal from "./SuggestionVouchModal";
export interface PlusHomePageProps {}
const PlusHomePage: React.FC<PlusHomePageProps> = () => {
const {
plusStatusData,
suggestionsData,
ownSuggestion,
suggestionsLoading,
suggestionCounts,
setSuggestionsFilter,
} = usePlus();
return (
<>
<SuggestionVouchModal
canSuggest={true}
canVouch={false}
userPlusMembershipTier={1}
/>
<Box>No suggestions yet for this month</Box>
{plusStatusData && plusStatusData.membershipTier && (
<SuggestionVouchModal
canSuggest={!ownSuggestion}
canVouch={!!plusStatusData.canVouchFor}
userPlusMembershipTier={plusStatusData.membershipTier}
/>
)}
<Center mt={6}>
<RadioGroup
defaultValue="ALL"
onChange={(value) => {
const tier = [null, "ONE", "TWO", "THREE"].indexOf(value as any);
setSuggestionsFilter(tier === -1 ? undefined : tier);
}}
>
<Stack spacing={4} direction={["column", "row"]}>
<Radio value="ALL">
<Trans>
All (
{suggestionCounts.ONE +
suggestionCounts.TWO +
suggestionCounts.THREE}
)
</Trans>
</Radio>
<Radio value="ONE">
<Flex align="center">
<SubText mr={2}>+1</SubText> ({suggestionCounts.ONE})
</Flex>
</Radio>
<Radio value="TWO">
<Flex align="center">
<SubText mr={2}>+2</SubText> ({suggestionCounts.TWO})
</Flex>
</Radio>
<Radio value="THREE">
<Flex align="center">
<SubText mr={2}>+3</SubText> ({suggestionCounts.THREE})
</Flex>
</Radio>
</Stack>
</RadioGroup>
</Center>
{!suggestionsLoading &&
suggestionCounts.ONE + suggestionCounts.TWO + suggestionCounts.THREE ===
0 ? (
<Box mt={4}>No suggestions yet for this month</Box>
) : (
<>
{suggestionsData.map((suggestion, i) => {
return (
<Fragment key={suggestion.suggestedUser.id}>
<Box as="section" my={8}>
<Flex
alignItems="center"
fontWeight="bold"
fontSize="1.25rem"
>
<UserAvatar user={suggestion.suggestedUser} mr={3} />
<MyLink
href={`/u/${suggestion.suggestedUser.discordId}`}
isColored={false}
>
{getFullUsername(suggestion.suggestedUser)}
</MyLink>
</Flex>
<SubText ml={2} mt={2}>
+{suggestion.tier}
</SubText>
<Box ml={2} mt={4} fontSize="sm">
"{suggestion.description}" -{" "}
<MyLink
href={`/u/${suggestion.suggesterUser.discordId}`}
isColored={false}
>
{getFullUsername(suggestion.suggesterUser)}
</MyLink>
</Box>
{suggestion.resuggestions?.map((resuggestion) => {
return (
<Box
key={resuggestion.suggesterUser.id}
ml={2}
mt={4}
fontSize="sm"
>
"{resuggestion.description}" -{" "}
<MyLink
href={`/u/${resuggestion.suggesterUser.discordId}`}
isColored={false}
>
{getFullUsername(resuggestion.suggesterUser)}
</MyLink>
</Box>
);
})}
</Box>
{i < suggestionsData.length - 1 && <Divider />}
</Fragment>
);
})}
</>
)}
</>
);
};

View File

@ -46,7 +46,7 @@ const SuggestionVouchModal: React.FC<Props> = ({
const { onSubmit, sending } = useMutation({
onClose: () => setIsOpen(false),
route: "plus/suggestions",
mutationKey: "",
mutationKey: "plus/suggestions",
successText: t`New suggestion submitted`,
});

65
hooks/plus.ts Normal file
View File

@ -0,0 +1,65 @@
import { Unpacked } from "lib/types";
import { useState } from "react";
import { PlusStatus, Suggestions } from "services/plus";
import useSWR from "swr";
import { useUser } from "./common";
export function usePlus() {
const [user] = useUser();
const [suggestionsFilter, setSuggestionsFilter] = useState<
number | undefined
>(undefined);
const { data: plusStatusData } = useSWR<PlusStatus>("/api/plus");
const { data: suggestionsData } = useSWR<Suggestions>(
"/api/plus/suggestions"
);
const suggestions = suggestionsData ?? [];
const suggestionDescriptions = suggestions
.filter((suggestion) => suggestion.isResuggestion)
.reduce(
(descriptions: Partial<Record<string, Suggestions>>, suggestion) => {
const key = suggestion.suggestedUser.id + "_" + suggestion.tier;
if (!descriptions[key]) descriptions[key] = [];
descriptions[key]!.push(suggestion);
return descriptions;
},
{}
);
return {
plusStatusData,
suggestionsData: suggestions
.filter((suggestion) => {
if (suggestion.isResuggestion) return false;
return !suggestionsFilter || suggestion.tier === suggestionsFilter;
})
.map((suggestion) => ({
...suggestion,
resuggestions:
suggestionDescriptions[
suggestion.suggestedUser.id + "_" + suggestion.tier
],
})),
suggestionsLoading: !suggestionsData,
suggestionCounts: suggestions.reduce(
(counts, suggestion) => {
const tierString = [null, "ONE", "TWO", "THREE"][
suggestion.tier
] as keyof typeof counts;
counts[tierString]++;
return counts;
},
{ ONE: 0, TWO: 0, THREE: 0 }
),
ownSuggestion: suggestions.find(
(suggestion) => suggestion.suggesterUser.id === user?.id
),
setSuggestionsFilter,
};
}

View File

@ -39,7 +39,7 @@ const useMutation = ({
setSending(false);
if (!success) return;
mutate(mutationKey);
mutate("/api/" + mutationKey);
toast(getToastOptions(successText, "success"));
onClose();

28
pages/api/plus/index.ts Normal file
View File

@ -0,0 +1,28 @@
import { getMySession } from "lib/api";
import { NextApiRequest, NextApiResponse } from "next";
import plusService from "services/plus";
const plusHandler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = await getMySession(req);
switch (req.method) {
case "GET":
await getHandler(req, res);
break;
default:
res.status(405).end();
}
async function getHandler(_req: NextApiRequest, res: NextApiResponse) {
if (!user) return res.status(401).end();
try {
res.status(200).json(await plusService.getPlusStatus(user.id));
} catch (e) {
console.error(e.message);
res.status(500).end();
}
}
};
export default plusHandler;

View File

@ -11,6 +11,9 @@ const suggestionsHandler = async (
const user = await getMySession(req);
switch (req.method) {
case "GET":
await getHandler(req, res);
break;
case "POST":
await postHandler(req, res);
break;
@ -29,6 +32,7 @@ const suggestionsHandler = async (
} else if (e instanceof UserError) {
res.status(400).json({ message: e.message });
} else {
console.error(e.message);
res.status(500).end();
}
@ -37,6 +41,15 @@ const suggestionsHandler = async (
res.status(200).end();
}
async function getHandler(_req: NextApiRequest, res: NextApiResponse) {
try {
res.status(200).json(await plusService.getSuggestions());
} catch (e) {
console.error(e.message);
res.status(500).end();
}
}
};
export default suggestionsHandler;

View File

@ -5,6 +5,37 @@ import { userBasicSelection } from "lib/prisma";
import { suggestionSchema } from "lib/validators/suggestion";
import prisma from "prisma/client";
export type PlusStatus = Prisma.PromiseReturnType<typeof getPlusStatus>;
const getPlusStatus = async (userId: number) => {
return prisma.plusStatus.findUnique({
where: { userId },
select: {
canVouchAgainAfter: true,
vouchTier: true,
canVouchFor: true,
membershipTier: true,
region: true,
voucher: { select: userBasicSelection },
},
});
};
export type Suggestions = Prisma.PromiseReturnType<typeof getSuggestions>;
const getSuggestions = async () => {
return prisma.plusSuggestion.findMany({
select: {
createdAt: true,
description: true,
isResuggestion: true,
tier: true,
suggestedUser: { select: userBasicSelection },
suggesterUser: { select: userBasicSelection },
},
});
};
export type VotingSummariesByMonthAndTier = Prisma.PromiseReturnType<
typeof getVotingSummariesByMonthAndTier
>;
@ -172,6 +203,8 @@ const addSuggestion = async ({
};
export default {
getPlusStatus,
getSuggestions,
getVotingSummariesByMonthAndTier,
getMostRecentVotingWithResultsMonth,
getDistinctSummaryMonths,