Open Kim-Xu617 opened 4 months ago
Navbar Material Icons: https://mui.com/material-ui/material-icons/
import React, { useState } from 'react'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import SearchIcon from '@mui/icons-material/Search';
import PersonOutlineIcon from '@mui/icons-material/PersonOutline';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import ShoppingCartOutlinedIcon from '@mui/icons-material/ShoppingCartOutlined';
import {Link} from 'react-router-dom'
import './Navbar.scss'
import Cart from '../Cart/Cart';
import {useDispatch, useSelector} from 'react-redux'
import {onChangeLoggedIn} from "../../redux/authReducer"
const Navbar = () => {
const [open, setOpen] = useState(false)
const dispatch = useDispatch()
const isLoggedIn = useSelector(state=>state.auth.isLoggedIn)
console.log("isLoggedIn"+isLoggedIn)
const number = useSelector(state=>state.cart.products.length)
return (
<div className='Navbar'>
<div className="wrapper">
<div className='left'>
<div className="item">
<img src='/img/en.png' alt='nation'></img>
<KeyboardArrowDownIcon/>
</div>
<div className="item">
<span>USD</span>
<KeyboardArrowDownIcon/>
</div>
<div className="item">
<Link to="/products/1" className='link'>Women</Link>
</div>
<div className="item">
<Link to="/products/2" className='link'>Men</Link>
</div>
<div className="item">
<Link to="/products/3" className='link'>Children</Link>
</div>
</div>
<div className='center'>
<Link to="/" className='link'>LAMASHOP</Link>
</div>
<div className='right'>
<div className="item">
<Link to="/products/3" className='link'>Homepage</Link>
</div>
<div className="item">
<Link to="/products/3" className='link'>About</Link>
</div>
{isLoggedIn?
(<div className="item">
<button onClick={()=>
dispatch(
onChangeLoggedIn()
)}>
Logout
</button>
</div>)
:
(<div className="item">
<Link to="/login">Login</Link>
</div>)
}
<div className="item">
<Link to="/products/3" className='link'>Stores</Link>
</div>
<div className="icons">
<SearchIcon/>
<PersonOutlineIcon/>
<FavoriteBorderIcon/>
<div className="cartIcon" onClick={()=>{setOpen(!open)}}>
<ShoppingCartOutlinedIcon/>
<span>{number}</span>
</div>
</div>
</div>
</div>
{open&&<Cart/>}
</div>
)
}
export default Navbar
Style scss: https://sass-lang.com/
.Navbar{
height: 80px;
.wrapper{
padding: 10px 30px;
display: flex;
justify-content: space-between;
align-items: center;
.item{
display: flex;
align-items: center;
font-size: 18px;
}
.left{
display: flex;
align-items: center;
gap: 20px;
letter-spacing: 2px;
}
.center{
align-items: center;
font-size: 30px;
}
.right{
display: flex;
align-items: center;
gap: 20px;
.icons{
display: flex;
gap: 15px;
cursor: pointer;
color: #777;
.cartIcon{
position: relative;
span{
font-size: 12px;
height: 20px;
width: 20px;
border-radius: 50%;
background-color:#2879FE;
color: white;
align-items: center;
justify-content: center;
position: absolute;
top: -10px;
right: -10px;
display: flex;
}
}
}
}
}
}
Footer
import React from 'react'
import './Footer.scss'
const Footer = () => {
return (
<div className='footer'>
<div className='top'>
<div className="item">
<h1>Categories</h1>
<span>Women</span>
<span>Men</span>
<span>Shoes</span>
<span>Accessories</span>
<span>New Arrivals</span>
</div>
<div className="item">
<h1>Links</h1>
<span>FAQ</span>
<span>Pages</span>
<span>Stores</span>
<span>Compare</span>
<span>Cookies</span>
</div>
<div className="item">
<h1>About</h1>
<span>
Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore. Lorem ipsum dolor sit
amet conse ctetur adipisicing elit, seddo eiusmod tempor incididunt
ut labore etdolore.
</span>
</div>
<div className="item">
<h1>Contact</h1>
<span>
Lorem ipsum dolor sit amet conse ctetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore. Lorem ipsum dolor sit
amet conse ctetur adipisicing elit, seddo eiusmod tempor incididunt
ut labore etdolore.
</span>
</div>
</div>
<div className="bottom">
<div className="left">
<span className="logo">Lamastore</span>
<span className="copyright">
© Copyright 2023. All Rights Reserved
</span>
</div>
<div className="right">
<img src="/img/payment.png" alt="Payment method" />
</div>
</div>
</div>
)
}
export default Footer
.footer{
margin: 100px 200px 20px 200px;
.top{
display: flex;
gap: 50px;
.item{
flex:1;
display: flex;
flex-direction: column;
text-align: justify;
gap: 10px;
font-size: 14px;
h1{
font-size: 18px;
font-weight: 500;
color: #555;
}
span{
color: gray;
}
}
}
.bottom{
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 50px;
.left{
display: flex;
align-items: center;
.logo{
color: #2879fe;
font-weight: bold;
font-size: 24px;
}
.copyright{
margin-left: 20px;
font-size: 12px;
color: gray;
}
}
.right{
display: flex;
align-items: center;
img{
height: 50px;
}
}
}
}
Slider
import React, { useState } from 'react'
import "./Slider.scss"
import EastOutlinedIcon from "@mui/icons-material/EastOutlined";
import WestOutlinedIcon from "@mui/icons-material/WestOutlined";
const Slider = () => {
const data = [
"https://images.pexels.com/photos/1549200/pexels-photo-1549200.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/949670/pexels-photo-949670.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/837140/pexels-photo-837140.jpeg?auto=compress&cs=tinysrgb&w=1600",
];
const [currentSlide, setCurrentSlide] = useState(0)
const prevSlide = () =>{
setCurrentSlide(currentSlide===0? 2: currentSlide-1)
}
const nextSlide = () =>{
setCurrentSlide(currentSlide===2? 0: currentSlide+1)
}
return (
<div>
<div className="slider">
<div className="container" style={{transform:`translateX(-${100*currentSlide}vw)`}}>
<img src={data[0]} alt="slider 1st image" />
<img src={data[1]} alt="slider 2nd image" />
<img src={data[2]} alt="slider 3th image" />
</div>
<div className="icons">
<div className="icon" onClick={prevSlide}>
<WestOutlinedIcon/>
</div>
<div className="icon" onClick={nextSlide}>
<EastOutlinedIcon/>
</div>
</div>
</div>
</div>
)
}
export default Slider
.slider{
height: calc(100vh - 80px);
width: 100vw;
position: relative;
overflow: hidden;
.container{
width: 300vw;
height: 100%;
transition: all 1s ease;
img{
width: 100vw;
height: 100%;
object-fit: cover;
}
}
.icons{
position: absolute;
left: 0px;
right: 0px;
bottom: 100px;
margin: auto;
width: fit-content;
display: flex;
gap: 10px;
.icon{
border: 1px solid #999;
width: 50px;
height: 50px;
justify-content: center;
align-items: center;
display: flex;
cursor: pointer;
}
}
}
FeaturedProducts
import React from 'react'
import './FeaturedProducts.scss'
import Card from '../Card/Card.jsx'
import useFetch from '../../hooks/useFetch.js'
export const FeaturedProducts = ({type}) => {
const {data, loading, error} = useFetch(`/products?populate=*&filters[type][$eq]=${type}`)
return (
<div className='featureProducts'>
<div className="top">
<h1>{type} Products</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Quis ipsum
suspendisse ultrices gravida. Risus commodo viverra maecenas accumsan
lacus vel facilisis labore et dolore magna aliqua. Quis ipsum
suspendisse ultrices gravida. Risus commodo viverra maecenas.
</p>
</div>
<div className="bottom">
{ error
? "Something went error"
: loading
? "loading"
: data?.map(item=><Card item={item} key={item.id}/>)
}
</div>
</div>
)
}
.featureProducts{
margin: 100px 200px;
.top{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 50px;
h1{
flex:2;
text-transform: capitalize;
}
p{
flex:3;
color: grey;
}
}
.bottom{
display: flex;
justify-content: center;
gap: 50px;
}
}
Card
import "./Card.scss";
import {Link} from 'react-router-dom'
import React from 'react'
const Card = ({item}) => {
return (
<Link className="link" to={`/product/${item.id}`}>
<div className="card">
<div className="image">
{item.attributes.isNew&&<span>New Season</span>}
<img
src={
process.env.REACT_APP_UPLOAD_URL+item.attributes?.img?.data?.attributes?.url
}
className="mainImg"
alt="item 1"
/>
{item.attributes.img2&&<img
src={
process.env.REACT_APP_UPLOAD_URL+item.attributes?.img2?.data?.attributes?.url
}
className="secondImg"
alt="item 2" />
}
</div>
<h2>{item.attributes.title}</h2>
<div className="prices">
<h3>${item.attributes.price + 20}</h3>
<h3>${item.attributes.price}</h3>
</div>
</div>
</Link>
)
}
export default Card
.card{
display: flex;
flex-direction: column;
width: 280px;
gap: 10px;
margin-bottom: 50px;
.image{
height: 400px;
width: 100%;
overflow: hidden;
position: relative;
span{
z-index: 3;
position: absolute;
top:5px;
left: 5px;
background-color: white;
color: teal;
font-size: 12px;
font-weight: 500;
padding: 3px 5px;
}
&:hover{
.secondImg{
z-index: 2;
}
}
.mainImg{
z-index: 1;
}
img{
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
}
}
h2{
font-size: 16px;
font-weight: 400;
margin: 0px;
}
.prices{
display: flex;
gap: 20px;
h3{
font-size: 18px;
font-weight: 500;
margin: 0px;
&:first-child{
color: grey;
text-decoration: line-through;
}
}
}
}
import React from 'react'
import "../Catergories/Catergories.scss"
import {Link} from 'react-router-dom'
const Catergories = () => {
return (
<div className='catergories'>
<div className="col">
<div className="row">
<img
src="https://images.pexels.com/photos/818992/pexels-photo-818992.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<Link to="products/1" className='link'>
<button>
Sale
</button>
</Link>
</div>
<div className="row">
<img
src="https://images.pexels.com/photos/2036646/pexels-photo-2036646.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<Link to="products/1" className='link'>
<button>
Women
</button>
</Link>
</div>
</div>
<div className="col">
<div className="row">
<img
src="https://images.pexels.com/photos/1813947/pexels-photo-1813947.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<Link to="products/3" className='link'>
<button>
New Season
</button>
</Link>
</div>
</div>
<div className="col col-l">
<div className="row">
<div className="col">
<div className="row">
<img
src="https://images.pexels.com/photos/1192609/pexels-photo-1192609.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<Link to="products/2" className='link'>
<button>
Men
</button>
</Link>
</div>
</div>
<div className="col">
<div className="row">
<img
src="https://images.pexels.com/photos/2703202/pexels-photo-2703202.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<Link to="products/5" className='link'>
<button>
Accessories
</button>
</Link>
</div>
</div>
</div>
<div className="row">
<img
src="https://images.pexels.com/photos/1159670/pexels-photo-1159670.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<Link to="products/6" className='link'>
<button>
Shoes
</button>
</Link>
</div>
</div>
</div>
)
}
export default Catergories
.catergories{
display: flex;
height: 80vh;
gap:10px;
margin: 10px;
.col{
flex:1;
display: flex;
flex-direction: column;
gap:10px;
}
.col-l{
flex:2;
}
.row{
flex: 1;
display: flex;
gap: 10px;
position: relative;
overflow: hidden;
img{
height: 100%;
width: 100%;
object-fit: cover;
}
button{
position: absolute;
top:0px;
bottom:0px;
right:0px;
left:0px;
min-width: 100px;
height: 50px;
width: fit-content;
margin: auto;
cursor: pointer;
padding: 10px;
border: none;
background-color: #fff;
text-transform:uppercase;
}
}
}
import React from 'react'
import "./Contact.scss"
import FacebookIcon from "@mui/icons-material/Facebook";
import InstagramIcon from "@mui/icons-material/Instagram";
import TwitterIcon from "@mui/icons-material/Twitter";
import GoogleIcon from "@mui/icons-material/Google";
import PinterestIcon from "@mui/icons-material/Pinterest";
const Contact = () => {
return (
<div className='contact'>
<div className="wrapper">
<span>BE IN TOUCH WITH US</span>
<div className="mail">
<input type='text' placeholder='Enter your email...'></input>
<button>JOIN US</button>
</div>
<div className="icons">
<FacebookIcon/>
<InstagramIcon/>
<TwitterIcon/>
<GoogleIcon/>
<PinterestIcon/>
</div>
</div>
</div>
)
}
export default Contact
.contact{
background-color: #2879fe;
color: #fff;
padding: 15px;
display: flex;
justify-content: center;
.wrapper{
width: 50%;
display: flex;
justify-content: space-between;
align-items: center;
}
input{
padding: 10px;
border:none;
border-radius: 5px 0 0 5px;
}
button{
background-color: black;
color: #fff;
border-radius: 0 5px 5px 0;
padding: 10px;
border: none;
}
.icons{
display: flex;
gap:10px
}
}
const Home = () => {
return (
<div className='home'>
<Slider/>
<FeaturedProducts type="featured"/>
<Catergories/>
<FeaturedProducts type="trending"/>
<Contact/>
</div>
)
}
export default Home
import './Products.scss'
import List from '../../components/List/List'
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import useFetch from '../../hooks/useFetch'
const Products = () => {
const catId = parseInt(useParams().id)
const [maxPrice, setMaxPrice] = useState(1000)
const [sort, setSort]=useState("asc")
const {data, loading, error} = useFetch(`/sub-categories?[filters][categories][id][$eq]=${catId}`)
const [selectedSubCats, setSelectedSubCats] = useState([])
const handleChange = (e) =>{
const value=e.target.value;
const isChecked = e.target.checked;
setSelectedSubCats(
isChecked
?[...selectedSubCats,value]
:selectedSubCats.filter(item=>(item!==value))
)
}
return (
<div className='products'>
<div className="left">
<div className="filterItem">
<h2>Product Catergories</h2>
{
data?.map(item=>(
<div className="inputItem" key={item.id}>
<input type="checkbox" id='1' value={item.id} onChange={handleChange}></input>
<label htmlFor={item.id}>{item.attributes.title}</label>
</div>
))
}
</div>
<div className="filterItem">
<h2>Filter by Price</h2>
<div className="inputItem">
<span>0</span>
<input type='range' min="0" max="1000" onChange={(e)=>setMaxPrice(e.target.value)}/>
<span>{maxPrice}</span>
</div>
</div>
<div className="filterItem">
<h2>Sort by</h2>
<div className="inputItem">
<input type='radio' id='asc' value='asc' name='price' onChange={()=>setSort("asc")}/>
<label htmlFor='asc'>Price (Lowest first)</label>
</div>
<div className="inputItem">
<input type='radio' id='desc' value='desc' name='price' onChange={()=>setSort("desc")}/>
<label htmlFor='desc'>Price (Highest first)</label>
</div>
</div>
</div>
<div className="right">
<img
className="catImg"
src="https://images.pexels.com/photos/1074535/pexels-photo-1074535.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<List catId={catId} sort={sort} maxPrice={maxPrice} subCats={selectedSubCats}/>
</div>
</div>
)
}
export default Products
List component in products page
import React from 'react'
import "./List.scss"
import Card from '../Card/Card'
import useFetch from '../../hooks/useFetch'
const List = ({catId,sort,maxPrice, subCats})=>{
const {data, loading, error} = useFetch(
`/products?populate=*&[filters][categories][id][$eq]=${catId}
${subCats.map(item=>`&[filters][sub_categories][id][$eq]=${item}`
)}&[filters][price][$lte]=${maxPrice}&sort=price:${sort}`
);
console.log(data)
return (
<div className='list'>
{
error
? console.log(error)
:
loading
? "loading"
:data?.map(item =>
<Card item={item} key={item.attributes.id}/>
)}
</div>
)
}
export default List
.list{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
import React, { useState } from 'react'
import './Product.scss'
import AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
import BalanceIcon from "@mui/icons-material/Balance";
import useFetch from '../../hooks/useFetch';
import { useParams } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { addToCart } from '../../redux/cartReducer';
const Product = () => {
const [selectImg, setSelectImg] = useState("img")
const [quantity, setQuantity] = useState(0)
const productId = useParams().id
const {data, loading, error} = useFetch(`/products/${productId}?populate=*`)
const dispatch = useDispatch()
return (
<div className='product'>
{error?
"Something went wrong"
:loading? "loading"
:(<>
<div className="left">
<div className="images">
<img src={process.env.REACT_APP_UPLOAD_URL + data?.attributes?.img?.data?.attributes?.url} alt="" onClick={()=>{setSelectImg("img")}}/>
<img src={process.env.REACT_APP_UPLOAD_URL + data?.attributes?.img2?.data?.attributes?.url} alt="" onClick={()=>{setSelectImg("img2")}} />
</div>
<div className="mainImg">
<img src={process.env.REACT_APP_UPLOAD_URL + data?.attributes[selectImg].data?.attributes?.url} alt="" />
</div>
</div>
<div className="right">
<h1>{data?.attributes?.title}</h1>
<span className='price'>${data?.attributes?.price}</span>
<p>{data?.attributes?.desc}</p>
<div className="quantity">
<button onClick={()=>{setQuantity(quantity===1?1:quantity-1)}}>-</button>
{quantity}
<button onClick={()=>{setQuantity(quantity+1)}}>+</button>
</div>
<button
className="add"
onClick={()=>
dispatch(
addToCart({
id: data.id,
title: data.attributes.title,
desc: data.attributes.desc,
price: data.attributes.price,
img: data.attributes.img.data.attributes.url,
quantity: quantity,
})
)
}>
<AddShoppingCartIcon/>
ADD TO CART
</button>
<div className="links">
<div className="item">
<FavoriteBorderIcon/>
ADD TO WISHLIST
</div>
<div className="item">
<BalanceIcon/>
ADD TO COMPARE
</div>
</div>
<div className="info">
<span>Vendor: Polo</span>
<span>Product Type: T-Shirt</span>
<span>Tag: T-Shirt, Women, Top</span>
</div>
<hr />
<div className="info">
<span>DESCRIPTION</span>
<hr />
<span>ADDITIONAL INFORMATION</span>
<hr />
<span>FAQ</span>
</div>
</div>
</>)}
</div>
)
}
export default Product
.product{
padding: 20px 50px;
display: flex;
gap: 50px;
.left{
flex:1;
display: flex;
gap: 20px;
.images{
flex:1;
img{
width: 100%;
height: 150px;
object-fit: cover;
cursor: pointer;
margin-bottom: 10px;
}
}
.mainImg{
flex: 5;
img{
width: 100%;
max-height: 800px;
object-fit: cover;
}
}
}
.right{
flex:1;
display: flex;
flex-direction: column;
gap: 30px;
.price{
font-size: 30px;
color: #2879fe;
font-weight: 500;
}
p{
font-size: 18px;
font-weight: 300;
text-align: justify;
}
.quantity{
display: flex;
align-items: center;
gap: 10px;
button{
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
}
}
.add{
width: 250px;
padding: 10px;
background-color: #2879fe;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
gap: 20px;
border: none;
font-weight: 500;
}
.links{
display: flex;
gap: 20px;
.item{
display: flex;
align-items: center;
gap: 10px;
color: #2879fe;
font-size: 14px;
}
}
.info{
display: flex;
flex-direction: column;
gap: 10px;
color: gray;
margin-top: 30px;
font-size: 14px;
hr{
width: 200px;
border: 1px solid rgb(238,237,237);
justify-content: left;
margin-left: 0px;
}
}
hr{
width:100%;
border: 1px solid rgb(238,237,237);
}
}
}
import React from 'react'
import './Cart.scss'
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
import {useDispatch, useSelector} from 'react-redux'
import { removeItem, resetCart } from '../../redux/cartReducer';
import { makeRequest } from "../../makeRequest";
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';
const Cart = () => {
const products = useSelector(state=>state.cart.products)
const total = () =>{
let total = 0;
products.map(item=>total+=item.price*item.quantity)
return total.toFixed(2)
}
const dispatch = useDispatch()
const stripePromise = loadStripe('pk_test_51P8mMdF11XhvAKLGcJEi5CvV6IQeVz6TyCVMKAfUJ3P0PyG3dRKzdKaxm6fHv0sODycvU3pPTs1uWMrbB2ncHMWu00Wa5dm0qM');
const handlePayment = async() =>{
try {
const stripe = await stripePromise;
const res = await makeRequest.post("/orders",{
products,
});
await stripe.redirectToCheckout({
sessionId: res.data.stripeSession.id,
})
} catch (error) {
console.log(error)
}
}
return (
<div className='cart'>
<h1>Products in your cart</h1>
{products?.map(item=>(
<div className="item" key={item.id}>
<img src={process.env.REACT_APP_UPLOAD_URL +item.img} alt="" />
<div className="details">
<h1>{item.title}</h1>
<p>{item.desc?.substring(0,100)}</p>
<div className="price">{item.quantity} x ${item.price}</div>
</div>
<DeleteOutlinedIcon
className='delete'
onClick={()=>dispatch(
removeItem(item)
)}/>
</div>
))}
<div className="total">
<span>SUBTOTAL</span>
<span>${total()}</span>
</div>
<button onClick={handlePayment}>PROCEED TO CHECKOUT</button>
<span
className='reset'
onClick={()=>dispatch(
resetCart()
)}
>
Reset Cart
</span>
</div>
)
}
export default Cart
.cart{
position: absolute;
top: 80px;
right: 20px;
z-index: 999;
background-color: #fff;
padding: 20px;
-webkit-box-shadow: -5px 6px 5px 0px rgba(0,0,0,0.75);
-moz-box-shadow: -5px 6px 5px 0px rgba(0,0,0,0.75);
box-shadow: -5px 6px 5px 0px rgba(0,0,0,0.75);
h1{
margin-bottom: 30px;
color: gray;
font-weight: 400;
font-size: 24px;
}
.item{
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 30px;
img{
width: 80px;
height: 100px;
object-fit: cover;
}
.details {
h1 {
font-size: 18px;
font-weight: 500;
}
p {
color: gray;
margin-bottom: 10px;
font-size: 14px;
}
.price {
color: #2879fe;
}
}
.delete {
color: red;
font-size: 30px;
cursor: pointer;
}
}
.total {
display: flex;
justify-content: space-between;
font-weight: 500;
font-size: 18px;
margin-bottom: 20px;
}
button {
width: 250px;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
cursor: pointer;
border: none;
background-color: #2879fe;
color: white;
font-weight: 500;
margin-bottom: 20px;
}
.reset{
color:red;
font-size: 12px;
cursor: pointer;
}
}
npx create-strapi-app@latest .
Create relationship between collection types
Create API Token
Change permission for each collection type
Paste API token to .env in client folder REACT_APP_API_TOKEN REACT_APP_API_URL = http://localhost:1337/api REACT_APP_UPLOAD_URL = http://localhost:1337
makeRequest.js
import axios from "axios";
export const makeRequest = axios.create({
baseURL: process.env.REACT_APP_API_URL,
headers:{
Authorization: "bearer "+process.env.REACT_APP_API_TOKEN
}
})
import { useEffect, useState } from "react"
import { makeRequest } from "../makeRequest"
const useFetch = (url) =>{
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(false)
useEffect(()=>{
const fetchData = async () =>{
try {
setLoading(true)
const res = await makeRequest.get(url)
setData(res.data.data)
} catch (error) {
setError(true)
}
setLoading(false)
}
fetchData()
},[url]);
return {data, loading, error}
}
export default useFetch;
In FeaturedProducts we need to fetch data
import React from 'react'
import './FeaturedProducts.scss'
import Card from '../Card/Card.jsx'
import useFetch from '../../hooks/useFetch.js'
export const FeaturedProducts = ({type}) => {
const {data, loading, error} = useFetch(`/products?populate=*&filters[type][$eq]=${type}`)
return (
<div className='featureProducts'>
<div className="top">
<h1>{type} Products</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Quis ipsum
suspendisse ultrices gravida. Risus commodo viverra maecenas accumsan
lacus vel facilisis labore et dolore magna aliqua. Quis ipsum
suspendisse ultrices gravida. Risus commodo viverra maecenas.
</p>
</div>
<div className="bottom">
{ error
? "Something went error"
: loading
? "loading"
: data?.map(item=><Card item={item} key={item.id}/>)
}
</div>
</div>
)
}
Left part of products page we need to fetch subcategory of category(url)
import './Products.scss'
import List from '../../components/List/List'
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import useFetch from '../../hooks/useFetch'
const Products = () => {
const catId = parseInt(useParams().id)
const [maxPrice, setMaxPrice] = useState(1000)
const [sort, setSort]=useState("asc")
const {data, loading, error} = useFetch(`/sub-categories?[filters][categories][id][$eq]=${catId}`)
const [selectedSubCats, setSelectedSubCats] = useState([])
const handleChange = (e) =>{
const value=e.target.value;
const isChecked = e.target.checked;
setSelectedSubCats(
isChecked
?[...selectedSubCats,value]
:selectedSubCats.filter(item=>(item!==value))
)
}
return (
<div className='products'>
<div className="left">
<div className="filterItem">
<h2>Product Catergories</h2>
{
data?.map(item=>(
<div className="inputItem" key={item.id}>
<input type="checkbox" id='1' value={item.id} onChange={handleChange}></input>
<label htmlFor={item.id}>{item.attributes.title}</label>
</div>
))
}
</div>
<div className="filterItem">
<h2>Filter by Price</h2>
<div className="inputItem">
<span>0</span>
<input type='range' min="0" max="1000" onChange={(e)=>setMaxPrice(e.target.value)}/>
<span>{maxPrice}</span>
</div>
</div>
<div className="filterItem">
<h2>Sort by</h2>
<div className="inputItem">
<input type='radio' id='asc' value='asc' name='price' onChange={()=>setSort("asc")}/>
<label htmlFor='asc'>Price (Lowest first)</label>
</div>
<div className="inputItem">
<input type='radio' id='desc' value='desc' name='price' onChange={()=>setSort("desc")}/>
<label htmlFor='desc'>Price (Highest first)</label>
</div>
</div>
</div>
<div className="right">
<img
className="catImg"
src="https://images.pexels.com/photos/1074535/pexels-photo-1074535.jpeg?auto=compress&cs=tinysrgb&w=1600"
alt=""
/>
<List catId={catId} sort={sort} maxPrice={maxPrice} subCats={selectedSubCats}/>
</div>
</div>
)
}
export default Products
In list component in products we need to fetch data according to props from products
import React from 'react'
import "./List.scss"
import Card from '../Card/Card'
import useFetch from '../../hooks/useFetch'
const List = ({catId,sort,maxPrice, subCats})=>{
const {data, loading, error} = useFetch(
`/products?populate=*&[filters][categories][id][$eq]=${catId}
${subCats.map(item=>`&[filters][sub_categories][id][$eq]=${item}`
)}&[filters][price][$lte]=${maxPrice}&sort=price:${sort}`
);
console.log(data)
return (
<div className='list'>
{
error
? console.log(error)
:
loading
? "loading"
:data?.map(item =>
<Card item={item} key={item.attributes.id}/>
)}
</div>
)
}
export default List
product
import React, { useState } from 'react'
import './Product.scss'
import AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
import BalanceIcon from "@mui/icons-material/Balance";
import useFetch from '../../hooks/useFetch';
import { useParams } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { addToCart } from '../../redux/cartReducer';
const Product = () => {
const [selectImg, setSelectImg] = useState("img")
const [quantity, setQuantity] = useState(0)
const productId = useParams().id
const {data, loading, error} = useFetch(`/products/${productId}?populate=*`)
const dispatch = useDispatch()
return (
<div className='product'>
{error?
"Something went wrong"
:loading? "loading"
:(<>
<div className="left">
<div className="images">
<img src={process.env.REACT_APP_UPLOAD_URL + data?.attributes?.img?.data?.attributes?.url} alt="" onClick={()=>{setSelectImg("img")}}/>
<img src={process.env.REACT_APP_UPLOAD_URL + data?.attributes?.img2?.data?.attributes?.url} alt="" onClick={()=>{setSelectImg("img2")}} />
</div>
<div className="mainImg">
<img src={process.env.REACT_APP_UPLOAD_URL + data?.attributes[selectImg].data?.attributes?.url} alt="" />
</div>
</div>
<div className="right">
<h1>{data?.attributes?.title}</h1>
<span className='price'>${data?.attributes?.price}</span>
<p>{data?.attributes?.desc}</p>
<div className="quantity">
<button onClick={()=>{setQuantity(quantity===1?1:quantity-1)}}>-</button>
{quantity}
<button onClick={()=>{setQuantity(quantity+1)}}>+</button>
</div>
<button
className="add"
onClick={()=>
dispatch(
addToCart({
id: data.id,
title: data.attributes.title,
desc: data.attributes.desc,
price: data.attributes.price,
img: data.attributes.img.data.attributes.url,
quantity: quantity,
})
)
}>
<AddShoppingCartIcon/>
ADD TO CART
</button>
<div className="links">
<div className="item">
<FavoriteBorderIcon/>
ADD TO WISHLIST
</div>
<div className="item">
<BalanceIcon/>
ADD TO COMPARE
</div>
</div>
<div className="info">
<span>Vendor: Polo</span>
<span>Product Type: T-Shirt</span>
<span>Tag: T-Shirt, Women, Top</span>
</div>
<hr />
<div className="info">
<span>DESCRIPTION</span>
<hr />
<span>ADDITIONAL INFORMATION</span>
<hr />
<span>FAQ</span>
</div>
</div>
</>)}
</div>
)
}
export default Product
Redux to centralize management of cart state React persist to persist its state
import { configureStore } from '@reduxjs/toolkit'
import cartReducer from './cartReducer'
import authReducer from './authReducer'
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const persistConfig = {
key: 'root',
version: 1,
storage,
}
const persistedReducer = persistReducer(persistConfig, cartReducer)
const persistedAuthReducer = persistReducer(persistConfig, authReducer)
export const store = configureStore({
reducer:{
cart: persistedReducer,
auth: persistedAuthReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
})
export let persistor = persistStore(store)
cart reducer slice
import { createSlice } from '@reduxjs/toolkit'
export const cartSlice = createSlice({
name: 'cart',
initialState: {
products:[]
},
reducers: {
addToCart: (state, action) => {
const item = state.products.find(item=>item.id===action.payload.id)
if(item){
item.quantity=action.payload.quantity+item.quantity
}
else{
state.products.push(action.payload)
}
},
removeItem: (state, action) => {
state.products = state.products.filter(item=>item.id!==action.payload.id)
},
resetCart: (state) => {
state.products=[]
},
},
})
// Action creators are generated for each case reducer function
export const { addToCart, removeItem, resetCart } = cartSlice.actions
export default cartSlice.reducer
auth reducer slice
import { createSlice } from '@reduxjs/toolkit'
export const authSlice = createSlice({
name: 'auth',
initialState: {
isLoggedIn: false
},
reducers:{
onChangeLoggedIn: (state) => {
state.isLoggedIn = !state.isLoggedIn
}
}
})
export const {onChangeLoggedIn} = authSlice.actions
export default authSlice.reducer
Index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
//import './index.css';
import App from './App';
//import reportWebVitals from './reportWebVitals';
import {store} from './redux/store';
import {persistor} from './redux/store';
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</React.StrictMode>
);
dispatch cartslice
<div className='cart'>
<h1>Products in your cart</h1>
{products?.map(item=>(
<div className="item" key={item.id}>
<img src={process.env.REACT_APP_UPLOAD_URL +item.img} alt="" />
<div className="details">
<h1>{item.title}</h1>
<p>{item.desc?.substring(0,100)}</p>
<div className="price">{item.quantity} x ${item.price}</div>
</div>
<DeleteOutlinedIcon
className='delete'
onClick={()=>dispatch(
removeItem(item)
)}/>
</div>
))}
<div className="total">
<span>SUBTOTAL</span>
<span>${total()}</span>
</div>
<button onClick={handlePayment}>PROCEED TO CHECKOUT</button>
<span
className='reset'
onClick={()=>dispatch(
resetCart()
)}
>
Reset Cart
</span>
</div>
Login submit form with correct credentials dispatch authslice
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import "../Login/Login.scss"
import axios from 'axios'
import { useNavigate } from 'react-router-dom';
import {useDispatch} from 'react-redux'
import { onChangeLoggedIn } from '../../redux/authReducer';
const Login = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
identifier: '',
password: '',
})
const dispatch = useDispatch();
const handleSubmit = (e) =>{
e.preventDefault();
axios
.post('http://localhost:1337/api/auth/local', formData)
.then(response => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
console.log('User token', response.data.jwt);
dispatch(onChangeLoggedIn());
navigate('/');
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error.response);
alert("Login failed")
});
}
const handleChange = (e) =>{
setFormData({
...formData,
[e.target.name]:e.target.value
})
}
return (
<div className='login'>
<h1>Sign in to LAMASTORE</h1>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="column">
<h2>Username or email</h2>
<input type='text' name="identifier" onChange={handleChange}></input>
</div>
</div>
<div className="row">
<div className="column">
<h2>Password</h2>
<input type="password" name="password" onChange={handleChange}></input>
</div>
</div>
<div className="row">
<button type='submit'>Sign In</button>
</div>
<div className="row">
<p>Don't have an account?</p>
<Link to="/signup">Sign up</Link>
</div>
</form>
</div>
)
}
export default Login
Create collectionId
api > src > api > table names We have controllers, routes and services. We can write complex function is controllers. We need to make payment function in backend server. We will create a session, and save the sessionId and order into table.
Create .env file and keep stripe key there Order.js
// @ts-ignore
const stripe = require('stripe')(process.env.STRIPE_KEY);
'use strict';
/**
* order controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::order.order',({strapi})=>({
async create(ctx) {
// @ts-ignore
const {products} = ctx.request.body;
try {
const lineItems = await Promise.all(
products.map(async(product)=>{
const item = await strapi
.service("api::product.product")
.findOne(product.id);
return{
price_data: {
currency: "usd",
product_data: {
name: item.title,
},
unit_amount: item.price*100,
},
quantity: product.quantity,
};
})
)
console.log(lineItems);
const session = await stripe.checkout.sessions.create({
mode: 'payment',
success_url: `${process.env.CLIENT_URL}?success=true`,
cancel_url: `${process.env.CLIENT_URL}?canceled=true`,
line_items: lineItems,
shipping_address_collection: {allowed_countries: ['US', 'CA']},
payment_method_types: ["card"],
});
await strapi
.service("api::order.order")
.create({
data: {
products,
stripeId: session.id
}
});
await strapi
.service("api::order.order")
.create({
data: {
products,
stripeId: session.id
}
});
return { stripeSession: session };
} catch (error) {
ctx.response.status = 500
return error;
}
}
}));
Cart.jsx
const stripePromise = loadStripe('pk_test_51P8mMdF11XhvAKLGcJEi5CvV6IQeVz6TyCVMKAfUJ3P0PyG3dRKzdKaxm6fHv0sODycvU3pPTs1uWMrbB2ncHMWu00Wa5dm0qM');
const handlePayment = async() =>{
try {
const stripe = await stripePromise;
const res = await makeRequest.post("/orders",{
products,
});
await stripe.redirectToCheckout({
sessionId: res.data.stripeSession.id,
})
} catch (error) {
console.log(error)
}
}
My custom code: Login
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import "../Login/Login.scss"
import axios from 'axios'
import { useNavigate } from 'react-router-dom';
import {useDispatch} from 'react-redux'
import { onChangeLoggedIn } from '../../redux/authReducer';
const Login = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
identifier: '',
password: '',
})
const dispatch = useDispatch();
const handleSubmit = (e) =>{
e.preventDefault();
axios
.post('http://localhost:1337/api/auth/local', formData)
.then(response => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
console.log('User token', response.data.jwt);
dispatch(onChangeLoggedIn());
navigate('/');
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error.response);
alert("Login failed")
});
}
const handleChange = (e) =>{
setFormData({
...formData,
[e.target.name]:e.target.value
})
}
return (
<div className='login'>
<h1>Sign in to LAMASTORE</h1>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="column">
<h2>Username or email</h2>
<input type='text' name="identifier" onChange={handleChange}></input>
</div>
</div>
<div className="row">
<div className="column">
<h2>Password</h2>
<input type="password" name="password" onChange={handleChange}></input>
</div>
</div>
<div className="row">
<button type='submit'>Sign In</button>
</div>
<div className="row">
<p>Don't have an account?</p>
<Link to="/signup">Sign up</Link>
</div>
</form>
</div>
)
}
export default Login
Sign up
import '../Signup/Signup.scss'
import React from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
const Signup = () => {
const navigate = useNavigate()
const [formData, setFormData] = useState({
name: '',
username: '',
email: '',
password: ''
});
const handleChange = (e)=>{
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
const handleSubmit = (e) =>{
e.preventDefault();
if(formData.password.length<6){
alert('password at least 6 characters');
return
}
if(formData.username.length<3){
alert('username at least 3 characters');
return
}
axios
.post('http://localhost:1337/api/auth/local/register', formData)
.then(response => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
console.log('User token', response.data.jwt);
navigate('/login');
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error.response);
alert("Sign up failed")
});
}
return (
<div className='signup'>
<h1>Sign up to LAMASHOP</h1>
<form onSubmit={handleSubmit}>
<div className='row'>
<div className='column'>
<h2>Name</h2>
<input type='text' name='name' onChange={handleChange}></input>
</div>
<div className='column'>
<h2>Username</h2>
<input type='text' name='username' onChange={handleChange}></input>
</div>
</div>
<div className='row'>
<div className="column">
<h2>Email</h2>
<input type='email' className='email' name='email' onChange={handleChange}></input>
</div>
</div>
<div className='row'>
<div className="column">
<h2>Password</h2>
<input type='password' className="psw" name='password' onChange={handleChange}></input>
</div>
</div>
<div className="row">
<button type='submit'>Create Account</button>
</div>
<div className="row">
<Link to="/Login">Sign In</Link>
</div>
</form>
</div>
)
}
export default Signup
Pending action:
clear cache when log out 发货地址 订单管理
Homepage
Categories
Product
Client
Install react app make it React app react router dom to change router in App.js
Navbar component and Footer component is fixed, we use Outlet in react-router-dom
First we only use Home page, product page and products page for createBrowserRouter. List all of them as below. { path: "/", element: < Home /> } But since Navbar and footer are fixed, we utilize and make element as and change as children to replace only middle part of the page.