Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 선착순 밸런스 게임 구현 #158

Merged
merged 114 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from 104 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
565ca29
feat: 밸런스 게임 카드 데이터 상수화
sooyeoniya Aug 4, 2024
40d385b
feat: 선칙순 밸런스 게임 RushCardDescription 컴포넌트 UI 구현
sooyeoniya Aug 4, 2024
f25b85c
feat: 선착순 밸런스 게임 RushCard 컴포넌트 UI 구현
sooyeoniya Aug 4, 2024
e60991d
feat: 선착순 밸런스 게임 RushCardComparison 컴포넌트 UI 구현
sooyeoniya Aug 4, 2024
6787f39
fix: CARD_COLOR 타입 오류 수정
sooyeoniya Aug 4, 2024
e754349
feat: CARD_TYPE 상수 선언
sooyeoniya Aug 4, 2024
1f05f0c
feat: RushCard 컴포넌트 데이터 동적 업데이트 방식 적용
sooyeoniya Aug 4, 2024
f2add85
refactor: RushCard 컴포넌트 내부 객체 타입 명시
sooyeoniya Aug 4, 2024
b48ba8c
feat: RushCardComparison 상수 데이터 연동 및 RushCard로 Props 전달
sooyeoniya Aug 4, 2024
481123e
chore: 필요 없는 테스트용 컴포넌트 호출 제거
sooyeoniya Aug 4, 2024
2b609f9
fix: RushCard 컴포넌트 내부 ReactNodes[] 렌더링 시 key Props 추가
sooyeoniya Aug 4, 2024
1230052
feat: RushCardDescription 컴포넌트 mock 데이터 임시 연동
sooyeoniya Aug 4, 2024
fd9e8fd
fix: 불필요한 import 제거
sooyeoniya Aug 4, 2024
df7f0c5
fix: 브랜치 충돌 해결 및 머지
sooyeoniya Aug 7, 2024
b647820
Merge branch 'dev' into feat/#82-rush-card
sooyeoniya Aug 7, 2024
e225a98
feat: Rush 타입 선언 및 Fetch API 구현
sooyeoniya Aug 8, 2024
d259f5e
chore: Rush API 관련 주석 삭제
sooyeoniya Aug 8, 2024
bdcdecc
fix: Rush API POST 응답 상태 처리 코드 삭제
sooyeoniya Aug 8, 2024
f3d9921
feat: Main 페이지 Rush 이벤트 경품 API 연동
sooyeoniya Aug 8, 2024
8e5954f
feat: Rush 이벤트 경품 isPastEvent, isTodayEvent 로직 수정
sooyeoniya Aug 8, 2024
3c78a9b
feat: date util 포맷 함수 추가 및 공통 파싱 로직 분리
sooyeoniya Aug 9, 2024
ebbc15c
feat: Main 페이지 Rush 이벤트 기간 API 연동
sooyeoniya Aug 9, 2024
1554c64
feat: RushEvent 컴포넌트 storybook 작성
sooyeoniya Aug 9, 2024
edea461
feat: Lottery 타입 API 명세 반영 및 API 연동
sooyeoniya Aug 9, 2024
252c1e3
feat: date util 연도 추가
sooyeoniya Aug 9, 2024
cc81296
fix: 빌드 오류 해결
sooyeoniya Aug 9, 2024
5d8e753
refactor: formatEventDate 함수 빼기
sooyeoniya Aug 9, 2024
585d852
refactor: Notice fetch API 함수 분리
sooyeoniya Aug 9, 2024
3ef0416
rename: rushEventData 상수 이름 변경
sooyeoniya Aug 9, 2024
0c8322a
feat: Main Headline 임시 API 연동 (추후 전체 기간에 대한 API로 연동)
sooyeoniya Aug 9, 2024
537d75a
refactor: 로딩 데이터 메시지 추가
sooyeoniya Aug 9, 2024
9a3134c
rename: data 이름 변경
sooyeoniya Aug 9, 2024
87c0e36
chore: RushCard 컴포넌트 임시 구현 및 폴더 변경
sooyeoniya Aug 9, 2024
48acdaa
feat: 카운트 다운 훅 구현
sooyeoniya Aug 10, 2024
13bb254
feat: BalanceGame 게임 진행 및 사용자 참여 상태 Context 구현
sooyeoniya Aug 10, 2024
da6e206
feat: RushCard 컴포넌트 description 타입 변경
sooyeoniya Aug 10, 2024
5ec5249
fix: 밸런스게임 사용자 참여 상태 타입 수정
sooyeoniya Aug 10, 2024
1d1f904
feat: countdown timer 0초일 때 종료 로직 추가
sooyeoniya Aug 10, 2024
f7caf8f
feat: 밸런스 게임 카운트 다운 관련 API 연동 및 각 섹션 분기 처리
sooyeoniya Aug 10, 2024
f9d5022
chore: RushCard 컴포넌트 스토리북 임시 주석 처리
sooyeoniya Aug 10, 2024
285744b
chore: 밸런스 게임 카운트 다운 API 미연동에 대한 테스트용 코드
sooyeoniya Aug 10, 2024
b21ba80
feat: 밸런스 게임 상수 데이터 구조 변경
sooyeoniya Aug 10, 2024
5108933
refactor: Rush 결과 카드 변경된 mock 데이터 연동
sooyeoniya Aug 10, 2024
1583b2e
refactor: RushCard, RushCardComparison 컴포넌트 변경된 mock 데이터 연동
sooyeoniya Aug 10, 2024
13632f1
design: 밸런스 게임 카드 내부 텍스트 width 조정
sooyeoniya Aug 11, 2024
a641fa7
feat: 밸런스 게임 카드 선택 페이지 구현
sooyeoniya Aug 11, 2024
cd4e705
feat: 밸런스 게임 선택한 카드 페이지 구현 및 카운트 다운 공통 컴포넌트 분리
sooyeoniya Aug 11, 2024
224f565
refactor: CountDown 컴포넌트 구조 리팩토링
sooyeoniya Aug 11, 2024
b5f5993
refactor: RushCountDown 컴포넌트 구조 리팩토링
sooyeoniya Aug 11, 2024
f2ecf82
feat: RushCard 선택 POST API 연동 및 카드 옵션 타입 number로 변경
sooyeoniya Aug 11, 2024
f4a8428
chore: RushCardResultDescription Props 수정
sooyeoniya Aug 11, 2024
a62eba0
design: RushCardResultDescription, RushCard 내부 텍스트 width 조정
sooyeoniya Aug 11, 2024
cc1f69b
rename: arrow 버튼 이름 변경
sooyeoniya Aug 11, 2024
72bd7a6
feat: SelectedCard 내부 컴포넌트 구조 변경 및 구현
sooyeoniya Aug 11, 2024
df1545f
fix: 카운트 다운 0초까지 카운트 되도록 수정
sooyeoniya Aug 11, 2024
3b11af9
design: SelectedCard 컴포넌트 인터렉션 추가
sooyeoniya Aug 11, 2024
16d0409
design: RushCardCurrentRatio 컴포넌트 UI 구현
sooyeoniya Aug 11, 2024
220d8bf
feat: Tooltip 5초 후 사라지는 로직 구현
sooyeoniya Aug 11, 2024
6abe5f1
feat: 실시간 비율 API 연동 및 사용자 선택에 따른 내부 요소 분기 처리
sooyeoniya Aug 11, 2024
c0eafeb
refactor: Rush 컴포넌트 파일 위치 수정
sooyeoniya Aug 11, 2024
3c4d6bc
refactor: features Rush 전체 폴더 구조 변경
sooyeoniya Aug 11, 2024
0c409d1
rename: balanceGame rushGame 으로 명칭 변경
sooyeoniya Aug 11, 2024
8b9def7
chore: RushGame API 주석 해제
sooyeoniya Aug 12, 2024
f0177ff
feat: 사용자 전화번호 팝업 인증 훅으로 빼기
sooyeoniya Aug 12, 2024
86c2c36
feat: RUSH_EVENT_DATA와 Rush API 데이터 인덱스 매칭
sooyeoniya Aug 12, 2024
ef02b9c
fix: Rush API 타입 명칭 수정
sooyeoniya Aug 12, 2024
83feff9
chore: Rush startDateTime, endDateTime 초깃값 수정
sooyeoniya Aug 12, 2024
4d48f11
feat: Main 페이지 이벤트 전체 기간 TotalAPI 연동
sooyeoniya Aug 12, 2024
13beb56
fix: 쿠키 path 옵션 루트로 설정
sooyeoniya Aug 12, 2024
a4e385c
feat: 선착순 밸런스 게임 진입 화면 추가 구현
sooyeoniya Aug 12, 2024
e3054b8
fix: 카운트 다운 초깃값 설정 안되는 오류 수정
sooyeoniya Aug 12, 2024
941ea23
feat: useTimer 훅 구현 및 Scroll 컴포넌트 type 수정
sooyeoniya Aug 12, 2024
ed517dc
refactor: Background 컴포넌트 폴더 경로 수정
sooyeoniya Aug 12, 2024
e7f89af
feat: Countdown 표기법 변경 및 밸런스 게임 진행 카운트다운 Props로 전달
sooyeoniya Aug 12, 2024
610a73e
design: RushCard 호버 시 CSS 수정
sooyeoniya Aug 12, 2024
396fd17
feat: RushCard API 연동
sooyeoniya Aug 12, 2024
453ae16
feat: RushCardComparison API 연동
sooyeoniya Aug 12, 2024
97f5948
feat: RushCard 선택 시 POST 후 사용자 참여 여부 반영 및 POST 타입 오류 수정
sooyeoniya Aug 12, 2024
64e2d5c
fix: userParticipated 반영 안되는 오류 수정(API GET response 타입 오류)
sooyeoniya Aug 12, 2024
61bd174
feat: Rush test API 추가
sooyeoniya Aug 13, 2024
561bf6f
feat: useToggleContents 토글 기능 추가 및 훅 이름 변경
sooyeoniya Aug 13, 2024
8090a12
feat: 사용자가 선택한 카드에 대한 결과 카드 API 연동
sooyeoniya Aug 13, 2024
fac92e0
design: Rush 진입 화면 배경 추가
sooyeoniya Aug 13, 2024
6a9c647
design: RushGame 내부 스크롤 삭제 및 padding 조정
sooyeoniya Aug 13, 2024
4329f35
feat: RushGame 상태 관리 타입 및 context 내부 getter, setter 수정
sooyeoniya Aug 13, 2024
09b0dda
feat: Rush 카운트 다운 상태 관리 Context로 분리
sooyeoniya Aug 13, 2024
f6808f2
fix: 게임이 이미 진행 중인 상태에서 0:0:0이 뜨고 게임 화면으로 넘어가는 오류
sooyeoniya Aug 13, 2024
1e7c40f
feat: POST 후 사용자 참여 상태 업데이트 및 사용자가 선택한 옵션 번호 저장
sooyeoniya Aug 13, 2024
edacc26
feat: API로 받은 두 개의 카드 옵션 데이터 context에 저장
sooyeoniya Aug 13, 2024
fb6ffc7
feat: 사용자가 선택한 카드에 대한 설명 카드 context로 상태 관리 반영
sooyeoniya Aug 14, 2024
f2e3e60
feat: 실시간 비율 관련 상태 호출 공통으로 분리 및 API 연동
sooyeoniya Aug 14, 2024
a9af8d7
feat: 사용자 선택에 따라 메인 텍스트 분기 처리
sooyeoniya Aug 14, 2024
02d359f
feat: 비율 소수점 2자리까지 반올림 구현
sooyeoniya Aug 14, 2024
59751a2
feat: FinalResult UI 구현 및 mainText, 비율 연동, 사용자 선택에 따른 메시지, 카텍고리 분기 처리
sooyeoniya Aug 14, 2024
47dc381
feat: FinalResult 인터렉션 추가
sooyeoniya Aug 14, 2024
88a9a19
feat: 카테고리 타입 추가 및 getRushResult API 데이터 변경 반영
sooyeoniya Aug 14, 2024
6e0ff43
feat: 프로그래스바 공통 컴포넌트 분리 및 승리 여부에 따른 텍스트 색상 변경 로직 구현
sooyeoniya Aug 14, 2024
461df26
feat: 프로그래스바 비율에 따른 CSS 반영
sooyeoniya Aug 14, 2024
8f4d216
chore: API 연동 후 사용하지 않는 상수 데이터 삭제(CARD_OPTIONS)
sooyeoniya Aug 15, 2024
9484905
chore: 외부에서 사용하지 않는 context 함수 value에서 삭제
sooyeoniya Aug 15, 2024
cb915bb
chore: 프로그래스바 컴포넌트 내부 컨벤션 통일
sooyeoniya Aug 15, 2024
527def5
rename: RushGame 관련 폴더명 변경
sooyeoniya Aug 15, 2024
9f63c0a
refactor: RushGame 관련 상수 데이터 및 타입 수정
sooyeoniya Aug 15, 2024
8a53a53
chore: 게임 진행 카운트 다운 시간 변경
sooyeoniya Aug 15, 2024
1d074ca
fix: 충돌 해결 및 토큰 경로 수정
sooyeoniya Aug 15, 2024
c02924a
refactor: useAuth 훅 재구현
sooyeoniya Aug 15, 2024
06ec8b9
refactor: getMsTime 유틸 반영
sooyeoniya Aug 15, 2024
d88b744
refactor: fetchWithTimeout 반영
sooyeoniya Aug 15, 2024
ebf96db
chore: 카운트 변경
sooyeoniya Aug 16, 2024
3d3b90d
refactor: useFetch 훅 반영
sooyeoniya Aug 16, 2024
df54ecd
refactor: Notice/index.tsx 내부 useFetch 훅 반영 시 promise.all 빼고 리팩토링
sooyeoniya Aug 16, 2024
6db3ea4
fix: EventDateData 타입 오류 수정
sooyeoniya Aug 16, 2024
508e696
refactor: PR 리뷰 반영
sooyeoniya Aug 18, 2024
fdad436
refactor: useToggleContents 훅 관련 PR 리뷰 반영
sooyeoniya Aug 18, 2024
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
3 changes: 3 additions & 0 deletions client/public/assets/icons/arrow-line-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions client/public/assets/icons/reload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/assets/rush/two-car.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
130 changes: 130 additions & 0 deletions client/src/apis/rushAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
GetRushBalanceResponse,
GetRushOptionResultResponse,
GetRushResultResponse,
GetRushUserParticipationStatusResponse,
GetTodayRushEventResponse,
GetTotalRushEventsResponse,
PostSelectedRushCardOptionResponse,
} from "@/types/rushApi";

