mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-02 08:07:40 -05:00
@@ -17,16 +17,14 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Stack,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import MarkdownTextarea from "components/common/MarkdownTextarea";
|
||||
import { useMutation } from "hooks/common";
|
||||
import { FreeAgentsGet } from "pages/api/free-agents";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { FiTrash } from "react-icons/fi";
|
||||
import { getToastOptions } from "utils/objects";
|
||||
import { trpc } from "utils/trpc";
|
||||
import { Unpacked } from "utils/types";
|
||||
import {
|
||||
FA_POST_CONTENT_LIMIT,
|
||||
@@ -40,43 +38,37 @@ interface Props {
|
||||
post?: Unpacked<FreeAgentsGet>;
|
||||
}
|
||||
|
||||
type FormData = z.infer<typeof freeAgentPostSchema>;
|
||||
type FreeAgentPostSchemaInput = z.infer<typeof freeAgentPostSchema>;
|
||||
|
||||
const FAModal = ({ onClose, post, refetchQuery }: Props) => {
|
||||
const { handleSubmit, errors, register, watch, control } = useForm<FormData>({
|
||||
resolver: zodResolver(freeAgentPostSchema),
|
||||
defaultValues: post,
|
||||
});
|
||||
const { handleSubmit, errors, register, watch, control } =
|
||||
useForm<FreeAgentPostSchemaInput>({
|
||||
resolver: zodResolver(freeAgentPostSchema),
|
||||
defaultValues: post,
|
||||
});
|
||||
|
||||
const upsertPostMutation = trpc.useMutation("freeAgents.upsertPost", {
|
||||
onSuccess() {
|
||||
toast(
|
||||
getToastOptions(
|
||||
post ? t`Free agent post updated` : t`Free agent post submitted`,
|
||||
"success"
|
||||
)
|
||||
);
|
||||
const upsertFreeAgentPostMutation = useMutation<FreeAgentPostSchemaInput>({
|
||||
url: "/api/free-agents",
|
||||
method: "POST",
|
||||
successToastMsg: post
|
||||
? t`Free agent post updated`
|
||||
: t`Free agent post submitted`,
|
||||
afterSuccess: () => {
|
||||
refetchQuery();
|
||||
onClose();
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
const deletePostMutation = trpc.useMutation("freeAgents.deletePost", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions(t`Free agent post deleted`, "success"));
|
||||
const deleteFreeAgentPostMutation = useMutation<FreeAgentPostSchemaInput>({
|
||||
url: "/api/free-agents",
|
||||
method: "DELETE",
|
||||
successToastMsg: t`Free agent post deleted`,
|
||||
afterSuccess: () => {
|
||||
refetchQuery();
|
||||
onClose();
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const watchContent = watch("content", ""); // TODO: get initial fa content from props
|
||||
|
||||
return (
|
||||
@@ -92,7 +84,9 @@ const FAModal = ({ onClose, post, refetchQuery }: Props) => {
|
||||
</ModalHeader>
|
||||
<ModalCloseButton borderRadius="50%" />
|
||||
<form
|
||||
onSubmit={handleSubmit((data) => upsertPostMutation.mutate(data))}
|
||||
onSubmit={handleSubmit((data) =>
|
||||
upsertFreeAgentPostMutation.mutate(data)
|
||||
)}
|
||||
>
|
||||
<ModalBody pb={6}>
|
||||
{post && (
|
||||
@@ -101,10 +95,10 @@ const FAModal = ({ onClose, post, refetchQuery }: Props) => {
|
||||
leftIcon={<FiTrash />}
|
||||
variant="outline"
|
||||
color="red.500"
|
||||
isLoading={deletePostMutation.isLoading}
|
||||
isLoading={deleteFreeAgentPostMutation.isMutating}
|
||||
onClick={async () => {
|
||||
if (window.confirm(t`Delete the free agent post?`)) {
|
||||
deletePostMutation.mutate(null);
|
||||
deleteFreeAgentPostMutation.mutate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -185,7 +179,8 @@ const FAModal = ({ onClose, post, refetchQuery }: Props) => {
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={
|
||||
upsertPostMutation.isLoading || deletePostMutation.isLoading
|
||||
upsertFreeAgentPostMutation.isMutating ||
|
||||
deleteFreeAgentPostMutation.isMutating
|
||||
}
|
||||
>
|
||||
<Trans>Save</Trans>
|
||||
@@ -194,7 +189,8 @@ const FAModal = ({ onClose, post, refetchQuery }: Props) => {
|
||||
onClick={onClose}
|
||||
variant="outline"
|
||||
isDisabled={
|
||||
upsertPostMutation.isLoading || deletePostMutation.isLoading
|
||||
upsertFreeAgentPostMutation.isMutating ||
|
||||
deleteFreeAgentPostMutation.isMutating
|
||||
}
|
||||
>
|
||||
<Trans>Cancel</Trans>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Divider, Flex, IconButton, useToast } from "@chakra-ui/react";
|
||||
import { Box, Divider, Flex, IconButton } from "@chakra-ui/react";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import Flag from "components/common/Flag";
|
||||
import Markdown from "components/common/Markdown";
|
||||
@@ -18,11 +18,10 @@ import {
|
||||
RiPaintLine,
|
||||
RiSwordLine,
|
||||
} from "react-icons/ri";
|
||||
import { getToastOptions } from "utils/objects";
|
||||
import { trpc } from "utils/trpc";
|
||||
import { Unpacked } from "utils/types";
|
||||
import { FreeAgentsGet } from "pages/api/free-agents";
|
||||
import { mutate } from "swr";
|
||||
import { useMutation } from "hooks/common";
|
||||
|
||||
const playstyleToEmoji = {
|
||||
FRONTLINE: RiSwordLine,
|
||||
@@ -45,22 +44,19 @@ const FreeAgentSection = ({
|
||||
showPlusServerMembership: boolean;
|
||||
postRef?: RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
const addLikeMutation = trpc.useMutation("freeAgents.addLike", {
|
||||
onSuccess() {
|
||||
const addLikeMutation = useMutation<{ postId: number }>({
|
||||
url: "/api/free-agents/likes",
|
||||
method: "POST",
|
||||
afterSuccess: () => {
|
||||
mutate("/api/free-agents/likes");
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
const deleteLikeMutation = trpc.useMutation("freeAgents.deleteLike", {
|
||||
onSuccess() {
|
||||
const deleteLikeMutation = useMutation<{ postId: number }>({
|
||||
url: "/api/free-agents/likes",
|
||||
method: "DELETE",
|
||||
afterSuccess: () => {
|
||||
mutate("/api/free-agents/likes");
|
||||
},
|
||||
onError(error) {
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
const handleClick = () =>
|
||||
@@ -179,7 +175,9 @@ const FreeAgentSection = ({
|
||||
mt={4}
|
||||
variant="ghost"
|
||||
icon={isLiked ? <FaHeart /> : <FaRegHeart />}
|
||||
disabled={addLikeMutation.isLoading || deleteLikeMutation.isLoading}
|
||||
disabled={
|
||||
addLikeMutation.isMutating || deleteLikeMutation.isMutating
|
||||
}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const useMutation = <T>({
|
||||
url: string;
|
||||
method?: "POST" | "DELETE" | "PUT";
|
||||
data?: T;
|
||||
successToastMsg: string;
|
||||
successToastMsg?: string;
|
||||
afterSuccess?: () => void;
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
@@ -85,7 +85,9 @@ export const useMutation = <T>({
|
||||
description,
|
||||
});
|
||||
} else {
|
||||
toast(getToastOptions(successToastMsg, "success"));
|
||||
if (successToastMsg) {
|
||||
toast(getToastOptions(successToastMsg, "success"));
|
||||
}
|
||||
afterSuccess?.();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createHandler } from "utils/api";
|
||||
import { createHandler, getMySession } from "utils/api";
|
||||
import freeAgentsService, { PostsData } from "services/freeagents";
|
||||
import { Serialized } from "utils/types";
|
||||
import { freeAgentPostSchema } from "utils/validators/fapost";
|
||||
|
||||
export type FreeAgentsGet = Serialized<PostsData>;
|
||||
|
||||
@@ -10,7 +11,32 @@ const GET = async (_req: NextApiRequest, res: NextApiResponse<PostsData>) => {
|
||||
res.status(200).json(events);
|
||||
};
|
||||
|
||||
const POST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getMySession(req);
|
||||
if (!user) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
|
||||
const parsed = freeAgentPostSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json(parsed.error);
|
||||
}
|
||||
|
||||
await freeAgentsService.upsertPost({ input: parsed.data, userId: user.id });
|
||||
res.status(200).end();
|
||||
};
|
||||
|
||||
const DELETE = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getMySession(req);
|
||||
if (!user) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
|
||||
await freeAgentsService.deletePost(user.id);
|
||||
res.status(200).end();
|
||||
};
|
||||
|
||||
const freeAgentsHandler = (req: NextApiRequest, res: NextApiResponse) =>
|
||||
createHandler(req, res, { GET });
|
||||
createHandler(req, res, { GET, POST, DELETE });
|
||||
|
||||
export default freeAgentsHandler;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createHandler, getMySession } from "utils/api";
|
||||
import freeAgentsService, { LikesData } from "services/freeagents";
|
||||
import { Serialized } from "utils/types";
|
||||
import * as z from "zod";
|
||||
|
||||
export type LikesGet = Serialized<LikesData>;
|
||||
|
||||
@@ -15,7 +16,43 @@ const GET = async (req: NextApiRequest, res: NextApiResponse<LikesData>) => {
|
||||
res.status(200).json(likes);
|
||||
};
|
||||
|
||||
const POST = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getMySession(req);
|
||||
if (!user) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
|
||||
const parsed = z.object({ postId: z.number() }).safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
|
||||
await freeAgentsService.addLike({
|
||||
userId: user.id,
|
||||
postId: parsed.data.postId,
|
||||
});
|
||||
res.status(200).end();
|
||||
};
|
||||
|
||||
const DELETE = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const user = await getMySession(req);
|
||||
if (!user) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
|
||||
const parsed = z.object({ postId: z.number() }).safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(401).end();
|
||||
}
|
||||
|
||||
await freeAgentsService.deleteLike({
|
||||
userId: user.id,
|
||||
postId: parsed.data.postId,
|
||||
});
|
||||
res.status(200).end();
|
||||
};
|
||||
|
||||
const likesHandler = (req: NextApiRequest, res: NextApiResponse) =>
|
||||
createHandler(req, res, { GET });
|
||||
createHandler(req, res, { GET, POST, DELETE });
|
||||
|
||||
export default likesHandler;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as trpc from "@trpc/server";
|
||||
import { inferAsyncReturnType, inferProcedureOutput } from "@trpc/server";
|
||||
import * as trpcNext from "@trpc/server/adapters/next";
|
||||
import freeAgentsApi from "routers/freeagents";
|
||||
import plusApi from "routers/plus";
|
||||
import superjson from "superjson";
|
||||
import { getMySession } from "utils/api";
|
||||
@@ -18,9 +17,7 @@ export function createRouter() {
|
||||
return trpc.router<Context>();
|
||||
}
|
||||
// Important: only use this export with SSR/SSG
|
||||
export const appRouter = createRouter()
|
||||
.merge("plus.", plusApi)
|
||||
.merge("freeAgents.", freeAgentsApi);
|
||||
export const appRouter = createRouter().merge("plus.", plusApi);
|
||||
|
||||
// Exporting type _type_ AppRouter only exposes types that can be used for inference
|
||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export
|
||||
|
||||
@@ -12,22 +12,24 @@ import FAFilters from "components/freeagents/FAFilters";
|
||||
import FAModal from "components/freeagents/FAModal";
|
||||
import FreeAgentSection from "components/freeagents/FreeAgentSection";
|
||||
import MatchesInfo from "components/freeagents/MatchesInfo";
|
||||
import { useUser } from "hooks/common";
|
||||
import { useMutation, useUser } from "hooks/common";
|
||||
import { useFreeAgents } from "hooks/freeagents";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { mutate } from "swr";
|
||||
import { CSSVariables } from "utils/CSSVariables";
|
||||
import { sendData } from "utils/postData";
|
||||
import freeAgentsService from "services/freeagents";
|
||||
import { GetStaticProps } from "next";
|
||||
import { serializeDataForGetStaticProps } from "utils/objects";
|
||||
import { FreeAgentsGet } from "./api/free-agents";
|
||||
import * as z from "zod";
|
||||
import { freeAgentPostSchema } from "utils/validators/fapost";
|
||||
|
||||
interface Props {
|
||||
postsInitialData: FreeAgentsGet;
|
||||
}
|
||||
|
||||
type FreeAgentPostSchemaInput = z.infer<typeof freeAgentPostSchema>;
|
||||
|
||||
const FreeAgentsPage = ({ postsInitialData }: Props) => {
|
||||
const {
|
||||
postsData,
|
||||
@@ -46,13 +48,20 @@ const FreeAgentsPage = ({ postsInitialData }: Props) => {
|
||||
const [postIdToScrollTo, setPostIdToScrollTo] = useState<undefined | number>(
|
||||
undefined
|
||||
);
|
||||
const [sending, setSending] = useState(false);
|
||||
const postRef = useRef<HTMLDivElement>(null);
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
|
||||
const refreshPostMutation = useMutation<FreeAgentPostSchemaInput>({
|
||||
url: "/api/free-agents",
|
||||
method: "POST",
|
||||
successToastMsg: "Free agent post refreshed",
|
||||
afterSuccess: () => {
|
||||
refetchPosts();
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!postRef.current) {
|
||||
console.error("no postRef");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,18 +71,11 @@ const FreeAgentsPage = ({ postsInitialData }: Props) => {
|
||||
const dateThreeWeeksAgo = new Date();
|
||||
dateThreeWeeksAgo.setDate(dateThreeWeeksAgo.getDate() - 7 * 3);
|
||||
|
||||
const onPostRefresh = async () => {
|
||||
setSending(true);
|
||||
const usersPostForRefresh = () => {
|
||||
if (!usersPost) return;
|
||||
const { id, user, updatedAt, ...result } = usersPost;
|
||||
|
||||
const success = await sendData("PUT", "/api/freeagents", {
|
||||
canVC: usersPost!.canVC,
|
||||
playstyles: usersPost!.playstyles,
|
||||
content: usersPost!.content,
|
||||
});
|
||||
setSending(false);
|
||||
if (!success) return;
|
||||
|
||||
mutate("/api/freeagents");
|
||||
return result;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -122,8 +124,10 @@ const FreeAgentsPage = ({ postsInitialData }: Props) => {
|
||||
<Button
|
||||
mt={4}
|
||||
variant="outline"
|
||||
onClick={onPostRefresh}
|
||||
isLoading={sending}
|
||||
onClick={() =>
|
||||
refreshPostMutation.mutate(usersPostForRefresh())
|
||||
}
|
||||
isLoading={refreshPostMutation.isMutating}
|
||||
>
|
||||
<Trans>I'm still a free agent</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { createRouter } from "pages/api/trpc/[trpc]";
|
||||
import service from "services/freeagents";
|
||||
import { throwIfNotLoggedIn } from "utils/api";
|
||||
import { freeAgentPostSchema } from "utils/validators/fapost";
|
||||
import * as z from "zod";
|
||||
|
||||
const freeAgentsApi = createRouter()
|
||||
.mutation("upsertPost", {
|
||||
input: freeAgentPostSchema,
|
||||
resolve({ ctx, input }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.upsertPost({ input, userId: user.id });
|
||||
},
|
||||
})
|
||||
.mutation("deletePost", {
|
||||
resolve({ ctx }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.deletePost(user.id);
|
||||
},
|
||||
})
|
||||
.mutation("addLike", {
|
||||
input: z.object({ postId: z.number() }),
|
||||
resolve({ ctx, input }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.addLike({ userId: user.id, postId: input.postId });
|
||||
},
|
||||
})
|
||||
.mutation("deleteLike", {
|
||||
input: z.object({ postId: z.number() }),
|
||||
resolve({ ctx, input }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.deleteLike({ userId: user.id, postId: input.postId });
|
||||
},
|
||||
});
|
||||
|
||||
export default freeAgentsApi;
|
||||
Reference in New Issue
Block a user