Skip to content

Commit 6eaaace

Browse files
committed
Feat: 링크 검색기능 추가, CardsLayout margin 추가
2 parents 4055f2b + fdb3c5b commit 6eaaace

File tree

21 files changed

+437
-62
lines changed

21 files changed

+437
-62
lines changed

components/Dropdown.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
3+
interface DropdownProps {
4+
onEdit?: () => void;
5+
openDelete?: () => void;
6+
}
7+
8+
const Dropdown = ({ onEdit, openDelete }: DropdownProps) => {
9+
const buttonStyle =
10+
"block w-full py-2 text-sm hover:bg-gray200 hover:text-purple100";
11+
12+
return (
13+
<div className="absolute top-[17px] right-0 flex flex-col gap-[2px] min-w-[100px] bg-white shadow-lg rounded">
14+
<button className={buttonStyle} onClick={onEdit}>
15+
수정하기
16+
</button>
17+
<button className={buttonStyle} onClick={openDelete}>
18+
삭제하기
19+
</button>
20+
</div>
21+
);
22+
};
23+
24+
export default Dropdown;

components/Layout/CardsLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface CardsLayoutProps {
66

77
const CardsLayout = ({ children }: CardsLayoutProps) => {
88
return (
9-
<div className="grid place-items-center grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 md:gap-6 lg:gap-[20px] w-full">
9+
<div className="grid place-items-center grid-cols-1 my-[24px] md:grid-cols-2 lg:grid-cols-3 gap-5 md:gap-6 lg:gap-[20px] w-full">
1010
{children}
1111
</div>
1212
);

components/Layout/Footer.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Link from "next/link";
2+
import SocialLinks from "../SocialLinks";
3+
4+
const Footer = () => {
5+
return (
6+
<footer className="bg-black200 pt-8 sm:px-8 pb-[108px] md:px-[104px] lg:px-[104px]">
7+
<nav className="relative flex justify-between">
8+
<p className="text-gray700 sm:absolute sm:top-[60px] leading-[18.4px]">
9+
©codeit - 2023
10+
</p>
11+
12+
<ul className="flex gap-[30px] text-gray800 leading-[18.4px]">
13+
<li>
14+
<Link href={"/"}>Privacy Policy</Link>
15+
</li>
16+
<li>
17+
<Link href={"/"}>FAQ</Link>
18+
</li>
19+
</ul>
20+
21+
<SocialLinks />
22+
</nav>
23+
</footer>
24+
);
25+
};
26+
27+
export default Footer;

components/LinkCard.tsx

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
2+
import { useRouter } from "next/router";
23
import timeAgo from "@/util/timAgo";
34
import Image from "next/image";
5+
import Dropdown from "./Dropdown";
6+
import useModalStore from "@/store/useModalStore";
47

58
interface LinkCardProps {
69
info: {
@@ -12,16 +15,29 @@ interface LinkCardProps {
1215
url: string;
1316
createdAt: string;
1417
};
15-
isFavoritePage?: boolean;
18+
onEdit?: () => void;
19+
openDelete?: () => void;
1620
}
1721

18-
const LinkCard = ({ isFavoritePage, info }: LinkCardProps) => {
22+
const LinkCard = ({ onEdit, openDelete, info }: LinkCardProps) => {
1923
const [isSubscribed, setIsSubscribed] = useState(false);
20-
const [isOpen, setIsOpen] = useState(false);
24+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
25+
const { isOpen: isModalOpen } = useModalStore(); // 모달 열림 상태 구독
2126

2227
const formattedDate = info.createdAt?.slice(0, 10).replace(/-/g, ".");
2328
const createdTime = timeAgo(info.createdAt);
2429

30+
const router = useRouter();
31+
const isFavoritePage = router.pathname === "/favorite";
32+
33+
// 모달이 열릴 때 드롭다운 닫기
34+
useEffect(() => {
35+
if (isModalOpen) setIsDropdownOpen(false);
36+
}, [isModalOpen]);
37+
38+
// dropdown 버튼
39+
const toggleDropdown = () => setIsDropdownOpen((prev) => !prev);
40+
2541
return (
2642
<div className="w-[340px] h-[344px] rounded-[12px] shadow-lg overflow-hidden cursor-pointer hover:scale-105 hover:duration-300">
2743
<section className="relative w-full h-[60%]">
@@ -31,47 +47,41 @@ const LinkCard = ({ isFavoritePage, info }: LinkCardProps) => {
3147
alt="링크 미리보기"
3248
fill
3349
/>
34-
{/* isFavoritePage가 false일 때만 즐겨찾기 버튼 렌더링 */}
35-
{!isFavoritePage &&
36-
(isSubscribed ? (
37-
<div
38-
onClick={() => setIsSubscribed(!isSubscribed)}
39-
className="absolute top-[15px] right-[15px] z-1"
40-
>
41-
<Image
42-
src="/icons/star-fill.svg"
43-
width={32}
44-
height={32}
45-
alt="subscripe button"
46-
/>
47-
</div>
48-
) : (
49-
<div
50-
onClick={() => setIsSubscribed(!isSubscribed)}
51-
className="absolute top-[15px] right-[15px] z-1"
52-
>
53-
<Image
54-
src="/icons/star-empty.svg"
55-
width={32}
56-
height={32}
57-
alt="subscripe button"
58-
/>
59-
</div>
60-
))}
50+
{/* isFavoritePage일 때만 즐겨찾기 버튼 렌더링 */}
51+
{!isFavoritePage && (
52+
<div
53+
onClick={() => setIsSubscribed(!isSubscribed)}
54+
className="absolute top-[15px] right-[15px] z-1"
55+
>
56+
<Image
57+
src={
58+
isSubscribed ? "/icons/star-fill.svg" : "/icons/star-empty.svg"
59+
}
60+
width={32}
61+
height={32}
62+
alt="subscribe button"
63+
/>
64+
</div>
65+
)}
6166
</section>
6267

6368
<section className="w-full h-[40%] flex flex-col justify-between gap-[10px] pt-[15px] px-[20px] pb-[10px]">
6469
<div className="flex justify-between">
6570
<span className="text-sm text-gray-400">
6671
{createdTime || "1일 전"}
6772
</span>
68-
{/* isFavoritePage가 false일 때만 케밥 버튼 렌더링 */}
73+
{/* isFavoritePage일 때만 케밥 버튼 렌더링 */}
6974
{!isFavoritePage && (
70-
<div
71-
className="relative w-[21px] h-[17px]"
72-
onClick={(state) => setIsOpen(!state)}
73-
>
74-
<Image src="/icons/kebab.svg" alt="kebab button" fill />
75+
<div className="relative">
76+
<button
77+
className="relative w-[21px] h-[17px]"
78+
onClick={toggleDropdown}
79+
>
80+
<Image src="/icons/kebab.svg" alt="kebab button" fill />
81+
</button>
82+
{isDropdownOpen && (
83+
<Dropdown onEdit={onEdit} openDelete={openDelete} />
84+
)}
7585
</div>
7686
)}
7787
</div>

components/SocialLinks.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Image from "next/image";
2+
import Link from "next/link";
3+
import Facebook from "@/public/icons/Facebook.svg";
4+
import twitter from "@/public/icons/twitter.svg";
5+
import youtube from "@/public/icons/youtube.svg";
6+
import instagram from "@/public/icons/instagram.svg";
7+
8+
const SocialLinks = () => {
9+
return (
10+
<ul className="flex gap-3">
11+
<li>
12+
<Link href={"https://www.facebook.com/codeit.kr/"} target="_blank">
13+
<Image src={Facebook} width={20} height={20} alt="facebook" />
14+
</Link>
15+
</li>
16+
<li>
17+
<Link href={"https://x.com/"} target="_blank">
18+
<Image src={twitter} width={20} height={20} alt="twitter" />
19+
</Link>
20+
</li>
21+
<li>
22+
<Link
23+
href={"https://www.youtube.com/channel/UCCM79CPm2WbBYTRaiNEExbg"}
24+
target="_blank"
25+
>
26+
<Image src={youtube} width={20} height={20} alt="youtube" />
27+
</Link>
28+
</li>
29+
<li>
30+
<Link href={"https://www.instagram.com/codeit_kr/"} target="_blank">
31+
<Image src={instagram} width={20} height={20} alt="instagram" />
32+
</Link>
33+
</li>
34+
</ul>
35+
);
36+
};
37+
38+
export default SocialLinks;

components/modal/AddFolderModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ChangeEvent, useState } from "react";
2+
import { postFolders } from "@/lib/api/folder";
23
import ModalContainer from "./modalComponents/ModalContainer";
34
import ModalInput from "./modalComponents/ModalInput";
4-
import { postFolders } from "@/lib/api/folder";
55
import useModalStore from "@/store/useModalStore";
66
import SubmitButton from "../SubMitButton";
77

components/modal/DeleteLinkModal.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
1+
import useModalStore from "@/store/useModalStore";
12
import SubmitButton from "../SubMitButton";
23
import ModalContainer from "./modalComponents/ModalContainer";
4+
import { useLinkCardStore } from "@/store/useLinkCardStore";
5+
6+
const DeleteLinkModal = ({
7+
link,
8+
linkId,
9+
}: {
10+
link: string;
11+
linkId: number;
12+
}) => {
13+
const { closeModal } = useModalStore();
14+
const { deleteLink } = useLinkCardStore();
15+
16+
const handleDelete = async () => {
17+
try {
18+
await deleteLink(linkId);
19+
closeModal();
20+
} catch (error) {
21+
console.error("Failed to delete the link:", error);
22+
}
23+
};
324

4-
const DeleteLinkModal = ({ link }: { link: string }) => {
525
return (
626
<ModalContainer title="링크 삭제" subtitle={link}>
727
<SubmitButton
828
type="button"
9-
// onClick={handleSubmit}
29+
onClick={handleDelete}
1030
width="w-full"
11-
height="h-[51px] "
31+
height="h-[51px]"
1232
color="negative"
1333
>
1434
삭제하기

components/modal/EditLink.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ChangeEvent, useState } from "react";
2+
import { useLinkCardStore } from "@/store/useLinkCardStore";
3+
import ModalContainer from "./modalComponents/ModalContainer";
4+
import ModalInput from "./modalComponents/ModalInput";
5+
import useModalStore from "@/store/useModalStore";
6+
import SubmitButton from "../SubMitButton";
7+
8+
const EditLink = ({
9+
folderName,
10+
link,
11+
linkId,
12+
}: {
13+
folderName: string;
14+
link: string;
15+
linkId: number;
16+
}) => {
17+
const [value, setValue] = useState("");
18+
const { closeModal } = useModalStore();
19+
const { updateLink } = useLinkCardStore();
20+
21+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
22+
setValue(e.target.value);
23+
};
24+
25+
const handleSubmit = async () => {
26+
const body = {
27+
url: value,
28+
};
29+
if (value !== "") {
30+
await updateLink(linkId, body);
31+
}
32+
closeModal();
33+
};
34+
return (
35+
<ModalContainer title="링크 주소 변경">
36+
<ModalInput
37+
placeholder={link}
38+
name={folderName}
39+
value={value}
40+
onChange={handleChange}
41+
/>
42+
<SubmitButton
43+
type="button"
44+
onClick={handleSubmit}
45+
width="w-full"
46+
height="h-[51px] "
47+
color="positive"
48+
>
49+
변경하기
50+
</SubmitButton>
51+
</ModalContainer>
52+
);
53+
};
54+
export default EditLink;

components/modal/modalManager/ModalManager.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import AddFolderModal from "../AddFolderModal";
55
import DeleteLinkModal from "../DeleteLinkModal";
66
import EditModal from "../EditModal";
77
import SNSModal from "../SNSModal";
8+
import EditLink from "../EditLink";
89

910
export const ModalType = {
1011
AddFolderModal: "AddFolderModal",
@@ -13,6 +14,7 @@ export const ModalType = {
1314
DeleteLinkModal: "DeleteLinkModal",
1415
EditModal: "EditModal",
1516
SNSModal: "SNSModal",
17+
EditLink: "EditLink",
1618
} as const;
1719

1820
export type ModalKeysType = keyof typeof ModalType;
@@ -42,10 +44,23 @@ export const Modal = () => {
4244
case "DeleteFolderModal":
4345
return <DeleteFolderModal folderName={props.folderName || "폴더이름"} />;
4446
case "DeleteLinkModal":
45-
return <DeleteLinkModal link={props.link || "링크"} />;
47+
return (
48+
<DeleteLinkModal
49+
link={props.link || "링크"}
50+
linkId={Number(props.linkId)}
51+
/>
52+
);
4653
case "EditModal":
4754
return <EditModal folderName={props.folderName || "폴더이름"} />;
4855
case "SNSModal":
4956
return <SNSModal folderName={props.folderName || "폴더이름"} />;
57+
case "EditLink":
58+
return (
59+
<EditLink
60+
folderName={props.folderName || "폴더이름"}
61+
link={props.link || "링크"}
62+
linkId={Number(props.linkId)}
63+
/>
64+
);
5065
}
5166
};

pages/_app.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Footer from "@/components/Layout/Footer";
12
import Header from "@/components/Layout/Header";
23
import "@/styles/globals.css";
34
import type { AppProps } from "next/app";
@@ -8,9 +9,12 @@ export default function App({ Component, pageProps }: AppProps) {
89
const hidePaths = ["/login", "/signup"];
910

1011
return (
11-
<>
12+
<div className="min-h-screen flex flex-col">
1213
{!hidePaths.includes(router.pathname) && <Header />}
13-
<Component {...pageProps} />
14-
</>
14+
<div className="flex-grow">
15+
<Component {...pageProps} />
16+
</div>
17+
{!hidePaths.includes(router.pathname) && <Footer />}
18+
</div>
1519
);
1620
}

0 commit comments

Comments
 (0)