Skip to content

Commit 6e0ef18

Browse files
authored
Feature/#41 - 아이콘 컴포넌트 추가 (#42)
* feat: 아이콘 컴포넌트 추가 * feat: 아이콘 적용
1 parent 07ac3b0 commit 6e0ef18

File tree

11 files changed

+331
-205
lines changed

11 files changed

+331
-205
lines changed
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import { useToast } from "@gotchai/ui";
1+
import { Icon, useToast } from "@gotchai/ui";
22
import { cloneElement, ReactElement } from "react";
33

44
import { useClipboard } from "../hooks/useClipboard";
55

66
interface CopyableProps {
7-
children: ReactElement<{ onClick: () => void }>;
8-
text: string;
7+
children: ReactElement<{ onClick: () => void }>;
8+
text: string;
99
}
1010

1111
export const Copyable = ({ children, text }: CopyableProps) => {
12-
const { showToast } = useToast();
13-
const { copy } = useClipboard({
14-
onSuccess: () => {
15-
showToast({
16-
data: {
17-
message: "링크를 복사했어요",
18-
},
19-
});
20-
},
21-
});
12+
const { showToast } = useToast();
13+
const { copy } = useClipboard({
14+
onSuccess: () => {
15+
showToast({
16+
data: {
17+
icon: <Icon.Check />,
18+
message: "링크를 복사했어요",
19+
},
20+
});
21+
},
22+
});
2223

23-
return cloneElement(children, {
24-
onClick: () => copy(text),
25-
});
24+
return cloneElement(children, {
25+
onClick: () => copy(text),
26+
});
2627
};

apps/web/src/components/Logo.tsx

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
1-
import { SVGProps } from "react";
1+
import type { SVGProps } from "react";
22

