Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[유호민] Week15 #471

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions axios/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import axios from "axios";

const axiosInstance = axios.create({
baseURL: "https://bootcamp-api.codeit.kr/api",
});

axiosInstance.interceptors.request.use(
(config) => {
const token = localStorage.getItem("accessToken");
if (token) {
config.headers.Authorization = token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
Comment on lines +7 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터셉터로 헤더에 토큰 값 넣어주신 부분도 좋습니다~

스토리지 키값은 상수로 관리해서 일괄적으로 사용하면 더 좋을 것 같아요


export default axiosInstance;
60 changes: 47 additions & 13 deletions component/FolderMain.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import useUserFolders from "@/hooks/useUserFolders";
import { useEffect, useState } from "react";
import LinkCardListByFolderId from "./LinkCardListByFolderId";
import useSWR from "swr";
import { fetcher } from "@/lib/fetcher";
import axios from "axios";
import FolderButtons from "./FolderButtons";
import FolderTabs from "./FolderTabs";
import FolderTitlebar from "./FolderTitlebar";
import { useRouter } from "next/router";
import axiosInstance from "@/axios/axiosInstance";
import { fetchFolders } from "@/lib/folderFetcher";

interface UserData {
id: number;
Expand Down Expand Up @@ -41,8 +42,12 @@ export default function FolderMain({
if (!user) {
return <div>Loading...</div>;
}

const router = useRouter();

const [title, setTitle] = useState<string>("전체");
const [clickedButton, setClickedButton] = useState<number | null>(0);
const [folders, setFolders] = useState<Folder[] | undefined | null>();
const [folderId, setFolderId] = useState<number>(0);
const [filteredLinks, setFilteredLinks] = useState<Link[]>([]);
const [modalStates, setModalStates] = useState<{
Expand All @@ -60,14 +65,13 @@ export default function FolderMain({
addLinkModal: false,
deleteLinkModal: false,
});

const { data: folders } = useUserFolders(user.id);
const { data: links } = useSWR(
() =>
folderId === 0
? `https://bootcamp-api.codeit.kr/api/users/${user.id}/links`
: `https://bootcamp-api.codeit.kr/api/users/${user.id}/links?folderId=${folderId}`,
fetcher
? `https://bootcamp-api.codeit.kr/api/links`
: `https://bootcamp-api.codeit.kr/api/links?folderId=${folderId}`,
// fetcher
(url) => axiosInstance.get(url).then((res) => res.data)
);

const openModal = (modal: keyof typeof modalStates) => {
Expand All @@ -78,21 +82,23 @@ export default function FolderMain({
setModalStates({ ...modalStates, [modal]: false });
};

const handleButtonClick = (folderId: number) => {
const handleButtonClick = (folderId: number): void => {
setClickedButton(folderId);
const clickedFolder = folders.data.find(
const clickedFolder = folders?.find(
(folder: Folder) => folder.id === folderId
);
if (clickedFolder) {
setFolderId(clickedFolder.id);
setTitle(clickedFolder.name);
router.push(`/folder/${folderId}`);
}
};

const handleAllButtonClick = () => {
setClickedButton(0);
setFolderId(0);
setTitle("전체");
router.push("/folder");
};

const getInputValue = async () => {
Expand All @@ -104,7 +110,7 @@ export default function FolderMain({
const handleFilter = async () => {
if (links) {
const i = await getInputValue();
const nextLinks = links.data.filter(
const nextLinks = links?.data?.folder?.filter(
(link: any) =>
(link.url && link.url.includes(i)) ||
(link.title && link.title.includes(i)) ||
Expand All @@ -119,9 +125,37 @@ export default function FolderMain({
handleFilter();
}, [inputValue]);

useEffect(() => {
const folderIdFromURL = parseInt(router.query.folderId as string, 10) || 0;
setClickedButton(folderIdFromURL);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿼리 값이 유니언 타입이라 as 로 타입 단언해주신 것 같아요.
as 는 값 형태를 바꿔 디버깅이 어렵기 때문에 지양하는 편인데요.
타입 처리하는 건 타입 가드를 이용해 처리하면 좋을 것 같습니다.

// util
const isStringQuery = (value: unknown): value is string => {
  return typeof value === 'string';
}

// hook
const useRouterQuery = (segmentName: string) => {
  const { query } = useRouter();

  const segment =  query[segmentName];

  if (!isStringQuery(segment)) {
    return;
  }
  return segment;
}

이런식으로 타입 가드 함수와 커스텀 훅 작성해서 세그먼트 값과 타입 관련된 관심사를 분리할 수 있을 것 같습니다.
그냥 코멘트로 남기는 코드라 동작이 되는지는 모르겠네요.. ㅎㅎ 한번 확인해보시고 수정해서 적용해보세요~

타입 단언 관련 글도 참고해보세요!
https://velog.io/@kmh060020/%EC%93%B0%EC%A7%80%EB%A7%90%EB%9D%BC%EB%8A%94-Type-Assertions%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8-%EC%9D%80-%EC%99%9C-%EC%9E%88%EB%82%98

setFolderId(folderIdFromURL);

Comment on lines +130 to +131
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동일한 값을 두개의 상태로 관리하시는 이유가 있을까요?

if (folderIdFromURL === 0) {
setTitle("전체");
} else {
const clickedFolder = folders?.find(
(folder: Folder) => folder.id === folderIdFromURL
);
setTitle(clickedFolder ? clickedFolder.name : "전체");
}
}, [router.query.folderId, folders]);

useEffect(() => {
const fetchFoldersData = async () => {
try {
const { data } = await fetchFolders();
setFolders(data.folder);
} catch (error) {
console.error(error);
}
};

fetchFoldersData();
}, []);

return (
<>
<FolderButtons
<FolderTabs
clickedButton={clickedButton}
handleButtonClick={handleButtonClick}
handleAllButtonClick={handleAllButtonClick}
Expand All @@ -137,7 +171,7 @@ export default function FolderMain({
modalStates={modalStates}
/>
<LinkCardListByFolderId
links={links?.data}
links={links?.data?.folder}
filteredLinks={filteredLinks}
inputValue={inputValue}
modalStates={modalStates}
Expand Down
27 changes: 18 additions & 9 deletions component/FolderButtons.tsx → component/FolderTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ interface Folder {
favorite?: boolean;
}

export default function FolderButtons({
interface ModalStates {
addModal: boolean;
shareModal: boolean;
editModal: boolean;
deleteModal: boolean;
addLinkModal: boolean;
deleteLinkModal: boolean;
}

export default function FolderTabs({
clickedButton,
handleButtonClick,
handleAllButtonClick,
Expand All @@ -17,13 +26,13 @@ export default function FolderButtons({
closeModal,
modalStates,
}: {
clickedButton: any;
handleButtonClick: any;
handleAllButtonClick: any;
folders: any;
openModal: any;
closeModal: any;
modalStates: any;
clickedButton: number | null;
handleButtonClick: (folderId: number) => void;
handleAllButtonClick: () => void;
Comment on lines +30 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수 props 네이밍 관련된 자료입니당 참고해보시면 좋을 것 같아요
https://jaketrent.com/post/naming-event-handlers-react/

folders: Folder[] | undefined | null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

folders 타입으로 세가지를 가지고 있는데 각각 값에 따라서 다른 처리를 해야하나용?

그게 아니라면

  1. folders: Folder[] (외부에서 props 값 넘겨줄 때 undefined 나 null 이면 빈 배열 전달하도록 기본값 설정)
  2. folders?: Folder[] (없을 수 있다는 뜻)
  3. folders: Folder[] | null (없을 수 있다는 뜻2, undefined 와 Null 중에선 한번 고민해보세요)

요렇게 세가지 중에 folders 값에 따라서 처리하는게 어떻게 달라지는지를 생각하고 타입을 명시해주시면 더 명확한 인터페이스가 될 것 같아요!

openModal: (modal: keyof ModalStates) => void;
closeModal: (modal: keyof ModalStates) => void;
modalStates: ModalStates;
}) {
return (
<div className="flex items-center justify-between mt-[40px] px-[32px] xl:px-[200px]">
Expand All @@ -39,7 +48,7 @@ export default function FolderButtons({
전체
</button>
{folders &&
folders.data.map((folder: Folder) => {
folders?.map((folder: Folder) => {
return (
<button
key={folder.id}
Expand Down
4 changes: 2 additions & 2 deletions component/FolderTitlebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function FolderTitlebar({
<div className="flex items-center justify-between mt-[40px] px-[32px] xl:px-[200px]">
<div className="text-[30px] font-bold">{title}</div>
<div className="flex space-x-2">
{title !== "전체" ? (
{title !== "전체" && (
<>
<ShareFolderModal
openModal={openModal}
Expand All @@ -35,7 +35,7 @@ export default function FolderTitlebar({
modalStates={modalStates}
/>
</>
) : null}
)}
</div>
</div>
);
Expand Down
10 changes: 5 additions & 5 deletions component/LinkCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { useState } from "react";

interface Link {
id: string;
createdAt: Date;
created_at: Date;
url: string;
title: string;
description: string;
imageSource: string;
image_source: string;
}

export default function LinkCard({ link }: { link: Link }) {
Expand All @@ -22,15 +22,15 @@ export default function LinkCard({ link }: { link: Link }) {
<div className="w-[300px] h-[300px] m-[50px] shadow-2xl">
<a href={link.url} target="_blank" rel="noreferrer">
<img
src={link.imageSource ? link.imageSource : "/images/no-image.svg"}
src={link.image_source ? link.image_source : "/images/no-image.svg"}
alt="link"
className="w-[350px] h-[200px] object-cover rounded-md"
/>
</a>
<div className="px-5 py-4">
<div className="relative flex justify-between">
<p className="text-[#666] text-[13px]">
{calculateTimeDiff(link.createdAt)}
{calculateTimeDiff(link.created_at)}
</p>
<img
src="/images/kebab.svg"
Expand Down Expand Up @@ -59,7 +59,7 @@ export default function LinkCard({ link }: { link: Link }) {
{link.description}
</p>
<p className="mt-2 text-[#333] text-[14px]">
{formatDate(link.createdAt)}
{formatDate(link.created_at)}
</p>
</div>
</div>
Expand Down
7 changes: 5 additions & 2 deletions component/LinkCardByFolderId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { calculateTimeDiff } from "@/utils/calculateTimeDiff";
import { useState } from "react";
import Modal from "./Modal";
import { formatDate } from "@/utils/formatDate";
import Link from "next/link";

interface Link {
id: string;
Expand Down Expand Up @@ -29,15 +30,17 @@ export default function LinkCardByFolderId({
setShowPopover(!showPopover);
};

console.log(link);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그 지워주세용


return (
<div className="w-[300px] h-[300px] m-[50px] shadow-2xl">
<a href={link.url} target="_blank" rel="noreferrer">
<Link href={`https://${link.url}`} target="_blank" rel="noreferrer">
<img
src={link.image_source ? link.image_source : "/images/no-image.svg"}
alt="link"
className="w-[350px] h-[200px] object-cover rounded-md"
/>
</a>
</Link>
<div className="px-5 py-4">
<div className="relative flex justify-between">
<p className="text-[#666] text-[13px]">
Expand Down
18 changes: 10 additions & 8 deletions component/LinkCardList.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import useSampleFolder from "@/hooks/useSampleFolder";
import LinkCard from "./LinkCard";

interface Link {
id: string;
createdAt: Date;
created_at: Date;
url: string;
title: string;
description: string;
imageSource: string;
image_source: string;
}

export default function LinkCardList() {
const { data } = useSampleFolder();

const links: Link[] = data?.folder?.links;

export default function LinkCardList({ links }: { links: Link[] }) {
if (links?.length === 0) {
return (
<div className="flex justify-center items-center mt-[50px]">
저장된 링크가 없습니다.
</div>
);
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 place-items-center mt-[40px] xl:px-[200px] w-full">
{links?.map((link) => {
Expand Down
Loading
Loading