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

[GGFE-144] 상점 아이템 보관함 페이지 #917

Merged
merged 19 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9e8a1fd
[Feat] [GGFE-144] Inventory 타입 추가
yoouyeon Aug 3, 2023
1facbab
[Feat] [GGFE-144] Inventory Component 추가
yoouyeon Aug 3, 2023
7b7166f
[Test] [GGFE-144] items mock api 추가
yoouyeon Aug 3, 2023
80f6a3c
[FEAT] [GGFE-144] Inventory api를 위한 InfinityScroll 함수 추가
yoouyeon Aug 4, 2023
7376955
[Feat] [GGFE-144] api 호출 로직 적용
yoouyeon Aug 4, 2023
e06781a
[Feat] [GGFE-144] InventoryList 컴포넌트 추가
yoouyeon Aug 4, 2023
d11e40a
[Feat] [GGFE-144] InvetoryItem 컴포넌트 추가
yoouyeon Aug 4, 2023
b0ba5f5
[Style] [GGFE-144] 이미지, 상태 뱃지 스타일 적용
yoouyeon Aug 4, 2023
6e962cd
[Style] [GGFE-144] 상단 tooltip 적용
yoouyeon Aug 4, 2023
3a8d018
[Style] [GGFE-144] 글자 정렬
yoouyeon Aug 4, 2023
2f9bdd8
[Feat] [GGFE-144] dummy button handler 추가
yoouyeon Aug 4, 2023
619662f
[Feat] [GGFE-144] 무한스크롤 적용
yoouyeon Aug 6, 2023
4c4337f
[Feat] [GGFE-144] 무한스크롤 컴포넌트 분리
yoouyeon Aug 6, 2023
dfa647b
[Feat] [GGFE-144] 빈 보관함인 경우 안내 메시지 추가
yoouyeon Aug 6, 2023
52ae3e5
Merge branch 'main' into GGFE-144-상점-아이템-보관함-페이지
yoouyeon Aug 7, 2023
a6fa795
[Chore] [GGFE-144] 불필요한 클릭 핸들러 삭제
yoouyeon Aug 7, 2023
0e6e238
[Chore] [GGFE-144] 불필요한 div 태그 삭제
yoouyeon Aug 7, 2023
a4af302
[Feat] [GGFE-144] coin 정보 props로 넘길 수 있게 수정
yoouyeon Aug 7, 2023
c54bb84
[Style] [GGFE-144] 아이템 보관함 이미지 contain으로 수정
yoouyeon Aug 7, 2023
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
33 changes: 33 additions & 0 deletions components/store/InfiniteScrollComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useEffect, useRef } from 'react';
import { RiPingPongFill } from 'react-icons/ri';
import styles from 'styles/store/Inventory.module.scss';

type InfiniteScrollComponentProps = {
fetchNextPage: () => void;
hasNextPage?: boolean;
};

export function InfiniteScrollComponent({
fetchNextPage,
hasNextPage,
}: InfiniteScrollComponentProps) {
const interactionObserverRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!interactionObserverRef.current || !hasNextPage) return;
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
fetchNextPage();
}
});
observer.observe(interactionObserverRef.current);
return () => observer.disconnect();
}, [fetchNextPage, hasNextPage]);

if (!hasNextPage) return null;
return (
<div className={styles.loadIcon} ref={interactionObserverRef}>
<RiPingPongFill />
</div>
);
}
12 changes: 12 additions & 0 deletions components/store/Inventory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { QueryClientProvider, QueryClient } from 'react-query';
import { InventoryList } from './InventoryList';

export function Inventory() {
const queryClient = new QueryClient();

return (
<QueryClientProvider client={queryClient}>
<InventoryList />{' '}
</QueryClientProvider>
);
}
59 changes: 59 additions & 0 deletions components/store/InventoryItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Image from 'next/image';
import { Tooltip } from '@mui/material';
import { useRecoilValue } from 'recoil';
import { BsGiftFill, BsCircleFill } from 'react-icons/bs';
import { InventoryItem } from 'types/storeTypes';
import { userState } from 'utils/recoil/layout';
import styles from 'styles/store/Inventory.module.scss';

type inventoryItemProps = {
item: InventoryItem;
};

export function InvetoryItem({ item }: inventoryItemProps) {
const user = useRecoilValue(userState);

const { itemId, name, imageUrl, purchaserIntra, itemStatus } = item;

function handleUseItem(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
e.preventDefault();
console.log(`use item ${itemId}`);
}

function handleEditItem(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
e.preventDefault;
console.log(`edit item ${itemId}`);
}

return (
<div key={itemId} className={styles.inventoryItem}>
<div className={styles.topBadgeContainer}>
{user.intraId !== purchaserIntra ? (
<Tooltip title={`from ${purchaserIntra}`}>
<button>
<BsGiftFill />
</button>
</Tooltip>
) : (
<div></div>
)}
{itemStatus === 'USING' && (
<div className={styles.usingBadge}>
<BsCircleFill /> 사용중
</div>
)}
</div>
<div className={styles.overlay}>
{itemStatus === 'USING' ? (
<button onClick={handleEditItem}>편집하기</button>
) : (
<button onClick={handleUseItem}>사용하기</button>
)}
</div>
<div className={styles.imgContainer}>
<Image className={styles.img} src={imageUrl} alt={name} fill />
</div>
<div className={styles.itemName}>{name}</div>
</div>
);
}
48 changes: 48 additions & 0 deletions components/store/InventoryList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { InvetoryItem } from './InventoryItem';
import { InfiniteScrollComponent } from './InfiniteScrollComponent';
import { InfinityScroll } from 'utils/infinityScroll';
import { mockInstance } from 'utils/mockAxios';
import styles from 'styles/store/Inventory.module.scss';

