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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
"@vanilla-extract/dynamic": "^2.1.5",
"@vanilla-extract/recipes": "^0.5.7",
"es-toolkit": "^1.39.7",
"framer-motion": "^12.23.9",
"iron-session": "^8.0.4",
"ky": "1.7.5",
"motion": "^12.23.11",
"lottie-react": "^2.4.1",
"next": "15.3.2",
"react": "^19.0.0",
Expand Down
34 changes: 28 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/app/(search)/_api/search.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { authHttp } from "@/lib/api";
import { http } from "@/lib/api";

import { type StoreSearchResponse } from "./search.types";

Expand All @@ -10,7 +10,7 @@ import { type StoreSearchResponse } from "./search.types";
export const getStoreSearch = async (
query: string
): Promise<StoreSearchResponse> => {
return await authHttp
return await http
.get("api/shop/search", {
searchParams: { query },
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useQuery } from "@tanstack/react-query";
import { AnimatePresence, motion } from "framer-motion";
import { AnimatePresence, motion } from "motion/react";
import { type ReactNode, useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useDebounce } from "react-simplikit";
Expand Down
13 changes: 13 additions & 0 deletions src/app/member/_components/Avatar/Avatar.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { style } from "@vanilla-extract/css";

import { radius } from "@/styles";

export const avatar = style({
width: "3.4rem",
height: "3.4rem",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "0.6rem",
borderRadius: radius.circle,
});
41 changes: 41 additions & 0 deletions src/app/member/_components/Avatar/Avatar.tsx
Copy link
Member

Choose a reason for hiding this comment

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

오오우,,!

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Bapurit from "@/assets/logo/symbol.svg";

import { PROFILE_COLORS } from "../../_constants";
import { getProfileColorIndex } from "../../_utils";
import * as styles from "./Avatar.css";

type AvatarProps = {
/** 사용자 고유 ID */
memberId: number;

className?: string;
};

/**
* 사용자별 고유한 색상 배경을 가진 프로필 아바타 컴포넌트
*
* @description
* memberId를 기반으로 3가지 색상(노랑, 분홍, 파랑) 중 하나를 자동으로 할당합니다.
* 같은 memberId는 항상 같은 색상을 가집니다.
*
* @example
* ```tsx
* <Avatar memberId={123} />
* <Avatar memberId={story.memberId} />
* ```
*/
export const Avatar = ({ memberId, className }: AvatarProps) => {
const colorIndex = getProfileColorIndex(memberId);
const colorConfig = PROFILE_COLORS[colorIndex];

return (
<div
className={`${styles.avatar} ${className || ""}`}
style={{
backgroundColor: colorConfig.background,
}}
>
<Bapurit width={23} height={23} />
</div>
);
};
1 change: 1 addition & 0 deletions src/app/member/_components/Avatar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Avatar } from "./Avatar";
1 change: 1 addition & 0 deletions src/app/member/_constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./profileColors.constants";
19 changes: 19 additions & 0 deletions src/app/member/_constants/profileColors.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const PROFILE_COLORS = {
0: {
background: "#FFF1AF",
name: "yellow",
},
1: {
background: "#FFDDDA",
name: "pink",
},
2: {
background: "#C9ECFF",
name: "blue",
},
} as const;

export type ProfileColorIndex = keyof typeof PROFILE_COLORS;

// 프로필 색상의 개수
export const PROFILE_COLOR_COUNT = Object.keys(PROFILE_COLORS).length;
3 changes: 2 additions & 1 deletion src/app/member/_utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { phoneNumberUtils } from "./phoneNumberUtils";
export * from "./phoneNumberUtils";
export * from "./profileUtils";
12 changes: 12 additions & 0 deletions src/app/member/_utils/profileUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {
PROFILE_COLOR_COUNT,
type ProfileColorIndex,
} from "../_constants/profileColors.constants";

/**
* memberId를 기반으로 프로필 색상 인덱스 반환 (0, 1, 2)
* memberId를 색상 개수로 나눈 나머지 사용
*/
export const getProfileColorIndex = (memberId: number): ProfileColorIndex => {
return (Math.abs(memberId) % PROFILE_COLOR_COUNT) as ProfileColorIndex;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { type ReactNode } from "react";

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

import * as styles from "./Profile.css";
Expand Down
14 changes: 14 additions & 0 deletions src/app/story/[id]/_api/detail.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { http } from "@/lib/api";

import { type StoryDetailResponse } from "./detail.types";

/**
* 스토리 상세 정보 조회 API
* @param storyId - 조회할 스토리 ID
* @returns {Promise<StoryDetailResponse>} 스토리 상세 정보
*/
export const getStoryDetail = async (
storyId: string
): Promise<StoryDetailResponse> => {
return await http.get(`api/stories/${storyId}`).json<StoryDetailResponse>();
};
12 changes: 12 additions & 0 deletions src/app/story/[id]/_api/detail.queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getStoryDetail } from "./detail.api";

export const storyDetailQueryKeys = {
all: ["story", "detail"] as const,
detail: (id: string) => [...storyDetailQueryKeys.all, id] as const,
} as const;

export const storyDetailQueryOptions = (storyId: string) => ({
queryKey: storyDetailQueryKeys.detail(storyId),
queryFn: () => getStoryDetail(storyId),
enabled: !!storyId,
});
11 changes: 11 additions & 0 deletions src/app/story/[id]/_api/detail.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type StoryDetailResponse = {
storeKakaoId: string;
category: string;
storeName: string;
storeDistrict: string;
storeNeighborhood: string;
description: string | null;
imageUrl: string;
memberId: number;
memberNickname: string;
};
3 changes: 3 additions & 0 deletions src/app/story/[id]/_api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./detail.api";
export * from "./detail.queries";
export * from "./detail.types";
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { style } from "@vanilla-extract/css";

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

export const container = style({
position: "relative",
display: "flex",
flexDirection: "column",
width: "100%",
height: "100dvh",
backgroundColor: semantic.dark.normal,
});

export const gnbOverlay = style({
position: "absolute",
top: 0,
left: 0,
right: 0,
});

export const cancelIconWrapper = style({
display: "flex",
justifyContent: "center",
alignItems: "center",
});

export const cancelIcon = style({
width: "2.4rem",
height: "2.4rem",
color: semantic.icon.white,
});

export const imageCard = style({
position: "relative",
width: "100%",
height: "100%",
flex: 1,
overflow: "hidden",
});

export const image = style({
objectFit: "contain",
});

export const imageContent = style({
position: "absolute",
bottom: 0,
left: 0,
right: 0,
padding: "0 2rem 2rem",
display: "flex",
flexDirection: "column",
gap: "1.2rem",
zIndex: 10,
});

export const userWrapper = style({
width: "100%",
display: "flex",
alignItems: "center",
gap: "1.2rem",
});

export const descriptionContainer = style({
width: "100%",
position: "relative",
});

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

export const descriptionText = style({
cursor: "pointer",
wordBreak: "keep-all",
wordWrap: "break-word",
});

export const collapsed = style({
display: "-webkit-box",
WebkitLineClamp: 1,
WebkitBoxOrient: "vertical",
overflow: "hidden",
textOverflow: "ellipsis",
});

export const expanded = style({
display: "block",
overflow: "visible",
});

export const tag = style({
display: "flex",
alignItems: "center",
gap: "0.8rem",
padding: "0.8rem 1.2rem",
borderRadius: radius.circle,
border: "1px solid rgba(255, 255, 255, 0.16)",
background: " rgba(0, 0, 0, 0.28)",
});

export const tagIcon = style({
width: "1.6rem",
height: "1.6rem",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: semantic.icon.white,
});

export const descriptionOverlay = style({
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: "60%",
background:
"linear-gradient(180deg, rgba(40, 40, 40, 0.00) 0%, #282828 100%)",
});
Loading