Skip to content

Commit

Permalink
DNA 페이지 이미지 로직 변경 (#451)
Browse files Browse the repository at this point in the history
* refactor: 시그니처 정리

* chore: satori version update

* refactor: 불필요 로직 삭제

* refactor: 불필요 로직 삭제

* feat: image response api route 생성

* chore: api extension 설정

* feat: api route 이용해서 이미지 사용

* feat: dna 이미지 다운로드 모달 lazy 임포트

* refactor: 미사용 라이브러리 및 유틸 삭제

* chore: 주석 추가
  • Loading branch information
hyesungoh authored Nov 2, 2023
1 parent 1077694 commit 37e5b81
Show file tree
Hide file tree
Showing 30 changed files with 189 additions and 812 deletions.
303 changes: 0 additions & 303 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
사용자 지표를 수집 및 분석하여 이탈율을 줄이고, 사용자 경험을 개선했어요.
* Sentry 부착으로 에러 로그를 수집하고, 디버깅에 유리한 환경을 구성했어요
* SSR 을 활용한 SEO 개인화를 진행했어요.
* Satori를 사용한 이미지 동적 생성으로 사용자가 이미지를 다운받을 수 있게 만들었어요.
* API Route를 사용해 이미지를 동적으로 생성해 사용자가 이미지를 다운받을 수 있게 만들었어요.
* Stylelint를 사용해 스타일링 컨벤션을 확립했어요.
* Vitest를 활용해 TDD로 개발했어요.

Expand Down
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const nextConfig = {
appDir: false,
forceSwcTransforms: true,
},
pageExtensions: ['page.tsx', 'page.ts'],
pageExtensions: ['page.tsx', 'page.ts', 'api.tsx', 'api.ts'],
swcMinify: true,
compiler: {
emotion: true,
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"@egjs/flicking-plugins": "^4.7.0",
"@egjs/react-flicking": "^4.10.9",
"@emotion/react": "^11.10.6",
"@resvg/resvg-js": "^2.4.1",
"@sentry/nextjs": "^7.49.0",
"@tanstack/react-query": "^4.29.3",
"@vercel/analytics": "^1.0.1",
Expand All @@ -58,7 +57,6 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hotjar": "^6.1.0",
"satori": "^0.10.1",
"sharp": "^0.32.1"
},
"packageManager": "yarn@3.5.0",
Expand Down
File renamed without changes.
112 changes: 112 additions & 0 deletions src/pages/api/dna-image.api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* eslint-disable unicorn/filename-case */
import { ImageResponse, type NextRequest } from 'next/server';

import { type Group } from '~/utils/resultLogic';

type ImageOptions = ConstructorParameters<typeof ImageResponse>[1];

export const config = {
runtime: 'edge',
};

export default async function handler(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const group = searchParams.get('group');
const nickname = searchParams.get('nickname');
const position = searchParams.get('position');

const notoSansScFont700 = await fetchFont('Noto+Sans+KR', 700);
if (!notoSansScFont700) {
throw new Error('Failed to fetch font');
}

// TODO: 이미지 크기 및 위치 조절
const imageOptions: ImageOptions = {
width: 375,
height: 666,
fonts: [
{
name: 'Noto Sans KR',
data: notoSansScFont700,
weight: 700,
style: 'normal',
},
],
};

return new ImageResponse(
(
<div
style={{
position: 'relative',
display: 'flex',
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
style={{
width: '375px',
height: '666px',
}}
src={HOISTING_IMAGE_BY_GROUP[group as Group]}
alt={'dna_' + group}
width={375}
height={666}
/>
<span
style={{
position: 'absolute',
top: '112px',
fontWeight: 700,
fontSize: '20px',
color: '#fff',
left: '70px',
textShadow: '1px 1px 2px #849BDA30',
}}
>
{nickname}
</span>
<span
style={{
position: 'absolute',
top: '407px',
left: '71px',
color: '#17171B',
fontWeight: 700,
fontSize: '16.8px',
}}
>
{position}
</span>
</div>
),
imageOptions,
);
} catch (error: unknown) {
return new Response('Failed to generate image', { status: 500 });
}
}

async function fetchFont(fontFamily = 'Noto+Sans+KR', fontWeight = 700): Promise<ArrayBuffer | null> {
const fontUrl = `https://fonts.googleapis.com/css2?family=${fontFamily}:wght@${fontWeight}`;

const css = await (await fetch(fontUrl)).text();

const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);

if (!resource) return null;

const res = await fetch(resource[1]);

return res.arrayBuffer();
}

const HOISTING_IMAGE_BY_GROUP: Record<Group, string> = {
A: `https://github.com/depromeet/na-lab-client/assets/49177223/0b224b08-3858-4305-8323-1a6082dbb4f7`,
B: `https://github.com/depromeet/na-lab-client/assets/49177223/fec4951a-270f-40e8-8520-43eecb89416f`,
C: `https://github.com/depromeet/na-lab-client/assets/49177223/faefd0ee-7048-4578-8ec5-b70713e6efd9`,
D: `https://github.com/depromeet/na-lab-client/assets/49177223/4dbecfeb-5492-4c6b-b4a7-cd933cd5621e`,
E: `https://github.com/depromeet/na-lab-client/assets/49177223/1eb9e7e4-801d-475a-8037-aa35a2776441`,
F: `https://github.com/depromeet/na-lab-client/assets/49177223/8667e31c-9722-490b-9def-3b952d115275`,
};
62 changes: 62 additions & 0 deletions src/pages/dna/DNAImageDownloadModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-disable unicorn/filename-case */
import { css } from '@emotion/react';
import { m } from 'framer-motion';

import Modal from '~/components/modal/Modal';
import { HEAD_2_BOLD } from '~/styles/typo';

const DNAImageDownloadModal = ({
imageSrc,
onClose,
isShowing,
}: {
imageSrc: string;
onClose: () => void;
isShowing: boolean;
}) => {
return (
<Modal isShowing={isShowing}>
<Modal.Header onBackClick={onClose} overrideCss={imageDownloadModalHeaderCss} />
<m.div
css={imageDownloadModalCss}
variants={{
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
}}
initial="initial"
animate="animate"
exit="exit"
>
<h1>꾹 눌러서 이미지를 저장하세요</h1>

<img src={imageSrc} alt="dna" />
</m.div>
</Modal>
);
};

export default DNAImageDownloadModal;

const imageDownloadModalHeaderCss = css`
background-color: transparent;
border-bottom: none;
`;

const imageDownloadModalCss = css`
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
h1 {
${HEAD_2_BOLD};
user-select: none;
}
img {
user-select: none;
width: 80%;
}
`;
77 changes: 8 additions & 69 deletions src/pages/dna/LoadedDna.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
/* eslint-disable @next/next/no-img-element */
import { type FC, useState } from 'react';
import dynamic from 'next/dynamic';
import Image from 'next/image';
import { css, type Theme } from '@emotion/react';
import { useQueryClient } from '@tanstack/react-query';
import { m } from 'framer-motion';

import { type Softskills } from '~/components/graphic/softskills/type';
import Header from '~/components/header/Header';
import DownloadCircleIcon from '~/components/icons/DownloadCircleIcon';
import HomeIcon from '~/components/icons/HomeIcon';
import Modal from '~/components/modal/Modal';
import useToast from '~/components/toast/useToast';
import { type DNA } from '~/constants/dna';
import BookmarkSection from '~/features/dna/BookmarkSection';
Expand All @@ -23,12 +21,13 @@ import { getUserInfoBySurveyIdQueryKey } from '~/hooks/api/user/useGetUserInfoBy
import useInternalRouter from '~/hooks/router/useInternalRouter';
import { BODY_1, HEAD_2_BOLD } from '~/styles/typo';
import { getBrowser, isAndroid, isIos } from '~/utils/agent';
import { type CreateImage } from '~/utils/createImage';
import { imageDownloadPC } from '~/utils/image';
import { type Group } from '~/utils/resultLogic';

import { type DnaOwnerStatus } from './type';

const DNAImageDownloadModal = dynamic(() => import('./DNAImageDownloadModal'), { ssr: false });

interface Image {
webp: string;
png: string;
Expand All @@ -51,7 +50,6 @@ interface Props {
userInfo: ReturnType<typeof useGetUserInfoBySurveyId>['data'];
topTendencies: Softskills[];
bookmarkedFeedbacks: QuestionFeedback[];
downloadableImage: CreateImage;
}

const LoadedDna: FC<Props> = ({
Expand All @@ -62,7 +60,6 @@ const LoadedDna: FC<Props> = ({
userInfo,
topTendencies,
bookmarkedFeedbacks,
downloadableImage,
}) => {
const { fireToast } = useToast();

Expand All @@ -76,8 +73,6 @@ const LoadedDna: FC<Props> = ({
const [isImageModalShowing, setIsImageModalShowing] = useState(false);

const onDownloadClick = async () => {
const imageObj = JSON.parse(downloadableImage.base64);
const imageBase64 = 'data:image/png;base64,' + imageObj.base64 ?? '';
const browser = getBrowser();

// TODO: share 갤러리에 저장 기능 되살리기
Expand All @@ -99,7 +94,10 @@ const LoadedDna: FC<Props> = ({
return;
}

imageDownloadPC(imageBase64, 'dna');
imageDownloadPC(
`/api/dna-image?group=${group}&nickname=${userInfo?.nickname}&position=${userInfo?.position}`,
'dna',
);
fireToast({ content: '이미지 다운로드 되었습니다.', higherThanCTA: true });
};

Expand Down Expand Up @@ -181,7 +179,7 @@ const LoadedDna: FC<Props> = ({
</main>

<DNAImageDownloadModal
downloadableBase64={downloadableImage.base64}
imageSrc={`/api/dna-image?group=${group}&nickname=${userInfo?.nickname}&position=${userInfo?.position}`}
isShowing={isImageModalShowing}
onClose={() => setIsImageModalShowing(false)}
/>
Expand Down Expand Up @@ -243,7 +241,6 @@ const ulCss = css`

const subTitleCss = css`
${HEAD_2_BOLD};
color: var(--gray-500-text-secondary, #394258);
`;

Expand All @@ -252,61 +249,3 @@ const downloadIconCss = css`
right: -2px;
bottom: -5px;
`;

const DNAImageDownloadModal = ({
downloadableBase64,
onClose,
isShowing,
}: {
downloadableBase64: string;
onClose: () => void;
isShowing: boolean;
}) => {
const imageObj = JSON.parse(downloadableBase64);
const imageBase64 = 'data:image/png;base64,' + imageObj.base64 ?? '';

return (
<Modal isShowing={isShowing}>
<Modal.Header onBackClick={onClose} overrideCss={imageDownloadModalHeaderCss} />
<m.div
css={imageDownloadModalCss}
variants={{
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
}}
initial="initial"
animate="animate"
exit="exit"
>
<h1>꾹 눌러서 이미지를 저장하세요</h1>

<img src={imageBase64} alt="dna" />
</m.div>
</Modal>
);
};

const imageDownloadModalHeaderCss = css`
background-color: transparent;
border-bottom: none;
`;

const imageDownloadModalCss = css`
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
h1 {
${HEAD_2_BOLD};
user-select: none;
}
img {
user-select: none;
width: 80%;
}
`;
Loading

0 comments on commit 37e5b81

Please sign in to comment.