function fetchInventoryData(page: number) {
return mockInstance.get(`items?page=${page}&size=${8}`).then((res) => {
return res.data;
});
}

export function InventoryList() {
const { data, error, isLoading, hasNextPage, fetchNextPage } = InfinityScroll(
'inventory',
fetchInventoryData,
'JY03'
);

// TODO : 에러 컴포넌트 구체화 필요함.
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
if (!data) return <div>No data</div>;

return (
<div className={styles.inventoryList}>
{data.pages.map((page, pageIndex) => (
<React.Fragment key={pageIndex}>
{page.storageItemList.length === 0 ? (
<div className={styles.emptyMessage}>
보유한 아이템이 없습니다.
<br /> 상점 탭에서 아이템을 구입해 보세요!
</div>
) : (
page.storageItemList.map((item) => (
<InvetoryItem key={item.itemId} item={item} />
))
)}
</React.Fragment>
))}
<InfiniteScrollComponent
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
/>
</div>
);
}
157 changes: 157 additions & 0 deletions pages/api/pingpong/items/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { InventoryData, InventoryItem } from 'types/storeTypes';

const item1: InventoryItem = {
itemId: 1,
name: '테스트1',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'hyobicho',
itemStatus: 'USING',
};

const item2: InventoryItem = {
itemId: 2,
name: '테스트2',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'hyungjpa',
itemStatus: 'BEFORE',
};

const item3: InventoryItem = {
itemId: 3,
name: '테스트3',
imageUrl:
'https://dodo.ac/np/images/thumb/1/17/NH_Balloon.jpg/600px-NH_Balloon.jpg',
purchaserIntra: 'jeyoon',
itemStatus: 'USING',
};

const item4: InventoryItem = {
itemId: 4,
name: '테스트4',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'sangmipa',
itemStatus: 'USED',
};

const item5: InventoryItem = {
itemId: 5,
name: '테스트5',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'hyobicho',
itemStatus: 'BEFORE',
};

const item6: InventoryItem = {
itemId: 6,
name: '테스트6',
imageUrl:
'https://dodo.ac/np/images/thumb/1/17/NH_Balloon.jpg/600px-NH_Balloon.jpg',
purchaserIntra: 'hyungjpa',
itemStatus: 'BEFORE',
};

const item7: InventoryItem = {
itemId: 7,
name: '테스트7',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'jeyoon',
itemStatus: 'USING',
};

const item8: InventoryItem = {
itemId: 8,
name: '테스트8',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'sangmipa',
itemStatus: 'USED',
};

const item9: InventoryItem = {
itemId: 9,
name: '테스트9',
imageUrl:
'https://dodo.ac/np/images/thumb/1/17/NH_Balloon.jpg/600px-NH_Balloon.jpg',
purchaserIntra: 'hyobicho',
itemStatus: 'BEFORE',
};

const item10: InventoryItem = {
itemId: 10,
name: '테스트10',
imageUrl: 'https://cdn.nookazon.com/nookazon/icons/leaf.png',
purchaserIntra: 'hyungjpa',
itemStatus: 'BEFORE',
};

const storageItemList: InventoryItem[] = [
item1,
item2,
item3,
item4,
item5,
item6,
item7,
item8,
item9,
item10,
];

// 빈 인벤토리
const InventoryDataEmpty: InventoryData = {
storageItemList: [],
totalPage: 0,
};

// 페이지 1개
const InventoryDataSingle: InventoryData = {
storageItemList: storageItemList.slice(0, 4),
totalPage: 1,
};

// 페이지 2개
const InventoryDataDouble: InventoryData = {
storageItemList: storageItemList,
totalPage: 2,
};

function pagination(
Copy link
Contributor

Choose a reason for hiding this comment

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

이런식으로 테스트데이터 보내는 건 생각도 못했네요 배워갑니다!

page: number,
size: number,
storageItemList: InventoryItem[]
): InventoryItem[] {
const ret = [];
for (let index = 0; index < storageItemList.length; index += size) {
ret.push(storageItemList.slice(index, index + size));
}
return ret[page - 1];
}

export default function handler(
req: NextApiRequest,
res: NextApiResponse<InventoryData>
) {
// NOTE: 테스트 케이스에 맞춰서 다른 데이터를 보내주면 됩니다.
// const InventoryData: InventoryData = InventoryDataEmpty;
// const InventoryData: InventoryData = InventoryDataSingle;
const InventoryData: InventoryData = InventoryDataDouble;

const { page, size } = req.query;

const ret: InventoryData = {
storageItemList: [],
totalPage: InventoryData.totalPage,
};
if (page && size) {
const storageItemList = pagination(
Number(page as string),
Number(size as string),
InventoryData.storageItemList
);
if (InventoryData.totalPage !== 0) {
ret.storageItemList = storageItemList;
}
}

res.status(200).json(ret);
}
3 changes: 2 additions & 1 deletion pages/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from 'react';
import { useRouter } from 'next/router';
import { StoreMode } from 'types/storeTypes';
import { StoreModeWrap } from 'components/mode/modeWraps/StoreModeWrap';
import { Inventory } from 'components/store/Inventory';
import styles from 'styles/store/StoreContainer.module.scss';
import ItemsList from 'components/shop/ItemsList';

Expand All @@ -27,7 +28,7 @@ export default function Store() {
<ItemsList />
</div>
) : (
<div>INVENTORY</div>
<Inventory />
)}
</div>
</div>
Expand Down
Loading