Skip to content

Commit

Permalink
이미지 다운로드 기능 구현 (#387)
Browse files Browse the repository at this point in the history
* result DNA 페이지 작업 중 (#357)

Co-authored-by: 권오연 <ohyeon.kwon@flask.global>

* feat: 나와 잘맞는 DNA 배너 (#366)

* feat: 나와 잘맞는 DNA 배너

* Update src/components/banner/DnaBanner.tsx

Co-authored-by: hyesung oh <haesungoh414@gmail.com>

---------

Co-authored-by: oyeon-kwon <ohyeon.kwon@flask.global>
Co-authored-by: hyesung oh <haesungoh414@gmail.com>

* 결과 로직 알고리즘 짜기  (#371)

* feat: useIsMounted 작성 (#71)

* feat: 결과 로직 짜기 - 많이 선택된 성향 찾기

* feat: 결과 로직 카운트 수정

* feat: 결과 그룹 도출

* feat: 결과 그룹 도출 로직 연결

---------

Co-authored-by: Sangjo LEE <cent7425@gmail.com>

* DNA 페이지 API 관련 로직 (#365)

* feat: use get user info by survey id

* feat: use get tendency와 로딩 핸들링

* feat: dna 페이지 접근 유저 확인

* 결과 페이지 상단 DNA 배너 (#370)

* feat: 결과 페이지 dna 배너 피드백 없을 때

* feat: 결과 페이지 상단 배너

* feat: 북마크 아이콘 피드백 컴포넌트에 추가 (#369)

* feat: useIsMounted 작성 (#71)

* feat: 북마크 아이콘 피드백 컴포넌트에 추가

* fix: isView Props 삭제

---------

Co-authored-by: Sangjo LEE <cent7425@gmail.com>
Co-authored-by: oyeon-kwon <ohyeon.kwon@flask.global>

* dna 페이지 퍼블리싱 및 로직 추가 (#372)

* feat: 성향 데이터

* feat: 결과 dna 페이지 알고리즘 사용

* feat: dna 페이지 배너 사용 및 결과 렌더링

* feat: 외부자 dna 페이지 대응

* DNA 결과 로직 테스트 코드 작성 (#373)

* feat: 성향 데이터

* feat: 결과 dna 페이지 알고리즘 사용

* feat: dna 페이지 배너 사용 및 결과 렌더링

* feat: 외부자 dna 페이지 대응

* fix: 타입 선언 충돌 수정

* test: 결과 로직 테스트 코드 작성

* fix: console 제거

* fix: build error type fix

* refactor: 주석 제거

---------

Co-authored-by: hyesungoh <haesungoh414@gmail.com>

* 서버에서 SVG DNA 이미지 만들기 (#381)

* feat: 성향 데이터

* feat: 결과 dna 페이지 알고리즘 사용

* feat: dna 페이지 배너 사용 및 결과 렌더링

* feat: 외부자 dna 페이지 대응

* chore: satori 설치

* feat: image server에서 만들기

* feat: 강제 html 집어넣기

* feat: 일단 귀여운 이미지 배치

* refactor: 안쓰는거 정리

* feat: text 배치

* fix: 안쓰는 모듈 제거

* refactor: prod base url 추가 (될지는 모르겠음)

* feat: create image 정리

* fix: build error fix

---------

Co-authored-by: hyesungoh <haesungoh414@gmail.com>

* satori로 만든 svg를 png로 변환 (#382)

* feat: 성향 데이터

* feat: 결과 dna 페이지 알고리즘 사용

* feat: dna 페이지 배너 사용 및 결과 렌더링

* feat: 외부자 dna 페이지 대응

* chore: satori 설치

* feat: image server에서 만들기

* feat: 강제 html 집어넣기

* feat: 일단 귀여운 이미지 배치

* refactor: 안쓰는거 정리

* feat: text 배치

* fix: 안쓰는 모듈 제거

* refactor: prod base url 추가 (될지는 모르겠음)

* feat: create image 정리

* fix: build error fix

* chore: resvg 라이브러리 설치

* feat: resvg로 png base encoding

---------

Co-authored-by: hyesungoh <haesungoh414@gmail.com>

* refactor: 페이지 경로 변경

* feat: 이미지 다운로드 기능 추가

* feat: 모바일 감지해서 토스트 메시지 띄우기

* feat: mock data 추가

* feat: 폰트 적용

* feat: 화질 깨지는 문제 수정

* feat: 포지션  집어넣기

* feat: 배너 완성

* fix: minor style fix

* feat: 이미지 변경

* feat: 포지션 추가

* refactor: 이미지 로직 분리

* feat: pc 다운

* feat: 다운로드 추가

* fix: 충돌 에러 수정

* feat: image 경로 변경

* refactor: 사용하지 않는 파일 삭제

---------

Co-authored-by: 권오연 <61301574+oyeon-kwon@users.noreply.github.com>
Co-authored-by: 권오연 <ohyeon.kwon@flask.global>
Co-authored-by: hyesung oh <haesungoh414@gmail.com>
Co-authored-by: Sangjo LEE <cent7425@gmail.com>
  • Loading branch information
5 people authored Jul 20, 2023
1 parent 6a65aa9 commit 97282a7
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 38 deletions.
9 changes: 9 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions src/components/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ReactNode } from 'react';
import { useRouter } from 'next/router';
import { css, type Theme } from '@emotion/react';
import { css, type Interpolation, type Theme } from '@emotion/react';

import ArrowIcon from '~/components/icons/ArrowIcon';
import { HEAD_2_BOLD } from '~/styles/typo';
Expand All @@ -11,9 +11,10 @@ interface Props {
onBackClick?: () => void;
isContainRemainer?: boolean;
backIcon?: ReactNode;
overrideCss?: Interpolation<Theme>;
}

const Header = ({ title, rightButton, onBackClick, isContainRemainer, backIcon }: Props) => {
const Header = ({ title, rightButton, onBackClick, isContainRemainer, backIcon, overrideCss }: Props) => {
const router = useRouter();

const onBack = () => {
Expand All @@ -22,7 +23,7 @@ const Header = ({ title, rightButton, onBackClick, isContainRemainer, backIcon }

return (
<>
<header css={[headerCss, fixedTopCss]}>
<header css={[headerCss, fixedTopCss, overrideCss]}>
<button type="button" onClick={onBack} css={iconButtonCss}>
{backIcon ? backIcon : <ArrowIcon />}
</button>
Expand Down
46 changes: 46 additions & 0 deletions src/components/icons/DownloadCircleIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { type ComponentProps } from 'react';

import Svg from '~/components/svg/Svg';

function DownloadCircleIcon({ width = 88, height = 87, color = '#1D2942', ...rest }: ComponentProps<typeof Svg>) {
return (
<Svg width={width} height={height} {...rest}>
<g filter="url(#filter0_d_4281_77401)">
<rect x="19.5" y="14" width="49" height="49" rx="24.5" fill="white" />
<path
d="M54.024 41.8402V43.1766C54.024 45.0476 54.024 45.9832 53.6598 46.6978C53.3395 47.3265 52.8284 47.8376 52.1998 48.1579C51.4851 48.522 50.5496 48.522 48.6785 48.522H39.324C37.4529 48.522 36.5173 48.522 35.8027 48.1579C35.1741 47.8376 34.663 47.3265 34.3427 46.6978C33.9785 45.9832 33.9785 45.0476 33.9785 43.1766V41.8402M49.5694 36.272L44.0012 41.8402M44.0012 41.8402L38.4331 36.272M44.0012 41.8402V28.4766"
stroke={color}
strokeWidth="2.22727"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<filter
id="filter0_d_4281_77401"
x="0.881838"
y="0.0363784"
width="86.2363"
height="86.2363"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4.65454" />
<feGaussianBlur stdDeviation="9.30908" />
<feColorMatrix type="matrix" values="0 0 0 0 0.615686 0 0 0 0 0.690196 0 0 0 0 0.866667 0 0 0 0.13 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4281_77401" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4281_77401" result="shape" />
</filter>
</defs>
</Svg>
);
}

export default DownloadCircleIcon;
105 changes: 105 additions & 0 deletions src/features/dna/DnaBannerView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* eslint-disable @next/next/no-img-element */
import colors from '~/styles/color';
import { type Group } from '~/utils/resultLogic';

interface DnaProps {
title: string;
desc: string;
imageBaseUrl: string;
}

const DNA_TITLE_TO_GROUP: Record<string, Group> = {
'카리스마 지휘관': 'A',
'철두철미한 설계자': 'B',
'활동적인 외교관': 'C',
'독창적인 트렌드세터': 'D',
'굴하지 않는 개척자': 'E',
'유연한 중재자': 'F',
};

const IMAGE_BY_GROUP: Record<Group, string> = {
A: `/images/dna/01_dna.png`,
B: `/images/dna/02_dna.png`,
C: `/images/dna/03_dna.png`,
D: `/images/dna/04_dna.png`,
E: `/images/dna/05_dna.png`,
F: `/images/dna/06_dna.png`,
} as const;

const DnaBannerView = ({
imageBaseUrl,
title = '독창적인 트렌드세터',
desc = '특별함을 추구하는 예술가적 성향',
}: DnaProps) => {
return (
<section
style={{
position: 'relative',
marginTop: '21px',
overflow: 'hidden',
display: 'flex',
backgroundColor: colors.gray_50,
borderRadius: '6px',
width: '100%',
height: '78px',
paddingLeft: '86px',
}}
>
<div
style={{
position: 'absolute',
left: '0',
width: '9px',
backgroundColor: colors.primary_100,
height: '100%',
}}
/>
<img
style={{
position: 'absolute',
bottom: '15px',
left: '24px',
objectFit: 'cover',
width: '48.95px',
}}
src={`${imageBaseUrl}${IMAGE_BY_GROUP[DNA_TITLE_TO_GROUP[title]]}`}
alt="dna"
/>

<div
style={{
position: 'relative',
zIndex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '17px 0',
}}
>
<div
style={{
fontWeight: 400,
fontSize: '14px',
lineHeight: '150%',
letterSpacing: '-0.3px',
}}
>
{desc}
</div>
<div
style={{
fontWeight: 600,
fontSize: '16px',
lineHeight: '150%',
letterSpacing: '-0.3px',
color: colors.gray_500,
}}
>
{title}
</div>
</div>
</section>
);
};

export default DnaBannerView;
2 changes: 1 addition & 1 deletion src/features/feedback/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const Input = ({ onInputSubmit, value }: Props) => {
<input
ref={inputRef}
css={(theme) => inputCss(theme, inputWidth)}
value={text}
value={text ?? ''}
onChange={onChange}
onFocus={setFalse}
placeholder="EX) UX 디자이너"
Expand Down
24 changes: 24 additions & 0 deletions src/pages/dna/LoadedDna.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useQueryClient } from '@tanstack/react-query';

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 { type DNA } from '~/constants/dna';
import BookmarkSection from '~/features/dna/BookmarkSection';
Expand All @@ -17,6 +18,7 @@ import type useGetUserInfoBySurveyId from '~/hooks/api/user/useGetUserInfoBySurv
import { getUserInfoBySurveyIdQueryKey } from '~/hooks/api/user/useGetUserInfoBySurveyId';
import useInternalRouter from '~/hooks/router/useInternalRouter';
import { BODY_1, HEAD_2_BOLD } from '~/styles/typo';
import { imageDownloadPC } from '~/utils/image';
import { type Group } from '~/utils/resultLogic';

import { type DnaOwnerStatus } from './type';
Expand All @@ -43,6 +45,7 @@ interface Props {
userInfo: ReturnType<typeof useGetUserInfoBySurveyId>['data'];
topTendencies: Softskills[];
bookmarkedFeedbacks: QuestionFeedback[];
downloadableImageBase64: string;
}

const LoadedDna: FC<Props> = ({
Expand All @@ -53,6 +56,7 @@ const LoadedDna: FC<Props> = ({
userInfo,
topTendencies,
bookmarkedFeedbacks,
downloadableImageBase64,
}) => {
const router = useInternalRouter();

Expand All @@ -61,6 +65,15 @@ const LoadedDna: FC<Props> = ({
onSuccess: () => queryClient.invalidateQueries(getUserInfoBySurveyIdQueryKey(surveyId)),
});

const onDownloadClick = () => {
const imageObj = JSON.parse(downloadableImageBase64);
const imageBase64 = 'data:image/png;base64,' + imageObj.base64 ?? '';
// if (detectMobileDevice(window.navigator.userAgent)) {
// return;
// }
imageDownloadPC(imageBase64, 'dna');
};

return (
<>
{dnaOwnerStatus === 'current_user' ? (
Expand All @@ -80,6 +93,11 @@ const LoadedDna: FC<Props> = ({
<source srcSet={IMAGE_BY_GROUP[group].webp} type="image/webp" />
<Image priority unoptimized css={dnaImageCss} src={IMAGE_BY_GROUP[group].png} alt="DNA 이미지" fill />
</picture>
{dnaOwnerStatus === 'current_user' && (
<button type="button" css={downloadIconCss} onClick={onDownloadClick}>
<DownloadCircleIcon />
</button>
)}
</section>

<section
Expand Down Expand Up @@ -187,3 +205,9 @@ const subTitleCss = css`
color: var(--gray-500-text-secondary, #394258);
`;

const downloadIconCss = css`
position: absolute;
right: -2px;
bottom: -5px;
`;
21 changes: 20 additions & 1 deletion src/pages/dna/[id].page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ import {
type NextPageWithLayout,
type WithSeoProps,
} from '~/types/page';
import { createImage } from '~/utils/createImage';
import { getResultGroup, type Group } from '~/utils/resultLogic';

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

const Dna: NextPageWithLayout<WithSeoProps<ServerSideProps>> = ({ surveyId, seo: { title, description, ogImage } }) => {
const Dna: NextPageWithLayout<WithSeoProps<ServerSideProps>> = ({
surveyId,
downloadableImageBase64,
seo: { title, description, ogImage },
}) => {
const { data: userInfo, isLoading } = useGetUserInfoBySurveyId(String(surveyId), { enabled: Boolean(surveyId) });
const { tendencies } = useSortedTop5Tendencies(surveyId);
const { dnaOwnerStatus } = useDanOnwerStatus(surveyId);
Expand All @@ -44,6 +49,7 @@ const Dna: NextPageWithLayout<WithSeoProps<ServerSideProps>> = ({ surveyId, seo:
userInfo={userInfo}
topTendencies={tendencies}
bookmarkedFeedbacks={bookmarkedFeedbacks}
downloadableImageBase64={downloadableImageBase64}
/>
)}
</LoadingHandler>
Expand All @@ -57,6 +63,7 @@ Dna.getLayout = (page: ReactElement) => <LayoutPaddingTo23>{page}</LayoutPadding

type ServerSideProps = {
surveyId: string;
downloadableImageBase64: string;
};

const OG_IMAGE_BASE = '/images/dna/seo';
Expand Down Expand Up @@ -94,6 +101,17 @@ export const getServerSideProps: GetServerSidePropsWithDehydratedStateAndSEO<Ser
};
}

const imageData = await createImage({
group: group,
userInfo: userInfo,
});

if (typeof imageData !== 'string') {
return {
notFound: true,
};
}

return {
props: {
surveyId: id,
Expand All @@ -103,6 +121,7 @@ export const getServerSideProps: GetServerSidePropsWithDehydratedStateAndSEO<Ser
description: userInfo.position,
ogImage: OG_IMAGE_MAP_BY_GROUP[group],
},
downloadableImageBase64: imageData,
},
};
};
Expand Down
33 changes: 0 additions & 33 deletions src/utils/createImage.ts

This file was deleted.

Loading

0 comments on commit 97282a7

Please sign in to comment.