Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ type Props = {
const TournamentFeed: FC<Props> = ({ tournament }) => {
const searchParams = useSearchParams();
const questionFilters = generateFiltersFromSearchParams(
Object.fromEntries(searchParams)
Object.fromEntries(searchParams),
{ withoutPageParam: true }
);
const pageFilters: PostsParams = {
statuses: PostStatus.APPROVED,
Expand All @@ -46,7 +47,8 @@ const TournamentFeed: FC<Props> = ({ tournament }) => {
setBannerisVisible(true);
}
}, [questions, setBannerisVisible, tournament]);

const relevantParams = Object.fromEntries(searchParams);
const { page, ...otherParams } = relevantParams;
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
Expand All @@ -58,7 +60,7 @@ const TournamentFeed: FC<Props> = ({ tournament }) => {
const { questions } = (await fetchPosts(
pageFilters,
0,
POSTS_PER_PAGE
(!isNaN(Number(page)) ? Number(page) : 1) * POSTS_PER_PAGE
)) as { questions: PostWithForecasts[]; count: number };

setQuestions(questions);
Expand All @@ -70,9 +72,10 @@ const TournamentFeed: FC<Props> = ({ tournament }) => {
setIsLoading(false);
}
};

fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);
}, [JSON.stringify(otherParams)]);

