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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/images/store.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";

import { redirectToKakaoOAuthLoginPage } from "@/app/(auth)/_api/auth/auth.api";
import KakaoLogoIcon from "@/assets/kakao-logo.svg";
import KoreanOrangeLogo from "@/assets/korean-orange-logo.svg";
import LogoWordmarkIcon from "@/assets/logo-wordmark.svg";
import { Button } from "@/components/ui/Button";
import { GNB } from "@/components/ui/GNB";
import { TextButton } from "@/components/ui/TextButton";
Expand Down Expand Up @@ -37,7 +37,7 @@ export default function LoginPage() {
}
/>
<div className={styles.logoWrapper}>
<KoreanOrangeLogo className={styles.logoIcon} />
<LogoWordmarkIcon className={styles.logoIcon} />
<Image
src='/images/login-house.png'
alt='로그인 일러스트'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useDebounce } from "react-simplikit";

import { useNicknameCheckMutation } from "@/app/member/_api/member.queries";
import { nicknameSchema } from "@/app/member/_schemas";
import ClearIcon from "@/assets/circle-clear.svg";
import CircleCloseIcon from "@/assets/circle-close.svg";
import { Button } from "@/components/ui/Button";
import { VStack } from "@/components/ui/Stack";
import { TextField } from "@/components/ui/TextField";
Expand Down Expand Up @@ -108,7 +108,7 @@ export const NicknameStep = ({ nickname, onNext }: NicknameStepProps) => {
helperText={errors.nickname?.message}
rightAddon={
nicknameValue && (
<ClearIcon
<CircleCloseIcon
width={22}
height={22}
className={styles.icon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
import { usePhoneNumberCheckMutation } from "@/app/member/_api/member.queries";
import { phoneNumberSchema } from "@/app/member/_schemas";
import { type PhoneNumberFormValues } from "@/app/member/_types/member.types";
import ClearIcon from "@/assets/circle-clear.svg";
import CircleCloseIcon from "@/assets/circle-close.svg";
import { Button } from "@/components/ui/Button";
import { VStack } from "@/components/ui/Stack";
import { TextField } from "@/components/ui/TextField";
Expand Down Expand Up @@ -89,7 +89,7 @@ export const PhoneNumberStep = ({
helperText={errors.phoneNumber?.message}
rightAddon={
phoneNumberValue && (
<ClearIcon
<CircleCloseIcon
width={22}
height={22}
className={styles.icon}
Expand Down
4 changes: 2 additions & 2 deletions src/app/member/onboarding/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useRouter } from "next/navigation";
import { type ReactNode } from "react";

import LeftArrowIcon from "@/assets/left-arrow.svg";
import ChevronLeftIcon from "@/assets/chevron-left.svg";
import { GNB } from "@/components/ui/GNB";

import * as styles from "./layout.css";
Expand All @@ -28,7 +28,7 @@ export default function OnboardingLayout({ children }: OnboardingLayoutProps) {
aria-label='뒤로가기'
className={styles.button}
>
<LeftArrowIcon width={20} height={20} />
<ChevronLeftIcon width={20} height={20} />
</button>
}
/>
Expand Down
18 changes: 18 additions & 0 deletions src/app/member/profile/_components/Banner/Banner.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { style } from "@vanilla-extract/css";

import { colors, semantic } from "@/styles";

export const wrapper = style({
backgroundColor: colors.redOrange[90],
padding: "1.4rem 2.4rem",
});

export const linkWrapper = style({
display: "flex",
alignItems: "center",
gap: "0.4rem",
});

export const icon = style({
color: semantic.icon.black,
});
38 changes: 38 additions & 0 deletions src/app/member/profile/_components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import Image from "next/image";
import Link from "next/link";

import ChevronRightIcon from "@/assets/chevron-right.svg";
import { Bleed } from "@/components/ui/Bleed";
import { HStack, VStack } from "@/components/ui/Stack";
import { Text } from "@/components/ui/Text";

import * as styles from "./Banner.css";

export const Banner = () => {
return (
<Bleed inline={20} className={styles.wrapper}>
<HStack align='center' gap={8}>
<Image
src='/images/store.png'
alt='가게 이미지'
width={55}
height={55}
/>
<VStack gap={4} justify='center'>
<Text typo='body1Sb' color='neutral.10'>
당신의 소중한 가게를 소개해주세요
</Text>
{/* TODO: [가게 등록하기] url로 변경 */}
<Link href='/' className={styles.linkWrapper}>
<Text typo='label2' color='neutral.10'>
가게 등록하기
</Text>
<ChevronRightIcon width={16} height={16} className={styles.icon} />
</Link>
</VStack>
</HStack>
</Bleed>
);
};
1 change: 1 addition & 0 deletions src/app/member/profile/_components/Banner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Banner } from "./Banner";
16 changes: 16 additions & 0 deletions src/app/member/profile/_components/MenuList/MenuList.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { style } from "@vanilla-extract/css";

import { semantic, typography } from "@/styles";

export const wrapper = style({
padding: "1.6rem 0",
});

export const list = style({
padding: "1.4rem 0",
});

export const menuItem = style({
...typography.body1Sb,
color: semantic.text.normal,
});
31 changes: 31 additions & 0 deletions src/app/member/profile/_components/MenuList/MenuList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import Link from "next/link";

import { VStack } from "@/components/ui/Stack";

import { MENU_LIST } from "../../_constants";
import * as styles from "./MenuList.css";

export const MenuList = () => {
return (
<>
<VStack as='ul' gap={8} className={styles.wrapper}>
{MENU_LIST.map(menu => (
<li key={menu.id} className={styles.list}>
{menu.type === "link" ? (
<Link href={menu.link} className={styles.menuItem}>
{menu.label}
</Link>
) : (
<button type='button' className={styles.menuItem}>
{menu.label}
</button>
)}
</li>
))}
</VStack>
{/* TODO: 로그아웃시 나타날 모달 연결 */}
</>
);
};
1 change: 1 addition & 0 deletions src/app/member/profile/_components/MenuList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MenuList } from "./MenuList";
19 changes: 19 additions & 0 deletions src/app/member/profile/_components/Profile/Profile.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { style } from "@vanilla-extract/css";

import { colors, radius } from "@/styles";

export const wrapper = style({
paddingBottom: "2rem",
});

export const imageBackground = style({
display: "flex",
justifyContent: "center",
width: "7rem",
height: "7rem",
paddingTop: "1.6rem",
backgroundColor: colors.redOrange[80],
border: `1px solid ${colors.redOrange[70]}`,
borderRadius: radius.circle,
overflow: "hidden",
});
23 changes: 23 additions & 0 deletions src/app/member/profile/_components/Profile/Profile.tsx
Copy link
Member

Choose a reason for hiding this comment

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

P3: useSuspenseQuery, suspense, error boundary로 loading, error 처리의 관심을 분리해보는 건 어떨까요?!

Copy link
Contributor Author

@wkdtnqls0506 wkdtnqls0506 Jul 20, 2025

Choose a reason for hiding this comment

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

deaed52

오.. 그렇게 분리하면 컴포넌트에서는 성공한 상태만 관리하면 되니 좋군요..!! 수정했습니다 ~ . ~👍🏻👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

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

한가지 궁금한 점이 있습니다 ~ . ~
닉네임과 전화번호만 필요한 데이터라 해당 부분만 스켈레톤 처리가 필요해서 ProfileLayout을 상위에 따로 생성하고 컴포넌트와 Skeleton을 따로 선언해서 사용했는데요!!
일부 데이터만 로딩 처리가 필요한 경우엔 보통 어떤 방식으로 처리하시나용?!

Copy link
Member

Choose a reason for hiding this comment

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

수빈님 하신 것처럼 처리하기도 합니다!

최대한 먼저 보여줄 수 있는 부분은 보여주는 것이 경험에 더 좋아서, 적절히 영역을 나눠서 처리하면 좋아요~! ex) a 데이터 가져온 후 b 데이터 가져와서 a+b를 보여줄 때, a 데이터를 먼저 보여줘도 괜찮으면 그거 먼저 보여주고 b 로딩되면 b도 보여주고,,

이 질문이 맞으실까요?,,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 정확히 맞습니다~!~! Profile 전체를 처리할지, 아니면 필요한 부분만 레아이웃으로 분리해서 관리하는지 궁금해서 여쭤봤습니다 ㅎㅎ
감사해요!!🙇🏻‍♀️👍🏻

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { useSuspenseQuery } from "@tanstack/react-query";

import { memberQueryOptions } from "@/app/member/_api";
import { Text } from "@/components/ui/Text";

import { ProfileLayout } from "./ProfileLayout";

export const Profile = () => {
const { data: member } = useSuspenseQuery(memberQueryOptions);

return (
<ProfileLayout>
<Text typo='title2Sb' color='neutral.10'>
{member.nickname}
</Text>
<Text typo='caption1Md' color='neutral.50'>
{member.email}
</Text>
</ProfileLayout>
);
};
25 changes: 25 additions & 0 deletions src/app/member/profile/_components/Profile/ProfileLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import { type ReactNode } from "react";

import Bapurit from "@/assets/logo/front-bapurit.svg";
import { HStack, VStack } from "@/components/ui/Stack";

import * as styles from "./Profile.css";

export type ProfileLayoutProps = {
children: ReactNode;
};

export const ProfileLayout = ({ children }: ProfileLayoutProps) => {
return (
<HStack gap={16} className={styles.wrapper}>
<div className={styles.imageBackground}>
<Bapurit width={61} height={61} />
</div>
<VStack justify='center' gap={8}>
{children}
</VStack>
</HStack>
);
};
13 changes: 13 additions & 0 deletions src/app/member/profile/_components/Profile/ProfileSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Skeleton } from "@/components/ui/Skeleton";
import { radius } from "@/styles";

import { ProfileLayout } from "./ProfileLayout";

export const ProfileSkeleton = () => {
return (
<ProfileLayout>
<Skeleton width={80} height={20} radius={radius[40]} />
<Skeleton width={160} height={20} radius={radius[40]} />
</ProfileLayout>
);
};
1 change: 1 addition & 0 deletions src/app/member/profile/_components/Profile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Profile } from "./Profile";
1 change: 1 addition & 0 deletions src/app/member/profile/_constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./menuList.constants";
9 changes: 9 additions & 0 deletions src/app/member/profile/_constants/menuList.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type MenuListItem } from "../types";

// TODO: 노션, 인스타 링크 전달받으면 수정
export const MENU_LIST: readonly MenuListItem[] = [
{ type: "link", id: "notice", label: "공지사항", link: "/" },
{ type: "link", id: "customer_service", label: "고객센터", link: "/" },
{ type: "link", id: "guide", label: "잇다 이용가이드", link: "/" },
{ type: "action", id: "logout", label: "로그아웃" },
] as const;
28 changes: 28 additions & 0 deletions src/app/member/profile/layout.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { style } from "@vanilla-extract/css";

import { semantic } from "@/styles";

export const wrapper = style({
height: "100dvh",
display: "flex",
flexDirection: "column",
});

export const button = style({
width: "2.4rem",
height: "2.4rem",
display: "flex",
justifyContent: "center",
alignItems: "center",
});

export const icon = style({
color: semantic.icon.black,
});

export const mainWrapper = style({
display: "flex",
flex: 1,
flexDirection: "column",
padding: "2rem",
});
47 changes: 47 additions & 0 deletions src/app/member/profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { useRouter } from "next/navigation";
import { type ReactNode } from "react";

import ChevronLeftIcon from "@/assets/chevron-left.svg";
import { GNB } from "@/components/ui/GNB";

import * as styles from "./layout.css";

type MemberProfileLayoutProps = {
children: ReactNode;
};

export default function MemberProfileLayout({
children,
}: MemberProfileLayoutProps) {
const router = useRouter();
const handleClick = () => {
router.push("/");
};

return (
<div className={styles.wrapper}>
<GNB
align='center'
title='마이페이지'
leftAddon={
<button
type='button'
onClick={handleClick}
aria-label='홈으로 이동하기'
className={styles.button}
>
<ChevronLeftIcon
width={24}
height={24}
onClick={handleClick}
className={styles.icon}
/>
</button>
}
/>
<main className={styles.mainWrapper}>{children}</main>
</div>
);
}
20 changes: 20 additions & 0 deletions src/app/member/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Suspense } from "react";

import { VStack } from "@/components/ui/Stack";

import { Banner } from "./_components/Banner";
import { MenuList } from "./_components/MenuList";
import { Profile } from "./_components/Profile";
import { ProfileSkeleton } from "./_components/Profile/ProfileSkeleton";

export default function ProfilePage() {
return (
<VStack>
<Suspense fallback={<ProfileSkeleton />}>
<Profile />
</Suspense>
<Banner />
<MenuList />
</VStack>
);
}
1 change: 1 addition & 0 deletions src/app/member/profile/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./menuList.types";
14 changes: 14 additions & 0 deletions src/app/member/profile/types/menuList.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type LinkMenu = {
type: "link";
id: string;
label: string;
link: string;
};

type ActionMenu = {
type: "action";
id: string;
label: string;
};

export type MenuListItem = LinkMenu | ActionMenu;
File renamed without changes
3 changes: 3 additions & 0 deletions src/assets/chevron-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
Loading