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
19 changes: 19 additions & 0 deletions frontends/mit-open/src/common/urls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { login } from "./urls"

test("login encodes the next parameter appropriately", () => {
expect(login()).toBe("/login/ol-oidc/?next=/")
expect(login({})).toBe("/login/ol-oidc/?next=/")

expect(
login({
pathname: "/foo/bar",
}),
).toBe("/login/ol-oidc/?next=/foo/bar")

expect(
login({
pathname: "/foo/bar",
search: "?cat=meow",
}),
).toBe("/login/ol-oidc/?next=/foo/bar%3Fcat%3Dmeow")
})
23 changes: 23 additions & 0 deletions frontends/mit-open/src/common/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,26 @@ export const articlesEditView = (id: number) =>

export const LOGIN = "/login/ol-oidc/"
export const LOGOUT = "/logout/"

/**
* Returns the URL to the login page, with a `next` parameter to redirect back
* to the given pathname + search parameters.
*/
export const login = ({
pathname = "/",
search = "",
}: {
pathname?: string
search?: string
} = {}) => {
/**
* To include search parameters in the next URL, we need to encode them.
* If we pass `?next=/foo/bar?cat=meow` directly, Django receives two separate
* parameters: `next` and `cat`.
*
* There's no need to encode the path parameter (it might contain slashes,
* but those are allowed in search parameters) so let's keep it readable.
*/
const next = `${pathname}${encodeURIComponent(search)}`
return `${LOGIN}?next=${next}`
}
10 changes: 8 additions & 2 deletions frontends/mit-open/src/page-components/Header/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,22 @@ describe("UserMenu", () => {
test.each([
{
isAuthenticated: false,
expected: { text: "Log in", url: urls.LOGIN },
initialUrl: "/foo/bar?cat=meow",
expected: {
text: "Log in",
url: urls.login({ pathname: "/foo/bar", search: "?cat=meow" }),
},
},
{
isAuthenticated: true,
initialUrl: "/foo/bar?cat=meow",
expected: { text: "Log out", url: urls.LOGOUT },
},
])(
"Users (authenticated=$isAuthenticated) see '$expected.text' link",
async ({ isAuthenticated, expected }) => {
async ({ isAuthenticated, expected, initialUrl }) => {
renderWithProviders(<Header />, {
url: initialUrl,
user: { is_authenticated: isAuthenticated },
})
const menu = await findUserMenu()
Expand Down
12 changes: 9 additions & 3 deletions frontends/mit-open/src/page-components/Header/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import * as urls from "@/common/urls"
import { Permissions, hasPermission } from "@/common/permissions"
import type { User } from "@/types/settings"
import PersonIcon from "@mui/icons-material/Person"
import { useLocation } from "react-router"
import type { Location } from "react-router"

const StyledBadge = styled(Badge)`
pointer-events: none;
Expand All @@ -30,13 +32,16 @@ interface AuthMenuItem extends SimpleMenuItem {
allow: boolean
}

const getUserMenuItems = (): SimpleMenuItem[] => {
const getUserMenuItems = (location: Location): SimpleMenuItem[] => {
const items: AuthMenuItem[] = [
{
label: "Log in",
key: "login",
allow: !hasPermission(Permissions.Authenticated),
href: urls.LOGIN,
href: urls.login({
pathname: location.pathname,
search: location.search,
}),
LinkComponent: "a",
},
{
Expand Down Expand Up @@ -66,7 +71,8 @@ const UserIcon: React.FC<{ user: User }> = ({ user }) => {

const UserMenu: React.FC = () => {
const [visible, setVisible] = useState(false)
const items = useMemo(getUserMenuItems, [])
const location = useLocation()
const items = useMemo(() => getUserMenuItems(location), [location])

return (
<SimpleMenu
Expand Down