Skip to content

Commit bd39c1f

Browse files
committed
Merge branch 'develop' of https://github.com/Nexters/boolti-web into feature/profile
2 parents 617bcf0 + 25e4984 commit bd39c1f

File tree

8 files changed

+153
-17
lines changed

8 files changed

+153
-17
lines changed

apps/admin/src/components/ShowDetailLayout/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useInView } from 'react-intersection-observer';
1313
import { useMatch, useNavigate, useParams } from 'react-router-dom';
1414
import { Tooltip } from 'react-tooltip';
1515

16+
import { EXTERNAL_URL } from '~/constants/external';
1617
import { HREF, PATH } from '~/constants/routes';
1718

1819
import Header from '../Header/index.tsx';
@@ -241,6 +242,14 @@ const ShowDetailLayout = ({ children }: ShowDetailLayoutProps) => {
241242
onClickDeleteButton={() => {
242243
push('deleteShow');
243244
}}
245+
onClickShowManagerCopyLink={async () => {
246+
const text = EXTERNAL_URL.SHOW_MANAGER_INFO(showId);
247+
await navigator.clipboard.writeText(text);
248+
}}
249+
onClickShowTicketCopyLink={async () => {
250+
const text = EXTERNAL_URL.SHOW_TICKET_PREVIEW(showId);
251+
await navigator.clipboard.writeText(text);
252+
}}
244253
/>
245254
),
246255
},

apps/admin/src/components/ShowSettingDialogContent/ShowSettingDialogContent.styles.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const Container = styled.div`
1212
}
1313
`;
1414

