Skip to content

Commit

Permalink
feat: 선물 기능 추가 (#124)
Browse files Browse the repository at this point in the history
* feat: 선물 등록 페이지 route 추가

* feat: sh0 typo 추가, sh1 typo line-height 수정

* feat: 선물 등록 페이지 route 변경

* feat: 선물 페이지 추가

* feat: 선물 수령 화면 컴포넌트 추가

* feat: 선물 초대장 이미지 추가

* feat: 선물 가이드 컴포넌트 추가

* feat: 선물 초대장 정보 컴포넌트 추가

* feat: 선물 페이지에 컴포넌트 렌더링

* feat: 라우트 분리 및 UI 스타일 조금 수정

* feat: 버튼에 커서 포인터 적용

* feat: 선물 조회 추가 및 유효하지 않은 선물 대으

* feat: draggable false 적용

* feat: 선물 등록 페이지에 데이터 연동

* fix: 선물 등록하기 페이지 파일 이름 변경

* feat: 선물 거절하기 mutation 추가 및 적용

* feat: 선물 약관 컴포넌트 추가

* feat: QR 페이지 앱링크 추가

* feat: 선물 정보 컴포넌트 앱링크 처리

* fix: type checking

---------

Co-authored-by: Minsu Kim <[email protected]>
  • Loading branch information
hexdrinker and alstn2468 authored Jul 21, 2024
1 parent 86e0da4 commit 0f34bfb
Show file tree
Hide file tree
Showing 26 changed files with 719 additions and 8 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
- Storybook : https://ui.boolti.in

## 기술 스택

- React
- TypeScript
- Turborepo
- Vite

## 패키지 설명

- `apps/admin`: 불티에서 공연을 생성하고 관리하는 사용자들을 위한 서비스입니다.
- `apps/preview`: 공연 예매 페이지를 공유했을 때 랜딩될 페이지입니다. (WIP)
- `apps/super-admin`: 불티 팀원이 사용할 슈퍼 어드민 페이지입니다. (WIP)
Expand Down
10 changes: 10 additions & 0 deletions apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import ShowSettlementPage from './pages/ShowSettlementPage/ShowSettlementPage';
import ShowTicketPage from './pages/ShowTicketPage/ShowTicketPage';
import SignUpCompletePage from './pages/SignUpComplete/SignUpCompletePage';
import SitePolicyPage from './pages/SitePolicyPage/SitePolicyPage';
import GiftRegisterPage from './pages/GiftRegisterPage';
import GiftIntroPage from './pages/GiftIntroPage';
import { useAuthAtom } from './atoms/useAuthAtom';

setDefaultOptions({ locale: ko });
Expand Down Expand Up @@ -67,6 +69,14 @@ const publicRoutes = [
path: PATH.SITE_POLICY,
element: <SitePolicyPage />,
},
{
path: PATH.GIFT_INTRO,
element: <GiftIntroPage />,
},
{
path: PATH.GIFT_REGISTER,
element: <GiftRegisterPage />,
},
{
path: '*',
element: <Navigate to={PATH.INDEX} replace />,
Expand Down
Binary file added apps/admin/src/assets/images/gift-invitation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions apps/admin/src/constants/link.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const LINK = {
TERMS: 'https://boolti.notion.site/b4c5beac61c2480886da75a1f3afb982',
PRIVACY_POLICY: 'https://boolti.notion.site/5f73661efdcd4507a1e5b6827aa0da70',
// TODO: 다이나믹 링크값 변경
DYNAMIC_LINK:
'https://boolti.page.link/?link=https://preview.boolti.in/show/&apn=com.nexters.boolti&ibi=com.nexters.boolti&isi=6476589322',
APP_QR:
'https://boolti.page.link/?link=https://app.boolti.in/home/shows?apn=com.nexters.boolti&ibi=com.nexters.boolti&isi=6476589322',
BOOLTI_KAKAO_CHANNEL: 'http://pf.kakao.com/_pVxfxaG/chat',
};
2 changes: 2 additions & 0 deletions apps/admin/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const PATH = {
SHOW_ENTRANCE: '/show/:showId/enterance',
SHOW_SETTLEMENT: '/show/:showId/settlement',
SITE_POLICY: '/site-policy/:policyId',
GIFT_INTRO: '/gift/:giftId',
GIFT_REGISTER: '/gift/:giftId/register',
} as const;

export const HREF = {
Expand Down
34 changes: 34 additions & 0 deletions apps/admin/src/pages/GiftIntroPage/GiftIntroPage.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from '@emotion/styled';

const Container = styled.div`
padding: 0 24px;
height: 100vh;
padding-top: 184px;
text-align: center;
background: linear-gradient(#121215, #434753);
`;

const Button = styled.button`
cursor: pointer;
`;

const LetterImg = styled.img``;

const Description = styled.p`
${({ theme }) => theme.typo.b4}
color: ${({ theme }) => theme.palette.grey.g20};
white-space: pre-wrap;
text-align: center;
margin-bottom: 20px;
& > strong {
${({ theme }) => theme.typo.sh2};
}
`;

export default {
Container,
Button,
LetterImg,
Description,
};
47 changes: 47 additions & 0 deletions apps/admin/src/pages/GiftIntroPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { generatePath, useNavigate, useParams } from 'react-router-dom';
import Styled from './GiftIntroPage.styles';
import invitationImg from '~/assets/images/gift-invitation.png';
import unknownGiftImg from '~/assets/images/unknown-gift-invitation.png';
import { PATH } from '~/constants/routes';
import { useGift } from '@boolti/api';

const GiftIntroPage = () => {
const { giftId = '' } = useParams<{ giftId: string }>();
const navigate = useNavigate();

const { data, isLoading, error } = useGift(giftId);
return (
<Styled.Container>
{!isLoading && (
<>
<Styled.Description>
{error ? (
'유효하지 않은 선물이에요.'
) : (
<>
<strong>{data?.recipientName}</strong>님이 선물을 보냈어요.{'\n'}터치해서 확인해
보세요!
</>
)}
</Styled.Description>
{error ? (
<Styled.LetterImg src={unknownGiftImg} alt="" draggable={false} />
) : (
<Styled.Button
role="link"
onClick={() => {
if (giftId) {
navigate(generatePath(PATH.GIFT_REGISTER, { giftId }), { replace: true });
}
}}
>
<Styled.LetterImg src={invitationImg} alt="" draggable={false} />
</Styled.Button>
)}
</>
)}
</Styled.Container>
);
};

export default GiftIntroPage;
24 changes: 24 additions & 0 deletions apps/admin/src/pages/GiftRegisterPage/GiftRegisterPage.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import styled from '@emotion/styled';

const Container = styled.div`
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(#121215, #434753);
margin: 0 auto;
`;

const GiftWrapper = styled.section`
width: 100%;
max-width: ${({ theme }) => theme.breakpoint.mobile};
background: linear-gradient(#121215, #434753);
position: relative;
padding: 0 20px;
`;

export default {
Container,
GiftWrapper,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import styled from '@emotion/styled';

const Container = styled.section`
width: 100%;
max-width: ${({ theme }) => theme.breakpoint.mobile};
padding: 28px 20px;
background-color: ${({ theme }) => theme.palette.grey.w};
`;

const DescriptoinList = styled.ul`
list-style: none;
${({ theme }) => theme.typo.c1};
color: ${({ theme }) => theme.palette.grey.g60};
`;

const DescriptionListItem = styled.li`
${({ theme }) => theme.typo.c1};
color: ${({ theme }) => theme.palette.grey.g60};
position: relative;
&::before {
content: '・';
padding-right: 4px;
color: ${({ theme }) => theme.palette.grey.g60};
}
`;

const RejectButton = styled.button`
text-decoration: underline;
${({ theme }) => theme.typo.c1};
color: ${({ theme }) => theme.palette.grey.g90};
cursor: pointer;
margin-left: 15px;
`;

const RegisterGuideContainer = styled.div`
padding: 12px;
background-color: ${({ theme }) => theme.palette.grey.g00};
border-radius: 6px;
width: 100%;
margin-top: 28px;
margin-bottom: 20px;
`;

const RegisterGuideTitle = styled.h3`
${({ theme }) => theme.typo.sh0};
color: ${({ theme }) => theme.palette.grey.g90};
margin-bottom: 8px;
`;

const RegisterGuideDescription = styled.p`
${({ theme }) => theme.typo.c1};
color: ${({ theme }) => theme.palette.grey.g60};
`;

export default {
Container,
DescriptoinList,
DescriptionListItem,
RegisterGuideContainer,
RejectButton,
RegisterGuideTitle,
RegisterGuideDescription,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useConfirm } from '@boolti/ui';
import Styled from './GiftGuide.styles';
import { useGift, useRejectGift } from '@boolti/api';
import { useParams } from 'react-router-dom';
import { GiftStatus } from '@boolti/api/src/types/gift';

const GiftGuide = () => {
const confirm = useConfirm();
const { giftId = '' } = useParams<{ giftId: string }>();
const { data } = useGift(giftId);

const { status } = data ?? {};

const isRegistered = status === GiftStatus.REGISTERED;
const isCancelled = status === GiftStatus.CANCELLED;
const isRegistrable = status === GiftStatus.REGISTRABLE;
const isRejected = status === GiftStatus.REJECTED;

const rejectGiftMutation = useRejectGift(giftId);

const descriptionList = [
'선물 등록 후에는 거절 또는 결제 취소 및 환불이 불가합니다.',
'기한 내 미등록 시 선물 거절 처리되며 결제가 자동 취소됩니다.',
'선물 거절 시 보낸 분께 알림이 발송되며 결제가 자동 취소됩니다.',
];
return (
<Styled.Container>
<Styled.DescriptoinList>
{isRegistrable && (
<>
{descriptionList.map((item, index) => (
<Styled.DescriptionListItem key={index}>{item}</Styled.DescriptionListItem>
))}
<Styled.RejectButton
onClick={async () => {
const result = await confirm(
'선물 거절 시 보낸 분께 알림이 발송되며 결제가 자동 취소됩니다.거절하시겠습니까?',
{
cancel: '취소하기',
confirm: '거절하기',
},
);
if (result) {
await rejectGiftMutation.mutateAsync();
}
}}
>
선물 거절하기
</Styled.RejectButton>
</>
)}
{isRegistered && (
<Styled.DescriptionListItem>
선물 등록 후에는 거절 또는 결제 취소 및 환불이 불가합니다.
</Styled.DescriptionListItem>
)}
{(isCancelled || isRejected) && (
<Styled.DescriptionListItem>
취소되어 등록할 수 없는 선물입니다.
</Styled.DescriptionListItem>
)}
</Styled.DescriptoinList>

{isRegistrable && (
<Styled.RegisterGuideContainer>
<Styled.RegisterGuideTitle>선물 등록 방법</Styled.RegisterGuideTitle>
<Styled.RegisterGuideDescription>[불티앱이 있다면]</Styled.RegisterGuideDescription>
<Styled.RegisterGuideDescription>
선물 등록하기 버튼 클릭 {'>'} 선물 등록 안내 확인
</Styled.RegisterGuideDescription>
<Styled.RegisterGuideDescription style={{ marginTop: '20px' }}>
[불티앱이 없다면]
</Styled.RegisterGuideDescription>
<Styled.RegisterGuideDescription>
스토어에서 앱 다운로드 {'>'} 회원가입 및 로그인 {'>'} 해당 페이지 재접속 {'>'} 선물
등록하기 버튼 클릭{'>'} 선물 등록 안내 확인
</Styled.RegisterGuideDescription>
</Styled.RegisterGuideContainer>
)}
</Styled.Container>
);
};

export default GiftGuide;
Loading

0 comments on commit 0f34bfb

Please sign in to comment.