diff --git a/next.config.js b/next.config.js index 6506fd0..25963d7 100644 --- a/next.config.js +++ b/next.config.js @@ -5,6 +5,9 @@ const withVanillaExtract = createVanillaExtractPlugin(); const nextConfig = { reactStrictMode: true, swcMinify: true, + images: { + domains: ['horosocular.s3.ap-northeast-1.amazonaws.com'], + }, }; module.exports = withVanillaExtract(nextConfig); diff --git a/package.json b/package.json index 697af25..7820a6a 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,16 @@ "dependencies": { "@prisma/client": "4.9.0", "@tanstack/react-query": "4.16.1", + "@types/dom-to-image": "^2.6.4", + "@types/file-saver": "^2.0.5", "@types/node": "18.11.9", "@types/react": "18.0.25", "@types/react-dom": "18.0.9", "@vanilla-extract/css": "1.9.2", + "dom-to-image": "^2.6.0", "eslint": "8.28.0", "eslint-config-next": "13.1.6", + "file-saver": "^2.0.5", "framer-motion": "7.6.7", "jotai": "1.10.0", "next": "13.1.6", diff --git a/src/components/community/index.ts b/src/components/community/index.ts new file mode 100644 index 0000000..9946c60 --- /dev/null +++ b/src/components/community/index.ts @@ -0,0 +1,5 @@ +import StartPage from './playground/DevTest/StartPage'; +import PlayPage from './playground/DevTest/PlayPage'; +import ResultPage from './playground/DevTest/ResultPage'; + +export { StartPage, PlayPage, ResultPage }; diff --git a/src/components/community/playground/DevTest/PlayPage.tsx b/src/components/community/playground/DevTest/PlayPage.tsx new file mode 100644 index 0000000..a9e2de0 --- /dev/null +++ b/src/components/community/playground/DevTest/PlayPage.tsx @@ -0,0 +1,109 @@ +import { questions } from '@/resources/devTestQustions'; +import { useState, useEffect } from 'react'; +import { Button, TitleBox } from '../common'; +import type { StepProps } from './types'; +import * as styles from './devtest.css'; +import ProgressBar from './ProgressBar/ProgressBar'; +import { DevType, results } from '@/resources/devTestQustions'; + +interface Props extends StepProps { + saveResult?: (res: DevType) => void; +} + +type Count = { + [key: number]: number; +}; + +const PlayPage = (props: Props) => { + const { onEnd, saveResult } = props; + const [idx, setIdx] = useState(0); + const [answers, setAnswers] = useState>( + Array(questions.length).fill(0), + ); + const [choices, setChoices] = useState>( + questions[0].select[0].type, + ); + const [types, setTypes] = useState>([]); + + const choice = (aIdx: number, devType: Array) => { + setChoices(devType); + setAnswers((prev) => prev.map((p, pi) => (idx === pi ? aIdx : p))); + }; + + const moveToNext = () => { + setTypes((prev) => [...prev, ...choices]); + setIdx((prev) => prev + 1); + }; + + const getMode = () => { + const devTypes = types.map((type) => type.name); + + const counts: Count = { + 0: 0, + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0, + }; + + const newObject = devTypes.reduce((acc, cur) => { + acc.hasOwnProperty(cur) ? (acc[cur] += 1) : (acc[cur] = 1); + return acc; + }, counts); + + const modeKey: number = Object.keys(newObject) + .map((a) => Number(a)) + .reduce((acc, cur) => (newObject[acc] > newObject[cur] ? acc : cur)); + + saveResult!(results[modeKey]); + }; + + useEffect(() => { + if (idx === questions.length) { + getMode(); + onEnd(); + } else { + setChoices(questions[idx].select[0].type); + } + }, [idx]); + + return ( +
+ {questions[idx] ? ( + <> + + + +
+ +
+
+ {questions[idx].select.map((s, aIdx) => ( +
+
+ ); +}; + +export default PlayPage; diff --git a/src/components/community/playground/DevTest/ProgressBar/ProgressBar.css.ts b/src/components/community/playground/DevTest/ProgressBar/ProgressBar.css.ts new file mode 100644 index 0000000..cabdcff --- /dev/null +++ b/src/components/community/playground/DevTest/ProgressBar/ProgressBar.css.ts @@ -0,0 +1,98 @@ +import { styleVariants, style } from '@vanilla-extract/css'; +import { COLORS } from '../../common/colorToken'; + +const innerBase = style({ + transition: 'all 0.25s', + background: `linear-gradient(135deg, ${COLORS.SSU.DeepBlue}, ${COLORS.SSU.SkyBlue})`, + height: '100%', + borderRadius: '500px', +}); + +export const progress = styleVariants({ + outer: [ + { + width: '100%', + height: '15px', + borderRadius: '500px', + backgroundColor: `${COLORS.grayscale.Gray2}`, + overflow: 'hidden', + padding: '2px', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }, + ], + inner__0: [ + innerBase, + { + width: `2%`, + }, + ], + inner__1: [ + innerBase, + { + width: `9%`, + }, + ], + inner__2: [ + innerBase, + { + width: `18%`, + }, + ], + inner__3: [ + innerBase, + { + width: `27%`, + }, + ], + inner__4: [ + innerBase, + { + width: `36%`, + }, + ], + inner__5: [ + innerBase, + { + width: `45%`, + }, + ], + inner__6: [ + innerBase, + { + width: `54%`, + }, + ], + inner__7: [ + innerBase, + { + width: `63%`, + }, + ], + inner__8: [ + innerBase, + { + width: `72%`, + }, + ], + inner__9: [ + innerBase, + { + width: `81%`, + }, + ], + inner__10: [ + innerBase, + { + width: `90%`, + }, + ], + inner__11: [ + innerBase, + { + width: `100%`, + }, + ], +}); diff --git a/src/components/community/playground/DevTest/ProgressBar/ProgressBar.tsx b/src/components/community/playground/DevTest/ProgressBar/ProgressBar.tsx new file mode 100644 index 0000000..87caaf8 --- /dev/null +++ b/src/components/community/playground/DevTest/ProgressBar/ProgressBar.tsx @@ -0,0 +1,15 @@ +import * as styles from './ProgressBar.css'; + +interface Props { + curIdx: number; +} + +const ProgressBar = ({ curIdx }: Props) => { + return ( +
+
+
+ ); +}; + +export default ProgressBar; diff --git a/src/components/community/playground/DevTest/ResultBox/ResultBox.css.ts b/src/components/community/playground/DevTest/ResultBox/ResultBox.css.ts new file mode 100644 index 0000000..0bd14f9 --- /dev/null +++ b/src/components/community/playground/DevTest/ResultBox/ResultBox.css.ts @@ -0,0 +1,80 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '../../common/colorToken'; +import { bodyText } from '../../common/textToken.css'; +import { titleText } from '../../common/textToken.css'; + +export const resultBox = styleVariants({ + box: [ + { + width: '100%', + backgroundColor: 'white', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + borderRadius: '20px', + padding: '40px 10px', + gap: '40px', + }, + ], + titleBox: [ + { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + gap: '3px', + whiteSpace: 'pre-line', + wordBreak: 'keep-all', + }, + ], + title: [ + bodyText.body2R, + { + color: COLORS.grayscale.Gray8, + }, + ], + subtitle: [ + bodyText.body1B, + { + color: COLORS.grayscale.Gray9, + }, + ], + img: [ + { + position: 'relative', + width: '90%', + height: '200px', + }, + ], + list: [ + bodyText.body3, + { + listStyleType: 'none', + margin: '0px', + padding: '0px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + whiteSpace: 'pre-line', + wordBreak: 'keep-all', + gap: '8px', + color: COLORS.grayscale.Gray9, + }, + ], + logo: [ + { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '4px', + }, + ], + recDetail: [ + { + fontSize: '14px', + textAlign: 'center', + color: COLORS.grayscale.Gray7, + }, + ], +}); diff --git a/src/components/community/playground/DevTest/ResultBox/ResultBox.tsx b/src/components/community/playground/DevTest/ResultBox/ResultBox.tsx new file mode 100644 index 0000000..e8a168b --- /dev/null +++ b/src/components/community/playground/DevTest/ResultBox/ResultBox.tsx @@ -0,0 +1,43 @@ +import { DevType, images } from '@/resources/devTestQustions'; +import * as styles from './ResultBox.css'; +import Image from 'next/image'; +import { Button } from '../../common'; +import Logo from '@/resources/assets/logo.svg'; + +interface Props { + result: DevType; +} + +const ResultBox = ({ result }: Props) => { + return ( +
+
+ {result.title} + {result.subtitle} +
+ + {result.type} + +
    + {result.content.map((r) => ( +
  • + {r} +
  • + ))} +
+
+ logo + + GDSC Soongsil Univ. + +
+ + {/* + 나와 비슷한 개발자들이 모여있는... + +
+ ); +}; + +export default ResultBox; diff --git a/src/components/community/playground/DevTest/ResultPage.tsx b/src/components/community/playground/DevTest/ResultPage.tsx new file mode 100644 index 0000000..4887401 --- /dev/null +++ b/src/components/community/playground/DevTest/ResultPage.tsx @@ -0,0 +1,68 @@ +import { DevType } from '@/resources/devTestQustions'; +import type { StepProps } from './types'; +import * as styles from './devtest.css'; +import ResultBox from './ResultBox/ResultBox'; +import { Button, WaitingModal } from '../common'; +import domtoimage from 'dom-to-image'; +import { saveAs } from 'file-saver'; +import { useRef, useState } from 'react'; +import { useInterval } from '@/hooks/useInterval'; + +interface Props extends StepProps { + result?: DevType; +} + +const ResultPage = ({ result }: Props) => { + const cardRef = useRef(null); + const [loading, setLoading] = useState(true); + + useInterval(() => { + setLoading(false); + }, 3000); + + const onDownloadBtn = () => { + const card = cardRef.current; + if (card) { + domtoimage.toBlob(card).then((blob) => { + saveAs(blob, 'card.png'); + }); + } + }; + + return ( +
+
+
+ + 당신의 개발자 유형은, + + {`${result?.type}!`} +
+ +
+
+
+ + 나와 비슷한 개발자들이 모여있는 + +
+
+ {loading ? : <>} +
+ ); +}; + +export default ResultPage; diff --git a/src/components/community/playground/DevTest/StartPage.tsx b/src/components/community/playground/DevTest/StartPage.tsx new file mode 100644 index 0000000..acfaf17 --- /dev/null +++ b/src/components/community/playground/DevTest/StartPage.tsx @@ -0,0 +1,30 @@ +import { StartButton, TitleBox, VisitorBox } from '../common'; +import * as styles from './devtest.css'; +import { devTestLogo } from '@/resources/communityRes'; +import type { StepProps } from './types'; +import Image from 'next/image'; + +interface Props extends StepProps {} + +/** + * 시작하기 버튼이 있는 테스트 시작 페이지 + */ +const StartPage = (props: Props) => { + const { onEnd } = props; + return ( +
+ + + onEnd()} /> + GDSC Soongsil Univ. + + devtest-logo + +
+ ); +}; + +export default StartPage; diff --git a/src/components/community/playground/DevTest/devtest.css.ts b/src/components/community/playground/DevTest/devtest.css.ts new file mode 100644 index 0000000..2780e1d --- /dev/null +++ b/src/components/community/playground/DevTest/devtest.css.ts @@ -0,0 +1,159 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { bodyText } from '../common/textToken.css'; +import { titleText } from '../common/textToken.css'; +import { COLORS } from '../common/colorToken'; + +const BREAKPOINTS = [500, 800] as const; +const MEDIA_QUERY = { + pc: `screen and (min-width: ${BREAKPOINTS[1]}px)`, + mobile: `screen and (max-width: ${BREAKPOINTS[0]}px)`, +} as const; + +/** + * start-page의 container + */ +export const firstPage = style({ + width: '100%', + height: '100%', + position: 'relative', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'flex-start', + gap: '30px', + paddingTop: '80px', + '@media': { + [MEDIA_QUERY.pc]: { + paddingTop: '120px', + }, + [`screen and (min-height: ${BREAKPOINTS[1]}px)`]: { + paddingTop: '120px', + }, + }, +}); + +/** + * play-page의 container + */ +export const playPage = style({ + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '50px', + position: 'relative', +}); + +/** + * 질문, 선택지가 담긴 영역 + */ +export const questionArea = style({ + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '15px', +}); + +export const text = styleVariants({ + title: [bodyText['body2B'], { color: 'white' }], +}); + +export const decoImg = style({ + width: '65%', + height: '40%', + position: 'absolute', + bottom: '0px', + left: '50%', + transform: 'translateX(-50%)', +}); + +export const progress = style({ + width: '90%', + position: 'absolute', + top: '20px', + left: '50%', + transform: 'translate(-50%, 0)', +}); + +export const resultPage = styleVariants({ + container: [ + { + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '30px', + paddingBottom: '60px', + }, + ], + resultWrapper: [ + { + background: `linear-gradient( + 135deg, + ${COLORS.SSU.Blue}, + ${COLORS.SSU.SkyBlue} + )`, + width: '100%', + padding: '60px 30px', + display: 'flex', + flexDirection: 'column', + gap: '15px', + }, + ], + btnBox: [ + { + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '10px', + padding: '0px 10px', + }, + ], + recruiting: [ + { + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '8px', + }, + ], + recDetail: [ + { + fontSize: '14px', + textAlign: 'center', + color: COLORS.grayscale.white, + }, + ], +}); + +export const resultTitle = styleVariants({ + box: [ + { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '5px', + whiteSpace: 'pre-line', + textAlign: 'center', + }, + ], + title: [ + titleText.subtitle1, + { + color: COLORS.grayscale.white, + textShadow: '1px 1px 2px #14141460', + }, + ], + subtitle: [ + titleText.subtitle2B, + { + color: COLORS.grayscale.white, + textShadow: '1px 1px 2px #14141460', + }, + ], +}); diff --git a/src/components/community/playground/DevTest/types.ts b/src/components/community/playground/DevTest/types.ts new file mode 100644 index 0000000..501031e --- /dev/null +++ b/src/components/community/playground/DevTest/types.ts @@ -0,0 +1,3 @@ +export interface StepProps { + onEnd: () => void; +} diff --git a/src/components/community/playground/common/Button/Button.css.ts b/src/components/community/playground/common/Button/Button.css.ts new file mode 100644 index 0000000..e002a26 --- /dev/null +++ b/src/components/community/playground/common/Button/Button.css.ts @@ -0,0 +1,63 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '../colorToken'; +import { btnText } from '../textToken.css'; + +const btnBase = style({ + border: 'none', + width: '90%', + minWidth: '270px', + minHeight: '50px', + borderRadius: '500px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + boxShadow: 'rgba(0, 0, 0, 0.05) 0px 10px 50px', + fontFamily: `'SUIT Variable', sans-serif`, + whiteSpace: 'pre-line', + wordBreak: 'keep-all', + textAlign: 'center', +}); + +export const button = styleVariants({ + black: [ + btnBase, + btnText.button4B, + { + backgroundColor: `black`, + color: `white`, + }, + ], + white: [ + btnBase, + btnText.button4B, + { + backgroundColor: `white`, + color: `black`, + }, + ], + blue: [ + btnBase, + btnText.button4B, + { + backgroundColor: `white`, + color: COLORS.SSU.DeepBlue, + }, + ], + result: [ + btnBase, + btnText.button2B, + { + backgroundColor: COLORS.grayscale.white, + color: COLORS.SSU.DeepBlue, + }, + ], + gradient: [ + btnBase, + btnText.button4B, + { + background: `linear-gradient(135deg, ${COLORS.GDSC.Blue}, ${COLORS.GDSC.Red})`, + color: COLORS.grayscale.white, + }, + ], +}); diff --git a/src/components/community/playground/common/Button/Button.tsx b/src/components/community/playground/common/Button/Button.tsx new file mode 100644 index 0000000..e4c274e --- /dev/null +++ b/src/components/community/playground/common/Button/Button.tsx @@ -0,0 +1,29 @@ +import * as styles from './Button.css'; + +const Themes = ['black', 'white', 'blue', 'result', 'gradient'] as const; + +interface Props extends React.ComponentProps<'button'> { + /**0번 : 블랙 테마 + * 1번 : 화이트 테마 + * 2번 : 블루 테마 + * 3번 : 결과 버튼 + * 4번 : 그라디언트 + */ + selected: number; + title: string; +} + +/** + * 테스트 중 사용될 일반 버튼 + */ +const Button = ({ selected, title, ...buttonProps }: Props) => { + const Theme = Themes[selected]; + + return ( + + ); +}; + +export default Button; diff --git a/src/components/community/playground/common/LoadingSpinner/LoadingSpinner.css.ts b/src/components/community/playground/common/LoadingSpinner/LoadingSpinner.css.ts new file mode 100644 index 0000000..6aeb166 --- /dev/null +++ b/src/components/community/playground/common/LoadingSpinner/LoadingSpinner.css.ts @@ -0,0 +1,116 @@ +import { keyframes, style, styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '../colorToken'; +import { bodyText } from '../textToken.css'; + +export const Spinner = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '30px', +}); + +const flip = keyframes({ + '0%': { transform: 'rotateY(360deg)' }, + '80%': { transform: 'rotateY(360deg)' }, +}); + +const textBase = style({ + position: 'relative', + display: 'inline-block', + fontSize: '40px', + fontWeight: '900', + color: '#fff', + textTransform: 'uppercase', + animation: `${flip} 2s infinite`, +}); + +export const waviy = styleVariants({ + container: [ + { + position: 'relative', + }, + ], + text1: [ + textBase, + { + animationDelay: 'calc(.2s * 1)', + }, + ], + text2: [ + textBase, + { + animationDelay: 'calc(.2s * 2)', + }, + ], + text3: [ + textBase, + { + animationDelay: 'calc(.2s * 3)', + }, + ], + text4: [ + textBase, + { + animationDelay: 'calc(.2s * 4)', + }, + ], +}); + +const load = keyframes({ + '0%': { WebkitTransform: 'rotate(0deg)', transform: 'rotate(0deg)' }, + '100%': { WebkitTransform: 'rotate(360deg)', transform: 'rotate(360deg)' }, +}); + +export const Loader = style({ + borderRadius: '50%', + color: '#ffffff', + fontSize: '11px', + textIndent: '-99999em', + position: 'relative', + width: '10em', + height: '10em', + boxShadow: 'inset 0 0 0 1em', + WebkitTransform: 'translateZ(0)', + msTransform: 'translateZ(0)', + transform: 'translateZ(0)', + selectors: { + '&:before': { + position: 'absolute', + content: '', + width: '5.2em', + height: '10.2em', + background: COLORS.SSU.Blue, + borderRadius: '10.2em 0 0 10.2em', + top: '-0.1em', + left: '-0.1em', + WebkitTransformOrigin: '5.1em 5.1em', + transformOrigin: '5.1em 5.1em', + WebkitAnimation: `${load} 2s infinite ease 1.5s`, + animation: `${load} 2s infinite ease 1.5s`, + }, + '&:after': { + position: 'absolute', + content: '', + width: '5.2em', + height: '10.2em', + background: COLORS.SSU.Blue, + borderRadius: '0 10.2em 10.2em 0', + top: '-0.1em', + left: '4.9em', + WebkitTransformOrigin: ' 0.1em 5.1em', + transformOrigin: '0.1em 5.1em', + WebkitAnimation: `${load} 2s infinite ease`, + animation: `${load} 2s infinite ease`, + }, + }, +}); + +export const Text = styleVariants({ + subtext: [ + bodyText.body1R, + { + color: `white`, + marginTop: '-25px', + }, + ], +}); diff --git a/src/components/community/playground/common/LoadingSpinner/LoadingSpinner.tsx b/src/components/community/playground/common/LoadingSpinner/LoadingSpinner.tsx new file mode 100644 index 0000000..c9bc828 --- /dev/null +++ b/src/components/community/playground/common/LoadingSpinner/LoadingSpinner.tsx @@ -0,0 +1,18 @@ +import * as styles from './LoadingSpinner.css'; + +const LoadingSpinner = () => { + return ( +
+
+
+ G + D + S + C +
+ 결과를 확인 중 입니다. +
+ ); +}; +('#0dc5c1'); +export default LoadingSpinner; diff --git a/src/components/community/playground/common/LoadingSpinner/WaitingModal.css.ts b/src/components/community/playground/common/LoadingSpinner/WaitingModal.css.ts new file mode 100644 index 0000000..8e55996 --- /dev/null +++ b/src/components/community/playground/common/LoadingSpinner/WaitingModal.css.ts @@ -0,0 +1,22 @@ +import { styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '../colorToken'; + +export const Modal = styleVariants({ + container: [ + { + width: '100%', + height: '100%', + background: COLORS.SSU.Blue, + // background: 'rgba( 0, 0, 0, 0.25 )', + // boxShadow: '0 8px 32px 0 rgba( 31, 38, 135, 0.37 )', + // backdropFilter: 'blur( 4px )', + // webkitBackdropFilter: 'blur( 4px )', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + position: 'absolute', + top: '0px', + left: '0px', + }, + ], +}); diff --git a/src/components/community/playground/common/LoadingSpinner/WaitingModal.tsx b/src/components/community/playground/common/LoadingSpinner/WaitingModal.tsx new file mode 100644 index 0000000..e3514a8 --- /dev/null +++ b/src/components/community/playground/common/LoadingSpinner/WaitingModal.tsx @@ -0,0 +1,12 @@ +import LoadingSpinner from './LoadingSpinner'; +import * as styles from './WaitingModal.css'; + +const WaitingModal = () => { + return ( +
+ +
+ ); +}; + +export default WaitingModal; diff --git a/src/components/community/playground/common/StartButton/StartButton.css.ts b/src/components/community/playground/common/StartButton/StartButton.css.ts new file mode 100644 index 0000000..410dab1 --- /dev/null +++ b/src/components/community/playground/common/StartButton/StartButton.css.ts @@ -0,0 +1,40 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '../colorToken'; +import { btnText } from '../textToken.css'; + +const btnBase = style({ + border: 'none', + width: '65%', + minWidth: '220px', + minHeight: '55px', + borderRadius: '20px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + boxShadow: 'rgba(0, 0, 0, 0.05) 0px 10px 50px', + fontFamily: `'SUIT Variable', sans-serif`, + whiteSpace: 'pre-line', + wordBreak: 'keep-all', + textAlign: 'center', + backgroundColor: 'white', +}); + +export const startBtn = styleVariants({ + devTest: [ + btnBase, + btnText.border, + { + color: `${COLORS.SSU.DeepBlue}`, + fontWeight: '800', + }, + ], + fortune: [ + btnBase, + btnText.border, + { + color: `${COLORS.SSU.DeepBlue}`, + fontWeight: '800', + }, + ], +}); diff --git a/src/components/community/playground/common/StartButton/StartButton.tsx b/src/components/community/playground/common/StartButton/StartButton.tsx new file mode 100644 index 0000000..b3ed9e4 --- /dev/null +++ b/src/components/community/playground/common/StartButton/StartButton.tsx @@ -0,0 +1,26 @@ +import * as styles from './StartButton.css'; + +const Themes = ['devTest', 'fortune'] as const; + +interface Props extends React.ComponentProps<'button'> { + /** 0 : devTest + * 1 : fortune + * 둘 중 하나 */ + theme: number; + title: string; +} + +/** + * 플레이그라운드에서 사용하는 시작버튼 + * + */ +const StartButton = ({ theme, title, ...buttonProps }: Props) => { + const Theme = Themes[theme]; + return ( + + ); +}; + +export default StartButton; diff --git a/src/components/community/playground/common/TitleBox/TitleBox.css.ts b/src/components/community/playground/common/TitleBox/TitleBox.css.ts new file mode 100644 index 0000000..dc2ba03 --- /dev/null +++ b/src/components/community/playground/common/TitleBox/TitleBox.css.ts @@ -0,0 +1,33 @@ +import { styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '../colorToken'; +import { titleText } from '../textToken.css'; + +export const titleBox = styleVariants({ + box: [ + { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '5px', + color: 'white', + }, + ], + title: [ + titleText.title, + { + textAlign: 'center', + textShadow: `0.5px 0.5px 1.5px ${COLORS.grayscale.Gray8}`, + }, + ], + subtitle: [ + titleText.subtitle3, + { + whiteSpace: 'pre-line', + wordBreak: 'keep-all', + textAlign: 'center', + textShadow: `0.5px 0.5px 1.5px ${COLORS.grayscale.Gray8}`, + lineHeight: '27px', + }, + ], +}); diff --git a/src/components/community/playground/common/TitleBox/TitleBox.tsx b/src/components/community/playground/common/TitleBox/TitleBox.tsx new file mode 100644 index 0000000..f7f76fd --- /dev/null +++ b/src/components/community/playground/common/TitleBox/TitleBox.tsx @@ -0,0 +1,22 @@ +import * as styles from './TitleBox.css'; + +interface Props { + title: string; + subtitle: string; +} + +/** + * 플레이그라운드 전용 타이틀 박스 + * @param title 타이틀 첫 줄 (eg. 개발자 성향테스트) + * @param subtitle 타이틀 두 번째 줄 + */ +const TitleBox = (props: Props) => { + return ( +
+ {props.title} + {props.subtitle} +
+ ); +}; + +export default TitleBox; diff --git a/src/components/community/playground/common/VisitorBox/VisitorBox.css.ts b/src/components/community/playground/common/VisitorBox/VisitorBox.css.ts new file mode 100644 index 0000000..dcb0dc5 --- /dev/null +++ b/src/components/community/playground/common/VisitorBox/VisitorBox.css.ts @@ -0,0 +1,30 @@ +import { styleVariants } from '@vanilla-extract/css'; + +export const visitor = styleVariants({ + box: [ + { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '5px', + color: 'white', + }, + ], + title: [ + { + padding: '5px 10px', + backgroundColor: 'black', + fontSize: '10px', + fontWeight: '400', + borderRadius: '5px', + cursor: 'default', + }, + ], + subtitle: [ + { + fontSize: '12px', + fontWeight: '5600', + }, + ], +}); diff --git a/src/components/community/playground/common/VisitorBox/VisitorBox.tsx b/src/components/community/playground/common/VisitorBox/VisitorBox.tsx new file mode 100644 index 0000000..0c423f0 --- /dev/null +++ b/src/components/community/playground/common/VisitorBox/VisitorBox.tsx @@ -0,0 +1,24 @@ +import * as styles from './VisitorBox.css'; + +interface Props { + today: number; + sum: number; +} + +/** + * 방문자 수를 보여주는 컴포넌트 + * @param today 오늘 방문자 + * @param sum 총 방문자 + */ +const VisitorBox = (props: Props) => { + return ( +
+ {`${props.today} / ${props.sum}`} + 명이 참여 중! +
+ ); +}; + +export default VisitorBox; diff --git a/src/components/community/playground/common/colorToken.ts b/src/components/community/playground/common/colorToken.ts new file mode 100644 index 0000000..138c5f5 --- /dev/null +++ b/src/components/community/playground/common/colorToken.ts @@ -0,0 +1,30 @@ +/** + * 색상 토큰 + */ +export const COLORS = { + GDSC: { + Red: '#EA4235', + Yellow: '#FABC05', + Green: '#34A853', + Blue: '#4286F5', + }, + SSU: { + DeepBlue: '#00688F', + Blue: '#00A4CA', + SkyBlue: '#58C4C4', + }, + grayscale: { + white: '#FFFFFF', + Gray1: '#F3F5F7', + Gray2: '#ECEEF0', + Gray3: '#E2E5E8', + Gray4: '#D4D8DC', + Gray5: '#B5B9BD', + Gray6: '#8E9398', + Gray7: '#505458', + Gray8: '#3A3D40', + Gray9: '#252729', + Black: '#101112', + }, + background: '#0A0A0A', +}; diff --git a/src/components/community/playground/common/index.ts b/src/components/community/playground/common/index.ts new file mode 100644 index 0000000..849b0f2 --- /dev/null +++ b/src/components/community/playground/common/index.ts @@ -0,0 +1,15 @@ +import StartButton from './StartButton/StartButton'; +import Button from './Button/Button'; +import TitleBox from './TitleBox/TitleBox'; +import VisitorBox from './VisitorBox/VisitorBox'; +import LoadingSpinner from './LoadingSpinner/LoadingSpinner'; +import WaitingModal from './LoadingSpinner/WaitingModal'; + +export { + StartButton, + Button, + TitleBox, + VisitorBox, + LoadingSpinner, + WaitingModal, +}; diff --git a/src/components/community/playground/common/textToken.css.ts b/src/components/community/playground/common/textToken.css.ts new file mode 100644 index 0000000..8cd7174 --- /dev/null +++ b/src/components/community/playground/common/textToken.css.ts @@ -0,0 +1,205 @@ +import { styleVariants } from '@vanilla-extract/css'; + +const BREAKPOINTS = [500, 800] as const; +const MEDIA_QUERY = { + pc: `screen and (min-width: ${BREAKPOINTS[1]}px)`, + mobile: `screen and (max-width: ${BREAKPOINTS[0]}px)`, +} as const; + +/** + * 타이틀 텍스트 토큰 + */ +export const titleText = styleVariants({ + title: { + fontSize: 36, + fontWeight: 900, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 30, + fontWeight: 900, + }, + }, + }, + subtitle1: { + fontSize: 30, + fontWeight: 800, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 22, + fontWeight: 800, + }, + }, + }, + subtitle2B: { + fontSize: 22, + fontWeight: 700, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 20, + fontWeight: 700, + }, + }, + }, + subtitle2R: { + fontSize: 22, + fontWeight: 500, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 20, + fontWeight: 500, + }, + }, + }, + subtitle3: { + fontSize: 20, + fontWeight: 700, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 18, + fontWeight: 700, + }, + }, + }, +}); + +/** + * Body 텍스트 토큰 + */ +export const bodyText = styleVariants({ + body1B: { + fontSize: 20, + fontWeight: 700, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 18, + fontWeight: 700, + }, + }, + }, + body1R: { + fontSize: 20, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 18, + fontWeight: 400, + }, + }, + }, + body2B: { + fontSize: 18, + fontWeight: 700, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 18, + fontWeight: 700, + }, + }, + }, + body2R: { + fontSize: 18, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 16, + fontWeight: 400, + }, + }, + }, + body3: { + fontSize: 16, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 14, + fontWeight: 400, + }, + }, + }, + caption: { + fontSize: 14, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 12, + fontWeight: 400, + }, + }, + }, +}); + +/** + * 버튼 텍스트 토큰 + */ +export const btnText = styleVariants({ + border: { + fontSize: 20, + fontWeight: 800, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 18, + fontWeight: 800, + }, + }, + }, + button1B: { + fontSize: 20, + fontWeight: 700, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 12, + fontWeight: 400, + }, + }, + }, + button1R: { + fontSize: 20, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 18, + fontWeight: 400, + }, + }, + }, + button2B: { + fontSize: 18, + fontWeight: 700, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 16, + fontWeight: 700, + }, + }, + }, + button2R: { + fontSize: 18, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 16, + fontWeight: 400, + }, + }, + }, + button3R: { + fontSize: 16, + fontWeight: 400, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 14, + fontWeight: 400, + }, + }, + }, + button4B: { + fontSize: 14, + fontWeight: 800, + '@media': { + [MEDIA_QUERY.mobile]: { + fontSize: 13, + fontWeight: 800, + }, + }, + }, +}); diff --git a/src/components/community/playground/playground.css.ts b/src/components/community/playground/playground.css.ts new file mode 100644 index 0000000..6fbd753 --- /dev/null +++ b/src/components/community/playground/playground.css.ts @@ -0,0 +1,72 @@ +import { style, styleVariants } from '@vanilla-extract/css'; +import { COLORS } from '@/components/community/playground/common/colorToken'; + +const BREAKPOINTS = [500] as const; +const MEDIA_QUERY = { + pc: `screen and (min-width: ${BREAKPOINTS[0]}px)`, + mobile: `screen and (max-width: ${BREAKPOINTS[0]}px)`, +} as const; + +/** + * 개발자 성향 테스트에서 단독으로 사용하는 콘테이너 스타일 + */ +export const devtestPage = style({ + padding: '0', + margin: '0', + background: `linear-gradient( + 135deg, + ${COLORS.SSU.Blue}70, + ${COLORS.SSU.SkyBlue}50 + )`, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + +/** + * 공통 wrapper의 base style + */ +const wrapperbase = style({ + width: '31.2vw', + minHeight: '100vh', + height: '100vh', + overflowX: `hidden`, + '@media': { + [MEDIA_QUERY.mobile]: { + width: '100vw', + height: `calc(var(--var, 1vh) * 100)`, + }, + [MEDIA_QUERY.pc]: { + minWidth: '500px', + boxShadow: + 'rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px', + }, + }, +}); + +/** + * 개발자 성향테스트 / 오늘의 포춘쿠키 공통 Wrapper + * devtest & fortune + */ +export const wrapper = styleVariants({ + devtest: [ + wrapperbase, + { + background: `linear-gradient( + 135deg, + ${COLORS.SSU.Blue}, + ${COLORS.SSU.SkyBlue} + )`, + }, + ], + fortune: [ + wrapperbase, + { + background: `linear-gradient( + 135deg, + ${COLORS.SSU.Blue}, + ${COLORS.SSU.SkyBlue} + )`, + }, + ], +}); diff --git a/src/hooks/useInterval.tsx b/src/hooks/useInterval.tsx new file mode 100644 index 0000000..3b6c122 --- /dev/null +++ b/src/hooks/useInterval.tsx @@ -0,0 +1,26 @@ +import { useEffect, useRef } from 'react'; + +type IntervalFunction = () => unknown | void; + +/** + * 리렌더링으로 인한 setInterval 오작동 방지를 위한 useInterval Hooks + */ +export function useInterval(callback: IntervalFunction, delay: number | null) { + const savedCallback = useRef(null); + + useEffect(() => { + if (delay === null) return; + savedCallback.current = callback; + }); + + useEffect(() => { + if (delay === null) return; + function tick() { + if (savedCallback.current !== null) { + savedCallback.current(); + } + } + const id = setInterval(tick, delay); + return () => clearInterval(id); + }, [delay]); +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index da826ed..09e0882 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,4 +1,5 @@ import type { AppProps } from 'next/app'; +import './globals.css'; export default function App({ Component, pageProps }: AppProps) { return ; diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx new file mode 100644 index 0000000..966d332 --- /dev/null +++ b/src/pages/_document.tsx @@ -0,0 +1,23 @@ +import Document, { Html, Head, Main, NextScript } from "next/document"; + +class MyDocument extends Document { + render() { + return ( + + + + + +
+ + + + ); + } +} + +export default MyDocument; diff --git a/src/pages/community/playground/devtest.tsx b/src/pages/community/playground/devtest.tsx new file mode 100644 index 0000000..acb7099 --- /dev/null +++ b/src/pages/community/playground/devtest.tsx @@ -0,0 +1,37 @@ +import { PlayPage, StartPage, ResultPage } from '@/components/community/index'; +import { useState } from 'react'; +import * as styles from '@/components/community/playground/playground.css'; +import { DevType } from '@/resources/devTestQustions'; + +const STAGES = [StartPage, PlayPage, ResultPage] as const; + +/** + * 개발자 성향테스트 페이지 최상위 Wrapper + */ +const DevTestPage = () => { + const [stage, setStage] = useState(0); + const [result, setResult] = useState(); + const CurrentPage = STAGES[stage]; + + const goToNextStep = () => { + setStage((prev) => prev + 1); + }; + + const saveResult = (res: DevType) => { + setResult(res); + }; + + return ( +
+
+ +
+
+ ); +}; + +export default DevTestPage; diff --git a/src/pages/globals.css b/src/pages/globals.css new file mode 100644 index 0000000..02aa436 --- /dev/null +++ b/src/pages/globals.css @@ -0,0 +1,30 @@ +:root { + --vh: 100%; +} + +html, +body { + padding: 0; + margin: 0; + font-family: 'SUIT Variable', sans-serif; + background-color: #0a0a0a; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } + body { + color: white; + background: black; + } +} diff --git a/src/resources/assets/artist.svg b/src/resources/assets/artist.svg new file mode 100644 index 0000000..90d8494 --- /dev/null +++ b/src/resources/assets/artist.svg @@ -0,0 +1 @@ +makeup artist \ No newline at end of file diff --git a/src/resources/assets/fighter.svg b/src/resources/assets/fighter.svg new file mode 100644 index 0000000..8a958d5 --- /dev/null +++ b/src/resources/assets/fighter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/assets/fox.svg b/src/resources/assets/fox.svg new file mode 100644 index 0000000..589e896 --- /dev/null +++ b/src/resources/assets/fox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/assets/imagine.svg b/src/resources/assets/imagine.svg new file mode 100644 index 0000000..0337c0b --- /dev/null +++ b/src/resources/assets/imagine.svg @@ -0,0 +1 @@ +flowers \ No newline at end of file diff --git a/src/resources/assets/logo.svg b/src/resources/assets/logo.svg new file mode 100644 index 0000000..3c55051 --- /dev/null +++ b/src/resources/assets/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/resources/assets/planner.svg b/src/resources/assets/planner.svg new file mode 100644 index 0000000..d8133b9 --- /dev/null +++ b/src/resources/assets/planner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/assets/vitamin.svg b/src/resources/assets/vitamin.svg new file mode 100644 index 0000000..bac7124 --- /dev/null +++ b/src/resources/assets/vitamin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/communityRes.tsx b/src/resources/communityRes.tsx new file mode 100644 index 0000000..4c21353 --- /dev/null +++ b/src/resources/communityRes.tsx @@ -0,0 +1,7 @@ +/** 임시 이미지 리소스 -- 커뮤니티팀 + * 임시로 제(이준규) S3버킷에 업로드해서 사용하고 있어요. + * 이미지 업로드 필요하면 말씀해주세요! + */ + +export const devTestLogo = + 'https://horosocular.s3.ap-northeast-1.amazonaws.com/res1.svg'; diff --git a/src/resources/devTestQustions.tsx b/src/resources/devTestQustions.tsx new file mode 100644 index 0000000..46aa94b --- /dev/null +++ b/src/resources/devTestQustions.tsx @@ -0,0 +1,411 @@ +import vitamin from './assets/vitamin.svg'; +import artist from './assets/artist.svg'; +import planner from './assets/planner.svg'; +import fox from './assets/fox.svg'; +import fighter from './assets/fighter.svg'; +import imagine from './assets/imagine.svg'; + +export const images = [vitamin, artist, planner, fox, fighter, imagine]; + +export enum Part { + WM = 'Web/Mobile', + SC = 'Server/Cloud', + AM = 'AI/ML', +} + +export enum Dev { + Vitamin, + Artist, + Planner, + Fox, + Fighter, + Imagine, +} + +export interface DevType { + name: Dev; + title: string; + subtitle: string; + type: string; + part: Part; + content: Array; +} + +export interface Questions { + question: string; + select: Array; +} + +export interface Answer { + type: Array; + answer: string; +} + +export const vitaminDev: DevType = { + name: Dev.Vitamin, + title: '놀기 위해 개발하는거 아니겠어?', + subtitle: '회식이 제일 좋아! 노는게 제일 좋아!', + type: '분위기 메이커 비타민 개발자', + part: Part.WM, + content: [ + '어디서든 인기만점인 당신은 비타민 같아요!', + '팀 프로젝트가 너무 좋아요! 항상 설레요!', + '아주 열심히 일하지만, 나에게 1순위는 언제나 행복이에요.', + '어둡고 칙칙한 것은 싫어. 생기있는게 좋아요.', + '친구가 힘들바엔, 그냥 내가 힘든게 나아요. 버스기사 장인!', + '나의 지친 모습은 아무에게도 보여주기 싫어요!', + ], +}; + +export const artistDev: DevType = { + name: Dev.Artist, + title: '소피, 아름답지 않다면 살아도 산게 아니야.', + subtitle: '섬세함과 완벽함은 나의 원동력!', + type: '하울 스타일의 예술가 개발자', + part: Part.WM, + content: [ + '조용해 보이지만, 주변에 사람이 끊이질 않아요.', + '디자인, 코드, 로그... 모두 깔끔하고 완벽해야해요.', + '자기 계발을 위해 끊임없이 노력해요.', + '순간의 소모적인 즐거움보단 미래까지 생각하는 편이에요!', + '리더십이 짱짱하대요. 나는 모르겠지만 항상 팀플 조장이에요.', + '나의 목표는 육각형 인재! 뭐든 잘 하고 싶어요.', + ], +}; + +export const plannerDev: DevType = { + name: Dev.Planner, + title: '잠깐, 이건 내 계획에 없었는데...? 죽을게.', + subtitle: '뭐든지 철저한 계획은 기본!', + type: '인간 플래너 그 자체 개발자', + part: Part.SC, + content: [ + '가만히 있는 것 처럼 보여도, 머리는 쉬지 않아요.', + '프로젝트 기획하고, 계획 짜는게 가장 재미있어요!', + '인생 계획을 이루기 위해서 항상 노력하고 있어요.', + '코드가 이상해도, 디자인이 별로여도 괜찮아요. 대신 기한은 지켜라...', + '팀 리더가 되기 싫어요. 가뜩이나 생각할거 많은데!', + '빠르게 변하는 유행은 나의 적이에요.', + ], +}; + +export const foxDev: DevType = { + name: Dev.Fox, + title: '검은 화면에 코드를 적어내려가는 모습.. 멋있지? 반했지?', + subtitle: '스타벅스에서 개발하는 모습 보여주면 다 나한테 반하더라...', + type: '인간 퐉스 개발자', + part: Part.SC, + content: [ + '사람들이 무슨 생각하고 있는지 다 보이는걸요?', + '무엇을 해야 사람들이 날 좋아하는지 알고있어요.', + '머리가 비범한 편이라, 남들보다 적게 공부하고 많이 배워요.', + '팀 리더? 귀찮지만 꽤 재밌어요.', + '매력이 흘러 넘쳐요. 주변에는 항상 사람이 바글바글!', + '느리고 비효율적인 것들을 싫어해요.', + ], +}; + +export const fighterDev: DevType = { + name: Dev.Fighter, + title: '잠깐 나 궁금한거 있어, 아 아니 싸우자는거 아니야!', + subtitle: '데브톡 얼마나 재밌게요~', + type: '인간 100분 토론 개발자', + part: Part.AM, + content: [ + '하나에 집중하면 그것만 보여요. 아무것도 안들려요!', + '뭔가에 꽂히기 전 까지는 일상이 지루해요.', + '나와 다른 생각이 너무 궁금해요! 듣고 배우고 싶어요.', + '더 완벽한 결과를 위해서라면 어려운 공부도 Easy해져요.', + '느리고, 비효율적이어도 괜찮아요. 분명 배우는게 있거든요.', + '남들의 칭찬보다는 나의 만족이 중요한 완벽주의자예요.', + ], +}; + +export const imagineDev: DevType = { + name: Dev.Imagine, + title: '(개발자 성향 테스트하다가 갑자기 지구멸망하면 어떡하지...?)', + subtitle: '일상에서 상상하는 시간을 빼보라구요?', + type: '그럼 하루가 없어지는 개발자', + part: Part.AM, + content: [ + '이런 저런 상상이 너무 즐겁고 재밌어요.', + '상상하다가 밤 잠을 설치기도 해요.', + '그래도 때로는 상상에서 솔루션을 찾아내기도 해요.', + '깊게 생각할 수 있는 여지를 주는 것들이 좋아요.', + '한 번 읽고나면, 혼자 곱씹어보다가 완벽하게 이해해버려요.', + '이미 수백번 상상해봤기에 설명도 기가막히게 잘해요!', + ], +}; + +export const results: Array = [ + vitaminDev, + artistDev, + plannerDev, + foxDev, + fighterDev, + imagineDev, +]; + +export const questions: Array = [ + { + question: + '바늘 구멍같은 취업시장에서 나는 승리했다...\n오늘은 첫 출근! 나는 귀요미 신입 개발자!\n드디어 회사 입구에 도착했다.', + select: [ + { + type: [vitaminDev, imagineDev, artistDev], + answer: '두근두근, 어떤 사람들이 았을까? 너무 기대돼!', + }, + { + type: [foxDev, vitaminDev, artistDev], + answer: '내가 얼마나 뛰어난 개발자인지 알려줘야겠군.', + }, + { + type: [imagineDev, plannerDev, fighterDev], + answer: '일단 들어가서 인사를...\n아 괜히 말실수하는거 아냐? 개떨려...', + }, + { + type: [foxDev, plannerDev, fighterDev], + answer: '1년만 경력 쌓고 나가야지.\n나는 더 높은 곳에 있어야해.', + }, + ], + }, + { + question: + '신입끼리 모여서 진행하는 온보딩 교육.\n내 옆에 있는 이 사람이 나의 동기인가보다.\n들떠있는건지 나한테 이것저것 물어보네?', + select: [ + { + type: [vitaminDev, plannerDev, fighterDev], + answer: + '반가워요! 서로 도와주면서 잘 지내봐요!\n(좋은 사람인 것 같다!)', + }, + { + type: [artistDev, plannerDev, foxDev], + answer: '반가워요! 서로 도와주면서 잘 지내봐요^^\n(말좀 그만 걸어)', + }, + { + type: [artistDev, foxDev, imagineDev], + answer: '아~ 동작구 사시는구나. 아~ 숭실대 나오셨구나. 아~', + }, + { + type: [vitaminDev, fighterDev, imagineDev], + answer: '아진짜요?\n갑자기생각난건데제가LA에있을때말이죠...', + }, + ], + }, + { + question: + '헉 앞으로 일하게 될 팀을 배정해준다고 한다.\n내가 들어갈 팀은 이랬으면 좋겠는데?!', + select: [ + { + type: [vitaminDev, fighterDev, imagineDev], + answer: '성과가 뭐가 중요해. 난 무조건 사람 냄새 나는 팀!', + }, + { + type: [artistDev, plannerDev, foxDev], + answer: '회사에서 무슨 인간관계야.. 성과 좋고 철저한 팀.', + }, + { + type: [plannerDev, foxDev, fighterDev], + answer: '난 내가 주인공이면 돼. 내가 활약할 수 있는 팀.', + }, + { + type: [vitaminDev, artistDev, imagineDev], + answer: '인생은 끝 없는 공부! 배울 점이 많은 사람들이 있는 팀!', + }, + ], + }, + { + question: + '긴장해서 그런가?\n배고파서 죽을 것만 같다.\n2시가 다 되어가는데 왜 아무도 밥을 안먹지...?', + select: [ + { + type: [vitaminDev, imagineDev, plannerDev], + answer: '(끙... 오늘은 물만 마셔야겠다.)', + }, + { + type: [artistDev, foxDev, fighterDev], + answer: '저 혹시, 저희 식사는 알아서 하고 오면 됩니까?', + }, + { + type: [vitaminDev, plannerDev, foxDev], + answer: '(화장실 가서 초코바 먹고와야겠다. 념념굿)', + }, + { + type: [artistDev, fighterDev, imagineDev], + answer: '(회사 리뷰 사이트 뒤져보는 중...)', + }, + ], + }, + { + question: + '빡세다곤 들었지만...\n첫 날부터 과제라니!\n어...? 과제의 상태가...', + select: [ + { + type: [foxDev, fighterDev, imagineDev], + answer: '가이드라인 빡세네... 난 자유로운게 좋은데.', + }, + { + type: [artistDev, plannerDev, imagineDev], + answer: '자유롭게 해결해보라고...? 장난치는 것도 아니고...', + }, + { + type: [vitaminDev, artistDev, foxDev], + answer: '쉽네ㅋㅋ', + }, + { + type: [vitaminDev, plannerDev, fighterDev], + answer: '동기랑 같이 이야기하면서 해결해봐야지!\n카톡을 해봐야겠다.', + }, + ], + }, + { + question: + '오후 업무를 받았다.\n엥? 나 개발자로 들어온거 아니었나...?\n난생 처음보는 업무를 받았다.', + select: [ + { + type: [vitaminDev, artistDev, plannerDev], + answer: '그래도 일단 받았으니, 할 수 있을 만큼은 해야지.', + }, + { + type: [vitaminDev, plannerDev, imagineDev], + answer: '선배님, 제 업무가 맞는지 확인 부탁드려도 될까요?\n(제발...)', + }, + { + type: [artistDev, foxDev, fighterDev], + answer: '선배님, 제 업무가 맞는지 확인 부탁드려도 될까요?\n(장난하냐?)', + }, + { + type: [foxDev, fighterDev, imagineDev], + answer: '내 업무 아니니까 대충해서 내야지.', + }, + ], + }, + { + question: + '주변 사람들에게 물어보니, 일단 외우라고 한다.\n관련 자료도 받긴 받았는데...', + select: [ + { + type: [vitaminDev, fighterDev, imagineDev], + answer: '자료까지 받으니까 힘이 나네. 열심히 외워보자!', + }, + { + type: [artistDev, plannerDev, fighterDev], + answer: '이걸 왜 나한테...? 진지하게 도망갈까...', + }, + { + type: [vitaminDev, artistDev, foxDev], + answer: '하라면 못할 줄 알고? 이딴 잡무 1시간 안에 끝내간다.', + }, + { + type: [plannerDev, foxDev, imagineDev], + answer: '블라인드를 켠다...', + }, + ], + }, + { + question: + '벌써 팀 프로젝트에 들어간다고?\n뭐하는 회사지... 쨌든 입사 후 첫 팀플!', + select: [ + { + type: [vitaminDev, foxDev, fighterDev], + answer: '너무 설렌다... 이번에 얼마나 성장할까?', + }, + { + type: [artistDev, plannerDev, imagineDev], + answer: '아니 적응하고 공부할 시간은 달라고', + }, + { + type: [vitaminDev, artistDev, foxDev], + answer: '드디어 내 능력을 보여줄 시간이다.', + }, + { + type: [plannerDev, fighterDev, imagineDev], + answer: '(오만가지 상상 중)\n팀플하다 싸우진 않겠지...', + }, + ], + }, + { + question: '용기내서 낸 나의 의견!\n그리고 이어지는 팀원들의 지적...', + select: [ + { + type: [vitaminDev, plannerDev, imagineDev], + answer: '후잉... 죄송합니다...', + }, + { + type: [artistDev, foxDev, fighterDev], + answer: '오호 그 점을 역이용해서 보완해보는건 어떨까요?', + }, + { + type: [artistDev, foxDev, fighterDev], + answer: '아 네~ (뭐래 내 말이 맞구만;)', + }, + { + type: [vitaminDev, plannerDev, imagineDev], + answer: '그쵸! 별로죠?! 저도 별로라고 생각했어요!(ㅠㅠ)', + }, + ], + }, + { + question: '오 아침에 봤던 동기다!\n잠깐... 동기가 선배에게 혼나고 있는데?', + select: [ + { + type: [vitaminDev, artistDev, imagineDev], + answer: '괜찮아... 원래 그런게 사회생활이래. 힘내자!', + }, + { + type: [artistDev, foxDev, fighterDev], + answer: '그럼 이따 가서 그걸 이렇게 한 번 해봐.', + }, + { + type: [plannerDev, foxDev, imagineDev], + answer: '이따 술이나 한잔 할래?', + }, + { + type: [vitaminDev, plannerDev, fighterDev], + answer: '엥 너네 선배 진짜 이상하네 말을 왜 그따구로 해?', + }, + ], + }, + { + question: '어우 첫 날이라 그런가?\n시간이 너무 빨라! 슬슬 퇴근 시간이네...', + select: [ + { + type: [vitaminDev, plannerDev, imagineDev], + answer: '오늘의 나,, 너무 고생 많았다⭐', + }, + { + type: [artistDev, plannerDev, fighterDev], + answer: '내일은 아침에 과제를 절반하고, 잡무 해결한 다음...', + }, + { + type: [vitaminDev, foxDev, imagineDev], + answer: '놀다갈까?', + }, + { + type: [artistDev, foxDev, fighterDev], + answer: '근데 생각해보니까 갑자기 빡치네 아까...', + }, + ], + }, + { + question: '앗 아니나 다를까..\n첫 날이니 회식을 하자고 한다.', + select: [ + { + type: [vitaminDev, artistDev, fighterDev], + answer: '오옹!!! 기대하던 회식!!! 너무 좋아!!!', + }, + { + type: [artistDev, plannerDev, imagineDev], + answer: '아... 네! 좋아요 ~... ㅎㅎ...', + }, + { + type: [vitaminDev, foxDev, fighterDev], + answer: '아니? 내가 먼저 회식 하자고 말한다.', + }, + { + type: [plannerDev, foxDev, imagineDev], + answer: '앗 정말 죄송합니다 지금 급한 일이 있어서 (없지롱ㅋㅋ)', + }, + ], + }, +]; diff --git a/tsconfig.json b/tsconfig.json index b14e1e7..c678212 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,9 @@ "@/logics/*": ["src/logics/*"], "@/constants/*": ["src/constants/*"], "@/assets/*": ["src/assets/*"], - "@/lib/*": ["lib/*"] + "@/hooks/*": ["src/hooks/*"], + "@/lib/*": ["lib/*"], + "@/resources/*": ["src/resources/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], diff --git a/yarn.lock b/yarn.lock index 4ff601e..ed47771 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2739,6 +2739,11 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== +"@types/dom-to-image@^2.6.4": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@types/dom-to-image/-/dom-to-image-2.6.4.tgz#008411e23903cb0ee9e51a42ab8358c609541ee8" + integrity sha512-UddUdGF1qulrSDulkz3K2Ypq527MR6ixlgAzqLbxSiQ0icx0XDlIV+h4+edmjq/1dqn0KgN0xGSe1kI9t+vGuw== + "@types/eslint-scope@^3.7.3": version "3.7.4" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" @@ -2765,6 +2770,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/file-saver@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.5.tgz#9ee342a5d1314bb0928375424a2f162f97c310c7" + integrity sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ== + "@types/glob@*": version "8.0.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.0.0.tgz#321607e9cbaec54f687a0792b2d1d370739455d2" @@ -5177,6 +5187,11 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-to-image@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/dom-to-image/-/dom-to-image-2.6.0.tgz#8a503608088c87b1c22f9034ae032e1898955867" + integrity sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA== + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -5944,6 +5959,11 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + file-system-cache@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.1.0.tgz#984de17b976b75a77a27e08d6828137c1aa80fa1" @@ -11093,8 +11113,10 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: + chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" + watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1"