From 94ce3062b5f7b7a99887966a42962d7997e74f6f Mon Sep 17 00:00:00 2001 From: ttiimmothy Date: Fri, 24 Nov 2023 15:53:54 -0500 Subject: [PATCH] feat: add skeleton loader, closes #26 co-authored-by: wingck --- .../RestaurantCardSkeletonLoader.tsx | 21 +++++++++++++ .../RestaurantDetailSkeletonLoader.tsx | 2 +- src/components/utils/cards/ReviewCard.tsx | 2 +- src/pages/home/HomePage.tsx | 27 +++++++++++++--- src/pages/login/LoginPage.tsx | 15 ++++++--- src/pages/restaurant/CreateRestaurantPage.tsx | 6 ++++ src/pages/restaurant/RestaurantHomePage.tsx | 31 ++++++++++++++++--- .../restaurant/RestaurantOverviewPage.tsx | 2 +- src/pages/signUp/SignUpPage.tsx | 12 +++++-- src/redux/auth/authSlice.ts | 9 ++++-- src/redux/photo/photoSlice.ts | 6 ++-- src/redux/review/reviewSlice.ts | 4 +-- 12 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/components/skeletonLoader/RestaurantCardSkeletonLoader.tsx diff --git a/src/components/skeletonLoader/RestaurantCardSkeletonLoader.tsx b/src/components/skeletonLoader/RestaurantCardSkeletonLoader.tsx new file mode 100644 index 0000000..4ffcc08 --- /dev/null +++ b/src/components/skeletonLoader/RestaurantCardSkeletonLoader.tsx @@ -0,0 +1,21 @@ +import ContentLoader from "react-content-loader"; + +const RestaurantCardSkeletonLoader: React.FC<{ width: string }> = ({ + width, +}) => ( + + + + + + +); + +export default RestaurantCardSkeletonLoader; diff --git a/src/components/skeletonLoader/RestaurantDetailSkeletonLoader.tsx b/src/components/skeletonLoader/RestaurantDetailSkeletonLoader.tsx index 8ffa4ed..d875ac1 100644 --- a/src/components/skeletonLoader/RestaurantDetailSkeletonLoader.tsx +++ b/src/components/skeletonLoader/RestaurantDetailSkeletonLoader.tsx @@ -1,6 +1,6 @@ import ContentLoader from "react-content-loader"; -const RestaurantDetailSkeletonLoader = () => ( +const RestaurantDetailSkeletonLoader: React.FC = () => ( = (props: Review) => {
{Array.from({ length: props.rating }).map((_, index) => ( - {} + ))}
diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 176fe79..7704e05 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -1,13 +1,16 @@ import { useForm, Controller } from "react-hook-form"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; + import { AppDispatch, IRootState } from "../../store"; import { getRestaurantsByQueryThunk } from "../../redux/restaurant/restaurantSlice"; +import RestaurantCardSkeletonLoader from "../../components/skeletonLoader/RestaurantCardSkeletonLoader"; import RestaurantCard from "../../components/utils/cards/RestaurantCard"; import SearchInput from "../../components/utils/inputs/SearchInput"; export default function HomePage(): JSX.Element { + const [loading, setLoading] = useState(true); const navigate = useNavigate(); const { control, handleSubmit } = useForm(); @@ -24,6 +27,12 @@ export default function HomePage(): JSX.Element { fetchRestaurants(); }, [dispatch]); + useEffect(() => { + if (restaurants.length > 0) { + setLoading(false); + } + }, [restaurants]); + return (
- {restaurants.map((restaurant) => ( - - ))} + {loading && + Array.from({ + length: 6, + }).map((_, index) => ( + + ))} + {!loading && + restaurants.map((restaurant) => ( + + ))}
diff --git a/src/pages/login/LoginPage.tsx b/src/pages/login/LoginPage.tsx index 8a3e776..d2fdd86 100644 --- a/src/pages/login/LoginPage.tsx +++ b/src/pages/login/LoginPage.tsx @@ -2,7 +2,7 @@ import { useForm, Controller } from "react-hook-form"; import { Link, useNavigate } from "react-router-dom"; import { closeSnackbar, enqueueSnackbar } from "notistack"; import { useDispatch, useSelector } from "react-redux"; -import { loginThunk } from "../../redux/auth/authSlice"; +import { loginThunk, updateMessage } from "../../redux/auth/authSlice"; import { AppDispatch, IRootState } from "../../store"; import TextInput from "../../components/utils/inputs/TextInput"; @@ -27,16 +27,23 @@ function LoginPage() { setTimeout(() => { navigate("/"); }, 1000); - } else if (loginSuccess === false) { + } else if (loginSuccess === false && message) { enqueueSnackbar(`${message} You may try again`, { variant: "error", }); } - setTimeout(() => { closeSnackbar(); }, 2000); - }, [loginSuccess, navigate, message]); + }, [loginSuccess, navigate, message, dispatch]); + + useEffect(() => { + setTimeout(() => { + if (message) { + dispatch(updateMessage("")); + } + }, 2000); + }, [dispatch, message]); return ( { fetchPaymentMethods(); }, [user?.role, dispatch]); + useEffect(() => { + if (!user?.user_id) { + navigate("/"); + } + }, [user, navigate]); + const createNewRestaurant = async ( restaurant: RestaurantForm, start_time: Date, diff --git a/src/pages/restaurant/RestaurantHomePage.tsx b/src/pages/restaurant/RestaurantHomePage.tsx index 4a306c1..b8e6808 100644 --- a/src/pages/restaurant/RestaurantHomePage.tsx +++ b/src/pages/restaurant/RestaurantHomePage.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { Controller, useForm } from "react-hook-form"; import { useDispatch, useSelector } from "react-redux"; @@ -7,8 +7,11 @@ import { AppDispatch, IRootState } from "../../store"; import { getRestaurantsByQueryThunk } from "../../redux/restaurant/restaurantSlice"; import RestaurantCard from "../../components/utils/cards/RestaurantCard"; import SearchInput from "../../components/utils/inputs/SearchInput"; +import RestaurantCardSkeletonLoader from "../../components/skeletonLoader/RestaurantCardSkeletonLoader"; const RestaurantHomePage = () => { + const [loading, setLoading] = useState(true); + const dispatch = useDispatch(); const restaurants = useSelector( (state: IRootState) => state.restaurant.restaurants @@ -35,6 +38,16 @@ const RestaurantHomePage = () => { fetchRestaurants(); }, [searchParams, dispatch]); + useEffect(() => { + if (restaurants.length > 0 && !searchParams.get("search")) { + setLoading(false); + } + + if (searchParams.get("search")) { + setLoading(false); + } + }, [restaurants, searchParams]); + const handleSubmitSearch = (data: { name: string }) => { navigate(`/restaurants/?search=${data.name}`); navigate(0); @@ -60,9 +73,19 @@ const RestaurantHomePage = () => { )} />
- {restaurants.map((restaurant, index) => ( - - ))} + {loading && + Array.from({ + length: 10, + }).map((_, index) => ( + + ))} + {!loading && + restaurants.map((restaurant, index) => ( + + ))}
diff --git a/src/pages/restaurant/RestaurantOverviewPage.tsx b/src/pages/restaurant/RestaurantOverviewPage.tsx index 8992771..da40b64 100644 --- a/src/pages/restaurant/RestaurantOverviewPage.tsx +++ b/src/pages/restaurant/RestaurantOverviewPage.tsx @@ -170,7 +170,7 @@ const RestaurantOverviewPage: React.FC = () => { length: Math.round(restaurantDetail.averageRating), }).map((_, index) => ( - {} + ))} diff --git a/src/pages/signUp/SignUpPage.tsx b/src/pages/signUp/SignUpPage.tsx index 45e0da5..9fd73cd 100644 --- a/src/pages/signUp/SignUpPage.tsx +++ b/src/pages/signUp/SignUpPage.tsx @@ -5,7 +5,7 @@ import { useDispatch, useSelector } from "react-redux"; import { closeSnackbar, enqueueSnackbar } from "notistack"; import { AppDispatch, IRootState } from "../../store"; -import { registerThunk } from "../../redux/auth/authSlice"; +import { registerThunk, updateMessage } from "../../redux/auth/authSlice"; import TextInput from "../../components/utils/inputs/TextInput"; const SignUpPage = () => { @@ -33,7 +33,7 @@ const SignUpPage = () => { navigate("/"); navigate(0); }, 1000); - } else if (registerSuccess === false) { + } else if (registerSuccess === false && message) { enqueueSnackbar(message, { variant: "error", }); @@ -44,6 +44,14 @@ const SignUpPage = () => { }, 2000); }, [registerSuccess, navigate, message]); + useEffect(() => { + setTimeout(() => { + if (message) { + dispatch(updateMessage("")); + } + }, 2000); + }, [dispatch, message]); + return (
) => { + state.message = action.payload; + }, + }, extraReducers: (builder) => { builder.addCase(registerThunk.fulfilled, (state, action) => { if (action.payload?.token) { @@ -82,4 +86,5 @@ const authSlice = createSlice({ }, }); +export const { updateMessage } = authSlice.actions; export default authSlice.reducer; diff --git a/src/redux/photo/photoSlice.ts b/src/redux/photo/photoSlice.ts index df5ab64..cc37495 100644 --- a/src/redux/photo/photoSlice.ts +++ b/src/redux/photo/photoSlice.ts @@ -52,7 +52,7 @@ export const createMenuPhotoThunk = createAsyncThunk( } ); -const photoReducer = createSlice({ +const photoSlice = createSlice({ name: "photo", initialState, reducers: { @@ -85,5 +85,5 @@ const photoReducer = createSlice({ }, }); -export const { updateReviewPhotos, updateMenuPhotos } = photoReducer.actions; -export default photoReducer.reducer; +export const { updateReviewPhotos, updateMenuPhotos } = photoSlice.actions; +export default photoSlice.reducer; diff --git a/src/redux/review/reviewSlice.ts b/src/redux/review/reviewSlice.ts index 1d67ce7..42aba38 100644 --- a/src/redux/review/reviewSlice.ts +++ b/src/redux/review/reviewSlice.ts @@ -48,7 +48,7 @@ export const createReviewThunk = createAsyncThunk( } ); -const reviewReducer = createSlice({ +const reviewSlice = createSlice({ name: "review", initialState, reducers: {}, @@ -73,4 +73,4 @@ const reviewReducer = createSlice({ }, }); -export default reviewReducer.reducer; +export default reviewSlice.reducer;