Skip to content
Snippets Groups Projects
Commit 68c318a3 authored by YU SUN's avatar YU SUN
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #7127 failed
Showing
with 618 additions and 0 deletions
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
\ No newline at end of file
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
package-lock.json
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# React PetShop - React.js로 만드는 애완용품 샵
- BootStrap 3을 사용합니다.
- [Vue.js in Action 의 프로젝트] (http://github.com/gilbutITbook/007024) 를 React.js로 변경한 코드입니다.
{
"name": "react-boilerplate",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^1.4.0",
"express": "^4.18.2",
"firebase": "^9.21.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.1"
},
"scripts": {
"start": "webpack-dev-server --open --mode development --port 3000",
"build": "webpack --mode production"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"css-loader": "^6.7.3",
"style-loader": "^3.3.2",
"file-loader": "^6.2.0",
"webpack": "^5.77.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1"
}
}
This diff is collapsed.
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
header h1 {
padding: 10px 20px;
}
body {
max-width: 970px;
}
.cart {
padding: 20px 50px;
}
.boxes {
margin-top: 20px;
}
.verify {
margin-top: 20px;
}
.submit {
margin-top: 20px;
float: right;
}
.pagecheckout {
padding: 20px 50px;
}
.description {
font-size: 150%;
margin-top: 50px;
}
public/assets/images/Mindy_Mouse_cat_toy.jpg

87.4 KiB

public/assets/images/cat-house.jpg

54.8 KiB

public/assets/images/cat-litter.jpg

647 KiB

public/assets/images/laser-pointer.jpg

1.43 MiB

public/assets/images/product-fullsize.png

88.1 KiB

public/assets/images/yarn.jpg

208 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous" >
<title>React PetShop</title>
</head>
<body>
<div id="root"></div>
<script src="./app.bundle.js"></script>
</body>
</html>
{
"products":[
{
"id": 1001,
"title": "고양이 사료, 25파운드",
"description": "당신의 고양이를 위한 <em>거부할 수 없는</em>, 유기농 25파운드 사료입니다.",
"price": 2000,
"image": "assets/images/product-fullsize.png",
"availableInventory": 10,
"rating": 1
},
{
"id": 1002,
"title": "실뭉치",
"description": "실뭉치로 당신의 고양이에게 <strong>오랜</strong> 놀이 시간을 주세요!",
"price": 299,
"image": "assets/images/yarn.jpg",
"availableInventory": 7,
"rating": 1
},
{
"id": 1003,
"title": "고양이 화장실",
"description": "당신의 고양이를 위한 최고급 화장실입니다.",
"price": 1100,
"image": "assets/images/cat-litter.jpg",
"availableInventory": 99,
"rating": 4
},
{
"id": 1004,
"title": "고양이 집",
"description": "고양이가 놀 수 있는 장소!",
"price": 799,
"image": "assets/images/cat-house.jpg",
"availableInventory": 11,
"rating": 5
},
{
"id": 1005,
"title": "레이저 포인터",
"description": "이 <em>놀라운</em> 상품으로 고양이와 놀아주세요.",
"price": 4999,
"image": "assets/images/laser-pointer.jpg",
"availableInventory": 25,
"rating": 1
}
]
}
const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();
const PORT = 3001;
app.use(express.json());
app.post("/products", (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
const product = req.body;
const productsFilePath = path.join(__dirname, "public/products.json");
fs.readFile(productsFilePath, "utf8", (err, data) => {
if (err) {
console.error(err);
res.status(500).json({ error: "写入文件时发生错误" });
return;
}
let products = [];
if (data) {
products = JSON.parse(data);
}
products.push(product);
fs.writeFile(productsFilePath, JSON.stringify(products), "utf8", (err) => {
if (err) {
console.error(err);
res.status(500).json({ error: "写入文件时发生错误" });
return;
}
res.json({ success: true });
});
});
});
app.listen(PORT, () => {
console.log(`服务器正在运行,端口号:${PORT}`);
});
import React, { useState, useEffect } from "react";
import "./assets/css/app.css";
import CheckoutPage from "./checkout";
import Products from "./products";
import Header from "./header";
import RegisterPage from "./register-product";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { collection, getDocs, addDoc } from "firebase/firestore";
import { db } from "./firebase";
const App = (props) => {
let [cartItems, setCartItems] = useState([]);
let [products, setProducts] = useState([]);
let [user, setUser] = useState(null);
const fetchData = async () => {
/* firestore security rule change :
allow read : if request.auth != null;
allow write : if request != null;
*/
const querySnapshot = await getDocs(collection(db, "products"));
let products = [];
querySnapshot.forEach((doc) => {
products.push(doc.data());
});
setProducts([...products]);
console.log(products);
};
useEffect(() => {
fetchData();
}, []);
const handleDeleteItem = (index) => {
const updatedCartItems = cartItems.filter((item, i) => i !== index);
setCartItems(updatedCartItems);
};
const DeleteAllItem = () => {
setCartItems([]);
};
const updateInventory = () => {
cartItems.forEach((item) => {
console.log("item" + item);
products.forEach((product) => {
console.log("product" + product);
if (item.id === product.id) product.availableInventory -= 1;
});
});
setCartItems([]);
};
const addProduct = async (product) => {
const docRef = await addDoc(collection(db, "products"), product);
console.log(`document id : ${docRef.id}`);
setProducts([...products, product]);
};
return (
<BrowserRouter>
<header>
<Header
name={props.sitename}
nItems={cartItems.length}
user={user}
onSignIn={setUser}
onSignOut={() => setUser(null)}
/>
</header>
<main>
<Routes>
<Route
path="/"
element={
<Products
products={products}
cart={cartItems}
onSetCart={setCartItems}
user={user}
/>
}
></Route>
<Route
path="/checkout"
element={
<CheckoutPage
cart={cartItems}
onDeleteItem={handleDeleteItem}
deleteAll={DeleteAllItem}
updateInventory={updateInventory}
/>
}
></Route>
<Route
path="/register-product"
element={
<RegisterPage
addProduct={addProduct}
/>
}
></Route>
</Routes>
</main>
</BrowserRouter>
);
};
export default App;
header h1 {
padding: 10px 20px;
}
body {
max-width: 970px;
}
label {
padding: 3px;
}
.cart {
padding: 20px 50px;
}
.boxes {
margin-top: 20px;
}
.verify {
margin-top: 20px;
}
.submit {
margin-top: 20px;
float: right;
}
.pagecheckout {
padding: 20px 50px;
}
.description {
font-size: 150%;
margin-top: 50px;
}
.inventory-message {
margin-left: 20px;
font-weight: bold;
font-size: 120%;
}
.product {
margin-top: 30px;
margin-left: 20px;
max-height: 300px;
max-width: 100%;
}
.figure {}
.rating-active:before {
content: "\2605";
position: absolute;
}
.rating {
display: inline;
margin-left: 10px;
margin-top: 10px;
float: right;
}
.rating>span {
display: inline-block;
position: relative;
width: 1.1em;
}
@media (min-width: 1200px) {
.container {
max-width: 970px;
}
}
\ No newline at end of file
src/assets/images/product-fullsize.png

