Skip to content

Commit

Permalink
Merge pull request #1 from Nexters/feature/add_error_page
Browse files Browse the repository at this point in the history
Feature/add error page
  • Loading branch information
ooooorobo committed Aug 12, 2023
2 parents f14f240 + c21695e commit d5a72a8
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 98 deletions.
8 changes: 6 additions & 2 deletions src/agent/ApiClient.ts
@@ -1,12 +1,16 @@
import axios from 'axios';
import axios, { AxiosResponse } from 'axios';

export const initApiClient = () => {
axios.defaults.baseURL = process.env.API_BASE_URL;
};

export const apiClient = () => {
return {
get: <Params>(url: string, params?: Params) => axios.get(url, { params }),
get: <Params, T>(
url: string,
params?: Params,
): Promise<AxiosResponse<T> & { statusCode: number }> =>
axios.get(url, { params }),
post: <Body>(url: string, data: Body) => axios.post(url, data),
put: <Body>(url: string, data: Body) => axios.put(url, data),
delete: (url: string) => axios.delete(url),
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icon/hourglass.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icon/warning.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 27 additions & 25 deletions src/client/renderer.tsx
Expand Up @@ -2,22 +2,15 @@ import React from 'react';
import { renderToString } from 'react-dom/server';
import { Store } from 'redux';
import { App } from './view/App';
import { NotFoundPage } from './view/pages/404';
import { ExpiredPage } from './view/pages/expired';

export const renderer = ({
assetPath,
store,
}: {
assetPath: string;
store: Store;
}) => {
try {
const content = renderToString(<App store={store} assetPath={assetPath} />);
return `
const defaultHtml = (assetPath: string, content: string) => `
<!DOCTYPE html>
<html lang="ko">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<meta name="description" content="μž‘μ€ 행동이 λͺ¨μ—¬μ„œ 큰 λͺ©ν‘œλ₯Ό μ΄λ£Ήλ‹ˆλ‹€">
<meta name="description" content="뢀담감은 적게, λͺ©ν‘œλŠ” 크게">
<title>λ°˜λ‹€λΌνŠΈ</title>
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/static/pretendard-dynamic-subset.css" />
<link rel="stylesheet" href="${assetPath}/styles.css" />
Expand All @@ -28,20 +21,29 @@ export const renderer = ({
<body>
<div id="root">${content}</div>
</body>
</html>
`;
</html>`;

export const renderer = ({
assetPath,
store,
}: {
assetPath: string;
store: Store;
}) => {
try {
const content = renderToString(<App store={store} assetPath={assetPath} />);
return defaultHtml(assetPath, content);
} catch (e) {
console.error(e);
return `<html lang="ko">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>λ°˜λ‹€λΌνŠΈ</title>
<link rel="stylesheet" as="style" crossOrigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/static/pretendard.css"/>
</head>
<body>
<div id="root">μ—λŸ¬κ°€ λ°œμƒν–ˆμ–΄μš”!!</div>
</body>
</html>`;
return renderNotFound(assetPath);
}
};

export const renderNotFound = (assetPath: string) => {
const content = renderToString(<NotFoundPage assetPath={assetPath} />);
return defaultHtml(assetPath, content);
};

export const renderExpired = (assetPath: string) => {
const content = renderToString(<ExpiredPage assetPath={assetPath} />);
return defaultHtml(assetPath, content);
};
70 changes: 4 additions & 66 deletions src/client/view/App.tsx
Expand Up @@ -3,9 +3,7 @@ import { initApiClient } from '../../agent/ApiClient';
import { BandalartSharePage } from './pages/share';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { css, cx } from '@linaria/core';
import { theme } from './theme';
import { EnvContextProvider } from './components/context/EnvContext';
import { DefaultContainer } from './components/_common/DefaultContainer';

initApiClient();

Expand All @@ -17,69 +15,9 @@ type AppProps = {
export const App = ({ store, assetPath }: AppProps) => {
return (
<Provider store={store}>
<div className={cx(globalStyle, 'theme-light')}>
<EnvContextProvider assetPath={assetPath}>
<BandalartSharePage />
</EnvContextProvider>
</div>
<DefaultContainer assetPath={assetPath}>
<BandalartSharePage />
</DefaultContainer>
</Provider>
);
};

const globalStyle = css`
font-family:
'Pretendard Variable',
Pretendard,
-apple-system,
BlinkMacSystemFont,
system-ui,
Roboto,
'Helvetica Neue',
'Segoe UI',
'Apple SD Gothic Neo',
'Noto Sans KR',
'Malgun Gothic',
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
sans-serif;
background-color: var(--color-50);
${theme};
:global(html) {
html {
box-sizing: border-box;
}
body {
margin: 0;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin: 0;
}
ul {
list-style: none;
padding: 0;
margin: 0;
margin-block: 0;
margin-inline: 0;
padding-inline-start: 0;
}
}
`;
15 changes: 15 additions & 0 deletions src/client/view/components/_common/DefaultContainer.tsx
@@ -0,0 +1,15 @@
import { cx } from '@linaria/core';
import { globalStyle } from '../../styles/globalStyles';
import React, { ReactNode } from 'react';
import { EnvContextProvider } from '../context/EnvContext';

type Props = {
children: ReactNode;
assetPath: string;
};

export const DefaultContainer = ({ assetPath, children }: Props) => (
<div className={cx(globalStyle, 'theme-light')}>
<EnvContextProvider assetPath={assetPath}>{children}</EnvContextProvider>
</div>
);
13 changes: 10 additions & 3 deletions src/client/view/components/_common/Icon.tsx
Expand Up @@ -3,20 +3,27 @@ import { EnvContext } from '../context/EnvContext';
import { LinariaClassName } from '@linaria/core';

export type IconProps = {
className: LinariaClassName;
className?: LinariaClassName;
alt: string;
iconName: IconName;
size?: number;
};

type IconName = 'check';
const IconNameList = ['check', 'warning', 'hourglass'] as const;

export const Icon = ({ className, alt, iconName }: IconProps) => {
export type IconName = (typeof IconNameList)[number];

export const Icon = ({ className, alt, iconName, size }: IconProps) => {
const { assetPath } = useContext(EnvContext);
return (
<img
className={className}
src={`${assetPath}/icon/${iconName}.svg`}
alt={alt}
style={{
width: size ? `${size}px` : undefined,
height: size ? `${size}px` : undefined,
}}
/>
);
};
57 changes: 57 additions & 0 deletions src/client/view/components/template/WarningTemplate.tsx
@@ -0,0 +1,57 @@
import React from 'react';
import { Icon, IconName } from '../_common/Icon';
import { css } from '@linaria/core';

type Props = {
iconName: IconName;
title: string;
description?: string;
iconSize?: number;
};

export const WarningTemplate = ({
title,
description,
iconName,
iconSize,
}: Props) => (
<div className={container}>
<div className={iconWrapper}>
<Icon iconName={iconName} alt={'icon'} size={iconSize} />
</div>
<h2 className={titleStyle}>{title}</h2>
<p className={descriptionStyle}>{description}</p>
</div>
);

const container = css`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;

const iconWrapper = css`
height: 80px;
width: 80px;
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
`;

const titleStyle = css`
color: var(--color-600);
font-size: 20px;
font-weight: 600;
letter-spacing: -0.4px;
text-align: center;
`;

const descriptionStyle = css`
margin-top: 6px;
color: var(--color-400);
font-size: 12px;
font-weight: 500;
letter-spacing: -0.24px;
`;
24 changes: 24 additions & 0 deletions src/client/view/pages/404/index.tsx
@@ -0,0 +1,24 @@
import React from 'react';
import { WarningTemplate } from '../../components/template/WarningTemplate';
import { DefaultContainer } from '../../components/_common/DefaultContainer';
import { css } from '@linaria/core';

export const NotFoundPage = ({ assetPath }: { assetPath: string }) => (
<DefaultContainer assetPath={assetPath}>
<div className={container}>
<WarningTemplate
iconName={'warning'}
title={'μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νŽ˜μ΄μ§€μ—μš”'}
iconSize={53}
/>
</div>
</DefaultContainer>
);

const container = css`
height: 100dvh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
25 changes: 25 additions & 0 deletions src/client/view/pages/expired/index.tsx
@@ -0,0 +1,25 @@
import React from 'react';
import { WarningTemplate } from '../../components/template/WarningTemplate';
import { DefaultContainer } from '../../components/_common/DefaultContainer';
import { css } from '@linaria/core';

export const ExpiredPage = ({ assetPath }: { assetPath: string }) => (
<DefaultContainer assetPath={assetPath}>
<div className={container}>
<WarningTemplate
iconName={'hourglass'}
title={'μœ νš¨κΈ°κ°„μ΄ 만료된 νŽ˜μ΄μ§€μ—μš”'}
description={'λ§ν¬λŠ” μƒμ„±λœ 이후 7일 λ™μ•ˆ λ³Ό 수 μžˆμ–΄μš”'}
iconSize={46}
/>
</div>
</DefaultContainer>
);

const container = css`
height: 100dvh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;

0 comments on commit d5a72a8

Please sign in to comment.