33
export const Logo = ({
4-
fill = "currentColor",
5-
className,
6-
...props
4+
fill = "currentColor",
5+
className,
6+
...props
77
}: SVGProps<SVGSVGElement>) => {
8-
return (
9-
<svg
10-
fill={fill}
11-
xmlns="http://www.w3.org/2000/svg"
12-
className={className}
13-
{...props}
14-
>
15-
<g clipPath="url(#prefix__clip0_636_2726)">
16-
<path d="M9.244 10.23c-.22.288-.493.54-.815.752-.325.213-.67.391-1.042.534a6.458 6.458 0 01-2.252.424 4.502 4.502 0 01-3.359-1.43 4.84 4.84 0 01-.974-1.575 5.356 5.356 0 01-.35-1.95 6.923 6.923 0 01.68-2.999c.218-.45.486-.866.8-1.24.314-.378.67-.704 1.07-.977a4.959 4.959 0 011.31-.636C4.783.982 5.29.906 5.83.906c.203 0 .418.008.647.025.228.016.457.045.686.089.228.04.449.1.665.172.215.073.413.165.592.27l-.198 2.791a3.374 3.374 0 00-.813-.207 6.131 6.131 0 00-.82-.054c-.375 0-.725.059-1.053.178a2.422 2.422 0 00-1.426 1.332c-.14.32-.21.685-.21 1.09 0 .253.034.498.099.733.065.237.166.445.298.629.133.183.302.329.5.442.2.113.43.167.693.167.179 0 .363-.024.553-.073.19-.048.353-.132.488-.248l.029-.569-1.689-.03.086-2.192c.699-.025 1.398-.046 2.096-.062.7-.017 1.403-.04 2.112-.07l.084 4.908-.006.003zM18.648 6.564c0 .442-.05.871-.148 1.281a5.133 5.133 0 01-.426 1.157 4.74 4.74 0 01-.676.99c-.265.3-.564.555-.893.771-.33.213-.691.38-1.081.502a4.23 4.23 0 01-1.24.178c-.436 0-.823-.057-1.207-.167a4.536 4.536 0 01-1.073-.472 4.34 4.34 0 01-.904-.731 4.53 4.53 0 01-.688-.95 5.062 5.062 0 01-.445-1.121 4.818 4.818 0 01-.158-1.25c0-.433.05-.838.148-1.245a5.11 5.11 0 01.426-1.152c.184-.361.41-.69.673-.992.262-.302.556-.561.883-.782a4.225 4.225 0 011.06-.513 3.862 3.862 0 011.203-.183c.68 0 1.301.11 1.862.329a4.01 4.01 0 011.437.938c.397.408.704.898.922 1.476.218.577.327 1.221.327 1.933l-.002.003zm-3.125.192c0-.21-.029-.413-.089-.61a1.692 1.692 0 00-.257-.526 1.311 1.311 0 00-.418-.372 1.145 1.145 0 00-.574-.143c-.216 0-.418.04-.592.124a1.375 1.375 0 00-.447.34 1.535 1.535 0 00-.286.504 1.895 1.895 0 00-.099.61c0 .213.029.41.086.615.057.205.14.388.252.555.112.167.252.3.418.402.167.103.361.154.58.154.218 0 .418-.046.594-.135.177-.089.325-.213.447-.364a1.67 1.67 0 00.28-.529c.066-.2.1-.407.1-.62l.005-.005zM26.575 1.273l-.07 3.1-2.096.088-.535 6.926-2.97.162.014-6.926-2.097.116.086-3.45 7.671-.013-.003-.003zM33.792 1.449l-.395 3.082a2.727 2.727 0 00-.408-.08 4.127 4.127 0 00-.407-.022c-.349 0-.678.048-.993.143-.314.094-.592.24-.834.434a2.1 2.1 0 00-.576.73 2.322 2.322 0 00-.216 1.031c0 .254.04.472.117.658a1.245 1.245 0 00.826.731c.192.057.403.09.634.09.174 0 .353-.02.543-.06a4.863 4.863 0 001.091-.372c.174-.084.332-.168.478-.257l-.28 3.434a4.354 4.354 0 01-.596.258c-.213.076-.433.14-.657.197-.226.057-.45.1-.675.127-.226.03-.442.043-.647.043-.595 0-1.15-.113-1.668-.342a4.149 4.149 0 01-1.343-.941 4.43 4.43 0 01-.896-1.414 4.656 4.656 0 01-.327-1.755c0-.834.114-1.619.345-2.358a5.83 5.83 0 011.013-1.939 4.787 4.787 0 011.658-1.316c.66-.32 1.418-.483 2.275-.483.325 0 .655.027.995.081.34.054.655.154.946.3h-.003zM42.588 11.722l-3.518.175-.028-3.374h-1.619l-.098 3.199h-3.151l.267-10.273 3.349.162-.21 5.245h1.56l.014-5.64 3.208.09.226 10.418v-.002zM52.581 11.093l-3.49.467-.424-1.651H47.06l-.35 1.65-3.588-.364 2.8-9.73 3.913-.205 2.743 9.833h.002zm-4.164-3.42l-.535-2.543-.52 2.543h1.055zM53.363 3.235c.143.143.31.254.496.337.187.084.387.122.6.119.216-.005.416-.051.6-.143a1.66 1.66 0 00.483-.359c.138-.148.245-.323.322-.523.078-.2.115-.41.11-.628a1.644 1.644 0 00-.133-.623 1.656 1.656 0 00-.34-.502 1.552 1.552 0 00-.496-.334 1.494 1.494 0 00-1.683.378c-.138.148-.247.32-.325.514-.08.195-.117.405-.115.629.006.218.05.426.138.623.088.197.203.367.345.51l-.002.002zM53.373 4.336l-.194 7.63 3.374-.356-.782-7.325-2.398.051z" />
17-
</g>
18-
<defs>
19-
<clipPath id="prefix__clip0_636_2726">
20-
<path
21-
fill="#fff"
22-
transform="translate(.448 .465)"
23-
d="M0 0h56.105v11.5H0z"
24-
/>
25-
</clipPath>
26-
</defs>
27-
</svg>
28-
);
8+
return (
9+
<svg
10+
fill={fill}
11+
xmlns="http://www.w3.org/2000/svg"
12+
className={className}
13+
{...props}
14+
>
15+
<title>Logo</title>
16+
<g clipPath="url(#prefix__clip0_636_2726)">
17+
<path d="M9.244 10.23c-.22.288-.493.54-.815.752-.325.213-.67.391-1.042.534a6.458 6.458 0 01-2.252.424 4.502 4.502 0 01-3.359-1.43 4.84 4.84 0 01-.974-1.575 5.356 5.356 0 01-.35-1.95 6.923 6.923 0 01.68-2.999c.218-.45.486-.866.8-1.24.314-.378.67-.704 1.07-.977a4.959 4.959 0 011.31-.636C4.783.982 5.29.906 5.83.906c.203 0 .418.008.647.025.228.016.457.045.686.089.228.04.449.1.665.172.215.073.413.165.592.27l-.198 2.791a3.374 3.374 0 00-.813-.207 6.131 6.131 0 00-.82-.054c-.375 0-.725.059-1.053.178a2.422 2.422 0 00-1.426 1.332c-.14.32-.21.685-.21 1.09 0 .253.034.498.099.733.065.237.166.445.298.629.133.183.302.329.5.442.2.113.43.167.693.167.179 0 .363-.024.553-.073.19-.048.353-.132.488-.248l.029-.569-1.689-.03.086-2.192c.699-.025 1.398-.046 2.096-.062.7-.017 1.403-.04 2.112-.07l.084 4.908-.006.003zM18.648 6.564c0 .442-.05.871-.148 1.281a5.133 5.133 0 01-.426 1.157 4.74 4.74 0 01-.676.99c-.265.3-.564.555-.893.771-.33.213-.691.38-1.081.502a4.23 4.23 0 01-1.24.178c-.436 0-.823-.057-1.207-.167a4.536 4.536 0 01-1.073-.472 4.34 4.34 0 01-.904-.731 4.53 4.53 0 01-.688-.95 5.062 5.062 0 01-.445-1.121 4.818 4.818 0 01-.158-1.25c0-.433.05-.838.148-1.245a5.11 5.11 0 01.426-1.152c.184-.361.41-.69.673-.992.262-.302.556-.561.883-.782a4.225 4.225 0 011.06-.513 3.862 3.862 0 011.203-.183c.68 0 1.301.11 1.862.329a4.01 4.01 0 011.437.938c.397.408.704.898.922 1.476.218.577.327 1.221.327 1.933l-.002.003zm-3.125.192c0-.21-.029-.413-.089-.61a1.692 1.692 0 00-.257-.526 1.311 1.311 0 00-.418-.372 1.145 1.145 0 00-.574-.143c-.216 0-.418.04-.592.124a1.375 1.375 0 00-.447.34 1.535 1.535 0 00-.286.504 1.895 1.895 0 00-.099.61c0 .213.029.41.086.615.057.205.14.388.252.555.112.167.252.3.418.402.167.103.361.154.58.154.218 0 .418-.046.594-.135.177-.089.325-.213.447-.364a1.67 1.67 0 00.28-.529c.066-.2.1-.407.1-.62l.005-.005zM26.575 1.273l-.07 3.1-2.096.088-.535 6.926-2.97.162.014-6.926-2.097.116.086-3.45 7.671-.013-.003-.003zM33.792 1.449l-.395 3.082a2.727 2.727 0 00-.408-.08 4.127 4.127 0 00-.407-.022c-.349 0-.678.048-.993.143-.314.094-.592.24-.834.434a2.1 2.1 0 00-.576.73 2.322 2.322 0 00-.216 1.031c0 .254.04.472.117.658a1.245 1.245 0 00.826.731c.192.057.403.09.634.09.174 0 .353-.02.543-.06a4.863 4.863 0 001.091-.372c.174-.084.332-.168.478-.257l-.28 3.434a4.354 4.354 0 01-.596.258c-.213.076-.433.14-.657.197-.226.057-.45.1-.675.127-.226.03-.442.043-.647.043-.595 0-1.15-.113-1.668-.342a4.149 4.149 0 01-1.343-.941 4.43 4.43 0 01-.896-1.414 4.656 4.656 0 01-.327-1.755c0-.834.114-1.619.345-2.358a5.83 5.83 0 011.013-1.939 4.787 4.787 0 011.658-1.316c.66-.32 1.418-.483 2.275-.483.325 0 .655.027.995.081.34.054.655.154.946.3h-.003zM42.588 11.722l-3.518.175-.028-3.374h-1.619l-.098 3.199h-3.151l.267-10.273 3.349.162-.21 5.245h1.56l.014-5.64 3.208.09.226 10.418v-.002zM52.581 11.093l-3.49.467-.424-1.651H47.06l-.35 1.65-3.588-.364 2.8-9.73 3.913-.205 2.743 9.833h.002zm-4.164-3.42l-.535-2.543-.52 2.543h1.055zM53.363 3.235c.143.143.31.254.496.337.187.084.387.122.6.119.216-.005.416-.051.6-.143a1.66 1.66 0 00.483-.359c.138-.148.245-.323.322-.523.078-.2.115-.41.11-.628a1.644 1.644 0 00-.133-.623 1.656 1.656 0 00-.34-.502 1.552 1.552 0 00-.496-.334 1.494 1.494 0 00-1.683.378c-.138.148-.247.32-.325.514-.08.195-.117.405-.115.629.006.218.05.426.138.623.088.197.203.367.345.51l-.002.002zM53.373 4.336l-.194 7.63 3.374-.356-.782-7.325-2.398.051z" />
18+
</g>
19+
<defs>
20+
<clipPath id="prefix__clip0_636_2726">
21+
<path
22+
fill="#fff"
23+
transform="translate(.448 .465)"
24+
d="M0 0h56.105v11.5H0z"
25+
/>
26+
</clipPath>
27+
</defs>
28+
</svg>
29+
);
2930
};
3031