88.1 KiB

import React, { useState, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
const CheckoutPage = (props) => {
let navigate = useNavigate();
let cartItems = props.cart;
useEffect(() => {
if (cartItems.length === 0) {
alert("상품을 먼저 구매하세요");
navigate(-1);
}
}, [cartItems, navigate]);
let [order, setOrder] = useState({
firstName: "",
lastName: "",
state: "CA",
address: "",
});
let states = ["AL", "AR", "CA", "NV", "NY", "FL"];
const updateOrder = (event) => {
let control = event.target;
if (control.name == "gift") {
setOrder({ ...order, gift: control.checked });
return;
}
setOrder({ ...order, [control.name]: control.value });
};
const total = cartItems.reduce((sum, item) => sum + item.price, 0);
return (
<div>
{/* 渲染购物车项 */}
{cartItems.map((item, index) => (
<div key={index} className="row">
<div className="col-md-3">{item.title}</div>
<div className="col-md-3">{item.price}</div>
<div className="col-md-3">1</div>
<div className="col-md-3">
<button
onClick={() => props.onDeleteItem(index)}
className="btn btn-danger"
>
Delete
</button>
</div>
</div>
))}
{/* 显示总价 */}
<div className="col-md-12">Total Price: {total}</div>
<div className="col-md-6">
<strong>이름</strong>
<input
className="form-control"
name="firstName"
value={order.firstName}
onChange={updateOrder}
/>
</div>
<div className="col-md-6">
<strong></strong>
<input
className="form-control"
name="lastName"
value={order.lastName}
onChange={updateOrder}
/>
</div>
<div className="form-group">
<div className="col-md-12">
{" "}
<strong>주소:</strong>
</div>
<div className="col-md-12">
<input
className="form-control"
name="address"
value={order.address}
onChange={updateOrder}
/>
</div>
</div>
<div className="form-group">
<div className="col-md-12">
{" "}
<strong>:</strong>
<select
className="form-control"
name="state"
value={order.state}
onChange={updateOrder}
>
{states.map((st) => (
<option value={st}> {st}</option>
))}
</select>
</div>
</div>
<div className="form-group">
<div className="col-md-6 boxes">
<input
type="checkbox"
name="gift"
id="gift"
value={true}
onChange={updateOrder}
/>
<label htmlFor="gift">선물로 보내기?</label>
</div>
</div>
<div className="form-group">
<div className="col-md-9 boxes">
<div className="col-md-3">
<input
key="radio-1"
type="radio"
name="method"
id="home"
value="자택"
defaultChecked={true}
onChange={updateOrder}
/>
<label htmlFor="home">자택</label>
</div>
<div className="col-md-3">
<input
key="radio-2"
type="radio"
name="method"
id="business"
value="직장"
onChange={updateOrder}
/>
<label htmlFor="business">직장</label>
</div>
</div>
</div>
<div className="form-group">
<div className="col-md-12">
<Link to={"/"}>
<button
className="btn btn-lg btn-primary"
onClick={() => {
props.deleteAll();
}}
>
취소
</button>
</Link>
<Link to={"/"}>
<button
onClick={() => {
props.updateInventory();
}}
className="btn btn-primary btn-lg"
>
제출
</button>
</Link>
</div>
</div>
<div className="col-md-12 verify">
<pre>
이름 : {order.firstName}
<br /> : {order.lastName}
<br />
주소 : {order.address}
<br /> : {order.state}
<br />
배송지: {order.method}
<br />
선물 : {order.gift ? "선물" : "선물아님"}
</pre>
</div>
</div>
);
};
export default CheckoutPage;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment