Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { useMemo } from "react"
import { Popover, Typography, styled, Button } from "ol-components"
import type { PopoverProps } from "ol-components"
import { getSearchParamMap } from "@/common/utils"

import { SignupPopover } from "../SignupPopover/SignupPopover"

import { useUserMe } from "api/hooks/user"
import { SourceTypeEnum } from "api"
import {
useSearchSubscriptionCreate,
useSearchSubscriptionDelete,
useSearchSubscriptionList,
} from "api/hooks/searchSubscription"

const StyledPopover = styled(Popover)({
width: "300px",
maxWidth: "100vw",
})
const HeaderText = styled(Typography)(({ theme }) => ({
color: theme.custom.colors.darkGray2,
marginBottom: "8px",
...theme.typography.subtitle2,
}))
const BodyText = styled(Typography)(({ theme }) => ({
...theme.typography.body2,
color: theme.custom.colors.silverGrayDark,
marginBottom: "16px",
}))

const Footer = styled.div({
display: "flex",
justifyContent: "end",
gap: "16px",
})

interface FollowPopoverProps
extends Pick<PopoverProps, "anchorEl" | "onClose" | "placement"> {
itemName?: string
searchParams: URLSearchParams
sourceType: SourceTypeEnum
}

const FollowPopover: React.FC<FollowPopoverProps> = ({
itemName,
searchParams,
sourceType,
...props
}) => {
const { data: user } = useUserMe()
const subscribeParams: Record<string, string[] | string> = useMemo(() => {
return { source_type: sourceType, ...getSearchParamMap(searchParams) }
}, [searchParams, sourceType])

const subscriptionDelete = useSearchSubscriptionDelete()
const subscriptionCreate = useSearchSubscriptionCreate()
const subscriptionList = useSearchSubscriptionList(subscribeParams, {
enabled: !!user?.is_authenticated,
})
const unsubscribe = subscriptionDelete.mutate
const subscriptionId = subscriptionList.data?.[0]?.id

const isSubscribed = !!subscriptionId
const handleFollowAction = async (): Promise<void> => {
props.onClose()
if (!isSubscribed) {
await subscriptionCreate.mutateAsync({
PercolateQuerySubscriptionRequestRequest: subscribeParams,
})
} else {
unsubscribe(subscriptionId)
}
}

if (user?.is_authenticated && subscriptionList.isLoading) return null
if (!user) return null
if (!user?.is_authenticated) {
return <SignupPopover {...props}></SignupPopover>
}

if (isSubscribed) {
return (
<StyledPopover {...props} open={!!props.anchorEl}>
<HeaderText variant="subtitle2">
You are following {itemName}
</HeaderText>
<BodyText variant="body2">
Unfollow to stop getting emails for new {itemName} courses.
</BodyText>
<Footer>
<Button
size="medium"
responsive={true}
variant="inverted"
edge="rounded"
data-testid="action-unfollow"
onClick={handleFollowAction}
>
Unfollow
</Button>
<Button
responsive={true}
size="medium"
edge="rounded"
onClick={() => props.onClose()}
>
Close
</Button>
</Footer>
</StyledPopover>
)
}
return (
<StyledPopover {...props} open={!!props.anchorEl}>
<HeaderText variant="subtitle2">Follow {itemName}?</HeaderText>
<BodyText variant="body2">
You will get an email when new courses are available.
</BodyText>
<Footer>
<Button
responsive={true}
size="medium"
edge="rounded"
variant="inverted"
onClick={() => props.onClose()}
>
Close
</Button>
<Button
responsive={true}
size="medium"
edge="rounded"
data-testid="action-follow"
onClick={handleFollowAction}
>
Follow
</Button>
</Footer>
</StyledPopover>
)
}

export { FollowPopover }
export type { FollowPopoverProps }
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ test.each(Object.values(SourceTypeEnum))(
})
await user.click(subscribeButton)