3132
export default Logo;

apps/web/src/pages/intro/index.tsx

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,69 @@
1-
import { Button, Text, Title } from "@gotchai/ui";
1+
import { Button, Icon, Text, Title } from "@gotchai/ui";
22
import { useNavigate } from "react-router-dom";
33

44
import { Copyable } from "../../components/Copyable";
55
import { Layout } from "../../components/Layout/Layout";
66
import {
7-
introContainer,
8-
introFooter,
9-
introImage,
10-
introMessage,
11-
introParticipant,
12-
introTitle,
7+
introContainer,
8+
introFooter,
9+
introImage,
10+
introMessage,
11+
introParticipant,
12+
introTitle,
1313
} from "./style.css";
1414
import { URLS } from "../../constants/urls";
1515

1616
export const IntroPage = () => {
17-
return (
18-
<Layout footer={<IntroFooter />}>
19-
<IntroContent />
20-
</Layout>
21-
);
17+
return (
18+
<Layout footer={<IntroFooter />}>
19+
<IntroContent />
20+
</Layout>
21+
);
2222
};
2323

2424
const IntroContent = () => {
25-
const navigate = useNavigate();
25+
const navigate = useNavigate();
2626

27-
return (
28-
<div className={introContainer}>
29-
<Title order={1} className={introTitle}>
30-
숨은 AI 찾기
31-
</Title>
32-
<img src="/assets/gotchai-intro.png" alt="intro" className={introImage} />
33-
<div>
34-
<Text size="lg" weight="medium" className={introMessage}>
35-
AI와 사람의 경계가 희미해진 시대에서 안녕하신가요?
36-
</Text>
37-
<Text size="lg" weight="medium" className={introMessage}>
38-
아무리 사람처럼 말한다고 해도
39-
<br />
40-
AI가 사람보다 마음을 더 잘 전달할 수는 없겠죠.
41-
</Text>
42-
<Text
43-
size="lg"
44-
weight="medium"
45-
style={{ marginTop: 8 }}
46-
className={introMessage}
47-
>
48-
그럼, 사람 사이에 숨은 AI를 찾으러 가 볼까요?
49-
</Text>
50-
</div>
51-
<div className={introFooter}>
52-
<Text className={introParticipant}>참여자 수 | 435명</Text>
53-
<Button variant="filled" onClick={() => navigate(URLS.TEST)}>
54-
나는 얼마나 AI를 잘 찾을까?
55-
</Button>
56-
</div>
57-
</div>
58-
);
27+
return (
28+
<div className={introContainer}>
29+
<Title order={1} className={introTitle}>
30+
숨은 AI 찾기
31+
</Title>
32+
<img src="/assets/gotchai-intro.png" alt="intro" className={introImage} />
33+
<div>
34+
<Text size="lg" weight="medium" className={introMessage}>
35+
AI와 사람의 경계가 희미해진 시대에서 안녕하신가요?
36+
</Text>
37+
<Text size="lg" weight="medium" className={introMessage}>
38+
아무리 사람처럼 말한다고 해도
39+
<br />
40+
AI가 사람보다 마음을 더 잘 전달할 수는 없겠죠.
41+
</Text>
42+
<Text
43+
size="lg"
44+
weight="medium"
45+
style={{ marginTop: 8 }}
46+
className={introMessage}
47+
>
48+
그럼, 사람 사이에 숨은 AI를 찾으러 가 볼까요?
49+
</Text>
50+
</div>
51+
<div className={introFooter}>
52+
<Text className={introParticipant}>참여자 수 | 435명</Text>
53+
<Button variant="filled" onClick={() => navigate(URLS.TEST)}>
54+
나는 얼마나 AI를 잘 찾을까?
55+
</Button>
56+
</div>
57+
</div>
58+
);
5959
};
6060