const baseURL = `${import.meta.env.VITE_API_URL}/event/rush`;
const headers = {
"Content-Type": "application/json",
};

export const RushAPI = {
async getRush(): Promise<GetTotalRushEventsResponse> {
try {
const response = await fetch(`${baseURL}`, {
method: "GET",
headers: headers,
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getRushUserParticipationStatus(
token: string
): Promise<GetRushUserParticipationStatusResponse> {
try {
const response = await fetch(`${baseURL}/applied`, {
method: "GET",
headers: { ...headers, Authorization: `Bearer ${token}` },
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getTodayRushEvent(token: string): Promise<GetTodayRushEventResponse> {
try {
const response = await fetch(`${baseURL}/today`, {
method: "GET",
headers: { ...headers, Authorization: `Bearer ${token}` },
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async postSelectedRushOptionApply(
token: string,
optionId: number
): Promise<PostSelectedRushCardOptionResponse> {
try {
const response = await fetch(`${baseURL}/options/${optionId}/apply`, {
method: "POST",
headers: { ...headers, Authorization: `Bearer ${token}` },
});

if (response.status === 204 || response.status === 404) {
return response.status;
}

throw new Error(`Unexpected response status: ${response.status}`);
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getRushOptionResult(
token: string,
optionId: number
): Promise<GetRushOptionResultResponse> {
try {
const response = await fetch(`${baseURL}/options/${optionId}/result`, {
method: "GET",
headers: { ...headers, Authorization: `Bearer ${token}` },
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getRushBalance(token: string): Promise<GetRushBalanceResponse> {
try {
const response = await fetch(`${baseURL}/balance`, {
method: "GET",
headers: { ...headers, Authorization: `Bearer ${token}` },
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getRushResult(token: string): Promise<GetRushResultResponse> {
try {
const response = await fetch(`${baseURL}/result`, {
method: "GET",
headers: { ...headers, Authorization: `Bearer ${token}` },
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
async getRushTodayEventTest(token: string): Promise<PostSelectedRushCardOptionResponse> {
try {
const response = await fetch(`${baseURL}/today/test`, {
method: "GET",
headers: { ...headers, Authorization: `Bearer ${token}` },
});
if (response.status === 204 || response.status === 404) {
return response.status;
}

throw new Error(`Unexpected response status: ${response.status}`);
} catch (error) {
console.error("Error:", error);
throw error;
}
},
};
21 changes: 21 additions & 0 deletions client/src/apis/totalAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { GetTotalEventDateResponse } from "@/types/totalApi.ts";

const baseURL = `${import.meta.env.VITE_API_URL}/event/total`;
const headers = {
"Content-Type": "application/json",
};

export const TotalAPI = {
async getTotal(): Promise<GetTotalEventDateResponse> {
try {
const response = await fetch(`${baseURL}`, {
method: "GET",
headers: headers,
});
return response.json();
} catch (error) {
console.error("Error:", error);
throw error;
}
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export function Background({ children }: PropsWithChildren) {
<div className="absolute top-0 right-[35px] w-[800px] h-[390px] rounded-[29px] overflow-hidden bg-n-white/[.16] z-5">
<div className="w-1/2 h-full float-left bg-gradient-green blur-[40px]" />
</div>
{children}
<div className="flex flex-col gap-6 justify-center items-center w-[800px] h-[390px] bg-n-white rounded-[29px] relative z-20">
{children}
</div>
</motion.div>
);
}
4 changes: 2 additions & 2 deletions client/src/components/CTAButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VariantProps, cva } from "class-variance-authority";
import { Link } from "react-router-dom";
import "@/index.css";
import ArrowIcon from "/public/assets/icons/arrow.svg?react";
import ArrowRightIcon from "/public/assets/icons/arrow-line-right.svg?react";
import ShareIcon from "/public/assets/icons/share.svg?react";

const BUTTON_STATUS: Record<string, ButtonStatusType> = {
Expand Down Expand Up @@ -60,7 +60,7 @@ export default function CTAButton({
const content = (
<>
{label}
{hasArrowIcon && <ArrowIcon stroke={strokeColor} />}
{hasArrowIcon && <ArrowRightIcon stroke={strokeColor} />}
{hasShareIcon && <ShareIcon stroke={strokeColor} />}
</>
);
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Category/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { cva } from "class-variance-authority";

export interface CategoryProps {
children: ReactNode;
type: "basic" | "limited";
type: "basic" | "limited" | "selected";
}

const categoryVariants = cva(`w-fit px-300 py-200 rounded-1000 text-n-white h-body-2-regular`, {
variants: {
type: {
basic: "bg-n-neutral-500",
limited: "bg-s-red",
selected: "bg-s-blue",
},
},
});
Expand Down
80 changes: 53 additions & 27 deletions client/src/components/Notice/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { LotteryAPI } from "@/apis/lotteryAPI.ts";
import { RushAPI } from "@/apis/rushAPI.ts";
import { formatEventDate } from "@/utils/formatDate.ts";

interface EventDetails {
interface EventDateDetails {
startDate: string;
endDate: string;
days: number;
activePeriod: number;
}

interface EventData {
[key: string]: EventDetails;
export interface EventDateData {
[key: string]: EventDateDetails;
}

interface SectionProps {
Expand All @@ -27,27 +30,50 @@ const Section: React.FC<SectionProps> = ({ title, items, indentedIndices = [] })
</div>
);

const getEventsDateDetails = async (): Promise<{
rush: EventDateDetails;
lottery: EventDateDetails;
}> => {
try {
const [rushData, lotteryData] = await Promise.all([
RushAPI.getRush(),
LotteryAPI.getLottery(),
]);

const rushEventDetails: EventDateDetails = {
startDate: rushData.eventStartDate,
endDate: rushData.eventEndDate,
activePeriod: rushData.activePeriod,
};

const lotteryEventDetails: EventDateDetails = {
startDate: lotteryData.eventStartDate,
endDate: lotteryData.eventEndDate,
activePeriod: lotteryData.activePeriod,
};

return {
rush: lotteryEventDetails,
lottery: rushEventDetails,
};
} catch (error) {
console.error("Error: ", error);
return {
rush: { startDate: "", endDate: "", activePeriod: 0 },
lottery: { startDate: "", endDate: "", activePeriod: 0 },
};
}
};

export default function Notice() {
const eventDetails: EventData = {
// TODO: 임시 데이터 -> API로 변경 필요
badgeDraw: {
startDate: "2024.08.23.",
endDate: "2024.09.05.",
days: 14,
},
balanceGame: {
startDate: "2024.08.31.",
endDate: "2024.09.05.",
days: 6,
},
};
const formatEventItem = (eventName: string, eventKey: keyof EventData) => {
const event = eventDetails[eventKey];
if (event) {
return `${eventName} : ${event.startDate} ~ ${event.endDate} (${event.days}일)`;
}
return `${eventName} : 날짜를 불러오는 중입니다...`;
};
const [eventDateDetails, setEventDateDetails] = useState<EventDateData>({});

useEffect(() => {
(async () => {
const details = await getEventsDateDetails();
setEventDateDetails(details);
})();
}, []);

return (
<div className="w-full h-[756px] flex flex-col gap-y-5 bg-n-neutral-100 py-20 px-[180px] text-n-black snap-center">
Expand All @@ -56,8 +82,8 @@ export default function Notice() {
title="이벤트 참여"
items={[
"이벤트 기간",
formatEventItem("캐스퍼봇 뱃지 추첨 이벤트", "badgeDraw"),
formatEventItem("선착순 밸런스 게임 이벤트", "balanceGame"),
formatEventDate("캐스퍼봇 뱃지 추첨 이벤트", "rush", eventDateDetails),
formatEventDate("선착순 밸런스 게임 이벤트", "lottery", eventDateDetails),
"선착순 밸런스 게임 이벤트는 이벤트 기간 내 매일 하루에 한 번씩, 기간 내 최대 6번 참여 가능합니다.",
"이벤트 참여 시 당첨자 연락을 위해 전화번호 기재와 개인정보 수집 동의, 마케팅 정보 수신 동의가 필수로 요구됩니다.",
"본 이벤트에서 제작해주신 캐스퍼봇 이미지는 추후 마케팅에 이용될 수 있습니다.",
Expand Down
Loading