mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-16 00:14:21 -05:00
new api for plus
This commit is contained in:
parent
710520d0f6
commit
950f2be402
|
|
@ -1,6 +1,7 @@
|
|||
import { createRouter } from "pages/api/trpc/[trpc]";
|
||||
import { throwIfNotLoggedIn } from "utils/api";
|
||||
import { suggestionFullSchema } from "utils/validators/suggestion";
|
||||
import { vouchSchema } from "utils/validators/vouch";
|
||||
import service from "./service";
|
||||
|
||||
const plusApi = createRouter()
|
||||
|
|
@ -20,6 +21,12 @@ const plusApi = createRouter()
|
|||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.addSuggestion({ input, userId: user.id });
|
||||
},
|
||||
})
|
||||
.mutation("vouch", {
|
||||
input: vouchSchema,
|
||||
resolve({ input, ctx }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.addVouch({ input, userId: user.id });
|
||||
},
|
||||
});
|
||||
|
||||
export default plusApi;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
FormHelperText,
|
||||
FormLabel,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Trans } from "@lingui/macro";
|
||||
|
|
@ -14,10 +15,11 @@ import MyLink from "components/common/MyLink";
|
|||
import SubText from "components/common/SubText";
|
||||
import UserAvatar from "components/common/UserAvatar";
|
||||
import { useMyTheme } from "hooks/common";
|
||||
import useMutation from "hooks/useMutation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { getToastOptions } from "utils/getToastOptions";
|
||||
import { getFullUsername } from "utils/strings";
|
||||
import { trpc } from "utils/trpc";
|
||||
import { Unpacked } from "utils/types";
|
||||
import {
|
||||
resuggestionSchema,
|
||||
|
|
@ -34,16 +36,23 @@ const Suggestion = ({
|
|||
suggestion: Unpacked<Suggestions>;
|
||||
canSuggest: boolean;
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
const { gray } = useMyTheme();
|
||||
const [showTextarea, setShowTextarea] = useState(false);
|
||||
const { handleSubmit, errors, register, watch } = useForm<FormData>({
|
||||
resolver: zodResolver(resuggestionSchema),
|
||||
});
|
||||
const { onSubmit, sending } = useMutation({
|
||||
route: "plus/suggestions",
|
||||
mutationKey: "plus/suggestions",
|
||||
successText: "Comment added",
|
||||
onSuccess: () => setShowTextarea(false),
|
||||
|
||||
const { mutate, status } = trpc.useMutation("plus.suggestion", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions("Comment added", "success"));
|
||||
// TODO:
|
||||
trpc.queryClient.invalidateQueries(["plus.suggestions"]);
|
||||
setShowTextarea(false);
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
const watchDescription = watch("description", "");
|
||||
|
|
@ -100,7 +109,7 @@ const Suggestion = ({
|
|||
{showTextarea && (
|
||||
<form
|
||||
onSubmit={handleSubmit((values) =>
|
||||
onSubmit({
|
||||
mutate({
|
||||
...values,
|
||||
// region doesn't matter as it is not updated after the first suggestion
|
||||
region: "NA",
|
||||
|
|
@ -127,7 +136,7 @@ const Suggestion = ({
|
|||
size="sm"
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={sending}
|
||||
isLoading={status === "loading"}
|
||||
data-cy="submit-button"
|
||||
>
|
||||
<Trans>Save</Trans>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ import {
|
|||
ModalOverlay,
|
||||
Select,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import UserSelector from "components/common/UserSelector";
|
||||
import useMutation from "hooks/useMutation";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { getToastOptions } from "utils/getToastOptions";
|
||||
import { trpc } from "utils/trpc";
|
||||
import {
|
||||
suggestionFullSchema,
|
||||
SUGGESTION_DESCRIPTION_LIMIT,
|
||||
|
|
@ -32,15 +34,22 @@ interface Props {
|
|||
type FormData = z.infer<typeof suggestionFullSchema>;
|
||||
|
||||
const SuggestionModal: React.FC<Props> = ({ userPlusMembershipTier }) => {
|
||||
const toast = useToast();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { handleSubmit, errors, register, watch, control } = useForm<FormData>({
|
||||
resolver: zodResolver(suggestionFullSchema),
|
||||
});
|
||||
const { onSubmit, sending } = useMutation({
|
||||
onSuccess: () => setIsOpen(false),
|
||||
route: "plus/suggestions",
|
||||
mutationKey: "plus/suggestions",
|
||||
successText: "New suggestion submitted",
|
||||
|
||||
const { mutate, status } = trpc.useMutation("plus.suggestion", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions("New suggestion submitted", "success"));
|
||||
// TODO:
|
||||
trpc.queryClient.invalidateQueries(["plus.suggestions"]);
|
||||
setIsOpen(false);
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
const watchDescription = watch("description", "");
|
||||
|
|
@ -66,7 +75,7 @@ const SuggestionModal: React.FC<Props> = ({ userPlusMembershipTier }) => {
|
|||
<ModalContent>
|
||||
<ModalHeader>Adding a new suggestion</ModalHeader>
|
||||
<ModalCloseButton borderRadius="50%" />
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form onSubmit={handleSubmit((data) => mutate(data))}>
|
||||
<ModalBody pb={2}>
|
||||
<FormLabel>Tier</FormLabel>
|
||||
<Controller
|
||||
|
|
@ -146,7 +155,7 @@ const SuggestionModal: React.FC<Props> = ({ userPlusMembershipTier }) => {
|
|||
<Button
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={sending}
|
||||
isLoading={status === "loading"}
|
||||
data-cy="submit-button"
|
||||
>
|
||||
Save
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ import {
|
|||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Select,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import UserSelector from "components/common/UserSelector";
|
||||
import useMutation from "hooks/useMutation";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { getToastOptions } from "utils/getToastOptions";
|
||||
import { trpc } from "utils/trpc";
|
||||
import { vouchSchema } from "utils/validators/vouch";
|
||||
import * as z from "zod";
|
||||
|
||||
|
|
@ -28,15 +30,22 @@ interface Props {
|
|||
type FormData = z.infer<typeof vouchSchema>;
|
||||
|
||||
const VouchModal: React.FC<Props> = ({ canVouchFor }) => {
|
||||
const toast = useToast();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { handleSubmit, errors, register, control } = useForm<FormData>({
|
||||
resolver: zodResolver(vouchSchema),
|
||||
});
|
||||
const { onSubmit, sending } = useMutation({
|
||||
onSuccess: () => setIsOpen(false),
|
||||
route: "plus/vouch",
|
||||
mutationKey: "plus",
|
||||
successText: "Successfully vouched",
|
||||
|
||||
const { mutate, status } = trpc.useMutation("plus.vouch", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions("Successfully vouched", "success"));
|
||||
// TODO:
|
||||
trpc.queryClient.invalidateQueries(["plus.statuses"]);
|
||||
setIsOpen(false);
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -61,7 +70,7 @@ const VouchModal: React.FC<Props> = ({ canVouchFor }) => {
|
|||
<ModalContent>
|
||||
<ModalHeader>Vouching</ModalHeader>
|
||||
<ModalCloseButton borderRadius="50%" />
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form onSubmit={handleSubmit((data) => mutate(data))}>
|
||||
<ModalBody pb={2}>
|
||||
<FormLabel>Tier</FormLabel>
|
||||
<Controller
|
||||
|
|
@ -119,7 +128,7 @@ const VouchModal: React.FC<Props> = ({ canVouchFor }) => {
|
|||
<Button
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={sending}
|
||||
isLoading={status === "loading"}
|
||||
data-cy="submit-button"
|
||||
>
|
||||
Save
|
||||
|
|
|
|||
|
|
@ -240,20 +240,19 @@ const addSuggestion = async ({
|
|||
};
|
||||
|
||||
const addVouch = async ({
|
||||
data,
|
||||
input,
|
||||
userId,
|
||||
}: {
|
||||
data: unknown;
|
||||
input: z.infer<typeof vouchSchema>;
|
||||
userId: number;
|
||||
}) => {
|
||||
const parsedData = vouchSchema.parse(data);
|
||||
const plusStatuses = await prisma.plusStatus.findMany({});
|
||||
|
||||
const suggesterPlusStatus = plusStatuses.find(
|
||||
(status) => status.userId === userId
|
||||
);
|
||||
|
||||
if ((suggesterPlusStatus?.canVouchFor ?? Infinity) > parsedData.tier) {
|
||||
if ((suggesterPlusStatus?.canVouchFor ?? Infinity) > input.tier) {
|
||||
httpError.badRequest(
|
||||
"not a member of high enough tier to vouch for this tier"
|
||||
);
|
||||
|
|
@ -269,9 +268,9 @@ const addVouch = async ({
|
|||
|
||||
return prisma.$transaction([
|
||||
prisma.plusStatus.upsert({
|
||||
where: { userId: parsedData.vouchedId },
|
||||
create: { region: parsedData.region, userId: parsedData.vouchedId },
|
||||
update: { voucherId: userId, vouchTier: parsedData.tier },
|
||||
where: { userId: input.vouchedId },
|
||||
create: { region: input.region, userId: input.vouchedId },
|
||||
update: { voucherId: userId, vouchTier: input.tier },
|
||||
}),
|
||||
prisma.plusStatus.update({
|
||||
where: { userId },
|
||||
|
|
@ -281,12 +280,12 @@ const addVouch = async ({
|
|||
|
||||
function vouchedUserAlreadyHasAccess() {
|
||||
const suggestedPlusStatus = plusStatuses.find(
|
||||
(status) => status.userId === parsedData.vouchedId
|
||||
(status) => status.userId === input.vouchedId
|
||||
);
|
||||
return Boolean(
|
||||
suggestedPlusStatus &&
|
||||
((suggestedPlusStatus.membershipTier ?? 999) <= parsedData.tier ||
|
||||
(suggestedPlusStatus.vouchTier ?? 999) <= parsedData.tier)
|
||||
((suggestedPlusStatus.membershipTier ?? 999) <= input.tier ||
|
||||
(suggestedPlusStatus.vouchTier ?? 999) <= input.tier)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
import { useToast } from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { mutate } from "swr";
|
||||
import { getToastOptions } from "utils/getToastOptions";
|
||||
import { sendData } from "utils/postData";
|
||||
import { useUser } from "./common";
|
||||
|
||||
const useMutation = ({
|
||||
route,
|
||||
mutationKey,
|
||||
onSuccess,
|
||||
successText,
|
||||
}: {
|
||||
route: string;
|
||||
mutationKey: string;
|
||||
onSuccess?: () => void;
|
||||
successText: string;
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
const [sending, setSending] = useState(false);
|
||||
const [loggedInUser] = useUser();
|
||||
|
||||
const onSubmit = async (formData: object) => {
|
||||
if (!loggedInUser) {
|
||||
console.error("Unexpected no logged in user");
|
||||
return;
|
||||
}
|
||||
setSending(true);
|
||||
const mutationData = { ...formData };
|
||||
|
||||
for (const [key, value] of Object.entries(mutationData)) {
|
||||
if (value === "" || value === undefined) {
|
||||
// @ts-ignore
|
||||
mutationData[key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
const success = await sendData("POST", "/api/" + route, mutationData);
|
||||
setSending(false);
|
||||
if (!success) return;
|
||||
|
||||
mutate("/api/" + mutationKey);
|
||||
|
||||
toast(getToastOptions(successText, "success"));
|
||||
onSuccess?.();
|
||||
};
|
||||
|
||||
return { onSubmit, sending };
|
||||
};
|
||||
|
||||
export default useMutation;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import plusService from "app/plus/service";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
const plusHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
await getHandler(req, res);
|
||||
break;
|
||||
default:
|
||||
res.status(405).end();
|
||||
}
|
||||
|
||||
async function getHandler(_req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
res.status(200).json(await plusService.getPlusStatuses());
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default plusHandler;
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import plusService from "app/plus/service";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getMySession } from "utils/api";
|
||||
import { UserError } from "utils/errors";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
const vouchHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getMySession(req);
|
||||
|
||||
switch (req.method) {
|
||||
case "POST":
|
||||
await postHandler(req, res);
|
||||
break;
|
||||
default:
|
||||
res.status(405).end();
|
||||
}
|
||||
|
||||
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!user) return res.status(401).end();
|
||||
|
||||
try {
|
||||
await plusService.addVouch({ data: req.body, userId: user.id });
|
||||
} catch (e) {
|
||||
if (e instanceof ZodError) {
|
||||
res.status(400).json({ message: e.message });
|
||||
} else if (e instanceof UserError) {
|
||||
res.status(400).json({ message: e.message });
|
||||
} else {
|
||||
console.error(e.message);
|
||||
res.status(500).end();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).end();
|
||||
}
|
||||
};
|
||||
|
||||
export default vouchHandler;
|
||||
Loading…
Reference in New Issue
Block a user