diff --git a/package-lock.json b/package-lock.json index c8cb542..ba3cfcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "@storybook/node-logger": "^6.3.12", "@storybook/preset-create-react-app": "^3.2.0", "@storybook/react": "^6.3.12", + "@types/lodash.debounce": "^4.0.6", "@types/lodash.throttle": "^4.1.6", "@types/react-copy-to-clipboard": "^5.0.2", "autoprefixer": "^9.8.8", @@ -9612,6 +9613,15 @@ "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==", "dev": true }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz", + "integrity": "sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.throttle": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.6.tgz", @@ -68051,6 +68061,15 @@ "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==", "dev": true }, + "@types/lodash.debounce": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz", + "integrity": "sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.throttle": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.6.tgz", diff --git a/package.json b/package.json index 4f9f345..3286bf5 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@storybook/node-logger": "^6.3.12", "@storybook/preset-create-react-app": "^3.2.0", "@storybook/react": "^6.3.12", + "@types/lodash.debounce": "^4.0.6", "@types/lodash.throttle": "^4.1.6", "@types/react-copy-to-clipboard": "^5.0.2", "autoprefixer": "^9.8.8", diff --git a/src/features/Posts/Components/PostCard/BountyCard.stories.tsx b/src/features/Posts/Components/PostCard/BountyCard/BountyCard.stories.tsx similarity index 100% rename from src/features/Posts/Components/PostCard/BountyCard.stories.tsx rename to src/features/Posts/Components/PostCard/BountyCard/BountyCard.stories.tsx diff --git a/src/features/Posts/Components/PostCard/BountyCard.tsx b/src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx similarity index 98% rename from src/features/Posts/Components/PostCard/BountyCard.tsx rename to src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx index 0bdce44..736116e 100644 --- a/src/features/Posts/Components/PostCard/BountyCard.tsx +++ b/src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx @@ -1,6 +1,6 @@ import VotesCount from "src/Components/VotesCount/VotesCount" import { Bounty } from "src/features/Posts/types" -import Header from "./Header" +import Header from "../Header/Header" import { FiUsers } from "react-icons/fi" import Badge from "src/Components/Badge/Badge" import Button from "src/Components/Button/Button" diff --git a/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx b/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx new file mode 100644 index 0000000..9917276 --- /dev/null +++ b/src/features/Posts/Components/PostCard/Header/Header.Skeleton.tsx @@ -0,0 +1,24 @@ +import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'; +import dayjs from 'dayjs' +import Skeleton from 'react-loading-skeleton'; + +interface Props { + size?: 'sm' | 'md' +} + +export default function HeaderSkeleton({ size = 'md', }: Props) { + + return ( +
+ +
+

+ +

+

+ +

