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

finished

parent 231a5f5a
Branches master
No related tags found
No related merge requests found
Pipeline #7130 failed
......@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"firebase": "^9.21.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.1"
......
import React, { useState, useEffect } from 'react';
import './assets/css/app.css';
import CheckoutPage from './checkout';
import Products from './products';
import Header from './header';
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 "./RegisterProductPage";
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 [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 (
<>
<BrowserRouter>
<header>
<Header name={props.sitename}
<Header
name={props.sitename}
nItems={cartItems.length}
onToggleShowProduct={() => setShowProduct(!showProduct)}
user={user}
onSignIn={setUser}
onSignOut={() => setUser(null)}
/>
</header>
<main>
{showProduct &&
<Products cart={cartItems} onSetCart={setCartItems}/>
<Routes>
<Route
path="/"
element={
<Products
products={products}
cart={cartItems}
onSetCart={setCartItems}
user={user}
/>
}
{!showProduct && /* Checkout 페이지 들어갈 자리 */
<CheckoutPage />
></Route>
<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>
</>
</BrowserRouter>
);
}
};
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 {
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) => {
let [order, setOrder] = useState({ firstName: '', lastName: '', state: 'CA', address: '' });
let states = ['AL', 'AR', 'CA', 'NV', 'NY', 'FL'];
const CheckoutPage = ({ products, cartItems, onSetCartItems, onSetProducts }) => {
const [order, setOrder] = useState({ firstName: '', lastName: '', state: 'CA', address: '', gift: false });
const 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 { name, value, type, checked } = event.target;
const newValue = type === 'checkbox' ? checked : value;
setOrder({ ...order, [name]: newValue });
};
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 (
<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">
<strong>이름</strong>
<input className="form-control" name="firstName" value={order.firstName} onChange={updateOrder} />
......@@ -24,29 +76,32 @@ const CheckoutPage = (props) => {
<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">
<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>
<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>)}
{states.map((st) => (
<option key={st} 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} />
<input type="checkbox" name="gift" id="gift" checked={order.gift} onChange={updateOrder} />
<label htmlFor="gift">선물로 보내기?</label>
</div>
</div>
<div className="form-group">
<div className="col-md-9 boxes">
<div className="col-md-3">
......@@ -59,31 +114,26 @@ const CheckoutPage = (props) => {
</div>
</div>
</div>
</form>
<div className="form-group">
<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">
<pre>
이름 : {order.firstName}
<br />
: {order.lastName}
<br />
주소 : {order.address}
<br />
: {order.state}
<br />
배송지: {order.method}
<br />
선물 : {order.gift ? "선물" : "선물아님"}
</pre>
<button type="button" className="btn btn-default btn-lg" onClick={handleSubmit}>
<span className="glyphicon glyphicon-shopping-cart">
{nItems || ''}
</span>
제출
</button>
<Link to="/">
<button type="button" className="btn btn-default btn-lg" onClick={handleCancel}>
구매취소
</button>
</Link>
</div>
</div>
</div>
)
}
);
};
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 { 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 (
<div className="navbar navbar-default">
<div className="navbar-header">
<h1>
{name}
</h1>
<h1>{name}</h1>
</div>
<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">
{nItems || ''}
</span>
체크아웃
{checkoutButtonText}
</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>
)
}
);
};
export default Header;
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
const Products = ({cart, onSetCart}) => {
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 Products = ({ products, cart, onSetCart,user }) => {
const formatPrice = (price) => {
if (!parseInt(price)) { return ""; }
if (!parseInt(price)) {
return '';
}
if (price > 99999) {
var priceString = (price / 100).toFixed(2);
var priceArray = priceString.split("").reverse();
var priceArray = priceString.split('').reverse();
var index = 3;
while (priceArray.length > index + 3) {
priceArray.splice(index + 3, 0, ",");
priceArray.splice(index + 3, 0, ',');
index += 4;
}
return "$" + priceArray.reverse().join("");
return '$' + priceArray.reverse().join('');
} else {
return "$" + (price / 100).toFixed(2);
}
return '$' + (price / 100).toFixed(2);
}
};
const addToShoppingCart = (product) => {
console.log(cart);
onSetCart([...cart, product]);
}
};
const canAddToCart = (product) => {
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;
}
};
const checkRating = (n, product) => {
return product.rating - n >= 0;
}
};
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) {
return 1;
}
else if(pLow == qLow) {
return 0;
}
else {
} else if (pLow < qLow) {
return -1;
} else {
return 0;
}
}
};
return (
<>
{products && products.sort(nameOrder).map(product => {
let nProductInCart = cart.filter((p) => p.id == product.id).length;
<div className="row product">
<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);
return (
<div className="row product">
<div className="row product" key={product.id}>
<div className="col-md-5 col-md-offset-0">
<figure>
<img className="product" src={product.image} />
<img className="product" src={product.image} alt={product.title} />
</figure>
</div>
<div className="col-md-6 col-md-offset-0 description">
<h1>{product.title}</h1>
<p>{product.description}</p>
<p className="price">{formatPrice(product.price)}</p>
{canAddToCart(product) &&
<button className="btn btn-primary btn-lg"
onClick={() => addToShoppingCart(product)}> 장바구니 담기
{canAddToCart(product) && (
<button className="btn btn-primary btn-lg" onClick={() => addToShoppingCart(product)}>
장바구니 담기
</button>
}
)}
<span className="inventory-message">
{product.availableInventory == nProductInCart && `품절!(Sold out)`}
{nProductInCart == 0 && `지금 구매하세요.`}
{product.availableInventory > nProductInCart && nProductInCart > 0 &&
{product.availableInventory === nProductInCart && `품절!(Sold out)`}
{/*nProductInCart === 0 && `지금 구매하세요.`*/}
{product.availableInventory > nProductInCart &&
nProductInCart >= 0 &&
`${product.availableInventory - nProductInCart} 남았습니다.`}
</span>
<div className="rating">
{[1, 2, 3, 4, 5].map((i) => {
return <span className={checkRating(i, product) ? "rating-active" : ""}></span>
})
}
{[1, 2, 3, 4, 5].map((i) => (
<span className={checkRating(i, product) ? "rating-active" : ""} key={i}>
</span>
))}
</div>
</div>
</div>
)
})
}
);
})}
</>
)
}
);
};
export default Products;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment