diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0a72520 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/package-lock.json b/package-lock.json index eb6e658..2c41875 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-components": "^5.3.9", "web-vitals": "^2.1.4" } }, @@ -2131,6 +2132,29 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", @@ -5314,6 +5338,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -5627,6 +5671,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6058,6 +6110,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -6239,6 +6299,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -7789,6 +7859,16 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "optional": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -8604,6 +8684,13 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "node_modules/growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "optional": true, + "peer": true + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -8710,6 +8797,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -12183,6 +12283,21 @@ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, + "node_modules/node-notifier": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", + "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", + "optional": true, + "peer": true, + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.5", + "shellwords": "^0.1.1", + "uuid": "^8.3.2", + "which": "^2.0.2" + } + }, "node_modules/node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -15129,6 +15244,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15156,6 +15276,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "optional": true, + "peer": true + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -15197,6 +15324,36 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "optional": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://tidelift.com/funding/github/npm/sockjs-client" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "optional": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -15531,6 +15688,35 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16064,16 +16250,16 @@ } }, "node_modules/typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { @@ -18598,6 +18784,29 @@ "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", "requires": {} }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint-community/eslint-utils": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", @@ -20949,6 +21158,23 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -21193,6 +21419,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -21513,6 +21744,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -21620,6 +21856,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -22759,6 +23005,13 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, + "eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "optional": true, + "peer": true + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -23358,6 +23611,13 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "optional": true, + "peer": true + }, "gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -23425,6 +23685,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25946,6 +26221,21 @@ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, + "node-notifier": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", + "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", + "optional": true, + "peer": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.5", + "shellwords": "^0.1.1", + "uuid": "^8.3.2", + "which": "^2.0.2" + } + }, "node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -27883,6 +28173,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -27901,6 +28196,13 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==" }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "optional": true, + "peer": true + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -27936,6 +28238,32 @@ "websocket-driver": "^0.7.4" } }, + "sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "optional": true, + "peer": true, + "requires": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "optional": true, + "peer": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -28186,6 +28514,23 @@ "integrity": "sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==", "requires": {} }, + "styled-components": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -28588,9 +28933,9 @@ } }, "typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 101e3ed..1eb20f3 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-components": "^5.3.9", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/App.js b/src/App.js index 7c4c71a..2b50f7d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,107 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { Font } from './styles/font'; +import DateBox from './components/DateBox'; +import TodayGoal from './components/TodayGoal'; +import TodoInput from './components/TodoInput'; +import Tasks from './components/Tasks'; function App() { + const TODO = 0; + const DONE = 1; + const [tasks, setTasks] = useState({ + 12345678: { + id: '12345678', + state: TODO, + text: 'CEOS 1주차 과제 : JS로 Todo 구현하기', + }, + 12345679: { + id: '12345679', + state: TODO, + text: 'CEOS 2주차 과제 : React로 Todo 구현하기', + }, + }); + const _saveTasks = (tasks) => { + setTasks(tasks); + localStorage.setItem('tasks', tasks); + }; + + const _loadTasks = () => { + var tasks = JSON.parse(localStorage.getItem('tasks') || `{}`); // 저장된 tasks 정보 불러오기 + // 저장된 tasks가 없을 경우 비어있는 객체 불러올 수 있도록 + setTasks(tasks); + }; + const _toggleTask = (id) => { + var newTasks = { ...tasks }; + + newTasks[id].state = 1 - newTasks[id].state; // TODO <-> DONE 상태 전환 + console.log(newTasks); + _saveTasks(newTasks); + }; + + const _deleteTask = (id) => { + var newTasks = { ...tasks }; + delete newTasks[id]; //id가 key값인 객체 삭제 + _saveTasks(newTasks); + }; + const _addTask = (text) => { + if (text.trim() === '') { + return; + } + var newId = Date.now(); + + /* 변수의 value값을 JS의 key값으로 사용하는 방법 + https://koonsland.tistory.com/146 */ + + _saveTasks({ + ...tasks, + [newId]: { + id: [newId], + state: TODO, + text: text, + }, + }); + }; + useEffect(() => { + //_loadTasks(); + }, []); + + console.log(tasks); return ( -
-

17기 프론트 화이팅~ 우하하

-
+ + + + + + + + ); } +const Background = styled.div` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + height: 100%; + width: 100vw; +`; + +const MainBox = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + + padding: 5vh 5vw; + box-shadow: 0px 4px 4px rgba(45, 45, 45, 0.45); + width: 90vw; + height: 100vh; + + max-width: 600px; +`; -export default App; \ No newline at end of file +export default App; diff --git a/src/assets/images/check.svg b/src/assets/images/check.svg new file mode 100644 index 0000000..4936789 --- /dev/null +++ b/src/assets/images/check.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/cherry-blossom.svg b/src/assets/images/cherry-blossom.svg new file mode 100644 index 0000000..05ad8f9 --- /dev/null +++ b/src/assets/images/cherry-blossom.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/delete.svg b/src/assets/images/delete.svg new file mode 100644 index 0000000..121787e --- /dev/null +++ b/src/assets/images/delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/uncheck.svg b/src/assets/images/uncheck.svg new file mode 100644 index 0000000..6171d47 --- /dev/null +++ b/src/assets/images/uncheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/DateBox.js b/src/components/DateBox.js new file mode 100644 index 0000000..0c64023 --- /dev/null +++ b/src/components/DateBox.js @@ -0,0 +1,84 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { Font } from '../styles/font'; +export default function DateBox() { + let days = [ + 'SUNDAY', + 'MONDAY', + 'TUESDAY', + 'WEDNESDAY', + 'THURSDAY', + 'FRIDAY', + 'SATURDAY', + ]; + + let months = [ + '', + 'JANUARY', + 'FEBRUARY', + 'MARCH', + 'APRILL', + 'MAY', + 'JUNE', + 'JULY', + 'AUGUST', + 'SEMTEMBER', + 'OCTOBER', + 'NOBEMBER', + 'DESEMBER', + ]; + + const [currentMonthText, setCurrentMonthText] = useState('MARCH'); + const [currentMonth, setCurrentMonth] = useState(0); + const [currentDate, setCurrentDate] = useState(0); + const [currentDay, setCurrentDay] = useState('MON'); + + useEffect(() => { + let today = new Date(); + + setCurrentMonth(today.getMonth() + 1); + setCurrentDate(today.getDate()); + + let currentDayIdx = today.getDay(); + setCurrentDay(days[currentDayIdx]); + setCurrentMonthText(months[currentMonth]); + }, []); + return ( + + + {currentMonthText} + + + + {currentMonth}.{currentDate} + +      + + {currentDay} + + + + ); +} + +const Row = styled.div` + display: flex; + flex-direction: row; + justify-content: center; + align-content: center; +`; + +const Col = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-content: center; + margin: 10px; +`; +const HeadLine = styled(Font)` + letter-spacing: 3px; + display: flex; + flex-direction: row; + justify-content: center; + align-content: center; +`; diff --git a/src/components/Tasks.js b/src/components/Tasks.js new file mode 100644 index 0000000..163e998 --- /dev/null +++ b/src/components/Tasks.js @@ -0,0 +1,110 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Font } from '../styles/font'; +import CheckIconSrc from '../assets/images/check.svg'; +import DeleteIconSrc from '../assets/images/delete.svg'; +import UnCheckIconSrc from '../assets/images/uncheck.svg'; +export default function Tasks({ tasks, _toggleTask, _deleteTask }) { + return ( + + + TO DO + + {Object.values(tasks) + .filter((task) => task.state === 0) + .map(({ text, id }) => ( + + _toggleTask(id)} /> + {text} + _deleteTask(id)} /> + + ))} + + + + + DONE + + {' '} + {Object.values(tasks) + .filter((task) => task.state === 1) + .map(({ text, id }) => ( + + _toggleTask(id)} + width={'var(--font-size-sm)'} + /> + + {text} + _deleteTask(id)} + width={'var(--font-size-sm)'} + /> + + ))} + + + + ); +} +const TasksLayout = styled.div` + display: flex; + gap: 20px; + height: 100%; + width: 100%; + + @media (max-width: 767px) { + //모바일 + + flex-direction: column; + justify-content: space-between; + } + + @media (min-width: 1200px) { + // 데스크탑 일반 + flex-direction: row; + + justify-content: space-between; + } +`; + +const TaskContent = styled.ul` + display: flex; + flex-direction: column; + gap: 20px; + border: 3px solid var(--medium-pink); + width: 100%; + height: 100%; + padding: 10px; + overflow-y: scroll; + list-style: none; +`; + +const TaskContentItem = styled.div` + display: flex; + gap: 5px; +`; + +const TaskBox = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + @media (max-width: 767px) { + //모바일 + + height: 50%; + } + + @media (min-width: 1200px) { + // 데스크탑 일반 + height: 50vh; + } +`; + +const SvgIcon = styled.img` + cursor: pointer; +`; diff --git a/src/components/TodayGoal.js b/src/components/TodayGoal.js new file mode 100644 index 0000000..bc92fa1 --- /dev/null +++ b/src/components/TodayGoal.js @@ -0,0 +1,38 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Font } from '../styles/font'; +export default function TodayGoal() { + return ( + + + + + ); +} + +const Box = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-content: center; + background-color: var(--light-pink); + width: 100%; + height: 30%; + padding: 10px; +`; +/* 상속 받아서 사용하기 */ +const TextInput = styled.input` + border: none; + background: transparent; + height: 100%; + font-size: var(--font-size-md); +`; + +const Label = styled(Font)` + font-weight: 500; + letter-spacing: 3px; + + align-self: center; +`; diff --git a/src/components/TodoInput.js b/src/components/TodoInput.js new file mode 100644 index 0000000..1644b73 --- /dev/null +++ b/src/components/TodoInput.js @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import CherryBlossomSrc from '../assets/images/cherry-blossom.svg'; + +export default function TodoInput({ _addTask }) { + const [inputValue, setInputValue] = useState(''); + + const _submitTodo = () => { + _addTask(inputValue); + setInputValue(''); + }; + return ( + + setInputValue(e.target.value)} + /> + + + ); +} + +const Box = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-content: center; + margin-top: 50px; + margin-bottom: 30px; + width: 100%; +`; + +const TextInput = styled.input` + border: 3px solid #ffdfda; + width: 90%; + text-align: center; + ::placeholder { + color: var(--darken-pink); + letter-spacing: 3px; + font-family: sans-serif; + } +`; + +const SvgIcon = styled.img` + cursor: pointer; +`; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..1945e32 --- /dev/null +++ b/src/index.css @@ -0,0 +1,51 @@ +@import url('https://fonts.googleapis.com/css2?family=Lexend+Tera:wght@700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Lexend+Tera:wght@400;700&display=swap'); + +* { + box-sizing: border-box; + font-family: 'Lexend Tera', sans-serif; +} +:root { + font-size: 10px; + /* 반응형 작업을 위한 루트 폰트 크기 지정. -> 10px로 맞추면 편하다! + https://yoonjong-park.tistory.com/entry/%EB%B0%98%EC%9D%91%ED%98%95-%EC%9E%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%8F%B0%ED%8A%B8-%EB%8B%A8%EC%9C%84-em-rem-%EC%82%AC%EC%9A%A9 + */ + + --font-size-lg: clamp(3rem, 10vw, 7rem); /*최소 증감량 최대*/ + --font-size-ml: clamp(3rem, 10vw, 3.5rem); + --font-size-md: clamp(2rem, 4vw, 1.8rem); + --font-size-sm: clamp(0.5rem, 3vw, 1rem); + + /* 반응형 글꼴 디자인 + https://www.daleseo.com/css-responsive-typography/ */ + + --pink: #f8a6a2; + --light-pink: #faefed; + --medium-pink: #ffdfda; + --darken-pink: #e6b0b1; + + --gray: #6f6f6f; +} + +html, +body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + user-select: none; +} +::-webkit-scrollbar { + width: 8px; /* 스크롤바의 너비 */ +} + +::-webkit-scrollbar-thumb { + height: 30%; /* 스크롤바의 길이 */ + background: #f8a6a2; /* 스크롤바의 색상 */ + + border-radius: 10px; +} + +::-webkit-scrollbar-track { + background: rgba(33, 122, 244, 0.1); /*스크롤바 뒷 배경 색상*/ +} diff --git a/src/index.js b/src/index.js index 7173ce5..e17ef33 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; - -ReactDOM.render( - - - , - document.getElementById('root') -); \ No newline at end of file +import './index.css'; +ReactDOM.render(, document.getElementById('root')); diff --git a/src/styles/font.js b/src/styles/font.js new file mode 100644 index 0000000..c5436ad --- /dev/null +++ b/src/styles/font.js @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +const Font = styled.text` + font-size: ${({ fontSize }) => (fontSize ? fontSize : '1rem')}; + font-family: 'Lexend Tera', sans-serif; + color: ${({ color }) => (color ? color : 'black')}; +`; +export { Font };