diff --git a/src/components/profile/SkillManagement.tsx b/src/components/profile/SkillManagement.tsx index 633791470d2b9e364fcfbbef015ab95e142cca22..b44d3e6580b72a4764f6f468c7c73abfead60590 100644 --- a/src/components/profile/SkillManagement.tsx +++ b/src/components/profile/SkillManagement.tsx @@ -1,22 +1,23 @@ -import { deleteUserSkill } from "@/utils/api/skill"; +import { deleteUserSkill, addUserSkill } from "@/utils/api/skill"; import { useUser } from "@/utils/context/AuthProvider"; -import { useUserSkills } from "@/utils/context/SkillProvider"; +import { useAllSkills, useUserSkills } from "@/utils/context/SkillProvider"; import { Skill } from "@/utils/validationSchema/skill"; -import { log } from "console"; import React, { useEffect, useState } from "react"; import { useMutation, useQueryClient } from "react-query"; -function SkillBit({ +function UserSkillBit({ onDelete, skillName, + onClick, }: { skillName: string; onDelete: () => void; + onClick: () => void; }) { return ( <div className="flex gap-2 bg-primary text-white px-4 py-2 rounded-md btn btn-neutral" - onClick={() => {}} + onClick={onClick} > <p>{skillName}</p> <button @@ -32,10 +33,59 @@ function SkillBit({ ); } +function AllSkillBit({ + skillName, + onClick, + onAdd, + alreadyOwned, +}: { + skillName: string; + onAdd: () => void; + onClick: () => void; + alreadyOwned: boolean; +}) { + return ( + <div + className="flex w-full bg-primary normal-case text-white px-4 py-2 rounded-md btn btn-neutral" + onClick={() => { + if (!alreadyOwned) { + onClick(); + } + }} + > + <p>{skillName}</p> + <div className="flex-grow"></div> + <button + className="px-2 py-0 bg-transparent" + onClick={(e) => { + e.stopPropagation(); + onAdd(); + }} + > + + + </button> + </div> + ); +} + +function allSkillsProcessor( + allSkills: Skill[], + userSkills: Skill[] +): (Skill & { alreadyOwned: boolean })[] { + return allSkills.map((skill) => { + const alreadyOwned = userSkills.some( + ({ skillName }) => skillName === skill.skillName + ); + return { ...skill, alreadyOwned }; + }); +} + function SkillManagement() { const queryClient = useQueryClient(); const { user, status } = useUser(); const { userSkills, userSkillsStatus } = useUserSkills(); + const { allSkills, allSkillsStatus } = useAllSkills(); + const [selectedSkill, setSelectedSkill] = useState<Skill | undefined>( undefined ); @@ -45,7 +95,7 @@ function SkillManagement() { return () => {}; }, [userSkills]); - const { mutateAsync: removeUserSkill } = useMutation({ + const { mutateAsync: removeUserSkillMutation } = useMutation({ mutationFn: async ({ description, skillName }: Skill) => { if (status !== "success" || user === undefined) { return; @@ -83,16 +133,52 @@ function SkillManagement() { }, }); + const { mutateAsync: addUserSkillMutation } = useMutation({ + mutationFn: async (skill: Skill) => { + if (status !== "success" || user === undefined) { + return; + } + return await addUserSkill({ + username: user.username, + ...skill, + }); + }, + onMutate: async (addedSkill: Skill) => { + await queryClient.cancelQueries({ queryKey: ["skills", "me"] }); + + const previousUserSkill = userSkills; + + queryClient.setQueryData<Skill[] | undefined>( + ["skills", "me"], + (oldSkills) => { + console.log("Old skills", oldSkills); + if (!oldSkills) { + return oldSkills; + } + return [addedSkill, ...(previousUserSkill ?? [])]; + } + ); + return { previousUserSkill }; + }, + onError(_, __, context) { + queryClient.setQueryData(["skills", "me"], context?.previousUserSkill); + }, + onSuccess() { + queryClient.invalidateQueries(["skills", "me"]); + }, + }); + return ( - <div className="grid grid-cols-2 grid-rows-2 w-full"> + <div className="flex flex-col lg:grid grid-cols-2 grid-rows-2 w-full gap-4 lg:gap-6"> <div className="w-full flex flex-col items-center row-span-1 col-span-1 row-start-1 col-start-1"> {userSkillsStatus === "success" && userSkills ? ( <div className="flex flex-row flex-wrap gap-2"> {userSkills.map((skill) => ( - <SkillBit + <UserSkillBit + onClick={() => setSelectedSkill(skill)} skillName={skill.skillName} key={skill.skillName} - onDelete={() => removeUserSkill(skill)} + onDelete={() => removeUserSkillMutation(skill)} /> ))} </div> @@ -100,8 +186,27 @@ function SkillManagement() { "Loading" )} </div> - <section className="w-full flex flex-col items-center lg:items-start row-span-1 row-start-2 col-span-1"> + <section className="w-full flex flex-col items-center lg:items-start row-span-1 row-start-2 col-span-1 gap-2"> <h3 className="text-xl font-bold self-start">Choose New Skills</h3> + {allSkillsStatus !== "success" && + userSkillsStatus !== "success" && + allSkills && + userSkills ? ( + <p>Loading</p> + ) : ( + <div className="w-full min-h-[18.75rem] rounded-lg overflow-y-scroll"> + {allSkillsProcessor(allSkills ?? [], userSkills ?? []).map( + ({ alreadyOwned, description, skillName }) => ( + <AllSkillBit + alreadyOwned={alreadyOwned} + onAdd={() => addUserSkillMutation({ skillName, description })} + onClick={() => setSelectedSkill({ description, skillName })} + skillName={skillName} + /> + ) + )} + </div> + )} </section> <section className="bg-slate-600 box-border p-4 rounded-xl gap-4 w-full flex flex-col items-start row-span-2 row-start-1 col-start-2 col-span-1 h-full"> {selectedSkill ? ( diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index d0d045b45498af938c1331e0062a32b73d3bcfd0..22015ae03f0b1b0e637fb46e3db967590eb6cb1b 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -26,11 +26,11 @@ function Profile() { <h1 className="text-3xl font-bold self-start">Profile</h1> {user === undefined ? <p>Loading...</p> : <ProfileForm user={user} />} </section> - <section className="w-full flex flex-col items-start lg:items-center"> + <section className="w-full flex flex-col items-start lg:items-center"> <h2 className="text-2xl font-bold self-start">Skills</h2> <div className="w-full flex flex-col items-start"> <SkillManagement /> - <section className="w-full flex max-w-xl flex-col items-center"> + <section className="w-full flex max-w-xl flex-col items-center mt-4"> <h3 className="text-xl font-bold self-start">Add New Skills</h3> <AddSkillForm /> </section> diff --git a/src/utils/api/skill.ts b/src/utils/api/skill.ts index 9f36f04189830c68f3cf579c54f08bf6c0c01278..060882de47b7a775287fe20a2431e7b921a51c31 100644 --- a/src/utils/api/skill.ts +++ b/src/utils/api/skill.ts @@ -17,7 +17,6 @@ export async function getUserSkills({ }: { username: string; }): Promise<Skill[]> { - console.log("Getting user skills"); return ( (await axios.get(`${config.NODE_JS_API}/api/skill/${username}`, header)) .data as SkillReturned[] @@ -26,6 +25,21 @@ export async function getUserSkills({ }); } +export async function addUserSkill({ + username, + skillName, +}: { + username: string; +} & Skill): Promise<Skill> { + return ( + await axios.post( + `${config.NODE_JS_API}/api/skill/${username}`, + { skillName }, + header + ) + ).data; +} + export async function createSkill(payload: Skill): Promise<Skill> { return (await axios.post(`${config.NODE_JS_API}/api/skill/`, payload, header)) .data;