return isLoading ? (
<LoadingIndicator className="mx-auto h-8 w-24 text-gray-600 dark:text-gray-600-dark" />
Expand Down
9 changes: 7 additions & 2 deletions front_end/src/app/(main)/questions/helpers/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
POST_NOT_FORECASTER_ID_FILTER,
POST_ORDER_BY_FILTER,
POST_STATUS_FILTER,
POST_PAGE_FILTER,
POST_TAGS_FILTER,
POST_TEXT_SEARCH_FILTER,
POST_TOPIC_FILTER,
Expand All @@ -34,7 +35,6 @@ import {
PostForecastType,
PostStatus,
} from "@/types/post";
import { Category, Tag } from "@/types/projects";
import { QuestionOrder, QuestionType } from "@/types/question";
import { CurrentUser } from "@/types/users";

Expand Down Expand Up @@ -65,15 +65,20 @@ export const POST_STATUS_LABEL_MAP = {
type FiltersFromSearchParamsOptions = {
defaultOrderBy?: string;
defaultForMainFeed?: boolean;
withoutPageParam?: boolean;
};

export function generateFiltersFromSearchParams(
searchParams: SearchParams,
options: FiltersFromSearchParamsOptions = {}
): PostsParams {
const { defaultOrderBy, defaultForMainFeed } = options;
const { defaultOrderBy, defaultForMainFeed, withoutPageParam } = options;
const filters: PostsParams = {};

if (!withoutPageParam && typeof searchParams[POST_PAGE_FILTER] === "string") {
filters.page = Number(searchParams[POST_PAGE_FILTER]);
}

if (typeof searchParams[POST_TEXT_SEARCH_FILTER] === "string") {
filters.search = searchParams[POST_TEXT_SEARCH_FILTER];
}
Expand Down
66 changes: 66 additions & 0 deletions front_end/src/components/posts_feed/feed_scroll_restoration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { usePathname } from "next/navigation";
import { FC, useEffect } from "react";

import useSearchParams from "@/hooks/use_search_params";
import { PostWithForecasts } from "@/types/post";

type Props = {
initialQuestions: PostWithForecasts[];
pageNumber: number;
};
const PostsFeedScrollRestoration: FC<Props> = ({
initialQuestions,
pageNumber,
}) => {
const pathname = usePathname();
const { params } = useSearchParams();
const fullPathname = `${pathname}${params.toString() ? `?${params.toString()}` : ""}`;

useEffect(() => {
const cacheKey = `feed-scroll-restoration`;
let timeoutId = undefined;
const saveScrollPosition = () => {
const currentScroll = window.scrollY;
if (currentScroll > 0) {
sessionStorage.setItem(
cacheKey,
JSON.stringify({
scrollPathName: fullPathname,
scrollPosition: currentScroll.toString(),
})
);
}
};

const savedScrollData = sessionStorage.getItem(cacheKey);
const parsedScrollData = savedScrollData ? JSON.parse(savedScrollData) : {};
const { scrollPathName, scrollPosition } = parsedScrollData;
if (
scrollPosition &&
initialQuestions.length > 0 &&
!!pageNumber &&
scrollPathName === fullPathname
) {
timeoutId = setTimeout(() => {
window.scrollTo({
top: parseInt(scrollPosition),
behavior: "smooth",
});

sessionStorage.removeItem(cacheKey);
window.addEventListener("scrollend", saveScrollPosition);
}, 1000);
} else {
window.addEventListener("scrollend", saveScrollPosition);
}

return () => {
window.removeEventListener("scrollend", saveScrollPosition);
timeoutId && clearTimeout(timeoutId);
};
}, [fullPathname, initialQuestions.length, pageNumber]);

return null;
};

export default PostsFeedScrollRestoration;
4 changes: 3 additions & 1 deletion front_end/src/components/posts_feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ const AwaitedPostsFeed: FC<Props> = async ({

const { results: questions, count } = await PostsApi.getPostsWithCP({
...filters,
limit: POSTS_PER_PAGE,
limit:
(!isNaN(Number(filters.page)) ? Number(filters.page) : 1) *
POSTS_PER_PAGE,
});

return (
Expand Down
26 changes: 20 additions & 6 deletions front_end/src/components/posts_feed/paginated_feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import NewsCard from "@/components/news_card";
import PostCard from "@/components/post_card";
import Button from "@/components/ui/button";
import LoadingIndicator from "@/components/ui/loading_indicator";
import { POSTS_PER_PAGE } from "@/constants/posts_feed";
import { POSTS_PER_PAGE, POST_PAGE_FILTER } from "@/constants/posts_feed";
import useSearchParams from "@/hooks/use_search_params";
import { PostsParams } from "@/services/posts";
import { PostWithForecasts, PostWithNotebook } from "@/types/post";
import { logError } from "@/utils/errors";

import EmptyCommunityFeed from "./empty_community_feed";
import PostsFeedScrollRestoration from "./feed_scroll_restoration";
import InReviewBox from "./in_review_box";
import { FormErrorMessage } from "../ui/form_field";

Expand All @@ -34,10 +36,15 @@ const PaginatedPostsFeed: FC<Props> = ({
isCommunity,
}) => {
const t = useTranslations();

const { params, setParam, shallowNavigateToSearchParams } = useSearchParams();
const pageNumber = Number(params.get(POST_PAGE_FILTER));
const [paginatedPosts, setPaginatedPosts] =
useState<PostWithForecasts[]>(initialQuestions);
const [offset, setOffset] = useState(POSTS_PER_PAGE);
const [offset, setOffset] = useState(
!isNaN(pageNumber) && pageNumber > 0
? pageNumber * POSTS_PER_PAGE
: POSTS_PER_PAGE
);
const [hasMoreData, setHasMoreData] = useState(
initialQuestions.length >= POSTS_PER_PAGE
);
Expand Down Expand Up @@ -83,16 +90,19 @@ const PaginatedPostsFeed: FC<Props> = ({
}

if (!hasNextPage) setHasMoreData(false);
setPaginatedPosts((prevPosts) => [...prevPosts, ...newPosts]);
setOffset((prevOffset) => prevOffset + POSTS_PER_PAGE);
if (!!newPosts.length) {
setPaginatedPosts((prevPosts) => [...prevPosts, ...newPosts]);
setParam(POST_PAGE_FILTER, `${offset / POSTS_PER_PAGE + 1}`, false);
setOffset((prevOffset) => prevOffset + POSTS_PER_PAGE);
shallowNavigateToSearchParams();
}
} catch (err) {
logError(err);
const error = err as Error & { digest?: string };
setError(error);
} finally {
setIsLoading(false);
}
setIsLoading(false);
}
};

Expand Down Expand Up @@ -124,6 +134,10 @@ const PaginatedPostsFeed: FC<Props> = ({
{paginatedPosts.map((p) => (
<Fragment key={p.id}>{renderPost(p)}</Fragment>
))}
<PostsFeedScrollRestoration
pageNumber={pageNumber}
initialQuestions={initialQuestions}
/>
</div>

{hasMoreData ? (
Expand Down
14 changes: 12 additions & 2 deletions front_end/src/components/posts_filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { faCircleXmark } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { sendGAEvent } from "@next/third-parties/google";
import { useTranslations } from "next-intl";
import { FC, useMemo } from "react";
import { FC, useEffect, useMemo } from "react";

import { getFilterChipColor } from "@/app/(main)/questions/helpers/filters";
import PopoverFilter from "@/components/popover_filter";
Expand All @@ -18,6 +18,7 @@ import Chip from "@/components/ui/chip";
import Listbox, { SelectOption } from "@/components/ui/listbox";
import {
POST_ORDER_BY_FILTER,
POST_PAGE_FILTER,
POST_TEXT_SEARCH_FILTER,
} from "@/constants/posts_feed";
import useSearchInputState from "@/hooks/use_search_input_state";
Expand Down Expand Up @@ -102,6 +103,12 @@ const PostsFilters: FC<Props> = ({

return [filters, activeFilters];
}, [filters]);

// reset page param after applying new filters
useEffect(() => {
deleteParam(POST_PAGE_FILTER, false);
}, [filters, deleteParam]);

const handleOrderChange = (order: QuestionOrder) => {
const withNavigation = false;

Expand Down Expand Up @@ -178,7 +185,10 @@ const PostsFilters: FC<Props> = ({
<div className="block">
<SearchInput
value={search}
onChange={(e) => setSearch(e.target.value)}
onChange={(e) => {
deleteParam(POST_PAGE_FILTER, true);
setSearch(e.target.value);
}}
onErase={eraseSearch}
placeholder={t("questionSearchPlaceholder")}
/>
Expand Down
1 change: 1 addition & 0 deletions front_end/src/constants/posts_feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum FeedType {
COMMUNITIES = "communities",
}

export const POST_PAGE_FILTER = "page";
export const POST_TOPIC_FILTER = "topic";
export const POST_TEXT_SEARCH_FILTER = "search";
export const POST_TYPE_FILTER = "forecast_type";
Expand Down
8 changes: 5 additions & 3 deletions front_end/src/hooks/use_search_input_state.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";

import { POST_ORDER_BY_FILTER } from "@/constants/posts_feed";
import { POST_ORDER_BY_FILTER, POST_PAGE_FILTER } from "@/constants/posts_feed";
import useDebounce from "@/hooks/use_debounce";
import useSearchParams from "@/hooks/use_search_params";
import { QuestionOrder } from "@/types/question";
Expand Down Expand Up @@ -30,7 +30,8 @@ const useSearchInputState = (paramName: string, config?: Config) => {
if (withNavigation && !params.get(POST_ORDER_BY_FILTER)) {
setParam(POST_ORDER_BY_FILTER, QuestionOrder.RankDesc, withNavigation);
}

// Auto-remove page filter on input change
deleteParam(POST_PAGE_FILTER, false);
setParam(paramName, debouncedSearch, withNavigation);
} else {
// Auto-remove -rank ordering for server search
Expand All @@ -40,7 +41,8 @@ const useSearchInputState = (paramName: string, config?: Config) => {
) {
deleteParam(POST_ORDER_BY_FILTER, withNavigation);
}

// Auto-remove page filter on input change
deleteParam(POST_PAGE_FILTER, false);
deleteParam(paramName, withNavigation);
}

Expand Down
1 change: 1 addition & 0 deletions front_end/src/services/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { get, post, put } from "@/utils/fetch";
import { encodeQueryParams } from "@/utils/navigation";

export type PostsParams = PaginationParams & {
page?: number;
topic?: string;
answered_by_me?: boolean;
search?: string;
Expand Down