Skip to content

Commit

Permalink
Merge pull request #1028 from suzinxix/part3-길수진-week15
Browse files Browse the repository at this point in the history
[길수진] week15
  • Loading branch information
Seung-wan authored Apr 12, 2024
2 parents 742532f + 337d3dc commit 3d98617
Show file tree
Hide file tree
Showing 58 changed files with 870 additions and 813 deletions.
3 changes: 0 additions & 3 deletions .eslintrc.json

This file was deleted.

10 changes: 10 additions & 0 deletions components/auth/Navigation/Navigation.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.container {
display: flex;
gap: 0.8rem;
font-size: 1.6rem;
}

.link {
color: var(--primary-color);
text-decoration: underline;
}
21 changes: 21 additions & 0 deletions components/auth/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Link from "next/link";
import styles from "./Navigation.module.css";

type Props = {
question: string;
navigation: string;
link: string;
};

const Navigation = ({ question, navigation, link }: Props) => {
return (
<div className={styles.container}>
<span className={styles.text}>{question}</span>
<Link href={link} className={styles.link}>
{navigation}
</Link>
</div>
);
};

export default Navigation;
20 changes: 20 additions & 0 deletions components/auth/SocialAuth/SocialAuth.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.container {
width: 40rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.2rem 2.4rem;
border-radius: 0.8rem;
border: 1px solid var(--gray20-color);
background: var(--gray10-color);
}

.text {
color: var(--gray100-color);
font-size: 1.4rem;
}

.icons {
display: flex;
gap: 1.6rem;
}
18 changes: 18 additions & 0 deletions components/auth/SocialAuth/SocialAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PropsWithChildren } from "react";
import styles from "./SocialAuth.module.css";
import GoogleIcon from "@/images/ic_google_cicle.svg";
import KakaoIcon from "@/images/ic_kakao_cicle.svg";

const SocialAuth = ({ children }: PropsWithChildren) => {
return (
<div className={styles.container}>
<div className={styles.text}>{children}</div>
<div className={styles.icons}>
<GoogleIcon />
<KakaoIcon />
</div>
</div>
);
};

export default SocialAuth;
123 changes: 73 additions & 50 deletions components/common/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { useState, MouseEvent } from "react";
import Image from "next/image";
import styles from "./card.module.css";

import DeleteModal from "@/components/common/Modal/DeleteModal/DeleteModal";
import FolderModal from "@/components/common/Modal/FolderModal/FolderModal";
import { formatDate, getTimeDifference } from "utils/dateUtils";
import { DELETE_LINK, ADD_LINK } from "constants/strings";
import type { LinkItem } from "hooks/useGetLinks";
import { UseModal } from "hooks/useModal";

import { formatDate, getTimeDifference } from "utils/date";
import { MODALS } from "constants/modals";
import type { LinkItem, Folder } from "types";
import noImage from "@/images/bg_noImage.png";

