From 556953a045c113c5cdc8b1d867e8d013bb5393e9 Mon Sep 17 00:00:00 2001 From: Pau Matas Date: Fri, 15 Mar 2024 00:34:59 +0100 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20cors=20i=20subscripcions=20a=20assi?= =?UTF-8?q?gnatures=20a=20la=20p=C3=A0gina=20principal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/page.tsx | 111 +++++++++++++++++--- src/components/SubscribeHeartToggle.tsx | 130 ++++++++++++++++++++++++ 2 files changed, 225 insertions(+), 16 deletions(-) create mode 100644 src/components/SubscribeHeartToggle.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index 71e60a8..fd152a0 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,16 +1,16 @@ // import CustomFeed from "@/components/CustomFeed"; import { buttonVariants } from "@/components/ui/Button" -// import { getAuthSession } from "@/lib/auth"; +import { getAuthSession } from "@/lib/auth" import { FileTextIcon, HelpCircleIcon, HomeIcon, BookIcon } from "lucide-react" import Link from "next/link" import { db } from "@/lib/db" -// import { HeartIcon, HeartPulseIcon } from "lucide-react"; import { Badge } from "@/components/ui/Badge" import { cn } from "@/lib/utils" +import SubscribeHeartToggle from "@/components/SubscribeHeartToggle" export default async function Home() { - // const session = await getAuthSession(); + const session = await getAuthSession() const subjects = await db.subject.findMany({ select: { @@ -31,17 +31,41 @@ export default async function Home() { }, }) - // const subscription = !session?.user - // ? undefined - // : await db.subscription.findFirst({ - // where: { - // userId: session.user.id, - // subjectId: subjects.id, - // }, - // }); + const subscribedSubjects = await db.subscription.findMany({ + where: { + userId: session?.user.id, + }, + select: { + subject: { + select: { + id: true, + acronym: true, + name: true, + semester: true, + posts: { + select: { + _count: true, + }, + }, + questions: { + select: { + _count: true, + }, + }, + }, + }, + }, + orderBy: { + subject: { + semester: "asc", + }, + }, + }) - // const isSubscribed = !!subscription; - // const ColorClass = isSubscribed ? "text-red-500" : "text-black"; + const notSubscribedSubjects = subjects.filter( + (subject) => + !subscribedSubjects.some((sub) => sub.subject.id === subject.id), + ) function semesterColor(semester: string) { switch (semester) { @@ -137,7 +161,55 @@ export default async function Home() {
- {subjects.map((subject, index) => { + {subscribedSubjects.map((subscription, index) => { + return ( + +
+
+

+ + {subscription.subject.name} +

+ +
+ +
+ + {subscription.subject.semester} + + + {subscription.subject.acronym} + + + {subscription.subject.posts.length} + + + + {subscription.subject.questions.length} + + +
+
+ + ) + })} +
+ +
+ {notSubscribedSubjects.map((subject, index) => { return (

{subject.name} - {/* */}

+
diff --git a/src/components/SubscribeHeartToggle.tsx b/src/components/SubscribeHeartToggle.tsx new file mode 100644 index 0000000..270ad68 --- /dev/null +++ b/src/components/SubscribeHeartToggle.tsx @@ -0,0 +1,130 @@ +"use client" + +import { FC, startTransition } from "react" +import { Button } from "./ui/Button" +import { SubscribeToSubjectPayload } from "@/lib/validators/subject" +import { useMutation } from "@tanstack/react-query" +import axios, { AxiosError } from "axios" +import { toast } from "@/hooks/use-toast" +import { useRouter } from "next/navigation" +import { useCustomToasts } from "@/hooks/use-custom-toasts" +import { Heart, HeartIcon } from "lucide-react" + +interface SubscribeLeaveToggleProps { + subjectId: string + subjectName: string + isSubscribed: boolean +} + +const SubscribeLeaveToggle: FC = ({ + subjectId, + subjectName, + isSubscribed, +}) => { + const { loginToast } = useCustomToasts() + const router = useRouter() + + const { mutate: subscribe, isLoading: isSubLoading } = useMutation({ + mutationFn: async () => { + const payload: SubscribeToSubjectPayload = { + subjectId, + } + const { data } = await axios.post("/api/subject/subscribe", payload) + return data as string + }, + onError: (err) => { + if (err instanceof AxiosError) { + if (err.response?.status === 401) { + return loginToast() + } + } + + return toast({ + title: "S'ha produït un error desconegut.", + description: + "No s'ha pogut subscriure a l'assignatura. Siusplau, torna a intentar-ho més tard.", + variant: "destructive", + }) + }, + onSuccess: ({}) => { + startTransition(() => { + router.refresh() + }) + + const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i + const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de " + + return toast({ + title: `T'has subscrit als apunts ${subjectArticle}${subjectName}!`, + description: "", + }) + }, + }) + + const { mutate: unsubscribe, isLoading: isUnsubLoading } = useMutation({ + mutationFn: async () => { + const payload: SubscribeToSubjectPayload = { + subjectId, + } + const { data } = await axios.post("/api/subject/unsubscribe", payload) + return data as string + }, + onError: (err) => { + if (err instanceof AxiosError) { + if (err.response?.status === 401) { + return loginToast() + } + } + + return toast({ + title: "S'ha produït un error desconegut.", + description: + "No s'ha pogut donar de baixa la subscripció a l'assignatura. Siusplau, torna a intentar-ho més tard.", + variant: "destructive", + }) + }, + onSuccess: ({}) => { + startTransition(() => { + router.refresh() + }) + + const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i + const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de " + + return toast({ + title: `Has donat de baixa la teva subscripció als apunts ${subjectArticle}${subjectName}!`, + description: "", + }) + }, + }) + + return isSubscribed ? ( + + ) : ( + + ) +} + +export default SubscribeLeaveToggle From e28363f71e5d7c4f37e19fb22ac4ef500d2f7553 Mon Sep 17 00:00:00 2001 From: Pau Matas Date: Fri, 15 Mar 2024 00:38:03 +0100 Subject: [PATCH 2/7] =?UTF-8?q?feat=20+=20fix:=20subscripci=C3=B3=20a=20as?= =?UTF-8?q?signatura=20a=20traves=20de=20cor=20en=20la=20p=C3=A0gina=20d'a?= =?UTF-8?q?ssignatura.=20A=20m=C3=A9s=20l'apartat=20"M=C3=A9s=20sobre..."?= =?UTF-8?q?=20ja=20no=20te=20l'opci=C3=B3=20de=20subscriure's.=20Tampoc=20?= =?UTF-8?q?t=C3=A9=20les=20de=20pujar=20apunts=20i=20llan=C3=A7ar=20pregun?= =?UTF-8?q?tes=20ja=20que=20no=20funcionaven=20i=20ja=20estan=20implementa?= =?UTF-8?q?des=20fora=20de=20l'apartat.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/[slug]/layout.tsx | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/src/app/[slug]/layout.tsx b/src/app/[slug]/layout.tsx index 8dc1e2b..5fcd8e1 100644 --- a/src/app/[slug]/layout.tsx +++ b/src/app/[slug]/layout.tsx @@ -1,4 +1,3 @@ -import SubscribeLeaveToggle from "@/components/SubscribeLeaveToggle" import { buttonVariants } from "@/components/ui/Button" import { getAuthSession } from "@/lib/auth" import { db } from "@/lib/db" @@ -8,7 +7,6 @@ import { FileTextIcon, InfoIcon, } from "lucide-react" -import Link from "next/link" import { notFound } from "next/navigation" import { Tooltip, @@ -16,6 +14,7 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" +import SubscribeHeartToggle from "@/components/SubscribeHeartToggle" const Layout = async ({ children, @@ -168,40 +167,20 @@ const Layout = async ({

) : null} - - {subject.creatorId !== session?.user?.id ? ( - - ) : null} - - - Penja Apunts - - - - Llança una pregunta -
+ {subject.creatorId !== session?.user?.id ? ( + + ) : null} +
{children}
From d11e47525dedfff37a0ca9cd10687cc9976da30d Mon Sep 17 00:00:00 2001 From: Pau Matas Date: Fri, 15 Mar 2024 00:43:02 +0100 Subject: [PATCH 3/7] cleanup --- src/app/page.tsx | 30 ------ src/components/CustomFeed.tsx | 45 --------- src/components/SubscribeLeaveToggle.tsx | 119 ------------------------ 3 files changed, 194 deletions(-) delete mode 100644 src/components/CustomFeed.tsx delete mode 100644 src/components/SubscribeLeaveToggle.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index fd152a0..eb2d0c3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,3 @@ -// import CustomFeed from "@/components/CustomFeed"; import { buttonVariants } from "@/components/ui/Button" import { getAuthSession } from "@/lib/auth" import { FileTextIcon, HelpCircleIcon, HomeIcon, BookIcon } from "lucide-react" @@ -90,35 +89,6 @@ export default async function Home() { <>

El teu espai

- {/* Feed - {session ? : null} */} - - {/* subjects info */} - {/*
-
-

- - Home -

-
- -
-
-

- La teva pàgina d Apunts de Dades. Accedeix aquí per a veure els apunts de les assignatures que - t interessen. -

-
- - - Crea una assignatura - -
-
*/}

diff --git a/src/components/CustomFeed.tsx b/src/components/CustomFeed.tsx deleted file mode 100644 index 08ff8a0..0000000 --- a/src/components/CustomFeed.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config" -import { getAuthSession } from "@/lib/auth" -import { db } from "@/lib/db" -import PostFeed from "./PostFeed" -import { notFound } from "next/navigation" - -const CustomFeed = async () => { - const session = await getAuthSession() - - // only rendered if session exists, so this will not happen - if (!session) return notFound() - - const followedCommunities = await db.subscription.findMany({ - where: { - userId: session.user.id, - }, - include: { - subject: true, - }, - }) - - const posts = await db.post.findMany({ - where: { - subject: { - name: { - in: followedCommunities.map((sub) => sub.subject.name), - }, - }, - }, - orderBy: { - createdAt: "desc", - }, - include: { - votes: true, - author: true, - comments: true, - subject: true, - }, - take: INFINITE_SCROLL_PAGINATION_RESULTS, - }) - - return -} - -export default CustomFeed diff --git a/src/components/SubscribeLeaveToggle.tsx b/src/components/SubscribeLeaveToggle.tsx deleted file mode 100644 index 7c5a76d..0000000 --- a/src/components/SubscribeLeaveToggle.tsx +++ /dev/null @@ -1,119 +0,0 @@ -"use client" - -import { FC, startTransition } from "react" -import { Button } from "./ui/Button" -import { SubscribeToSubjectPayload } from "@/lib/validators/subject" -import { useMutation } from "@tanstack/react-query" -import axios, { AxiosError } from "axios" -import { toast } from "@/hooks/use-toast" -import { useRouter } from "next/navigation" -import { useCustomToasts } from "@/hooks/use-custom-toasts" - -interface SubscribeLeaveToggleProps { - subjectId: string - subjectName: string - isSubscribed: boolean -} - -const SubscribeLeaveToggle: FC = ({ - subjectId, - subjectName, - isSubscribed, -}) => { - const { loginToast } = useCustomToasts() - const router = useRouter() - - const { mutate: subscribe, isLoading: isSubLoading } = useMutation({ - mutationFn: async () => { - const payload: SubscribeToSubjectPayload = { - subjectId, - } - const { data } = await axios.post("/api/subject/subscribe", payload) - return data as string - }, - onError: (err) => { - if (err instanceof AxiosError) { - if (err.response?.status === 401) { - return loginToast() - } - } - - return toast({ - title: "S'ha produït un error desconegut.", - description: - "No s'ha pogut subscriure a l'assignatura. Siusplau, torna a intentar-ho més tard.", - variant: "destructive", - }) - }, - onSuccess: ({}) => { - startTransition(() => { - router.refresh() - }) - - const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i - const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de " - - return toast({ - title: `T'has subscrit als apunts ${subjectArticle}${subjectName}!`, - description: "", - }) - }, - }) - - const { mutate: unsubscribe, isLoading: isUnsubLoading } = useMutation({ - mutationFn: async () => { - const payload: SubscribeToSubjectPayload = { - subjectId, - } - const { data } = await axios.post("/api/subject/unsubscribe", payload) - return data as string - }, - onError: (err) => { - if (err instanceof AxiosError) { - if (err.response?.status === 401) { - return loginToast() - } - } - - return toast({ - title: "S'ha produït un error desconegut.", - description: - "No s'ha pogut donar de baixa la subscripció a l'assignatura. Siusplau, torna a intentar-ho més tard.", - variant: "destructive", - }) - }, - onSuccess: ({}) => { - startTransition(() => { - router.refresh() - }) - - const startsWithVowel = /^[aeiouàáâãäåæçèéêëìíîïðòóôõöøùúûüýÿ]/i - const subjectArticle = subjectName.match(startsWithVowel) ? "d'" : "de " - - return toast({ - title: `Has donat de baixa la teva subscripció als apunts ${subjectArticle}${subjectName}!`, - description: "", - }) - }, - }) - - return isSubscribed ? ( - - ) : ( - - ) -} - -export default SubscribeLeaveToggle From 5a7c679753210aff1e55c0db5c4e0eff24fd7c4a Mon Sep 17 00:00:00 2001 From: Pau Matas Date: Fri, 15 Mar 2024 14:31:08 +0100 Subject: [PATCH 4/7] feat: refresh web when a comment or answer is published During the fix I have also restyled a little bit the Editor interface so the title can use the placeholder instead of having placed text. --- src/app/[slug]/page.tsx | 6 +- src/app/api/subject/answer/create/route.ts | 2 +- src/components/Editor.tsx | 104 +++++++++++++-------- src/components/MiniCreateAnswer.tsx | 25 ++--- src/components/MiniCreateComment.tsx | 14 ++- src/components/MiniCreateQuestion.tsx | 21 +---- src/lib/validators/question.ts | 4 +- 7 files changed, 94 insertions(+), 82 deletions(-) diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index e1d174a..8b664e4 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -23,7 +23,11 @@ const page = async ({ params }: PageProps) => { include: { author: true, votes: true, - comments: true, + comments: { + include: { + _count: true, + }, + }, subject: true, }, orderBy: { diff --git a/src/app/api/subject/answer/create/route.ts b/src/app/api/subject/answer/create/route.ts index c58ba95..b3c0b56 100644 --- a/src/app/api/subject/answer/create/route.ts +++ b/src/app/api/subject/answer/create/route.ts @@ -23,7 +23,7 @@ export async function POST(req: Request) { authorId: session.user.id, }, }) - return new Response("Answer created", { status: 201 }) + return new Response(JSON.stringify(questionId), { status: 201 }) } catch (error) { if (error instanceof z.ZodError) { return new Response(error.message, { status: 422 }) diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index c5a0a89..b0b648a 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,6 +1,13 @@ "use client" -import { FC, useCallback, useEffect, useRef, useState } from "react" +import { + FC, + startTransition, + useCallback, + useEffect, + useRef, + useState, +} from "react" import TextareaAutosize from "react-textarea-autosize" import { useForm } from "react-hook-form" import { @@ -15,27 +22,34 @@ import { toast } from "@/hooks/use-toast" import { useMutation } from "@tanstack/react-query" import axios from "axios" import { usePathname, useRouter } from "next/navigation" +import { Button } from "./ui/Button" + +type FormData = QuestionCreationRequest | AnswerCreationRequest interface EditorProps { subjectId: string contentType: "question" | "answer" questionId?: string + formId: string } -const Editor: FC = ({ subjectId, contentType, questionId }) => { +const Editor: FC = ({ + subjectId, + contentType, + questionId, + formId, +}) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ + } = useForm({ resolver: zodResolver( contentType === "question" ? QuestionValidator : AnswerValidator, ), defaultValues: { subjectId, - title: `${ - contentType === "question" ? "Pregunta" : "Resposta" - } ${new Date().toLocaleDateString()}`, + title: "", content: null, questionId: questionId || "", // Add questionId as a value in the form }, @@ -61,6 +75,7 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => { if (!ref.current) { const editor = new EditorJS({ holder: "editor", + minHeight: 200, onReady() { ref.current = editor }, @@ -124,13 +139,9 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => { } }, [isMounted, initializeEditor]) - const { mutate: createContent } = useMutation({ - mutationFn: async ({ - title, - content, - subjectId, - }: QuestionCreationRequest | AnswerCreationRequest) => { - const payload: QuestionCreationRequest | AnswerCreationRequest = { + const { mutate: createContent, isLoading } = useMutation({ + mutationFn: async ({ title, content, subjectId }: FormData) => { + const payload: FormData = { title, content, subjectId, @@ -158,22 +169,26 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => { const questionId = data as string const newPathname = pathname.replace("/q", `/q/${questionId}`) router.push(newPathname) + router.refresh() + } else { + ref.current?.clear() + _titleRef.current!.value = "" + window.location.reload() } - router.refresh() return toast({ description: `La teva ${ - contentType === "question" ? "pregunta" : "reposta" + contentType === "question" ? "pregunta" : "resposta" } s'ha creat correctament`, }) }, }) - async function onSubmit() { + async function onSubmit(data: FormData) { const blocks = await ref.current?.save() - const title = (await _titleRef.current?.value) as string - const payload: QuestionCreationRequest | AnswerCreationRequest = { - title: title, + + const payload: FormData = { + title: data.title, content: blocks, subjectId: subjectId, ...(contentType === "answer" && { questionId: questionId }), @@ -187,25 +202,38 @@ const Editor: FC = ({ subjectId, contentType, questionId }) => { const { ref: titleRef, ...rest } = register("title") return ( -

-
-
- { - titleRef(e) - // @ts-ignore - _titleRef.current = e - }} - placeholder="Títol" - className="w-full resize-none appearance-none overflow-hidden bg-transparent text-xl font-bold focus:outline-none h-12" - /> -
-
-
+
+
+
+
+ { + titleRef(e) + // @ts-ignore + _titleRef.current = e + }} + {...rest} + placeholder="Títol" + className="w-full resize-none appearance-none overflow-hidden bg-transparent text-xl font-bold focus:outline-none h-12" + /> +
+
+
+
+
+ +
) } diff --git a/src/components/MiniCreateAnswer.tsx b/src/components/MiniCreateAnswer.tsx index 190e25b..30c6d7f 100644 --- a/src/components/MiniCreateAnswer.tsx +++ b/src/components/MiniCreateAnswer.tsx @@ -30,25 +30,12 @@ const MiniCreateAnswer: FC = ({
{/* form */} -
-
- -
-
-
- -
- +
diff --git a/src/components/MiniCreateComment.tsx b/src/components/MiniCreateComment.tsx index 524a1a3..c9a331d 100644 --- a/src/components/MiniCreateComment.tsx +++ b/src/components/MiniCreateComment.tsx @@ -17,7 +17,7 @@ const MiniCreateComment: FC = ({ session, postId }) => { const [content, setContent] = useState("") // Define the mutation function using useMutation hook - const { mutate: createComment } = useMutation({ + const { mutate: createComment, isLoading } = useMutation({ mutationFn: async () => { const { data } = await axios.post("/api/subject/comment/create", { content: content, @@ -26,14 +26,18 @@ const MiniCreateComment: FC = ({ session, postId }) => { return data }, onSuccess: ({}) => { - // Handle success and show toast toast({ description: `Comment created successfully`, }) - // You can add any additional handling specific to your needs here + setContent("") + window.location.reload() }, onError: ({}) => { - // Handle error if needed + toast({ + title: "Something went wrong", + description: `The comment could not be created. Please try again later.`, + variant: "destructive", + }) }, }) @@ -72,8 +76,8 @@ const MiniCreateComment: FC = ({ session, postId }) => { diff --git a/src/components/MiniCreateQuestion.tsx b/src/components/MiniCreateQuestion.tsx index 16a82a9..606b6f8 100644 --- a/src/components/MiniCreateQuestion.tsx +++ b/src/components/MiniCreateQuestion.tsx @@ -1,7 +1,6 @@ "use client" import { Session } from "next-auth" -import { Button } from "@/components/ui/Button" import { FC } from "react" import UserAvatar from "./UserAvatar" import Editor from "@/components/Editor" @@ -28,21 +27,11 @@ const MiniCreateQuestion: FC = ({
{/* form */} -
-
- -
-
- - -
- +
diff --git a/src/lib/validators/question.ts b/src/lib/validators/question.ts index 835df5b..5cba920 100644 --- a/src/lib/validators/question.ts +++ b/src/lib/validators/question.ts @@ -11,8 +11,8 @@ export const QuestionValidator = z.object({ export const AnswerValidator = z.object({ title: z .string() - .min(3, { message: "Content must be at least 3 characters long" }) - .max(128, { message: "Content must be at most 2048 characters long" }), + .min(3, { message: "Title must be at least 3 characters long" }) + .max(128, { message: "Title must be at most 2048 characters long" }), subjectId: z.string(), content: z.any(), questionId: z.string(), From 5402c4d7ff2f04fa083d84906f35e22c734d0cf7 Mon Sep 17 00:00:00 2001 From: Pau Matas Date: Fri, 15 Mar 2024 14:35:42 +0100 Subject: [PATCH 5/7] refactor: https://open.spotify.com/track/3e3K2dVdbOZRdJttaCsLNh?si=992d85092fde4803 --- src/app/[slug]/page.tsx | 4 ++-- src/app/[slug]/q/page.tsx | 4 ++-- .../{MiniCreateAnswer.tsx => CreateAnswer.tsx} | 10 +++------- .../{MiniCreateComment.tsx => CreateComment.tsx} | 6 +++--- src/components/{MiniCreatePost.tsx => CreatePost.tsx} | 6 +++--- .../{MiniCreateQuestion.tsx => CreateQuestion.tsx} | 9 +++------ src/components/PostView.tsx | 4 ++-- src/components/QuestionView.tsx | 4 ++-- 8 files changed, 20 insertions(+), 27 deletions(-) rename src/components/{MiniCreateAnswer.tsx => CreateAnswer.tsx} (87%) rename src/components/{MiniCreateComment.tsx => CreateComment.tsx} (94%) rename src/components/{MiniCreatePost.tsx => CreatePost.tsx} (90%) rename src/components/{MiniCreateQuestion.tsx => CreateQuestion.tsx} (86%) diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index 8b664e4..4a49ab6 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -1,4 +1,4 @@ -import MiniCreatePost from "@/components/MiniCreatePost" +import CreatePost from "@/components/CreatePost" import PostFeed from "@/components/PostFeed" import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config" import { getAuthSession } from "@/lib/auth" @@ -48,7 +48,7 @@ const page = async ({ params }: PageProps) => { {subject.name} - + diff --git a/src/app/[slug]/q/page.tsx b/src/app/[slug]/q/page.tsx index e3cfb9a..16620e3 100644 --- a/src/app/[slug]/q/page.tsx +++ b/src/app/[slug]/q/page.tsx @@ -1,7 +1,7 @@ import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config" import { getAuthSession } from "@/lib/auth" import { db } from "@/lib/db" -import MiniCreateQuestion from "@/components/MiniCreateQuestion" +import CreateQuestion from "@/components/CreateQuestion" import QuestionFeed from "@/components/QuestionFeed" import { notFound } from "next/navigation" @@ -41,7 +41,7 @@ const page = async ({ params }: PageProps) => { {subject.name} - Preguntes - + = ({ - session, - subjectId, - questionId, -}) => { +const CreateAnswer: FC = ({ session, subjectId, questionId }) => { return (
@@ -42,4 +38,4 @@ const MiniCreateAnswer: FC = ({ ) } -export default MiniCreateAnswer +export default CreateAnswer diff --git a/src/components/MiniCreateComment.tsx b/src/components/CreateComment.tsx similarity index 94% rename from src/components/MiniCreateComment.tsx rename to src/components/CreateComment.tsx index c9a331d..73f55af 100644 --- a/src/components/MiniCreateComment.tsx +++ b/src/components/CreateComment.tsx @@ -8,12 +8,12 @@ import axios from "axios" import { useMutation } from "@tanstack/react-query" import { toast } from "@/hooks/use-toast" -interface MiniCreateComment { +interface CreateComment { session: Session | null postId: string } -const MiniCreateComment: FC = ({ session, postId }) => { +const CreateComment: FC = ({ session, postId }) => { const [content, setContent] = useState("") // Define the mutation function using useMutation hook @@ -87,4 +87,4 @@ const MiniCreateComment: FC = ({ session, postId }) => { ) } -export default MiniCreateComment +export default CreateComment diff --git a/src/components/MiniCreatePost.tsx b/src/components/CreatePost.tsx similarity index 90% rename from src/components/MiniCreatePost.tsx rename to src/components/CreatePost.tsx index cd9462d..63b8091 100644 --- a/src/components/MiniCreatePost.tsx +++ b/src/components/CreatePost.tsx @@ -7,11 +7,11 @@ import UserAvatar from "./UserAvatar" import Link from "next/link" import { buttonVariants } from "@/components/ui/Button" -interface MiniCreatePostProps { +interface CreatePostProps { session: Session | null } -const MiniCreatePost: FC = ({ session }) => { +const CreatePost: FC = ({ session }) => { const router = useRouter() const pathname = usePathname() @@ -51,4 +51,4 @@ const MiniCreatePost: FC = ({ session }) => { ) } -export default MiniCreatePost +export default CreatePost diff --git a/src/components/MiniCreateQuestion.tsx b/src/components/CreateQuestion.tsx similarity index 86% rename from src/components/MiniCreateQuestion.tsx rename to src/components/CreateQuestion.tsx index 606b6f8..efd583b 100644 --- a/src/components/MiniCreateQuestion.tsx +++ b/src/components/CreateQuestion.tsx @@ -5,14 +5,11 @@ import { FC } from "react" import UserAvatar from "./UserAvatar" import Editor from "@/components/Editor" -interface MiniCreateQuestionProps { +interface CreateQuestionProps { session: Session | null subjectId: string } -const MiniCreateQuestion: FC = ({ - session, - subjectId, -}) => { +const CreateQuestion: FC = ({ session, subjectId }) => { return (
@@ -38,4 +35,4 @@ const MiniCreateQuestion: FC = ({ ) } -export default MiniCreateQuestion +export default CreateQuestion diff --git a/src/components/PostView.tsx b/src/components/PostView.tsx index 0755cad..9da30e5 100644 --- a/src/components/PostView.tsx +++ b/src/components/PostView.tsx @@ -2,7 +2,7 @@ import { FC } from "react" import { ExtendedPost, ExtendedComment } from "@/types/db" import { useSession } from "next-auth/react" -import MiniCreateComment from "@/components/MiniCreateComment" +import CreateComment from "@/components/CreateComment" import CommentFeed from "@/components/CommentFeed" import Post from "@/components/Post" @@ -29,7 +29,7 @@ export const PostView: FC = ({ post, comments }) => { />
- +
= ({ question, answers }) => { />
- Date: Sat, 16 Mar 2024 20:40:29 +0100 Subject: [PATCH 6/7] Afegir selector any al pujar apunts (#71) * Afegir selector any al pujar apunts * Afegir set year * https://www.youtube.com/watch?v=My2FRPA3Gf8 * BREW UNINSTALL HARDCODED --- src/app/[slug]/submit/page.tsx | 10 ++++- src/app/api/subject/post/create/route.ts | 10 +---- src/app/submit/page.tsx | 9 +++- src/components/Form.tsx | 55 ++++++++++++++++++++++++ src/config.ts | 1 + src/lib/validators/post.ts | 1 + 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/app/[slug]/submit/page.tsx b/src/app/[slug]/submit/page.tsx index dbf59c4..03d297b 100644 --- a/src/app/[slug]/submit/page.tsx +++ b/src/app/[slug]/submit/page.tsx @@ -2,6 +2,7 @@ import { ProfileForm } from "@/components/Form" import { db } from "@/lib/db" import { getAuthSession } from "@/lib/auth" import { notFound } from "next/navigation" +import { GCED_START } from "@/config" interface PageProps { params: { @@ -31,7 +32,14 @@ const page = async ({ params }: PageProps) => { Penja apunts {subjectNameArticle} {subject.name} - + ) } diff --git a/src/app/api/subject/post/create/route.ts b/src/app/api/subject/post/create/route.ts index fffec0b..ab5350d 100644 --- a/src/app/api/subject/post/create/route.ts +++ b/src/app/api/subject/post/create/route.ts @@ -14,7 +14,7 @@ export async function POST(req: Request) { const body = await req.json() - const { pdf, title, assignatura, tipus, anonim, authorEmail } = + const { pdf, title, year, assignatura, tipus, anonim, authorEmail } = ApuntsPostValidator.parse(body) const subject = await db.subject.findFirst({ @@ -27,14 +27,6 @@ export async function POST(req: Request) { return new Response("Subject not found", { status: 404 }) } - const semester = subject.semester - const semesterNumber = semester[0] === "Q" ? parseInt(semester[1]) : 8 - if (typeof session.user.generacio !== "number") { - return new Response("Invalid generacio", { status: 409 }) - } - const year: number = - session.user.generacio + Math.floor((semesterNumber - 1) / 2) - if ( !["apunts", "examens", "exercicis", "diapositives", "altres"].includes( tipus, diff --git a/src/app/submit/page.tsx b/src/app/submit/page.tsx index 4100ad5..b3b0893 100644 --- a/src/app/submit/page.tsx +++ b/src/app/submit/page.tsx @@ -1,12 +1,19 @@ import { getAuthSession } from "@/lib/auth" import { ProfileForm } from "@/components/Form" import { notFound } from "next/navigation" +import { GCED_START } from "@/config" const page = async ({}) => { const session = await getAuthSession() const isAdmin = session?.user?.isAdmin if (isAdmin === undefined) return notFound() - return + return ( + + ) } export default page diff --git a/src/components/Form.tsx b/src/components/Form.tsx index d6c89de..298c911 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -22,12 +22,16 @@ import { Combobox } from "@/components/Combobox" import { Checkbox } from "@/components/ui/checkbox" import { ApuntsPostCreationRequest } from "@/lib/validators/post" import { uploadFiles } from "@/lib/uploadthing" +import { GCED_START } from "@/config" const formSchema = z.object({ pdf: z.any(), title: z.string({ required_error: "Selecciona un títol", }), + year: z.string({ + required_error: "Selecciona un any", + }), assignatura: z.string({ required_error: "Selecciona una assignatura.", }), @@ -43,9 +47,13 @@ const formSchema = z.object({ export function ProfileForm({ PreselectedSubject, isAdmin, + generacio, + semester, }: { PreselectedSubject: string isAdmin: boolean + generacio: number + semester?: number }) { const router = useRouter() @@ -53,6 +61,7 @@ export function ProfileForm({ mutationFn: async ({ pdf, title, + year, assignatura, tipus, anonim, @@ -61,6 +70,7 @@ export function ProfileForm({ const payload: ApuntsPostCreationRequest = { pdf, title, + year, assignatura, tipus, anonim, @@ -115,6 +125,7 @@ export function ProfileForm({ const payload: ApuntsPostCreationRequest = { pdf: res.fileUrl, title: data.title, + year: Number(data.year), assignatura: data.assignatura, tipus: data.tipus, anonim: data.anonim, @@ -264,6 +275,24 @@ export function ProfileForm({ label: "Altres", }, ] + + const date = new Date() + const end = date.getFullYear() + interface Year { + value: string + label: string + } + let tipus_any: Year[] = [] + for (let i = generacio; i < end; i++) { + tipus_any.push({ + value: i.toString(), + label: i.toString(), + }) + } + const default_year = semester + ? (generacio + Math.floor((semester - 1) / 2)).toString() + : undefined + // ------------------------------ return (
@@ -271,6 +300,32 @@ export function ProfileForm({ onSubmit={form.handleSubmit(onSubmit as SubmitHandler)} className="space-y-8" > + { + if (!field.value && default_year) { + field.value = default_year + field.onChange(default_year) + } + + return ( + + Any + + + + + Any que has cursat l'assignatura. + + + ) + }} + /> Date: Sat, 16 Mar 2024 21:05:48 +0100 Subject: [PATCH 7/7] feature: confeti al pujar apunts jajajaj --- package.json | 1 + src/components/Form.tsx | 6 +++++- yarn.lock | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ab47367..bbcbd73 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "papaparse": "^5.4.1", "postcss": "8.4.23", "react": "^18.2.0", + "react-canvas-confetti": "^2.0.7", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.50.1", diff --git a/src/components/Form.tsx b/src/components/Form.tsx index 298c911..3810275 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -6,7 +6,7 @@ import { useMutation } from "@tanstack/react-query" import axios from "axios" import { useRouter } from "next/navigation" import { toast } from "@/hooks/use-toast" -import { useEffect } from "react" +import { useEffect, useState } from "react" import { Button } from "@/components/ui/Button" import { Form, @@ -23,6 +23,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { ApuntsPostCreationRequest } from "@/lib/validators/post" import { uploadFiles } from "@/lib/uploadthing" import { GCED_START } from "@/config" +import Fireworks from "react-canvas-confetti/dist/presets/fireworks" const formSchema = z.object({ pdf: z.any(), @@ -56,6 +57,7 @@ export function ProfileForm({ semester?: number }) { const router = useRouter() + const [isVisible, setIsVisible] = useState(false) const { mutate: createApuntsPost } = useMutation({ mutationFn: async ({ @@ -99,6 +101,7 @@ export function ProfileForm({ } }, onSuccess: (subjectAcronym) => { + setIsVisible(true) router.push(`/${subjectAcronym}`) router.refresh() @@ -446,6 +449,7 @@ export function ProfileForm({ + {isVisible && } ) diff --git a/yarn.lock b/yarn.lock index ef89138..368e1b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1109,6 +1109,11 @@ "@tanstack/query-core" "4.29.11" use-sync-external-store "^1.2.0" +"@types/canvas-confetti@^1.6.4": + version "1.6.4" + resolved "https://registry.yarnpkg.com/@types/canvas-confetti/-/canvas-confetti-1.6.4.tgz#620fd8d78b335d6a4046c0f73236d6988b37552a" + integrity sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA== + "@types/editorjs__header@^2.6.0": version "2.6.0" resolved "https://registry.npmjs.org/@types/editorjs__header/-/editorjs__header-2.6.0.tgz" @@ -1656,6 +1661,11 @@ caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001489, caniuse-lite@^1.0.300015 resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz" integrity sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q== +canvas-confetti@^1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.9.2.tgz#c9f74098c7fdf66dd9d1aab5d381061cf74e48f0" + integrity sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg== + canvas@^2.11.2: version "2.11.2" resolved "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz" @@ -4183,6 +4193,14 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-canvas-confetti@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/react-canvas-confetti/-/react-canvas-confetti-2.0.7.tgz#0499e7815b50f6a7731fd01729e07c30faa300f3" + integrity sha512-DIj44O35TPAwJkUSIZqWdVsgAMHtVf8h7YNmnr3jF3bn5mG+d7Rh9gEcRmdJfYgRzh6K+MAGujwUoIqQyLnMJw== + dependencies: + "@types/canvas-confetti" "^1.6.4" + canvas-confetti "^1.9.2" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"