Skip to content

Commit

Permalink
[#187862829] ft-create-landing-page
Browse files Browse the repository at this point in the history
  • Loading branch information
SaddockAime committed Jul 7, 2024
1 parent 04b498f commit 7273f38
Show file tree
Hide file tree
Showing 27 changed files with 693 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/coverage

# production
/build
/dist

# misc
.DS_Store
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react-icons": "^5.2.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.24.0",
"react-spinners": "^0.14.1",
"sass": "^1.77.6",
"save-dev": "0.0.1-security"
},
Expand Down
Binary file added public/assets/left-bottom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/left-top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/middle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/right-bottom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/right-top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/shoe1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/shoe2.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/shoe3.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/shoe4.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/layout/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,4 @@ function Footer() {
</footer>
);
}
export default Footer;
export default Footer;
2 changes: 1 addition & 1 deletion src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,4 @@ function Header() {
);
}

export default Header;
export default Header;
134 changes: 134 additions & 0 deletions src/components/layout/Sample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/* eslint-disable */
import React, { useState, useEffect, useRef } from 'react';
import { TfiHeadphoneAlt } from "react-icons/tfi";
import { VscWorkspaceTrusted } from "react-icons/vsc";
import { LiaShippingFastSolid } from "react-icons/lia";
import { FaHandHoldingUsd } from "react-icons/fa";
import { GoDotFill } from "react-icons/go";
import { FaChevronCircleLeft, FaChevronCircleRight } from "react-icons/fa";
import '../../styles/index.scss';

const images = [
'/assets/middle.png',
'/assets/shoe2.jpeg',
'/assets/shoe3.jpeg'
];

const Sample: React.FC = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);

const handleLeftClick = () => {
setCurrentIndex((prevIndex) => (prevIndex === 0 ? images.length - 1 : prevIndex - 1));
};

const handleRightClick = () => {
setCurrentIndex((prevIndex) => (prevIndex === images.length - 1 ? 0 : prevIndex + 1));
};

const startImageChangeInterval = () => {
intervalRef.current = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length);
}, 5000);
};

useEffect(() => {
startImageChangeInterval();
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);

useEffect(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
startImageChangeInterval();
}
}, [currentIndex]);

return (
<div>
<div className="sampleImages1">
<div className="sample1">
<div className="menShoe">
<div className="text-container">
<p>Men's Shoes</p>
<button>View &gt;</button>
</div>
</div>
<div className="phones">
<div className="text-container">
<p>Phones</p>
<button>View &gt;</button>
</div>
</div>
</div>
<div className="sample2" style={{ backgroundImage: `url(${images[currentIndex]})` }}>
<div className="arrow left" onClick={handleLeftClick}>
<FaChevronCircleLeft className="icon-arrow" />
</div>
<div className="arrow right" onClick={handleRightClick}>
<FaChevronCircleRight className="icon-arrow" />
</div>
<div className="dots">
{images.map((_, index) => (
<GoDotFill
key={index}
className="icon-dots"
style={{ color: currentIndex === index ? '#ff6d18' : '#fff' }}
/>
))}
</div>
</div>
<div className="sample3">
<div className="womenShoe">
<div className="text-container">
<p>Women's Shoes</p>
<button>View &gt;</button>
</div>
</div>
<div className="accessories">
<div className="text-container">
<p>Accessories</p>
<button>View &gt;</button>
</div>
</div>
</div>
</div>
<div className="trust-container">
<div className="trust">
<TfiHeadphoneAlt className="icon" />
<div className="name">
<h2>Responsive</h2>
<p>Customer service available 24/7</p>
</div>
</div>
<div className="trust">
<VscWorkspaceTrusted className="icon" />
<div className="name">
<h2>Secure</h2>
<p>Certified marketplace since 2024</p>
</div>
</div>
<div className="trust">
<LiaShippingFastSolid className="icon" />
<div className="name">
<h2>Shipping</h2>
<p>Free, fast, and reliable worldwide</p>
</div>
</div>
<div className="trust">
<FaHandHoldingUsd className="icon" />
<div className="name">
<h2>Transparent</h2>
<p>Free return policy</p>
</div>
</div>
</div>
</div>
);
};

export default Sample;
63 changes: 63 additions & 0 deletions src/components/product/Product.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint-disable */
import React, { useState, useRef } from 'react';
import { CiHeart } from "react-icons/ci";
import { PiShoppingCartThin } from "react-icons/pi";
import '../../styles/index.scss';

interface ProductProps {
images: string[];
name: string;
price: string;
stock: number;
description: string;
discount: number;
}

const Product: React.FC<ProductProps> = ({ images, name, price, stock, description, discount }) => {
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);

