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-229] 상점 모달 로딩 처리 #982

Merged
merged 16 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { StorybookConfig } from '@storybook/nextjs';
const config: StorybookConfig = {
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'],
staticDirs: ['../public'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
Expand Down
13 changes: 13 additions & 0 deletions components/modal/LoadingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import styles from 'styles/modal/LoadingButton.module.scss';

export default function LoadingButton() {
return (
<div className={styles.loadingButton}>
<div className={styles.loading}>
<span className={styles.span1}>o</span>
<span className={styles.span2}>o</span>
<span className={styles.span3}>o</span>
</div>
</div>
);
}
Comment on lines +3 to +13
Copy link
Member

Choose a reason for hiding this comment

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

이 컴포넌트 이름은 버튼인데 사실은 버튼이 아니라 (?) 나중에 보면 헷갈릴수도 있을 것 같아요 🥲
LoadingEffect같이 애니메이션이나 효과를 보여주는 컴포넌트 이름으로 바꾸면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

사실 버튼은 아니지만 버튼 모양처럼 생기긴해서 버튼이라고 지었습니다! ㅎㅎ 로딩애니메이션은 따로 컴포넌트로 분리하진 않아서 이것도 시간 되면 분리하면 좋을 것 같긴 하네요!

16 changes: 14 additions & 2 deletions components/modal/ModalButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import LoadingButton from 'components/modal/LoadingButton';
import styles from 'styles/modal/Modal.module.scss';

type ButtonProps = {
style: 'positive' | 'negative';
value: string;
form?: string;
isLoading?: boolean;
onClick: () => void;
};

Expand All @@ -15,10 +17,20 @@ export function ModalButtonContainer({
return <div className={styles.modalButtonContainer}>{children}</div>;
}

export function ModalButton({ style, value, onClick, form }: ButtonProps) {
export function ModalButton({
style,
value,
onClick,
form,
isLoading,
}: ButtonProps) {
return (
<div className={styles[style]}>
<input onClick={onClick} type='button' value={value} form={form} />
{isLoading ? (
<LoadingButton />
) : (
<input onClick={onClick} type='button' value={value} form={form} />
)}
</div>
);
}
21 changes: 14 additions & 7 deletions components/modal/store/CoinHistoryContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ICoinHistory } from 'types/userTypes';
import CoinHistoryDetails from 'components/modal/store/CoinHistoryDetails';
import ErrorEmoji from 'public/image/noti_empty.svg';
import styles from 'styles/modal/store/CoinHistoryContainer.module.scss';

type CoinHistoryProps = {
Expand All @@ -12,14 +13,20 @@ export default function CoinHistoryContainer({
return (
<div className={styles.container}>
{useCoinList.length === 0 ? (
<div className={styles.empty}>GG코인 내역이 존재하지 않습니다.</div>
<div className={styles.empty}>
<div>GG코인 내역이 존재하지 않습니다.</div>
<ErrorEmoji />
</div>
) : (
useCoinList.map((coinHistory) => (
<CoinHistoryDetails
key={coinHistory.createdAt.toString()}
details={coinHistory}
/>
))
useCoinList.map(
(coinHistory) =>
coinHistory.amount !== 0 && (
<CoinHistoryDetails
key={coinHistory.createdAt.toString()}
details={coinHistory}
/>
)
)
)}
</div>
);
Expand Down
15 changes: 13 additions & 2 deletions components/modal/store/UserCoinHistoryModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import styles from 'styles/modal/store/UserCoinHistoryModal.module.scss';

export default function UserCoinHistoryModal({ coin }: ICoin) {
const setModal = useSetRecoilState(modalState);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [currentPage, setCurrentPage] = useState<number>(1);
const [coinHistoryList, setCoinHistoryList] = useState<ICoinHistoryTable>({
useCoinList: [],
Expand All @@ -33,8 +34,8 @@ export default function UserCoinHistoryModal({ coin }: ICoin) {
getCoinHistoryList();
}, [currentPage]);

// 현재는 출석만 되는 상태
const getCoinHistoryList = async () => {
setIsLoading(true);
try {
const res = await instance.get(
`pingpong/users/coinhistory/?page=${currentPage}&size=5`
Expand All @@ -46,7 +47,9 @@ export default function UserCoinHistoryModal({ coin }: ICoin) {
});
} catch (e) {
setError('HB06');
closeModal();
}
setIsLoading(false);
};

return (
Expand All @@ -56,7 +59,15 @@ export default function UserCoinHistoryModal({ coin }: ICoin) {
<div>현재 코인</div>
<CoinImage amount={coin} size={25} />
</div>
<CoinHistoryContainer useCoinList={coinHistoryList.useCoinList} />
{isLoading ? (
<div className={styles.loading}>
<span className={styles.span1}>*</span>
<span className={styles.span2}>*</span>
<span className={styles.span3}>*</span>
</div>
) : (
<CoinHistoryContainer useCoinList={coinHistoryList.useCoinList} />
)}
<div>
<PageNation
curPage={currentPage}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { useResetRecoilState, useSetRecoilState } from 'recoil';
import { UseItemRequest } from 'types/inventoryTypes';
import { Modal } from 'types/modalTypes';
Expand All @@ -23,11 +24,13 @@ const caution = [
export default function ChangeProfileBackgroundModal({
receiptId,
}: ChangeProfileBackgroundModalProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const resetModal = useResetRecoilState(modalState);
const setModal = useSetRecoilState<Modal>(modalState);
const setError = useSetRecoilState<string>(errorState);

const gachaAction = async () => {
setIsLoading(true);
const data: UseItemRequest = {
receiptId: receiptId,
};
Expand All @@ -43,6 +46,7 @@ export default function ChangeProfileBackgroundModal({
} catch (error) {
setError('HB05');
}
setIsLoading(false);
};

return (
Expand All @@ -55,15 +59,12 @@ export default function ChangeProfileBackgroundModal({
</div>
<ItemCautionContainer caution={caution} />
<ModalButtonContainer>
<ModalButton
style='negative'
value='취소'
onClick={() => resetModal()}
/>
<ModalButton style='negative' value='취소' onClick={resetModal} />
<ModalButton
style='positive'
value='뽑기'
onClick={() => gachaAction()}
isLoading={isLoading}
onClick={gachaAction}
/>
</ModalButtonContainer>
</div>
Expand Down
13 changes: 7 additions & 6 deletions components/modal/store/inventory/ChangeProfileEdgeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { useResetRecoilState, useSetRecoilState } from 'recoil';
import { UseItemRequest } from 'types/inventoryTypes';
import { Modal } from 'types/modalTypes';
Expand All @@ -23,11 +24,13 @@ const caution = [
export default function ChangeProfileEdgeModal({
receiptId,
}: ChangeProfileEdgeModalProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const resetModal = useResetRecoilState(modalState);
const setModal = useSetRecoilState<Modal>(modalState);
const setError = useSetRecoilState<string>(errorState);

const gachaAction = async () => {
setIsLoading(true);
const data: UseItemRequest = {
receiptId: receiptId,
};
Expand All @@ -43,6 +46,7 @@ export default function ChangeProfileEdgeModal({
} catch (error) {
setError('HB04');
}
setIsLoading(false);
};

return (
Expand All @@ -55,15 +59,12 @@ export default function ChangeProfileEdgeModal({
</div>
<ItemCautionContainer caution={caution} />
<ModalButtonContainer>
<ModalButton
style='negative'
value='취소'
onClick={() => resetModal()}
/>
<ModalButton style='negative' value='취소' onClick={resetModal} />
<ModalButton
style='positive'
value='뽑기'
onClick={() => gachaAction()}
isLoading={isLoading}
onClick={gachaAction}
/>
</ModalButtonContainer>
</div>
Expand Down
39 changes: 27 additions & 12 deletions components/modal/store/purchase/BuyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import { useEffect, useState } from 'react';
import { Purchase } from 'types/itemTypes';
import { useState } from 'react';
import { useSetRecoilState, useResetRecoilState } from 'recoil';
import { PriceTag } from 'types/modalTypes';
import { instance } from 'utils/axios';
import { errorState } from 'utils/recoil/error';
import { modalState } from 'utils/recoil/modal';
import {
ModalButtonContainer,
ModalButton,
} from 'components/modal/ModalButton';
import useBuyModal from 'hooks/modal/store/purchase/useBuyModal';
import styles from 'styles/modal/store/BuyModal.module.scss';

export default function BuyModal({ itemId, product, price }: PriceTag) {
const [purchaseItem, setPurchaseItem] = useState<Purchase>({ itemId: -1 });
const { onPurchase, onCancel } = useBuyModal(purchaseItem);
const [isLoading, setIsLoading] = useState<boolean>(false);
const resetModal = useResetRecoilState(modalState);
const setError = useSetRecoilState<string>(errorState);

useEffect(() => {
setPurchaseItem({
itemId: itemId,
});
}, [itemId]);
// TODO: 에러 처리
const onPurchase = async () => {
setIsLoading(true);
try {
await instance.post(`/pingpong/items/purchases/${itemId}`, null);
alert(`구매 성공!`);
} catch (error) {
setError('HB03');
}
setIsLoading(false);
resetModal();
Copy link
Contributor

Choose a reason for hiding this comment

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

setIsLoading(false)는 없어도 될거 같아요! 요청이 빠르게 완료되고 나면 로딩 버튼 애니메이션이랑 아닐 때 버튼 배경이랑 겹치는 것 같아요

};

return (
<div className={styles.container}>
Expand All @@ -38,8 +48,13 @@ export default function BuyModal({ itemId, product, price }: PriceTag) {
</div>
</div>
<ModalButtonContainer>
<ModalButton style='negative' value='아니오' onClick={onCancel} />
<ModalButton style='positive' value='예' onClick={onPurchase} />
<ModalButton style='negative' value='아니오' onClick={resetModal} />
<ModalButton
style='positive'
value='예'
isLoading={isLoading}
onClick={onPurchase}
/>
</ModalButtonContainer>
</div>
);
Expand Down
59 changes: 41 additions & 18 deletions components/modal/store/purchase/GiftModal.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
import { useEffect, useState } from 'react';
import { Gift } from 'types/itemTypes';
import { useState } from 'react';
import { useSetRecoilState, useResetRecoilState } from 'recoil';
import { GiftRequest } from 'types/itemTypes';
import { PriceTag } from 'types/modalTypes';
import { instance } from 'utils/axios';
import { errorState } from 'utils/recoil/error';
import { modalState } from 'utils/recoil/modal';
import {
ModalButtonContainer,
ModalButton,
} from 'components/modal/ModalButton';
import GiftSearchBar from 'components/store/purchase/GiftSearchBar';
import useGiftModal from 'hooks/modal/store/purchase/useGiftModal';
import styles from 'styles/modal/store/GiftModal.module.scss';

export default function GiftModal({ itemId, product, price }: PriceTag) {
const [recipient, setRecipient] = useState<string>('');
const [gift, setGift] = useState<Gift>({
itemId: -1,
const [isLoading, setIsLoading] = useState<boolean>(false);
const resetModal = useResetRecoilState(modalState);
const setError = useSetRecoilState<string>(errorState);
const [giftReqData, setGiftReqData] = useState<GiftRequest>({
ownerId: '',
});
const { onPurchase, onCancel } = useGiftModal(gift);

useEffect(() => {
setGift({
itemId: itemId,
ownerId: recipient,
});
}, [itemId, recipient]);
// TODO: 에러 처리
const onPurchase = async () => {
if (giftReqData.ownerId === '') {
alert('선물할 유저를 선택해주세요.');
return;
}
setIsLoading(true);
try {
const res = await instance.post(
`/pingpong/items/gift/${itemId}`,
giftReqData
);
if (res.status === 201) {
alert(`${giftReqData.ownerId}님께 선물이 전달되었습니다.`);
}
Copy link
Member

Choose a reason for hiding this comment

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

여기 res.status가 201인 경우에는 처리가 되어서 alert가 뜰 것 같은데, 200같은 다른 성공 응답이 혹시라도 온다면 이 부분에 대해서는 처리가 안되어 있어서 바로 모달이 꺼질 것 같아요...!
특별히 res.status 201에 대한 처리가 필요한 게 아니라면 그냥 catch 블록으로 넘어가지 않았을 경우에는 성공이라고 판단하고 alert를 띄우는 것도 괜찮지 않을까 싶습니다 ^_ㅜ

Suggested change
const res = await instance.post(
`/pingpong/items/gift/${itemId}`,
giftReqData
);
if (res.status === 201) {
alert(`${giftReqData.ownerId}님께 선물이 전달되었습니다.`);
}
const res = await instance.post(
`/pingpong/items/gift/${itemId}`,
giftReqData
);
alert(`${giftReqData.ownerId}님께 선물이 전달되었습니다.`);

Copy link
Member Author

Choose a reason for hiding this comment

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

그렇게 하는게 좋을 것 같네요! 수정하겠습니다👍

} catch (error) {
setError('HB02');
}
setIsLoading(false);
resetModal();
};

return (
<div className={styles.container}>
Expand All @@ -39,19 +57,24 @@ export default function GiftModal({ itemId, product, price }: PriceTag) {
<div>{price} 코인</div>
</div>
</div>
<GiftSearchBar setRecipient={setRecipient} />
{recipient !== '' && (
<GiftSearchBar setGiftReqData={setGiftReqData} />
{giftReqData.ownerId !== '' && (
<div className={styles.recipient}>
<span>{recipient}</span>님에게 선물하시겠습니까?
<span>{giftReqData.ownerId}</span>님에게 선물하시겠습니까?
</div>
)}
<div className={styles.warning}>
<p>⚠ 선물한 아이템은 환불 및 취소가 불가합니다 ⚠</p>
</div>
</div>
<ModalButtonContainer>
<ModalButton style='negative' value='취소' onClick={onCancel} />
<ModalButton style='positive' value='보내기' onClick={onPurchase} />
<ModalButton style='negative' value='취소' onClick={resetModal} />
<ModalButton
style='positive'
value='보내기'
isLoading={isLoading}
onClick={onPurchase}
/>
</ModalButtonContainer>
</div>
);
Expand Down
Loading