diff --git a/frontends/api/src/hooks/channels/index.ts b/frontends/api/src/hooks/channels/index.ts index 549e77778b..cbcbf2c580 100644 --- a/frontends/api/src/hooks/channels/index.ts +++ b/frontends/api/src/hooks/channels/index.ts @@ -55,5 +55,5 @@ export { useChannelsList, useChannelPartialUpdate, useChannelCounts, - channels as channelsKeyFactory, + channels, } diff --git a/frontends/api/src/hooks/learningResources/index.ts b/frontends/api/src/hooks/learningResources/index.ts index b323cee041..745be9f33c 100644 --- a/frontends/api/src/hooks/learningResources/index.ts +++ b/frontends/api/src/hooks/learningResources/index.ts @@ -150,5 +150,5 @@ export { useSchoolsList, useSimilarLearningResources, useVectorSimilarLearningResources, - learningResources as learningResourcesKeyFactory, + learningResources, } diff --git a/frontends/api/src/hooks/newsEvents/index.ts b/frontends/api/src/hooks/newsEvents/index.ts index b6f0d9b26f..9d20868f2d 100644 --- a/frontends/api/src/hooks/newsEvents/index.ts +++ b/frontends/api/src/hooks/newsEvents/index.ts @@ -19,5 +19,5 @@ export { useNewsEventsList, useNewsEventsDetail, NewsEventsListFeedTypeEnum, - newsEvents as newsEventsKeyFactory, + newsEvents, } diff --git a/frontends/api/src/hooks/testimonials/index.ts b/frontends/api/src/hooks/testimonials/index.ts index da98b49afc..9cb3ebc212 100644 --- a/frontends/api/src/hooks/testimonials/index.ts +++ b/frontends/api/src/hooks/testimonials/index.ts @@ -23,8 +23,4 @@ const useTestimonialDetail = (id: number | undefined) => { }) } -export { - useTestimonialDetail, - useTestimonialList, - testimonials as testimonialsKeyFactory, -} +export { useTestimonialDetail, useTestimonialList, testimonials } diff --git a/frontends/api/src/ssr/prefetch.ts b/frontends/api/src/ssr/prefetch.ts index 6078fcafec..67e377e28c 100644 --- a/frontends/api/src/ssr/prefetch.ts +++ b/frontends/api/src/ssr/prefetch.ts @@ -1,13 +1,19 @@ import { QueryClient, dehydrate } from "@tanstack/react-query" import type { Query } from "@tanstack/react-query" -// Utility to avoid repetition in server components -export const prefetch = async (queries: (Query | unknown)[]) => { - const queryClient = new QueryClient() +/* Utility to avoid repetition in server components + * Optionally pass the queryClient returned from a previous prefetch + * where queries are dependent on previous results + */ +export const prefetch = async ( + queries: (Query | unknown)[], + queryClient?: QueryClient, +) => { + queryClient = queryClient || new QueryClient() await Promise.all( queries.map((query) => queryClient.prefetchQuery(query as Query)), ) - return dehydrate(queryClient) + return { dehydratedState: dehydrate(queryClient), queryClient } } diff --git a/frontends/api/src/ssr/usePrefetchWarnings.test.ts b/frontends/api/src/ssr/usePrefetchWarnings.test.ts index ab34473ba8..1b937459e1 100644 --- a/frontends/api/src/ssr/usePrefetchWarnings.test.ts +++ b/frontends/api/src/ssr/usePrefetchWarnings.test.ts @@ -4,7 +4,7 @@ import { usePrefetchWarnings } from "./usePrefetchWarnings" import { setupReactQueryTest } from "../hooks/test-utils" import { urls, factories, setMockResponse } from "../test-utils" import { - learningResourcesKeyFactory, + learningResources, useLearningResourcesDetail, } from "../hooks/learningResources" @@ -45,7 +45,7 @@ describe("SSR prefetch warnings", () => { expect.objectContaining({ disabled: false, initialStatus: "loading", - key: learningResourcesKeyFactory.detail(1).queryKey, + key: learningResources.detail(1).queryKey, observerCount: 1, }), ], @@ -65,7 +65,7 @@ describe("SSR prefetch warnings", () => { wrapper, initialProps: { queryClient, - exemptions: [learningResourcesKeyFactory.detail(1).queryKey], + exemptions: [learningResources.detail(1).queryKey], }, }) @@ -83,7 +83,7 @@ describe("SSR prefetch warnings", () => { const { unmount } = renderHook( () => useQuery({ - ...learningResourcesKeyFactory.detail(1), + ...learningResources.detail(1), initialData: data, }), { wrapper }, @@ -105,9 +105,9 @@ describe("SSR prefetch warnings", () => { [ { disabled: false, - hash: JSON.stringify(learningResourcesKeyFactory.detail(1).queryKey), + hash: JSON.stringify(learningResources.detail(1).queryKey), initialStatus: "success", - key: learningResourcesKeyFactory.detail(1).queryKey, + key: learningResources.detail(1).queryKey, observerCount: 0, status: "success", }, diff --git a/frontends/api/src/test-utils/factories/channels.ts b/frontends/api/src/test-utils/factories/channels.ts index 7a8a095388..3e7d4d9820 100644 --- a/frontends/api/src/test-utils/factories/channels.ts +++ b/frontends/api/src/test-utils/factories/channels.ts @@ -148,6 +148,7 @@ const _channelShared = (): Partial> => { key: faker.lorem.slug(), value: faker.lorem.slug(), }), + channel_url: `${faker.internet.url({ appendSlash: false })}${faker.system.directoryPath()}`, } } diff --git a/frontends/api/src/test-utils/urls.ts b/frontends/api/src/test-utils/urls.ts index ba9970f49f..e7e3716c03 100644 --- a/frontends/api/src/test-utils/urls.ts +++ b/frontends/api/src/test-utils/urls.ts @@ -8,6 +8,7 @@ import type { NewsEventsApiNewsEventsListRequest, TestimonialsApi, + ChannelsApi, } from "../generated/v0" import type { LearningResourcesApi as LRApi, @@ -186,6 +187,8 @@ const channels = { details: (channelType: string, name: string) => `${API_BASE_URL}/api/v0/channels/type/${channelType}/${name}/`, patch: (id: number) => `${API_BASE_URL}/api/v0/channels/${id}/`, + list: (params?: Paramsv0) => + `${API_BASE_URL}/api/v0/channels/${query(params)}`, } const widgetLists = { diff --git a/frontends/main/src/app-pages/UnitsListingPage/UnitCard.tsx b/frontends/main/src/app-pages/UnitsListingPage/UnitCard.tsx index 9767a828c1..4a0390f39e 100644 --- a/frontends/main/src/app-pages/UnitsListingPage/UnitCard.tsx +++ b/frontends/main/src/app-pages/UnitsListingPage/UnitCard.tsx @@ -1,5 +1,6 @@ import React from "react" -import { LearningResourceOfferorDetail, OfferedByEnum } from "api" +import type { OfferedByEnum } from "api" +import type { UnitChannel } from "api/v0" import { Card, Skeleton, @@ -8,7 +9,6 @@ import { theme, UnitLogo, } from "ol-components" -import { useChannelDetail } from "api/hooks/channels" import Link from "next/link" const CardStyled = styled(Card)({ @@ -102,25 +102,23 @@ const CountsText = styled(Typography)(({ theme }) => ({ })) interface UnitCardsProps { - units: LearningResourceOfferorDetail[] | undefined + channels: UnitChannel[] | undefined courseCounts: Record programCounts: Record } interface UnitCardProps { - unit: LearningResourceOfferorDetail + channel: UnitChannel courseCount: number programCount: number } const UnitCard: React.FC = (props) => { - const { unit, courseCount, programCount } = props - const channelDetailQuery = useChannelDetail("unit", unit.code) - const channelDetail = channelDetailQuery.data - const unitUrl = channelDetail?.channel_url + const { channel, courseCount, programCount } = props + const unit = channel.unit_detail.unit - if (!unitUrl) return null - const href = unitUrl && new URL(unitUrl).pathname + if (!channel.channel_url) return null + const href = new URL(channel.channel_url).pathname return ( @@ -134,9 +132,7 @@ const UnitCard: React.FC = (props) => { - - {channelDetail?.configuration?.heading} - + {channel?.configuration?.heading} @@ -174,17 +170,18 @@ export const UnitCardLoading = () => { } export const UnitCards: React.FC = (props) => { - const { units, courseCounts, programCounts } = props + const { channels, courseCounts, programCounts } = props return ( <> - {units?.map((unit) => { + {channels?.map((channel) => { + const unit = channel.unit_detail.unit const courseCount = courseCounts[unit.code] || 0 const programCount = programCounts[unit.code] || 0 return unit.value_prop ? ( diff --git a/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.test.tsx b/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.test.tsx index c4e6ba84cd..c5d1a05c35 100644 --- a/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.test.tsx +++ b/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.test.tsx @@ -2,44 +2,71 @@ import React from "react" import { renderWithProviders, screen, waitFor, within } from "@/test-utils" import UnitsListingPage from "./UnitsListingPage" import { factories, setMockResponse, urls } from "api/test-utils" +import { ChannelTypeEnum } from "api/v0" +import type { UnitChannel } from "api/v0" import { assertHeadings } from "ol-test-utilities" -describe("DepartmentListingPage", () => { +describe("UnitListingPage", () => { const setupApis = () => { - const make = factories.learningResources - const academicUnit1 = make.offeror({ - code: "academicUnit1", - name: "Academic Unit 1", - value_prop: "Academic Unit 1 value prop", - professional: false, + const make = factories.channels + const academicUnit1 = make.channel({ + channel_type: ChannelTypeEnum.Unit, + name: "academicUnit1", + title: "Academic Unit 1", + unit_detail: { + unit: { + value_prop: "Academic Unit 1 value prop", + professional: false, + }, + }, }) - const academicUnit2 = make.offeror({ - code: "academicUnit2", - name: "Academic Unit 2", - value_prop: "Academic Unit 2 value prop", - professional: false, + const academicUnit2 = make.channel({ + channel_type: ChannelTypeEnum.Unit, + name: "academicUnit2", + title: "Academic Unit 2", + unit_detail: { + unit: { + value_prop: "Academic Unit 2 value prop", + professional: false, + }, + }, }) - const academicUnit3 = make.offeror({ - code: "academicUnit3", - name: "Academic Unit 3", - value_prop: "Academic Unit 3 value prop", - professional: false, + const academicUnit3 = make.channel({ + channel_type: ChannelTypeEnum.Unit, + name: "academicUnit3", + title: "Academic Unit 3", + unit_detail: { + unit: { + value_prop: "Academic Unit 3 value prop", + professional: false, + }, + }, }) - const professionalUnit1 = make.offeror({ - code: "professionalUnit1", - name: "Professional Unit 1", - value_prop: "Professional Unit 1 value prop", - professional: true, + const professionalUnit1 = make.channel({ + channel_type: ChannelTypeEnum.Unit, + name: "professionalUnit1", + title: "Professional Unit 1", + unit_detail: { + unit: { + value_prop: "Professional Unit 1 value prop", + professional: true, + }, + }, }) - const professionalUnit2 = make.offeror({ - code: "professionalUnit2", - name: "Professional Unit 2", - value_prop: "Professional Unit 2 value prop", - professional: true, + const professionalUnit2 = make.channel({ + channel_type: ChannelTypeEnum.Unit, + name: "professionalUnit2", + title: "Professional Unit 2", + unit_detail: { + unit: { + value_prop: "Professional Unit 2 value prop", + professional: true, + }, + }, }) - const units = [ + const unitChannels = [ academicUnit1, academicUnit2, academicUnit3, @@ -63,29 +90,23 @@ describe("DepartmentListingPage", () => { setMockResponse.get( urls.channels.counts("unit"), - units.map((unit) => { + unitChannels.map((channel) => { return { - name: unit.code, + name: channel.name, counts: { - courses: courseCounts[unit.code], - programs: programCounts[unit.code], + courses: courseCounts[channel.name], + programs: programCounts[channel.name], }, } }), ) - setMockResponse.get(urls.offerors.list(), { - count: units.length, - results: units, - }) - - units.forEach((unit) => { - setMockResponse.get(urls.channels.details("unit", unit.code), { - channel_url: `${window.location.origin}/units/${unit.code}`, - }) + setMockResponse.get(urls.channels.list({ channel_type: "unit" }), { + count: unitChannels.length, + results: unitChannels, }) return { - units, + unitChannels, courseCounts, programCounts, } @@ -98,7 +119,7 @@ describe("DepartmentListingPage", () => { }) it("Shows unit properties within the proper section", async () => { - const { units, courseCounts, programCounts } = setupApis() + const { unitChannels, courseCounts, programCounts } = setupApis() renderWithProviders() @@ -116,11 +137,15 @@ describe("DepartmentListingPage", () => { return links }) - units.forEach((unit) => { + unitChannels.forEach((channel) => { + const { unit } = (channel as UnitChannel).unit_detail const section = unit.professional ? professionalSection : academicSection const card = within(section).getByTestId(`unit-card-${unit.code}`) const link = within(card).getByRole("link") - expect(link).toHaveAttribute("href", `/units/${unit.code}`) + expect(link).toHaveAttribute( + "href", + new URL(channel.channel_url!).pathname, + ) const courseCount = courseCounts[unit.code] const programCount = programCounts[unit.code] diff --git a/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.tsx b/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.tsx index 905f2edf22..57586dc685 100644 --- a/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.tsx +++ b/frontends/main/src/app-pages/UnitsListingPage/UnitsListingPage.tsx @@ -1,8 +1,7 @@ "use client" import React from "react" -import { useChannelCounts } from "api/hooks/channels" -import { useOfferorsList } from "api/hooks/learningResources" +import { useChannelCounts, useChannelsList } from "api/hooks/channels" import { Banner, Container, @@ -14,23 +13,23 @@ import { import { backgroundSrcSetCSS } from "ol-utilities" import backgroundSteps from "@/public/images/backgrounds/background_steps.jpg" import { RiBookOpenLine, RiSuitcaseLine } from "@remixicon/react" -import { LearningResourceOfferorDetail } from "api" +import type { Channel, UnitChannel } from "api/v0" import { HOME } from "@/common/urls" import { UnitCards, UnitCardLoading } from "./UnitCard" import { aggregateProgramCounts, aggregateCourseCounts } from "@/common/utils" const DESKTOP_WIDTH = "1056px" -const sortUnits = ( - units: Array | undefined, +const sortUnitChannels = ( + channels: Array | undefined, courseCounts: Record, programCounts: Record, ) => { - return units?.sort((a, b) => { - const courseCountA = courseCounts[a.code] || 0 - const programCountA = programCounts[a.code] || 0 - const courseCountB = courseCounts[b.code] || 0 - const programCountB = programCounts[b.code] || 0 + return channels?.sort((a, b) => { + const courseCountA = courseCounts[a.name] || 0 + const programCountA = programCounts[a.name] || 0 + const courseCountB = courseCounts[b.name] || 0 + const programCountB = programCounts[b.name] || 0 const totalA = courseCountA + programCountA const totalB = courseCountB + programCountB return totalB - totalA @@ -151,7 +150,7 @@ interface UnitSectionProps { icon: React.ReactNode title: string description: string - units: LearningResourceOfferorDetail[] | undefined + channels: UnitChannel[] | undefined courseCounts: Record programCounts: Record isLoading?: boolean @@ -163,12 +162,11 @@ const UnitSection: React.FC = (props) => { icon, title, description, - units, + channels, courseCounts, programCounts, isLoading, } = props - return (
@@ -187,7 +185,7 @@ const UnitSection: React.FC = (props) => { .map((_null, i) => ) ) : ( @@ -198,8 +196,7 @@ const UnitSection: React.FC = (props) => { } const UnitsListingPage: React.FC = () => { - const unitsQuery = useOfferorsList() - const units = unitsQuery.data?.results + const channelsQuery = useChannelsList({ channel_type: "unit" }) const channelCountQuery = useChannelCounts("unit") const courseCounts = channelCountQuery.data @@ -208,25 +205,33 @@ const UnitsListingPage: React.FC = () => { const programCounts = channelCountQuery.data ? aggregateProgramCounts("name", channelCountQuery.data) : {} - const academicUnits = sortUnits( - units?.filter((unit) => unit.professional === false), + + const channels = channelsQuery.data?.results + const academicUnits = sortUnitChannels( + channels?.filter( + (channel) => + (channel as UnitChannel).unit_detail.unit.professional === false, + ), courseCounts, programCounts, ) - const professionalUnits = sortUnits( - units?.filter((unit) => unit.professional === true), + const professionalUnits = sortUnitChannels( + channels?.filter( + (channel) => + (channel as UnitChannel).unit_detail.unit.professional === true, + ), courseCounts, programCounts, ) - const unitData = [ + const sections = [ { id: "academic", icon: , title: "Academic Units", description: "MIT's Academic courses, programs, and materials mirror MIT curriculum and residential programs, making these available to a global audience. Approved by faculty committees, Academic content furnishes a comprehensive foundation of knowledge, skills, and abilities for students pursuing their academic objectives. Renowned for their rigor and challenge, MIT's Academic offerings deliver an experience on par with the campus environment.", - units: academicUnits, + channels: academicUnits as UnitChannel[], }, { id: "professional", @@ -234,7 +239,7 @@ const UnitsListingPage: React.FC = () => { title: "Professional Units", description: "MIT's Professional courses and programs are tailored for working professionals seeking essential practical skills across various industries. Led by MIT faculty and maintaining challenging standards, Professional courses and programs prioritize real-world applications, emphasize practical skills and are directly relevant to today's workforce.", - units: professionalUnits, + channels: professionalUnits as UnitChannel[], }, ] @@ -267,17 +272,17 @@ const UnitsListingPage: React.FC = () => { - {unitData.map((unit) => ( + {sections.map((section) => ( ))} diff --git a/frontends/main/src/app/about/page.tsx b/frontends/main/src/app/about/page.tsx index f95679b170..813a8a3311 100644 --- a/frontends/main/src/app/about/page.tsx +++ b/frontends/main/src/app/about/page.tsx @@ -1,6 +1,5 @@ import React from "react" import { Metadata } from "next" - import { AboutPage } from "@/app-pages/AboutPage/AboutPage" import { standardizeMetadata } from "@/common/metadata" diff --git a/frontends/main/src/app/c/[channelType]/[name]/page.tsx b/frontends/main/src/app/c/[channelType]/[name]/page.tsx index 5cc73220e1..d696be97ca 100644 --- a/frontends/main/src/app/c/[channelType]/[name]/page.tsx +++ b/frontends/main/src/app/c/[channelType]/[name]/page.tsx @@ -4,10 +4,7 @@ import { channelsApi } from "api/clients" import { ChannelTypeEnum } from "api/v0" import { getMetadataAsync } from "@/common/metadata" import handleNotFound from "@/common/handleNotFound" - -type SearchParams = { - [key: string]: string | string[] | undefined -} +import type { PageParams } from "@/app/types" type RouteParams = { channelType: ChannelTypeEnum @@ -17,10 +14,7 @@ type RouteParams = { export async function generateMetadata({ searchParams, params, -}: { - searchParams: Promise - params: Promise -}) { +}: PageParams) { const { channelType, name } = await params const { data } = await handleNotFound( diff --git a/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx b/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx index 082710c443..47a9c88930 100644 --- a/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx +++ b/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx @@ -1,6 +1,5 @@ import React from "react" import DashboardPage from "@/app-pages/DashboardPage/DashboardPage" - import { Metadata } from "next" import { standardizeMetadata } from "@/common/metadata" import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" diff --git a/frontends/main/src/app/departments/page.tsx b/frontends/main/src/app/departments/page.tsx index a9b5133e9f..bfc54b2811 100644 --- a/frontends/main/src/app/departments/page.tsx +++ b/frontends/main/src/app/departments/page.tsx @@ -1,20 +1,20 @@ import React from "react" import { Metadata } from "next" -import DepartmentListingPage from "@/app-pages/DepartmentListingPage/DepartmentListingPage" -import { standardizeMetadata } from "@/common/metadata" import { Hydrate } from "@tanstack/react-query" -import { learningResourcesKeyFactory } from "api/hooks/learningResources" -import { channelsKeyFactory } from "api/hooks/channels" +import { standardizeMetadata } from "@/common/metadata" +import { learningResources } from "api/hooks/learningResources" +import { channels } from "api/hooks/channels" import { prefetch } from "api/ssr/prefetch" +import DepartmentListingPage from "@/app-pages/DepartmentListingPage/DepartmentListingPage" export const metadata: Metadata = standardizeMetadata({ title: "Departments", }) const Page: React.FC = async () => { - const dehydratedState = await prefetch([ - channelsKeyFactory.countsByType("department"), - learningResourcesKeyFactory.schools(), + const { dehydratedState } = await prefetch([ + channels.countsByType("department"), + learningResources.schools(), ]) return ( diff --git a/frontends/main/src/app/learningpaths/[id]/page.tsx b/frontends/main/src/app/learningpaths/[id]/page.tsx index 2d6cfc35b0..3ffd0ba826 100644 --- a/frontends/main/src/app/learningpaths/[id]/page.tsx +++ b/frontends/main/src/app/learningpaths/[id]/page.tsx @@ -1,7 +1,7 @@ import React from "react" -import LearningPathDetailsPage from "@/app-pages/LearningPathDetailsPage/LearningPathDetailsPage" -import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" import { Permissions } from "@/common/permissions" +import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" +import LearningPathDetailsPage from "@/app-pages/LearningPathDetailsPage/LearningPathDetailsPage" const Page: React.FC = () => { return ( diff --git a/frontends/main/src/app/learningpaths/page.tsx b/frontends/main/src/app/learningpaths/page.tsx index b48a847c31..f8dcb046b5 100644 --- a/frontends/main/src/app/learningpaths/page.tsx +++ b/frontends/main/src/app/learningpaths/page.tsx @@ -1,10 +1,9 @@ import React from "react" -import LearningPathListingPage from "@/app-pages/LearningPathListingPage/LearningPathListingPage" - import { Metadata } from "next" import { standardizeMetadata } from "@/common/metadata" -import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" import { Permissions } from "@/common/permissions" +import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" +import LearningPathListingPage from "@/app-pages/LearningPathListingPage/LearningPathListingPage" export const metadata: Metadata = standardizeMetadata({ title: "Learning Paths", diff --git a/frontends/main/src/app/onboarding/page.tsx b/frontends/main/src/app/onboarding/page.tsx index 34a51559db..8d94e352c5 100644 --- a/frontends/main/src/app/onboarding/page.tsx +++ b/frontends/main/src/app/onboarding/page.tsx @@ -1,9 +1,9 @@ import React from "react" import { Metadata } from "next" -import OnboardingPage from "@/app-pages/OnboardingPage/OnboardingPage" import { standardizeMetadata } from "@/common/metadata" -import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" import { Permissions } from "@/common/permissions" +import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" +import OnboardingPage from "@/app-pages/OnboardingPage/OnboardingPage" export const metadata: Metadata = standardizeMetadata({ title: "Onboarding", diff --git a/frontends/main/src/app/page.tsx b/frontends/main/src/app/page.tsx index ae920e6784..27e6f12f65 100644 --- a/frontends/main/src/app/page.tsx +++ b/frontends/main/src/app/page.tsx @@ -3,11 +3,9 @@ import type { Metadata } from "next" import HomePage from "@/app-pages/HomePage/HomePage" import { getMetadataAsync } from "@/common/metadata" import { Hydrate } from "@tanstack/react-query" -import { testimonialsKeyFactory } from "api/hooks/testimonials" -import { - NewsEventsListFeedTypeEnum, - newsEventsKeyFactory, -} from "api/hooks/newsEvents" +import { learningResources } from "api/hooks/learningResources" +import { testimonials } from "api/hooks/testimonials" +import { NewsEventsListFeedTypeEnum, newsEvents } from "api/hooks/newsEvents" import { prefetch } from "api/ssr/prefetch" type SearchParams = { @@ -26,18 +24,59 @@ export async function generateMetadata({ } const Page: React.FC = async () => { - const dehydratedState = await prefetch([ - testimonialsKeyFactory.list({ position: 1 }), - newsEventsKeyFactory.list({ - feed_type: [NewsEventsListFeedTypeEnum.News], - limit: 6, - sortby: "-news_date", + const { dehydratedState } = await prefetch([ + // Featured Courses carousel "All" + learningResources.featured({ + limit: 12, + }), + // Featured Courses carousel "Free" + learningResources.featured({ + limit: 12, + free: true, + }), + // Featured Courses carousel "With Certificate" + learningResources.featured({ + limit: 12, + certification: true, + professional: false, + }), + // Featured Courses carousel "Professional & Executive Learning" + learningResources.featured({ + limit: 12, + professional: true, + }), + // Media carousel "All" + learningResources.list({ + resource_type: ["video", "podcast_episode"], + limit: 12, + sortby: "new", }), - newsEventsKeyFactory.list({ + // Media carousel "Videos" + learningResources.list({ + resource_type: ["video"], + limit: 12, + sortby: "new", + }), + // Media carousel "Podcasts" + learningResources.list({ + resource_type: ["podcast_episode"], + limit: 12, + sortby: "new", + }), + // Browse by Topic + learningResources.topics({ is_toplevel: true }), + + testimonials.list({ position: 1 }), + newsEvents.list({ feed_type: [NewsEventsListFeedTypeEnum.Events], limit: 5, sortby: "event_date", }), + newsEvents.list({ + feed_type: [NewsEventsListFeedTypeEnum.News], + limit: 6, + sortby: "-news_date", + }), ]) return ( diff --git a/frontends/main/src/app/privacy/page.tsx b/frontends/main/src/app/privacy/page.tsx index bea7709610..b24728f354 100644 --- a/frontends/main/src/app/privacy/page.tsx +++ b/frontends/main/src/app/privacy/page.tsx @@ -1,8 +1,8 @@ import React from "react" import { Metadata } from "next" - -import PrivacyPage from "@/app-pages/PrivacyPage/PrivacyPage" import { standardizeMetadata } from "@/common/metadata" +import PrivacyPage from "@/app-pages/PrivacyPage/PrivacyPage" + export const metadata: Metadata = standardizeMetadata({ title: "Privacy Policy", }) diff --git a/frontends/main/src/app/search/page.tsx b/frontends/main/src/app/search/page.tsx index 71c7723373..239623d7fb 100644 --- a/frontends/main/src/app/search/page.tsx +++ b/frontends/main/src/app/search/page.tsx @@ -1,16 +1,9 @@ import React from "react" import { getMetadataAsync } from "@/common/metadata" import SearchPage from "@/app-pages/SearchPage/SearchPage" +import type { PageParams } from "@/app/types" -type SearchParams = { - [key: string]: string | string[] | undefined -} - -export async function generateMetadata({ - searchParams, -}: { - searchParams: Promise -}) { +export async function generateMetadata({ searchParams }: PageParams) { return await getMetadataAsync({ title: "Search", searchParams, diff --git a/frontends/main/src/app/terms/page.tsx b/frontends/main/src/app/terms/page.tsx index 67c45a73be..57d82d21bc 100644 --- a/frontends/main/src/app/terms/page.tsx +++ b/frontends/main/src/app/terms/page.tsx @@ -1,8 +1,8 @@ import React from "react" import { Metadata } from "next" - -import TermsPage from "@/app-pages/TermsPage/TermsPage" import { standardizeMetadata } from "@/common/metadata" +import TermsPage from "@/app-pages/TermsPage/TermsPage" + export const metadata: Metadata = standardizeMetadata({ title: "Terms of Service", }) diff --git a/frontends/main/src/app/topics/page.tsx b/frontends/main/src/app/topics/page.tsx index abbbfc7a66..0d742ce794 100644 --- a/frontends/main/src/app/topics/page.tsx +++ b/frontends/main/src/app/topics/page.tsx @@ -1,15 +1,27 @@ import React from "react" import { Metadata } from "next" +import { Hydrate } from "@tanstack/react-query" +import { prefetch } from "api/ssr/prefetch" +import { learningResources } from "api/hooks/learningResources" +import { channels } from "api/hooks/channels" +import TopicsListingPage from "@/app-pages/TopicsListingPage/TopicsListingPage" import { standardizeMetadata } from "@/common/metadata" export const metadata: Metadata = standardizeMetadata({ title: "Topics", }) -import TopicsListingPage from "@/app-pages/TopicsListingPage/TopicsListingPage" +const Page: React.FC = async () => { + const { dehydratedState } = await prefetch([ + learningResources.topics({}), + channels.countsByType("topic"), + ]) -const Page: React.FC = () => { - return + return ( + + + + ) } export default Page diff --git a/frontends/main/src/app/types.d.ts b/frontends/main/src/app/types.d.ts new file mode 100644 index 0000000000..77335d0f0a --- /dev/null +++ b/frontends/main/src/app/types.d.ts @@ -0,0 +1,17 @@ +export type SearchParams = { + [key: string]: string | string[] | undefined +} + +type PageParamsWithRouteParams = { + params: Promise + searchParams?: Promise +} + +type PageParamsWithoutRouteParams = { + searchParams?: Promise +} + +export type PageParams> = + RouteParams extends Record + ? PageParamsWithoutRoute + : PageParamsWithRoute diff --git a/frontends/main/src/app/units/page.tsx b/frontends/main/src/app/units/page.tsx index 1f1a535933..52cf658a24 100644 --- a/frontends/main/src/app/units/page.tsx +++ b/frontends/main/src/app/units/page.tsx @@ -1,14 +1,26 @@ import React from "react" import { Metadata } from "next" - -import UnitsListingPage from "@/app-pages/UnitsListingPage/UnitsListingPage" +import { Hydrate } from "@tanstack/react-query" +import { prefetch } from "api/ssr/prefetch" import { standardizeMetadata } from "@/common/metadata" +import { channels } from "api/hooks/channels" +import UnitsListingPage from "@/app-pages/UnitsListingPage/UnitsListingPage" + export const metadata: Metadata = standardizeMetadata({ title: "Units", }) -const Page: React.FC = () => { - return +const Page: React.FC = async () => { + const { dehydratedState } = await prefetch([ + channels.countsByType("unit"), + channels.list({ channel_type: "unit" }), + ]) + + return ( + + + + ) } export default Page diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx index 5eee0c4a99..18c8764dec 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx @@ -1,3 +1,5 @@ +"use client" + import React, { useCallback } from "react" import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" import { ReadonlyURLSearchParams, useSearchParams } from "next/navigation" @@ -25,7 +27,7 @@ const useResourceDrawerHref = () => { return useCallback( (resourceId: number) => { - const hash = window?.location.hash + const hash = typeof window !== "undefined" && window?.location.hash return `?${getOpenDrawerSearchParams(searchParams, resourceId)}${hash || ""}` }, [searchParams], diff --git a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx index d8c0041dd3..125a4dcae8 100644 --- a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx +++ b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx @@ -1,7 +1,7 @@ "use client" import React from "react" -import { learningResourcesKeyFactory } from "api/hooks/learningResources" +import { learningResources } from "api/hooks/learningResources" import { Carousel, TabButton, @@ -217,15 +217,15 @@ const ResourceCarousel: React.FC = ({ > => { switch (tab.data.type) { case "resources": - return learningResourcesKeyFactory.list(tab.data.params) + return learningResources.list(tab.data.params) case "lr_search": - return learningResourcesKeyFactory.search(tab.data.params) + return learningResources.search(tab.data.params) case "lr_featured": - return learningResourcesKeyFactory.featured(tab.data.params) + return learningResources.featured(tab.data.params) case "lr_similar": - return learningResourcesKeyFactory.similar(tab.data.params.id) + return learningResources.similar(tab.data.params.id) case "lr_vector_similar": - return learningResourcesKeyFactory.vectorSimilar(tab.data.params.id) + return learningResources.vectorSimilar(tab.data.params.id) } }, ),