const followButton = screen.getByTestId("action-follow")
await user.click(followButton)
expect(makeRequest).toHaveBeenCalledWith("post", subscribeUrl, {
source_type: sourceType,
offered_by: ["ocw"],
Expand Down Expand Up @@ -111,11 +113,8 @@ test.each(Object.values(SourceTypeEnum))(
})

await user.click(subscribedButton)
const unsubscribeButton = screen.getByTestId("action-unfollow")

const menu = screen.getByRole("menu")
const unsubscribeButton = within(menu).getByRole("menuitem", {
name: "Unfollow",
})
await user.click(unsubscribeButton)

expect(makeRequest).toHaveBeenCalledWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import React, { useState, useMemo } from "react"
import { getSearchParamMap } from "@/common/utils"
import {
useSearchSubscriptionCreate,
useSearchSubscriptionDelete,
useSearchSubscriptionList,
} from "api/hooks/searchSubscription"
import { Button, SimpleMenu, styled } from "ol-components"
import type { SimpleMenuItem } from "ol-components"
import { RiArrowDownSLine, RiMailLine } from "@remixicon/react"
import { Button, styled } from "ol-components"

import { RiMailLine } from "@remixicon/react"
import { useUserMe } from "api/hooks/user"
import { SourceTypeEnum } from "api"
import { SignupPopover } from "../SignupPopover/SignupPopover"
import { FollowPopover } from "../FollowPopover/FollowPopover"

const StyledButton = styled(Button)({
minWidth: "130px",
Expand All @@ -23,6 +22,7 @@ type SearchSubscriptionToggleProps = {
}

const SearchSubscriptionToggle: React.FC<SearchSubscriptionToggleProps> = ({
itemName,
searchParams,
sourceType,
}) => {
Expand All @@ -33,50 +33,40 @@ const SearchSubscriptionToggle: React.FC<SearchSubscriptionToggleProps> = ({
}, [searchParams, sourceType])

const { data: user } = useUserMe()
const subscriptionDelete = useSearchSubscriptionDelete()

const subscriptionCreate = useSearchSubscriptionCreate()
const subscriptionList = useSearchSubscriptionList(subscribeParams, {
enabled: !!user?.is_authenticated,
})

const unsubscribe = subscriptionDelete.mutate
const subscriptionId = subscriptionList.data?.[0]?.id
const isSubscribed = !!subscriptionId

const unsubscribeItems: SimpleMenuItem[] = useMemo(() => {
if (!subscriptionId) return []
return [
{
key: "unsubscribe",
label: "Unfollow",
onClick: () => unsubscribe(subscriptionId),
},
]
}, [unsubscribe, subscriptionId])

const onFollowClick = async (event: React.MouseEvent<HTMLElement>) => {
if (user?.is_authenticated) {
await subscriptionCreate.mutateAsync({
PercolateQuerySubscriptionRequestRequest: subscribeParams,
})
} else {
setButtonEl(event.currentTarget)
}
setButtonEl(event.currentTarget)
}

if (user?.is_authenticated && subscriptionList.isLoading) return null
if (!user) return null

if (isSubscribed) {
return (
<SimpleMenu
trigger={
<StyledButton variant="primary" endIcon={<RiArrowDownSLine />}>
Following
</StyledButton>
}
items={unsubscribeItems}
/>
<>
<StyledButton
variant="success"
onClick={onFollowClick}
startIcon={<RiMailLine />}
>
Following
</StyledButton>
<FollowPopover
searchParams={searchParams}
itemName={itemName}
sourceType={sourceType}
anchorEl={buttonEl}
onClose={() => setButtonEl(null)}
/>
</>
)
}

Expand All @@ -90,7 +80,13 @@ const SearchSubscriptionToggle: React.FC<SearchSubscriptionToggleProps> = ({
>
Follow
</StyledButton>
<SignupPopover anchorEl={buttonEl} onClose={() => setButtonEl(null)} />
<FollowPopover
searchParams={searchParams}
itemName={itemName}
sourceType={sourceType}
anchorEl={buttonEl}
onClose={() => setButtonEl(null)}
/>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ const StyledPopover = styled(Popover)({
const HeaderText = styled(Typography)(({ theme }) => ({
color: theme.custom.colors.darkGray2,
marginBottom: "8px",
...theme.typography.subtitle2,
}))
const BodyText = styled(Typography)(({ theme }) => ({
color: theme.custom.colors.silverGrayDark,
marginBottom: "16px",
...theme.typography.body2,
}))

const Footer = styled.div({
Expand Down
22 changes: 22 additions & 0 deletions frontends/ol-components/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ButtonVariant =
| "text"
| "noBorder"
| "inverted"
| "success"
type ButtonSize = "small" | "medium" | "large"
type ButtonEdge = "circular" | "rounded" | "none"

Expand Down Expand Up @@ -127,6 +128,27 @@ const ButtonStyled = styled.button<ButtonStyleProps>((props) => {
borderColor: "currentcolor",
borderStyle: "solid",
},
variant === "success" && {
backgroundColor: colors.darkGreen,
color: colors.white,
border: "none",
/* Shadow/04dp */
boxShadow:
"0px 2px 4px 0px rgba(37, 38, 43, 0.10), 0px 3px 8px 0px rgba(37, 38, 43, 0.12)",
":hover:not(:disabled)": {
backgroundColor: colors.darkGreen,
boxShadow: "none",
},
":disabled": {
backgroundColor: colors.silverGray,
boxShadow: "none",
},
},
hasBorder && {
backgroundColor: "transparent",
borderColor: "currentcolor",
borderStyle: "solid",
},
variant === "secondary" && {
color: colors.red,
":hover:not(:disabled)": {
Expand Down
1 change: 1 addition & 0 deletions frontends/ol-components/src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const Arrow = styled("div")({
const Content = styled.div(({ theme }) => ({
padding: "16px",
backgroundColor: theme.custom.colors.white,
borderRadius: "8px",
boxShadow:
"0px 2px 4px 0px rgba(37, 38, 43, 0.10), 0px 6px 24px 0px rgba(37, 38, 43, 0.24)",
}))
Expand Down