diff --git a/front_end/src/app/(main)/(home)/components/research_and_updates.tsx b/front_end/src/app/(main)/(home)/components/research_and_updates.tsx index 7e909285a5..a712745b8d 100644 --- a/front_end/src/app/(main)/(home)/components/research_and_updates.tsx +++ b/front_end/src/app/(main)/(home)/components/research_and_updates.tsx @@ -10,6 +10,7 @@ import imagePlaceholder from "@/app/assets/images/tournament.webp"; import PostsApi from "@/services/posts"; import { PostWithNotebook } from "@/types/post"; import { getNotebookSummary } from "@/utils/questions"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { postIds: number[]; @@ -91,4 +92,4 @@ const ResearchAndUpdatesBlock: FC = async ({ postIds }) => { ); }; -export default ResearchAndUpdatesBlock; +export default WithServerComponentErrorBoundary(ResearchAndUpdatesBlock); diff --git a/front_end/src/app/(main)/(home)/components/tournaments_block.tsx b/front_end/src/app/(main)/(home)/components/tournaments_block.tsx index de7f11c305..55742ea6df 100644 --- a/front_end/src/app/(main)/(home)/components/tournaments_block.tsx +++ b/front_end/src/app/(main)/(home)/components/tournaments_block.tsx @@ -5,6 +5,7 @@ import { getTranslations } from "next-intl/server"; import { FC } from "react"; import TournamentCard from "@/components/tournament_card"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; import ProjectsApi from "@/services/projects"; import { Tournament, TournamentType } from "@/types/projects"; @@ -60,4 +61,4 @@ const TournamentsBlock: FC = async ({ postSlugs }) => { ); }; -export default TournamentsBlock; +export default WithServerComponentErrorBoundary(TournamentsBlock); diff --git a/front_end/src/app/(main)/(leaderboards)/contributions/components/contributions_hero.tsx b/front_end/src/app/(main)/(leaderboards)/contributions/components/contributions_hero.tsx index f4d2fa114f..d755cb41ae 100644 --- a/front_end/src/app/(main)/(leaderboards)/contributions/components/contributions_hero.tsx +++ b/front_end/src/app/(main)/(leaderboards)/contributions/components/contributions_hero.tsx @@ -16,6 +16,7 @@ import { SCORING_DURATION_FILTER, SCORING_YEAR_FILTER, } from "../../search_params"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { category: CategoryKey; @@ -72,12 +73,13 @@ const ContributionsHero: FC = ({ year, duration, category, userId }) => { ); }; -const AwaitedUserHeader: FC<{ userId: number }> = async ({ userId }) => { - const t = await getTranslations(); - const profile = await ProfileApi.getProfileById(userId); +const AwaitedUserHeader: FC<{ userId: number }> = + WithServerComponentErrorBoundary(async ({ userId }) => { + const t = await getTranslations(); + const profile = await ProfileApi.getProfileById(userId); - return {profile.username ?? t("user")}; -}; + return {profile.username ?? t("user")}; + }); const UserHeader: FC = ({ children }) => (

diff --git a/front_end/src/app/(main)/(leaderboards)/contributions/components/global_contributions.tsx b/front_end/src/app/(main)/(leaderboards)/contributions/components/global_contributions.tsx index 254b769073..4454bed8d3 100644 --- a/front_end/src/app/(main)/(leaderboards)/contributions/components/global_contributions.tsx +++ b/front_end/src/app/(main)/(leaderboards)/contributions/components/global_contributions.tsx @@ -3,6 +3,7 @@ import { FC } from "react"; import ContributionsTable from "@/app/(main)/(leaderboards)/contributions/components/contributions_table"; import LeaderboardApi from "@/services/leaderboard"; import { CategoryKey, LeaderboardType } from "@/types/scoring"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { startTime: string; @@ -19,21 +20,21 @@ const GlobalContributions: FC = async ({ startTime, category, }) => { - const contributionsDetails = await LeaderboardApi.getContributions({ - type: "global", - leaderboardType, - userId, - startTime, - endTime, - }); + const contributionsDetails = await LeaderboardApi.getContributions({ + type: "global", + leaderboardType, + userId, + startTime, + endTime, + }); - return ( - - ); + return ( + + ); }; -export default GlobalContributions; +export default WithServerComponentErrorBoundary(GlobalContributions); diff --git a/front_end/src/app/(main)/(leaderboards)/contributions/components/project_contributions.tsx b/front_end/src/app/(main)/(leaderboards)/contributions/components/project_contributions.tsx index 25ce312dc7..5e8657b924 100644 --- a/front_end/src/app/(main)/(leaderboards)/contributions/components/project_contributions.tsx +++ b/front_end/src/app/(main)/(leaderboards)/contributions/components/project_contributions.tsx @@ -6,6 +6,7 @@ import InfoToggle from "@/components/ui/info_toggle"; import SectionToggle from "@/components/ui/section_toggle"; import LeaderboardApi from "@/services/leaderboard"; import { Tournament } from "@/types/projects"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { project: Tournament; @@ -13,169 +14,173 @@ type Props = { }; const ProjectContributions: FC = async ({ project, userId }) => { - const t = await getTranslations(); - const contributionsDetails = await LeaderboardApi.getContributions({ - type: "project", - userId, - projectId: project.id, - }); + const t = await getTranslations(); + const contributionsDetails = await LeaderboardApi.getContributions({ + type: "project", + userId, + projectId: project.id, + }); - return ( - - {!!contributionsDetails.contributions.length && ( - - - - - - {project.score_type === "relative_legacy_tournament" && ( + return ( + + {!!contributionsDetails.contributions.length && ( +
- {t("Question")} - {t("score")}
+ + + - )} - - - - {contributionsDetails.contributions.map((contribution, i) => ( - - + )} + + + + {contributionsDetails.contributions.map((contribution, i) => ( + + + + {project.score_type === "relative_legacy_tournament" && ( + + )} + + ))} + + + + + + + + + + + {project.score_type === "relative_legacy_tournament" && ( )} - ))} - + +
+ {t("Question")} + - {t("coverage")} + {t("score")}
- - {contribution.question_title} - + {project.score_type === "relative_legacy_tournament" && ( + + {t("coverage")} +
+ + {contribution.question_title} + + + {contribution.score ? contribution.score.toFixed(2) : "-"} + + {contribution.coverage + ? `${(contribution.coverage * 100).toFixed(0)}%` + : "0%"} +
+ {t("totalTake")} + + {contributionsDetails.leaderboard_entry.take + ? `${contributionsDetails.leaderboard_entry.take.toFixed(3)}` + : "-"}
+ {t("totalScore")} + - {contribution.score ? contribution.score.toFixed(2) : "-"} + {contributionsDetails.leaderboard_entry.score + ? `${contributionsDetails.leaderboard_entry.score.toFixed(2)}` + : "-"} - {contribution.coverage - ? `${(contribution.coverage * 100).toFixed(0)}%` + {contributionsDetails.leaderboard_entry.coverage + ? `${(contributionsDetails.leaderboard_entry.coverage * 100).toFixed(2)}%` : "0%"}
+ )} - - - {t("totalTake")} - - {contributionsDetails.leaderboard_entry.take - ? `${contributionsDetails.leaderboard_entry.take.toFixed(3)}` - : "-"} - - - - - - - {t("totalScore")} - - - {contributionsDetails.leaderboard_entry.score - ? `${contributionsDetails.leaderboard_entry.score.toFixed(2)}` - : "-"} - + +
+
+
+
{t("score")}
+ {project.score_type === "peer_tournament" ? ( +
+ {t.rich("peerScoreInfo", { + link: (chunks) => ( + + {chunks} + + ), + })} +
+ ) : ( +
+ {t.rich("relativeScoreInfo", { + link: (chunks) => ( + + {chunks} + + ), + })} +
+ )} +
+
+
+ {t("totalScore")} +
+ {project.score_type === "peer_tournament" ? ( +
+ {t.rich("totalPeerScoreInfo", { + link: (chunks) => ( + + {chunks} + + ), + })} +
+ ) : ( +
+ {t.rich("totalRelativeScoreInfo", { + link: (chunks) => ( + + {chunks} + + ), + })} +
+ )} +
{project.score_type === "relative_legacy_tournament" && ( - - {contributionsDetails.leaderboard_entry.coverage - ? `${(contributionsDetails.leaderboard_entry.coverage * 100).toFixed(2)}%` - : "0%"} - - )} - - - - )} - - -
-
-
-
{t("score")}
- {project.score_type === "peer_tournament" ? ( -
- {t.rich("peerScoreInfo", { - link: (chunks) => ( - - {chunks} - - ), - })} -
- ) : ( -
- {t.rich("relativeScoreInfo", { - link: (chunks) => ( - - {chunks} - - ), - })} -
+
+
+ {t("coverage")} +
+
{t("relativeCoverageInfo")}
+
)} -
-
-
- {t("totalScore")} -
- {project.score_type === "peer_tournament" ? ( -
- {t.rich("totalPeerScoreInfo", { - link: (chunks) => ( - - {chunks} - - ), - })} -
- ) : ( -
- {t.rich("totalRelativeScoreInfo", { - link: (chunks) => ( - - {chunks} - - ), - })} -
- )} -
- {project.score_type === "relative_legacy_tournament" && (
- {t("coverage")} + {t("totalTake")}
-
{t("relativeCoverageInfo")}
+ {project.score_type === "peer_tournament" ? ( +
{t("peerTakeInfo")}
+ ) : ( +
{t("relativeTakeInfo")}
+ )}
- )} -
-
- {t("totalTake")} -
- {project.score_type === "peer_tournament" ? ( -
{t("peerTakeInfo")}
- ) : ( -
{t("relativeTakeInfo")}
- )} -
-
-
-
- - ); +
+
+
+
+ ); }; -export default ProjectContributions; +export default WithServerComponentErrorBoundary(ProjectContributions); diff --git a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/global_leaderboard.tsx b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/global_leaderboard.tsx index 3012508807..cb8692eb14 100644 --- a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/global_leaderboard.tsx +++ b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/global_leaderboard.tsx @@ -4,6 +4,7 @@ import LeaderboardApi from "@/services/leaderboard"; import { CategoryKey, LeaderboardType } from "@/types/scoring"; import LeaderboardTable from "./leaderboard_table"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { startTime: string; @@ -24,21 +25,21 @@ const GlobalLeaderboard: FC = async ({ category, cardSized, }) => { - const leaderboardDetails = await LeaderboardApi.getGlobalLeaderboard( - startTime, - endTime, - leaderboardType - ); + const leaderboardDetails = await LeaderboardApi.getGlobalLeaderboard( + startTime, + endTime, + leaderboardType + ); - return ( - - ); + return ( + + ); }; -export default GlobalLeaderboard; +export default WithServerComponentErrorBoundary(GlobalLeaderboard); diff --git a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard.tsx b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard.tsx index 676bbb9d29..4adc0fd826 100644 --- a/front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard.tsx +++ b/front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard.tsx @@ -7,6 +7,7 @@ import LeaderboardApi from "@/services/leaderboard"; import { LeaderboardType } from "@/types/scoring"; import ProjectLeaderboardTable from "./project_leaderboard_table"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { projectId: number; @@ -23,37 +24,37 @@ const ProjectLeaderboard: FC = async ({ isQuestionSeries, userId, }) => { - const leaderboardDetails = await LeaderboardApi.getProjectLeaderboard( - projectId, - leaderboardType - ); - - if (!leaderboardDetails || !leaderboardDetails.entries.length) { - return null; - } - - const prizePoolValue = !isNaN(Number(prizePool)) ? Number(prizePool) : 0; - - const t = await getTranslations(); - - const leaderboardTitle = isQuestionSeries - ? t("openLeaderboard") - : t("leaderboard"); - - return ( - - - - ); + const leaderboardDetails = await LeaderboardApi.getProjectLeaderboard( + projectId, + leaderboardType + ); + + if (!leaderboardDetails || !leaderboardDetails.entries.length) { + return null; + } + + const prizePoolValue = !isNaN(Number(prizePool)) ? Number(prizePool) : 0; + + const t = await getTranslations(); + + const leaderboardTitle = isQuestionSeries + ? t("openLeaderboard") + : t("leaderboard"); + + return ( + + + + ); }; -export default ProjectLeaderboard; +export default WithServerComponentErrorBoundary(ProjectLeaderboard); diff --git a/front_end/src/app/(main)/(leaderboards)/medals/components/medals_page.tsx b/front_end/src/app/(main)/(leaderboards)/medals/components/medals_page.tsx index 81d72a4a42..ace01acd77 100644 --- a/front_end/src/app/(main)/(leaderboards)/medals/components/medals_page.tsx +++ b/front_end/src/app/(main)/(leaderboards)/medals/components/medals_page.tsx @@ -1,21 +1,14 @@ import classNames from "classnames"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { FC } from "react"; -import Tooltip from "@/components/ui/tooltip"; import LeaderboardApi from "@/services/leaderboard"; import MedalIcon from "../../components/medal_icon"; import { RANKING_CATEGORIES } from "../../ranking_categories"; -import { CONTRIBUTIONS_USER_FILTER } from "../../contributions/search_params"; - -import { - SCORING_CATEGORY_FILTER, - SCORING_YEAR_FILTER, -} from "../../search_params"; import { getMedalCategories } from "../helpers/medal_categories"; import { getMedalDisplayTitle } from "../helpers/medal_title"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { profileId: number; @@ -60,12 +53,7 @@ const MedalsPage: FC = async ({ profileId }) => { {!!category.medals.length ? ( category.medals.map((medal, index) => { return ( - @@ -89,7 +77,7 @@ const MedalsPage: FC = async ({ profileId }) => { - + ); }) ) : ( @@ -105,4 +93,4 @@ const MedalsPage: FC = async ({ profileId }) => { ); }; -export default MedalsPage; +export default WithServerComponentErrorBoundary(MedalsPage); diff --git a/front_end/src/app/(main)/(leaderboards)/medals/components/medals_widget.tsx b/front_end/src/app/(main)/(leaderboards)/medals/components/medals_widget.tsx index 90f82eb14a..67f3963dc1 100644 --- a/front_end/src/app/(main)/(leaderboards)/medals/components/medals_widget.tsx +++ b/front_end/src/app/(main)/(leaderboards)/medals/components/medals_widget.tsx @@ -11,85 +11,88 @@ import { RANKING_CATEGORIES } from "../../ranking_categories"; import { SCORING_CATEGORY_FILTER } from "../../search_params"; import { getMedalCategories } from "../helpers/medal_categories"; import { getMedalDisplayTitle } from "../helpers/medal_title"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { profileId: number; }; const MedalsWidget: FC = async ({ profileId }) => { - const t = await getTranslations(); + const t = await getTranslations(); - const userMedals = await LeaderboardApi.getUserMedals(profileId); - const categories = getMedalCategories(userMedals, true); + const userMedals = await LeaderboardApi.getUserMedals(profileId); + const categories = getMedalCategories(userMedals, true); - return ( -
-
-

- {t("medals")} -

- - View All - -
-
- {categories?.map((category, index) => ( -
+
+

+ {t("medals")} +

+ - +
+
+ {categories?.map((category, index) => ( +
- {t(RANKING_CATEGORIES[category.name].translationKey)} - -
- {!!category.medals.length ? ( - category.medals.map((medal, index) => { - const tooltipContent = ( -
- - {getMedalDisplayTitle(medal)} - - - {t("rank")}:{" "} - #{medal.rank}{" "} - {t("outOfRank", { total: medal.totalEntries })} - -
- ); - - return ( - -
- - - - ); - }) - ) : ( - - {t("noMedals")} + + + {t(RANKING_CATEGORIES[category.name].translationKey)} - )} + +
+ {!!category.medals.length ? ( + category.medals.map((medal, index) => { + const tooltipContent = ( +
+ + {getMedalDisplayTitle(medal)} + + + {t("rank")}:{" "} + #{medal.rank}{" "} + {t("outOfRank", { total: medal.totalEntries })} + +
+ ); + + return ( + + + + + + ); + }) + ) : ( + + {t("noMedals")} + + )} +
-
- ))} -
-
- ); + ))} + + + ); }; -export default MedalsWidget; +export default WithServerComponentErrorBoundary(MedalsWidget); diff --git a/front_end/src/app/(main)/experiments/elections/components/card_forecast.tsx b/front_end/src/app/(main)/experiments/elections/components/card_forecast.tsx index 28d3d29627..0bf7e7f946 100644 --- a/front_end/src/app/(main)/experiments/elections/components/card_forecast.tsx +++ b/front_end/src/app/(main)/experiments/elections/components/card_forecast.tsx @@ -3,6 +3,7 @@ import { FC } from "react"; import ForecastCard from "@/components/forecast_card"; import PostsApi from "@/services/posts"; import { TimelineChartZoomOption } from "@/types/charts"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { postId: number; @@ -20,4 +21,4 @@ const CardForecast: FC = async ({ postId }) => { ); }; -export default CardForecast; +export default WithServerComponentErrorBoundary(CardForecast); diff --git a/front_end/src/app/(main)/experiments/elections/components/expected_electoral_votes_forecast/index.tsx b/front_end/src/app/(main)/experiments/elections/components/expected_electoral_votes_forecast/index.tsx index d23ad39a33..4e4b54d4bf 100644 --- a/front_end/src/app/(main)/experiments/elections/components/expected_electoral_votes_forecast/index.tsx +++ b/front_end/src/app/(main)/experiments/elections/components/expected_electoral_votes_forecast/index.tsx @@ -11,6 +11,7 @@ import { Candle } from "@/types/experiments"; import { QuestionType, QuestionWithForecasts } from "@/types/question"; import { getDisplayValue } from "@/utils/charts"; import { computeQuartilesFromCDF } from "@/utils/math"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { democratPostId: number; @@ -150,4 +151,4 @@ function getForecastData( }; } -export default ExpectedElectoralVotesForecast; +export default WithServerComponentErrorBoundary(ExpectedElectoralVotesForecast); diff --git a/front_end/src/app/(main)/experiments/elections/components/state_by_forecast/index.tsx b/front_end/src/app/(main)/experiments/elections/components/state_by_forecast/index.tsx index ba014881e6..7b3997731d 100644 --- a/front_end/src/app/(main)/experiments/elections/components/state_by_forecast/index.tsx +++ b/front_end/src/app/(main)/experiments/elections/components/state_by_forecast/index.tsx @@ -17,6 +17,7 @@ import { extractQuestionGroupName } from "@/utils/questions"; import MiddleVotesArrow from "./middle_votes_arrow"; import StateByForecastCharts from "./state_by_forecast_charts"; import { US_MAP_AREAS } from "./us_areas"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { questionGroupId: number; @@ -226,4 +227,4 @@ function getDemocratRepublicanPrediction({ }; } -export default StateByForecast; +export default WithServerComponentErrorBoundary(StateByForecast); diff --git a/front_end/src/app/(main)/questions/[id]/components/sidebar/news_match/index.tsx b/front_end/src/app/(main)/questions/[id]/components/sidebar/news_match/index.tsx index 9d115ca944..62c58089ed 100644 --- a/front_end/src/app/(main)/questions/[id]/components/sidebar/news_match/index.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/sidebar/news_match/index.tsx @@ -3,21 +3,20 @@ import { FC } from "react"; import PostsApi from "@/services/posts"; import NewsMatchDrawer from "./news_match_drawer"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; interface Props { questionId: number; } -const fetchArticles = async (postId: number) => { - return await PostsApi.getRelatedNews(postId); -}; - const NewsMatch: FC = async ({ questionId }) => { - const articles = await fetchArticles(questionId); + const articles = await PostsApi.getRelatedNews(questionId); if (articles.length > 0) { return ; + } else { + return null; } }; -export default NewsMatch; +export default WithServerComponentErrorBoundary(NewsMatch); diff --git a/front_end/src/app/(main)/questions/[id]/components/sidebar/similar_questions/index.tsx b/front_end/src/app/(main)/questions/[id]/components/sidebar/similar_questions/index.tsx index 5cf1b24ee6..06524b2081 100644 --- a/front_end/src/app/(main)/questions/[id]/components/sidebar/similar_questions/index.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/sidebar/similar_questions/index.tsx @@ -5,6 +5,7 @@ import { PostStatus } from "@/types/post"; import { QuestionOrder } from "@/types/question"; import SimilarQuestionsDrawer from "./similar_questions_drawer"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; type Props = { post_id: number; @@ -23,4 +24,4 @@ const SimilarQuestions: FC = async ({ post_id }) => { return ; }; -export default SimilarQuestions; +export default WithServerComponentErrorBoundary(SimilarQuestions) as FC; diff --git a/front_end/src/app/(main)/questions/discovery/components/tags_discovery.tsx b/front_end/src/app/(main)/questions/discovery/components/tags_discovery.tsx index 02f837b2c9..2dcdadfa9c 100644 --- a/front_end/src/app/(main)/questions/discovery/components/tags_discovery.tsx +++ b/front_end/src/app/(main)/questions/discovery/components/tags_discovery.tsx @@ -9,6 +9,7 @@ import { SearchParams } from "@/types/navigation"; import DiscoverySection from "./section"; import AwaitedTags from "./tags"; import { TAGS_TEXT_SEARCH_FILTER } from "../constants/tags_feed"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; const getFilters = (searchParams: SearchParams) => { const filters: TagsParams = {}; @@ -40,4 +41,4 @@ const TagsDiscovery: FC<{ searchParams: SearchParams }> = async ({ ); }; -export default TagsDiscovery; +export default WithServerComponentErrorBoundary(TagsDiscovery); diff --git a/front_end/src/app/(main)/questions/page.tsx b/front_end/src/app/(main)/questions/page.tsx index 12b4840bd5..43ab17a2b3 100644 --- a/front_end/src/app/(main)/questions/page.tsx +++ b/front_end/src/app/(main)/questions/page.tsx @@ -34,7 +34,7 @@ export default async function Questions({ } > - + diff --git a/front_end/src/components/posts_feed/index.tsx b/front_end/src/components/posts_feed/index.tsx index 0ee317206c..7b3f109bfa 100644 --- a/front_end/src/components/posts_feed/index.tsx +++ b/front_end/src/components/posts_feed/index.tsx @@ -5,13 +5,28 @@ import PaginatedPostsFeed, { } from "@/components/posts_feed/paginated_feed"; import { POSTS_PER_PAGE } from "@/constants/posts_feed"; import PostsApi, { PostsParams } from "@/services/posts"; +import WithServerComponentErrorBoundary from "@/components/server_component_error_boundary"; +import { Topic } from "@/types/projects"; type Props = { filters: PostsParams; type?: PostsFeedType; + topics?: Topic[]; }; -const AwaitedPostsFeed: FC = async ({ filters, type }) => { +const AwaitedPostsFeed: FC = async ({ filters, type, topics }) => { + if ( + topics && + filters.topic && + !topics?.some((topic) => topic.slug === filters.topic) + ) { + return ( +
+ Such topic does not exist +
+ ); + } + const { results: questions, count } = await PostsApi.getPostsWithCP({ ...filters, limit: POSTS_PER_PAGE, @@ -27,4 +42,4 @@ const AwaitedPostsFeed: FC = async ({ filters, type }) => { ); }; -export default AwaitedPostsFeed; +export default WithServerComponentErrorBoundary(AwaitedPostsFeed); diff --git a/front_end/src/components/refresh_button.tsx b/front_end/src/components/refresh_button.tsx new file mode 100644 index 0000000000..ea7b7ca96a --- /dev/null +++ b/front_end/src/components/refresh_button.tsx @@ -0,0 +1,17 @@ +"use client"; + +import React from "react"; +import { useRouter } from "next/navigation"; +import Button from "./ui/button"; + +const RefreshButton: React.FC = () => { + const router = useRouter(); + + return ( + + ); +}; + +export default RefreshButton; diff --git a/front_end/src/components/server_component_error_boundary.tsx b/front_end/src/components/server_component_error_boundary.tsx new file mode 100644 index 0000000000..ffca597bf0 --- /dev/null +++ b/front_end/src/components/server_component_error_boundary.tsx @@ -0,0 +1,32 @@ +import { FC } from "react"; +import RefreshButton from "./refresh_button"; + +const WithServerComponentErrorBoundary =

( + Component: FC

+): FC

=> { + const WrappedComponent = async (props: P) => { + try { + return await Component(props); + } catch (error) { + if (error instanceof Error) { + const { digest } = error as Error & { digest?: string }; + return ( +

+

{digest ?? "Unknown error"}

+ +
+ ); + } + + return ( +
+

Unknown error

+ +
+ ); + } + }; + return WrappedComponent as FC

; +}; + +export default WithServerComponentErrorBoundary;