From 28ed47f539a090be1b9a2b2c4e3a61823a414551 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 12:03:54 +0900 Subject: [PATCH 01/82] =?UTF-8?q?FE-48=20=F0=9F=93=B0=20=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20face=20emoji=20svg=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Emotion/AngryFaceBWIcon.svg | 19 +++++++++++++++++++ src/components/Emotion/AngryFaceColorIcon.svg | 19 +++++++++++++++++++ src/components/Emotion/HeartFacedBWIcon.svg | 13 +++++++++++++ .../Emotion/HeartFacedColorIcon.svg | 13 +++++++++++++ src/components/Emotion/SadFaceBWIcon.svg | 14 ++++++++++++++ src/components/Emotion/SadFaceColorIcon.svg | 14 ++++++++++++++ src/components/Emotion/SmileFaceBWIcon.svg | 13 +++++++++++++ src/components/Emotion/SmileFaceColorIcon.svg | 13 +++++++++++++ src/components/Emotion/ThinkFaceBWIcon.svg | 11 +++++++++++ src/components/Emotion/ThinkFaceColorIcon.svg | 11 +++++++++++ 10 files changed, 140 insertions(+) create mode 100644 src/components/Emotion/AngryFaceBWIcon.svg create mode 100644 src/components/Emotion/AngryFaceColorIcon.svg create mode 100644 src/components/Emotion/HeartFacedBWIcon.svg create mode 100644 src/components/Emotion/HeartFacedColorIcon.svg create mode 100644 src/components/Emotion/SadFaceBWIcon.svg create mode 100644 src/components/Emotion/SadFaceColorIcon.svg create mode 100644 src/components/Emotion/SmileFaceBWIcon.svg create mode 100644 src/components/Emotion/SmileFaceColorIcon.svg create mode 100644 src/components/Emotion/ThinkFaceBWIcon.svg create mode 100644 src/components/Emotion/ThinkFaceColorIcon.svg diff --git a/src/components/Emotion/AngryFaceBWIcon.svg b/src/components/Emotion/AngryFaceBWIcon.svg new file mode 100644 index 00000000..c499add3 --- /dev/null +++ b/src/components/Emotion/AngryFaceBWIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Emotion/AngryFaceColorIcon.svg b/src/components/Emotion/AngryFaceColorIcon.svg new file mode 100644 index 00000000..ca6e754a --- /dev/null +++ b/src/components/Emotion/AngryFaceColorIcon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Emotion/HeartFacedBWIcon.svg b/src/components/Emotion/HeartFacedBWIcon.svg new file mode 100644 index 00000000..5702c0f9 --- /dev/null +++ b/src/components/Emotion/HeartFacedBWIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/components/Emotion/HeartFacedColorIcon.svg b/src/components/Emotion/HeartFacedColorIcon.svg new file mode 100644 index 00000000..9ef30c50 --- /dev/null +++ b/src/components/Emotion/HeartFacedColorIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/components/Emotion/SadFaceBWIcon.svg b/src/components/Emotion/SadFaceBWIcon.svg new file mode 100644 index 00000000..90df96bd --- /dev/null +++ b/src/components/Emotion/SadFaceBWIcon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/components/Emotion/SadFaceColorIcon.svg b/src/components/Emotion/SadFaceColorIcon.svg new file mode 100644 index 00000000..58e96e31 --- /dev/null +++ b/src/components/Emotion/SadFaceColorIcon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/components/Emotion/SmileFaceBWIcon.svg b/src/components/Emotion/SmileFaceBWIcon.svg new file mode 100644 index 00000000..58be6715 --- /dev/null +++ b/src/components/Emotion/SmileFaceBWIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/components/Emotion/SmileFaceColorIcon.svg b/src/components/Emotion/SmileFaceColorIcon.svg new file mode 100644 index 00000000..3e66c738 --- /dev/null +++ b/src/components/Emotion/SmileFaceColorIcon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/components/Emotion/ThinkFaceBWIcon.svg b/src/components/Emotion/ThinkFaceBWIcon.svg new file mode 100644 index 00000000..2ac21adc --- /dev/null +++ b/src/components/Emotion/ThinkFaceBWIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/Emotion/ThinkFaceColorIcon.svg b/src/components/Emotion/ThinkFaceColorIcon.svg new file mode 100644 index 00000000..0e2bab7d --- /dev/null +++ b/src/components/Emotion/ThinkFaceColorIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From 676bdae32d9ed858fd09cf733baff7249ca27e1a Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 12:42:05 +0900 Subject: [PATCH 02/82] =?UTF-8?q?FE-48=20:art:=20=EA=B0=90=EC=A0=95=20?= =?UTF-8?q?=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/favicon.ico | Bin 25931 -> 0 bytes .../icon/BW}/AngryFaceBWIcon.svg | 0 .../icon/BW}/HeartFacedBWIcon.svg | 0 .../icon/BW}/SadFaceBWIcon.svg | 0 .../icon/BW}/SmileFaceBWIcon.svg | 0 .../icon/BW}/ThinkFaceBWIcon.svg | 0 .../icon/Color}/AngryFaceColorIcon.svg | 0 .../icon/Color}/HeartFacedColorIcon.svg | 0 .../icon/Color}/SadFaceColorIcon.svg | 0 .../icon/Color}/SmileFaceColorIcon.svg | 0 .../icon/Color}/ThinkFaceColorIcon.svg | 0 public/next.svg | 1 - public/vercel.svg | 1 - 13 files changed, 2 deletions(-) delete mode 100644 public/favicon.ico rename {src/components/Emotion => public/icon/BW}/AngryFaceBWIcon.svg (100%) rename {src/components/Emotion => public/icon/BW}/HeartFacedBWIcon.svg (100%) rename {src/components/Emotion => public/icon/BW}/SadFaceBWIcon.svg (100%) rename {src/components/Emotion => public/icon/BW}/SmileFaceBWIcon.svg (100%) rename {src/components/Emotion => public/icon/BW}/ThinkFaceBWIcon.svg (100%) rename {src/components/Emotion => public/icon/Color}/AngryFaceColorIcon.svg (100%) rename {src/components/Emotion => public/icon/Color}/HeartFacedColorIcon.svg (100%) rename {src/components/Emotion => public/icon/Color}/SadFaceColorIcon.svg (100%) rename {src/components/Emotion => public/icon/Color}/SmileFaceColorIcon.svg (100%) rename {src/components/Emotion => public/icon/Color}/ThinkFaceColorIcon.svg (100%) delete mode 100644 public/next.svg delete mode 100644 public/vercel.svg diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 718d6fea4835ec2d246af9800eddb7ffb276240c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25931 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 LrJugUA?W`A8`#=m diff --git a/src/components/Emotion/AngryFaceBWIcon.svg b/public/icon/BW/AngryFaceBWIcon.svg similarity index 100% rename from src/components/Emotion/AngryFaceBWIcon.svg rename to public/icon/BW/AngryFaceBWIcon.svg diff --git a/src/components/Emotion/HeartFacedBWIcon.svg b/public/icon/BW/HeartFacedBWIcon.svg similarity index 100% rename from src/components/Emotion/HeartFacedBWIcon.svg rename to public/icon/BW/HeartFacedBWIcon.svg diff --git a/src/components/Emotion/SadFaceBWIcon.svg b/public/icon/BW/SadFaceBWIcon.svg similarity index 100% rename from src/components/Emotion/SadFaceBWIcon.svg rename to public/icon/BW/SadFaceBWIcon.svg diff --git a/src/components/Emotion/SmileFaceBWIcon.svg b/public/icon/BW/SmileFaceBWIcon.svg similarity index 100% rename from src/components/Emotion/SmileFaceBWIcon.svg rename to public/icon/BW/SmileFaceBWIcon.svg diff --git a/src/components/Emotion/ThinkFaceBWIcon.svg b/public/icon/BW/ThinkFaceBWIcon.svg similarity index 100% rename from src/components/Emotion/ThinkFaceBWIcon.svg rename to public/icon/BW/ThinkFaceBWIcon.svg diff --git a/src/components/Emotion/AngryFaceColorIcon.svg b/public/icon/Color/AngryFaceColorIcon.svg similarity index 100% rename from src/components/Emotion/AngryFaceColorIcon.svg rename to public/icon/Color/AngryFaceColorIcon.svg diff --git a/src/components/Emotion/HeartFacedColorIcon.svg b/public/icon/Color/HeartFacedColorIcon.svg similarity index 100% rename from src/components/Emotion/HeartFacedColorIcon.svg rename to public/icon/Color/HeartFacedColorIcon.svg diff --git a/src/components/Emotion/SadFaceColorIcon.svg b/public/icon/Color/SadFaceColorIcon.svg similarity index 100% rename from src/components/Emotion/SadFaceColorIcon.svg rename to public/icon/Color/SadFaceColorIcon.svg diff --git a/src/components/Emotion/SmileFaceColorIcon.svg b/public/icon/Color/SmileFaceColorIcon.svg similarity index 100% rename from src/components/Emotion/SmileFaceColorIcon.svg rename to public/icon/Color/SmileFaceColorIcon.svg diff --git a/src/components/Emotion/ThinkFaceColorIcon.svg b/public/icon/Color/ThinkFaceColorIcon.svg similarity index 100% rename from src/components/Emotion/ThinkFaceColorIcon.svg rename to public/icon/Color/ThinkFaceColorIcon.svg diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28c..00000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f84222..00000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 90925ccc655e339dbfbea8fb01c4293e0fad885d Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 12:48:50 +0900 Subject: [PATCH 03/82] =?UTF-8?q?FE-48=20:sparkles:=20=EA=B0=90=EC=A0=95?= =?UTF-8?q?=20=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20ui=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Emotion/card/EmotionIconCard.tsx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/components/Emotion/card/EmotionIconCard.tsx diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/card/EmotionIconCard.tsx new file mode 100644 index 00000000..27342525 --- /dev/null +++ b/src/components/Emotion/card/EmotionIconCard.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import cn from '@/lib/utils'; +import Image from 'next/image'; + +// EmotionIconCardProps 인터페이스 정의 +export interface EmotionIconCardProps { + iconType: '감동' | '기쁨' | '고민' | '슬픔' | '분노'; // 아이콘 종류 + state: 'Default' | 'Unclicked' | 'Clicked'; // 상태 + size: 'sm' | 'md' | 'lg'; // 크기 +} + +// 아이콘 파일 경로 매핑 +const iconPaths = { + Color: { + 감동: '/icon/Color/HeartFaceColorIcon.svg', + 기쁨: '/icon/Color/SmileFaceColorIcon.svg', + 고민: '/icon/Color/ThinkFaceColorIcon.svg', + 슬픔: '/icon/Color/SadFaceColorIcon.svg', + 분노: '/icon/Color/AngryFaceColorIcon.svg', + }, + BW: { + 감동: '/icon/BW/HeartFaceBWIcon.svg', + 기쁨: '/icon/BW/SmileFaceBWIcon.svg', + 고민: '/icon/BW/ThinkFaceBWIcon.svg', + 슬픔: '/icon/BW/SadFaceBWIcon.svg', + 분노: '/icon/BW/AngryFaceBWIcon.svg', + }, +}; + +// EmotionIconCard 컴포넌트 함수 선언 +function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' }: EmotionIconCardProps) { + // 크기에 따른 클래스 설정 + let sizeClass = ''; + let iconSizeClass = ''; + switch (size) { + case 'lg': + sizeClass = 'w-20 h-28'; + iconSizeClass = 'w-12 h-12 p-6'; + break; + case 'md': + sizeClass = 'w-16 h-24'; + iconSizeClass = 'w-10 h-10 p-5'; + break; + case 'sm': + default: + sizeClass = 'w-14 h-[84px]'; + iconSizeClass = 'w-8 h-8 p-4'; + break; + } + + // 상태에 따른 아이콘 경로 설정 + const iconPath = state === 'Default' ? iconPaths.BW[iconType] : iconPaths.Color[iconType]; + + return ( +
+
+ {iconType} +
+
{iconType}
+
+ ); +} + +EmotionIconCard.displayName = 'EmotionIconCard'; + +export { EmotionIconCard }; From fbeab3c15fcda383419721411a72ab2abab1a73e Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 14:11:59 +0900 Subject: [PATCH 04/82] =?UTF-8?q?FE-48=20:sparkles:=20=EA=B0=90=EC=A0=95?= =?UTF-8?q?=20=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Emotion/card/EmotionIconCard.tsx | 65 ++++++++++++++----- src/components/Emotion/card/TestComponent.tsx | 18 +++++ src/pages/index.tsx | 2 + 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 src/components/Emotion/card/TestComponent.tsx diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/card/EmotionIconCard.tsx index 27342525..4fbb0ddc 100644 --- a/src/components/Emotion/card/EmotionIconCard.tsx +++ b/src/components/Emotion/card/EmotionIconCard.tsx @@ -12,18 +12,18 @@ export interface EmotionIconCardProps { // 아이콘 파일 경로 매핑 const iconPaths = { Color: { - 감동: '/icon/Color/HeartFaceColorIcon.svg', - 기쁨: '/icon/Color/SmileFaceColorIcon.svg', - 고민: '/icon/Color/ThinkFaceColorIcon.svg', - 슬픔: '/icon/Color/SadFaceColorIcon.svg', - 분노: '/icon/Color/AngryFaceColorIcon.svg', + 감동: 'icon/Color/HeartFaceColorIcon.svg', + 기쁨: 'icon/Color/SmileFaceColorIcon.svg', + 고민: 'icon/Color/ThinkFaceColorIcon.svg', + 슬픔: 'icon/Color/SadFaceColorIcon.svg', + 분노: 'icon/Color/AngryFaceColorIcon.svg', }, BW: { - 감동: '/icon/BW/HeartFaceBWIcon.svg', - 기쁨: '/icon/BW/SmileFaceBWIcon.svg', - 고민: '/icon/BW/ThinkFaceBWIcon.svg', - 슬픔: '/icon/BW/SadFaceBWIcon.svg', - 분노: '/icon/BW/AngryFaceBWIcon.svg', + 감동: 'icon/BW/HeartFaceBWIcon.svg', + 기쁨: 'icon/BW/SmileFaceBWIcon.svg', + 고민: 'icon/BW/ThinkFaceBWIcon.svg', + 슬픔: 'icon/BW/SadFaceBWIcon.svg', + 분노: 'icon/BW/AngryFaceBWIcon.svg', }, }; @@ -32,14 +32,17 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' } // 크기에 따른 클래스 설정 let sizeClass = ''; let iconSizeClass = ''; + let textSizeClass = ''; switch (size) { case 'lg': sizeClass = 'w-20 h-28'; - iconSizeClass = 'w-12 h-12 p-6'; + iconSizeClass = 'w-12 h-12'; + textSizeClass = 'text-base leading-relaxed'; break; case 'md': sizeClass = 'w-16 h-24'; - iconSizeClass = 'w-10 h-10 p-5'; + iconSizeClass = 'w-10 h-10'; + textSizeClass = 'text-sm leading-normal'; break; case 'sm': default: @@ -49,14 +52,44 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' } } // 상태에 따른 아이콘 경로 설정 - const iconPath = state === 'Default' ? iconPaths.BW[iconType] : iconPaths.Color[iconType]; + const iconPath = state === 'Unclicked' ? iconPaths.BW[iconType] : iconPaths.Color[iconType]; + + // 상태에 따른 클래스 설정 + let borderClass = ''; + let textColorClass = 'text-neutral-400'; + + if (state === 'Clicked') { + textColorClass = 'text-neutral-700'; + + // iconType에 따라 다른 border 색상을 설정 + switch (iconType) { + case '감동': + borderClass = 'border-2 border-amber-300'; + break; + case '기쁨': + borderClass = 'border-2 border-emerald-400'; + break; + case '고민': + borderClass = 'border-2 border-indigo-400'; + break; + case '슬픔': + borderClass = 'border-2 border-blue-400'; + break; + case '분노': + borderClass = 'border-2 border-rose-400'; + break; + default: + borderClass = 'border-2 border-neutral-400'; + break; + } + } return (
-
- {iconType} +
+ {iconType}
-
{iconType}
+
{iconType}
); } diff --git a/src/components/Emotion/card/TestComponent.tsx b/src/components/Emotion/card/TestComponent.tsx new file mode 100644 index 00000000..37f3ad33 --- /dev/null +++ b/src/components/Emotion/card/TestComponent.tsx @@ -0,0 +1,18 @@ +import { EmotionIconCard } from '@/components/Emotion/card/EmotionIconCard'; + +function ExampleComponent() { + return ( +
+ {/* 기본 상태 (Default), 감동 아이콘, 작은 크기 (sm) */} + + + {/* 클릭되지 않은 상태 (Unclicked), 기쁨 아이콘, 중간 크기 (md) */} + + + {/* 클릭된 상태 (Clicked), 슬픔 아이콘, 큰 크기 (lg) */} + +
+ ); +} + +export default ExampleComponent; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6a3a652a..29cd325b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,6 @@ import Head from 'next/head'; import { useEffect, useState } from 'react'; +import ExampleComponent from '@/components/Emotion/card/TestComponent'; export default function Home() { // NOTE: 테스트를 위해서 typescript-eslint/no-unused-vars도 잠시 끔! @@ -32,6 +33,7 @@ export default function Home() {

Pretendard

Iropke Batang

+ ); From 400cfb56868b2e7c20df6e65771848abd96f991c Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 14:27:12 +0900 Subject: [PATCH 05/82] =?UTF-8?q?FE-48=20:lipstick:=20=EA=B0=90=EC=A0=95?= =?UTF-8?q?=20=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20ui=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/icon/Color/HeartFacedColorIcon.svg | 4 ++-- .../Emotion/card/EmotionIconCard.tsx | 19 ++++++++++--------- src/components/Emotion/card/TestComponent.tsx | 4 ++++ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/public/icon/Color/HeartFacedColorIcon.svg b/public/icon/Color/HeartFacedColorIcon.svg index 9ef30c50..9db0bbd0 100644 --- a/public/icon/Color/HeartFacedColorIcon.svg +++ b/public/icon/Color/HeartFacedColorIcon.svg @@ -1,12 +1,12 @@ - + - + diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/card/EmotionIconCard.tsx index 4fbb0ddc..bbfaadcc 100644 --- a/src/components/Emotion/card/EmotionIconCard.tsx +++ b/src/components/Emotion/card/EmotionIconCard.tsx @@ -46,8 +46,9 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' } break; case 'sm': default: - sizeClass = 'w-14 h-[84px]'; - iconSizeClass = 'w-8 h-8 p-4'; + sizeClass = 'w-14 h-21'; + iconSizeClass = 'w-8 h-8'; + textSizeClass = 'text-xs leading-tight'; break; } @@ -59,27 +60,27 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' } let textColorClass = 'text-neutral-400'; if (state === 'Clicked') { - textColorClass = 'text-neutral-700'; + textColorClass = 'hidden'; // iconType에 따라 다른 border 색상을 설정 switch (iconType) { case '감동': - borderClass = 'border-2 border-amber-300'; + borderClass = 'border-4 border-illust-yellow'; break; case '기쁨': - borderClass = 'border-2 border-emerald-400'; + borderClass = 'border-4 border-illust-green'; break; case '고민': - borderClass = 'border-2 border-indigo-400'; + borderClass = 'border-4 border-illust-purple'; break; case '슬픔': - borderClass = 'border-2 border-blue-400'; + borderClass = 'border-4 border-illust-blue'; break; case '분노': - borderClass = 'border-2 border-rose-400'; + borderClass = 'border-4 border-illust-red'; break; default: - borderClass = 'border-2 border-neutral-400'; + borderClass = 'border-4 border-sub_blue_1'; break; } } diff --git a/src/components/Emotion/card/TestComponent.tsx b/src/components/Emotion/card/TestComponent.tsx index 37f3ad33..2a7758ac 100644 --- a/src/components/Emotion/card/TestComponent.tsx +++ b/src/components/Emotion/card/TestComponent.tsx @@ -10,7 +10,11 @@ function ExampleComponent() { {/* 클릭된 상태 (Clicked), 슬픔 아이콘, 큰 크기 (lg) */} + + + +
); } From 830601e03f666019ee405cd03c53010c9d2b0baf Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 14:57:45 +0900 Subject: [PATCH 06/82] =?UTF-8?q?FE-48=20:sparkles:=20=EA=B0=90=EC=A0=95?= =?UTF-8?q?=20=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EmotionIconCardContainer를 사용해 상태관리와 이벤트 처리 (Clicked<->UnClicked) --- .../Emotion/card/EmotionIconCard.tsx | 53 +++++++++++++------ .../Emotion/card/EmotionIconCardContainer.tsx | 20 +++++++ src/components/Emotion/card/TestComponent.tsx | 17 +++--- 3 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 src/components/Emotion/card/EmotionIconCardContainer.tsx diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/card/EmotionIconCard.tsx index bbfaadcc..3bc25735 100644 --- a/src/components/Emotion/card/EmotionIconCard.tsx +++ b/src/components/Emotion/card/EmotionIconCard.tsx @@ -7,28 +7,29 @@ export interface EmotionIconCardProps { iconType: '감동' | '기쁨' | '고민' | '슬픔' | '분노'; // 아이콘 종류 state: 'Default' | 'Unclicked' | 'Clicked'; // 상태 size: 'sm' | 'md' | 'lg'; // 크기 + onClick?: () => void; // 클릭 이벤트 핸들러 } // 아이콘 파일 경로 매핑 const iconPaths = { Color: { - 감동: 'icon/Color/HeartFaceColorIcon.svg', - 기쁨: 'icon/Color/SmileFaceColorIcon.svg', - 고민: 'icon/Color/ThinkFaceColorIcon.svg', - 슬픔: 'icon/Color/SadFaceColorIcon.svg', - 분노: 'icon/Color/AngryFaceColorIcon.svg', + 감동: '/icon/Color/HeartFaceColorIcon.svg', + 기쁨: '/icon/Color/SmileFaceColorIcon.svg', + 고민: '/icon/Color/ThinkFaceColorIcon.svg', + 슬픔: '/icon/Color/SadFaceColorIcon.svg', + 분노: '/icon/Color/AngryFaceColorIcon.svg', }, BW: { - 감동: 'icon/BW/HeartFaceBWIcon.svg', - 기쁨: 'icon/BW/SmileFaceBWIcon.svg', - 고민: 'icon/BW/ThinkFaceBWIcon.svg', - 슬픔: 'icon/BW/SadFaceBWIcon.svg', - 분노: 'icon/BW/AngryFaceBWIcon.svg', + 감동: '/icon/BW/HeartFaceBWIcon.svg', + 기쁨: '/icon/BW/SmileFaceBWIcon.svg', + 고민: '/icon/BW/ThinkFaceBWIcon.svg', + 슬픔: '/icon/BW/SadFaceBWIcon.svg', + 분노: '/icon/BW/AngryFaceBWIcon.svg', }, }; // EmotionIconCard 컴포넌트 함수 선언 -function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' }: EmotionIconCardProps) { +function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm', onClick }: EmotionIconCardProps) { // 크기에 따른 클래스 설정 let sizeClass = ''; let iconSizeClass = ''; @@ -57,10 +58,13 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' } // 상태에 따른 클래스 설정 let borderClass = ''; - let textColorClass = 'text-neutral-400'; + const textColorClass = 'text-neutral-400'; + let backgroundClass = 'bg-slate-400/20'; + let textVisibilityClass = ''; if (state === 'Clicked') { - textColorClass = 'hidden'; + textVisibilityClass = 'hidden'; + backgroundClass = 'bg-transparent'; // iconType에 따라 다른 border 색상을 설정 switch (iconType) { @@ -86,15 +90,32 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm' } } return ( -
-
+
{ + if (e.key === 'Enter' || e.key === ' ') { + if (onClick) { + onClick(); + } + } + }} + > +
{iconType}
-
{iconType}
+
{iconType}
); } EmotionIconCard.displayName = 'EmotionIconCard'; +// 기본 props 설정 +EmotionIconCard.defaultProps = { + onClick: () => {}, +}; + export { EmotionIconCard }; diff --git a/src/components/Emotion/card/EmotionIconCardContainer.tsx b/src/components/Emotion/card/EmotionIconCardContainer.tsx new file mode 100644 index 00000000..8fcfa080 --- /dev/null +++ b/src/components/Emotion/card/EmotionIconCardContainer.tsx @@ -0,0 +1,20 @@ +import React, { useState } from 'react'; +import { EmotionIconCard, EmotionIconCardProps } from '@/components/Emotion/card/EmotionIconCard'; + +// EmotionIconCardContainerProps 인터페이스 정의 +interface EmotionIconCardContainerProps extends Omit {} + +// EmotionIconCardContainer 컴포넌트 함수 선언 +function EmotionIconCardContainer(props: EmotionIconCardContainerProps) { + const [state, setState] = useState<'Default' | 'Unclicked' | 'Clicked'>('Unclicked'); + + const handleClick = () => { + setState((prevState) => (prevState === 'Unclicked' ? 'Clicked' : 'Unclicked')); + }; + + return ; +} + +EmotionIconCardContainer.displayName = 'EmotionIconCardContainer'; + +export default EmotionIconCardContainer; diff --git a/src/components/Emotion/card/TestComponent.tsx b/src/components/Emotion/card/TestComponent.tsx index 2a7758ac..b720b503 100644 --- a/src/components/Emotion/card/TestComponent.tsx +++ b/src/components/Emotion/card/TestComponent.tsx @@ -1,20 +1,21 @@ -import { EmotionIconCard } from '@/components/Emotion/card/EmotionIconCard'; +import React from 'react'; +import EmotionIconCardContainer from '@/components/Emotion/card/EmotionIconCardContainer'; function ExampleComponent() { return (
{/* 기본 상태 (Default), 감동 아이콘, 작은 크기 (sm) */} - + {/* 클릭되지 않은 상태 (Unclicked), 기쁨 아이콘, 중간 크기 (md) */} - + {/* 클릭된 상태 (Clicked), 슬픔 아이콘, 큰 크기 (lg) */} - - - - - + + + + +
); } From 991b355907a4d5ea3ca5788cd58d616e1fc4b14f Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 15:01:59 +0900 Subject: [PATCH 07/82] =?UTF-8?q?FE-48=20=F0=9F=93=9D=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 명확한 의미 전달을 위해 컴포넌트 이름 변경 --- ...dContainer.tsx => InteractiveEmotionIconCard.tsx} | 12 ++++++------ src/components/Emotion/card/TestComponent.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) rename src/components/Emotion/card/{EmotionIconCardContainer.tsx => InteractiveEmotionIconCard.tsx} (52%) diff --git a/src/components/Emotion/card/EmotionIconCardContainer.tsx b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx similarity index 52% rename from src/components/Emotion/card/EmotionIconCardContainer.tsx rename to src/components/Emotion/card/InteractiveEmotionIconCard.tsx index 8fcfa080..1584b204 100644 --- a/src/components/Emotion/card/EmotionIconCardContainer.tsx +++ b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import { EmotionIconCard, EmotionIconCardProps } from '@/components/Emotion/card/EmotionIconCard'; -// EmotionIconCardContainerProps 인터페이스 정의 -interface EmotionIconCardContainerProps extends Omit {} +// InteractiveEmotionIconCardProps 인터페이스 정의 +interface InteractiveEmotionIconCardProps extends Omit {} -// EmotionIconCardContainer 컴포넌트 함수 선언 -function EmotionIconCardContainer(props: EmotionIconCardContainerProps) { +// InteractiveEmotionIconCard 컴포넌트 함수 선언 +function InteractiveEmotionIconCard(props: InteractiveEmotionIconCardProps) { const [state, setState] = useState<'Default' | 'Unclicked' | 'Clicked'>('Unclicked'); const handleClick = () => { @@ -15,6 +15,6 @@ function EmotionIconCardContainer(props: EmotionIconCardContainerProps) { return ; } -EmotionIconCardContainer.displayName = 'EmotionIconCardContainer'; +InteractiveEmotionIconCard.displayName = 'InteractiveEmotionIconCard'; -export default EmotionIconCardContainer; +export default InteractiveEmotionIconCard; diff --git a/src/components/Emotion/card/TestComponent.tsx b/src/components/Emotion/card/TestComponent.tsx index b720b503..5ae19ce2 100644 --- a/src/components/Emotion/card/TestComponent.tsx +++ b/src/components/Emotion/card/TestComponent.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import EmotionIconCardContainer from '@/components/Emotion/card/EmotionIconCardContainer'; +import EmotionIconCardContainer from '@/components/Emotion/card/InteractiveEmotionIconCard'; function ExampleComponent() { return ( From 9819c6da96f18714fa27e979696215bf7d3074a8 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 16:04:09 +0900 Subject: [PATCH 08/82] =?UTF-8?q?FE-48=20:sparkles:=20=EA=B0=90=EC=A0=95?= =?UTF-8?q?=20=EC=9D=B4=EB=AA=A8=ED=8B=B0=EC=BD=98=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=80=ED=99=94=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 감정 카드를 클릭할 때 상태가 올바르게 전환되고, 다른 카드의 상태도 동기화되는 기능 구현 --- .../Emotion/card/EmotionIconCard.tsx | 2 +- .../Emotion/card/EmotionSelector.tsx | 45 +++++++++++++++++++ .../card/InteractiveEmotionIconCard.tsx | 15 +++---- src/components/Emotion/card/TestComponent.tsx | 15 +------ 4 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 src/components/Emotion/card/EmotionSelector.tsx diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/card/EmotionIconCard.tsx index 3bc25735..a5b6d887 100644 --- a/src/components/Emotion/card/EmotionIconCard.tsx +++ b/src/components/Emotion/card/EmotionIconCard.tsx @@ -54,7 +54,7 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm', } // 상태에 따른 아이콘 경로 설정 - const iconPath = state === 'Unclicked' ? iconPaths.BW[iconType] : iconPaths.Color[iconType]; + const iconPath = state === 'Clicked' || state === 'Default' ? iconPaths.Color[iconType] : iconPaths.BW[iconType]; // 상태에 따른 클래스 설정 let borderClass = ''; diff --git a/src/components/Emotion/card/EmotionSelector.tsx b/src/components/Emotion/card/EmotionSelector.tsx new file mode 100644 index 00000000..50447987 --- /dev/null +++ b/src/components/Emotion/card/EmotionSelector.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import InteractiveEmotionIconCard from '@/components/Emotion/card/InteractiveEmotionIconCard'; + +// EmotionSelector 컴포넌트 함수 선언 +function EmotionSelector() { + // 감정 카드 상태 관리 + const [states, setStates] = useState({ + 감동: 'Default' as 'Default' | 'Unclicked' | 'Clicked', + 기쁨: 'Default' as 'Default' | 'Unclicked' | 'Clicked', + 고민: 'Default' as 'Default' | 'Unclicked' | 'Clicked', + 슬픔: 'Default' as 'Default' | 'Unclicked' | 'Clicked', + 분노: 'Default' as 'Default' | 'Unclicked' | 'Clicked', + }); + + // 감정 카드 클릭 핸들러 + const handleCardClick = (iconType: '감동' | '기쁨' | '고민' | '슬픔' | '분노') => { + setStates((prevStates) => { + const newStates = { ...prevStates }; + + if (prevStates[iconType] === 'Clicked') { + // 현재 클릭된 카드가 다시 클릭되면 모두 Default로 설정 + Object.keys(newStates).forEach((key) => { + newStates[key as '감동' | '기쁨' | '고민' | '슬픔' | '분노'] = 'Default'; + }); + } else { + // 하나의 카드가 클릭되면 그 카드만 Clicked, 나머지는 Unclicked로 설정 + Object.keys(newStates).forEach((key) => { + newStates[key as '감동' | '기쁨' | '고민' | '슬픔' | '분노'] = key === iconType ? 'Clicked' : 'Unclicked'; + }); + } + + return newStates; + }); + }; + + return ( +
+ {(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => ( + handleCardClick(iconType)} /> + ))} +
+ ); +} + +export default EmotionSelector; diff --git a/src/components/Emotion/card/InteractiveEmotionIconCard.tsx b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx index 1584b204..8993e1ec 100644 --- a/src/components/Emotion/card/InteractiveEmotionIconCard.tsx +++ b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx @@ -1,18 +1,15 @@ -import React, { useState } from 'react'; +import React from 'react'; import { EmotionIconCard, EmotionIconCardProps } from '@/components/Emotion/card/EmotionIconCard'; // InteractiveEmotionIconCardProps 인터페이스 정의 -interface InteractiveEmotionIconCardProps extends Omit {} +interface InteractiveEmotionIconCardProps extends Omit { + state: 'Default' | 'Unclicked' | 'Clicked'; + onClick: () => void; +} // InteractiveEmotionIconCard 컴포넌트 함수 선언 function InteractiveEmotionIconCard(props: InteractiveEmotionIconCardProps) { - const [state, setState] = useState<'Default' | 'Unclicked' | 'Clicked'>('Unclicked'); - - const handleClick = () => { - setState((prevState) => (prevState === 'Unclicked' ? 'Clicked' : 'Unclicked')); - }; - - return ; + return ; } InteractiveEmotionIconCard.displayName = 'InteractiveEmotionIconCard'; diff --git a/src/components/Emotion/card/TestComponent.tsx b/src/components/Emotion/card/TestComponent.tsx index 5ae19ce2..d97a4031 100644 --- a/src/components/Emotion/card/TestComponent.tsx +++ b/src/components/Emotion/card/TestComponent.tsx @@ -1,21 +1,10 @@ import React from 'react'; -import EmotionIconCardContainer from '@/components/Emotion/card/InteractiveEmotionIconCard'; +import EmotionSelector from '@/components/Emotion/card/EmotionSelector'; function ExampleComponent() { return (
- {/* 기본 상태 (Default), 감동 아이콘, 작은 크기 (sm) */} - - - {/* 클릭되지 않은 상태 (Unclicked), 기쁨 아이콘, 중간 크기 (md) */} - - - {/* 클릭된 상태 (Clicked), 슬픔 아이콘, 큰 크기 (lg) */} - - - - - +
); } From 9986486e987d6eabfb87068736d55db8cd122f40 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 16:13:01 +0900 Subject: [PATCH 09/82] =?UTF-8?q?FE-48=20:sparkles:=20EmotionSelector=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=8F=99=EC=A0=81=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EB=B3=80=EA=B2=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useMediaQuery 훅 생성: 화면의 크기가 변경될 때마다 리스너 추가 및 제거 --- .../Emotion/card/EmotionSelector.tsx | 19 +++++++++++++++++-- src/hooks/useMediaQuery.ts | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useMediaQuery.ts diff --git a/src/components/Emotion/card/EmotionSelector.tsx b/src/components/Emotion/card/EmotionSelector.tsx index 50447987..1726c11e 100644 --- a/src/components/Emotion/card/EmotionSelector.tsx +++ b/src/components/Emotion/card/EmotionSelector.tsx @@ -1,8 +1,12 @@ import React, { useState } from 'react'; import InteractiveEmotionIconCard from '@/components/Emotion/card/InteractiveEmotionIconCard'; +import useMediaQuery from '@/hooks/useMediaQuery'; // EmotionSelector 컴포넌트 함수 선언 function EmotionSelector() { + const isTablet = useMediaQuery('(min-width: 768px) and (max-width: 1024px)'); + const isMobile = useMediaQuery('(max-width: 767px)'); + // 감정 카드 상태 관리 const [states, setStates] = useState({ 감동: 'Default' as 'Default' | 'Unclicked' | 'Clicked', @@ -33,10 +37,21 @@ function EmotionSelector() { }); }; + let containerClass = 'w-[544px] h-[136px] gap-4'; + let cardSize: 'lg' | 'md' | 'sm' = 'lg'; + + if (isTablet) { + containerClass = 'w-[352px] h-[96px] gap-2'; + cardSize = 'md'; + } else if (isMobile) { + containerClass = 'w-[312px] h-[84px] gap-2'; + cardSize = 'sm'; + } + return ( -
+
{(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => ( - handleCardClick(iconType)} /> + handleCardClick(iconType)} /> ))}
); diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts new file mode 100644 index 00000000..899c0fce --- /dev/null +++ b/src/hooks/useMediaQuery.ts @@ -0,0 +1,19 @@ +import { useState, useEffect } from 'react'; + +function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + if (media.matches !== matches) { + setMatches(media.matches); + } + const listener = () => setMatches(media.matches); + media.addEventListener('change', listener); + return () => media.removeEventListener('change', listener); + }, [matches, query]); + + return matches; +} + +export default useMediaQuery; From 14063df54ae9679e1d6e70b56d5aa1cf6bece329 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 16:16:08 +0900 Subject: [PATCH 10/82] =?UTF-8?q?FE-48=20:fire:=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Emotion/card/TestComponent.tsx | 12 ------------ src/pages/index.tsx | 2 -- 2 files changed, 14 deletions(-) delete mode 100644 src/components/Emotion/card/TestComponent.tsx diff --git a/src/components/Emotion/card/TestComponent.tsx b/src/components/Emotion/card/TestComponent.tsx deleted file mode 100644 index d97a4031..00000000 --- a/src/components/Emotion/card/TestComponent.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import EmotionSelector from '@/components/Emotion/card/EmotionSelector'; - -function ExampleComponent() { - return ( -
- -
- ); -} - -export default ExampleComponent; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 29cd325b..6a3a652a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,5 @@ import Head from 'next/head'; import { useEffect, useState } from 'react'; -import ExampleComponent from '@/components/Emotion/card/TestComponent'; export default function Home() { // NOTE: 테스트를 위해서 typescript-eslint/no-unused-vars도 잠시 끔! @@ -33,7 +32,6 @@ export default function Home() {

Pretendard

Iropke Batang

- ); From cc00d3c2f9b7cf65a2cf97075bbb47fe3e4bd784 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Wed, 10 Jul 2024 16:52:03 +0900 Subject: [PATCH 11/82] =?UTF-8?q?FE-48=20:hammer:=20EmotionTypes=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit emotion 관련 컴포넌트에서 해당 인터페이스를 import하여 사용하게 구현 --- ...artFacedBWIcon.svg => HeartFaceBWIcon.svg} | 0 ...edColorIcon.svg => HeartFaceColorIcon.svg} | 0 .../Emotion/card/EmotionIconCard.tsx | 11 ++--------- .../Emotion/card/EmotionSelector.tsx | 19 ++++++++++--------- .../card/InteractiveEmotionIconCard.tsx | 9 ++------- src/types/EmotionTypes.ts | 14 ++++++++++++++ 6 files changed, 28 insertions(+), 25 deletions(-) rename public/icon/BW/{HeartFacedBWIcon.svg => HeartFaceBWIcon.svg} (100%) rename public/icon/Color/{HeartFacedColorIcon.svg => HeartFaceColorIcon.svg} (100%) create mode 100644 src/types/EmotionTypes.ts diff --git a/public/icon/BW/HeartFacedBWIcon.svg b/public/icon/BW/HeartFaceBWIcon.svg similarity index 100% rename from public/icon/BW/HeartFacedBWIcon.svg rename to public/icon/BW/HeartFaceBWIcon.svg diff --git a/public/icon/Color/HeartFacedColorIcon.svg b/public/icon/Color/HeartFaceColorIcon.svg similarity index 100% rename from public/icon/Color/HeartFacedColorIcon.svg rename to public/icon/Color/HeartFaceColorIcon.svg diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/card/EmotionIconCard.tsx index a5b6d887..a230deec 100644 --- a/src/components/Emotion/card/EmotionIconCard.tsx +++ b/src/components/Emotion/card/EmotionIconCard.tsx @@ -1,14 +1,7 @@ import React from 'react'; import cn from '@/lib/utils'; import Image from 'next/image'; - -// EmotionIconCardProps 인터페이스 정의 -export interface EmotionIconCardProps { - iconType: '감동' | '기쁨' | '고민' | '슬픔' | '분노'; // 아이콘 종류 - state: 'Default' | 'Unclicked' | 'Clicked'; // 상태 - size: 'sm' | 'md' | 'lg'; // 크기 - onClick?: () => void; // 클릭 이벤트 핸들러 -} +import { EmotionIconCardProps } from '@/types/EmotionTypes'; // 아이콘 파일 경로 매핑 const iconPaths = { @@ -118,4 +111,4 @@ EmotionIconCard.defaultProps = { onClick: () => {}, }; -export { EmotionIconCard }; +export default EmotionIconCard; diff --git a/src/components/Emotion/card/EmotionSelector.tsx b/src/components/Emotion/card/EmotionSelector.tsx index 1726c11e..29b3f104 100644 --- a/src/components/Emotion/card/EmotionSelector.tsx +++ b/src/components/Emotion/card/EmotionSelector.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import InteractiveEmotionIconCard from '@/components/Emotion/card/InteractiveEmotionIconCard'; import useMediaQuery from '@/hooks/useMediaQuery'; +import { EmotionType, EmotionState } from '@/types/EmotionTypes'; // EmotionSelector 컴포넌트 함수 선언 function EmotionSelector() { @@ -8,28 +9,28 @@ function EmotionSelector() { const isMobile = useMediaQuery('(max-width: 767px)'); // 감정 카드 상태 관리 - const [states, setStates] = useState({ - 감동: 'Default' as 'Default' | 'Unclicked' | 'Clicked', - 기쁨: 'Default' as 'Default' | 'Unclicked' | 'Clicked', - 고민: 'Default' as 'Default' | 'Unclicked' | 'Clicked', - 슬픔: 'Default' as 'Default' | 'Unclicked' | 'Clicked', - 분노: 'Default' as 'Default' | 'Unclicked' | 'Clicked', + const [states, setStates] = useState>({ + 감동: 'Default', + 기쁨: 'Default', + 고민: 'Default', + 슬픔: 'Default', + 분노: 'Default', }); // 감정 카드 클릭 핸들러 - const handleCardClick = (iconType: '감동' | '기쁨' | '고민' | '슬픔' | '분노') => { + const handleCardClick = (iconType: EmotionType) => { setStates((prevStates) => { const newStates = { ...prevStates }; if (prevStates[iconType] === 'Clicked') { // 현재 클릭된 카드가 다시 클릭되면 모두 Default로 설정 Object.keys(newStates).forEach((key) => { - newStates[key as '감동' | '기쁨' | '고민' | '슬픔' | '분노'] = 'Default'; + newStates[key as EmotionType] = 'Default'; }); } else { // 하나의 카드가 클릭되면 그 카드만 Clicked, 나머지는 Unclicked로 설정 Object.keys(newStates).forEach((key) => { - newStates[key as '감동' | '기쁨' | '고민' | '슬픔' | '분노'] = key === iconType ? 'Clicked' : 'Unclicked'; + newStates[key as EmotionType] = key === iconType ? 'Clicked' : 'Unclicked'; }); } diff --git a/src/components/Emotion/card/InteractiveEmotionIconCard.tsx b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx index 8993e1ec..f8448315 100644 --- a/src/components/Emotion/card/InteractiveEmotionIconCard.tsx +++ b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx @@ -1,11 +1,6 @@ import React from 'react'; -import { EmotionIconCard, EmotionIconCardProps } from '@/components/Emotion/card/EmotionIconCard'; - -// InteractiveEmotionIconCardProps 인터페이스 정의 -interface InteractiveEmotionIconCardProps extends Omit { - state: 'Default' | 'Unclicked' | 'Clicked'; - onClick: () => void; -} +import EmotionIconCard from '@/components/Emotion/card/EmotionIconCard'; +import { InteractiveEmotionIconCardProps } from '@/types/EmotionTypes'; // InteractiveEmotionIconCard 컴포넌트 함수 선언 function InteractiveEmotionIconCard(props: InteractiveEmotionIconCardProps) { diff --git a/src/types/EmotionTypes.ts b/src/types/EmotionTypes.ts new file mode 100644 index 00000000..e64f3d3f --- /dev/null +++ b/src/types/EmotionTypes.ts @@ -0,0 +1,14 @@ +export type EmotionType = '감동' | '기쁨' | '고민' | '슬픔' | '분노'; +export type EmotionState = 'Default' | 'Unclicked' | 'Clicked'; + +export interface EmotionIconCardProps { + iconType: EmotionType; // 아이콘 종류 + state: EmotionState; // 상태 + size: 'sm' | 'md' | 'lg'; // 크기 + onClick?: () => void; // 클릭 이벤트 핸들러 +} + +export interface InteractiveEmotionIconCardProps extends Omit { + state: EmotionState; + onClick: () => void; +} From 8ee83af7616228c106f48378eb7dde74db62d34f Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 10 Jul 2024 21:26:24 +0900 Subject: [PATCH 12/82] =?UTF-8?q?:heavy=5Fplus=5Fsign:=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/lg.svg | 5 +++++ public/logo-google.svg | 15 +++++++++++++++ public/logo-kakao.svg | 13 +++++++++++++ public/logo-naver.svg | 5 +++++ 4 files changed, 38 insertions(+) create mode 100644 public/lg.svg create mode 100644 public/logo-google.svg create mode 100644 public/logo-kakao.svg create mode 100644 public/logo-naver.svg diff --git a/public/lg.svg b/public/lg.svg new file mode 100644 index 00000000..a4d3364f --- /dev/null +++ b/public/lg.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/logo-google.svg b/public/logo-google.svg new file mode 100644 index 00000000..5b169484 --- /dev/null +++ b/public/logo-google.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/logo-kakao.svg b/public/logo-kakao.svg new file mode 100644 index 00000000..f546e64d --- /dev/null +++ b/public/logo-kakao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/logo-naver.svg b/public/logo-naver.svg new file mode 100644 index 00000000..dbec93dd --- /dev/null +++ b/public/logo-naver.svg @@ -0,0 +1,5 @@ + + + + + From f472f6fb7eb7d41c560afe2f733df6dd620f9cf5 Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 10 Jul 2024 21:27:08 +0900 Subject: [PATCH 13/82] =?UTF-8?q?:lipstick:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pageLayout/AuthLayout/AuthLayout.tsx | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/pageLayout/AuthLayout/AuthLayout.tsx diff --git a/src/pageLayout/AuthLayout/AuthLayout.tsx b/src/pageLayout/AuthLayout/AuthLayout.tsx new file mode 100644 index 00000000..3cc64aa7 --- /dev/null +++ b/src/pageLayout/AuthLayout/AuthLayout.tsx @@ -0,0 +1,5 @@ +import { ReactNode } from 'react'; + +export default function AuthLayout({ children }: { children: ReactNode }) { + return
{children}
; +} From 5b33ad70bc16cf53b1fef8c01cc7e0c28c119821 Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 10 Jul 2024 21:27:49 +0900 Subject: [PATCH 14/82] =?UTF-8?q?:lipstick:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=98=EC=9D=91=ED=98=95=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignIn.tsx | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/pages/auth/SignIn.tsx diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx new file mode 100644 index 00000000..488e13a0 --- /dev/null +++ b/src/pages/auth/SignIn.tsx @@ -0,0 +1,50 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import AuthLayout from '@/pageLayout/AuthLayout/AuthLayout'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; + +export default function SignIn() { + return ( + +
+ + logo + +
+ +
+ + + +
+ +
+

회원이 아니신가요?

+ + + +
+ +
+ + + +
+
+ ); +} From 8d83f8ec4be0d72a45aae680d06d7b1e27186d38 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 15:16:50 +0900 Subject: [PATCH 15/82] =?UTF-8?q?FE-59=20:sparkles:=20=EC=97=90=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EC=B9=B4=EB=93=9C=20ui=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tailwind css를 확장해 줄무늬 배경 이미지 구현 --- src/components/Card/EpigramCard.tsx | 37 +++++++++++++++++++++++++++++ src/pages/index.tsx | 3 +++ tailwind.config.js | 3 +++ 3 files changed, 43 insertions(+) create mode 100644 src/components/Card/EpigramCard.tsx diff --git a/src/components/Card/EpigramCard.tsx b/src/components/Card/EpigramCard.tsx new file mode 100644 index 00000000..8814b63c --- /dev/null +++ b/src/components/Card/EpigramCard.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface EpigramCardProps { + size: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'; +} + +const sizeStyles = { + sm: 'w-[286px] max-h-[132px]', + md: 'w-[312px] max-h-[152px]', + lg: 'w-[384px] max-h-[180px]', + xl: 'w-[540px] max-h-[160px]', + '2xl': 'w-[640px] max-h-[196px]', + '3xl': 'w-[744px] max-h-[196px]', +}; + +function EpigramCard({ size }: EpigramCardProps) { + return ( +
+
+ {/* eslint-disable-next-line */} +
{/* 줄무늬를 만들려면 비어있는 div가 필요 */} +
+
+
오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.
+
- 앙드레 말로 -
+
+
+
+
+
#나아가야할때
+
#꿈을이루고싶을때
+
+
+ ); +} + +export default EpigramCard; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6a3a652a..f47e5610 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,6 @@ import Head from 'next/head'; import { useEffect, useState } from 'react'; +import EpigramCard from '@/components/Card/EpigramCard'; export default function Home() { // NOTE: 테스트를 위해서 typescript-eslint/no-unused-vars도 잠시 끔! @@ -32,6 +33,8 @@ export default function Home() {

Pretendard

Iropke Batang

+ + ); diff --git a/tailwind.config.js b/tailwind.config.js index 1c497d8c..6360fdd7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -50,6 +50,9 @@ module.exports = { 'sub-gray_2': '#E3E9F1', 'sub-gray_3': '#EFF3F8', }, + backgroundImage: { + 'stripes': 'repeating-linear-gradient(to bottom, #ffffff, #ffffff 23px, #e5e7eb 23px, #e5e7eb 24px)', + }, }, }, plugins: [], From add618b816f2e9a42b9ce30f6eebcada53034997 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 16:01:57 +0900 Subject: [PATCH 16/82] =?UTF-8?q?FE-59=20:sparkles:=20=EC=97=90=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EC=B9=B4=EB=93=9C=20=EB=B0=98=EC=9D=91?= =?UTF-8?q?=ED=98=84=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/EpigramCard.tsx | 61 ++++++++++++++++++++--------- src/pages/index.tsx | 2 +- tailwind.config.js | 7 ++++ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/components/Card/EpigramCard.tsx b/src/components/Card/EpigramCard.tsx index 8814b63c..d8e959af 100644 --- a/src/components/Card/EpigramCard.tsx +++ b/src/components/Card/EpigramCard.tsx @@ -1,34 +1,57 @@ import React from 'react'; -interface EpigramCardProps { - size: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'; -} - +// figma 상으로는 sm ~ 3xl 사이즈로 구현되어 있는데, tailwind 환경을 반영해 +// base ~ 2xl 으로 정의했습니다. const sizeStyles = { - sm: 'w-[286px] max-h-[132px]', - md: 'w-[312px] max-h-[152px]', - lg: 'w-[384px] max-h-[180px]', - xl: 'w-[540px] max-h-[160px]', - '2xl': 'w-[640px] max-h-[196px]', - '3xl': 'w-[744px] max-h-[196px]', + base: 'w-[286px] max-h-[132px]', + sm: 'sm:w-[312px] sm:max-h-[152px]', + md: 'md:w-[384px] md:max-h-[180px]', + lg: 'lg:w-[540px] lg:max-h-[160px]', + xl: 'xl:w-[640px] xl:max-h-[196px]', + '2xl': '2xl:w-[744px] 2xl:max-h-[196px]', +}; + +const textSizeStyles = { + base: 'text-xs', + sm: 'sm:text-sm', + md: 'md:text-base', + lg: 'lg:text-xl', + xl: 'xl:text-2xl', + '2xl': '2xl:text-2xl', }; -function EpigramCard({ size }: EpigramCardProps) { +function EpigramCard() { return ( -
-
+
+
{/* eslint-disable-next-line */} -
{/* 줄무늬를 만들려면 비어있는 div가 필요 */} +
{/* 줄무늬를 만들려면 비어있는 div가 필요합니다. */}
-
-
오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.
-
- 앙드레 말로 -
+
+
+ 오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다. +
+
+ - 앙드레 말로 - +
-
#나아가야할때
-
#꿈을이루고싶을때
+
+ #나아가야할때 +
+
+ #꿈을이루고싶을때 +
); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f47e5610..b198cb56 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -34,7 +34,7 @@ export default function Home() {

Pretendard

Iropke Batang

- + ); diff --git a/tailwind.config.js b/tailwind.config.js index 6360fdd7..7274ffb8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -50,6 +50,13 @@ module.exports = { 'sub-gray_2': '#E3E9F1', 'sub-gray_3': '#EFF3F8', }, + screens: { + 'sm': '640px', + 'md': '768px', + 'lg': '1024px', + 'xl': '1280px', + '2xl': '1536px', + }, backgroundImage: { 'stripes': 'repeating-linear-gradient(to bottom, #ffffff, #ffffff 23px, #e5e7eb 23px, #e5e7eb 24px)', }, From 8381a0595a2f5eec9599dbdbf7631982a37d64bb Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 16:07:12 +0900 Subject: [PATCH 17/82] =?UTF-8?q?FE-59=20:lipstick:=20=EC=97=90=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EC=B9=B4=EB=93=9C=20=EA=B8=80=EC=94=A8?= =?UTF-8?q?=EC=B2=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/EpigramCard.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Card/EpigramCard.tsx b/src/components/Card/EpigramCard.tsx index d8e959af..6fb71f86 100644 --- a/src/components/Card/EpigramCard.tsx +++ b/src/components/Card/EpigramCard.tsx @@ -29,12 +29,12 @@ function EpigramCard() {
- 오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다. + 오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.
- 앙드레 말로 -
@@ -43,12 +43,12 @@ function EpigramCard() {
#나아가야할때
#꿈을이루고싶을때
From 5e0f15a03d18258566c3ec2a74b5491f65c4eb2a Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 16:07:50 +0900 Subject: [PATCH 18/82] =?UTF-8?q?FE-59=20:fire:=20=EC=97=90=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EC=B9=B4=EB=93=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b198cb56..f3993453 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,5 @@ import Head from 'next/head'; import { useEffect, useState } from 'react'; -import EpigramCard from '@/components/Card/EpigramCard'; export default function Home() { // NOTE: 테스트를 위해서 typescript-eslint/no-unused-vars도 잠시 끔! @@ -30,11 +29,8 @@ export default function Home() {
Epigram Main
-

Pretendard

-

Iropke Batang

- - +

Iropke Batang

{' '}
); From 929abb8fd030d7b1ad8b3943515a739daa467d22 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 16:17:40 +0900 Subject: [PATCH 19/82] =?UTF-8?q?FE-59=20:fire:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=9D=94=EC=A0=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index f3993453..1bbe9ac9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -30,7 +30,7 @@ export default function Home() {
Epigram Main

Pretendard

-

Iropke Batang

{' '} +

Iropke Batang

); From 0d964a442f6f784fe93fdc9a90b83fbddda63d73 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 17:25:18 +0900 Subject: [PATCH 20/82] =?UTF-8?q?FE-58=20:sparkles:=20=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EA=B8=B0=EB=B3=B8=20ui=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/ProfileTestImage.jpg | Bin 0 -> 92716 bytes src/components/Card/CommentCard.tsx | 46 ++++++++++++++++++++++++++++ src/pages/index.tsx | 2 ++ 3 files changed, 48 insertions(+) create mode 100644 public/ProfileTestImage.jpg create mode 100644 src/components/Card/CommentCard.tsx diff --git a/public/ProfileTestImage.jpg b/public/ProfileTestImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3d7e85d8e9fe1ceda40f7614ac8a11e137f33b3c GIT binary patch literal 92716 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

>PF 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 CvJr&< literal 0 HcmV?d00001 diff --git a/src/components/Card/CommentCard.tsx b/src/components/Card/CommentCard.tsx new file mode 100644 index 00000000..7d4c6ad4 --- /dev/null +++ b/src/components/Card/CommentCard.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import Image from 'next/image'; + +interface CommentCardProps { + size: 'sm' | 'md' | 'lg'; + status: 'edit' | 'complete'; +} + +const sizeStyles = { + sm: 'w-[360px] h-[130px]', + md: 'w-[384px] h-[162px]', + lg: 'w-[640px] h-[176px]', +}; + +function CommentCard({ size, status }: CommentCardProps) { + return ( +

+
+
+
+ 프로필 이미지{' '} +
+
+
+
+
+
지킬과 하이드
+
1시간 전
+
+ {status === 'edit' && ( +
+
수정
+
삭제
+
+ )} +
+
+ 오늘 하루 우울했었는데 덕분에 많은 힘 얻고 갑니다. 연금술사 책 다시 사서 오랜만에 읽어봐야겠어요! +
+
+
+
+ ); +} + +export default CommentCard; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 1bbe9ac9..443c4b35 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,6 @@ import Head from 'next/head'; import { useEffect, useState } from 'react'; +import CommentCard from '@/components/Card/CommentCard'; export default function Home() { // NOTE: 테스트를 위해서 typescript-eslint/no-unused-vars도 잠시 끔! @@ -31,6 +32,7 @@ export default function Home() {
Epigram Main

Pretendard

Iropke Batang

+ ); From 1bba1ac13b1580a52187303ff5eebf1fa01f762b Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 20:53:04 +0900 Subject: [PATCH 21/82] =?UTF-8?q?FE-58=20:lipstick:=20=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B0=98=EC=9D=91?= =?UTF-8?q?=ED=98=95=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/CommentCard.tsx | 66 +++++++++++++++++++++++------ src/pages/index.tsx | 2 +- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/components/Card/CommentCard.tsx b/src/components/Card/CommentCard.tsx index 7d4c6ad4..b64ed404 100644 --- a/src/components/Card/CommentCard.tsx +++ b/src/components/Card/CommentCard.tsx @@ -2,39 +2,81 @@ import React from 'react'; import Image from 'next/image'; interface CommentCardProps { - size: 'sm' | 'md' | 'lg'; status: 'edit' | 'complete'; } const sizeStyles = { sm: 'w-[360px] h-[130px]', - md: 'w-[384px] h-[162px]', - lg: 'w-[640px] h-[176px]', + md: 'md:w-[384px] md:h-[162px]', + lg: 'lg:w-[640px] lg:h-[176px]', }; -function CommentCard({ size, status }: CommentCardProps) { +const textSizeStyles = { + sm: { + name: 'text-xs', + time: 'text-xs', + action: 'text-xs', + content: 'text-sm', + }, + md: { + name: 'md:text-sm', + time: 'md:text-sm', + action: 'md:text-sm', + content: 'md:text-base', + }, + lg: { + name: 'lg:text-base', + time: 'lg:text-base', + action: 'lg:text-base', + content: 'lg:text-xl', + }, +}; + +const gapStyles = { + sm: 'gap-2', + md: 'md:gap-3', + lg: 'lg:gap-4', +}; + +const paddingStyles = { + sm: 'py-4 px-6', + md: 'md:p-6', + lg: 'lg:py-[35px] lg:px-6', +}; + +const contentWidthStyles = { + sm: 'w-[248px]', + md: 'md:w-[272px]', + lg: 'lg:w-[528px]', +}; + +function CommentCard({ status }: CommentCardProps) { return ( -
+
프로필 이미지{' '}
-
-
+
+
-
지킬과 하이드
-
1시간 전
+
지킬과 하이드
+
1시간 전
{status === 'edit' && (
-
수정
-
삭제
+
수정
+
삭제
)}
-
+
오늘 하루 우울했었는데 덕분에 많은 힘 얻고 갑니다. 연금술사 책 다시 사서 오랜만에 읽어봐야겠어요!
diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 443c4b35..eba3f577 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -32,7 +32,7 @@ export default function Home() {
Epigram Main

Pretendard

Iropke Batang

- + ); From 4b316365c344df31bc9eea7e2d3db77d2c3931e9 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 20:54:37 +0900 Subject: [PATCH 22/82] =?UTF-8?q?FE-58=20:fire:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index eba3f577..1bbe9ac9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,5 @@ import Head from 'next/head'; import { useEffect, useState } from 'react'; -import CommentCard from '@/components/Card/CommentCard'; export default function Home() { // NOTE: 테스트를 위해서 typescript-eslint/no-unused-vars도 잠시 끔! @@ -32,7 +31,6 @@ export default function Home() {
Epigram Main

Pretendard

Iropke Batang

- ); From b0591d61cebb4c7588b5d17d4142a63e33485edd Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Thu, 11 Jul 2024 23:42:30 +0900 Subject: [PATCH 23/82] =?UTF-8?q?FE-58=20:lips:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EA=B4=80=EB=A0=A8=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4,=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/CommentCard.tsx | 51 ++--------------------------- src/styles/CommentCardStyles.ts | 44 +++++++++++++++++++++++++ src/types/CommentCardTypes.ts | 3 ++ 3 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 src/styles/CommentCardStyles.ts create mode 100644 src/types/CommentCardTypes.ts diff --git a/src/components/Card/CommentCard.tsx b/src/components/Card/CommentCard.tsx index b64ed404..3975f828 100644 --- a/src/components/Card/CommentCard.tsx +++ b/src/components/Card/CommentCard.tsx @@ -1,54 +1,7 @@ import React from 'react'; import Image from 'next/image'; - -interface CommentCardProps { - status: 'edit' | 'complete'; -} - -const sizeStyles = { - sm: 'w-[360px] h-[130px]', - md: 'md:w-[384px] md:h-[162px]', - lg: 'lg:w-[640px] lg:h-[176px]', -}; - -const textSizeStyles = { - sm: { - name: 'text-xs', - time: 'text-xs', - action: 'text-xs', - content: 'text-sm', - }, - md: { - name: 'md:text-sm', - time: 'md:text-sm', - action: 'md:text-sm', - content: 'md:text-base', - }, - lg: { - name: 'lg:text-base', - time: 'lg:text-base', - action: 'lg:text-base', - content: 'lg:text-xl', - }, -}; - -const gapStyles = { - sm: 'gap-2', - md: 'md:gap-3', - lg: 'lg:gap-4', -}; - -const paddingStyles = { - sm: 'py-4 px-6', - md: 'md:p-6', - lg: 'lg:py-[35px] lg:px-6', -}; - -const contentWidthStyles = { - sm: 'w-[248px]', - md: 'md:w-[272px]', - lg: 'lg:w-[528px]', -}; +import { CommentCardProps } from '@/types/CommentCardTypes'; +import { sizeStyles, textSizeStyles, gapStyles, paddingStyles, contentWidthStyles } from '@/styles/CommentCardStyles'; function CommentCard({ status }: CommentCardProps) { return ( diff --git a/src/styles/CommentCardStyles.ts b/src/styles/CommentCardStyles.ts new file mode 100644 index 00000000..6d7a6a66 --- /dev/null +++ b/src/styles/CommentCardStyles.ts @@ -0,0 +1,44 @@ +export const sizeStyles = { + sm: 'w-[360px] h-[130px]', + md: 'md:w-[384px] md:h-[162px]', + lg: 'lg:w-[640px] lg:h-[176px]', +}; + +export const textSizeStyles = { + sm: { + name: 'text-xs', + time: 'text-xs', + action: 'text-xs', + content: 'text-sm', + }, + md: { + name: 'md:text-sm', + time: 'md:text-sm', + action: 'md:text-sm', + content: 'md:text-base', + }, + lg: { + name: 'lg:text-base', + time: 'lg:text-base', + action: 'lg:text-base', + content: 'lg:text-xl', + }, +}; + +export const gapStyles = { + sm: 'gap-2', + md: 'md:gap-3', + lg: 'lg:gap-4', +}; + +export const paddingStyles = { + sm: 'py-4 px-6', // 위아래 16px, 양 옆 24px + md: 'md:p-6', // 위아래, 양옆 24px + lg: 'lg:py-[35px] lg:px-6', // 위아래 35px, 양 옆 24px +}; + +export const contentWidthStyles = { + sm: 'w-[248px]', + md: 'md:w-[272px]', + lg: 'lg:w-[528px]', +}; diff --git a/src/types/CommentCardTypes.ts b/src/types/CommentCardTypes.ts new file mode 100644 index 00000000..abb0a28b --- /dev/null +++ b/src/types/CommentCardTypes.ts @@ -0,0 +1,3 @@ +export interface CommentCardProps { + status: 'edit' | 'complete'; +} From c38c937b65ea7b1b2f90eabdccfb563b8aa41daa Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 12 Jul 2024 14:51:23 +0900 Subject: [PATCH 24/82] =?UTF-8?q?FE-60=20:sparkles:=20react=20hook=20form,?= =?UTF-8?q?=20zod=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignIn.tsx | 70 +++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 488e13a0..5cfc919a 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -3,8 +3,34 @@ import Link from 'next/link'; import AuthLayout from '@/pageLayout/AuthLayout/AuthLayout'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; + +// 스키마 정의 +const formSchema = z.object({ + email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '올바른 이메일 주소가 아닙니다.' }), + password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), +}); export default function SignIn() { + // 폼 정의 + const form = useForm>({ + resolver: zodResolver(formSchema), + mode: 'onBlur', + defaultValues: { + email: '', + password: '', + }, + }); + + function onSubmit(values: z.infer) { + // NOTE : 테스트를 위해서 콘솔 넣음 + /* eslint-disable no-console */ + console.log(values); + } + return (
@@ -13,17 +39,39 @@ export default function SignIn() {
-
- - - -
+
+ +
+ ( + + + + + + + )} + /> + ( + + + + + + + )} + /> +
+ +
+

회원이 아니신가요?

From 09d3cb240b547917c5377e92e105d4d38eb8f0e2 Mon Sep 17 00:00:00 2001 From: imsoohyeok <160010477+imsoohyeok@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:09:03 +0900 Subject: [PATCH 25/82] =?UTF-8?q?FE-50=20=E2=9C=A8=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FE-5050✨ feat: 헤더 부분 기능 초안 * FE-50 ✨styles: 주석 추가 * FE-50 ✨styles: 주석 추추가 * FE-5050 ✨test: 테스트 코드 * FE-50 ✨fix: 테스트 코드 삭제 * FE-50 ✨feat: 공유 이미지 추가 및 현재 URL 복사 기능 추가 * FE-50 ✨styles: U셋 중 하나가 빠지더라도 안무너지게 UI 수정 * FE-50 ✨comment: 주석 수정 및 추가 * FE-50 ✨fix: 테스트 코드 삭제 * FE-50 ✨fix: 함수명 컨벤션에 맞게 변경 * FE-50 ✨fix: types 폴더에 interface 정의 * FE-50 fix: build 오류 수정 --- package-lock.json | 536 ++++++++++++++++++++++++++++++- package.json | 2 + public/icon/arrow-left-icon.svg | 5 + public/icon/profile-icon.svg | 6 + public/icon/search-icon.svg | 6 + public/icon/share-icon.svg | 7 + src/components/Header/Header.tsx | 107 ++++++ src/types/Header.ts | 12 + tailwind.config.js | 10 +- 9 files changed, 685 insertions(+), 6 deletions(-) create mode 100644 public/icon/arrow-left-icon.svg create mode 100644 public/icon/profile-icon.svg create mode 100644 public/icon/search-icon.svg create mode 100644 public/icon/share-icon.svg create mode 100644 src/components/Header/Header.tsx create mode 100644 src/types/Header.ts diff --git a/package-lock.json b/package-lock.json index 92e0a253..561da0f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,8 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.1", + "react-toastify": "^10.0.5", + "sharp": "^0.33.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" @@ -74,6 +76,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -251,6 +262,437 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz", + "integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz", + "integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz", + "integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz", + "integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz", + "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.31", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz", + "integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz", + "integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz", + "integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz", + "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.1.1" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz", + "integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz", + "integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2295,6 +2737,18 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2311,6 +2765,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2534,6 +2997,14 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4149,6 +4620,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-async-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", @@ -5855,6 +6331,18 @@ } } }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6090,7 +6578,6 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -6129,6 +6616,45 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.33.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz", + "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.0" + }, + "engines": { + "libvips": ">=8.15.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.4", + "@img/sharp-darwin-x64": "0.33.4", + "@img/sharp-libvips-darwin-arm64": "1.0.2", + "@img/sharp-libvips-darwin-x64": "1.0.2", + "@img/sharp-libvips-linux-arm": "1.0.2", + "@img/sharp-libvips-linux-arm64": "1.0.2", + "@img/sharp-libvips-linux-s390x": "1.0.2", + "@img/sharp-libvips-linux-x64": "1.0.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", + "@img/sharp-libvips-linuxmusl-x64": "1.0.2", + "@img/sharp-linux-arm": "0.33.4", + "@img/sharp-linux-arm64": "0.33.4", + "@img/sharp-linux-s390x": "0.33.4", + "@img/sharp-linux-x64": "0.33.4", + "@img/sharp-linuxmusl-arm64": "0.33.4", + "@img/sharp-linuxmusl-x64": "0.33.4", + "@img/sharp-wasm32": "0.33.4", + "@img/sharp-win32-ia32": "0.33.4", + "@img/sharp-win32-x64": "0.33.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6176,6 +6702,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index fd0a12f9..66aa1a4e 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.52.1", + "react-toastify": "^10.0.5", + "sharp": "^0.33.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" diff --git a/public/icon/arrow-left-icon.svg b/public/icon/arrow-left-icon.svg new file mode 100644 index 00000000..a54a7cb4 --- /dev/null +++ b/public/icon/arrow-left-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icon/profile-icon.svg b/public/icon/profile-icon.svg new file mode 100644 index 00000000..e7109ea6 --- /dev/null +++ b/public/icon/profile-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icon/search-icon.svg b/public/icon/search-icon.svg new file mode 100644 index 00000000..02de48b2 --- /dev/null +++ b/public/icon/search-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icon/share-icon.svg b/public/icon/share-icon.svg new file mode 100644 index 00000000..4d1c6d03 --- /dev/null +++ b/public/icon/share-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx new file mode 100644 index 00000000..5c893fed --- /dev/null +++ b/src/components/Header/Header.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import Image from 'next/image'; +import { HeaderProps } from '../../types/Header'; +import { useToast } from '../ui/use-toast'; +import LOGO_ICON from '../../../public/epigram-icon.png'; +import ARROW_LEFT_ICON from '../../../public/icon/arrow-left-icon.svg'; +import PROFILE_ICON from '../../../public/icon/profile-icon.svg'; +import SEARCH_ICON from '../../../public/icon/search-icon.svg'; +import SHARE_ICON from '../../../public/icon/share-icon.svg'; + +// TODO 네비게이션 바를 나타내는 컴포넌트 입니다. +// TODO 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다. +// TODO 사용 예시 +// TODO
+// TODO
{}} />; +// TODO icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다. +// TODO isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다. +// TODO isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다. +// TODO SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 를 추가해주세요. + +function Header({ isLogo, icon, insteadOfLogo, isButton, isProfileIcon, isShareIcon, textInButton, routerPage, disabled, onClick }: HeaderProps) { + const router = useRouter(); + const { toast } = useToast(); + + // 페이지 이동 함수 + const handleNavigateTo = (path: string) => { + router.push(path); + }; + + // 현재 링크 복사 함수 + const handleCopyToClipboard = async () => { + try { + // 현재 URL 가져오기 + const currentURL = window.location.href; + // 클립보드에 복사하기 + await navigator.clipboard.writeText(currentURL); + toast({ + title: '성공', + description: '링크가 클립보드에 복사되었습니다!', + }); + } catch (err) { + toast({ + title: '실패', + description: '링크 복사에 실패했습니다. 다시 시도해 주세요.', + variant: 'destructive', + }); + } + }; + + return ( + + ); +} + +export default Header; diff --git a/src/types/Header.ts b/src/types/Header.ts new file mode 100644 index 00000000..a75d1fce --- /dev/null +++ b/src/types/Header.ts @@ -0,0 +1,12 @@ +export interface HeaderProps { + icon: 'back' | 'search' | ''; + routerPage: string; + isLogo: boolean; + insteadOfLogo: string; + isProfileIcon: boolean; + isShareIcon: boolean; + isButton: boolean; + textInButton: string; + disabled: boolean; + onClick: (e: React.MouseEvent) => void; +} diff --git a/tailwind.config.js b/tailwind.config.js index 7274ffb8..25a4545d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -51,14 +51,14 @@ module.exports = { 'sub-gray_3': '#EFF3F8', }, screens: { - 'sm': '640px', - 'md': '768px', - 'lg': '1024px', - 'xl': '1280px', + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', '2xl': '1536px', }, backgroundImage: { - 'stripes': 'repeating-linear-gradient(to bottom, #ffffff, #ffffff 23px, #e5e7eb 23px, #e5e7eb 24px)', + stripes: 'repeating-linear-gradient(to bottom, #ffffff, #ffffff 23px, #e5e7eb 23px, #e5e7eb 24px)', }, }, }, From 03c6928069ebd22ecd3a65e44c7dc7548a076cd0 Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 12 Jul 2024 18:59:36 +0900 Subject: [PATCH 26/82] =?UTF-8?q?FE-60=20:lipstick:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=8F=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 텍스트 인풋 테두리 - 로그인 버튼 --- src/pages/auth/SignIn.tsx | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 5cfc919a..698f151a 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -31,6 +31,7 @@ export default function SignIn() { console.log(values); } + // TODO: 나중에 컴포넌트 분리하기 return (
@@ -40,15 +41,20 @@ export default function SignIn() {
- -
+ +
( + render={({ field, fieldState }) => ( - + @@ -57,22 +63,30 @@ export default function SignIn() { ( + render={({ field, fieldState }) => ( - + )} />
- -

회원이 아니신가요?

@@ -81,16 +95,15 @@ export default function SignIn() {
-
From 8717c2f480e5051e508b8adc1b1d43cd668d844c Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 12 Jul 2024 21:22:44 +0900 Subject: [PATCH 27/82] =?UTF-8?q?FE-60=20:recycle:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignIn.tsx | 14 ++++---------- src/schema/auth.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 src/schema/auth.ts diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 698f151a..49a869f3 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -5,19 +5,13 @@ import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; - -// 스키마 정의 -const formSchema = z.object({ - email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '올바른 이메일 주소가 아닙니다.' }), - password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), -}); +import { PostSigninRequest, PostSigninRequestType } from '@/schema/auth'; export default function SignIn() { // 폼 정의 - const form = useForm>({ - resolver: zodResolver(formSchema), + const form = useForm({ + resolver: zodResolver(PostSigninRequest), mode: 'onBlur', defaultValues: { email: '', @@ -25,7 +19,7 @@ export default function SignIn() { }, }); - function onSubmit(values: z.infer) { + function onSubmit(values: PostSigninRequestType) { // NOTE : 테스트를 위해서 콘솔 넣음 /* eslint-disable no-console */ console.log(values); diff --git a/src/schema/auth.ts b/src/schema/auth.ts new file mode 100644 index 00000000..9fcd92dd --- /dev/null +++ b/src/schema/auth.ts @@ -0,0 +1,8 @@ +import * as z from 'zod'; + +export const PostSigninRequest = z.object({ + email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '올바른 이메일 주소가 아닙니다.' }), + password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), +}); + +export type PostSigninRequestType = z.infer; From 2c6445ae967da93496d03bf0eaca7a160bd5843e Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:21:22 +0900 Subject: [PATCH 28/82] =?UTF-8?q?=20FE-61=20=F0=9F=94=A8=20eslint=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index cb854af6..a373fb94 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,8 @@ module.exports = { 'react/jsx-props-no-spreading': 'off', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': ['off'], + "react/require-default-props": 'off', + "react/self-closing-comp": 'off', }, settings: { react: { From 2ddc39819c975c0ede520a509f0b370d6e21e4f0 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:22:24 +0900 Subject: [PATCH 29/82] =?UTF-8?q?FE-61=20=F0=9F=94=A5=20InteractiveEmotion?= =?UTF-8?q?IconCard=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 단순 래핑 기능밖에 없는 컴포넌트 삭제 / emotionselector에서 emotioniconcard를 직접 사용하도록 수정 --- src/components/Emotion/card/EmotionSelector.tsx | 4 ++-- .../Emotion/card/InteractiveEmotionIconCard.tsx | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 src/components/Emotion/card/InteractiveEmotionIconCard.tsx diff --git a/src/components/Emotion/card/EmotionSelector.tsx b/src/components/Emotion/card/EmotionSelector.tsx index 29b3f104..ace202d4 100644 --- a/src/components/Emotion/card/EmotionSelector.tsx +++ b/src/components/Emotion/card/EmotionSelector.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import InteractiveEmotionIconCard from '@/components/Emotion/card/InteractiveEmotionIconCard'; +import EmotionIconCard from '@/components/Emotion/card/EmotionIconCard'; import useMediaQuery from '@/hooks/useMediaQuery'; import { EmotionType, EmotionState } from '@/types/EmotionTypes'; @@ -52,7 +52,7 @@ function EmotionSelector() { return (
{(['감동', '기쁨', '고민', '슬픔', '분노'] as const).map((iconType) => ( - handleCardClick(iconType)} /> + handleCardClick(iconType)} /> ))}
); diff --git a/src/components/Emotion/card/InteractiveEmotionIconCard.tsx b/src/components/Emotion/card/InteractiveEmotionIconCard.tsx deleted file mode 100644 index f8448315..00000000 --- a/src/components/Emotion/card/InteractiveEmotionIconCard.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import EmotionIconCard from '@/components/Emotion/card/EmotionIconCard'; -import { InteractiveEmotionIconCardProps } from '@/types/EmotionTypes'; - -// InteractiveEmotionIconCard 컴포넌트 함수 선언 -function InteractiveEmotionIconCard(props: InteractiveEmotionIconCardProps) { - return ; -} - -InteractiveEmotionIconCard.displayName = 'InteractiveEmotionIconCard'; - -export default InteractiveEmotionIconCard; From 3a0e0a028b8c67229c34b71cc42dee2ce2a0eac3 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:23:25 +0900 Subject: [PATCH 30/82] =?UTF-8?q?FE-61=20=F0=9F=94=A8=20EpigramCard=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EC=A6=88=20=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit base -> xs로 변경 --- src/components/Card/EpigramCard.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Card/EpigramCard.tsx b/src/components/Card/EpigramCard.tsx index 6fb71f86..c0cd5bc0 100644 --- a/src/components/Card/EpigramCard.tsx +++ b/src/components/Card/EpigramCard.tsx @@ -1,9 +1,9 @@ import React from 'react'; // figma 상으로는 sm ~ 3xl 사이즈로 구현되어 있는데, tailwind 환경을 반영해 -// base ~ 2xl 으로 정의했습니다. +// xs ~ 2xl 으로 정의했습니다. const sizeStyles = { - base: 'w-[286px] max-h-[132px]', + xs: 'w-[286px] max-h-[132px]', sm: 'sm:w-[312px] sm:max-h-[152px]', md: 'md:w-[384px] md:max-h-[180px]', lg: 'lg:w-[540px] lg:max-h-[160px]', @@ -12,7 +12,7 @@ const sizeStyles = { }; const textSizeStyles = { - base: 'text-xs', + xs: 'text-xs', sm: 'sm:text-sm', md: 'md:text-base', lg: 'lg:text-xl', @@ -22,19 +22,19 @@ const textSizeStyles = { function EpigramCard() { return ( -
+
{/* eslint-disable-next-line */}
{/* 줄무늬를 만들려면 비어있는 div가 필요합니다. */}
오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.
- 앙드레 말로 -
@@ -43,12 +43,12 @@ function EpigramCard() {
#나아가야할때
#꿈을이루고싶을때
From 6dd4a488571ddde165024d3ebecd073f17df6ab4 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:24:57 +0900 Subject: [PATCH 31/82] =?UTF-8?q?FE-61=20=F0=9F=93=9D=20=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/CommentCard.tsx | 11 +++++++++-- src/components/Card/EpigramCard.tsx | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/Card/CommentCard.tsx b/src/components/Card/CommentCard.tsx index 3975f828..db867651 100644 --- a/src/components/Card/CommentCard.tsx +++ b/src/components/Card/CommentCard.tsx @@ -17,8 +17,14 @@ function CommentCard({ status }: CommentCardProps) {
-
지킬과 하이드
-
1시간 전
+
+ {/* 테스트 텍스트입니다. */} + 지킬과 하이드 +
+
+ {/* 테스트 텍스트입니다. */} + 1시간 전 +
{status === 'edit' && (
@@ -30,6 +36,7 @@ function CommentCard({ status }: CommentCardProps) {
+ {/* 테스트 텍스트입니다. */} 오늘 하루 우울했었는데 덕분에 많은 힘 얻고 갑니다. 연금술사 책 다시 사서 오랜만에 읽어봐야겠어요!
diff --git a/src/components/Card/EpigramCard.tsx b/src/components/Card/EpigramCard.tsx index c0cd5bc0..07ceb392 100644 --- a/src/components/Card/EpigramCard.tsx +++ b/src/components/Card/EpigramCard.tsx @@ -31,12 +31,13 @@ function EpigramCard() {
+ {/* 테스트 텍스트입니다. */} 오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.
- - 앙드레 말로 - + {/* 테스트 텍스트입니다. */}- 앙드레 말로 -
@@ -45,11 +46,13 @@ function EpigramCard() {
+ {/* 테스트 텍스트입니다. */} #나아가야할때
+ {/* 테스트 텍스트입니다. */} #꿈을이루고싶을때
From 78d6bd0673c556196433024c6c9affe028d9e6ac Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:25:35 +0900 Subject: [PATCH 32/82] =?UTF-8?q?FE-61=20=F0=9F=94=A8=20CommentCard=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=82=B4=EB=B6=80=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/CommentCard.tsx | 5 ++++- src/types/CommentCardTypes.ts | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 src/types/CommentCardTypes.ts diff --git a/src/components/Card/CommentCard.tsx b/src/components/Card/CommentCard.tsx index db867651..a58f0e6f 100644 --- a/src/components/Card/CommentCard.tsx +++ b/src/components/Card/CommentCard.tsx @@ -1,8 +1,11 @@ import React from 'react'; import Image from 'next/image'; -import { CommentCardProps } from '@/types/CommentCardTypes'; import { sizeStyles, textSizeStyles, gapStyles, paddingStyles, contentWidthStyles } from '@/styles/CommentCardStyles'; +export interface CommentCardProps { + status: 'edit' | 'complete'; +} + function CommentCard({ status }: CommentCardProps) { return (
Date: Mon, 15 Jul 2024 10:26:51 +0900 Subject: [PATCH 33/82] =?UTF-8?q?FE-61=20:truck:=20=EA=B0=90=EC=A0=95=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Emotion/{card => }/EmotionIconCard.tsx | 0 src/components/Emotion/{card => }/EmotionSelector.tsx | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/Emotion/{card => }/EmotionIconCard.tsx (100%) rename src/components/Emotion/{card => }/EmotionSelector.tsx (100%) diff --git a/src/components/Emotion/card/EmotionIconCard.tsx b/src/components/Emotion/EmotionIconCard.tsx similarity index 100% rename from src/components/Emotion/card/EmotionIconCard.tsx rename to src/components/Emotion/EmotionIconCard.tsx diff --git a/src/components/Emotion/card/EmotionSelector.tsx b/src/components/Emotion/EmotionSelector.tsx similarity index 100% rename from src/components/Emotion/card/EmotionSelector.tsx rename to src/components/Emotion/EmotionSelector.tsx From 3732b6fb27ff91f237163c90f270a7f8ffcbd462 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:27:58 +0900 Subject: [PATCH 34/82] =?UTF-8?q?=20FE-61=20=F0=9F=93=9D=20=EA=B0=90?= =?UTF-8?q?=EC=A0=95=20=EC=B9=B4=EB=93=9C,=20=EA=B0=90=EC=A0=95=20?= =?UTF-8?q?=EC=85=80=EB=A0=89=ED=84=B0=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Emotion/{EmotionIconCard.tsx => EmotionCard.tsx} | 6 ++++++ src/components/Emotion/EmotionSelector.tsx | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) rename src/components/Emotion/{EmotionIconCard.tsx => EmotionCard.tsx} (93%) diff --git a/src/components/Emotion/EmotionIconCard.tsx b/src/components/Emotion/EmotionCard.tsx similarity index 93% rename from src/components/Emotion/EmotionIconCard.tsx rename to src/components/Emotion/EmotionCard.tsx index a230deec..ce14ccd3 100644 --- a/src/components/Emotion/EmotionIconCard.tsx +++ b/src/components/Emotion/EmotionCard.tsx @@ -1,3 +1,9 @@ +/* + 1개의 감정 아이콘 카드를 랜더링 합니다. + 아이콘의 타입, 상태, 크기, 클릭 이벤트를 관리합니다. + 아이콘 타입과 상태에 따라 아이콘의 모양과 스타일을 조정합니다. + */ + import React from 'react'; import cn from '@/lib/utils'; import Image from 'next/image'; diff --git a/src/components/Emotion/EmotionSelector.tsx b/src/components/Emotion/EmotionSelector.tsx index ace202d4..5a73639e 100644 --- a/src/components/Emotion/EmotionSelector.tsx +++ b/src/components/Emotion/EmotionSelector.tsx @@ -1,5 +1,10 @@ +/* + 여러 개의 EmotionIconCard를 관리합니다. + 사용자 인터페이스에 필요한 상호 작용 로직을 포함합니다. + */ + import React, { useState } from 'react'; -import EmotionIconCard from '@/components/Emotion/card/EmotionIconCard'; +import EmotionIconCard from '@/components/Emotion/EmotionCard'; import useMediaQuery from '@/hooks/useMediaQuery'; import { EmotionType, EmotionState } from '@/types/EmotionTypes'; From a2ba4b3700c15754693ce3bb488edb7aa9b33eb5 Mon Sep 17 00:00:00 2001 From: NEWJIN Date: Mon, 15 Jul 2024 10:45:32 +0900 Subject: [PATCH 35/82] =?UTF-8?q?FE-61=20:fire:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EB=94=94=ED=8F=B4=ED=8A=B8=20=ED=94=84?= =?UTF-8?q?=EB=A1=AD=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Emotion/EmotionCard.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/Emotion/EmotionCard.tsx b/src/components/Emotion/EmotionCard.tsx index ce14ccd3..1896f7cd 100644 --- a/src/components/Emotion/EmotionCard.tsx +++ b/src/components/Emotion/EmotionCard.tsx @@ -110,11 +110,4 @@ function EmotionIconCard({ iconType = '감동', state = 'Default', size = 'sm', ); } -EmotionIconCard.displayName = 'EmotionIconCard'; - -// 기본 props 설정 -EmotionIconCard.defaultProps = { - onClick: () => {}, -}; - export default EmotionIconCard; From 8dd99faa30a1456576747d11100332a007e08e19 Mon Sep 17 00:00:00 2001 From: imsoohyeok <160010477+imsoohyeok@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:08:27 +0900 Subject: [PATCH 36/82] =?UTF-8?q?FE-62=20=E2=9C=A8fix:=20=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Header/Header.tsx | 32 ++++++++++++++++++++++---------- src/types/Header.ts | 12 ------------ 2 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 src/types/Header.ts diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 5c893fed..d342347f 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { useRouter } from 'next/router'; import Image from 'next/image'; -import { HeaderProps } from '../../types/Header'; import { useToast } from '../ui/use-toast'; import LOGO_ICON from '../../../public/epigram-icon.png'; import ARROW_LEFT_ICON from '../../../public/icon/arrow-left-icon.svg'; @@ -9,15 +8,28 @@ import PROFILE_ICON from '../../../public/icon/profile-icon.svg'; import SEARCH_ICON from '../../../public/icon/search-icon.svg'; import SHARE_ICON from '../../../public/icon/share-icon.svg'; -// TODO 네비게이션 바를 나타내는 컴포넌트 입니다. -// TODO 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다. -// TODO 사용 예시 -// TODO
-// TODO
{}} />; -// TODO icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다. -// TODO isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다. -// TODO isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다. -// TODO SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 를 추가해주세요. +// NOTE 네비게이션 바를 나타내는 컴포넌트 입니다. +// NOTE 상위 컴포넌트에서 Props를 받아 원하는 스타일을 보여줍니다. +// NOTE 사용 예시 +// NOTE
+// NOTE
{}} />; +// NOTE icon: 'back'을 사용할 경우 routerPage의 값을 무조건 지정해줘야 합니다. +// NOTE isLogo={false}일 경우 insteadOfLogo의 값을 무조건 지정해줘야 합니다. +// NOTE isButton 일 경우 textInButton의 값을 무조건 지정해줘야 합니다. +// NOTE SHARE_ICON 추가 시 토스트 기능도 사용하려면 해당 컴포넌트 아래 를 추가해주세요. + +export interface HeaderProps { + icon: 'back' | 'search' | ''; + routerPage: string; + isLogo: boolean; + insteadOfLogo: string; + isProfileIcon: boolean; + isShareIcon: boolean; + isButton: boolean; + textInButton: string; + disabled: boolean; + onClick: (e: React.MouseEvent) => void; +} function Header({ isLogo, icon, insteadOfLogo, isButton, isProfileIcon, isShareIcon, textInButton, routerPage, disabled, onClick }: HeaderProps) { const router = useRouter(); diff --git a/src/types/Header.ts b/src/types/Header.ts deleted file mode 100644 index a75d1fce..00000000 --- a/src/types/Header.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface HeaderProps { - icon: 'back' | 'search' | ''; - routerPage: string; - isLogo: boolean; - insteadOfLogo: string; - isProfileIcon: boolean; - isShareIcon: boolean; - isButton: boolean; - textInButton: string; - disabled: boolean; - onClick: (e: React.MouseEvent) => void; -} From 07493a994329874f23448818b1740c0b56a0f7dc Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 13:19:30 +0900 Subject: [PATCH 37/82] =?UTF-8?q?:sparkles:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schema/auth.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/schema/auth.ts b/src/schema/auth.ts index 9fcd92dd..33466608 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -5,4 +5,21 @@ export const PostSigninRequest = z.object({ password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), }); +const User = z.object({ + id: z.number(), + email: z.string().email(), + nickname: z.string(), + teamId: z.string(), + updatedAt: z.coerce.date(), + createdAt: z.coerce.date(), + image: z.string(), +}); + +export const PostSigninResponse = z.object({ + accessToken: z.string(), + refreshToken: z.string(), + user: User, +}); + export type PostSigninRequestType = z.infer; +export type PostSigninResponseType = z.infer; From 10c64402a5f74e3c8574bfada468f06593dea4bd Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 14:13:51 +0900 Subject: [PATCH 38/82] =?UTF-8?q?:sparkles:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20api=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/auth.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/apis/auth.ts diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 00000000..60ba8f4d --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,30 @@ +import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth'; +import { AxiosError } from 'axios'; +import httpClient from '.'; + +const postSignin = async (request: PostSigninRequestType): Promise => { + try { + const response = await httpClient.post('/auth/signIn', request); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + // Axios 에러인 경우 + const axiosError = error as AxiosError; + if (axiosError.response) { + // 서버에서 응답이 온 경우 (예: 4xx, 5xx) + throw new Error('로그인 요청 처리 중 문제가 발생했습니다.'); + } else if (axiosError.request) { + // 요청을 보냈지만 응답을 받지 못한 경우 + throw new Error('서버 응답을 받지 못했습니다. 잠시 후 다시 시도해 주세요.'); + } else { + // 요청을 설정하는 과정에서 문제가 발생한 경우 + throw new Error('로그인 요청을 처리하는 동안 문제가 발생했습니다.'); + } + } else { + // Axios 에러가 아닌 경우 (네트워크 문제 등) + throw new Error('알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); + } + } +}; + +export default postSignin; From 539238401b4540d7ff8cd6adb3f388aee46bc91d Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 14:16:15 +0900 Subject: [PATCH 39/82] =?UTF-8?q?:sparkles:=20=EC=9A=94=EC=B2=AD=EA=B3=BC?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=EC=97=90=20=EA=B4=80=ED=95=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=EC=85=89=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/index.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/apis/index.ts b/src/apis/index.ts index 29949fc2..a1b6aaec 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -8,3 +8,42 @@ const httpClient = axios.create({ }); export default httpClient; + +// NOTE: eslint-disable no-param-reassign 미해결로 인한 설정 +httpClient.interceptors.request.use((config) => { + const accessToken = localStorage.getItem('accessToken'); + /* eslint-disable no-param-reassign */ + if (accessToken) config.headers.Authorization = `Bearer ${accessToken}`; + /* eslint-enable no-param-reassign */ + return config; +}); + +httpClient.interceptors.response.use( + (response) => response, + + (error) => { + if (error.response && error.response.status === 401) { + const refreshToken = localStorage.getItem('refreshToken'); + + if (!refreshToken) { + window.location.href = '/auth/SignIn'; + } else { + httpClient + .post('/auth/refresh-token', null, { + headers: { Authorization: `Bearer ${refreshToken}` }, + }) + .then((response) => { + const { accessToken } = response.data; + const { refreshToken: newRefreshToken } = response.data; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', newRefreshToken); + }) + .catch(() => { + window.location.href = '/auth/SignIn'; + }); + } + } else { + throw new Error(error.response.status); + } + }, +); From 22106ac3634810aefe035a90fd91b457d4256907 Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 14:56:35 +0900 Subject: [PATCH 40/82] =?UTF-8?q?:sparkles:=20useSignin=20mutation=20hook?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/userQueryHooks.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/hooks/userQueryHooks.ts b/src/hooks/userQueryHooks.ts index 7c28fe75..005fcf8c 100644 --- a/src/hooks/userQueryHooks.ts +++ b/src/hooks/userQueryHooks.ts @@ -1,8 +1,10 @@ +import postSignin from '@/apis/auth'; import quries from '@/apis/queries'; import { updateMe } from '@/apis/user'; import { GetUserReponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user'; import { MutationOptions } from '@/types/query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; export const useMeQuery = () => useQuery(quries.user.getMe()); @@ -21,3 +23,21 @@ export const useUpdateMe = (options: MutationOptions) => { }, }); }; + +export const useSignin = () => { + const router = useRouter(); + + return useMutation({ + mutationFn: postSignin, + onSuccess: (data) => { + localStorage.setItem('accessToken', data.accessToken); + localStorage.setItem('refreshToken', data.refreshToken); + router.push('/'); + }, + onError: (error) => { + // NOTE: 임시 테스트용 콘솔, 토스트 추가 예정 + /* eslint-disable no-console */ + console.error(error); + }, + }); +}; From e4bebb9be8b96a3f51f0b841e482211fc2f8ff4d Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 14:57:11 +0900 Subject: [PATCH 41/82] =?UTF-8?q?:zap:=20useSignin=20hook=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=8F=BC=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignIn.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 49a869f3..d19775e1 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -7,8 +7,10 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { PostSigninRequest, PostSigninRequestType } from '@/schema/auth'; +import { useSignin } from '@/hooks/userQueryHooks'; export default function SignIn() { + const mutationSignin = useSignin(); // 폼 정의 const form = useForm({ resolver: zodResolver(PostSigninRequest), @@ -20,9 +22,7 @@ export default function SignIn() { }); function onSubmit(values: PostSigninRequestType) { - // NOTE : 테스트를 위해서 콘솔 넣음 - /* eslint-disable no-console */ - console.log(values); + mutationSignin.mutate(values); } // TODO: 나중에 컴포넌트 분리하기 From 74648e721d2ceee33c4d3d462b78baeceaff8c30 Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 21:34:36 +0900 Subject: [PATCH 42/82] =?UTF-8?q?:lipstick:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pageLayout/AuthLayout/AuthLayout.tsx | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/pageLayout/AuthLayout/AuthLayout.tsx diff --git a/src/pageLayout/AuthLayout/AuthLayout.tsx b/src/pageLayout/AuthLayout/AuthLayout.tsx new file mode 100644 index 00000000..3cc64aa7 --- /dev/null +++ b/src/pageLayout/AuthLayout/AuthLayout.tsx @@ -0,0 +1,5 @@ +import { ReactNode } from 'react'; + +export default function AuthLayout({ children }: { children: ReactNode }) { + return
{children}
; +} From 8a437d10e52248830a54678c3567e928864aa445 Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 21:35:14 +0900 Subject: [PATCH 43/82] =?UTF-8?q?:lipstick:=20=EA=B0=84=ED=8E=B8=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EA=B3=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/lg.svg | 5 +++++ public/logo-google.svg | 15 +++++++++++++++ public/logo-kakao.svg | 13 +++++++++++++ public/logo-naver.svg | 5 +++++ 4 files changed, 38 insertions(+) create mode 100644 public/lg.svg create mode 100644 public/logo-google.svg create mode 100644 public/logo-kakao.svg create mode 100644 public/logo-naver.svg diff --git a/public/lg.svg b/public/lg.svg new file mode 100644 index 00000000..a4d3364f --- /dev/null +++ b/public/lg.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/logo-google.svg b/public/logo-google.svg new file mode 100644 index 00000000..5b169484 --- /dev/null +++ b/public/logo-google.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/logo-kakao.svg b/public/logo-kakao.svg new file mode 100644 index 00000000..f546e64d --- /dev/null +++ b/public/logo-kakao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/logo-naver.svg b/public/logo-naver.svg new file mode 100644 index 00000000..dbec93dd --- /dev/null +++ b/public/logo-naver.svg @@ -0,0 +1,5 @@ + + + + + From 9537b85948d3658f5d42e1f7e9d02c36be18cfeb Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 17 Jul 2024 21:36:18 +0900 Subject: [PATCH 44/82] =?UTF-8?q?:lipstick:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20ui=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 109 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/pages/auth/SignUp.tsx diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx new file mode 100644 index 00000000..8d391488 --- /dev/null +++ b/src/pages/auth/SignUp.tsx @@ -0,0 +1,109 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import AuthLayout from '@/pageLayout/AuthLayout/AuthLayout'; +import z from 'zod'; +import { useForm } from 'react-hook-form'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; + +export default function SignUp() { + const form = useForm>({ + defaultValues: { + email: '', + password: '', + passwordConfirmation: '', + nickname: '', + }, + }); + + const formSchema = z.object({ + email: z.string(), + password: z.string(), + passwordConfirmation: z.string(), + nickname: z.string(), + }); + + return ( + +
+ + logo + +
+
+ +
+ ( + + 이메일 + + + + + + )} + /> +
+ ( + + 비밀번호 + + + + + + )} + /> + ( + + + + + + + )} + /> +
+ ( + + 닉네임 + + + + + + )} + /> +
+ +
+ +
+ + + +
+
+ ); +} From eb34b5db35977d0cc1eb5e23579622396414f3c1 Mon Sep 17 00:00:00 2001 From: MOON Date: Thu, 18 Jul 2024 20:40:48 +0900 Subject: [PATCH 45/82] =?UTF-8?q?:sparkles:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schema/auth.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/schema/auth.ts diff --git a/src/schema/auth.ts b/src/schema/auth.ts new file mode 100644 index 00000000..5adda753 --- /dev/null +++ b/src/schema/auth.ts @@ -0,0 +1,20 @@ +import z from 'zod'; + +const PWD_VALIDATION = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/; +export const PostSignUpRequest = z + .object({ + email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '이메일 형식으로 작성해 주세요.' }), + password: z + .string() + .min(1, { message: '비밀번호는 필수 입력입니다.' }) + .min(8, { message: '비밀번호는 최소 8자 이상입니다.' }) + .regex(PWD_VALIDATION, { message: '비밀번호는 숫자, 영문, 특수문자로만 가능합니다.' }), + passwordConfirmation: z.string().min(1, { message: '비밀번호 확인을 입력해주세요.' }), + nickname: z.string().min(1, { message: '닉네임은 필수 입력입니다.' }).max(20, { message: '닉네임은 최대 20자까지 가능합니다.' }), + }) + .refine((data) => data.password === data.passwordConfirmation, { + message: '비밀번호가 일치하지 않습니다.', + path: ['passwordConfirmation'], + }); + +export type PostSignUpRequestType = z.infer; From eaaf1cc1c891ad449f8dca58a73897374db79895 Mon Sep 17 00:00:00 2001 From: MOON Date: Thu, 18 Jul 2024 20:43:32 +0900 Subject: [PATCH 46/82] =?UTF-8?q?:heavy=5Fplus=5Fsign:=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx index 8d391488..45950892 100644 --- a/src/pages/auth/SignUp.tsx +++ b/src/pages/auth/SignUp.tsx @@ -1,14 +1,17 @@ import Image from 'next/image'; import Link from 'next/link'; import AuthLayout from '@/pageLayout/AuthLayout/AuthLayout'; -import z from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { PostSignUpRequest, PostSignUpRequestType } from '@/schema/auth'; import { useForm } from 'react-hook-form'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; export default function SignUp() { - const form = useForm>({ + const form = useForm({ + resolver: zodResolver(PostSignUpRequest), + mode: 'onBlur', defaultValues: { email: '', password: '', @@ -17,13 +20,6 @@ export default function SignUp() { }, }); - const formSchema = z.object({ - email: z.string(), - password: z.string(), - passwordConfirmation: z.string(), - nickname: z.string(), - }); - return (
From dfb693ad6f94493547c4e240cfec9e1859662b79 Mon Sep 17 00:00:00 2001 From: MOON Date: Thu, 18 Jul 2024 20:55:46 +0900 Subject: [PATCH 47/82] =?UTF-8?q?:lipstick:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=9C=B0=20=EB=95=8C=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8,=20=EC=9D=B8=ED=92=8B=EB=8F=84=20=EA=B0=99=EC=9D=80?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=83=89=EA=B9=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx index 45950892..b8f1c2e1 100644 --- a/src/pages/auth/SignUp.tsx +++ b/src/pages/auth/SignUp.tsx @@ -33,9 +33,9 @@ export default function SignUp() { ( + render={({ field, fieldState }) => ( - 이메일 + 이메일 @@ -47,9 +47,9 @@ export default function SignUp() { ( + render={({ field, fieldState }) => ( - 비밀번호 + 비밀번호 @@ -73,9 +73,9 @@ export default function SignUp() { ( + render={({ field, fieldState }) => ( - 닉네임 + 닉네임 From c1305fc2bde63b9470f0502bdd16651b56bff6d4 Mon Sep 17 00:00:00 2001 From: MOON Date: Thu, 18 Jul 2024 21:11:43 +0900 Subject: [PATCH 48/82] =?UTF-8?q?:memo:=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=98=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx index b8f1c2e1..de2931fe 100644 --- a/src/pages/auth/SignUp.tsx +++ b/src/pages/auth/SignUp.tsx @@ -84,7 +84,11 @@ export default function SignUp() { )} />
- From 91e1fff822e29863ab0fe796e732b7ab6bc935eb Mon Sep 17 00:00:00 2001 From: MOON Date: Thu, 18 Jul 2024 21:32:17 +0900 Subject: [PATCH 49/82] =?UTF-8?q?:memo:=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=9D=B8?= =?UTF-8?q?=ED=92=8B=20=ED=85=8C=EB=91=90=EB=A6=AC=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx index de2931fe..66eb7629 100644 --- a/src/pages/auth/SignUp.tsx +++ b/src/pages/auth/SignUp.tsx @@ -37,7 +37,12 @@ export default function SignUp() { 이메일 - + @@ -51,7 +56,12 @@ export default function SignUp() { 비밀번호 - + @@ -60,10 +70,15 @@ export default function SignUp() { ( + render={({ field, fieldState }) => ( - + @@ -77,7 +92,12 @@ export default function SignUp() { 닉네임 - + From bfd7d3b32db3433ad7d7ca4a8f0b2bad4fab4697 Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 19 Jul 2024 19:05:22 +0900 Subject: [PATCH 50/82] =?UTF-8?q?:fire:=20AuthLayout=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pageLayout/AuthLayout/AuthLayout.tsx | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/pageLayout/AuthLayout/AuthLayout.tsx diff --git a/src/pageLayout/AuthLayout/AuthLayout.tsx b/src/pageLayout/AuthLayout/AuthLayout.tsx deleted file mode 100644 index 3cc64aa7..00000000 --- a/src/pageLayout/AuthLayout/AuthLayout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ReactNode } from 'react'; - -export default function AuthLayout({ children }: { children: ReactNode }) { - return
{children}
; -} From 467a76c9d699b6a2f5dafc395f6301365e32e3c8 Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 19 Jul 2024 19:09:45 +0900 Subject: [PATCH 51/82] =?UTF-8?q?:art:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B8=8C=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=EC=A0=80=20=ED=99=95=EB=8C=80=EC=8B=9C=20ui=20?= =?UTF-8?q?=EA=B9=A8=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 115 +++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx index 66eb7629..2465c7ec 100644 --- a/src/pages/auth/SignUp.tsx +++ b/src/pages/auth/SignUp.tsx @@ -1,6 +1,5 @@ import Image from 'next/image'; import Link from 'next/link'; -import AuthLayout from '@/pageLayout/AuthLayout/AuthLayout'; import { zodResolver } from '@hookform/resolvers/zod'; import { PostSignUpRequest, PostSignUpRequestType } from '@/schema/auth'; import { useForm } from 'react-hook-form'; @@ -21,21 +20,21 @@ export default function SignUp() { }); return ( - -
+
+
logo
-
- -
+
+ + ( - - 이메일 + + 이메일 )} /> -
- ( - - 비밀번호 - - - - - - )} - /> - ( - - - - - - - )} - /> -
+ ( + + 비밀번호 + + + + + + )} + /> + ( + + + + + + + )} + /> ( - - 닉네임 + + 닉네임 )} /> -
- - - -
+ + + +
+
@@ -124,6 +121,6 @@ export default function SignUp() { logo-kakao
- +
); } From bd4806141531a1efc2ecffa38721c90530e34268 Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 19 Jul 2024 19:20:23 +0900 Subject: [PATCH 52/82] =?UTF-8?q?:truck:=20=EC=A0=95=EA=B7=9C=ED=91=9C?= =?UTF-8?q?=ED=98=84=EC=8B=9D=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schema/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/schema/auth.ts b/src/schema/auth.ts index 5adda753..0c287eeb 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -1,6 +1,6 @@ import z from 'zod'; -const PWD_VALIDATION = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/; +const PWD_VALIDATION_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/; export const PostSignUpRequest = z .object({ email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '이메일 형식으로 작성해 주세요.' }), @@ -8,7 +8,7 @@ export const PostSignUpRequest = z .string() .min(1, { message: '비밀번호는 필수 입력입니다.' }) .min(8, { message: '비밀번호는 최소 8자 이상입니다.' }) - .regex(PWD_VALIDATION, { message: '비밀번호는 숫자, 영문, 특수문자로만 가능합니다.' }), + .regex(PWD_VALIDATION_REGEX, { message: '비밀번호는 숫자, 영문, 특수문자로만 가능합니다.' }), passwordConfirmation: z.string().min(1, { message: '비밀번호 확인을 입력해주세요.' }), nickname: z.string().min(1, { message: '닉네임은 필수 입력입니다.' }).max(20, { message: '닉네임은 최대 20자까지 가능합니다.' }), }) From a81913f6ccfcf6b23663dfc1fbde62c5576aaeef Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 10:30:14 +0900 Subject: [PATCH 53/82] =?UTF-8?q?:fire:=20AuthLayout=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pageLayout/AuthLayout/AuthLayout.tsx | 5 ----- src/pages/auth/SignIn.tsx | 5 ++--- 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 src/pageLayout/AuthLayout/AuthLayout.tsx diff --git a/src/pageLayout/AuthLayout/AuthLayout.tsx b/src/pageLayout/AuthLayout/AuthLayout.tsx deleted file mode 100644 index 3cc64aa7..00000000 --- a/src/pageLayout/AuthLayout/AuthLayout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ReactNode } from 'react'; - -export default function AuthLayout({ children }: { children: ReactNode }) { - return
{children}
; -} diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index d19775e1..0724a6a5 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -1,6 +1,5 @@ import Image from 'next/image'; import Link from 'next/link'; -import AuthLayout from '@/pageLayout/AuthLayout/AuthLayout'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { useForm } from 'react-hook-form'; @@ -27,7 +26,7 @@ export default function SignIn() { // TODO: 나중에 컴포넌트 분리하기 return ( - +
logo @@ -100,6 +99,6 @@ export default function SignIn() { logo-kakao
-
+
); } From d7cc6ad00e5380437ea43ac6efdd68596bf10426 Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 10:35:23 +0900 Subject: [PATCH 54/82] =?UTF-8?q?:art:=20onSubmit=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=9D=B8=EB=9D=BC=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignIn.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 0724a6a5..54845cc8 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -20,10 +20,6 @@ export default function SignIn() { }, }); - function onSubmit(values: PostSigninRequestType) { - mutationSignin.mutate(values); - } - // TODO: 나중에 컴포넌트 분리하기 return (
@@ -34,7 +30,7 @@ export default function SignIn() {
- + mutationSignin.mutate(values))} className='flex flex-col items-center lg:gap-6 gap-5 w-full px-6'>
Date: Mon, 22 Jul 2024 15:04:52 +0900 Subject: [PATCH 55/82] =?UTF-8?q?:recycle:=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=EC=9D=98=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=20=EA=B0=B1=EC=8B=A0=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/index.ts | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/apis/index.ts b/src/apis/index.ts index a1b6aaec..4167407d 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -27,23 +27,26 @@ httpClient.interceptors.response.use( if (!refreshToken) { window.location.href = '/auth/SignIn'; - } else { - httpClient - .post('/auth/refresh-token', null, { - headers: { Authorization: `Bearer ${refreshToken}` }, - }) - .then((response) => { - const { accessToken } = response.data; - const { refreshToken: newRefreshToken } = response.data; - localStorage.setItem('accessToken', accessToken); - localStorage.setItem('refreshToken', newRefreshToken); - }) - .catch(() => { - window.location.href = '/auth/SignIn'; - }); + return Promise.reject(error); } - } else { - throw new Error(error.response.status); + + return httpClient + .post('/auth/refresh-token', null, { + headers: { Authorization: `Bearer ${refreshToken}` }, + }) + .then((response) => { + const { accessToken, refreshToken: newRefreshToken } = response.data; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', newRefreshToken); + + const originalRequest = error.config; + return httpClient(originalRequest); + }) + .catch(() => { + window.location.href = '/auth/SignIn'; + return Promise.reject(error); + }); } + return Promise.reject(error); }, ); From 9f18429882161cf246dc6d540efa804d4f035791 Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 15:14:44 +0900 Subject: [PATCH 56/82] =?UTF-8?q?:recycle:=20postSignin=20api=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/auth.ts | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/apis/auth.ts b/src/apis/auth.ts index 60ba8f4d..8244a9b4 100644 --- a/src/apis/auth.ts +++ b/src/apis/auth.ts @@ -1,30 +1,9 @@ import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth'; -import { AxiosError } from 'axios'; import httpClient from '.'; const postSignin = async (request: PostSigninRequestType): Promise => { - try { - const response = await httpClient.post('/auth/signIn', request); - return response.data; - } catch (error) { - if (error instanceof AxiosError) { - // Axios 에러인 경우 - const axiosError = error as AxiosError; - if (axiosError.response) { - // 서버에서 응답이 온 경우 (예: 4xx, 5xx) - throw new Error('로그인 요청 처리 중 문제가 발생했습니다.'); - } else if (axiosError.request) { - // 요청을 보냈지만 응답을 받지 못한 경우 - throw new Error('서버 응답을 받지 못했습니다. 잠시 후 다시 시도해 주세요.'); - } else { - // 요청을 설정하는 과정에서 문제가 발생한 경우 - throw new Error('로그인 요청을 처리하는 동안 문제가 발생했습니다.'); - } - } else { - // Axios 에러가 아닌 경우 (네트워크 문제 등) - throw new Error('알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); - } - } + const response = await httpClient.post('/auth/signIn', request); + return response.data; }; export default postSignin; From ba68251c24f450df20e417cfca93c69eefc4ac5f Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 15:27:59 +0900 Subject: [PATCH 57/82] =?UTF-8?q?:fire:=20useSignin=20hook=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/userQueryHooks.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/hooks/userQueryHooks.ts b/src/hooks/userQueryHooks.ts index 005fcf8c..7c28fe75 100644 --- a/src/hooks/userQueryHooks.ts +++ b/src/hooks/userQueryHooks.ts @@ -1,10 +1,8 @@ -import postSignin from '@/apis/auth'; import quries from '@/apis/queries'; import { updateMe } from '@/apis/user'; import { GetUserReponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user'; import { MutationOptions } from '@/types/query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useRouter } from 'next/router'; export const useMeQuery = () => useQuery(quries.user.getMe()); @@ -23,21 +21,3 @@ export const useUpdateMe = (options: MutationOptions) => { }, }); }; - -export const useSignin = () => { - const router = useRouter(); - - return useMutation({ - mutationFn: postSignin, - onSuccess: (data) => { - localStorage.setItem('accessToken', data.accessToken); - localStorage.setItem('refreshToken', data.refreshToken); - router.push('/'); - }, - onError: (error) => { - // NOTE: 임시 테스트용 콘솔, 토스트 추가 예정 - /* eslint-disable no-console */ - console.error(error); - }, - }); -}; From 9b205a55f8fc40fd604e6cc588aaaf87420ecfbf Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 15:30:23 +0900 Subject: [PATCH 58/82] =?UTF-8?q?:truck:=20useSigninMutation=20hook?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useSignInMutation.ts | 23 +++++++++++++++++++++++ src/pages/auth/SignIn.tsx | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useSignInMutation.ts diff --git a/src/hooks/useSignInMutation.ts b/src/hooks/useSignInMutation.ts new file mode 100644 index 00000000..70e4d192 --- /dev/null +++ b/src/hooks/useSignInMutation.ts @@ -0,0 +1,23 @@ +import postSignin from '@/apis/auth'; +import { useMutation } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; + +const useSigninMutation = () => { + const router = useRouter(); + + return useMutation({ + mutationFn: postSignin, + onSuccess: (data) => { + localStorage.setItem('accessToken', data.accessToken); + localStorage.setItem('refreshToken', data.refreshToken); + router.push('/'); + }, + onError: (error) => { + // NOTE: 임시 테스트용 콘솔, 토스트 추가 예정 + /* eslint-disable no-console */ + console.error(error); + }, + }); +}; + +export default useSigninMutation; diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 54845cc8..400d0edd 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -6,10 +6,10 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { PostSigninRequest, PostSigninRequestType } from '@/schema/auth'; -import { useSignin } from '@/hooks/userQueryHooks'; +import useSigninMutation from '@/hooks/useSignInMutation'; export default function SignIn() { - const mutationSignin = useSignin(); + const mutationSignin = useSigninMutation(); // 폼 정의 const form = useForm({ resolver: zodResolver(PostSigninRequest), From 243e509622acf26c85c69a411a82fb00374d75b7 Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 16:19:31 +0900 Subject: [PATCH 59/82] =?UTF-8?q?:sparkles:=20Toaster=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/_app.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 37d2f8d3..107acf01 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import Toaster from '@/components/ui/toaster'; export default function App({ Component, pageProps }: AppProps) { const [queryClient] = React.useState(() => new QueryClient()); @@ -10,6 +11,7 @@ export default function App({ Component, pageProps }: AppProps) { + From 2c484baba3bd1a463e9fe5355d16e6124d1662ed Mon Sep 17 00:00:00 2001 From: MOON Date: Mon, 22 Jul 2024 16:20:21 +0900 Subject: [PATCH 60/82] =?UTF-8?q?:sparkles:=20toast=EB=A1=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=9D=84=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useSignInMutation.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hooks/useSignInMutation.ts b/src/hooks/useSignInMutation.ts index 70e4d192..eaf9fd76 100644 --- a/src/hooks/useSignInMutation.ts +++ b/src/hooks/useSignInMutation.ts @@ -1,4 +1,5 @@ import postSignin from '@/apis/auth'; +import { toast } from '@/components/ui/use-toast'; import { useMutation } from '@tanstack/react-query'; import { useRouter } from 'next/router'; @@ -12,10 +13,8 @@ const useSigninMutation = () => { localStorage.setItem('refreshToken', data.refreshToken); router.push('/'); }, - onError: (error) => { - // NOTE: 임시 테스트용 콘솔, 토스트 추가 예정 - /* eslint-disable no-console */ - console.error(error); + onError: () => { + toast({ description: '이메일 혹은 비밀번호를 확인해주세요.', className: 'border-state-error text-state-error font-semibold' }); }, }); }; From 70d45e4ddd3f1ea0f1b73d38d1adb7c972557e4f Mon Sep 17 00:00:00 2001 From: MOON Date: Tue, 23 Jul 2024 14:02:18 +0900 Subject: [PATCH 61/82] =?UTF-8?q?:sparkles:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schema/auth.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/schema/auth.ts b/src/schema/auth.ts index 0c287eeb..87fb2942 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -1,6 +1,7 @@ -import z from 'zod'; +import * as z from 'zod'; const PWD_VALIDATION_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/; + export const PostSignUpRequest = z .object({ email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '이메일 형식으로 작성해 주세요.' }), @@ -17,4 +18,22 @@ export const PostSignUpRequest = z path: ['passwordConfirmation'], }); +const User = z.object({ + id: z.number(), + email: z.string().email(), + nickname: z.string(), + teamId: z.string(), + updatedAt: z.coerce.date(), + createdAt: z.coerce.date(), + image: z.string(), +}); + +// TODO: 나중에 signin, signup의 response가 같아 같은 이름으로 통일 할 예정 +export const PostAuthResponse = z.object({ + accessToken: z.string(), + refreshToken: z.string(), + user: User, +}); + export type PostSignUpRequestType = z.infer; +export type PostAuthResponseType = z.infer; From f79b702aed27254519bc4f7ab02ba8375ece83fd Mon Sep 17 00:00:00 2001 From: MOON Date: Tue, 23 Jul 2024 14:03:57 +0900 Subject: [PATCH 62/82] =?UTF-8?q?:sparkles:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20api=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/auth.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/apis/auth.ts diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 00000000..16214f0a --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,11 @@ +import type { PostSignUpRequestType, PostAuthResponseType } from '@/schema/auth'; +import httpClient from '.'; + +// TODO: signin, signup 단어가 비슷해 login, register로 바꿀 예정 + +const postSignup = async (request: PostSignUpRequestType): Promise => { + const response = await httpClient.post('/auth/signUp', request); + return response.data; +}; + +export default postSignup; From 8ba0103ed2f28abb2ba14070a5144de5a4a226bc Mon Sep 17 00:00:00 2001 From: MOON Date: Tue, 23 Jul 2024 14:04:50 +0900 Subject: [PATCH 63/82] =?UTF-8?q?:sparkles:=20useRegisterMutation=20hook?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useRegisterMutation.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/hooks/useRegisterMutation.ts diff --git a/src/hooks/useRegisterMutation.ts b/src/hooks/useRegisterMutation.ts new file mode 100644 index 00000000..8748ffb0 --- /dev/null +++ b/src/hooks/useRegisterMutation.ts @@ -0,0 +1,19 @@ +import postSignup from '@/apis/auth'; +import { useMutation } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; + +const useRegisterMutation = () => { + const router = useRouter(); + + return useMutation({ + mutationFn: postSignup, + onSuccess: (data) => { + localStorage.setItem('accessToken', data.accessToken); + localStorage.setItem('refreshToken', data.refreshToken); + router.push('/'); + }, + onError: () => {}, + }); +}; + +export default useRegisterMutation; From e5efa3e6e324365c8d2092193718ee77e679f915 Mon Sep 17 00:00:00 2001 From: MOON Date: Tue, 23 Jul 2024 14:06:12 +0900 Subject: [PATCH 64/82] =?UTF-8?q?:zap:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8F=BC=EC=97=90=20mutaion=20hook=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/SignUp.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/auth/SignUp.tsx b/src/pages/auth/SignUp.tsx index 2465c7ec..e6c52906 100644 --- a/src/pages/auth/SignUp.tsx +++ b/src/pages/auth/SignUp.tsx @@ -6,8 +6,11 @@ import { useForm } from 'react-hook-form'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import useRegisterMutation from '@/hooks/useRegisterMutation'; export default function SignUp() { + const mutationRegister = useRegisterMutation(); + const form = useForm({ resolver: zodResolver(PostSignUpRequest), mode: 'onBlur', @@ -28,7 +31,7 @@ export default function SignUp() {
- + mutationRegister.mutate(values))} className='flex flex-col items-center w-full h-full px-6'> Date: Tue, 23 Jul 2024 15:40:40 +0900 Subject: [PATCH 65/82] =?UTF-8?q?:sparkles:=20Toaster=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/_app.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 37d2f8d3..107acf01 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import Toaster from '@/components/ui/toaster'; export default function App({ Component, pageProps }: AppProps) { const [queryClient] = React.useState(() => new QueryClient()); @@ -10,6 +11,7 @@ export default function App({ Component, pageProps }: AppProps) { + From 4e9e01a53e23855cd1f4b8f86ecd719a4122dccf Mon Sep 17 00:00:00 2001 From: MOON Date: Tue, 23 Jul 2024 15:41:40 +0900 Subject: [PATCH 66/82] =?UTF-8?q?:sparkles:=20toast=EB=A1=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=9D=84=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useRegisterMutation.ts | 35 +++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/hooks/useRegisterMutation.ts b/src/hooks/useRegisterMutation.ts index 8748ffb0..302ca05a 100644 --- a/src/hooks/useRegisterMutation.ts +++ b/src/hooks/useRegisterMutation.ts @@ -1,6 +1,8 @@ import postSignup from '@/apis/auth'; +import { toast } from '@/components/ui/use-toast'; import { useMutation } from '@tanstack/react-query'; import { useRouter } from 'next/router'; +import { AxiosError } from 'axios'; const useRegisterMutation = () => { const router = useRouter(); @@ -12,7 +14,38 @@ const useRegisterMutation = () => { localStorage.setItem('refreshToken', data.refreshToken); router.push('/'); }, - onError: () => {}, + onError: (error: AxiosError) => { + if (!error.response) { + toast({ + description: '네트워크 오류가 발생했습니다. 인터넷 연결을 확인해주세요.', + className: 'border-state-error text-state-error font-semibold', + }); + return; + } + + const { status } = error.response; + + if (status === 400) { + toast({ + description: '이미 사용중인 이메일입니다.', + className: 'border-state-error text-state-error font-semibold', + }); + return; + } + + if (status === 500) { + toast({ + description: '이미 존재하는 닉네임입니다.', + className: 'border-state-error text-state-error font-semibold', + }); + return; + } + + toast({ + description: '알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해주세요.', + className: 'border-state-error text-state-error font-semibold', + }); + }, }); }; From ea2264f2384dfae496765c8f0a4bd0f309002dbe Mon Sep 17 00:00:00 2001 From: MOON Date: Wed, 24 Jul 2024 20:03:19 +0900 Subject: [PATCH 67/82] =?UTF-8?q?:zap:=20isAxiosError=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useRegisterMutation.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useRegisterMutation.ts b/src/hooks/useRegisterMutation.ts index 302ca05a..d8db666e 100644 --- a/src/hooks/useRegisterMutation.ts +++ b/src/hooks/useRegisterMutation.ts @@ -2,7 +2,7 @@ import postSignup from '@/apis/auth'; import { toast } from '@/components/ui/use-toast'; import { useMutation } from '@tanstack/react-query'; import { useRouter } from 'next/router'; -import { AxiosError } from 'axios'; +import { isAxiosError } from 'axios'; const useRegisterMutation = () => { const router = useRouter(); @@ -14,8 +14,8 @@ const useRegisterMutation = () => { localStorage.setItem('refreshToken', data.refreshToken); router.push('/'); }, - onError: (error: AxiosError) => { - if (!error.response) { + onError: (error) => { + if (!isAxiosError(error)) { toast({ description: '네트워크 오류가 발생했습니다. 인터넷 연결을 확인해주세요.', className: 'border-state-error text-state-error font-semibold', @@ -23,7 +23,7 @@ const useRegisterMutation = () => { return; } - const { status } = error.response; + const { status } = error.response || {}; if (status === 400) { toast({ From 596369a0c8b03622335f6d1860a65a0f7b2ac3a0 Mon Sep 17 00:00:00 2001 From: MOON <50370479+jangmoonwon@users.noreply.github.com> Date: Fri, 26 Jul 2024 08:09:40 +0900 Subject: [PATCH 68/82] =?UTF-8?q?FE-29=20:twisted=5Frightwards=5Farrows:?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A8=B8=EC=A7=80=20=EC=9A=94=EC=B2=AD=20(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :heavy_plus_sign: 이미지 파일 추가 * :lipstick: 로그인 페이지 레이아웃 생성 * :lipstick: 로그인 페이지 UI 생성 및 반응형 디자인 구현 * FE-60 :sparkles: react hook form, zod 추가 * FE-60 :lipstick: 로그인 폼 스타일 수정 - 텍스트 인풋 테두리 - 로그인 버튼 * FE-60 :recycle: 로그인 스키마 분리 * :sparkles: 로그인 응답 데이터 스키마 정의 * :sparkles: 로그인 api 생성 * :sparkles: 요청과 응답에 관한 인터셉터 추가 * :sparkles: useSignin mutation hook 생성 * :zap: useSignin hook 로그인 폼에 적용 * :fire: AuthLayout 삭제 * :art: onSubmit 함수 인라인으로 정의 * :recycle: 응답 인터셉터의 에러 처리 및 토큰 갱신 로직 개선 * :recycle: postSignin api 에러처리 로직 삭제 * :fire: useSignin hook 삭제 * :truck: useSigninMutation hook으로 이름 변경 및 파일 이동 * :sparkles: Toaster 컴포넌트 추가 * :sparkles: toast로 에러메시지 띄우기 --- public/lg.svg | 5 ++ public/logo-google.svg | 15 +++++ public/logo-kakao.svg | 13 +++++ public/logo-naver.svg | 5 ++ src/apis/auth.ts | 9 +++ src/apis/index.ts | 42 ++++++++++++++ src/hooks/useSignInMutation.ts | 22 ++++++++ src/pages/_app.tsx | 2 + src/pages/auth/SignIn.tsx | 100 +++++++++++++++++++++++++++++++++ src/schema/auth.ts | 25 +++++++++ 10 files changed, 238 insertions(+) create mode 100644 public/lg.svg create mode 100644 public/logo-google.svg create mode 100644 public/logo-kakao.svg create mode 100644 public/logo-naver.svg create mode 100644 src/apis/auth.ts create mode 100644 src/hooks/useSignInMutation.ts create mode 100644 src/pages/auth/SignIn.tsx create mode 100644 src/schema/auth.ts diff --git a/public/lg.svg b/public/lg.svg new file mode 100644 index 00000000..a4d3364f --- /dev/null +++ b/public/lg.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/logo-google.svg b/public/logo-google.svg new file mode 100644 index 00000000..5b169484 --- /dev/null +++ b/public/logo-google.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/logo-kakao.svg b/public/logo-kakao.svg new file mode 100644 index 00000000..f546e64d --- /dev/null +++ b/public/logo-kakao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/logo-naver.svg b/public/logo-naver.svg new file mode 100644 index 00000000..dbec93dd --- /dev/null +++ b/public/logo-naver.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 00000000..8244a9b4 --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,9 @@ +import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth'; +import httpClient from '.'; + +const postSignin = async (request: PostSigninRequestType): Promise => { + const response = await httpClient.post('/auth/signIn', request); + return response.data; +}; + +export default postSignin; diff --git a/src/apis/index.ts b/src/apis/index.ts index 29949fc2..4167407d 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -8,3 +8,45 @@ const httpClient = axios.create({ }); export default httpClient; + +// NOTE: eslint-disable no-param-reassign 미해결로 인한 설정 +httpClient.interceptors.request.use((config) => { + const accessToken = localStorage.getItem('accessToken'); + /* eslint-disable no-param-reassign */ + if (accessToken) config.headers.Authorization = `Bearer ${accessToken}`; + /* eslint-enable no-param-reassign */ + return config; +}); + +httpClient.interceptors.response.use( + (response) => response, + + (error) => { + if (error.response && error.response.status === 401) { + const refreshToken = localStorage.getItem('refreshToken'); + + if (!refreshToken) { + window.location.href = '/auth/SignIn'; + return Promise.reject(error); + } + + return httpClient + .post('/auth/refresh-token', null, { + headers: { Authorization: `Bearer ${refreshToken}` }, + }) + .then((response) => { + const { accessToken, refreshToken: newRefreshToken } = response.data; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', newRefreshToken); + + const originalRequest = error.config; + return httpClient(originalRequest); + }) + .catch(() => { + window.location.href = '/auth/SignIn'; + return Promise.reject(error); + }); + } + return Promise.reject(error); + }, +); diff --git a/src/hooks/useSignInMutation.ts b/src/hooks/useSignInMutation.ts new file mode 100644 index 00000000..eaf9fd76 --- /dev/null +++ b/src/hooks/useSignInMutation.ts @@ -0,0 +1,22 @@ +import postSignin from '@/apis/auth'; +import { toast } from '@/components/ui/use-toast'; +import { useMutation } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; + +const useSigninMutation = () => { + const router = useRouter(); + + return useMutation({ + mutationFn: postSignin, + onSuccess: (data) => { + localStorage.setItem('accessToken', data.accessToken); + localStorage.setItem('refreshToken', data.refreshToken); + router.push('/'); + }, + onError: () => { + toast({ description: '이메일 혹은 비밀번호를 확인해주세요.', className: 'border-state-error text-state-error font-semibold' }); + }, + }); +}; + +export default useSigninMutation; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 37d2f8d3..107acf01 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import Toaster from '@/components/ui/toaster'; export default function App({ Component, pageProps }: AppProps) { const [queryClient] = React.useState(() => new QueryClient()); @@ -10,6 +11,7 @@ export default function App({ Component, pageProps }: AppProps) { + diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx new file mode 100644 index 00000000..400d0edd --- /dev/null +++ b/src/pages/auth/SignIn.tsx @@ -0,0 +1,100 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; +import { PostSigninRequest, PostSigninRequestType } from '@/schema/auth'; +import useSigninMutation from '@/hooks/useSignInMutation'; + +export default function SignIn() { + const mutationSignin = useSigninMutation(); + // 폼 정의 + const form = useForm({ + resolver: zodResolver(PostSigninRequest), + mode: 'onBlur', + defaultValues: { + email: '', + password: '', + }, + }); + + // TODO: 나중에 컴포넌트 분리하기 + return ( +
+
+ + logo + +
+ + + mutationSignin.mutate(values))} className='flex flex-col items-center lg:gap-6 gap-5 w-full px-6'> +
+ ( + + + + + + + )} + /> + ( + + + + + + + )} + /> +
+ + + +
+

회원이 아니신가요?

+ + + +
+
+ + + +
+
+ ); +} diff --git a/src/schema/auth.ts b/src/schema/auth.ts new file mode 100644 index 00000000..33466608 --- /dev/null +++ b/src/schema/auth.ts @@ -0,0 +1,25 @@ +import * as z from 'zod'; + +export const PostSigninRequest = z.object({ + email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '올바른 이메일 주소가 아닙니다.' }), + password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), +}); + +const User = z.object({ + id: z.number(), + email: z.string().email(), + nickname: z.string(), + teamId: z.string(), + updatedAt: z.coerce.date(), + createdAt: z.coerce.date(), + image: z.string(), +}); + +export const PostSigninResponse = z.object({ + accessToken: z.string(), + refreshToken: z.string(), + user: User, +}); + +export type PostSigninRequestType = z.infer; +export type PostSigninResponseType = z.infer; From 5785703fc287eeb2514da8c457a0d2dcf4da272d Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 26 Jul 2024 11:28:48 +0900 Subject: [PATCH 69/82] =?UTF-8?q?:twisted=5Frightwards=5Farrows:=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schema/auth.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/schema/auth.ts b/src/schema/auth.ts index fc1f3f06..0a9069a8 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -2,7 +2,7 @@ import * as z from 'zod'; const PWD_VALIDATION_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/; -//NOTE: 회원가입 스키마 +// NOTE: 회원가입 스키마 export const PostSignUpRequest = z .object({ email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '이메일 형식으로 작성해 주세요.' }), @@ -19,7 +19,7 @@ export const PostSignUpRequest = z path: ['passwordConfirmation'], }); -//NOTE: 로그인 스키마 +// NOTE: 로그인 스키마 export const PostSigninRequest = z.object({ email: z.string().min(1, { message: '이메일은 필수 입력입니다.' }).email({ message: '올바른 이메일 주소가 아닙니다.' }), password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), @@ -41,9 +41,9 @@ export const PostAuthResponse = z.object({ user: User, }); -//NOTE: 회원가입 타입 +// NOTE: 회원가입 타입 export type PostSignUpRequestType = z.infer; export type PostSignUpResponseType = z.infer; -//NOTE: 로그인 타입 +// NOTE: 로그인 타입 export type PostSigninRequestType = z.infer; export type PostSigninResponseType = z.infer; From 971591876495d99c2866ef057bc128bd349b3c6f Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 26 Jul 2024 11:48:10 +0900 Subject: [PATCH 70/82] =?UTF-8?q?:bug:=20postSignup=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/auth.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apis/auth.ts b/src/apis/auth.ts index 8244a9b4..e13efb7d 100644 --- a/src/apis/auth.ts +++ b/src/apis/auth.ts @@ -1,9 +1,12 @@ -import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth'; +import type { PostSigninRequestType, PostSigninResponseType, PostSignUpRequestType, PostSignUpResponseType } from '@/schema/auth'; import httpClient from '.'; -const postSignin = async (request: PostSigninRequestType): Promise => { +export const postSignin = async (request: PostSigninRequestType): Promise => { const response = await httpClient.post('/auth/signIn', request); return response.data; }; -export default postSignin; +export const postSignup = async (request: PostSignUpRequestType): Promise => { + const response = await httpClient.post('/auth/signUp', request); + return response.data; +}; From 0762f98667d300117d3a53027d4123a21b9cde07 Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 26 Jul 2024 11:50:11 +0900 Subject: [PATCH 71/82] =?UTF-8?q?:bug:=20postSignin=20=EB=82=B4=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EB=8A=94=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useSignInMutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useSignInMutation.ts b/src/hooks/useSignInMutation.ts index eaf9fd76..2f4ebb5e 100644 --- a/src/hooks/useSignInMutation.ts +++ b/src/hooks/useSignInMutation.ts @@ -1,4 +1,4 @@ -import postSignin from '@/apis/auth'; +import { postSignin } from '@/apis/auth'; import { toast } from '@/components/ui/use-toast'; import { useMutation } from '@tanstack/react-query'; import { useRouter } from 'next/router'; From 7e4eba6e52cea2a03fb1115993508049c53a7716 Mon Sep 17 00:00:00 2001 From: MOON Date: Fri, 26 Jul 2024 11:56:01 +0900 Subject: [PATCH 72/82] =?UTF-8?q?:wrench:=20lint=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useRegisterMutation.ts | 2 +- src/schema/auth.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useRegisterMutation.ts b/src/hooks/useRegisterMutation.ts index d8db666e..72307f98 100644 --- a/src/hooks/useRegisterMutation.ts +++ b/src/hooks/useRegisterMutation.ts @@ -1,4 +1,4 @@ -import postSignup from '@/apis/auth'; +import { postSignup } from '@/apis/auth'; import { toast } from '@/components/ui/use-toast'; import { useMutation } from '@tanstack/react-query'; import { useRouter } from 'next/router'; diff --git a/src/schema/auth.ts b/src/schema/auth.ts index 358dbd5d..0a9069a8 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -46,4 +46,4 @@ export type PostSignUpRequestType = z.infer; export type PostSignUpResponseType = z.infer; // NOTE: 로그인 타입 export type PostSigninRequestType = z.infer; -export type PostSigninResponseType = z.infer; \ No newline at end of file +export type PostSigninResponseType = z.infer; From fbe86b846daacbea7172f5c358a842ecbaf1163d Mon Sep 17 00:00:00 2001 From: MOON Date: Sat, 27 Jul 2024 14:12:14 +0900 Subject: [PATCH 73/82] =?UTF-8?q?:sparkles:=20oauth=20api=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/oauth.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/apis/oauth.ts diff --git a/src/apis/oauth.ts b/src/apis/oauth.ts new file mode 100644 index 00000000..ae6bd068 --- /dev/null +++ b/src/apis/oauth.ts @@ -0,0 +1,11 @@ +import axios from 'axios'; + +const postOauth = async (code: string) => { + const response = await axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}/auth/signIn/KAKAO`, { + redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI, + token: code, + }); + return response.data; +}; + +export default postOauth; From 4ba94c8b13cabb933a3fba0a7d9eaa23f6e4ff32 Mon Sep 17 00:00:00 2001 From: Jiseok Woo <115205098+jisurk@users.noreply.github.com> Date: Sat, 27 Jul 2024 22:19:45 +0900 Subject: [PATCH 74/82] =?UTF-8?q?FE-71=20=F0=9F=94=80=20=EC=97=90=ED=94=BC?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EC=9E=91=EC=84=B1=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20(#71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FE-64💄 글작성 페이지 UI추가 (#44) * FE-72 ✨ 에피그램 등록 api연동 (#52) * FE-72✨ 글작성페이지 스키마 추가 * FE-72✨ form태그 Form컴포넌트로 변경 * FE-72✨ 태그 저장기능 추가 * FE-72✨ 에피그램 등록 api연동 * FE-72✨ 에피그램 등록시 해당 에피그램 페이지로 이동 기능 추가 * FE-72✨ 등록 중일때의 로직추가 * FE-72✨ toast-> alert-dailog로 변경 * FE-72📝 TODO주석 추가 --------- Co-authored-by: 우지석 * FE-73✨ 유효성검사 추가 (#66) * FE-73♻️ Tag관리 함수 훅으로 분리 * FE-73✨ RadioGroup 로직 수정 * FE-73✨ 유효성검사 추가 * FE-73♻️ 저자 본인 선택시의 로직 변경 * FE-73✨ 중복 태그 검사 로직 추가 * FE-73♻️ 출처 유효성(optional)검사 수정 * FE-73✨ 필수항목 입력했을때 버튼 활성화 * FE-73🐛 태그를 입력했다가 지웠을때 버튼 활성화되있는 버그 수정 * FE-73🐛 useEffect 의존성배열 lint problem 해결 * FE-73🐛 url유효성검사 에러 메세지 안뜨는 버그 수정 --------- Co-authored-by: 우지석 * FE-71♻️ epic브랜치 코드리뷰 반영 (#76) * FE-71♻️ token,interceptor 로직 수정 * FE-71♻️ AddEpigram 코드리뷰 반영 * FE-71🔥 테스트용 상세페이지 삭제 * FE-71♻️ onKeyDown -> onKeyUp 수정 --------- Co-authored-by: 우지석 --- src/apis/add.ts | 9 + src/apis/index.ts | 4 +- src/hooks/epigramQueryHook.ts | 24 ++ src/hooks/useTagManagementHook.ts | 47 ++++ src/pageLayout/Epigram/AddEpigram.tsx | 316 ++++++++++++++++++++++++++ src/pages/addEpigram.tsx | 7 + src/schema/addEpigram.ts | 44 ++++ 7 files changed, 449 insertions(+), 2 deletions(-) create mode 100644 src/apis/add.ts create mode 100644 src/hooks/epigramQueryHook.ts create mode 100644 src/hooks/useTagManagementHook.ts create mode 100644 src/pageLayout/Epigram/AddEpigram.tsx create mode 100644 src/pages/addEpigram.tsx create mode 100644 src/schema/addEpigram.ts diff --git a/src/apis/add.ts b/src/apis/add.ts new file mode 100644 index 00000000..66a6b010 --- /dev/null +++ b/src/apis/add.ts @@ -0,0 +1,9 @@ +import { AddEpigramRequestType, AddEpigramResponseType } from '@/schema/addEpigram'; +import httpClient from '.'; + +const postEpigram = async (request: AddEpigramRequestType): Promise => { + const response = await httpClient.post('/epigrams', request); + return response.data; +}; + +export default postEpigram; diff --git a/src/apis/index.ts b/src/apis/index.ts index 4167407d..e58d8047 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -7,8 +7,6 @@ const httpClient = axios.create({ paramsSerializer: (parameters) => qs.stringify(parameters, { arrayFormat: 'repeat', encode: false }), }); -export default httpClient; - // NOTE: eslint-disable no-param-reassign 미해결로 인한 설정 httpClient.interceptors.request.use((config) => { const accessToken = localStorage.getItem('accessToken'); @@ -50,3 +48,5 @@ httpClient.interceptors.response.use( return Promise.reject(error); }, ); + +export default httpClient; diff --git a/src/hooks/epigramQueryHook.ts b/src/hooks/epigramQueryHook.ts new file mode 100644 index 00000000..e2ca6679 --- /dev/null +++ b/src/hooks/epigramQueryHook.ts @@ -0,0 +1,24 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { AddEpigramFormType, AddEpigramResponseType } from '@/schema/addEpigram'; +import { MutationOptions } from '@/types/query'; +import postEpigram from '@/apis/add'; +import { AxiosError } from 'axios'; + +// TODO: 에피그램 수정과 삭제에도 사용 가능하게 훅 수정 예정 + +const useAddEpigram = (options?: MutationOptions) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (newEpigram: AddEpigramFormType) => postEpigram(newEpigram), + ...options, + onSuccess: (...args) => { + queryClient.invalidateQueries({ queryKey: ['epigrams'] }); + if (options?.onSuccess) { + options.onSuccess(...args); + } + }, + }); +}; + +export default useAddEpigram; diff --git a/src/hooks/useTagManagementHook.ts b/src/hooks/useTagManagementHook.ts new file mode 100644 index 00000000..dd0082de --- /dev/null +++ b/src/hooks/useTagManagementHook.ts @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import { UseFormSetValue, UseFormGetValues, UseFormSetError } from 'react-hook-form'; +import { AddEpigramFormType } from '@/schema/addEpigram'; + +// NOTE: setError메서드로 FormField에 에러 설정 가능 +const useTagManagement = ({ + setValue, + getValues, + setError, +}: { + setValue: UseFormSetValue; + getValues: UseFormGetValues; + setError: UseFormSetError; +}) => { + const [currentTag, setCurrentTag] = useState(''); + + const handleAddTag = () => { + if (!currentTag || currentTag.length > 10) { + return; + } + const currentTags = getValues('tags') || []; + + if (currentTags.length >= 3) { + return; + } + if (currentTags.includes(currentTag)) { + setError('tags', { type: 'manual', message: '이미 저장된 태그입니다.' }); + return; + } + + setValue('tags', [...currentTags, currentTag]); + setCurrentTag(''); + setError('tags', { type: 'manual', message: '' }); + }; + + const handleRemoveTag = (tagToRemove: string) => { + const currentTags = getValues('tags') || []; + setValue( + 'tags', + currentTags.filter((tag) => tag !== tagToRemove), + ); + }; + + return { currentTag, setCurrentTag, handleAddTag, handleRemoveTag }; +}; + +export default useTagManagement; diff --git a/src/pageLayout/Epigram/AddEpigram.tsx b/src/pageLayout/Epigram/AddEpigram.tsx new file mode 100644 index 00000000..f314c730 --- /dev/null +++ b/src/pageLayout/Epigram/AddEpigram.tsx @@ -0,0 +1,316 @@ +import React, { KeyboardEvent, useCallback, useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import Header from '@/components/Header/Header'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Textarea } from '@/components/ui/textarea'; +import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'; +import { AddEpigramFormSchema, AddEpigramFormType } from '@/schema/addEpigram'; +import useAddEpigram from '@/hooks/epigramQueryHook'; +import { useRouter } from 'next/router'; +import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'; +import useTagManagement from '@/hooks/useTagManagementHook'; +import { useMeQuery } from '@/hooks/userQueryHooks'; + +function AddEpigram() { + const router = useRouter(); + const { data: userData, isPending, isError } = useMeQuery(); + const [isAlertOpen, setIsAlertOpen] = useState(false); + const [alertContent, setAlertContent] = useState({ title: '', description: '' }); + const [selectedAuthorOption, setSelectedAuthorOption] = useState('directly'); // 기본값을 'directly'로 설정 + const [isFormValid, setIsFormValid] = useState(false); + + const form = useForm({ + resolver: zodResolver(AddEpigramFormSchema), + defaultValues: { + content: '', + author: '', + referenceTitle: '', + referenceUrl: '', + tags: [], + }, + }); + + // NOTE: 필수항목들에 값이 들어있는지 확인 함수 + const checkFormEmpty = useCallback(() => { + const { content, author, tags } = form.getValues(); + return content.trim() !== '' && author.trim() !== '' && tags.length > 0; + }, [form]); + + // NOTE: form값이 변경될때 필수항목들이 들어있는지 확인 + const watchForm = useCallback(() => { + setIsFormValid(checkFormEmpty()); + }, [checkFormEmpty]); + + useEffect(() => { + const subscription = form.watch(watchForm); + return () => subscription.unsubscribe(); + }, [form, watchForm]); + + const { currentTag, setCurrentTag, handleAddTag, handleRemoveTag } = useTagManagement({ + setValue: form.setValue, + getValues: form.getValues, + setError: form.setError, + }); + const addEpigramMutation = useAddEpigram({ + onSuccess: () => { + setAlertContent({ + title: '등록 완료', + description: '등록이 완료되었습니다.', + }); + setIsAlertOpen(true); + form.reset(); + }, + onError: () => { + setAlertContent({ + title: '등록 실패', + description: '다시 시도해주세요.', + }); + setIsAlertOpen(true); + }, + }); + + const handleAlertClose = () => { + setIsAlertOpen(false); + if (alertContent.title === '등록 완료') { + router.push(`/epigram/${addEpigramMutation.data?.id}`); + } + }; + + const AUTHOR_OPTIONS = [ + { value: 'directly', label: '직접 입력' }, + { value: 'unknown', label: '알 수 없음' }, + { value: 'me', label: '본인' }, + ]; + + // NOTE: default를 직접 입력으로 설정 + // NOTE: 본인을 선택 시 유저의 nickname이 들어감 + const handleAuthorChange = async (value: string) => { + setSelectedAuthorOption(value); + let authorValue: string; + + switch (value) { + case 'unknown': + authorValue = '알 수 없음'; + break; + case 'me': + if (isPending) { + authorValue = '로딩 중...'; + } else if (userData) { + authorValue = userData.nickname; + } else { + authorValue = '본인 (정보 없음)'; + } + break; + default: + authorValue = ''; + } + form.setValue('author', authorValue); + }; + + if (isPending) { + return
사용자 정보를 불러오는 중...
; + } + + if (isError) { + return
사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침 해주세요.
; + } + + // NOTE: 태그를 저장하려고 할때 enter키를 누르면 폼제출이 되는걸 방지 + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddTag(); + } + }; + + // NOTE: url와title은 필수 항목이 아니라서 빈칸으로 제출할 때 항목에서 제외 + const handleSubmit = (data: AddEpigramFormType) => { + const submitData = { ...data }; + + if (!submitData.referenceUrl) { + delete submitData.referenceUrl; + } + + if (!submitData.referenceTitle) { + delete submitData.referenceTitle; + } + + addEpigramMutation.mutate(submitData); + }; + + return ( + <> +
{}} /> +
+
+ + ( + + + 내용 + * + + +