├── .eslintcache
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── public
├── favicon.ico
├── images
│ └── snow.svg
├── index.html
├── manifest.json
└── robots.txt
├── src
├── index.css
├── index.tsx
├── pages
│ ├── GeneratePage.tsx
│ ├── HomePage.tsx
│ └── LetterPage.tsx
├── react-app-env.d.ts
├── reportWebVitals.ts
├── routes
│ └── index.tsx
└── setupTests.ts
├── tsconfig.json
└── yarn.lock
/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/index.tsx":"1","/Users/suhdonghwi/Documents/projects/letter-to-santa/src/pages/HomePage.tsx":"2","/Users/suhdonghwi/Documents/projects/letter-to-santa/src/reportWebVitals.ts":"3","/Users/suhdonghwi/Documents/projects/letter-to-santa/src/routes/index.tsx":"4","/Users/suhdonghwi/Documents/projects/letter-to-santa/src/pages/GeneratePage.tsx":"5","/Users/suhdonghwi/Documents/projects/letter-to-santa/src/pages/LetterPage.tsx":"6"},{"size":1030,"mtime":1608390144275,"results":"7","hashOfConfig":"8"},{"size":1667,"mtime":1608408200091,"results":"9","hashOfConfig":"8"},{"size":425,"mtime":1608370283739,"results":"10","hashOfConfig":"8"},{"size":582,"mtime":1608387956754,"results":"11","hashOfConfig":"8"},{"size":4360,"mtime":1608395605772,"results":"12","hashOfConfig":"8"},{"size":5508,"mtime":1608391894568,"results":"13","hashOfConfig":"8"},{"filePath":"14","messages":"15","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1fakstz",{"filePath":"16","messages":"17","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/index.tsx",[],"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/pages/HomePage.tsx",[],"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/reportWebVitals.ts",[],"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/routes/index.tsx",[],"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/pages/GeneratePage.tsx",[],"/Users/suhdonghwi/Documents/projects/letter-to-santa/src/pages/LetterPage.tsx",[]]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 서동휘
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 산타 할아버지께 편지 🎅
2 |
3 | 아이들이 온라인으로 산타에게 가지고 싶은 선물들을 적어놓은 편지를 전송하면 미리 등록해둔 이메일로
4 | 해당 내용이 전송되는, 착한 아이들을 위한 피싱 사이트입니다.
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "letter-to-santa",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "@types/jest": "^26.0.15",
10 | "@types/node": "^12.0.0",
11 | "@types/react": "^16.9.53",
12 | "@types/react-dom": "^16.9.8",
13 | "@types/react-router-dom": "^5.1.6",
14 | "@types/styled-components": "^5.1.7",
15 | "dayjs": "^1.9.7",
16 | "emailjs-com": "^2.6.4",
17 | "firebase": "^8.2.1",
18 | "react": "^17.0.1",
19 | "react-dom": "^17.0.1",
20 | "react-router-dom": "^5.2.0",
21 | "react-scripts": "4.0.1",
22 | "react-snowfall": "^1.0.2",
23 | "styled-components": "^5.2.1",
24 | "sweetalert2": "^10.12.5",
25 | "typescript": "^4.0.3",
26 | "web-vitals": "^0.2.4"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject"
33 | },
34 | "eslintConfig": {
35 | "extends": [
36 | "react-app",
37 | "react-app/jest"
38 | ]
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/suhdonghwi/letter-to-santa/ed874cee542250d8ccabe5da845e5e145ef667e8/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/snow.svg:
--------------------------------------------------------------------------------
1 |
114 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | 산타 할아버지께 편지
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Letter To Santa",
3 | "name": "Letter To Santa",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#20c997",
14 | "background_color": "#f1f3f5"
15 | }
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'NanumBarunpen';
3 | src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_two@1.0/NanumBarunpen.woff') format('woff');
4 | font-weight: normal;
5 | font-style: normal;
6 | }
7 |
8 | html, body, #root {
9 | font-family: 'NanumBarunpen';
10 |
11 | height: 100%;
12 | margin: 0;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import reportWebVitals from "./reportWebVitals";
5 | import firebase from "firebase";
6 |
7 | import Root from "./routes";
8 | import { init } from "emailjs-com";
9 |
10 | const firebaseConfig = {
11 | apiKey: "AIzaSyBcEy7ZESIHxNlSVcPLtMat3TSoPjWJHzI",
12 | authDomain: "letter-to-santa-61efd.firebaseapp.com",
13 | projectId: "letter-to-santa-61efd",
14 | storageBucket: "letter-to-santa-61efd.appspot.com",
15 | messagingSenderId: "1082826458249",
16 | appId: "1:1082826458249:web:b4cf47d13705cd8776b258",
17 | measurementId: "G-8XMJXTNVCF",
18 | };
19 |
20 | firebase.initializeApp(firebaseConfig);
21 | firebase.analytics();
22 |
23 | init("user_tbeVQ3Dgxtdq1fCYiQ2OB");
24 |
25 | ReactDOM.render(
26 |
27 |
28 | ,
29 | document.getElementById("root")
30 | );
31 |
32 | // If you want to start measuring performance in your app, pass a function
33 | // to log results (for example: reportWebVitals(console.log))
34 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
35 | reportWebVitals();
36 |
--------------------------------------------------------------------------------
/src/pages/GeneratePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components/macro";
3 | import Swal from "sweetalert2";
4 | import firebase from "firebase";
5 |
6 | const Main = styled.main`
7 | min-height: 100%;
8 |
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: center;
13 |
14 | padding: 3rem 2rem;
15 | box-sizing: border-box;
16 | `;
17 |
18 | const Title = styled.h1`
19 | font-family: "NanumBarunpen";
20 | font-size: 3rem;
21 | color: #212529;
22 |
23 | margin: 0.5rem 0;
24 |
25 | @media screen and (max-width: 560px) {
26 | font-size: 1.7rem;
27 | }
28 | `;
29 |
30 | const Description = styled.p`
31 | font-family: "NanumBarunpen";
32 | font-size: 1.4rem;
33 |
34 | color: #868e96;
35 | max-width: 40rem;
36 | margin: 2rem 0 5rem 0;
37 |
38 | @media screen and (max-width: 560px) {
39 | font-size: 1rem;
40 | margin: 1.5rem 0 3rem 0;
41 | }
42 | `;
43 |
44 | const Form = styled.form`
45 | width: 17rem;
46 | `;
47 |
48 | const Label = styled.label`
49 | display: block;
50 |
51 | font-family: "NanumBarunpen";
52 | color: #212529;
53 | font-size: 1.1rem;
54 |
55 | margin-bottom: 0.5rem;
56 | `;
57 |
58 | const Input = styled.input`
59 | background-color: #e9ecef;
60 | border: none;
61 | border-radius: 10px;
62 |
63 | font-family: "NanumBarunpen";
64 | font-size: 1.2rem;
65 | padding: 0.6rem 0.7rem;
66 |
67 | width: 100%;
68 | box-sizing: border-box;
69 |
70 | margin-bottom: 2rem;
71 | `;
72 |
73 | const Submit = styled.input`
74 | cursor: pointer;
75 |
76 | font-family: "NanumBarunpen";
77 |
78 | appearance: none;
79 | background-color: #12b886;
80 |
81 | border: none;
82 | border-radius: 10px;
83 |
84 | padding: 0.5rem 1rem;
85 | font-size: 1.1rem;
86 | color: white;
87 | width: 100%;
88 |
89 | &.loading {
90 | background-color: #adb5bd;
91 | }
92 | `;
93 |
94 | function generateKey(existingKeys: string[]) {
95 | let result: string;
96 | const characters =
97 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
98 |
99 | do {
100 | result = "";
101 | for (let i = 0; i < 5; i++) {
102 | result += characters.charAt(
103 | Math.floor(Math.random() * characters.length)
104 | );
105 | }
106 | } while (existingKeys.includes(result));
107 |
108 | return result;
109 | }
110 |
111 | export default function GeneratePage() {
112 | const [name, setName] = useState("");
113 | const [email, setEmail] = useState("");
114 | const [loading, setLoading] = useState(false);
115 |
116 | async function onSubmit(event: React.FormEvent) {
117 | event.preventDefault();
118 | if (loading) return;
119 |
120 | let trimmedName = name.trim();
121 | let trimmedEmail = email.trim();
122 |
123 | if (trimmedName.length === 0) {
124 | Swal.fire({
125 | icon: "error",
126 | title: "다시 확인해주세요!",
127 | text: "이름을 입력해주세요.",
128 | heightAuto: false,
129 | });
130 | return;
131 | }
132 |
133 | if (trimmedEmail.length === 0) {
134 | Swal.fire({
135 | icon: "error",
136 | title: "다시 확인해주세요!",
137 | text: "이메일을 입력해주세요.",
138 | heightAuto: false,
139 | });
140 | return;
141 | }
142 |
143 | setLoading(true);
144 |
145 | const dataSnapshot = await firebase.database().ref("data").once("value");
146 | const key = generateKey(Object.keys(dataSnapshot.val()));
147 |
148 | await firebase
149 | .database()
150 | .ref(`data/${key}`)
151 | .set({ name: trimmedName, email: trimmedEmail });
152 |
153 | setLoading(false);
154 | setName("");
155 | setEmail("");
156 |
157 | const link = "https://official.christmas/" + key;
158 | Swal.fire({
159 | icon: "success",
160 | title: "성공!",
161 | html: `생성된 링크 : ${link}
위 링크를 복사해서 아이에게 보내주세요.`,
162 | heightAuto: false,
163 | });
164 | }
165 |
166 | return (
167 |
168 | 산타 할아버지께 편지 🎄
169 |
170 | 아이들이 받고 싶은 크리스마스 선물들을 편지에 써서 전송하면 미리
171 | 등록해두신 이메일 주소로 전송됩니다. 아이들의 크리스마스 선물 구입에
172 | 참고해보시고, 행복한 연말 보내세요!
173 |
174 |
175 |
197 |
198 | );
199 | }
200 |
--------------------------------------------------------------------------------
/src/pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components/macro";
3 | import dayjs from "dayjs";
4 | import Snowfall from "react-snowfall";
5 |
6 | const Main = styled.main`
7 | height: 100%;
8 |
9 | padding: 0 1rem;
10 | box-sizing: border-box;
11 |
12 | background-image: url("./images/snow.svg");
13 | background-repeat: no-repeat;
14 | background-position: center bottom;
15 | background-size: auto 50vh;
16 | background-color: #f1f3f5;
17 |
18 | @media screen and (max-height: 600px) {
19 | background: none;
20 | }
21 | `;
22 |
23 | const ContentBox = styled.div`
24 | display: flex;
25 | flex-direction: column;
26 | align-items: center;
27 | justify-content: center;
28 |
29 | height: 50vh;
30 |
31 | @media screen and (max-height: 600px) {
32 | height: 100%;
33 | }
34 | `;
35 |
36 | const CheerMessage = styled.p`
37 | font-family: "NanumBarunpen";
38 | font-size: 2rem;
39 |
40 | color: #495057;
41 |
42 | @media screen and (max-width: 560px) {
43 | font-size: 1.4rem;
44 | margin: 0.2rem 0;
45 | }
46 |
47 | @media screen and (max-height: 600px) {
48 | margin: 0.2rem 0;
49 | }
50 | `;
51 |
52 | const DDayText = styled.p`
53 | font-family: "NanumBarunpen";
54 | font-size: 7rem;
55 | margin: 0;
56 | `;
57 |
58 | const ToChristmas = styled.small`
59 | font-family: "NanumBarunpen";
60 | font-size: 1.5rem;
61 | color: #868e96;
62 | `;
63 |
64 | export default function HomePage() {
65 | const dday = Math.floor(dayjs().diff(dayjs("2020-12-25"), "day", true));
66 | const ddayString =
67 | dday === 0 ? "-DAY" : dday < 0 ? dday.toString() : "+" + dday;
68 |
69 | return (
70 |
71 |
72 |
73 | 조금 특별했던 2020, 수고 많으셨습니다! 🎄
74 | D{ddayString}
75 | 크리스마스까지
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/src/pages/LetterPage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import styled from "styled-components/macro";
3 | import { RouteComponentProps, useHistory } from "react-router-dom";
4 | import firebase from "firebase";
5 | import Swal from "sweetalert2";
6 | import emailjs from "emailjs-com";
7 |
8 | const Main = styled.main`
9 | min-height: 100%;
10 |
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: center;
15 |
16 | padding: 3rem 2rem;
17 | box-sizing: border-box;
18 | `;
19 |
20 | const LoadingText = styled.p`
21 | font-size: 1.5rem;
22 | `;
23 |
24 | const Title = styled.h1`
25 | font-family: "NanumBarunpen";
26 | font-size: 3rem;
27 | color: #212529;
28 |
29 | margin: 0.5rem 0;
30 |
31 | @media screen and (max-width: 560px) {
32 | font-size: 1.7rem;
33 | }
34 | `;
35 |
36 | const Description = styled.p`
37 | font-family: "NanumBarunpen";
38 | font-size: 1.4rem;
39 |
40 | color: #868e96;
41 | max-width: 40rem;
42 | margin: 2rem 0 3rem 0;
43 |
44 | @media screen and (max-width: 560px) {
45 | font-size: 1rem;
46 | margin: 1.5rem 0 3rem 0;
47 | }
48 | `;
49 |
50 | const Form = styled.div`
51 | width: 30rem;
52 |
53 | @media screen and (max-width: 560px) {
54 | width: 20rem;
55 | }
56 |
57 | @media screen and (max-width: 350px) {
58 | width: 16rem;
59 | }
60 | `;
61 |
62 | const Label = styled.label`
63 | display: block;
64 |
65 | font-family: "NanumBarunpen";
66 | color: #212529;
67 | font-size: 1.1rem;
68 |
69 | margin-bottom: 0.5rem;
70 | `;
71 |
72 | const LetterInput = styled.textarea`
73 | background-color: #f1f3f5;
74 | border: none;
75 | border-radius: 10px;
76 |
77 | font-family: "NanumBarunpen";
78 | font-size: 1.2rem;
79 | padding: 1rem;
80 |
81 | width: 100%;
82 | box-sizing: border-box;
83 |
84 | margin-bottom: 2rem;
85 | resize: vertical;
86 | outline: none;
87 | `;
88 |
89 | const YesNo = styled.div`
90 | display: flex;
91 | margin-top: 1rem;
92 | `;
93 |
94 | const YesNoButton = styled.button<{ color: string }>`
95 | cursor: pointer;
96 | outline: none;
97 |
98 | flex: 1;
99 |
100 | background-color: ${(props) => props.color};
101 | appearance: none;
102 |
103 | padding: 0.5rem 0;
104 | color: white;
105 |
106 | font-family: "NanumBarunpen";
107 | font-size: 1.2rem;
108 |
109 | border: none;
110 | border-radius: 10px;
111 |
112 | margin: 0 0.5rem;
113 | `;
114 |
115 | export default function LetterPage({
116 | match,
117 | }: RouteComponentProps<{ key: string }>) {
118 | const [loading, setLoading] = useState(true);
119 | const [sending, setSending] = useState(false);
120 |
121 | const [content, setContent] = useState("");
122 | const [name, setName] = useState("");
123 | const [email, setEmail] = useState("");
124 |
125 | const key = match.params.key;
126 | const history = useHistory();
127 |
128 | useEffect(() => {
129 | async function fetch() {
130 | const keySnapshot = await firebase.database().ref("data").once("value");
131 | if (!keySnapshot.hasChild(key)) {
132 | history.push("/");
133 | return;
134 | }
135 |
136 | const dataSnapshot = await firebase
137 | .database()
138 | .ref(`data/${key}`)
139 | .once("value");
140 | const data = dataSnapshot.val();
141 |
142 | if (data.sent) {
143 | history.push("/");
144 | return;
145 | }
146 |
147 | setName(data.name);
148 | setEmail(data.email);
149 | setLoading(false);
150 | }
151 |
152 | fetch();
153 | });
154 |
155 | async function onYes() {
156 | if (sending) return;
157 | if (content.trim().length < 10) {
158 | Swal.fire({
159 | icon: "error",
160 | title: "오류",
161 | text: "편지 내용이 너무 짧아요! 조금 더 입력해주세요.",
162 | heightAuto: false,
163 | });
164 | return;
165 | }
166 |
167 | const params = {
168 | from_name: name,
169 | to_email: email,
170 | message: content,
171 | };
172 |
173 | try {
174 | setSending(true);
175 | await emailjs.send("service_axuu45f", "template_fl5v0ub", params);
176 | await firebase.database().ref(`data/${key}/sent`).set(true);
177 | await Swal.fire({
178 | icon: "success",
179 | title: "성공!",
180 | text:
181 | "산타 할아버지께 편지를 보냈어요! 크리스마스까지 잘 기다릴 수 있죠?",
182 | heightAuto: false,
183 | });
184 |
185 | history.push("/");
186 | } catch (err) {
187 | console.log(err);
188 | Swal.fire({
189 | icon: "error",
190 | title: "오류",
191 | text: "편지를 보내기에 실패했어요. 이따가 다시 써주세요!",
192 | heightAuto: false,
193 | });
194 |
195 | setSending(false);
196 | }
197 | }
198 |
199 | function onNo() {
200 | if (sending) return;
201 | Swal.fire({
202 | icon: "error",
203 | title: "이런..",
204 | text: "산타 할아버지께서는 착한 아이에게만 소중한 선물을 준답니다.",
205 | heightAuto: false,
206 | });
207 | }
208 |
209 | return (
210 |
211 | {loading ? (
212 | 로딩중 ...
213 | ) : (
214 | <>
215 | 산타 할아버지께 편지 🎄
216 |
217 | {name} 친구! 올해는 산타 할아버지께 어떤 선물을 받고 싶나요? 산타
218 | 할아버지께 쓰고 싶은 말과 함께 적어보아요! 정성스럽게 적을수록 산타
219 | 할아버지께서 좋아하실 거예요!
220 |
221 |
245 | >
246 | )}
247 |
248 | );
249 | }
250 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom";
3 |
4 | import HomePage from "../pages/HomePage";
5 | import GeneratePage from "../pages/GeneratePage";
6 | import LetterPage from "../pages/LetterPage";
7 |
8 | export default function Root() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------