Skip to content

Commit

Permalink
[FE] Public 아이콘 색상 변경, Private 라우트 Forbidden 전환
Browse files Browse the repository at this point in the history
[FE] Public 아이콘 색상 변경, Private 라우트 Forbidden 전환
  • Loading branch information
Hain-tain authored Oct 25, 2024
2 parents 0474ffd + 5f18f5a commit 1e8958b
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 38 deletions.
5 changes: 4 additions & 1 deletion frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
<link rel="icon" href="./favicon.ico" />
<meta property="og:title" content="코드잽" />
<meta property="og:description" content="코드잽에서 자주 쓰는 코드를 템플릿으로 저장하고 빠르게 찾아보세요." />
<meta property="og:image" content="./codezap.png" />
<meta
property="og:image"
content="https://github.com/user-attachments/assets/969d44d2-efe6-4fca-a6bc-3856d62ac5f0"
/>
<meta property="og:url" content="https://code-zap.com" />
<!-- Google Tag Manager -->
<script>
Expand Down
13 changes: 4 additions & 9 deletions frontend/src/api/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,11 @@ export const getTemplateExplore = async ({
return data;
};

export const getTemplate = async ({ id }: TemplateRequest) => {
const response = await customFetch<Template>({
url: `${TEMPLATE_API_URL}/${id}`,
});

if ('sourceCodes' in response) {
return response;
}
export const getTemplate = async ({ id }: TemplateRequest): Promise<Template> => {
const response = await apiClient.get(`${END_POINTS.TEMPLATES_EXPLORE}/${id}`);
const data = response.json();

throw new Error(response.detail);
return data;
};

export const postTemplate = async (newTemplate: TemplateUploadRequest): Promise<Response> =>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/images/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { default as CheckCircleIcon } from './checkCircle.svg';
export { default as LikeIcon } from './like';
export { default as PrivateIcon } from './private.svg';
export { default as PublicIcon } from './public.svg';
export { default as ShareIcon } from './share.svg';

// Logo
export { default as CodeZapLogo } from './codezapLogo.svg';
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/assets/images/share.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions frontend/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { useEffect, useRef } from 'react';
import { Outlet, useLocation } from 'react-router-dom';

import { ApiError } from '@/api/Error/ApiError';
import { HTTP_STATUS } from '@/api/Error/statusCode';
import { Footer, Header, ScrollTopButton } from '@/components';
import { useHeaderHeight } from '@/hooks/useHeaderHeight';
import { NotFoundPage } from '@/pages';
import { ForbiddenPage, NotFoundPage } from '@/pages';

import * as S from './Layout.style';

Expand All @@ -27,7 +29,17 @@ const Layout = () => {
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
fallback={(fallbackProps) => <NotFoundPage {...fallbackProps} />}
fallback={(fallbackProps) => {
const error = fallbackProps.error;

if (error instanceof ApiError) {
if (error.statusCode === HTTP_STATUS.FORBIDDEN) {
return <ForbiddenPage resetError={fallbackProps.resetError} error={error} />;
}
}

return <NotFoundPage {...fallbackProps} />;
}}
onReset={reset}
key={location.pathname}
>
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/components/Modal/Modal.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export const Base = styled.div`
@media (max-width: 47.9375rem) {
align-items: flex-end;
height: 100%;
min-height: 100vh;
@supports (-webkit-touch-callout: none) {
min-height: -webkit-fill-available;
}
}
`;

Expand Down Expand Up @@ -54,6 +60,13 @@ export const FooterContainer = styled.div`
justify-content: space-between;
padding: 1rem;
@media (max-width: 47.9375rem) {
position: sticky;
z-index: 1;
bottom: 0;
background-color: white;
}
`;

export const ModalContainer = styled.div<{ size: ModalSize }>`
Expand All @@ -76,14 +89,19 @@ export const ModalContainer = styled.div<{ size: ModalSize }>`
}
@media (max-width: 47.9375rem) {
position: sticky;
bottom: 0;
left: 0;
overflow-y: auto;
width: 100%;
max-height: calc(100% - 2rem);
margin-top: auto;
border-radius: 1.5rem 1.5rem 0 0;
transition: max-height 0.3s ease-out;
}
`;

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/SourceCode/SourceCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const SourceCode = ({ mode = 'detailView', language, content, handleContentChang
css={{
width: '100%',
fontSize: `${mode === 'detailView' && windowWidth <= 400 ? '0.65rem' : '1rem'}`,
minHeight: '160px',
minHeight: `${mode === 'thumbnailView' ? '160px' : undefined}`,
backgroundColor: `rgba(0, 0, 0, 0.1)`,
'.cm-scroller': {
padding: '1rem 0',
Expand Down
15 changes: 11 additions & 4 deletions frontend/src/pages/ForbiddenPage/ForbiddenPage.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { captureException } from '@sentry/react';

import { ApiError } from '@/api/Error/ApiError';
import { TigerLogo } from '@/assets/images';
import { Button, Flex, Heading, Text } from '@/components';
import { Button, Flex, Text } from '@/components';
import { useCustomNavigate } from '@/hooks';
import { useTrackPageViewed } from '@/service/amplitude';
import { theme } from '@/style/theme';

interface props {
resetError?: () => void;
error?: ApiError;
}

const ForbiddenPage = ({ resetError }: props) => {
const ForbiddenPage = ({ resetError, error }: props) => {
const navigate = useCustomNavigate();

captureException(error);
useTrackPageViewed({ eventName: '[Viewed] 403 Forbidden 페이지' });

return (
<Flex direction='column' gap='3rem' margin='2rem 0 0 0' justify='center' align='center'>
<TigerLogo aria-label='호랑이 로고' />
<Heading.XLarge color={theme.color.light.primary_500}>403 ERROR</Heading.XLarge>
<Flex direction='column' gap='2rem' align='center'>
<Text.XLarge color={theme.color.light.primary_500} weight='bold'>
죄송합니다. 해당 페이지에 접근할 수 있는 권한이 없습니다.
해당 페이지에 접근할 수 있는 권한이 없습니다.
</Text.XLarge>
<Flex direction='column' justify='center' align='center' gap='1rem'>
<Text.Medium color={theme.color.light.secondary_600} weight='bold'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@ export const CancelButton = styled(Button)`
`;

export const ButtonGroup = styled.div`
position: sticky;
bottom: 0;
display: flex;
justify-content: space-between;
gap: 0.5rem;
justify-content: flex-end;
width: 100%;
padding-top: 0.5rem;
padding-bottom: 1rem;
`;

export const UnderlineInputWrapper = styled.div`
Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/TemplateEditPage/TemplateEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const TemplateEditPage = ({ template, toggleEditButton }: Props) => {
<PrivateIcon key={TEMPLATE_VISIBILITY[1]} width={ICON_SIZE.MEDIUM_SMALL} />,
<PublicIcon key={TEMPLATE_VISIBILITY[0]} width={ICON_SIZE.MEDIUM_SMALL} />,
]}
optionSliderColor={[undefined, theme.color.light.triadic_primary_800]}
selectedOption={visibility}
switchOption={setVisibility}
/>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/TemplatePage/TemplatePage.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export const DeleteButton = styled(Button)`
padding: 0;
`;

export const ShareButton = styled(Button)`
padding: 0;
`;

export const PrivateWrapper = styled.div`
flex-shrink: 0;
`;
51 changes: 31 additions & 20 deletions frontend/src/pages/TemplatePage/TemplatePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTheme } from '@emotion/react';
import { Link, useParams } from 'react-router-dom';