15+
const RowSection = styled.div`
16+
display: flex;
17+
`;
18+
1519
const Section = styled.div`
1620
display: flex;
1721
flex-direction: column;
@@ -83,8 +87,37 @@ const DeleteButtonContainer = styled.div`
8387
margin-top: 16px;
8488
`;
8589

90+
const CopyLickContainer = styled.div`
91+
display: flex;
92+
justify-content: space-between;
93+
cursor: pointer;
94+
padding: 12px 0px;
95+
&:hover {
96+
border-radius: 4px;
97+
background-color: ${({ theme }) => theme.palette.grey.g00};
98+
}
99+
`;
100+
101+
const CopyLinkText = styled.span`
102+
${({ theme }) => theme.typo.b3};
103+
color: ${({ theme }) => theme.palette.grey.g90};
104+
margin-left: 12px;
105+
`;
106+
107+
const CopyLinkSubText = styled.span`
108+
${({ theme }) => theme.typo.b1};
109+
color: ${({ theme }) => theme.palette.grey.g40};
110+
`;
111+
112+
const CopyCompleteToast = styled.span`
113+
color: ${({ theme }) => theme.palette.status.link};
114+
${({ theme }) => theme.typo.b3};
115+
margin-left: 8px;
116+
`;
117+
86118
export default {
87119
Container,
120+
RowSection,
88121
Section,
89122
SectionHeader,
90123
SectionTitle,
@@ -97,4 +130,8 @@ export default {
97130
HostDefaultProfileImage,
98131
HostName,
99132
DeleteButtonContainer,
133+
CopyLickContainer,
134+
CopyLinkText,
135+
CopyLinkSubText,
136+
CopyCompleteToast,
100137
};

apps/admin/src/components/ShowSettingDialogContent/index.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@ import Styled from './ShowSettingDialogContent.styles';
22
import { useHostList, useInvitationTicketList, useSalesTicketList } from '@boolti/api';
33
import HostInputForm from './components/HostInputForm';
44
import { Button } from '@boolti/ui';
5-
import { ChevronRightIcon } from '@boolti/icon';
5+
import { ChevronRightIcon, SettingIcon, TicketIcon } from '@boolti/icon';
66
import { myHostInfoAtom } from '../ShowDetailLayout';
77
import { useAtom } from 'jotai';
88
import { HostType } from '@boolti/api/src/types/host';
99
import { useBodyScrollLock } from '~/hooks/useBodyScrollLock';
10+
import { useClipboardCopy } from '~/hooks/useClipboardCopy';
1011

1112
interface ShowSettingDialogContentProps {
1213
showId: number;
1314
onClickHostList: () => void;
1415
onClickDeleteButton: () => void;
16+
onClickShowManagerCopyLink: () => void;
17+
onClickShowTicketCopyLink: () => void;
1518
}
1619

1720
const ShowSettingDialogContent = ({
1821
showId,
1922
onClickHostList,
2023
onClickDeleteButton,
24+
onClickShowManagerCopyLink,
25+
onClickShowTicketCopyLink,
2126
}: ShowSettingDialogContentProps) => {
2227
const { data: hosts } = useHostList(showId);
2328
const { data: salesTicketList } = useSalesTicketList(showId);
@@ -35,6 +40,12 @@ const ShowSettingDialogContent = ({
3540

3641
const [myHostInfo] = useAtom(myHostInfoAtom);
3742

43+
const { isCopied: showManagerCopied, handleCopy: handleShowManagerCopyLink } = useClipboardCopy(
44+
onClickShowManagerCopyLink,
45+
);
46+
const { isCopied: showTicketCopied, handleCopy: handleShowTicketCopyLink } =
47+
useClipboardCopy(onClickShowTicketCopyLink);
48+
3849
useBodyScrollLock();
3950

4051
return (
@@ -90,6 +101,34 @@ const ShowSettingDialogContent = ({
90101
</Styled.Section>
91102
</>
92103
)}
104+
{(myHostInfo?.type === HostType.MAIN || myHostInfo?.type === HostType.MANAGER) && (
105+
<>
106+
<Styled.SectionDivider />
107+
<Styled.CopyLickContainer onClick={handleShowManagerCopyLink}>
108+
<Styled.RowSection>
109+
<SettingIcon />
110+
<Styled.RowSection>
111+
<Styled.CopyLinkText> 공연 관리 링크 복사</Styled.CopyLinkText>
112+
{showManagerCopied && (
113+
<Styled.CopyCompleteToast>복사 완료!</Styled.CopyCompleteToast>
114+
)}
115+
</Styled.RowSection>
116+
</Styled.RowSection>
117+
<Styled.CopyLinkSubText>권한이 있는 그룹원만 접근 가능</Styled.CopyLinkSubText>
118+
</Styled.CopyLickContainer>
119+
<Styled.CopyLickContainer onClick={handleShowTicketCopyLink}>
120+
<Styled.RowSection>
121+
<TicketIcon />
122+
<Styled.RowSection>
123+
<Styled.CopyLinkText> 공연 예매 링크 복사</Styled.CopyLinkText>
124+
{showTicketCopied && (
125+
<Styled.CopyCompleteToast>복사 완료!</Styled.CopyCompleteToast>
126+
)}
127+
</Styled.RowSection>
128+
</Styled.RowSection>
129+
</Styled.CopyLickContainer>
130+
</>
131+
)}
93132
</Styled.Container>
94133
);
95134
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const EXTERNAL_DOMAIN = {
2+
SHOW_MANAGER: 'https://boolti.in',
3+
SHOW_TICKET_PREVIEW: 'https://preview.boolti.in',
4+
} as const;
5+
6+
export const EXTERNAL_URL = {
7+
SHOW_MANAGER_INFO: (showId: string | number) =>
8+
`${EXTERNAL_DOMAIN.SHOW_MANAGER}/show/${showId}/info`,
9+
10+
SHOW_TICKET_PREVIEW: (showId: string | number) =>
11+
`${EXTERNAL_DOMAIN.SHOW_TICKET_PREVIEW}/show/${showId}`,
12+
} as const;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const BASE_SCHEME = 'boolti://';
2+
3+
interface SchemeOptions {
4+
path: string;
5+
query?: Record<string, string | number | boolean | undefined>;
6+
}
7+
8+
export function createAppScheme({ path, query }: SchemeOptions) {
9+
const queryString = query
10+
? '?' +
11+
Object.entries(query)
12+
.filter(([, v]) => v !== undefined)
13+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
14+
.join('&')
15+
: '';
16+
return `${BASE_SCHEME}${path}${queryString}`;
17+
}
18+
19+
export const SCHEMES = {
20+
선물_등록: (giftId: string) => createAppScheme({ path: `gift/${giftId}` }),
21+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const COPY_TOAST_DURATION = 2000;
4+
5+
export const useClipboardCopy = (copyFn: () => void, duration: number = COPY_TOAST_DURATION) => {
6+
const [isCopied, setIsCopied] = useState(false);
7+
8+
useEffect(() => {
9+
if (isCopied) {
10+
const timerId = setTimeout(() => {
11+
setIsCopied(false);
12+
}, duration);
13+
return () => clearTimeout(timerId);
14+
}
15+
}, [isCopied, duration]);
16+
17+
const handleCopy = () => {
18+
copyFn();
19+
setIsCopied(true);
20+
};
21+
22+
return { isCopied, handleCopy };
23+
};

apps/admin/src/pages/GiftRegisterPage/components/GiftInformation/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { useDeviceWidth } from '~/hooks/useDeviceWidth';
99
import { useTheme } from '@emotion/react';
1010
import { LINK } from '~/constants/link';
1111
import { navigateToAppScheme } from '~/utils/app';
12+
import { SCHEMES } from '~/constants/schemes';
13+
import { openStoreLink } from '~/utils/link';
1214

1315
const GiftInformation = () => {
1416
const { giftId = '' } = useParams<{ giftId: string }>();
@@ -53,7 +55,11 @@ const GiftInformation = () => {
5355
return;
5456
}
5557

56-
navigateToAppScheme(`boolti://gift/${giftId}`);
58+
const success = await navigateToAppScheme(SCHEMES.선물_등록(giftId));
59+
60+
if (!success) {
61+
openStoreLink();
62+
}
5763
};
5864

5965
return (

apps/admin/src/utils/app.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
1-
import { openStoreLink } from './link';
2-
31
export const navigateToAppScheme = async (appScheme: string) => {
42
let timerId: ReturnType<typeof setTimeout>;
53

64
return new Promise((resolve) => {
7-
const startTime = Date.now();
8-
95
const handleVisibilityChange = () => {
106
if (document.hidden) {
117
clearTimeout(timerId);
128
document.removeEventListener('visibilitychange', handleVisibilityChange);
13-
149
resolve(true);
1510
}
1611
};
1712

13+
document.addEventListener('visibilitychange', handleVisibilityChange);
14+
1815
window.addEventListener(
1916
'blur',
2017
() => {
@@ -30,15 +27,7 @@ export const navigateToAppScheme = async (appScheme: string) => {
3027
timerId = setTimeout(() => {
3128
document.removeEventListener('visibilitychange', handleVisibilityChange);
3229

33-
const elapsedTime = Date.now() - startTime;
34-
35-
if (elapsedTime < 1_500) {
36-
resolve(false);
37-
38-
openStoreLink();
39-
} else {
40-
resolve(false);
41-
}
42-
}, 2_000);
30+
resolve(false);
31+
}, 1_000);
4332
});
4433
};

0 commit comments

Comments
 (0)