Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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.

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
16 changes: 16 additions & 0 deletions src/app/story/[id]/_api/detail.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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 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