Skip to content

Commit

Permalink
Merge pull request #555 from aowjarkwk/part3-윤대호-week19
Browse files Browse the repository at this point in the history
[윤대호] week19
  • Loading branch information
devym-37 authored Jan 14, 2024
2 parents 5b9550c + e9ccd60 commit bd33e85
Show file tree
Hide file tree
Showing 32 changed files with 718 additions and 413 deletions.
19 changes: 12 additions & 7 deletions components/card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import * as S from "@/components/card/Card.style";
import KebabMenu from "@/components/kebabMenu/KebabMenu";
import kebabImageSrc from "@/images/kebab.png";
import { Folder, MappedLink } from "@/types/type";
import { Folder, Link as LinkProp } from "@/types/type";
import getElapsedTime from "@/utils/getElapsedTime";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useRef, useState } from "react";

interface CardProps {
link: MappedLink;
link: LinkProp;
folders?: Folder[];
isShared: boolean;
folderId: string;
}

const Card = ({ link, folders, isShared }: CardProps) => {
const { elapsedTime, createdAt, url, title, imageSource } = link;
const Card = ({ link, folders, isShared, folderId }: CardProps) => {
const { created_at, url, title, image_source: imageSource } = link;

const { fromNow, formattedDate } = getElapsedTime(created_at);

const [isKebabOpen, setIsKebabOpen] = useState(false);
const kebabButtonRef = useRef<HTMLButtonElement>(null);
Expand Down Expand Up @@ -41,15 +45,16 @@ const Card = ({ link, folders, isShared }: CardProps) => {
</S.StarIconButton>
</S.CardImageWrap>
<S.CardTextWrap>
<S.TimeDiff>{elapsedTime}</S.TimeDiff>
<S.TimeDiff>{fromNow}</S.TimeDiff>
{!isShared && (
<>
<S.KebabButton onClick={() => setIsKebabOpen((prev) => !prev)} ref={kebabButtonRef}>
<Image src={kebabImageSrc} alt="메뉴 열기" />
</S.KebabButton>
<KebabMenu
linkUrl={url}
folderId={link.id}
folderId={folderId}
linkId={link.id}
isKebabOpen={isKebabOpen}
setIsKebabOpen={setIsKebabOpen}
folders={folders}
Expand All @@ -60,7 +65,7 @@ const Card = ({ link, folders, isShared }: CardProps) => {
<S.CardTitle href={url} target="_blank">
{title ?? "제목 없는 링크"}
</S.CardTitle>
<S.CardCreatedDate>{createdAt}</S.CardCreatedDate>
<S.CardCreatedDate>{formattedDate}</S.CardCreatedDate>
</S.CardTextWrap>
</S.CardWrap>
);
Expand Down
9 changes: 5 additions & 4 deletions components/cardList/CardList.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import Card from "@/components/card/Card";
import * as S from "@/components/cardList/CardList.style";
import { Folder, MappedLink } from "@/types/type";
import { Folder, Link } from "@/types/type";

interface CardListProps {
links: MappedLink[];
links: Link[];
folders?: Folder[];
isShared: boolean;
folderId: string;
}

const CardList = ({ links, folders, isShared }: CardListProps) => {
const CardList = ({ links, folders, isShared, folderId }: CardListProps) => {
if (!links?.length) return <S.EmptyListMessage>저장된 링크가 없습니다.</S.EmptyListMessage>;
return (
<S.CardList>
{links?.map((link) => (
<Card folders={folders} link={link} key={link.id} isShared={isShared} />
<Card folders={folders} link={link} key={link.id} isShared={isShared} folderId={folderId} />
))}
</S.CardList>
);
Expand Down
45 changes: 41 additions & 4 deletions components/folderAddBar/FolderAddBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import ModalSelectButton from "@/components/modalSelectButton/ModalSelectButton"
import { MODALS_ID } from "@/constants/constants";
import linkIcon from "@/images/link.png";
import { Folder } from "@/types/type";
import { postLinks } from "@/utils/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import Image from "next/image";
import { ChangeEvent, FormEvent, useState } from "react";

Expand All @@ -13,9 +15,8 @@ interface FolderAddBarProps {
isHidden: boolean;
}

// [ ]: 링크 개수 받아오기

const FolderAddBar = ({ folders, isSticky, isHidden }: FolderAddBarProps) => {
const queryClient = useQueryClient();
const [addLinkValue, setAddLinkValue] = useState("");
const [modalComponent, setModalComponent] = useState("");
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => setAddLinkValue(e.target.value);
Expand All @@ -25,6 +26,38 @@ const FolderAddBar = ({ folders, isSticky, isHidden }: FolderAddBarProps) => {
setModalComponent(MODALS_ID.addLinkToFolder);
};

const [selectedFolders, setSelectedFolders] = useState<{ [key: number]: boolean }>({});

const handleSelectFolder = (folderId: number) => {
setSelectedFolders((prevSelectedFolders) => {
const newSelectedFolders = { ...prevSelectedFolders };
if (newSelectedFolders[folderId]) {
delete newSelectedFolders[folderId];
} else {
newSelectedFolders[folderId] = true;
}
return newSelectedFolders;
});
};
const addLink = () => {
createFolderMutation.mutate({ addLinkValue, selectedFolderIds: Object.keys(selectedFolders).map(String) });
};
const createFolderMutation = useMutation({
mutationFn: ({ addLinkValue, selectedFolderIds }: { addLinkValue: string; selectedFolderIds: string[] }) =>
postLinks(addLinkValue, selectedFolderIds),
onSuccess: () => {
Object.keys(selectedFolders)
.map(String)
.forEach((folderId) => {
queryClient.invalidateQueries({ queryKey: ["links", folderId] });
queryClient.invalidateQueries({ queryKey: ["folders"] });
});
setModalComponent("");
},
onError: (error) => {
console.error(error);
},
});
return (
<>
<S.AddLinkBar $isSticky={isSticky} $isHidden={isHidden} aria-hidden={isHidden}>
Expand All @@ -41,11 +74,15 @@ const FolderAddBar = ({ folders, isSticky, isHidden }: FolderAddBarProps) => {
<Modal.SelectButtonWrap>
{folders?.map((folder) => (
<li key={folder.id}>
<ModalSelectButton folderName={folder.name} linkCount={1} />
<ModalSelectButton
folderName={folder.name}
linkCount={folder.link_count}
onClick={() => handleSelectFolder(folder.id)}
/>
</li>
))}
</Modal.SelectButtonWrap>
<Modal.BlueButton>추가하기</Modal.BlueButton>
<Modal.BlueButton handleClick={addLink}>추가하기</Modal.BlueButton>
</Modal>
)}
</>
Expand Down
27 changes: 23 additions & 4 deletions components/folderTabBar/FolderTabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import InputField from "@/components/inputField/InputField";
import Modal from "@/components/modal/Modal";
import { ALL_LINKS_ID, MODALS_ID } from "@/constants/constants";
import { Folder } from "@/types/type";
import { postFolder } from "@/utils/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";

interface FolderTabBarProps {
Expand All @@ -12,9 +14,26 @@ interface FolderTabBarProps {

const FolderTabBar = ({ folders, selectedFolderId }: FolderTabBarProps) => {
const [modalComponent, setModalComponent] = useState("");
const [folderName, setFolderName] = useState("");
const queryClient = useQueryClient();

const handleFolderAdd = () => {
setModalComponent(MODALS_ID.addFolder);
};
const createFolder = () => {
createFolderMutation.mutate(folderName);
};
const createFolderMutation = useMutation({
mutationFn: (folderName: string) => postFolder(folderName),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["folders"] });
setModalComponent("");
setFolderName("");
},
onError: (error) => {
console.error(error);
},
});

return (
<>
Expand All @@ -25,8 +44,8 @@ const FolderTabBar = ({ folders, selectedFolderId }: FolderTabBarProps) => {
전체
</S.FolderTabLink>
</li>
{folders.map((folder, i) => (
<li key={folder.id + i}>
{folders?.map((folder) => (
<li key={folder.id}>
<S.FolderTabLink
href={`/folder/${folder.id}`}
className={folder.id === +selectedFolderId ? "active" : ""}
Expand All @@ -44,8 +63,8 @@ const FolderTabBar = ({ folders, selectedFolderId }: FolderTabBarProps) => {
{modalComponent === MODALS_ID.addFolder && (
<Modal onClose={() => setModalComponent("")}>
<Modal.Title>폴더 추가</Modal.Title>
<InputField modalTarget="" />
<Modal.BlueButton>추가하기</Modal.BlueButton>
<InputField value={folderName} onChange={setFolderName} />
<Modal.BlueButton handleClick={createFolder}>추가하기</Modal.BlueButton>
</Modal>
)}
</>
Expand Down
45 changes: 41 additions & 4 deletions components/folderToolBar/FolderToolBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import InputField from "@/components/inputField/InputField";
import Modal from "@/components/modal/Modal";
import ShareButtons from "@/components/shareButtons/ShareButtons";
import { FOLDER_MANAGE_MENUS, MODALS_ID } from "@/constants/constants";
import { deleteFolder, putFolder } from "@/utils/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import Image, { StaticImageData } from "next/image";
import { useRouter } from "next/router";
import { MouseEvent, useState } from "react";

interface FolderManageMenusProps {
Expand All @@ -14,16 +17,50 @@ interface FolderManageMenusProps {

interface FolderToolbarProps {
folderName: string;
folderId: string;
}

const FolderToolbar = ({ folderName }: FolderToolbarProps) => {
const FolderToolbar = ({ folderName, folderId }: FolderToolbarProps) => {
const queryClient = useQueryClient();
const [modalComponent, setModalComponent] = useState("");
const [newFolderName, setNewFolderName] = useState(folderName);
const router = useRouter();

const handleMenuClick = (e: MouseEvent<HTMLButtonElement>, menu: FolderManageMenusProps) => {
e.stopPropagation();
setModalComponent(menu.modalId);
};

const handleChangeFolder = () => {
changeFolderMutation.mutate({ folderId, newFolderName });
};
const changeFolderMutation = useMutation({
mutationFn: ({ folderId, newFolderName }: { folderId: string; newFolderName: string }) =>
putFolder(folderId, newFolderName),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["folders"] });
setModalComponent("");
},
onError: (error) => {
console.error(error);
},
});

const handleDeleteFolder = () => {
deleteFolderMutation.mutate(folderId);
};
const deleteFolderMutation = useMutation({
mutationFn: (folderId: string) => deleteFolder(folderId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["folders"] });
setModalComponent("");
router.replace("/folder");
},
onError: (error) => {
console.error(error);
},
});

return (
<>
<S.ToolBarWrap>
Expand All @@ -50,15 +87,15 @@ const FolderToolbar = ({ folderName }: FolderToolbarProps) => {
{modalComponent === MODALS_ID.rename && (
<Modal onClose={() => setModalComponent("")}>
<Modal.Title>폴더 이름 변경</Modal.Title>
<InputField modalTarget={folderName} />
<Modal.BlueButton>변경하기</Modal.BlueButton>
<InputField value={newFolderName} onChange={setNewFolderName} />
<Modal.BlueButton handleClick={handleChangeFolder}>변경하기</Modal.BlueButton>
</Modal>
)}
{modalComponent === MODALS_ID.delete && (
<Modal onClose={() => setModalComponent("")}>
<Modal.Title>폴더 삭제</Modal.Title>
<Modal.TargetName>{folderName}</Modal.TargetName>
<Modal.RedButton>삭제하기</Modal.RedButton>
<Modal.RedButton handleClick={handleDeleteFolder}>삭제하기</Modal.RedButton>
</Modal>
)}
</>
Expand Down
17 changes: 8 additions & 9 deletions components/globalNav/GlobalNav.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import * as S from "@/components/globalNav/GlobalNav.style";
import { useUser } from "@/utils/AuthProvider";
import { signOut, useSession } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";

const GlobalNav = () => {
const { user } = useUser();
const { email = "", image_source: profileImageSource = "" } = user || {};
const { pathname } = useRouter();
const { data: session, status } = useSession();

const { pathname } = useRouter();
return (
<S.GlobalNavWrapper $isFolder={pathname.startsWith("/folder")}>
<S.GlobalNav>
<Link href="/">
<S.LogoImage />
</Link>
{user ? (
<Link href="/folder">
{status === "authenticated" ? (
<button onClick={() => signOut()}>
<S.UserInfo>
<S.ProfileImg src={profileImageSource} />
<S.UserEmail>{email}</S.UserEmail>
<S.ProfileImg src={session.user.profileImage} />
<S.UserEmail>{session.user.name}</S.UserEmail>
</S.UserInfo>
</Link>
</button>
) : (
<Link href="/user/signin">
<S.LoginButton>로그인</S.LoginButton>
Expand Down
19 changes: 19 additions & 0 deletions components/hocs/WithAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { ComponentType, useEffect } from "react";

const WithAuth = <P extends {}>(Component: ComponentType<P>) => {
return function AuthenticatedComponent(props: P) {
const { data: session, status } = useSession();
const router = useRouter();

useEffect(() => {
if (status === "loading") return;
if (!session) router.push("/user/signin");
}, [session, status, router]);

return <Component {...props} />;
};
};

export default WithAuth;
10 changes: 4 additions & 6 deletions components/inputField/InputField.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import * as S from "@/components/inputField/InputField.style";
import { useState } from "react";

interface InputFieldProps {
modalTarget: string;
value: string;
onChange: (value: string) => void;
}

const InputField = ({ modalTarget }: InputFieldProps) => {
const [value, setValue] = useState(modalTarget);
return <S.Input value={value} onChange={(e) => setValue(e.target.value)} placeholder="내용 입력" />;
const InputField = ({ value, onChange }: InputFieldProps) => {
return <S.Input value={value} onChange={(e) => onChange(e.target.value)} placeholder="내용 입력" />;
};

export default InputField;
Loading

0 comments on commit bd33e85

Please sign in to comment.