diff --git a/README.md b/README.md index 64282688..b1fecd4c 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,56 @@ -# Wine -(프로젝트 설명) - -## 목표 -- 목표: 프로젝트 완성 - -## 팀원 정보 및 역할 -- 준열: 팀 리딩, 랜딩 페이지, 와인 상세 페이지 -- 휘태: 로그인, 회원가입, 리뷰 남기기/와인 등록하기 모달 -- 연수: 마이페이지(내가 등록한 와인), notion 문서화 -- 지권: 마이페이지(내가 쓴 후기) -- 정훈: 와인 목록 페이지 - -> [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki) - -## Core Time -- 데일리 스크럼 - - 월요일: 오후 3시 - - 화요일 일요일: 오후 1시 30분 (20~30분 진행) -- 집중 코딩 (모각코) - - 9:00 ~ 18:00 (zep 활용) - -> [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki) +# 🍷 WHYNE + +### WHYNE은 사용자가 다양한 와인 리뷰를 확인하고, 구매 여부를 쉽게 판단할 수 있는 와인 리뷰 플랫폼입니다. + +image + +
+ +## 🎯 목표 + +> #### 주어진 요구사항을 충족하는 것을 넘어, 경쟁력 있는 특별한 UI / UX를 도입하고, 사용자에게 최적의 와인 선택 경험을 제공하는 것 + +
+ +## 👥 팀 소개 + + + + 🔗 [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki) + + + +
+ +|팀장 |팀원 |팀원 |팀원 | +|:---:|:---:|:---:|:---:| +|
🕊️
김준열
ISTJ
FE
|
🦇
서지권
INTJ
FE
|
🐣
이연수
INFP
FE
|
🐈‍⬛
황휘태
ISTJ
FE
| + +
+ +
+ +## ⏰ Core Time + + + + 🔗 [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki) + + + +> 원활한 소통을 위해 매일 진행하며, 의미 없는 시간이 되지 않도록 **노션 문서화**로 기록합니다. + +#### 🗓️ 시간표 + +| 요일 | 시간 | +| :-------------: | :-------: | +| 월요일 | **15:00** | +| 화요일 ~ 일요일 | **13:30** | + +
## Branch 전략 + - `main` → 배포 - `develop` → 통합 브랜치 - `feature/*`, `design/*`, `chore/*` → 작업 브랜치 → PR → develop → main @@ -30,6 +58,7 @@ > [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki/%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%BB%A8%EB%B2%A4%EC%85%98) ## 기술 스택 + - Next.js, React, TypeScript - Tailwind CSS - Storybook @@ -39,12 +68,14 @@ > [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki/%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D) ## 깃허브 액션 -- Vercel 배포 -- Chromatic으로 Storybook 배포 (develop 기준) + +- Vercel 배포 +- Chromatic으로 Storybook 배포 (develop 기준) > [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki/%EA%B9%83%ED%97%88%EB%B8%8C-%EC%95%A1%EC%85%98) ## 프로젝트 관리 + - README: 프로젝트 개요 / 기술 스택 / 실행 방법 - Wiki: 세부 규칙, 가이드 문서, 브랜치 룰 - Notion: 회의록, 멘토링 피드백, 참고 자료 @@ -53,6 +84,7 @@ > [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC) ## 실행 방법 + ```bash # 패키지 설치 npm install @@ -81,9 +113,11 @@ src ┣ types # 전역 타입 정의 (TypeScript 인터페이스, 타입) ┗ utils # 공통 유틸리티 함수 ``` + > [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki/%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0) ## 바로가기 + - [배포 바로가기](https://google.com/) - [스토리북 바로가기](https://68d3998e0b054d1207706cbb-tzevsxkvcq.chromatic.com/?path=/docs/my-profile-accountitem--docs) - [Wiki 바로가기](https://github.com/Team-3-2/Wine/wiki) diff --git a/src/api/auth/login.ts b/src/api/auth/login.ts index 5498885f..5318028e 100644 --- a/src/api/auth/login.ts +++ b/src/api/auth/login.ts @@ -1,6 +1,7 @@ "use server"; import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; const opts = { maxAge: 60 * 60 * 24 * 30, @@ -12,6 +13,7 @@ const opts = { const login = async (prevState: any, formData: FormData) => { const email = formData.get("email"); const password = formData.get("password"); + const redirectUrl = formData.get("redirect"); if (!email || !password) return { isError: true, message: "이메일 또는 비밀번호를 입력하세요." }; @@ -36,9 +38,9 @@ const login = async (prevState: any, formData: FormData) => { cookieStore.set("refreshToken", `${data.refreshToken}`); cookieStore.set("login_type", "basic", opts); - return { isError: false, data: data }; + redirect(redirectUrl as string); } catch (error) { - console.error(error); + throw error; } }; diff --git a/src/api/my-profile/get-user-review.ts b/src/api/my-profile/get-user-review.ts index 2e6c5f73..13831633 100644 --- a/src/api/my-profile/get-user-review.ts +++ b/src/api/my-profile/get-user-review.ts @@ -23,6 +23,6 @@ export default async function getUserReview({ const res = await instance.get(url); return { ...res.data, - list: [...res.data.list].reverse(), + list: [...res.data.list], }; } diff --git a/src/api/user/get-user-wines.ts b/src/api/user/get-user-wines.ts index a4b85064..ac191125 100644 --- a/src/api/user/get-user-wines.ts +++ b/src/api/user/get-user-wines.ts @@ -24,7 +24,7 @@ const getUserWines = async ({ cursor, limit }: GetUserWinesData) => { return { ...res.data, - list: [...res.data.list].reverse(), + list: [...res.data.list], }; }; diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 8bb8d2fd..5869260b 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -4,8 +4,11 @@ import { ReactNode } from "react"; const Layout = ({ children }: { children: Readonly }) => { return ( -
-
{children}
+
+
{children}
); }; diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index ce905a70..e30bd3d8 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -14,6 +14,7 @@ import { useToast } from "@/hooks/use-toast"; import { useRememberId } from "../_hooks/use-remember-id"; import { getCookie, setCookie } from "cookies-next"; import RecentLoginBadge from "../_components/recent-login-badge/recent-login-badge"; +import { useSearchParams } from "next/navigation"; interface LoginFormData { email: string; @@ -36,6 +37,7 @@ const Page = () => { const [loginType, setLoginType] = useState(null); const [state, formAction, isPending] = useActionState(login, null); const { checked, setChecked, initialId, opts } = useRememberId(); + const searchParams = useSearchParams(); const email = watch("email"); @@ -119,6 +121,12 @@ const Page = () => { }, })} /> +
diff --git a/src/app/(landing)/_components/font-color.stories.tsx b/src/app/(landing)/_components/font-color.stories.tsx new file mode 100644 index 00000000..612a9ea3 --- /dev/null +++ b/src/app/(landing)/_components/font-color.stories.tsx @@ -0,0 +1,227 @@ +import { Meta, StoryObj } from "@storybook/nextjs"; +import { sfPro } from "@/app/fonts"; + +const meta: Meta = { + title: "프로젝트 세팅/폰트 & 컬러", + parameters: { layout: "fullscreen" }, +}; +export default meta; + +type Story = StoryObj; + +const primaryPalette = [ + { name: "Black", value: "#1A1918", className: "bg-black text-white" }, + { name: "Primary", value: "#1A1918", className: "bg-primary text-white" }, + { + name: "White", + value: "#FFFFFF", + className: "bg-white text-black border border-gray-300", + }, + { name: "Default", value: "#31302F", className: "bg-default text-white" }, + { name: "Secondary", value: "#A3A3A3", className: "bg-secondary text-white" }, + { + name: "BgGray350", + value: "rgba(217,217,217,0.2)", + className: + "bg-[rgba(217,217,217,0.2)] text-gray-900 border border-gray-300", + }, +]; + +const grayScale = [ + { shade: 100, hex: "#FAFAFA" }, + { shade: 150, hex: "#F7F7F7" }, + { shade: 200, hex: "#F2F2F2" }, + { shade: 300, hex: "#D1D1D1" }, + { shade: 400, hex: "#BABABA" }, + { shade: 500, hex: "#A3A3A3" }, + { shade: 600, hex: "#8C8C8B" }, + { shade: 700, hex: "#374151" }, + { shade: 800, hex: "#484746" }, + { shade: 850, hex: "#373737" }, + { shade: 900, hex: "#31302F" }, + { shade: 950, hex: "#2D3034" }, + { shade: 1000, hex: "#1E1E1E" }, + { shade: 1050, hex: "#14171C" }, + { shade: 1100, hex: "#101318" }, +]; + +const accents = [ + { label: "Red 100", className: "bg-red-100" }, + { label: "Red 200", className: "bg-red-200" }, + { label: "Red 300", className: "bg-red-300" }, + { label: "Red 400", className: "bg-red-400" }, + { label: "Purple 100", className: "bg-purple-100" }, + { label: "Purple 200", className: "bg-purple-200" }, +]; + +const screenInfo = [ + { label: "mobile", value: "≤ 743px" }, + { label: "tablet", value: "744px – 1279px" }, + { label: "pc", value: "≥ 1280px" }, +]; + +export const FontColor: Story = { + render: () => ( +
+
+
+

+ SF Pro 폰트 & 커스텀 컬러 테스트 +

+
+ +
+

+ Typography (SF Pro) +

+
+
+

Title

+
+

+ Title Hero (32px Bold) +

+

+ Title Page MD (40px Bold) +

+

+ Title Page SM (32px Bold) +

+
+
+ +
+

Heading

+
+

+ Heading LG (24px SemiBold) +

+

+ Heading MD (20px Bold) +

+

+ Heading SM (18px Bold) +

+
+
+ +
+

Body

+
+

+ Body Large (18px SemiBold) - 나만의 와인창고, Whyne +

+

+ Body Medium (16px Medium) - 나만의 와인창고, Whyne +

+

+ Body Small (14px Medium) - 나만의 와인창고, Whyne +

+
+
+ +
+

+ Caption & Component +

+
+

+ Caption (12px Regular) +

+

+ Component Notes MD (12px Regular) +

+

+ Component Notes SM (10px Regular) +

+
+
+ + +
+
+
+
+ +
+

+ Tailwind Custom Colors +

+ +
+

+ 기본 팔레트 +

+
+ {primaryPalette.map(({ name, value, className }) => ( +
+
{name}
+
{value}
+
+ ))} +
+
+ +
+

+ Grayscale +

+
+ {grayScale.map(({ shade, hex }) => ( +
+
Gray {shade}
+
{hex}
+
+ ))} +
+
+ +
+

Accent

+
+ {accents.map(({ label, className }) => ( +
+
{label}
+
+ ))} +
+
+
+ +
+

+ Breakpoints +

+
+ {screenInfo.map(({ label, value }) => ( +
+
{label}
+
{value}
+
+ ))} +
+
+
+
+ ), +}; diff --git a/src/app/(landing)/_components/landing-section.tsx b/src/app/(landing)/_components/landing-section.tsx index 2061f6b1..ff65b0fc 100644 --- a/src/app/(landing)/_components/landing-section.tsx +++ b/src/app/(landing)/_components/landing-section.tsx @@ -71,7 +71,8 @@ const LandingSection = ({ src={imgSrc} alt={imgAlt} fill - sizes="(min-width: 1024px) 725px, 100vw" + draggable={false} + sizes="(min-width: 1024px) 725px, 100vw " className="object-cover" />
diff --git a/src/app/@modal/(.)register/[id]/page.tsx b/src/app/@modal/(.)register/[id]/page.tsx index 1a8a0f1e..dfd44935 100644 --- a/src/app/@modal/(.)register/[id]/page.tsx +++ b/src/app/@modal/(.)register/[id]/page.tsx @@ -17,7 +17,7 @@ const Page = async ({ params }: { params: Promise<{ id: string }> }) => { return ( diff --git a/src/app/globals.css b/src/app/globals.css index b44e0a6e..24499690 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -133,3 +133,7 @@ div.swiper-scrollbar-drag { div.swiper-scrollbar { background: var(--color-gray-300); } + +.break-word { + word-break: break-word; +} diff --git a/src/app/myprofile/_components/account-item/account-item.tsx b/src/app/myprofile/_components/account-item/account-item.tsx index e147c7c1..f5972513 100644 --- a/src/app/myprofile/_components/account-item/account-item.tsx +++ b/src/app/myprofile/_components/account-item/account-item.tsx @@ -71,8 +71,8 @@ const AccountItem = ({ user }: AccountItemProps) => { return (
@@ -110,8 +110,9 @@ const AccountItem = ({ user }: AccountItemProps) => { autoFocus maxLength={10} className={cn( - "w-2/3 flex-1 rounded-[4px] border border-gray-300 px-4 py-3 focus:outline-none", - "tablet:flex-1 pc:w-full" + "h-[42px] w-2/3 flex-1 rounded-[4px] border border-gray-300 px-4 py-3 focus:outline-none", + "tablet:h-[50px] tablet:flex-1", + "pc:h-[50px] pc:w-full" )} />
diff --git a/src/app/wines/_components/wine-options/type-option.tsx b/src/app/wines/_components/wine-options/type-option.tsx index 5ea52663..e9cc0c37 100644 --- a/src/app/wines/_components/wine-options/type-option.tsx +++ b/src/app/wines/_components/wine-options/type-option.tsx @@ -49,7 +49,7 @@ const TypeItem = ({ alt="타입" draggable={false} /> - + {item} @@ -75,7 +75,7 @@ const TypeOption = () => { return (

타입

-
+
{(WINE_TYPE as WineType[]).map((item) => ( {
); diff --git a/src/components/button/chat-button.tsx b/src/components/button/chat-button.tsx index 34779f2d..e1da6800 100644 --- a/src/components/button/chat-button.tsx +++ b/src/components/button/chat-button.tsx @@ -23,7 +23,7 @@ const ChatButton = () => { icon="GptIcon" iconSize="md" className={cn( - "relative h-[40px] w-[40px] rounded-full border-gray-300", + "relative h-[45px] w-[45px] rounded-full border-gray-300", "tablet:h-[50px] tablet:w-[50px]", "pc:h-[50px] pc:w-[50px]" )} diff --git a/src/components/button/scroll-top-button.tsx b/src/components/button/scroll-top-button.tsx index b55650b4..6c93889d 100644 --- a/src/components/button/scroll-top-button.tsx +++ b/src/components/button/scroll-top-button.tsx @@ -26,7 +26,7 @@ const ScrollTopButton = ({}: ScrollTopButtonProps) => { icon="ArrowTopIcon" iconSize="md" className={cn( - "h-[40px] w-[40px] rounded-full border-gray-300", + "h-[45px] w-[45px] rounded-full border-gray-300", "tablet:h-[50px] tablet:w-[50px]", "pc:h-[50px] pc:w-[50px]" )} diff --git a/src/components/card/card-img.tsx b/src/components/card/card-img.tsx index 4db82936..69cd2c91 100644 --- a/src/components/card/card-img.tsx +++ b/src/components/card/card-img.tsx @@ -75,9 +75,15 @@ const CardImage = ({ placeholder="blur" blurDataURL={blurDataURL ?? fallbackBlurDataURL} onError={() => setHasError(true)} + draggable={false} /> ) : ( - {alt} + {alt} )}
diff --git a/src/components/card/card-info.tsx b/src/components/card/card-info.tsx index c483927d..a3578266 100644 --- a/src/components/card/card-info.tsx +++ b/src/components/card/card-info.tsx @@ -1,3 +1,4 @@ +import { cn } from "@/lib/utils"; import Rating from "@/components/rating/rating"; /** @@ -35,21 +36,27 @@ const CardInfo = ({ avgRating, reviewCount = 0, className, - ratingWrapperClassName, - reviewCountClassName, - nameClassName, - regionClassName, - priceClassName, + ratingWrapperClassName = "", + reviewCountClassName = "", + nameClassName = "", + regionClassName = "", + priceClassName = "", }: CardInfoProps) => { return ( -
+
{typeof avgRating === "number" && (
{reviewCount > 0 ? `${reviewCount.toLocaleString()}개의 후기` @@ -58,18 +65,25 @@ const CardInfo = ({
)}
-
+
{name}
{region}
{typeof price === "number" && (
{price.toLocaleString()}원
diff --git a/src/components/card/card.tsx b/src/components/card/card.tsx index 6b9d0596..f35ac525 100644 --- a/src/components/card/card.tsx +++ b/src/components/card/card.tsx @@ -76,7 +76,7 @@ const Card = ({ blurDataURL={blurDataURL} /> )} -
+
{href ? ( { width={100} height={100} className="rounded-4" + draggable={false} /> {label} @@ -42,7 +43,7 @@ const Flavor = ({ count, items, showHeader = true }: FlavorProps) => {
)} -
+
{items.map((item, index) => ( { aria-label="메인페이지 이동" className="flex items-center leading-[26px] text-white" > - + -
{user ? (
-
+
{ const token = request.cookies.get("accessToken")?.value; + const requestedPage = request.nextUrl.pathname; - if (token) { + if (!token) { + if (requestedPage === "/login" || requestedPage === "/signup") { + return NextResponse.next(); + } + + const url = request.nextUrl.clone(); + url.pathname = "/login"; + url.searchParams.set("redirect", requestedPage); + + return NextResponse.redirect(url); + } + + if (requestedPage === "/login" || requestedPage === "/signup") { return NextResponse.redirect(new URL("/myprofile", request.url)); } @@ -11,7 +24,14 @@ const middleware = (request: NextRequest) => { }; export const config = { - matcher: ["/login", "/signup"], + matcher: [ + "/myprofile", + "/wine/:path*", + "/register/:path*", + "/review/:path*", + "/login", + "/signup", + ], }; export default middleware; diff --git a/tailwind.config.ts b/tailwind.config.ts index 5ad01d06..f9e6da46 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -54,6 +54,7 @@ export default { "title-hero": ["32px", { lineHeight: "46px", fontWeight: "700" }], "title-page-md": ["40px", { lineHeight: "52px", fontWeight: "700" }], "title-page-sm": ["32px", { lineHeight: "46px", fontWeight: "700" }], + "title-page-xs": ["28px", { lineHeight: "36px", fontWeight: "700" }], "heading-lg": ["24px", { lineHeight: "32px", fontWeight: "600" }], "heading-md": ["20px", { lineHeight: "30px", fontWeight: "700" }], "heading-sm": ["18px", { lineHeight: "30px", fontWeight: "700" }],