interface Props extends Partial<UseModal> {
interface Props {
item: LinkItem;
onClick: () => void;
folderList: Folder[] | null;
}

// TODO: Card 컴포넌트 분리
function Card({ item, onClick, modals, openModal, closeModal }: Props) {
function Card({ item, folderList }: Props) {
const { createdAt, created_at, description, imageSource, image_source, url } =
item;

Expand All @@ -27,31 +28,50 @@ function Card({ item, onClick, modals, openModal, closeModal }: Props) {
? `https:${imgUrl}`
: imgUrl;

const isFolderPage = modals && openModal && closeModal;

const [isMenuOpen, setIsMenuOpen] = useState(false);

const [image, setImage] = useState<string | null>(absoluteImageUrl);

const [currentModal, setCurrentModal] = useState<string | null>(null);

const closeModal = () => {
setCurrentModal(null);
};

const handleCardClick = (url: string) => {
window.open(url, "_blank");
};

const handleMenuClick = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setIsMenuOpen((prev) => !prev);
};

const handleOptionClick = (
e: MouseEvent<HTMLButtonElement>,
modal: MODALS
) => {
e.stopPropagation();
setCurrentModal(modal);
};

return (
<div className={styles.container} onClick={onClick}>
<div className={styles.imgWrapper}>
<Image
src={image ?? noImage}
onError={() => {
setImage("/images/bg_noImage.png");
}}
fill
sizes="340px"
alt="대표 이미지"
className={styles.image}
/>
{isFolderPage && (
<>
<div
className={styles.container}
onClick={() => handleCardClick(item.url)}
>
<div className={styles.imgWrapper}>
<Image
src={image ?? noImage}
onError={() => {
setImage("/images/bg_noImage.png");
}}
fill
sizes="340px"
alt="대표 이미지"
className={styles.image}
/>
<Image
src="/images/ic_star.svg"
width={34}
Expand All @@ -60,13 +80,12 @@ function Card({ item, onClick, modals, openModal, closeModal }: Props) {
className={styles.star}
priority
/>
)}
</div>
</div>

<div className={styles.info}>
<div className={styles.infoTop}>
<div className={styles.difference}>{getTimeDifference(date)}</div>

<div className={styles.info}>
<div className={styles.infoTop}>
<div className={styles.difference}>{getTimeDifference(date)}</div>
{isFolderPage && (
<div className={styles.menu}>
<button
type="button"
Expand All @@ -81,51 +100,55 @@ function Card({ item, onClick, modals, openModal, closeModal }: Props) {
priority
/>
</button>

{isMenuOpen && (
<div className={styles.options}>
<button
type="button"
className={styles.option}
onClick={(e) => {
e.stopPropagation();
openModal(DELETE_LINK);
handleOptionClick(e, MODALS.deleteLink);
}}
>
삭제하기
</button>
{modals[DELETE_LINK] && (
<DeleteModal
variant={DELETE_LINK}
deleted={url}
closeModal={closeModal}
/>
)}

<button
type="button"
className={styles.option}
onClick={(e) => {
e.stopPropagation();
openModal(ADD_LINK);
handleOptionClick(e, MODALS.addLink);
}}
>
폴더에 추가
</button>
{modals[ADD_LINK] && (
<FolderModal
variant={ADD_LINK}
deleted={url}
closeModal={closeModal}
/>
)}
</div>
)}
</div>
)}
</div>

<div className={styles.description}>{description}</div>

<div className={styles.date}>{formatDate(date)}</div>
</div>
<div className={styles.description}>{description}</div>
<div className={styles.date}>{formatDate(date)}</div>
</div>
</div>

<DeleteModal
isOpen={currentModal === MODALS.deleteLink}
title="폴더 삭제"
deletion={url}
onCloseClick={closeModal}
/>

<FolderModal
isOpen={currentModal === MODALS.addLink}
link={url}
title="폴더에 추가"
buttonText="추가하기"
folderList={folderList}
onCloseClick={closeModal}
/>
</>
);
}

Expand Down
15 changes: 6 additions & 9 deletions components/common/CardList/CardList.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { useState } from "react";
import styles from "./cardlist.module.css";
import Card from "@/components/common/Card/Card";
import NoResults from "@/components/common/NoResults/NoResults";
import type { LinkItem } from "hooks/useGetLinks";
import { UseModal } from "hooks/useModal";
import type { LinkItem, Folder } from "types";

interface Props extends Partial<UseModal> {
interface Props {
items: LinkItem[] | null;
folderList: Folder[] | null;
}

function CardList({ items, ...rest }: Props) {
const handleClick = (url: string) => {
window.open(url, "_blank");
};

function CardList({ items, folderList }: Props) {
if (!items || items.length === 0) {
return <NoResults />;
}
Expand All @@ -22,7 +19,7 @@ function CardList({ items, ...rest }: Props) {
<ul className={styles.list}>
{items.map((item) => (
<li key={item.id}>
<Card item={item} onClick={() => handleClick(item.url)} {...rest} />
<Card item={item} folderList={folderList} />
</li>
))}
</ul>
Expand Down
20 changes: 11 additions & 9 deletions components/common/InputField/InputField.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import React, { useState, InputHTMLAttributes, forwardRef } from "react";
import { useState, InputHTMLAttributes, forwardRef } from "react";
import styles from "./inputField.module.css";
import EyeOff from "@/images/ic_eye-off.svg";
import EyeOn from "@/images/ic_eye-on.svg";

type Props = {
id: "email" | "password" | "passwordConfirm";
id: string;
type: "text" | "email" | "password";
label: "이메일" | "비밀번호" | "비밀번호 확인";
error?: string;
label: string;
errorMessage?: string;
} & InputHTMLAttributes<HTMLInputElement>;

const InputField = forwardRef<HTMLInputElement, Props>(
({ id, type, label, error, ...rest }, ref) => {
({ id, type, label, errorMessage, ...rest }, ref) => {
const [showPassword, setShowPassword] = useState(false);
const varientType =
type !== "password" ? "email" : showPassword ? "text" : "password";

return (
<div className={styles.section}>
Expand All @@ -23,11 +25,11 @@ const InputField = forwardRef<HTMLInputElement, Props>(
<div className={styles.wrapper}>
<input
id={id}
type={showPassword ? "text" : "password"}
type={varientType}
ref={ref}
aria-invalid={error ? "true" : "false"}
aria-invalid={errorMessage ? "true" : "false"}
className={styles.input}
style={{ ...(error && { border: "1px solid var(--red-color)" }) }}
style={{ ...(errorMessage && { border: "1px solid var(--red-color)" }) }}
{...rest}
/>

Expand All @@ -50,7 +52,7 @@ const InputField = forwardRef<HTMLInputElement, Props>(
)}
</div>

{error && <p className={styles.error}>{error}</p>}
{errorMessage && <p className={styles.error}>{errorMessage}</p>}
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions components/common/Layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ReactNode } from "react";
import { PropsWithChildren } from "react";
import styles from "./layout.module.css";
import Navbar from "../Navbar/Navbar";
import Footer from "../Footer/Footer";

export default function Layout({ children }: { children: ReactNode }) {
export default function Layout({ children }: PropsWithChildren) {
return (
<div className={styles.app}>
<Navbar />
Expand Down
Loading

0 comments on commit 3d98617

Please sign in to comment.