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/227 #337

Merged
merged 20 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f3b146f
[feat]: 펀딩함 기본 탭 구현(#227)
devkyoung2 May 20, 2024
d91d198
Merge branch 'dev' into feat/227
devkyoung2 May 23, 2024
7e009bf
Merge branch 'dev' into feat/227
devkyoung2 May 23, 2024
bccca6a
Merge branch 'dev' into feat/227
devkyoung2 May 27, 2024
4e5df44
[feat]: 펀딩함 EmptyItem type 추가(#227)
devkyoung2 May 28, 2024
4887783
[feat] : 펀딩함 타입 작성(#227)
devkyoung2 May 28, 2024
3e0ebdf
[feat]: 펀딩함 api 요청을 위한 함수 구현(#227)
devkyoung2 May 28, 2024
b63c51c
[feat]: 펀딩아이템스토리북 작성(#227)
devkyoung2 May 28, 2024
c670ead
[remove]: 사용되지 않는 펀딩탭 삭제(#227)
devkyoung2 May 28, 2024
a33bfb0
[feat]: 펀딩함 레이아웃 구현 및 api 연동(#227)
devkyoung2 May 28, 2024
e707c3e
[feat]: 펀딩탭 레이아웃 구현 및 api 연동(#227)
devkyoung2 May 28, 2024
2a34213
[refactor]: DDay 함수 분리, 1년후 함수 로직 수정(#227)
devkyoung2 May 29, 2024
66ca435
[refactor]: 공통 믹스인 추출(#227)
devkyoung2 May 29, 2024
960ac64
[feat]: 펀딩함 펀딩 아이템 구현(#227)
devkyoung2 May 29, 2024
a8f92ca
[feat]: 펀딩함 펀딩상품 응답 타입 수정(#227)
devkyoung2 May 29, 2024
d6b5841
[feat]: 펀딩함 펀딩상품 응답 타입 수정(#227)
devkyoung2 May 29, 2024
1a4fb8c
[feat]: 탭 모드 추가(#227)
devkyoung2 May 31, 2024
7c7d4ab
Merge branch 'dev' into feat/227
devkyoung2 May 31, 2024
ee51e31
[refactor]: 잘못된 변수명 수정(#227)
devkyoung2 Jun 3, 2024
33eb46e
[refactor]: getOneYearLaterDate함수 리팩토링(#227)
devkyoung2 Jun 3, 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
8 changes: 8 additions & 0 deletions src/components/feature/EmptyItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ const EMPTY_ITEM_TEXT = {
title: `사용한 선물이 없어요`,
description: `받은 선물🎁을 사용하고, 친구에게 고마운 마음을 표현해보세요~!`,
},
funding_usable: {
title: `사용 가능한 펀딩이 없어요`,
description: `펀딩받고 싶은 선물🎁이 있나요?\n내 취향에 맞는 선물을 등록해보세요~!`,
},
funding_used: {
title: `사용한 펀딩이 없어요`,
description: `받은 펀딩을 사용하고, 친구들에게 고마운 마음을 표현해보세요~!`,
},
history_order: {
title: `주문내역이 없어요`,
description: `소중한 친구에게 마음❤️을 전해보아요~!`,
Expand Down
7 changes: 6 additions & 1 deletion src/components/ui/Tabs/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
}
}

.product_list {
.product_list,
.received_box {
.wrapper_tab {
margin-right: 33px;
padding: 14px 0;
Expand All @@ -67,6 +68,10 @@
}
}

.received_box {
margin-bottom: 30px;
}

.funding_history {
.wrapper_tab {
width: 50%;
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import styles from './index.module.scss';
type TabProps = {
initialTabId: Tab['id'];
tabs: Tab[];
mode: 'product_list' | 'product_detail' | 'funding_history';
mode: 'product_list' | 'product_detail' | 'funding_history' | 'received_box';
};

const Tabs = ({ initialTabId = 0, tabs, mode }: TabProps) => {
Expand Down
7 changes: 7 additions & 0 deletions src/layouts/MyPage/FundingBox/FundingTab/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.list_funding {
display: grid;
grid-template-columns: repeat(4, 1fr);
row-gap: 30px;
max-width: 840px;
margin: 30px 0 100px;
}
67 changes: 67 additions & 0 deletions src/layouts/MyPage/FundingBox/FundingTab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useQuery } from '@tanstack/react-query';

import { useEffect, useState } from 'react';

import EmptyItem from 'components/feature/EmptyItem';
import Spinner from 'components/ui/Spinner';

import { useInfinityScroll } from 'hooks/useInfinityScroll';
import { getMyFundingItems } from 'services/api/v1/funding';

import { MyFundingItemType } from 'types/funding';

import MyFundingItem from '../MyFundingItem';

import styles from './index.module.scss';

type FundingTabProps = {
status: 'USABLE' | 'USED';
};

const FundingTab = ({ status }: FundingTabProps) => {
const [fundingItems, setFundingItems] = useState<MyFundingItemType[]>([]);
const [hasNext, setHasNext] = useState<boolean>(true);
const [page, setPage] = useState<number>(0);

const { data, isLoading, refetch } = useQuery({
queryKey: ['fundingItem', status],
queryFn: () => getMyFundingItems(status),
});

const observingTarget = useInfinityScroll(() => {
if (data) setPage(data.pageNumber + 1);
}, hasNext);

useEffect(() => {
refetch();
}, [page]);

useEffect(() => {
if (data) {
setFundingItems((prev) => [...prev, ...data.items]);
setHasNext(data.hasNext);
}
}, [data]);

if (!hasNext && fundingItems.length === 0) {
const emptyType = status === 'USABLE' ? 'funding_usable' : 'funding_used';

return <EmptyItem type={emptyType} />;
}

return (
<>
<ul className={styles.list_funding}>
{fundingItems.map((fundingItem) => (
<li key={fundingItem.id}>
<MyFundingItem fundingItem={fundingItem} status={status} />
</li>
))}
</ul>
{isLoading && <Spinner />}
{hasNext && <div ref={observingTarget} />}
</>
);
};

export default FundingTab;
67 changes: 67 additions & 0 deletions src/layouts/MyPage/FundingBox/MyFundingItem/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@import 'styles/mixins.module';

.badge {
position: absolute;
top: 10px;
right: 10px;

&.used {
@include img-badge(0, -55px, 50px, 50px);
}

&.canceled {
@include img-badge(-55px, -55px, 50px, 50px);
}
}

.d_day {
display: inline-block;
position: absolute;
top: 0;
left: 0;
background-color: #888;
color: #fff;
font-size: 14px;
padding: 5px 8px;
letter-spacing: -0.03em;
}

.wrapper_funding {
$width: 196px;

width: $width;
position: relative;

.wrapper_thumb {
@include wrapper-prod-img($width, 0);

.img_unavailable {
opacity: 0.4;
}
}

.txt_brand {
display: block;
padding-top: 9px;
font-size: 13px;
color: #5990c7;

@include text-ellipsis(1);
}

.txt_prod {
display: block;
padding-top: 4px;
font-size: 15px;
color: #666;

@include text-ellipsis(1);
}
}

.txt_date {
margin-top: 8px;
font-size: 12px;
color: #a0a0a0;
letter-spacing: -0.028em;
}
50 changes: 50 additions & 0 deletions src/layouts/MyPage/FundingBox/MyFundingItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import clsx from 'clsx';

import { getDDay, getOneYearLaterDate } from 'utils/generate';

import {
MyFundingItemType,
STATUS_TEXT,
FundingItemStatusType,
} from 'types/funding';

import styles from './index.module.scss';

type MyFundingItemProps = {
fundingItem: MyFundingItemType;
status: 'USABLE' | 'USED';
};

const MyFundingItem = ({ fundingItem, status }: MyFundingItemProps) => {
const { product, receivedDate } = fundingItem;
const { name, photo, brandName } = product;
const expiredAt = getOneYearLaterDate(receivedDate).toString();

return (
<div className={styles.wrapper_funding}>
<div className={styles.wrapper_thumb}>
<img
src={photo}
alt={name}
className={clsx({ [styles.img_unavailable]: status !== 'USABLE' })}
/>
</div>
<span className={styles.txt_brand}>{brandName}</span>
<strong className={styles.txt_prod}>{name}</strong>

{status === 'USABLE' ? (
<span className={styles.d_day}>D-{getDDay(expiredAt)}</span>
) : (
<span className={clsx(styles.badge, styles[status.toLowerCase()])}>
{STATUS_TEXT[status as FundingItemStatusType]}
</span>
)}

<span className={styles.txt_date}>
{new Date(receivedDate).toLocaleString()}
</span>
</div>
);
};

export default MyFundingItem;
7 changes: 0 additions & 7 deletions src/layouts/MyPage/GiftBox/GiftItem/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
@import 'styles/mixins.module';

@mixin img-badge($x, $y, $width, $height) {
@include img-sprite($x, $y, $width, $height);

background-image: url('assets/badge_state.png');
background-size: 325px 105px;
}

.badge {
position: absolute;
top: 10px;
Expand Down
10 changes: 3 additions & 7 deletions src/layouts/MyPage/GiftBox/GiftItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import clsx from 'clsx';

import { getDDay } from 'utils/generate';

import { Gift, STATUS_TEXT, StatusType } from 'types/Gift';

import styles from './index.module.scss';
Expand All @@ -16,12 +18,6 @@ const GiftItem = ({ gift, status }: GiftItemProps) => {
receivedAt,
} = gift;

const getDDay = () => {
const diffTime = new Date(expiredAt).getTime() - new Date().getTime(); // 시간 차이, 단위: ms
const DDay = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); // 밀리초 / 1일
return DDay;
};

return (
<div className={styles.wrapper_gift}>
<div className={styles.wrapper_thumb}>
Expand All @@ -35,7 +31,7 @@ const GiftItem = ({ gift, status }: GiftItemProps) => {
<strong className={styles.txt_prod}>{productName}</strong>

{status === 'NOT_USED' ? (
<span className={styles.d_day}>D-{getDDay()}</span>
<span className={styles.d_day}>D-{getDDay(expiredAt)}</span>
) : (
<span className={clsx(styles.badge, styles[status.toLowerCase()])}>
{STATUS_TEXT[status]}
Expand Down
49 changes: 41 additions & 8 deletions src/pages/MyPage/FundingBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
import { useQuery } from '@tanstack/react-query';

import Spinner from 'components/ui/Spinner';
import Tabs from 'components/ui/Tabs';
import FundingTab from 'layouts/MyPage/FundingBox/FundingTab';

import { getMyFundingItems } from 'services/api/v1/funding';

import { Tab } from 'types/tab';

import styles from './index.module.scss';

const FundingBox = () => {
const { data, isLoading } = useQuery({
queryKey: ['fundingItem'],
queryFn: () => getMyFundingItems(),
});

const tabs: Tab[] = [
{
id: 0,
name: `사용 가능한 펀딩아이템`,
content: <FundingTab status="USABLE" />,
},
{
id: 1,
name: `사용 완료한 펀딩아이템 `,
content: <FundingTab status="USED" />,
},
];

return (
<>
<h1 className={styles.txt_title}>
총 n개의
<br />
펀딩을 달성했습니다. 🎉
</h1>
</>
<div>
{isLoading && <Spinner />}
{data && (
<>
<h1 className={styles.txt_title}>
총 {`${data.totalElements}`}개의
<br />
펀딩을 달성했습니다. 🎉
</h1>
<Tabs initialTabId={0} mode="received_box" tabs={tabs} />
</>
)}
</div>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/pages/MyPage/GiftBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const GiftBox = () => {
<br />
확인해보세요 🎁
</h1>
<Tabs initialTabId={0} mode="product_list" tabs={tabs} />
<Tabs initialTabId={0} mode="received_box" tabs={tabs} />
</>
);
};
Expand Down
18 changes: 18 additions & 0 deletions src/services/api/v1/funding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PaginationResponse } from 'types/PaginationResponse';
import { MyFundingItemType } from 'types/funding';

import { apiV1 } from '.';

export const getMyFundingItems = async (status?: string) => {
const baseUrl = `/funding/gift`;

if (status) {
const myFundingItems = await apiV1.get(`${baseUrl}?status=${status}`);

return myFundingItems.data as PaginationResponse<MyFundingItemType>;
}

const myFundingItems = await apiV1.get(baseUrl);

return myFundingItems.data as PaginationResponse<MyFundingItemType>;
};
Loading
Loading