6161
const IntroFooter = () => {
62-
return (
63-
<Copyable text={window.location.href}>
64-
<Button variant="default">테스트 공유하기</Button>
65-
</Copyable>
66-
);
62+
return (
63+
<Copyable text={window.location.href}>
64+
<Button variant="default" leftItem={<Icon.Save width={22} height={22} />}>
65+
테스트 공유하기
66+
</Button>
67+
</Copyable>
68+
);
6769
};
Lines changed: 77 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, COLOR_VARS, Text, useToast } from "@gotchai/ui";
1+
import { Button, COLOR_VARS, Icon, Text, useToast } from "@gotchai/ui";
22
import { useNavigate, useSearchParams } from "react-router-dom";
33
import { useRef } from "react";
44
import html2canvas from "html2canvas";
@@ -10,87 +10,92 @@ import { container, downloadSection, buttonContainer } from "./style.css";
1010
import { URLS } from "../../constants/urls";
1111

1212
const getValidResult = (type: string | null): CardResult | null => {
13-
const validTypes = Object.values(CardResult).find((result) =>
14-
result.toLocaleLowerCase().includes(type?.toLocaleLowerCase() ?? "")
15-
);
13+
const validTypes = Object.values(CardResult).find((result) =>
14+
result.toLocaleLowerCase().includes(type?.toLocaleLowerCase() ?? ""),
15+
);
1616

17-
return validTypes ?? CardResult.BRONZE;
17+
return validTypes ?? CardResult.BRONZE;
1818
};
1919

