v4: signing in comes back to the page that asked for it — a guest on a signed-in page, the dump editor, /notifications, /community, /settings or /manage, went to /login and, once signed in, to /, so a digest's link opened signed out lost its way; and since the redirect pushed, Back led to it again. RestrictedLoggedIn now replaces the page with /login?next=<the page>, as /authorize already did. A guest on a feed's "follow" tab, which only a link leads to, is sent there the same way, rather than shown the default tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { Navigate, useLocation } from "react-router";
|
||||
|
||||
import { AppHeader } from "../components/layout/AppHeader.tsx";
|
||||
import { PresenceRow } from "../components/feed/PresenceRow.tsx";
|
||||
@@ -36,6 +36,7 @@ import { DumpFeed, type MainFeedProps } from "../components/feed/DumpFeed.tsx";
|
||||
import { JournalFeed } from "../components/feed/JournalFeed.tsx";
|
||||
import { FollowedFeed } from "../components/feed/FollowedFeed.tsx";
|
||||
import { NotFound } from "./NotFound.tsx";
|
||||
import { loginPath } from "../utils/auth/next.ts";
|
||||
|
||||
/** Passed to {@link Index} by the router, from the path. */
|
||||
export interface IndexProps {
|
||||
@@ -76,11 +77,7 @@ export function Index(
|
||||
);
|
||||
|
||||
const defaultTab = useViewerDefaultFeedTab();
|
||||
// A guest on "follow", from a shared link or after signing out, gets the
|
||||
// default tab rather than an empty page.
|
||||
const tab: FeedTab = feedTab && (feedTab !== "follow" || !!user)
|
||||
? feedTab
|
||||
: defaultTab;
|
||||
const tab: FeedTab = feedTab ?? defaultTab;
|
||||
|
||||
const feedUrl = categorySlug
|
||||
? `${API_URL}/api/dumps?category=${encodeURIComponent(categorySlug)}`
|
||||
@@ -181,6 +178,13 @@ export function Index(
|
||||
};
|
||||
|
||||
if (categoryNotFound) return <NotFound />;
|
||||
// The default tab is never "follow" for a guest, so only a link leads one
|
||||
// here: a digest's, or the page they signed out on.
|
||||
if (tab === "follow" && !user) {
|
||||
return (
|
||||
<Navigate to={loginPath(location.pathname + location.search)} replace />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="index-page">
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/**
|
||||
* Guards `App`'s routes that need a session, the dump editor and
|
||||
* `/notifications`.
|
||||
* Guards `App`'s routes that need a session: the dump editor,
|
||||
* `/notifications`, `/community`, `/settings` and `/manage`. `Index` sends a
|
||||
* guest on the "follow" tab to sign in the same way.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { Navigate } from "react-router";
|
||||
import { Navigate, useLocation } from "react-router";
|
||||
|
||||
import { useAuth } from "../hooks/auth/useAuth.ts";
|
||||
import { loginPath } from "../utils/auth/next.ts";
|
||||
|
||||
/** Passed to {@link RestrictedLoggedIn} by `App`, around a route's page. */
|
||||
export interface RestrictedLoggedInProps {
|
||||
@@ -15,14 +17,17 @@ export interface RestrictedLoggedInProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders `children` when someone is signed in, and redirects to `/login`
|
||||
* otherwise. The page it came from is not remembered, so signing in lands on
|
||||
* `/`.
|
||||
* Renders `children` when someone is signed in, and otherwise replaces the
|
||||
* page with `/login`, which comes back to it once signed in: a digest's link
|
||||
* to `/notifications` opened signed out lands there after all.
|
||||
*/
|
||||
export function RestrictedLoggedIn(
|
||||
{ children }: RestrictedLoggedInProps,
|
||||
): React.JSX.Element {
|
||||
const { user } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
return user ? <>{children}</> : <Navigate to="/login" />;
|
||||
return user
|
||||
? <>{children}</>
|
||||
: <Navigate to={loginPath(location.pathname + location.search)} replace />;
|
||||
}
|
||||
|
||||
@@ -249,10 +249,10 @@ describe("Index feeds", () => {
|
||||
.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("a guest on the follow tab gets the default feed, not a blank page", async () => {
|
||||
test("a guest on the follow tab is sent to sign in, and back", () => {
|
||||
route("GET /api/dumps", () => page([older]));
|
||||
setup("/follow");
|
||||
await screen.findByText("Old but loved");
|
||||
setup("/music/follow");
|
||||
expect(currentPath()).toBe("/login?next=%2Fmusic%2Ffollow");
|
||||
});
|
||||
|
||||
test("the Android share sheet opens the new-dump form with the link", async () => {
|
||||
|
||||
@@ -3,21 +3,22 @@ import { screen } from "@testing-library/react";
|
||||
import { Route } from "react-router";
|
||||
import { RestrictedLoggedIn } from "../../pages/RestrictedLoggedIn.tsx";
|
||||
import { makeSession } from "../fixtures.ts";
|
||||
import { renderWithContexts } from "../render.tsx";
|
||||
import { currentPath, renderWithContexts } from "../render.tsx";
|
||||
|
||||
describe("RestrictedLoggedIn", () => {
|
||||
test("sends guests to login", () => {
|
||||
test("sends guests to login, which comes back to the page", () => {
|
||||
renderWithContexts(
|
||||
<RestrictedLoggedIn>
|
||||
<p>secret</p>
|
||||
</RestrictedLoggedIn>,
|
||||
{
|
||||
route: "/notifications",
|
||||
path: "/notifications",
|
||||
route: "/community/monthly?x=1",
|
||||
path: "/community/monthly",
|
||||
extraRoutes: <Route path="/login" element={<p>login page</p>} />,
|
||||
},
|
||||
);
|
||||
expect(screen.getByText("login page")).toBeInTheDocument();
|
||||
expect(currentPath()).toBe("/login?next=%2Fcommunity%2Fmonthly%3Fx%3D1");
|
||||
});
|
||||
|
||||
test("lets members through", () => {
|
||||
|
||||
Reference in New Issue
Block a user