diff --git a/package.json b/package.json index bc83a99a..2e4d10d6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-dom": "^18.2.0", "react-infinite-scroll-component": "^6.1.0", "react-linkify": "^1.0.0-alpha", + "react-loading-skeleton": "^3.4.0", "react-redux": "^8.1.0", "react-router-dom": "^6.13.0", "react-scripts": "5.0.1", diff --git a/src/components/Post/Skeleton/Styles.ts b/src/components/Post/Skeleton/Styles.ts new file mode 100644 index 00000000..309213eb --- /dev/null +++ b/src/components/Post/Skeleton/Styles.ts @@ -0,0 +1,28 @@ +import Skeleton from 'react-loading-skeleton'; +import styled from 'styled-components'; + +export const Div = styled.div` + align-items: center; + border-radius: 5px; + display: flex; + height: 100%; + padding: 0.5rem; + width: 100%; +`; + +export const Left = styled.div` + margin-right: 1rem; +`; + +export const Right = styled.div` + width: 100%; +`; + +export const TextSkeleton = styled(Skeleton)<{$width?: string}>` + margin-bottom: 0.6rem; + width: ${({$width}) => $width}; +`; + +export const BoxSkeleton = styled(Skeleton)` + min-height: 250px; +`; diff --git a/src/components/Post/Skeleton/index.tsx b/src/components/Post/Skeleton/index.tsx new file mode 100644 index 00000000..e03eaf07 --- /dev/null +++ b/src/components/Post/Skeleton/index.tsx @@ -0,0 +1,38 @@ +import Skeleton from 'react-loading-skeleton'; +import * as S from './Styles'; +import {SFC} from 'types'; + +interface PostSkeletonProps { + dataLength: number; +} + +const PostSkeleton: SFC = ({dataLength}) => { + const renderContent = () => { + return ( + <> + + + + + + + + + + + + + ); + }; + return ( + <> + {Array(dataLength) + .fill(0) + .map((_, index) => ( +
{renderContent()}
+ ))} + + ); +}; + +export default PostSkeleton; diff --git a/src/index.tsx b/src/index.tsx index 1e9b5c2f..0dc116d3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,12 +2,15 @@ import ReactDOM from 'react-dom/client'; import {Provider} from 'react-redux'; import {PersistGate} from 'redux-persist/integration/react'; import {BrowserRouter} from 'react-router-dom'; +import {SkeletonTheme} from 'react-loading-skeleton'; import App from 'containers/App'; import GlobalStyle from 'styles/components/GlobalStyle'; import ToastifyStyle from 'styles/components/ToastifyStyle'; import initSentry from 'config/sentry'; import {persistor, store} from 'store'; +import {colors} from 'styles'; +import 'react-loading-skeleton/dist/skeleton.css'; import 'styles/fonts.css'; initSentry(); @@ -19,9 +22,11 @@ root.render( - - - + + + + + , ); diff --git a/src/pages/Art/Marketplace/Skeleton/Styles.ts b/src/pages/Art/Marketplace/Skeleton/Styles.ts new file mode 100644 index 00000000..29035fad --- /dev/null +++ b/src/pages/Art/Marketplace/Skeleton/Styles.ts @@ -0,0 +1,25 @@ +import Skeleton from 'react-loading-skeleton'; +import styled from 'styled-components'; + +export const TextSkeleton = styled(Skeleton)<{$width?: string}>` + margin-top: 0.6rem; + width: ${({$width}) => $width}; +`; + +export const BoxSkeleton = styled(Skeleton)` + border-radius: 14px; + min-height: 292px; +`; + +export const Avatar = styled.div` + margin-right: 0.5rem; +`; +export const Text = styled.div` + width: 100%; +`; + +export const Div = styled.div` + align-items: center; + display: flex; + width: 100%; +`; diff --git a/src/pages/Art/Marketplace/Skeleton/index.tsx b/src/pages/Art/Marketplace/Skeleton/index.tsx new file mode 100644 index 00000000..22247b28 --- /dev/null +++ b/src/pages/Art/Marketplace/Skeleton/index.tsx @@ -0,0 +1,39 @@ +import Skeleton from 'react-loading-skeleton'; +import * as S from './Styles'; +import {SFC} from 'types'; + +interface ArtworkCardSkeletonProps { + dataLength: number; +} + +const ArtworkCardSkeleton: SFC = ({dataLength}) => { + const renderContent = () => { + return ( + <> + + + + + + + + + + + + + + ); + }; + return ( + <> + {Array(dataLength) + .fill(0) + .map((_, index) => ( +
{renderContent()}
+ ))} + + ); +}; + +export default ArtworkCardSkeleton; diff --git a/src/pages/Art/Marketplace/index.tsx b/src/pages/Art/Marketplace/index.tsx index 5ebc35a1..bf8cd0da 100644 --- a/src/pages/Art/Marketplace/index.tsx +++ b/src/pages/Art/Marketplace/index.tsx @@ -4,10 +4,10 @@ import {useDispatch, useSelector} from 'react-redux'; import ArtworkCard from 'components/ArtworkCard'; import EmptyText from 'components/EmptyText'; import InfiniteScroll from 'components/InfiniteScroll'; +import ArtworkCardSkeleton from './Skeleton'; import {AppDispatch, SFC} from 'types'; import {getArtworks as _getArtworks, resetArtworks as _resetArtworks} from 'dispatchers/artworks'; import {getArtworks as getArtworksState} from 'selectors/state'; - import * as S from './Styles'; const Marketplace: SFC = ({className}) => { @@ -35,28 +35,26 @@ const Marketplace: SFC = ({className}) => { }; const renderArtworkCards = () => { - if (artworkList.length) { - return ( - - {artworkList.map((artwork) => ( - - ))} - - ); - } + return ( + + {isLoading && } + {artworkList.map((artwork) => ( + + ))} + + ); }; const renderContent = () => { - if (!!artworkList.length) { - return ( - <> - Buy from our marketplace - {renderArtworkCards()} - - ); + if (!isLoading && !artworkList.length) { + return No artwork to display.; } - - return No artwork to display.; + return ( + <> + Buy from our marketplace + {renderArtworkCards()} + + ); }; return ( diff --git a/src/pages/Contributions/Skeleton/Styles.ts b/src/pages/Contributions/Skeleton/Styles.ts new file mode 100644 index 00000000..dd1e17da --- /dev/null +++ b/src/pages/Contributions/Skeleton/Styles.ts @@ -0,0 +1,53 @@ +import Skeleton from 'react-loading-skeleton'; +import styled from 'styled-components'; +import {colors} from 'styles'; +import {Col} from 'styles/components/GridStyle'; + +export const Avatar = styled.div` + margin-right: 1rem; +`; + +export const Text = styled.div` + width: 100%; +`; + +export const TextSkeleton = styled(Skeleton)<{$float?: string; $marginLeft?: string; $width?: string}>` + float: ${({$float}) => $float}; + margin-left: ${({$marginLeft}) => $marginLeft}; + margin-top: 0.6rem; + width: ${({$width}) => $width}; +`; + +export const Box = styled.div<{$padding?: string}>` + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 8px; + padding: ${({$padding}) => $padding}; + width: 100%; +`; +export const BoxLeft = styled.div` + align-items: center; + display: flex; + width: 100%; +`; +export const BoxRight = styled.div` + width: 100%; +`; + +export const Column = styled(Col)` + border-radius: 14px; + border: 1px solid ${colors.border}; + padding: 10px; +`; + +export const Card = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 276px; +`; + +export const Div = styled.div` + margin-top: 10px; +`; diff --git a/src/pages/Contributions/Skeleton/index.tsx b/src/pages/Contributions/Skeleton/index.tsx new file mode 100644 index 00000000..0a108338 --- /dev/null +++ b/src/pages/Contributions/Skeleton/index.tsx @@ -0,0 +1,88 @@ +import Skeleton from 'react-loading-skeleton'; +import Line from 'components/Line'; +import {SFC} from 'types'; +import * as S from './Styles'; + +interface ContributionsSkeletonProps { + dataLength: number; +} + +const ContributionsSkeleton: SFC = ({dataLength}) => { + const cardHeader = () => { + return ( + <> + + + + + + + + + + + + + + + + ); + }; + const renderContent = () => { + return ( + <> + + + + + + + + ); + }; + const cardBody = () => { + return ( + + {renderContent()} + {renderContent()} + + ); + }; + const cardFooter = () => { + return ( + <> + + + + + + + + + + + ); + }; + return ( + <> + {Array(dataLength) + .fill(0) + .map((_, index) => ( + + +
+ {cardHeader()} + {cardBody()} + {cardBody()} +
+ + + {cardFooter()} +
+
+ ))} + + ); +}; + +export default ContributionsSkeleton; diff --git a/src/pages/Contributions/index.tsx b/src/pages/Contributions/index.tsx index 743b7dca..f91d8272 100644 --- a/src/pages/Contributions/index.tsx +++ b/src/pages/Contributions/index.tsx @@ -3,10 +3,10 @@ import {useDispatch, useSelector} from 'react-redux'; import ContributionsList from 'components/Contributions/Contributions'; import EmptyText from 'components/EmptyText'; -import Loader from 'components/Loader'; import Toolbar from './Toolbar'; import TopContributors from './TopContributors'; import ContributionsCumulativeChart from './ContributionsCumulativeChart'; +import ContributionsSkeleton from './Skeleton'; import {AppDispatch, SFC} from 'types'; import {Col, Row} from 'styles/components/GridStyle'; import {UserIdFilterValues} from 'enums'; @@ -29,9 +29,10 @@ const Contributions: SFC = ({className, selfContributions = const {items, hasMore, isLoading} = useSelector(getContributions); const contributions = items; const dispatch = useDispatch(); - const contributionList = useMemo(() => { - return Object.values(contributions); + if (contributions.length) { + return Object.values(contributions); + } }, [contributions]); const apiParams = useMemo(() => { @@ -66,8 +67,8 @@ const Contributions: SFC = ({className, selfContributions = }; const renderContent = () => { - if (isInitialLoading) { - return ; + if (isInitialLoading && isLoading) { + return ; } if (selfContributions) { return renderSelfContributionsContent(); @@ -77,7 +78,7 @@ const Contributions: SFC = ({className, selfContributions = }; const renderHomeContributionsContent = () => { - if (!contributionList?.length) { + if (!contributionList?.length && !isInitialLoading && !isLoading) { return renderEmptyText(); } return ( @@ -90,11 +91,13 @@ const Contributions: SFC = ({className, selfContributions =
- + {contributionList && ( + + )} ); @@ -104,13 +107,13 @@ const Contributions: SFC = ({className, selfContributions = return ( - {!contributionList?.length ? renderEmptyText() : null} + {!contributionList?.length && !isInitialLoading && !isLoading ? renderEmptyText() : null} ); }; diff --git a/src/pages/Feed/index.tsx b/src/pages/Feed/index.tsx index 728a11b1..92e485d7 100644 --- a/src/pages/Feed/index.tsx +++ b/src/pages/Feed/index.tsx @@ -4,6 +4,7 @@ import {useDispatch, useSelector} from 'react-redux'; import LeavesEmptyState from 'assets/leaves-empty-state.png'; import EmptyPage from 'components/EmptyPage'; import InfiniteScroll from 'components/InfiniteScroll'; +import PostSkeleton from 'components/Post/Skeleton'; import Post from 'components/Post'; import {getPosts as _getPosts, resetPosts as _resetPosts} from 'dispatchers/posts'; import {getPosts, hasMorePosts, isLoadingPosts} from 'selectors/state'; @@ -16,7 +17,6 @@ const Feed: SFC = ({className}) => { const posts = useSelector(getPosts); const hasMore = useSelector(hasMorePosts); const isLoading = useSelector(isLoadingPosts); - const postList = useMemo(() => Object.values(posts), [posts]); useEffect(() => { @@ -38,24 +38,24 @@ const Feed: SFC = ({className}) => { }; const renderContent = () => { - if (postList.length) { + if (!isLoading && !postList.length) { return ( - - - {postList.map((post) => ( - - ))} - - + ); } - return ( - + + + {isLoading && } + {postList.map((post) => ( + + ))} + + ); }; diff --git a/src/pages/Profile/Artworks/Skeleton/Styles.ts b/src/pages/Profile/Artworks/Skeleton/Styles.ts new file mode 100644 index 00000000..9bb67a9b --- /dev/null +++ b/src/pages/Profile/Artworks/Skeleton/Styles.ts @@ -0,0 +1,11 @@ +import Skeleton from 'react-loading-skeleton'; +import styled from 'styled-components'; + +export const TextSkeleton = styled(Skeleton)<{$width?: string}>` + margin-top: 0.6rem; + width: ${({$width}) => $width}; +`; + +export const BoxSkeleton = styled(Skeleton)` + min-height: 250px; +`; diff --git a/src/pages/Profile/Artworks/Skeleton/index.tsx b/src/pages/Profile/Artworks/Skeleton/index.tsx new file mode 100644 index 00000000..6911cacf --- /dev/null +++ b/src/pages/Profile/Artworks/Skeleton/index.tsx @@ -0,0 +1,24 @@ +import * as S from './Styles'; +import {SFC} from 'types'; + +interface ArtWorksSkeletonProps { + dataLength: number; +} + +const ArtWorksSkeleton: SFC = ({dataLength}) => { + return ( + <> + {Array(dataLength) + .fill(0) + .map((_, index) => ( +
+ + + +
+ ))} + + ); +}; + +export default ArtWorksSkeleton; diff --git a/src/pages/Profile/Artworks/index.tsx b/src/pages/Profile/Artworks/index.tsx index b92cc7f2..63cefe98 100644 --- a/src/pages/Profile/Artworks/index.tsx +++ b/src/pages/Profile/Artworks/index.tsx @@ -5,6 +5,7 @@ import {useParams} from 'react-router-dom'; import ArtworkCard from 'components/ArtworkCard'; import EmptyText from 'components/EmptyText'; import InfiniteScroll from 'components/InfiniteScroll'; +import ArtWorksSkeleton from './Skeleton'; import {AppDispatch, SFC} from 'types'; import {getArtworks as _getArtworks, resetArtworks as _resetArtworks} from 'dispatchers/artworks'; import {getArtworks as getArtworksState} from 'selectors/state'; @@ -38,22 +39,21 @@ const Artworks: SFC = ({className}) => { }; const renderArtworkCards = () => { - if (artworkList.length) { - return ( - - - {artworkList.map((artwork) => ( - - ))} - - - ); - } + return ( + + + {isLoading && } + {artworkList.map((artwork) => ( + + ))} + + + ); }; const renderContent = () => { - if (!!artworkList.length) return renderArtworkCards(); - return No artwork to display.; + if (!artworkList.length && !isLoading) return No artwork to display.; + return renderArtworkCards(); }; return {renderContent()}; diff --git a/src/pages/Profile/Follower/index.tsx b/src/pages/Profile/Follower/index.tsx index d384c5ee..0b0bc4e1 100644 --- a/src/pages/Profile/Follower/index.tsx +++ b/src/pages/Profile/Follower/index.tsx @@ -74,8 +74,8 @@ const Follower: SFC = ({className, type = FollowerType.FOLLOWERS} }; const renderContent = () => { - if (followerList.length) return renderFollowerCards(); - return {emptyText}; + if (!followerList.length && !isLoading) return {emptyText}; + return renderFollowerCards(); }; const renderFollowerCards = () => { diff --git a/src/pages/Profile/Posts/index.tsx b/src/pages/Profile/Posts/index.tsx index 54f85a10..41bd3cde 100644 --- a/src/pages/Profile/Posts/index.tsx +++ b/src/pages/Profile/Posts/index.tsx @@ -5,6 +5,7 @@ import {useParams} from 'react-router-dom'; import EmptyText from 'components/EmptyText'; import InfiniteScroll from 'components/InfiniteScroll'; import Post from 'components/Post'; +import PostSkeleton from 'components/Post/Skeleton'; import {getPosts as _getPosts, resetPosts as _resetPosts} from 'dispatchers/posts'; import {getPosts, hasMorePosts, isLoadingPosts} from 'selectors/state'; import {AppDispatch, SFC} from 'types'; @@ -44,19 +45,19 @@ const Posts: SFC = ({className}) => { }; const renderContent = () => { - if (postList.length) { - return ( - - - {postList.map((post) => ( - - ))} - - - ); + if (!postList.length && !isLoading) { + return No posts to display.; } - - return No posts to display.; + return ( + + + {isLoading && } + {postList.map((post) => ( + + ))} + + + ); }; return {renderContent()}; diff --git a/src/pages/University/Courses/Skeleton/Styles.ts b/src/pages/University/Courses/Skeleton/Styles.ts new file mode 100644 index 00000000..13e9cf57 --- /dev/null +++ b/src/pages/University/Courses/Skeleton/Styles.ts @@ -0,0 +1,49 @@ +import Skeleton from 'react-loading-skeleton'; +import styled from 'styled-components'; + +export const TextSkeleton = styled(Skeleton)<{$width?: string}>` + margin-top: 0.6rem; + width: ${({$width}) => $width}; +`; + +export const BoxSkeleton = styled(Skeleton)` + border-radius: 14px; + margin-top: 0px; + min-height: 150px; +`; +export const Card = styled.div` + border-radius: 14px; + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 340px; +`; + +export const CardFooter = styled.div` + align-items: center; + border-radius: 5px; + display: flex; + height: 100%; + justify-content: space-between; + width: 100%; +`; + +export const Left = styled.div` + align-items: center; + border-radius: 5px; + display: flex; + height: 100%; + margin-right: 0.6rem; + width: 100%; +`; +export const Right = styled.div` + float: inline-block; + width: 40%; +`; +export const Text = styled.div` + margin-left: 10px; + width: 100%; +`; + +export const CardBody = styled.div``; +export const Div = styled.div``; diff --git a/src/pages/University/Courses/Skeleton/index.tsx b/src/pages/University/Courses/Skeleton/index.tsx new file mode 100644 index 00000000..7469f6a0 --- /dev/null +++ b/src/pages/University/Courses/Skeleton/index.tsx @@ -0,0 +1,58 @@ +import {Col} from 'styles/components/GridStyle'; +import {SFC} from 'types'; +import Skeleton from 'react-loading-skeleton'; +import * as S from './Styles'; + +interface CourseSkeletonProps { + dataLength: number; +} + +const CourseSkeleton: SFC = ({dataLength}) => { + const cardBody = () => { + return ( + <> + + + + + + + ); + }; + const cardFooter = () => { + return ( + <> + + + + + + + + + + + + + + + + ); + }; + return ( + <> + {Array(dataLength) + .fill(0) + .map((_, index) => ( + + + {cardBody()} + {cardFooter()} + + + ))} + + ); +}; + +export default CourseSkeleton; diff --git a/src/pages/University/Courses/index.tsx b/src/pages/University/Courses/index.tsx index 5a045189..d67dff43 100644 --- a/src/pages/University/Courses/index.tsx +++ b/src/pages/University/Courses/index.tsx @@ -5,7 +5,7 @@ import LeavesEmptyState from 'assets/leaves-empty-state.png'; import Button from 'components/Button'; import EmptyPage from 'components/EmptyPage'; import InfiniteScroll from 'components/InfiniteScroll'; -import Loader from 'components/Loader'; +import CourseSkeleton from './Skeleton'; import {getCourses as _getCourses, resetCourses as _resetCourses} from 'dispatchers/courses'; import {PublicationStatus} from 'enums'; import {useToggle} from 'hooks'; @@ -66,28 +66,24 @@ const Courses: SFC = ({className, selfCourses = false}) => { }; const renderContent = () => { - if (isLoading && isInitialLoading) { - return ; + if (!isLoading && !isInitialLoading && !courseList.length) { + return ; } - - if (courseList.length) { - return ( - - {renderSectionSubHeading()} - - {courseList.map((course) => { - return ( - - - - ); - })} - - - ); - } - - return ; + return ( + + {renderSectionSubHeading()} + + {isLoading && isInitialLoading && } + {courseList.map((course) => { + return ( + + + + ); + })} + + + ); }; const renderCourseModal = () => { diff --git a/src/styles/colors.ts b/src/styles/colors.ts index fadd9be1..02af7d2e 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -15,6 +15,7 @@ const colors = { secondary: '#536471', white: '#ffffff', whiteHover: '#f3f4f6', + whiteSmoke: '#ebebeb', palette: { blue: { '100': '#d1e9f5',