Skip to content
Open
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 @@ -726,7 +726,12 @@ const CertificatePage: React.FC<{
}, [print])

const [shareOpen, setShareOpen] = useState(false)
const shareButtonRef = useRef<HTMLDivElement>(null)
const [shareAnchorEl, setShareAnchorEl] = useState<HTMLDivElement | null>(
null,
)
const shareButtonRef = (node: HTMLDivElement | null) => {
setShareAnchorEl(node)
}

if (isCourseLoading || isProgramLoading) {
return <Page />
Expand Down Expand Up @@ -776,7 +781,7 @@ const CertificatePage: React.FC<{
<SharePopover
open={shareOpen}
title={`${title} Certificate issued by MIT Open Learning`}
anchorEl={shareButtonRef.current}
anchorEl={shareAnchorEl}
onClose={() => setShareOpen(false)}
pageUrl={pageUrl}
/>
Expand Down
27 changes: 11 additions & 16 deletions frontends/main/src/app-pages/ChannelPage/ChannelPageTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,18 @@ const ChannelPageTemplate: React.FC<ChannelSkeletonProps> = ({
channelType,
name,
}) => {
const getChannelTemplate = (channelType: string) => {
switch (channelType) {
case ChannelTypeEnum.Unit:
return UnitChannelTemplate
case ChannelTypeEnum.Topic:
return TopicChannelTemplate
default:
return DefaultChannelTemplate
}
switch (channelType) {
case ChannelTypeEnum.Unit:
return <UnitChannelTemplate name={name}>{children}</UnitChannelTemplate>
case ChannelTypeEnum.Topic:
return <TopicChannelTemplate name={name}>{children}</TopicChannelTemplate>
default:
return (
<DefaultChannelTemplate name={name} channelType={channelType}>
{children}
</DefaultChannelTemplate>
)
}
const ChannelTemplate = getChannelTemplate(channelType)

return (
<ChannelTemplate name={name} channelType={channelType}>
{children}
</ChannelTemplate>
)
}

export {
Expand Down
19 changes: 8 additions & 11 deletions frontends/main/src/app-pages/HomePage/TestimonialsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"
import React, { useMemo } from "react"
import { shuffle } from "lodash"
import {
Container,
Expand All @@ -11,7 +11,6 @@ import {
} from "ol-components"
import { ActionButton } from "@mitodl/smoot-design"
import { useTestimonialList } from "api/hooks/testimonials"
import type { Attestation } from "api/v0"
import { RiArrowRightLine, RiArrowLeftLine } from "@remixicon/react"
import Slider from "react-slick"
import AttestantBlock from "@/page-components/TestimonialDisplay/AttestantBlock"
Expand Down Expand Up @@ -231,14 +230,12 @@ const TestimonialTruncateText = styled(TruncateText)({
const SlickCarousel = () => {
const { data } = useTestimonialList({ position: 1 })
const [slick, setSlick] = React.useState<Slider | null>(null)
const [shuffled, setShuffled] = useState<Attestation[]>()
const [imageSequence, setImageSequence] = useState<number[]>()

useEffect(() => {
if (!data) return
setShuffled(shuffle(data?.results))
setImageSequence(shuffle([1, 2, 3, 4, 5, 6]))
}, [data])
const results = data?.results
const shuffled = useMemo(
() => (results ? shuffle(results) : undefined),
[results],
)
const imageSequence = useMemo(() => shuffle([1, 2, 3, 4, 5, 6]), [])

if (!data?.results?.length || !shuffled?.length) {
return null
Expand Down Expand Up @@ -277,7 +274,7 @@ const SlickCarousel = () => {
>
<TestimonialCardImage>
<Image
src={`/images/testimonial_images/testimonial-image-${imageSequence![idx % 6]}.png`}
src={`/images/testimonial_images/testimonial-image-${imageSequence[idx % 6]}.png`}
alt=""
width={300}
height={326}
Expand Down
6 changes: 5 additions & 1 deletion frontends/main/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
} from "api/hooks/newsEvents"
import { getQueryClient } from "@/app/getQueryClient"

const getRandomHeroImageIndex = () => Math.floor(Math.random() * 5) + 1

export async function generateMetadata({
searchParams,
}: PageProps<"/">): Promise<Metadata> {
Expand Down Expand Up @@ -101,9 +103,11 @@ const Page: React.FC<PageProps<"/">> = async () => {
),
])

const heroImageIndex = getRandomHeroImageIndex()

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<HomePage heroImageIndex={Math.floor(Math.random() * 5) + 1} />
<HomePage heroImageIndex={heroImageIndex} />
</HydrationBoundary>
)
}
Expand Down
17 changes: 14 additions & 3 deletions frontends/main/src/page-components/ItemsListing/ItemsListing.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from "react"
import React, { useCallback, useEffect, useRef } from "react"
import type { LearningResource } from "api"
import {
SortableItem,
Expand Down Expand Up @@ -56,7 +56,10 @@ const ItemsListingSortable: React.FC<{
const moveLearningPathListItem = useLearningPathListItemMove()
const moveUserListListItem = useUserListListItemMove()

const [sorted, setSorted] = React.useState<LearningResourceListItem[]>([])
const prevItemsRef = useRef<LearningResourceListItem[] | undefined>(undefined)
const [sorted, setSorted] = React.useState<LearningResourceListItem[]>(
() => items,
)

const ListCardComponent = condensed
? LearningResourceListCardCondensed
Expand All @@ -69,7 +72,15 @@ const ItemsListingSortable: React.FC<{
* - `items` is the source of truth (most likely, this is coming from an API)
* so sync `items` -> `sorted` when `items` changes.
*/
useEffect(() => setSorted(items), [items])
useEffect(() => {
if (prevItemsRef.current !== items) {
prevItemsRef.current = items
// Defer setState to avoid synchronous setState in effect
queueMicrotask(() => {
setSorted(items)
})
}
}, [items])

const renderDragging: RenderActive = useCallback(
(active) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,20 @@ const LearningResourceExpanded: React.FC<LearningResourceExpandedProps> = ({
const titleSectionRef = useRef<HTMLDivElement>(null)
const [titleSectionHeight, setTitleSectionHeight] = useState(0)
const [scrollPosition, setScrollPosition] = useState(0)
const scrollElementRef = useRef<HTMLElement | null>(null)
const [scrollElement, setScrollElement] = useState<HTMLElement | null>(null)
const [chatExpanded, setChatExpanded] = useToggle(initialChatExpanded)

useEffect(() => {
if (outerContainerRef?.current?.scrollTo) {
outerContainerRef.current.scrollTo(0, 0)
}
if (scrollElement) {
if (scrollElementRef.current) {
requestAnimationFrame(() => {
scrollElement.scrollTop = 0
scrollElementRef.current!.scrollTop = 0
})
}
}, [resourceId, scrollElement])
}, [resourceId])

useEffect(() => {
const updateHeight = () => {
Expand All @@ -202,20 +203,26 @@ const LearningResourceExpanded: React.FC<LearningResourceExpandedProps> = ({
const drawerPaper = outerContainerRef.current?.closest(
".MuiDrawer-paper",
) as HTMLElement
setScrollElement(drawerPaper)
scrollElementRef.current = drawerPaper
queueMicrotask(() => {
setScrollElement(drawerPaper)
})
}
}, [outerContainerRef])
}, [])

useEffect(() => {
if (scrollElement && chatTransitionState === ChatTransitionState.Closing) {
scrollElement.scrollTop = scrollPosition
if (
scrollElementRef.current &&
chatTransitionState === ChatTransitionState.Closing
) {
scrollElementRef.current.scrollTop = scrollPosition
}
}, [chatTransitionState, scrollElement, scrollPosition])
}, [chatTransitionState, scrollPosition])

const onChatOpenerToggle = (open: boolean) => {
if (open) {
setChatTransitionState(ChatTransitionState.Opening)
setScrollPosition(scrollElement?.scrollTop ?? 0)
setScrollPosition(scrollElementRef.current?.scrollTop ?? 0)
openChat()
} else {
setChatTransitionState(ChatTransitionState.Closing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const DescriptionExpanded = styled(Description)({

const ResourceDescription = ({ resource }: { resource?: LearningResource }) => {
const firstRender = useRef(true)
const clampedOnFirstRender = useRef(false)
const [clampedOnFirstRender, setClampedOnFirstRender] = useState(false)
const [isClamped, setClamped] = useState(false)
const [isExpanded, setExpanded] = useState(false)
const descriptionRendered = useCallback((node: HTMLDivElement) => {
Expand All @@ -55,7 +55,7 @@ const ResourceDescription = ({ resource }: { resource?: LearningResource }) => {
setClamped(clamped)
if (firstRender.current) {
firstRender.current = false
clampedOnFirstRender.current = clamped
setClampedOnFirstRender(clamped)
return
}
}
Expand Down Expand Up @@ -87,7 +87,7 @@ const ResourceDescription = ({ resource }: { resource?: LearningResource }) => {
dangerouslySetInnerHTML={{ __html: resource.description || "" }}
lang={getResourceLanguage(resource)}
/>
{(isClamped || clampedOnFirstRender.current) && (
{(isClamped || clampedOnFirstRender) && (
<Link
scroll={false}
color="red"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import { learningResourceQueries } from "api/hooks/learningResources"
import { ResourceCard } from "./ResourceCard"
import { getReadableResourceType } from "ol-utilities"
import { ResourceTypeEnum, MicroUserListRelationship } from "api"
import {
AddToLearningPathDialog,
AddToUserListDialog,
} from "../Dialogs/AddToListDialog"
import type { ResourceCardProps } from "./ResourceCard"
import { urls, factories, setMockResponse } from "api/test-utils"
import { RESOURCE_DRAWER_PARAMS } from "@/common/urls"
Expand Down Expand Up @@ -114,9 +110,13 @@ describe.each([
name: `Bookmark ${getReadableResourceType(resource?.resource_type as ResourceTypeEnum)}`,
})

const addToLearningPathButton = screen.queryByRole("button", {
name: "Add to Learning Path",
})
const addToLearningPathButton = expectAddToLearningPathButton
? await screen.findByRole("button", {
name: "Add to Learning Path",
})
: screen.queryByRole("button", {
name: "Add to Learning Path",
})
expect(!!addToLearningPathButton).toBe(expectAddToLearningPathButton)
},
)
Expand Down Expand Up @@ -191,13 +191,20 @@ describe.each([
expect(showModal).not.toHaveBeenCalled()
await user.click(addToLearningPathButton)
invariant(resource)
expect(showModal).toHaveBeenLastCalledWith(AddToLearningPathDialog, {
resourceId: resource.id,
})
expect(showModal).toHaveBeenCalledWith(
expect.any(Function),
expect.objectContaining({
resourceId: resource.id,
}),
)
showModal.mockClear()
await user.click(addToUserListButton)
expect(showModal).toHaveBeenLastCalledWith(AddToUserListDialog, {
resourceId: resource.id,
})
expect(showModal).toHaveBeenCalledWith(
expect.any(Function),
expect.objectContaining({
resourceId: resource.id,
}),
)
})

test("Clicking 'Add to User List' opens signup popover if not authenticated", async () => {
Expand Down
47 changes: 26 additions & 21 deletions frontends/main/src/page-components/ResourceCard/ResourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,36 @@ export const useResourceCard = (resource?: LearningResource | null) => {
const handleClosePopover = useCallback(() => {
setAnchorEl(null)
}, [])
const handleAddToLearningPathClickCallback = useCallback(
(event: React.MouseEvent, resourceId: number) => {
NiceModal.show(AddToLearningPathDialog, { resourceId })
},
[],
)
const handleAddToLearningPathClick: LearningResourceCardProps["onAddToLearningPathClick"] =
useMemo(() => {
if (user?.is_authenticated && user?.is_learning_path_editor) {
return (event, resourceId: number) => {
NiceModal.show(AddToLearningPathDialog, { resourceId })
}
}
return null
}, [user])
useMemo(
() =>
user?.is_authenticated && user?.is_learning_path_editor
? handleAddToLearningPathClickCallback
: null,
[user, handleAddToLearningPathClickCallback],
)

const handleAddToUserListClick: LearningResourceCardProps["onAddToUserListClick"] =
useMemo(() => {
if (!user) {
// user info is still loading
return null
}
if (user.is_authenticated) {
return (event, resourceId: number) => {
NiceModal.show(AddToUserListDialog, { resourceId })
useCallback(
(event: React.MouseEvent, resourceId?: number) => {
if (!user) {
// user info is still loading
return
}
if (user.is_authenticated) {
NiceModal.show(AddToUserListDialog, { resourceId: resourceId! })
} else {
setAnchorEl(event.currentTarget as HTMLElement)
}
}
return (event) => {
setAnchorEl(event.currentTarget)
}
}, [user])
},
[user],
)

const onClick = useLearningResourceDetailSetCache(resource)

Expand Down
Loading
Loading