Skip to content
Snippets Groups Projects
Commit e0050d6c authored by hongfeng wu's avatar hongfeng wu
Browse files

finished

parent 231a5f5a
No related branches found
No related tags found
No related merge requests found
Pipeline #7130 failed
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"firebase": "^9.21.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.11.1" "react-router-dom": "^6.11.1"
......
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import './assets/css/app.css'; import "./assets/css/app.css";
import CheckoutPage from './checkout'; import CheckoutPage from "./checkout";
import Products from './products'; import Products from "./products";
import Header from './header'; import Header from "./header";
import RegisterPage from "./RegisterProductPage";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { collection, getDocs, addDoc } from "firebase/firestore";
import { db } from "./firebase";
const App = (props) => { const App = (props) => {
let [cartItems, setCartItems] = useState([]); let [cartItems, setCartItems] = useState([]);
let [showProduct, setShowProduct] = useState(true); 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, "petshop"));
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, "petshop"), product);
console.log(`document id : ${docRef.id}`);
setProducts([...products, product]);
};
return ( return (
<> <BrowserRouter>
<header> <header>
<Header name={props.sitename} <Header
name={props.sitename}
nItems={cartItems.length} nItems={cartItems.length}
onToggleShowProduct={() => setShowProduct(!showProduct)} user={user}
onSignIn={setUser}
onSignOut={() => setUser(null)}
/> />
</header> </header>
<main> <main>
{showProduct && <Routes>
<Products cart={cartItems} onSetCart={setCartItems}/> <Route
path="/"
element={
<Products
products={products}
cart={cartItems}
onSetCart={setCartItems}
user={user}
/>
} }
{!showProduct && /* Checkout 페이지 들어갈 자리 */ ></Route>
<CheckoutPage /> <Route
path="/checkout"
element={
<CheckoutPage
cartItems={cartItems}
onDeleteItem={handleDeleteItem}
deleteAll={DeleteAllItem}
updateInventory={updateInventory}
/>
} }
></Route>
<Route
path="/register-product"
element={
<RegisterPage
addProduct={addProduct}
/>
}
></Route>
</Routes>
</main> </main>
</> </BrowserRouter>
); );
} };
export default App; export default App;
import React, { useState} from 'react';
import { Link, useNavigate } from 'react-router-dom';
const ProductRegistrationPage = (props) => {
const [product, setProduct] = useState({
title: '',
description: '',
image: '',
price: 0,
availableInventory:0,
});
let navigate = useNavigate();
const handleInputChange = (event) => {
const { name, value } = event.target;
setProduct({ ...product, [name]: value });
};
const handleSubmit = (event) => {
event.preventDefault();
console.log(product);
if (product.title.length < 1 || product.description.length < 3 || product.image.length < 3) {
alert('상품정보를 바르게 입력하세요');
return;
}
props.addProduct(product)
navigate("/");
};
return (
<div>
<h3>제품 등록</h3>
<form >
<div className="form-group">
<label htmlFor="title">상품 제목:</label>
<input
type="text"
id="title"
name="title"
className="form-control"
value={product.title}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="description">상품 설명:</label>
<textarea
id="description"
name="description"
className="form-control"
value={product.description}
onChange={handleInputChange}
required
></textarea>
</div>
<div className="form-group">
<label htmlFor="image">상품 이미지:</label>
<input
type="text"
id="image"
name="image"
className="form-control"
value={product.image}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="price">가격:</label>
<input
type="number"
id="price"
name="price"
className="form-control"
min="0"
value={product.price}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="availableInventory">재고량:</label>
<input
type="number"
id="availableInventory"
name="availableInventory"
className="form-control"
min="0"
value={product.availableInventory}
onChange={handleInputChange}
required
/>
</div>
<Link to="/">
<button type="button" className="btn btn-primary">취소
</button>
</Link>
<Link to="/">
<button type="submit" className="btn btn-primary" onClick={handleSubmit}>제출
</button>
</Link>
</form>
</div>
);
};
export default ProductRegistrationPage;
...@@ -74,3 +74,8 @@ label { ...@@ -74,3 +74,8 @@ label {
max-width: 970px; max-width: 970px;
} }
} }
.btn-add-product {
font-size: 50px;
padding: 5px 120px;
}
\ No newline at end of file
import React, {useState} from 'react'; import React, { useEffect, useState } from 'react';
import { Link,useNavigate } from 'react-router-dom';
const CheckoutPage = (props) => { const CheckoutPage = ({ products, cartItems, onSetCartItems, onSetProducts }) => {
const [order, setOrder] = useState({ firstName: '', lastName: '', state: 'CA', address: '', gift: false });
let [order, setOrder] = useState({ firstName: '', lastName: '', state: 'CA', address: '' }); const states = ['AL', 'AR', 'CA', 'NV', 'NY', 'FL'];
let states = ['AL', 'AR', 'CA', 'NV', 'NY', 'FL'];
const updateOrder = (event) => { const updateOrder = (event) => {
let control = event.target; const { name, value, type, checked } = event.target;
if (control.name == "gift") { const newValue = type === 'checkbox' ? checked : value;
setOrder({ ...order, gift: control.checked }); setOrder({ ...order, [name]: newValue });
return; };
}
setOrder({ ...order, [control.name]: control.value }); const navigate = useNavigate();
}
const [totalAmount, setTotalAmount] = useState(0);
useEffect(() => {
let total = 0;
cartItems.forEach((item) => {
total += item.price;
});
setTotalAmount(total);
}, [cartItems]);
const handleDelete = (index) => {
const updatedItems = [...cartItems];
updatedItems.splice(index, 1);
onSetCartItems(updatedItems);
};
const handleCancel = () => {
onSetCartItems([]);
};
const handleSubmit = (e) => {
e.preventDefault();
cartItems.forEach((item) => {
let product = products.find(p => p.id == item.id);
product.availableInventory -= 1;
onSetProducts([...products]);
});
onSetCartItems([]);
navigate('/');
};
const nItems = cartItems.length;
return ( return (
<div> <div>
<table>
<tbody>
{cartItems.map((item, i) => (
<tr key={i}>
<td>{item.title}</td>
<td>{item.price}</td>
<td>1</td>
<td>
<button onClick={() => handleDelete(i)}>삭제</button>
</td>
</tr>
))}
</tbody>
</table>
<div>합계{totalAmount}</div>
<form>
<div className="col-md-6"> <div className="col-md-6">
<strong>이름</strong> <strong>이름</strong>
<input className="form-control" name="firstName" value={order.firstName} onChange={updateOrder} /> <input className="form-control" name="firstName" value={order.firstName} onChange={updateOrder} />
...@@ -24,29 +76,32 @@ const CheckoutPage = (props) => { ...@@ -24,29 +76,32 @@ const CheckoutPage = (props) => {
<strong></strong> <strong></strong>
<input className="form-control" name="lastName" value={order.lastName} onChange={updateOrder} /> <input className="form-control" name="lastName" value={order.lastName} onChange={updateOrder} />
</div> </div>
<div className="form-group"> <div className="form-group">
<div className="col-md-12"> <strong>주소:</strong></div> <div className="col-md-12">
<strong>주소:</strong>
</div>
<div className="col-md-12"> <div className="col-md-12">
<input className="form-control" name="address" value={order.address} onChange={updateOrder} /> <input className="form-control" name="address" value={order.address} onChange={updateOrder} />
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
<div className="col-md-12"> <strong>:</strong> <div className="col-md-12">
<strong>:</strong>
<select className="form-control" name="state" value={order.state} onChange={updateOrder}> <select className="form-control" name="state" value={order.state} onChange={updateOrder}>
{states.map((st) => <option value={st}> {st}</option>)} {states.map((st) => (
<option key={st} value={st}>
{st}
</option>
))}
</select> </select>
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
<div className="col-md-6 boxes"> <div className="col-md-6 boxes">
<input type="checkbox" name="gift" id="gift" value={true} onChange={updateOrder} /> <input type="checkbox" name="gift" id="gift" checked={order.gift} onChange={updateOrder} />
<label htmlFor="gift">선물로 보내기?</label> <label htmlFor="gift">선물로 보내기?</label>
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
<div className="col-md-9 boxes"> <div className="col-md-9 boxes">
<div className="col-md-3"> <div className="col-md-3">
...@@ -59,31 +114,26 @@ const CheckoutPage = (props) => { ...@@ -59,31 +114,26 @@ const CheckoutPage = (props) => {
</div> </div>
</div> </div>
</div> </div>
</form>
<div className="form-group"> <div className="form-group">
<div className="col-md-12"> <div className="col-md-12">
<input type="submit" name="submit" className="btn btn-lg btn-primary submit"
onClick={() => { alert("제출완료") }} />
</div>
</div>
<div className="col-md-12 verify"> <button type="button" className="btn btn-default btn-lg" onClick={handleSubmit}>
<pre> <span className="glyphicon glyphicon-shopping-cart">
이름 : {order.firstName} {nItems || ''}
<br /> </span>
: {order.lastName} 제출
<br /> </button>
주소 : {order.address}
<br /> <Link to="/">
: {order.state} <button type="button" className="btn btn-default btn-lg" onClick={handleCancel}>
<br /> 구매취소
배송지: {order.method} </button>
<br /> </Link>
선물 : {order.gift ? "선물" : "선물아님"} </div>
</pre>
</div> </div>
</div> </div>
) );
} };
export default CheckoutPage; export default CheckoutPage;
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
import { getFirestore } from 'firebase/firestore';
import { getAuth ,GoogleAuthProvider } from 'firebase/auth';
const firebaseConfig = {
apiKey: "AIzaSyAMGZojwvf5o32lokZ1SkF3JRFYcD2NQcQ",
authDomain: "petshop-f62e0.firebaseapp.com",
projectId: "petshop-f62e0",
storageBucket: "petshop-f62e0.appspot.com",
messagingSenderId: "305060990130",
appId: "1:305060990130:web:98797b3fbb420f080a8dab",
measurementId: "G-CCV33S1HTF"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
export const db = getFirestore(app);
export const auth = getAuth();
export const auth_provider = new GoogleAuthProvider();
import React from 'react'; import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import {
GoogleAuthProvider, signInWithPopup,
getAdditionalUserInfo, signOut
} from 'firebase/auth';
import { auth, auth_provider } from './firebase';
const Header = ({ name, nItems, user, onSignIn, onSignOut }) => {
const location = useLocation();
const hasItemsInCart = nItems > 0;
const isCheckoutPage = location.pathname.startsWith('/checkout');
const checkoutButtonText = isCheckoutPage && hasItemsInCart ? '더 사기' : '체크아웃';
const handleCheckoutClick = () => {
if (isCheckoutPage && hasItemsInCart) {
}
};
const handleSignIn = () => {
signInWithPopup(auth, auth_provider)
.then(res => {
const credential = GoogleAuthProvider.credentialFromResult(res);
const token = credential.accessToken;
const user = res.user;
const userInfo = getAdditionalUserInfo(res);
console.log(userInfo.profile);
onSignIn(userInfo.profile);
}).catch(error => {
console.log(`[${error.code}] ${error.message}`);
})
}
const handleSignOut = () => {
signOut(auth).then(() => {
console.log(`Bye ${user.name}`);
onSignOut();
}).catch(error => {
console.log(`[${error.code}] ${error.message}`);
})
}
const Header = ({name, nItems, onToggleShowProduct}) => {
return ( return (
<div className="navbar navbar-default"> <div className="navbar navbar-default">
<div className="navbar-header"> <div className="navbar-header">
<h1> <h1>{name}</h1>
{name}
</h1>
</div> </div>
<div className="nav navbar-nav navbar-right cart"> <div className="nav navbar-nav navbar-right cart">
<button type="button" className="btn btn-default btn-lg"
onClick={onToggleShowProduct} > <Link to={isCheckoutPage ? '/' : '/checkout'}>
<button
type="button"
className="btn btn-default btn-lg"
onClick={handleCheckoutClick}
>
<span className="glyphicon glyphicon-shopping-cart"> <span className="glyphicon glyphicon-shopping-cart">
{nItems || ''} {nItems || ''}
</span> </span>
체크아웃 {checkoutButtonText}
</button> </button>
</Link>
{(user) ?
<button type="button" className="btn btn-default btn-lg"
style={{ margin: '5px' }}
onClick={handleSignOut} >
<span style={{ margin: '5px' }}>
<img className="rounded-circle" src={user.picture}
style={{ width: '20px', objectFit: 'cover' }} />
</span>
{user.name} 로그아웃
</button>
:
<button type="button" className="btn btn-default btn-lg"
onClick={handleSignIn} >
로그인
</button>
}
</div> </div>
</div> </div>
) );
} };
export default Header; export default Header;
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
const Products = ({cart, onSetCart}) => { const Products = ({ products, cart, onSetCart,user }) => {
let [products, setProducts] = useState([]);
useEffect(() => {
/* wget https://raw.githubusercontent.com/gilbutITbook/007024/master/chapter-05/products.json */
fetch('./products.json')
.then((r) => r.json())
.then(data => {
setProducts([...data.products]);
console.log(products);
})
}, [])
const formatPrice = (price) => { const formatPrice = (price) => {
if (!parseInt(price)) { return ""; } if (!parseInt(price)) {
return '';
}
if (price > 99999) { if (price > 99999) {
var priceString = (price / 100).toFixed(2); var priceString = (price / 100).toFixed(2);
var priceArray = priceString.split("").reverse(); var priceArray = priceString.split('').reverse();
var index = 3; var index = 3;
while (priceArray.length > index + 3) { while (priceArray.length > index + 3) {
priceArray.splice(index + 3, 0, ","); priceArray.splice(index + 3, 0, ',');
index += 4; index += 4;
} }
return "$" + priceArray.reverse().join(""); return '$' + priceArray.reverse().join('');
} else { } else {
return "$" + (price / 100).toFixed(2); return '$' + (price / 100).toFixed(2);
}
} }
};
const addToShoppingCart = (product) => { const addToShoppingCart = (product) => {
console.log(cart);
onSetCart([...cart, product]); onSetCart([...cart, product]);
} };
const canAddToCart = (product) => { const canAddToCart = (product) => {
if (product.availableInventory) { if (product.availableInventory) {
return product.availableInventory > cart.filter((p) => p.id == product.id).length; return product.availableInventory > cart.filter((p) => p.id === product.id).length;
} }
return false; return false;
} };
const checkRating = (n, product) => { const checkRating = (n, product) => {
return product.rating - n >= 0; return product.rating - n >= 0;
} };
const nameOrder = (p, q) => { const nameOrder = (p, q) => {
let [pLow, qLow] = [p.title.toLowerCase(), q.title.toLowerCase()] let [pLow, qLow] = [p.title.toLowerCase(), q.title.toLowerCase()];
if (pLow > qLow) { if (pLow > qLow) {
return 1; return 1;
} } else if (pLow < qLow) {
else if(pLow == qLow) {
return 0;
}
else {
return -1; return -1;
} else {
return 0;
} }
} };
return ( return (
<> <>
{products && products.sort(nameOrder).map(product => { <div className="row product">
let nProductInCart = cart.filter((p) => p.id == product.id).length; <div className="col-md-12 text-center">
{user!= null&&<Link to="/register-product" className="btn btn-primary btn-lg btn-add-product">
상품추가
</Link>}
</div>
</div>
{products &&
products.sort(nameOrder).map((product) => {
let nProductInCart = cart.filter((p) => p.id === product.id).length;
console.log(product.title, nProductInCart); console.log(product.title, nProductInCart);
return ( return (
<div className="row product"> <div className="row product" key={product.id}>
<div className="col-md-5 col-md-offset-0"> <div className="col-md-5 col-md-offset-0">
<figure> <figure>
<img className="product" src={product.image} /> <img className="product" src={product.image} alt={product.title} />
</figure> </figure>
</div> </div>
<div className="col-md-6 col-md-offset-0 description"> <div className="col-md-6 col-md-offset-0 description">
<h1>{product.title}</h1> <h1>{product.title}</h1>
<p>{product.description}</p> <p>{product.description}</p>
<p className="price">{formatPrice(product.price)}</p> <p className="price">{formatPrice(product.price)}</p>
{canAddToCart(product) && {canAddToCart(product) && (
<button className="btn btn-primary btn-lg" <button className="btn btn-primary btn-lg" onClick={() => addToShoppingCart(product)}>
onClick={() => addToShoppingCart(product)}> 장바구니 담기 장바구니 담기
</button> </button>
} )}
<span className="inventory-message"> <span className="inventory-message">
{product.availableInventory == nProductInCart && `품절!(Sold out)`} {product.availableInventory === nProductInCart && `품절!(Sold out)`}
{nProductInCart == 0 && `지금 구매하세요.`} {/*nProductInCart === 0 && `지금 구매하세요.`*/}
{product.availableInventory > nProductInCart && nProductInCart > 0 && {product.availableInventory > nProductInCart &&
nProductInCart >= 0 &&
`${product.availableInventory - nProductInCart} 남았습니다.`} `${product.availableInventory - nProductInCart} 남았습니다.`}
</span> </span>
<div className="rating"> <div className="rating">
{[1, 2, 3, 4, 5].map((i) => { {[1, 2, 3, 4, 5].map((i) => (
return <span className={checkRating(i, product) ? "rating-active" : ""}></span> <span className={checkRating(i, product) ? "rating-active" : ""} key={i}>
})
} </span>
))}
</div> </div>
</div> </div>
</div> </div>
) );
}) })}
}
</> </>
) );
} };
export default Products; export default Products;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment