diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..222861c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/package-lock.json b/package-lock.json index 1afe296..824dd50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@actions/github": "^6.0.0", "@slack/webhook": "^7.0.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "styled-components": "^6.1.8" }, "devDependencies": { "@types/react": "^18.2.64", @@ -22,6 +23,7 @@ "eslint-plugin-react": "^7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "prettier": "^3.2.5", "vite": "^5.1.6" } }, @@ -1151,6 +1153,17 @@ "@types/react": "*" } }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -1454,6 +1467,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/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1519,6 +1540,24 @@ "node": ">= 8" } }, + "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-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/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3013,7 +3052,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -3247,8 +3285,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -3287,6 +3324,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3296,6 +3338,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3601,6 +3658,11 @@ "node": ">= 0.4" } }, + "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", @@ -3748,6 +3810,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 9eb20ae..48ea321 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "@actions/github": "^6.0.0", "@slack/webhook": "^7.0.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "styled-components": "^6.1.8" }, "devDependencies": { "@types/react": "^18.2.64", @@ -24,6 +25,7 @@ "eslint-plugin-react": "^7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "prettier": "^3.2.5", "vite": "^5.1.6" } } diff --git a/public/api/products.json b/public/api/products.json new file mode 100644 index 0000000..7483f89 --- /dev/null +++ b/public/api/products.json @@ -0,0 +1,44 @@ +[ + { + "id": 1, + "src": "https://img.freepik.com/free-photo/jeans_1203-8092.jpg", + "name": "무시무시한바지", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 2, + "src": "https://img.freepik.com/free-psd/jerzees-pullover-hooded-sweatshirt-mockup-01_126278-94.jpg?w=1480&t=st=1712066977~exp=1712067577~hmac=e2134a114b35532277ab0c2b76ae698527139f0b46b345c0caa0e9aa3d66cd33", + "name": "후드티", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 3, + "src": "https://img.freepik.com/free-photo/basic-white-tee-women-s-apparel-rear-view_53876-108033.jpg?t=st=1712067014~exp=1712070614~hmac=7f176e8ed6876ac7593cb0bf2cc392ede4372858b1fec4bb3e585546753bea02&w=1380", + "name": "반팔 티", + "price": "200000₩", + "isSale": "false" + }, + { + "id": 4, + "src": "https://img.freepik.com/free-photo/background-textile-protection-back-object_1203-6405.jpg?t=st=1712067043~exp=1712070643~hmac=30c53d8fcb2e8a6d516b952a2f0b1df44ee66af6ec82856ec8f0e676db9b5c8e&w=1060", + "name": "볼 캡 (야구모자)", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 5, + "src": "https://img.freepik.com/free-photo/still-life-rendering-jackets-display_23-2149745031.jpg?t=st=1712067097~exp=1712070697~hmac=2a020243be6e84e0541f2292493bb00dd13ea328e50021ceff86fa567e5fab95&w=2000", + "name": "가죽 자켓", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 6, + "src": "https://img.freepik.com/free-vector/all-types-black-white-socks-sports-every-day-realistic-set-isolated-vector-illustration_1284-73137.jpg?t=st=1712067118~exp=1712070718~hmac=d5bc8f8b274ebd67d98d0bfa02db9e530818eb49b18a7ed13fa3751f26737dae&w=2000", + "name": "양말", + "price": "200000₩", + "isSale": "false" + } +] diff --git a/public/api/sale_products.json b/public/api/sale_products.json new file mode 100644 index 0000000..8a76cb9 --- /dev/null +++ b/public/api/sale_products.json @@ -0,0 +1,30 @@ +[ + { + "id": 1, + "src": "https://img.freepik.com/free-photo/jeans_1203-8092.jpg", + "name": "무시무시한바지", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 2, + "src": "https://img.freepik.com/free-psd/jerzees-pullover-hooded-sweatshirt-mockup-01_126278-94.jpg?w=1480&t=st=1712066977~exp=1712067577~hmac=e2134a114b35532277ab0c2b76ae698527139f0b46b345c0caa0e9aa3d66cd33", + "name": "후드티", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 4, + "src": "https://img.freepik.com/free-photo/background-textile-protection-back-object_1203-6405.jpg?t=st=1712067043~exp=1712070643~hmac=30c53d8fcb2e8a6d516b952a2f0b1df44ee66af6ec82856ec8f0e676db9b5c8e&w=1060", + "name": "볼 캡 (야구모자)", + "price": "500000₩", + "isSale": "true" + }, + { + "id": 5, + "src": "https://img.freepik.com/free-photo/still-life-rendering-jackets-display_23-2149745031.jpg?t=st=1712067097~exp=1712070697~hmac=2a020243be6e84e0541f2292493bb00dd13ea328e50021ceff86fa567e5fab95&w=2000", + "name": "가죽 자켓", + "price": "500000₩", + "isSale": "true" + } +] diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index b8b8473..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.jsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} - -export default App diff --git a/src/components/Intro.jsx b/src/components/Intro.jsx new file mode 100644 index 0000000..45876d5 --- /dev/null +++ b/src/components/Intro.jsx @@ -0,0 +1,40 @@ +import styled from "styled-components"; + +export default function Intro() { + return ( + +

ECNV

+
+

Welcome to ECNV, the best place to buy your favorite items.

+

Here is a list of items you can buy:

+
+
+ ); +} + +const IntroContainer = styled.div` + background-color: rgb(248, 250, 252); + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + + background: url("https://cdn.pixabay.com/photo/2016/04/03/11/36/berlin-1304370_1280.jpg"); + background-repeat: no-repeat; + background-size: cover; + + h1 { + font-size: 8rem; + color: rgb(2, 6, 23); + background-color: white; + --tw-bg-opacity: 0.35; + opacity: 35%; + padding-inline: 4rem; + } + div { + margin-top: 5rem; + background-color: #ffffff; + } +`; diff --git a/src/components/ItemFeed.jsx b/src/components/ItemFeed.jsx new file mode 100644 index 0000000..6c2a552 --- /dev/null +++ b/src/components/ItemFeed.jsx @@ -0,0 +1,25 @@ +import styled from "styled-components"; + +export default function ItemFeed({ id, src, name, price }) { + return ( + + +
+ {name} + {price} +
+
+ ); +} + +const StyledList = styled.li` + width: 100%; + img { + width: 100%; + border-radius: 0.75rem; + } + div { + display: flex; + flex-direction: column; + } +`; diff --git a/src/components/Shop.jsx b/src/components/Shop.jsx new file mode 100644 index 0000000..b91cf2d --- /dev/null +++ b/src/components/Shop.jsx @@ -0,0 +1,34 @@ +import styled from "styled-components"; +import Intro from "./Intro"; +import ShoppingList from "./ShoppingList"; +import ToggleBtn from "./ToggleBtn"; +import { useEffect, useState } from "react"; + +export default function Shop() { + const [items, setItems] = useState([]); + const [isSale, setIsSale] = useState(false); + const url = !isSale ? "api/products.json" : "api/sale_products.json"; + const buttonText = !isSale ? "Show Sale product" : "All products"; + + useEffect(() => { + fetch(url) + .then((response) => response.json()) + .then((data) => setItems(data)); + }, [isSale]); + + const buttonHandler = () => setIsSale(!isSale); + + return ( +
+ + + +
+ ); +} + +const Main = styled.main` + display: flex; + flex-direction: column; + align-items: center; +`; diff --git a/src/components/ShoppingList.jsx b/src/components/ShoppingList.jsx new file mode 100644 index 0000000..c761272 --- /dev/null +++ b/src/components/ShoppingList.jsx @@ -0,0 +1,22 @@ +import styled from "styled-components"; +import ItemFeed from "./ItemFeed"; + +export default function ShoppingList({ items }) { + return ( + + {items.map((item) => ( + + ))} + + ); +} + +const StyledUl = styled.ul` + display: grid; + padding-left: 5rem; + padding-right: 5rem; + margin-top: 1rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6rem; + width: 100vw; +`; diff --git a/src/components/ToggleBtn.jsx b/src/components/ToggleBtn.jsx new file mode 100644 index 0000000..7bd9e80 --- /dev/null +++ b/src/components/ToggleBtn.jsx @@ -0,0 +1,18 @@ +import styled from "styled-components"; + +export default function ToggleBtn({ buttonText, buttonHandler }) { + return {buttonText}; +} + +const StyledBtn = styled.button` + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + padding-right: 1rem; + margin-top: 1.25rem; + margin-right: 5rem; + align-self: flex-end; + border-radius: 0.5rem; + background-color: #ffffff; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +`; diff --git a/src/index.css b/src/index.css index 6119ad9..82935ae 100644 --- a/src/index.css +++ b/src/index.css @@ -1,68 +1,124 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; } -a:hover { - color: #535bf2; +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; } - body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; + line-height: 1; } - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; +ol, +ul { + list-style: none; } -button:hover { - border-color: #646cff; +blockquote, +q { + quotes: none; } -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; } - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +table { + border-collapse: collapse; + border-spacing: 0; } diff --git a/src/main.jsx b/src/main.jsx index 54b39dd..1ebb73d 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,10 +1,5 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.jsx' -import './index.css' +import ReactDOM from "react-dom/client"; +import "./index.css"; +import Shop from "./components/Shop.jsx"; -ReactDOM.createRoot(document.getElementById('root')).render( - - - , -) +ReactDOM.createRoot(document.getElementById("root")).render();