+
+
+ ) +} diff --git a/src/features/Posts/Components/PostCard/Header.tsx b/src/features/Posts/Components/PostCard/Header/Header.tsx similarity index 100% rename from src/features/Posts/Components/PostCard/Header.tsx rename to src/features/Posts/Components/PostCard/Header/Header.tsx diff --git a/src/features/Posts/Components/PostCard/PostCard/PostCard.Skeleton.tsx b/src/features/Posts/Components/PostCard/PostCard/PostCard.Skeleton.tsx new file mode 100644 index 0000000..9fab751 --- /dev/null +++ b/src/features/Posts/Components/PostCard/PostCard/PostCard.Skeleton.tsx @@ -0,0 +1,29 @@ +import Skeleton from "react-loading-skeleton" +import HeaderSkeleton from "../Header/Header.Skeleton" +import Badge from 'src/Components/Badge/Badge' + +export default function PostCardSkeleton() { + return
+
+ +
+
+ +

+ +

+

+ + +

+ +
+
+ +
+ +
+
+
+
+} diff --git a/src/features/Posts/Components/PostCard/PostCard/PostCard.stories.tsx b/src/features/Posts/Components/PostCard/PostCard/PostCard.stories.tsx new file mode 100644 index 0000000..bc31427 --- /dev/null +++ b/src/features/Posts/Components/PostCard/PostCard/PostCard.stories.tsx @@ -0,0 +1,29 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { MOCK_DATA } from 'src/mocks/data'; + +import PostCard from './PostCard'; +import PostCardSkeleton from './PostCard.Skeleton'; + +export default { + title: 'Posts/Components/PostCard', + component: PostCard, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
+ +export const Default = Template.bind({}); +Default.args = { + post: MOCK_DATA['posts'].stories[0] +} + +const LoadingTemplate: ComponentStory = (args) =>
+ +export const Loading = LoadingTemplate.bind({}); +Loading.args = { +} + + diff --git a/src/features/Posts/Components/PostCard/PostCard.tsx b/src/features/Posts/Components/PostCard/PostCard/PostCard.tsx similarity index 70% rename from src/features/Posts/Components/PostCard/PostCard.tsx rename to src/features/Posts/Components/PostCard/PostCard/PostCard.tsx index bf0802e..324df10 100644 --- a/src/features/Posts/Components/PostCard/PostCard.tsx +++ b/src/features/Posts/Components/PostCard/PostCard/PostCard.tsx @@ -1,7 +1,7 @@ import { Post, isStory, isBounty, isQuestion } from "src/features/Posts/types" -import BountyCard from "./BountyCard" -import QuestionCard from "./QuestionCard" -import StoryCard from "./StoryCard" +import BountyCard from "../BountyCard/BountyCard" +import QuestionCard from "../QuestionCard/QuestionCard" +import StoryCard from "../StoryCard/StoryCard" type Props = { post: Post diff --git a/src/features/Posts/Components/PostCard/QuestionCard.stories.tsx b/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.stories.tsx similarity index 100% rename from src/features/Posts/Components/PostCard/QuestionCard.stories.tsx rename to src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.stories.tsx diff --git a/src/features/Posts/Components/PostCard/QuestionCard.tsx b/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx similarity index 98% rename from src/features/Posts/Components/PostCard/QuestionCard.tsx rename to src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx index 60b073e..47eca06 100644 --- a/src/features/Posts/Components/PostCard/QuestionCard.tsx +++ b/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx @@ -1,6 +1,6 @@ import VotesCount from "src/Components/VotesCount/VotesCount" import { Question } from "src/features/Posts/types" -import Header from "./Header" +import Header from "../Header/Header" import { FiUsers } from "react-icons/fi" import Badge from "src/Components/Badge/Badge" import Avatar from "src/features/Profiles/Components/Avatar/Avatar" diff --git a/src/features/Posts/Components/PostCard/StoryCard.stories.tsx b/src/features/Posts/Components/PostCard/StoryCard/StoryCard.stories.tsx similarity index 100% rename from src/features/Posts/Components/PostCard/StoryCard.stories.tsx rename to src/features/Posts/Components/PostCard/StoryCard/StoryCard.stories.tsx diff --git a/src/features/Posts/Components/PostCard/StoryCard.tsx b/src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx similarity index 96% rename from src/features/Posts/Components/PostCard/StoryCard.tsx rename to src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx index 10af4e4..aa5f3df 100644 --- a/src/features/Posts/Components/PostCard/StoryCard.tsx +++ b/src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx @@ -1,6 +1,6 @@ import VotesCount from "src/Components/VotesCount/VotesCount" import { Story } from "src/features/Posts/types" -import Header from "./Header" +import Header from "../Header/Header" import { BiComment } from 'react-icons/bi' interface Props { diff --git a/src/features/Posts/Components/PostCard/index.tsx b/src/features/Posts/Components/PostCard/index.tsx index bcdab81..dbbd0ac 100644 --- a/src/features/Posts/Components/PostCard/index.tsx +++ b/src/features/Posts/Components/PostCard/index.tsx @@ -1,2 +1,5 @@ +import PostCard from "./PostCard/PostCard"; -export { } \ No newline at end of file +export { default as PostCardSkeleton } from './PostCard/PostCard.Skeleton' + +export default PostCard; \ No newline at end of file diff --git a/src/features/Posts/Components/PostsList/PostsList.stories.tsx b/src/features/Posts/Components/PostsList/PostsList.stories.tsx index f34894c..4bb226b 100644 --- a/src/features/Posts/Components/PostsList/PostsList.stories.tsx +++ b/src/features/Posts/Components/PostsList/PostsList.stories.tsx @@ -16,7 +16,7 @@ const Template: ComponentStory = (args) =>
export default function PostsList(props: Props) { - const { data, loading } = useFeedQuery() - if (loading) return

Loading

- return ( -
- { - data?.getFeed.map(post => ) + + const reachedBottom = useCallback(() => { + console.log("NEW FETCH") + }, []) + + const { ref } = useReachedBottom(reachedBottom) + + if (props.isLoading) + return
+ {<> + + + + }
+ + return ( +
+ { + props.items?.map(post => ) + } + {props.isFetching && } +
) } diff --git a/src/features/Posts/pages/FeedPage/FeedPage.tsx b/src/features/Posts/pages/FeedPage/FeedPage.tsx index be484bd..282f0f8 100644 --- a/src/features/Posts/pages/FeedPage/FeedPage.tsx +++ b/src/features/Posts/pages/FeedPage/FeedPage.tsx @@ -1,4 +1,5 @@ +import { useFeedQuery } from 'src/graphql' import { MOCK_DATA } from 'src/mocks/data' import PostsList from '../../Components/PostsList/PostsList' import TrendingCard from '../../Components/TrendingCard/TrendingCard' @@ -7,17 +8,19 @@ import SortBy from './SortBy/SortBy' import styles from './styles.module.css' export default function FeedPage() { + + const feedQuery = useFeedQuery() + return (
- + diff --git a/src/utils/hooks/useReachedBottom.ts b/src/utils/hooks/useReachedBottom.ts new file mode 100644 index 0000000..1837ef6 --- /dev/null +++ b/src/utils/hooks/useReachedBottom.ts @@ -0,0 +1,30 @@ +import _debounce from "lodash.debounce"; +import { useEffect, useRef } from "react"; + +export const useReachedBottom = (cb?: () => void, options: Partial<{ offset: number, throttle: number }> = {}) => { + + const { offset = window.innerHeight, throttle = 600 } = options + + const ref = useRef(null); + + + useEffect(() => { + if (!cb) return; + + const cbDebounced = _debounce(cb, throttle) + const listener = () => { + if (!ref.current) return; + const curWindowPosition = window.scrollY + window.innerHeight; + const elTriggerPosition = ref.current.offsetTop + ref.current.scrollHeight - offset; + if (curWindowPosition > elTriggerPosition) cbDebounced(); + } + + document.addEventListener('scroll', listener) + + return () => { + document.removeEventListener('scroll', listener) + } + }, [cb, offset, throttle]) + + return { ref } +} \ No newline at end of file diff --git a/src/utils/interfaces/misc.interfaces.ts b/src/utils/interfaces/misc.interfaces.ts index d810cbb..2831c9c 100644 --- a/src/utils/interfaces/misc.interfaces.ts +++ b/src/utils/interfaces/misc.interfaces.ts @@ -4,4 +4,12 @@ export type Tag = { title: string } + +export type ListProps = { + items?: T[] + isLoading?: boolean; + isFetching?: boolean; + onReachedBottom?: () => void +} + export type Image = string; \ No newline at end of file