import { ClockIcon, PrivateIcon } from '@/assets/images';
import { ClockIcon, PrivateIcon, ShareIcon } from '@/assets/images';
import {
Button,
Flex,
Expand All @@ -18,10 +18,11 @@ import {
import AuthorInfo from '@/components/AuthorInfo/AuthorInfo';
import { useToggle } from '@/hooks';
import { useAuth } from '@/hooks/authentication';
import { useToast } from '@/hooks/useToast';
import { TemplateEditPage } from '@/pages';
import { END_POINTS } from '@/routes';
import { useTrackPageViewed } from '@/service/amplitude';
import { trackLikeButton } from '@/service/amplitude/track';
import { trackClickTemplateShare, trackLikeButton } from '@/service/amplitude/track';
import { VISIBILITY_PRIVATE } from '@/service/constants';
import { ICON_SIZE } from '@/style/styleConstants';
import { formatRelativeTime } from '@/utils';
Expand All @@ -33,6 +34,9 @@ const TemplatePage = () => {
const { id } = useParams<{ id: string }>();

useTrackPageViewed({ eventName: '[Viewed] 템플릿 조회 페이지', eventProps: { templateId: id } });

const { infoAlert } = useToast();

const theme = useTheme();
const [isNonmemberAlerterOpen, toggleNonmemberAlerter] = useToggle();

Expand Down Expand Up @@ -73,6 +77,14 @@ const TemplatePage = () => {
trackLikeButton({ isLiked, likesCount, templateId: id as string });
};

const handleShareButtonClick = () => {
const currentUrl = window.location.href;

navigator.clipboard.writeText(currentUrl);
infoAlert('붙여넣기로 링크를 공유해보세요');
trackClickTemplateShare();
};

if (!template) {
return <LoadingFallback />;
}
Expand All @@ -96,25 +108,24 @@ const TemplatePage = () => {
}}
>
<Flex direction='column' gap='0.75rem' width='100%'>
<Flex justify='space-between'>
<Flex justify='space-between' align='center'>
<Text.Medium color={theme.color.dark.secondary_500}>{template.category?.name}</Text.Medium>
{template.member.name === name && (
<Flex width='5.5rem' justify='space-around'>
<S.EditButton
size='small'
variant='text'
onClick={() => {
handleEditButtonClick();
}}
style={{ width: '2rem' }}
>
<Text.Small color={theme.color.light.secondary_700}>편집</Text.Small>
</S.EditButton>
<S.DeleteButton size='small' variant='text' onClick={toggleModal} style={{ width: '2rem' }}>
<Text.Small color={theme.color.light.secondary_700}>삭제</Text.Small>
</S.DeleteButton>
</Flex>
)}
<Flex gap='0.5rem' justify='flex-end' align='flex-end'>
{template.member.name === name && (
<>
<S.EditButton size='small' variant='text' onClick={handleEditButtonClick}>
<Text.Medium color={theme.color.light.secondary_700}>편집</Text.Medium>
</S.EditButton>
<S.DeleteButton size='small' variant='text' onClick={toggleModal}>
<Text.Medium color={theme.color.light.secondary_700}>삭제</Text.Medium>
</S.DeleteButton>
</>
)}
<S.ShareButton size='small' variant='text' onClick={handleShareButtonClick}>
<Text.Medium color={theme.color.light.secondary_700}>공유</Text.Medium>
<ShareIcon width={ICON_SIZE.SMALL} color={theme.color.light.secondary_700} />
</S.ShareButton>
</Flex>
</Flex>

<Flex align='center' justify='space-between' gap='1rem'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const TemplateUploadPage = () => {
<PrivateIcon key={TEMPLATE_VISIBILITY[1]} width={ICON_SIZE.MEDIUM_SMALL} />,
<PublicIcon key={TEMPLATE_VISIBILITY[0]} width={ICON_SIZE.MEDIUM_SMALL} />,
]}
optionSliderColor={[undefined, theme.color.light.triadic_primary_800]}
selectedOption={visibility}
switchOption={setVisibility}
/>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/queries/templates/useTemplateQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export const useTemplateQuery = (id: number): UseQueryResult<Template, Error> =>
useQuery<Template, Error>({
queryKey: [QUERY_KEY.TEMPLATE, id],
queryFn: () => getTemplate({ id }),
throwOnError: true,
retry: false,
});
4 changes: 4 additions & 0 deletions frontend/src/service/amplitude/track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export const trackClickCopyClipBoard = () => {
amplitudeService.customTrack('[Click] 클립보드 복사 버튼');
};

export const trackClickTemplateShare = () => {
amplitudeService.customTrack('[Click] 공유 버튼');
};

interface PagingButtonData {
page: number;
totalPages: number;
Expand Down

0 comments on commit 1e8958b

Please sign in to comment.