2020
export const ResultPage = () => {
21-
const { showToast } = useToast();
22-
const navigate = useNavigate();
23-
const [searchParams] = useSearchParams();
24-
const resultCardRef = useRef<HTMLDivElement>(null);
21+
const { showToast } = useToast();
22+
const navigate = useNavigate();
23+
const [searchParams] = useSearchParams();
24+
const resultCardRef = useRef<HTMLDivElement>(null);
2525

26-
const result = getValidResult(searchParams.get("type"));
26+
const result = getValidResult(searchParams.get("type"));
2727

28-
const handleClickReTry = () => {
29-
navigate(URLS.MAIN);
30-
};
28+
const handleClickReTry = () => {
29+
navigate(URLS.MAIN);
30+
};
3131

32-
const handleClickDownload = async () => {
33-
if (!resultCardRef.current) return;
32+
const handleClickDownload = async () => {
33+
if (!resultCardRef.current) return;
3434

35-
try {
36-
const canvas = await html2canvas(resultCardRef.current, {
37-
scale: 2, // 고해상도
38-
useCORS: true, // 외부 이미지 허용
39-
allowTaint: true, // 외부 리소스 허용
40-
backgroundColor: null, // 배경 투명
41-
logging: false, // 로그 비활성화
42-
});
35+
try {
36+
const canvas = await html2canvas(resultCardRef.current, {
37+
scale: 2, // 고해상도
38+
useCORS: true, // 외부 이미지 허용
39+
allowTaint: true, // 외부 리소스 허용
40+
backgroundColor: null, // 배경 투명
41+
logging: false, // 로그 비활성화
42+
});
4343

44-
canvas.toBlob(async (blob) => {
45-
if (!blob) {
46-
showToast({
47-
data: {
48-
message: "이미지 변환에 실패했어요.",
49-
},
50-
});
51-
return;
52-
}
44+
canvas.toBlob(async (blob) => {
45+
if (!blob) {
46+
showToast({
47+
data: {
48+
message: "이미지 변환에 실패했어요.",
49+
},
50+
});
51+
return;
52+
}
5353

54-
try {
55-
// 클립보드에 이미지 저장
56-
const clipboardItem = new ClipboardItem({
57-
"image/png": blob,
58-
});
59-
await navigator.clipboard.write([clipboardItem]);
54+
try {
55+
// 클립보드에 이미지 저장
56+
const clipboardItem = new ClipboardItem({
57+
"image/png": blob,
58+
});
59+
await navigator.clipboard.write([clipboardItem]);
6060

61-
showToast({
62-
data: {
63-
message: "이미지를 저장했어요.",
64-
},
65-
});
66-
} catch (error) {
67-
console.error("클립보드 저장 실패:", error);
68-
}
69-
}, "image/png");
70-
} catch (error) {
71-
console.error("이미지 변환 실패:", error);
72-
}
73-
};
61+
showToast({
62+
data: {
63+
icon: <Icon.Check />,
64+
message: "이미지를 저장했어요.",
65+
},
66+
});
67+
} catch (error) {
68+
console.error("클립보드 저장 실패:", error);
69+
}
70+
}, "image/png");
71+
} catch (error) {
72+
console.error("이미지 변환 실패:", error);
73+
}
74+
};
7475

75-
return (
76-
<Layout>
77-
<div className={container}>
78-
<div ref={resultCardRef}>
79-
<ResultCard result={result as CardResult} />
80-
</div>
81-
<div className={downloadSection}>
82-
<Text color={COLOR_VARS.green[600]} size="lg" weight="medium">
83-
앱에서도 계속 퀴즈를 풀어볼까요?
84-
</Text>
85-
<Button variant="filled" disabled>
86-
1분만에 다운로드하기
87-
</Button>
88-
</div>
89-
<div className={buttonContainer}>
90-
<Button onClick={handleClickReTry}>다시하기</Button>
91-
<Button onClick={handleClickDownload}>결과 저장하기</Button>
92-
</div>
93-
</div>
94-
</Layout>
95-
);
76+
return (
77+
<Layout>
78+
<div className={container}>
79+
<div ref={resultCardRef}>
80+
<ResultCard result={result as CardResult} />
81+
</div>
82+
<div className={downloadSection}>
83+
<Text color={COLOR_VARS.green[600]} size="lg" weight="medium">
84+
앱에서도 계속 퀴즈를 풀어볼까요?
85+
</Text>
86+
<Button variant="filled" disabled>
87+
1분만에 다운로드하기
88+
</Button>
89+
</div>
90+
<div className={buttonContainer}>
91+
<Button onClick={handleClickReTry} leftItem={<Icon.Reset />}>
92+
다시하기
93+
</Button>
94+
<Button onClick={handleClickDownload} leftItem={<Icon.Save />}>
95+
결과 저장하기
96+
</Button>
97+
</div>
98+
</div>
99+
</Layout>
100+
);
96101
};

0 commit comments

Comments
 (0)