diff --git a/src/components/landing-page/create-production.tsx b/src/components/landing-page/create-production.tsx index 33cc0d30..f767efd1 100644 --- a/src/components/landing-page/create-production.tsx +++ b/src/components/landing-page/create-production.tsx @@ -13,7 +13,7 @@ import { } from "./form-elements.tsx"; import { API } from "../../api/api.ts"; import { useGlobalState } from "../../global-state/context-provider.tsx"; -import { Loader } from "../loader/loader.tsx"; +import { Spinner } from "../loader/loader.tsx"; import { isMobile } from "../../bowser.ts"; type FormValues = { @@ -154,7 +154,7 @@ export const CreateProduction = () => { onClick={handleSubmit(onSubmit)} > Create Production - {loading && } + {loading && } {createdProductionId !== null && ( diff --git a/src/components/landing-page/productions-list.tsx b/src/components/landing-page/productions-list.tsx index b7f4adb6..318076d4 100644 --- a/src/components/landing-page/productions-list.tsx +++ b/src/components/landing-page/productions-list.tsx @@ -3,6 +3,8 @@ import { useEffect, useState } from "react"; import { API } from "../../api/api.ts"; import { TProduction } from "../production-line/types.ts"; import { useGlobalState } from "../../global-state/context-provider.tsx"; +import { LoaderDots } from "../loader/loader.tsx"; +import { useRefreshAnimation } from "./use-refresh-animation.ts"; const ProductionListContainer = styled.div` display: flex; @@ -41,11 +43,9 @@ export const ProductionsList = () => { let aborted = false; if (reloadProductionList || intervalLoad) { - setIntervalLoad(false); API.listProductions() .then((result) => { if (aborted) return; - setProductions( result // pick laste 10 items @@ -70,6 +70,7 @@ export const ProductionsList = () => { dispatch({ type: "PRODUCTION_LIST_FETCHED", }); + setIntervalLoad(false); }) .catch(() => { // TODO handle error/retry @@ -79,7 +80,9 @@ export const ProductionsList = () => { return () => { aborted = true; }; - }, [dispatch, reloadProductionList, intervalLoad]); + }, [dispatch, intervalLoad, reloadProductionList]); + + const showRefreshing = useRefreshAnimation({ reloadProductionList }); useEffect(() => { const interval = window.setInterval(() => { @@ -92,16 +95,16 @@ export const ProductionsList = () => { }, []); return ( - - {/* // TODO handle so future load-component isn't shown on every update - // TODO ex className={loading && !intervalLoad ? "active" : "in-active"} */} - {/* TODO add loading indicator */} - {productions.map((p) => ( - - {p.name} - {p.id} - - ))} - + <> + + + {productions.map((p) => ( + + {p.name} + {p.id} + + ))} + + ); }; diff --git a/src/components/landing-page/use-refresh-animation.ts b/src/components/landing-page/use-refresh-animation.ts new file mode 100644 index 00000000..0d61ee2c --- /dev/null +++ b/src/components/landing-page/use-refresh-animation.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react"; + +type TUseRefreshAnimationOptions = { + reloadProductionList: boolean; +}; + +export const useRefreshAnimation = ({ + reloadProductionList, +}: TUseRefreshAnimationOptions) => { + const [showRefreshing, setShowRefreshing] = useState(true); + + useEffect(() => { + let timeout: number | null = null; + + if (showRefreshing) { + timeout = window.setTimeout(() => { + setShowRefreshing(false); + }, 1500); + } + + return () => { + if (timeout !== null) { + window.clearTimeout(timeout); + } + }; + }, [showRefreshing]); + + useEffect(() => { + if (reloadProductionList) { + setShowRefreshing(true); + } + }, [reloadProductionList]); + + return showRefreshing; +}; diff --git a/src/components/loader/loader.tsx b/src/components/loader/loader.tsx index 14e4b8e9..6a3c235c 100644 --- a/src/components/loader/loader.tsx +++ b/src/components/loader/loader.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import { FC } from "react"; +import { FC, useEffect, useState } from "react"; const Loading = styled.div` border: 4px solid rgba(0, 0, 0, 0.1); @@ -49,8 +49,52 @@ const Loading = styled.div` } `; +const Text = styled.span` + padding-left: 2rem; + font-size: 1.8rem; + &.active { + color: #cdcdcd; + } + &.in-active { + color: #242424; + } +`; + +const Dots = styled.span` + padding-left: 0.2rem; + font-size: 2rem; + transform: translateY(-50%); + &.active { + color: #cdcdcd; + } + &.in-active { + color: #242424; + } +`; + type Props = { className: string }; -export const Loader: FC = ({ className }: Props) => { +export const Spinner: FC = ({ className }: Props) => { return ; }; + +export const LoaderDots: FC = ({ className }: Props) => { + const [dots, setDots] = useState("."); + + useEffect(() => { + const intervalId = window.setInterval(() => { + setDots((prevDots) => (prevDots.length > 2 ? "." : `${prevDots}.`)); + }, 300); + + return () => { + clearInterval(intervalId); + }; + }, []); + + return ( +
+ refreshing + {dots} +
+ ); +}; diff --git a/src/components/production-line/production-line.tsx b/src/components/production-line/production-line.tsx index b7c19123..dbb04072 100644 --- a/src/components/production-line/production-line.tsx +++ b/src/components/production-line/production-line.tsx @@ -9,7 +9,7 @@ import { ActionButton } from "../landing-page/form-elements.tsx"; import { UserList } from "./user-list.tsx"; import { API } from "../../api/api.ts"; import { noop } from "../../helpers.ts"; -import { Loader } from "../loader/loader.tsx"; +import { Spinner } from "../loader/loader.tsx"; const TempDiv = styled.div` padding: 1rem; @@ -91,7 +91,7 @@ export const ProductionLine: FC = () => { Exit - {loading && } + {loading && } {!loading && ( <> Production View