const handleMouseEnter = () => {
intervalRef.current = setInterval(() => {
setCurrentImageIndex((prevIndex) => (prevIndex + 1) % images.length);
}, 2000);
};

const handleMouseLeave = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
setCurrentImageIndex(0);
};

const truncateDescription = (desc: string, length: number) => {
return desc.length > length ? `${desc.substring(0, length)}...` : desc;
};

return (
<div className="product">
<div
className="product-image-container"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<span className="discount-badge">{discount}%</span>
<img src={images[currentImageIndex]} alt="Product" className="product-image" />
</div>
<div className="product-info">
<div className="product-add">
<div className="icon-container"><PiShoppingCartThin className="icon" /></div>
<div className="icon-container"><CiHeart className="icon" /></div>
</div>
<div className="product-name">{name}</div>
<div className="product-details">
<p className="product-price">{price}</p>
<p className="product-stock"><span className="stock">Stock:</span>{stock}</p>
</div>
<p className="product-description">{truncateDescription(description, 20)}</p>
</div>
</div>
);
};

export default Product;
52 changes: 33 additions & 19 deletions src/pages/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,55 @@
/* eslint-disable */

import React, { useEffect } from "react";
import { useAppDispatch, useAppSelector } from "../store/store";
import { loadWelcomeMessage } from "../store/features/welcomeSlice";
import { IWelcomeMessage } from "../utils/types/store";
import { fetchProducts } from '../store/features/product/productSlice';
import Header from "../components/layout/Header";
import Footer from "../components/layout/Footer";
import Product from '../components/product/Product';
import Sample from '../components/layout/Sample';
import { PuffLoader } from 'react-spinners';
import "../styles/LandingPage.scss";
import SearchInput from "../components/inputs/SearchInput";
import Sidebar from "../components/sidebar/Sidebar";
import AdminHeader from "../components/layout/AdminHeader";
import SellerHeader from "../components/layout/SellerHeader";

const LandingPage: React.FC = () => {
const dispatch = useAppDispatch();
const welcomeMessage: IWelcomeMessage = useAppSelector(
(state) => state.initialMessage.welcomeMessage
);
const { products, isError, isSuccess, isLoading, message } = useAppSelector((state: any) => state.products);

useEffect(() => {
dispatch(loadWelcomeMessage());
dispatch(fetchProducts());
}, [dispatch]);

return (
<>
<AdminHeader />
<SellerHeader />
<Header />
<div className="landingPage">
<h1>{welcomeMessage.message}</h1>
</div>
<div className="container__button">
<SearchInput />
<Sample />
<div className="landing-container">
{
isLoading ? <div className="loader">
<PuffLoader color="#ff6d18" size={300} loading={isLoading} />
</div> : (
<div>
<div className="head">
<h1>Today's Deal</h1>
</div>
<div className="product-list">
{isSuccess ? (products.map((product: any) => (
<Product
key={product.id}
images={product.images}
name={product.name}
price={`$${product.price}`}
stock={Number(product.quantity)}
description={product.description}
discount={Number(product.discount.replace('%', ''))}
/>
))) : null}
</div>
</div>
)
}
</div>
<Footer />
</>
);
};

export default LandingPage;
export default LandingPage;
16 changes: 16 additions & 0 deletions src/store/features/product/productService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable */
import axiosInstance from "../../../utils/axios/axiosInstance";

const fetchProducts = async () => {
try {
const response = await axiosInstance.get(`/api/shop/user-get-products`);
return response.data;
} catch (error) {
throw new Error('Failed to fetch products.');
}
};

const productService = {
fetchProducts,
}
export default productService;
48 changes: 48 additions & 0 deletions src/store/features/product/productSlice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable */
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
import productService from "./productService";
import { IProduct } from "../../../utils/types/store";

const initialState: { products: IProduct[] | null; isLoading: boolean; isError: string | null; isSuccess: boolean; message: string } = {
products: null,
isLoading: false,
isError: null,
isSuccess: false,
message: ''
}

export const fetchProducts = createAsyncThunk<IProduct[]>("products/fetchProducts", async () => {
try {
const response = await productService.fetchProducts();
return response.data;
} catch (error) {
throw new Error('Failed to fetch products.');
}
});


const productSlice = createSlice({
name: "products",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state) => {
state.isLoading = true;
state.isError = null;
state.isSuccess = false;
})
.addCase(fetchProducts.fulfilled, (state, action: PayloadAction<any>) => {
state.isLoading = false;
state.isSuccess = true;
state.products = action.payload;
})
.addCase(fetchProducts.rejected, (state, action: PayloadAction<any>) => {
state.isLoading = false;
state.isError = action.payload;
state.isSuccess = false;
});
}
})

export default productSlice.reducer;
Loading

0 comments on commit 7273f38

Please sign in to comment.