v3: consistent tabs url across app, maxy small fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s

This commit is contained in:
khannurien
2026-06-22 17:25:40 +00:00
parent dbd2e15158
commit a1b71ad0c8
22 changed files with 295 additions and 236 deletions

View File

@@ -1,5 +1,5 @@
import { lazy, Suspense } from "react";
import { BrowserRouter, Route, Routes } from "react-router";
import { BrowserRouter, Navigate, Route, Routes } from "react-router";
import { RestrictedGuest } from "./pages/RestrictedGuest.tsx";
import { RestrictedLoggedIn } from "./pages/RestrictedLoggedIn.tsx";
@@ -70,7 +70,8 @@ function AppRoutes() {
return (
<Suspense>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/" element={<Navigate to="/~/hot" replace />} />
<Route path="/~/:feedTab" element={<Index />} />
<Route path="/dumps/:selectedDump" element={<Dump />} />
<Route
path="/dumps/:selectedDump/edit"
@@ -97,6 +98,10 @@ function AppRoutes() {
}
/>
<Route path="/users/:username" element={<UserPublicProfile />} />
<Route
path="/users/:username/~/:profileTab"
element={<UserPublicProfile />}
/>
<Route path="/users/:username/dumps" element={<UserDumps />} />
<Route path="/users/:username/upvoted" element={<UserUpvoted />} />
<Route
@@ -105,6 +110,7 @@ function AppRoutes() {
/>
<Route path="/playlists/:playlistId" element={<PlaylistDetail />} />
<Route path="/search" element={<Search />} />
<Route path="/search/~/:searchTab" element={<Search />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route
path="/notifications"

View File

@@ -42,7 +42,7 @@ export function AppHeader(
return (
<>
<header className="app-header app-header--has-center">
<Link to="/?tab=hot" className="app-header-brand">
<Link to="/" className="app-header-brand">
🚚<span className="app-header-brand-name">
{" "}
{document.querySelector<HTMLMetaElement>('meta[name="site-name"]')

View File

@@ -24,7 +24,7 @@ export function ChangePasswordModal({ onClose }: ChangePasswordModalProps) {
const tooShort = newPassword.length > 0 &&
newPassword.length < VALIDATION.PASSWORD_MIN;
const handleSubmit = async (e: React.FormEvent) => {
const handleSubmit = async (e: React.SubmitEvent) => {
e.preventDefault();
if (mismatch || tooShort || !currentPassword || !newPassword) return;

View File

@@ -188,7 +188,7 @@ export function DumpCreateModal(
return () => globalThis.removeEventListener("paste", handler);
}, []);
const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
const handleSubmit = async (e: React.SubmitEvent) => {
e.preventDefault();
if (comment.length > VALIDATION.DUMP_COMMENT_MAX) return;
setSubmitState({ status: "submitting" });

View File

@@ -1,16 +1,12 @@
import { useLocation, useNavigate } from "react-router";
import { Trans } from "@lingui/react/macro";
import { useAuth } from "../hooks/useAuth.ts";
import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts";
import { type FeedTab, FEED_TABS } from "../config/feedTabs.ts";
import { useTabParam } from "../hooks/useTabParam.ts";
import { TabBar } from "./TabBar.tsx";
export function FeedTabBar() {
const location = useLocation();
const navigate = useNavigate();
const { user } = useAuth();
const rawTab = new URLSearchParams(location.search).get("tab") ?? "hot";
const tab: FeedTab = VALID_TABS.has(rawTab) ? (rawTab as FeedTab) : "hot";
const [tab, setTab] = useTabParam<FeedTab>(FEED_TABS, "hot");
const tabs = [
{ key: "hot" as FeedTab, label: <Trans>Hot</Trans> },
@@ -25,7 +21,7 @@ export function FeedTabBar() {
<TabBar
tabs={tabs}
activeTab={tab}
onChange={(t) => navigate(`/?tab=${t}`, { replace: true })}
onChange={setTab}
/>
);
}

View File

@@ -1,4 +1,4 @@
import { type FormEvent, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router";
import { t } from "@lingui/core/macro";
@@ -41,11 +41,11 @@ export function SearchBar(
}
}
function handleSubmit(e: FormEvent) {
function handleSubmit(e: React.SubmitEvent) {
e.preventDefault();
const q = value.trim();
if (!q) return;
navigate(`/search?q=${encodeURIComponent(q)}&tab=dumps`);
navigate(`/search?q=${encodeURIComponent(q)}`);
if (collapsible || isControlled) {
setExpanded(false);
setValue("");

View File

@@ -1,3 +1,2 @@
export const FEED_TABS = ["hot", "new", "journal", "followed"] as const;
export type FeedTab = (typeof FEED_TABS)[number];
export const VALID_TABS = new Set<string>(FEED_TABS);

47
src/hooks/useTabParam.ts Normal file
View File

@@ -0,0 +1,47 @@
import { useCallback } from "react";
import { useLocation, useNavigate } from "react-router";
const SEP = "/~/";
/**
* Reflects the active tab in the URL path using a `/~/` delimiter, e.g.
* `/users/vincent/~/playlists` or `/~/new`. The segment before `/~/` is the
* base path and the segment after is the tab. When the base is non-empty the
* default tab collapses to the clean base path (`/users/vincent`); at the root
* the base is empty, so the default stays explicit (`/~/hot`). Generic and
* path-agnostic: the base is derived from the current location, so the same
* hook works for any route.
*
* Returns the validated active tab (falling back to `defaultTab`) and a setter
* that navigates to the matching path (replacing history).
*/
export function useTabParam<T extends string>(
validTabs: Iterable<T>,
defaultTab: T,
): [T, (tab: T) => void] {
const location = useLocation();
const navigate = useNavigate();
const valid = new Set<string>(validTabs as Iterable<string>);
const path = location.pathname;
const sepAt = path.indexOf(SEP);
let base = sepAt === -1 ? path : path.slice(0, sepAt);
if (base.endsWith("/")) base = base.slice(0, -1); // root "/" → ""
const raw = sepAt === -1
? null
: path.slice(sepAt + SEP.length).replace(/\/$/, "");
const tab = (raw && valid.has(raw) ? raw : defaultTab) as T;
const setTab = useCallback(
(t: T) => {
const target = t === defaultTab && base ? base : `${base}${SEP}${t}`;
// Preserve any unrelated query string (e.g. the search page's `?q=`).
navigate(`${target}${location.search}`, { replace: true });
},
[navigate, base, defaultTab, location.search],
);
return [tab, setTab];
}

View File

@@ -42,7 +42,7 @@ msgstr "{days}d ago"
msgid "{hrs}h ago"
msgstr "{hrs}h ago"
#: src/pages/Search.tsx:182
#: src/pages/Search.tsx:176
msgid "{label} ({count})"
msgstr "{label} ({count})"
@@ -55,7 +55,7 @@ msgid "{visibleCount, plural, one {# comment} other {# comments}}"
msgstr "{visibleCount, plural, one {# comment} other {# comments}}"
#: src/pages/PlaylistDetail.tsx:617
#: src/pages/UserPublicProfile.tsx:753
#: src/pages/UserPublicProfile.tsx:766
msgid "← Back"
msgstr "← Back"
@@ -71,7 +71,7 @@ msgstr "← Back to all dumps"
msgid "← Back to profile"
msgstr "← Back to profile"
#: src/pages/UserPublicProfile.tsx:101
#: src/pages/UserPublicProfile.tsx:102
msgid "+ Invite someone"
msgstr "+ Invite someone"
@@ -142,7 +142,7 @@ msgstr "a comment"
msgid "a post"
msgstr "a post"
#: src/pages/UserPublicProfile.tsx:1204
#: src/pages/UserPublicProfile.tsx:1217
msgid "Account"
msgstr "Account"
@@ -150,7 +150,7 @@ msgstr "Account"
msgid "Add a comment…"
msgstr "Add a comment…"
#: src/pages/UserPublicProfile.tsx:871
#: src/pages/UserPublicProfile.tsx:884
msgid "Add email…"
msgstr "Add email…"
@@ -171,7 +171,7 @@ msgstr "All {0, plural, one {# upvoted dump} other {# upvoted dumps}} loaded."
msgid "Already have an account? <0>Log in</0>"
msgstr "Already have an account? <0>Log in</0>"
#: src/pages/UserPublicProfile.tsx:1223
#: src/pages/UserPublicProfile.tsx:1236
msgid "Appearance"
msgstr "Appearance"
@@ -181,7 +181,7 @@ msgstr "Appearance"
msgid "At least {0} characters"
msgstr "At least {0} characters"
#: src/pages/UserPublicProfile.tsx:1257
#: src/pages/UserPublicProfile.tsx:1270
msgid "Auto"
msgstr "Auto"
@@ -205,8 +205,8 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
#: src/pages/Dump.tsx:355
#: src/pages/DumpEdit.tsx:391
#: src/pages/PlaylistDetail.tsx:686
#: src/pages/UserPublicProfile.tsx:850
#: src/pages/UserPublicProfile.tsx:932
#: src/pages/UserPublicProfile.tsx:863
#: src/pages/UserPublicProfile.tsx:945
msgid "Cancel"
msgstr "Cancel"
@@ -218,7 +218,7 @@ msgstr "Cancel removal"
msgid "Cancel search"
msgstr "Cancel search"
#: src/pages/UserPublicProfile.tsx:780
#: src/pages/UserPublicProfile.tsx:793
msgid "Change avatar"
msgstr "Change avatar"
@@ -227,7 +227,7 @@ msgstr "Change avatar"
msgid "Change password"
msgstr "Change password"
#: src/pages/UserPublicProfile.tsx:1216
#: src/pages/UserPublicProfile.tsx:1229
msgid "Change password…"
msgstr "Change password…"
@@ -240,7 +240,7 @@ msgstr "Checking invite…"
msgid "Close"
msgstr "Close"
#: src/pages/UserPublicProfile.tsx:1249
#: src/pages/UserPublicProfile.tsx:1262
msgid "Color scheme"
msgstr "Color scheme"
@@ -249,11 +249,11 @@ msgstr "Color scheme"
msgid "Confirm new password"
msgstr "Confirm new password"
#: src/pages/UserPublicProfile.tsx:92
#: src/pages/UserPublicProfile.tsx:93
msgid "Copied!"
msgstr "Copied!"
#: src/pages/UserPublicProfile.tsx:92
#: src/pages/UserPublicProfile.tsx:93
msgid "Copy"
msgstr "Copy"
@@ -298,7 +298,7 @@ msgstr "Creating…"
msgid "Current password"
msgstr "Current password"
#: src/pages/UserPublicProfile.tsx:1271
#: src/pages/UserPublicProfile.tsx:1284
msgid "Dark"
msgstr "Dark"
@@ -362,15 +362,15 @@ msgstr "Dump it"
msgid "Dumped!"
msgstr "Dumped!"
#: src/pages/Search.tsx:178
#: src/pages/Search.tsx:172
#: src/pages/UserDumps.tsx:107
#: src/pages/UserPublicProfile.tsx:976
#: src/pages/UserPublicProfile.tsx:989
msgid "Dumps"
msgstr "Dumps"
#. placeholder {0}: dumps.items.length
#. placeholder {1}: dumps.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:993
#: src/pages/UserPublicProfile.tsx:1006
msgid "Dumps ({0}{1})"
msgstr "Dumps ({0}{1})"
@@ -414,7 +414,7 @@ msgstr "Editing"
msgid "Email address"
msgstr "Email address"
#: src/pages/Search.tsx:211
#: src/pages/Search.tsx:205
msgid "Enter a query to search."
msgstr "Enter a query to search."
@@ -427,9 +427,9 @@ msgstr "Failed to change password"
msgid "Failed to create playlist"
msgstr "Failed to create playlist"
#: src/pages/UserPublicProfile.tsx:73
#: src/pages/UserPublicProfile.tsx:76
#: src/pages/UserPublicProfile.tsx:104
#: src/pages/UserPublicProfile.tsx:74
#: src/pages/UserPublicProfile.tsx:77
#: src/pages/UserPublicProfile.tsx:105
msgid "Failed to generate invite"
msgstr "Failed to generate invite"
@@ -438,9 +438,9 @@ msgstr "Failed to generate invite"
#: src/pages/index/JournalFeed.tsx:48
#: src/pages/index/NewFeed.tsx:36
#: src/pages/Notifications.tsx:367
#: src/pages/UserPublicProfile.tsx:1095
#: src/pages/UserPublicProfile.tsx:1137
#: src/pages/UserPublicProfile.tsx:1182
#: src/pages/UserPublicProfile.tsx:1108
#: src/pages/UserPublicProfile.tsx:1150
#: src/pages/UserPublicProfile.tsx:1195
msgid "Failed to load"
msgstr "Failed to load"
@@ -457,10 +457,10 @@ msgid "Failed to post reply"
msgstr "Failed to post reply"
#: src/pages/PlaylistDetail.tsx:795
#: src/pages/UserPublicProfile.tsx:688
#: src/pages/UserPublicProfile.tsx:726
#: src/pages/UserPublicProfile.tsx:855
#: src/pages/UserPublicProfile.tsx:935
#: src/pages/UserPublicProfile.tsx:701
#: src/pages/UserPublicProfile.tsx:739
#: src/pages/UserPublicProfile.tsx:868
#: src/pages/UserPublicProfile.tsx:948
msgid "Failed to save"
msgstr "Failed to save"
@@ -468,7 +468,7 @@ msgstr "Failed to save"
msgid "Failed to save edit"
msgstr "Failed to save edit"
#: src/pages/UserPublicProfile.tsx:880
#: src/pages/UserPublicProfile.tsx:893
msgid "Failed to update avatar"
msgstr "Failed to update avatar"
@@ -510,8 +510,8 @@ msgstr "Follow some public playlists to see their dumps here."
msgid "Follow some users to see their dumps here."
msgstr "Follow some users to see their dumps here."
#: src/components/FeedTabBar.tsx:20
#: src/pages/UserPublicProfile.tsx:978
#: src/components/FeedTabBar.tsx:16
#: src/pages/UserPublicProfile.tsx:991
msgid "Followed"
msgstr "Followed"
@@ -521,13 +521,13 @@ msgstr "Followed"
msgid "Followed ({0}{1})"
msgstr "Followed ({0}{1})"
#: src/pages/UserPublicProfile.tsx:1126
#: src/pages/UserPublicProfile.tsx:1139
msgid "Followed playlists"
msgstr "Followed playlists"
#: src/components/FollowButton.tsx:37
#: src/components/FollowButton.tsx:64
#: src/pages/UserPublicProfile.tsx:1084
#: src/pages/UserPublicProfile.tsx:1097
msgid "Following"
msgstr "Following"
@@ -551,7 +551,7 @@ msgstr "Go home"
msgid "Go to login"
msgstr "Go to login"
#: src/components/FeedTabBar.tsx:16
#: src/components/FeedTabBar.tsx:12
msgid "Hot"
msgstr "Hot"
@@ -567,16 +567,16 @@ msgstr "Invalid invite"
msgid "Invalid link"
msgstr "Invalid link"
#: src/pages/UserPublicProfile.tsx:799
#: src/pages/UserPublicProfile.tsx:812
msgid "invited by"
msgstr "invited by"
#: src/pages/UserPublicProfile.tsx:979
#: src/pages/UserPublicProfile.tsx:1171
#: src/pages/UserPublicProfile.tsx:992
#: src/pages/UserPublicProfile.tsx:1184
msgid "Invitees"
msgstr "Invitees"
#: src/components/FeedTabBar.tsx:18
#: src/components/FeedTabBar.tsx:14
msgid "Journal"
msgstr "Journal"
@@ -584,7 +584,7 @@ msgstr "Journal"
msgid "just now"
msgstr "just now"
#: src/pages/UserPublicProfile.tsx:1264
#: src/pages/UserPublicProfile.tsx:1277
msgid "Light"
msgstr "Light"
@@ -614,7 +614,7 @@ msgstr "Loading dump…"
#: src/pages/index/HotFeed.tsx:64
#: src/pages/index/JournalFeed.tsx:77
#: src/pages/index/NewFeed.tsx:64
#: src/pages/Search.tsx:248
#: src/pages/Search.tsx:242
#: src/pages/UserDumps.tsx:93
#: src/pages/UserPlaylists.tsx:417
#: src/pages/UserPlaylists.tsx:452
@@ -626,7 +626,7 @@ msgstr "Loading more…"
msgid "Loading playlist…"
msgstr "Loading playlist…"
#: src/pages/UserPublicProfile.tsx:736
#: src/pages/UserPublicProfile.tsx:749
msgid "Loading profile…"
msgstr "Loading profile…"
@@ -642,9 +642,9 @@ msgstr "Loading profile…"
#: src/pages/Notifications.tsx:439
#: src/pages/UserDumps.tsx:51
#: src/pages/UserPlaylists.tsx:342
#: src/pages/UserPublicProfile.tsx:1089
#: src/pages/UserPublicProfile.tsx:1131
#: src/pages/UserPublicProfile.tsx:1176
#: src/pages/UserPublicProfile.tsx:1102
#: src/pages/UserPublicProfile.tsx:1144
#: src/pages/UserPublicProfile.tsx:1189
#: src/pages/UserUpvoted.tsx:122
msgid "Loading…"
msgstr "Loading…"
@@ -663,8 +663,8 @@ msgstr "Log in to like"
msgid "Log in to vote"
msgstr "Log in to vote"
#: src/pages/UserPublicProfile.tsx:757
#: src/pages/UserPublicProfile.tsx:894
#: src/pages/UserPublicProfile.tsx:770
#: src/pages/UserPublicProfile.tsx:907
msgid "Log out"
msgstr "Log out"
@@ -684,13 +684,13 @@ msgstr "Max 50 MB"
msgid "new"
msgstr "new"
#: src/components/FeedTabBar.tsx:17
#: src/components/FeedTabBar.tsx:13
msgid "New"
msgstr "New"
#: src/components/DumpCreateModal.tsx:302
#: src/pages/UserDumps.tsx:115
#: src/pages/UserPublicProfile.tsx:1320
#: src/pages/UserPublicProfile.tsx:1333
msgid "New dump"
msgstr "New dump"
@@ -708,7 +708,7 @@ msgstr "New playlist"
msgid "No dumps in this playlist yet."
msgstr "No dumps in this playlist yet."
#: src/pages/Search.tsx:228
#: src/pages/Search.tsx:222
msgid "No dumps match \"{q}\"."
msgstr "No dumps match \"{q}\"."
@@ -723,11 +723,11 @@ msgid "No emoji found."
msgstr "No emoji found."
#: src/pages/UserPlaylists.tsx:439
#: src/pages/UserPublicProfile.tsx:1144
#: src/pages/UserPublicProfile.tsx:1157
msgid "No followed playlists yet."
msgstr "No followed playlists yet."
#: src/pages/UserPublicProfile.tsx:1189
#: src/pages/UserPublicProfile.tsx:1202
msgid "No invitees yet."
msgstr "No invitees yet."
@@ -735,28 +735,28 @@ msgstr "No invitees yet."
msgid "No one yet."
msgstr "No one yet."
#: src/pages/Search.tsx:287
#: src/pages/Search.tsx:281
msgid "No playlists match \"{q}\"."
msgstr "No playlists match \"{q}\"."
#: src/components/PlaylistMembershipPanel.tsx:34
#: src/pages/UserPlaylists.tsx:397
#: src/pages/UserPublicProfile.tsx:1055
#: src/pages/UserPublicProfile.tsx:1068
msgid "No playlists yet."
msgstr "No playlists yet."
#: src/pages/Search.tsx:261
#: src/pages/Search.tsx:255
msgid "No users match \"{q}\"."
msgstr "No users match \"{q}\"."
#: src/pages/UserPublicProfile.tsx:1102
#: src/pages/UserPublicProfile.tsx:1115
msgid "Not following anyone yet."
msgstr "Not following anyone yet."
#: src/pages/Notifications.tsx:374
#: src/pages/UserDumps.tsx:125
#: src/pages/UserPublicProfile.tsx:1331
#: src/pages/UserPublicProfile.tsx:1453
#: src/pages/UserPublicProfile.tsx:1344
#: src/pages/UserPublicProfile.tsx:1466
#: src/pages/UserUpvoted.tsx:194
msgid "Nothing here yet."
msgstr "Nothing here yet."
@@ -779,7 +779,7 @@ msgid "or <0>browse files</0>"
msgstr "or <0>browse files</0>"
#: src/pages/UserLogin.tsx:106
#: src/pages/UserPublicProfile.tsx:1209
#: src/pages/UserPublicProfile.tsx:1222
msgid "Password"
msgstr "Password"
@@ -803,15 +803,15 @@ msgstr "Passwords do not match"
#: src/components/AppHeader.tsx:85
#: src/components/UserMenu.tsx:62
#: src/pages/Search.tsx:181
#: src/pages/Search.tsx:175
#: src/pages/UserPlaylists.tsx:368
#: src/pages/UserPublicProfile.tsx:977
#: src/pages/UserPublicProfile.tsx:990
msgid "Playlists"
msgstr "Playlists"
#. placeholder {0}: playlists.items.length
#. placeholder {1}: playlists.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1024
#: src/pages/UserPublicProfile.tsx:1037
msgid "Playlists ({0}{1})"
msgstr "Playlists ({0}{1})"
@@ -930,8 +930,8 @@ msgstr "Retry"
#: src/pages/Dump.tsx:347
#: src/pages/DumpEdit.tsx:400
#: src/pages/PlaylistDetail.tsx:679
#: src/pages/UserPublicProfile.tsx:842
#: src/pages/UserPublicProfile.tsx:924
#: src/pages/UserPublicProfile.tsx:855
#: src/pages/UserPublicProfile.tsx:937
msgid "Save"
msgstr "Save"
@@ -940,8 +940,8 @@ msgstr "Save"
#: src/pages/Dump.tsx:347
#: src/pages/PlaylistDetail.tsx:679
#: src/pages/ResetPassword.tsx:152
#: src/pages/UserPublicProfile.tsx:841
#: src/pages/UserPublicProfile.tsx:924
#: src/pages/UserPublicProfile.tsx:854
#: src/pages/UserPublicProfile.tsx:937
msgid "Saving…"
msgstr "Saving…"
@@ -954,11 +954,11 @@ msgstr "Search"
msgid "Search dumps, users, playlists…"
msgstr "Search dumps, users, playlists…"
#: src/pages/Search.tsx:222
#: src/pages/Search.tsx:216
msgid "Search failed"
msgstr "Search failed"
#: src/pages/Search.tsx:217
#: src/pages/Search.tsx:211
msgid "Searching…"
msgstr "Searching…"
@@ -979,7 +979,7 @@ msgstr "Server unreachable"
msgid "Set new password"
msgstr "Set new password"
#: src/pages/UserPublicProfile.tsx:981
#: src/pages/UserPublicProfile.tsx:994
msgid "Settings"
msgstr "Settings"
@@ -987,7 +987,7 @@ msgstr "Settings"
msgid "Something went wrong"
msgstr "Something went wrong"
#: src/pages/UserPublicProfile.tsx:1228
#: src/pages/UserPublicProfile.tsx:1241
msgid "Style"
msgstr "Style"
@@ -1042,7 +1042,7 @@ msgstr "Unfollow playlist"
msgid "Unknown error"
msgstr "Unknown error"
#: src/pages/UserPublicProfile.tsx:657
#: src/pages/UserPublicProfile.tsx:670
msgid "Upload failed"
msgstr "Upload failed"
@@ -1060,7 +1060,7 @@ msgstr "Upvoted"
#. placeholder {0}: votes.items.length
#. placeholder {1}: votes.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1004
#: src/pages/UserPublicProfile.tsx:1017
msgid "Upvoted ({0}{1})"
msgstr "Upvoted ({0}{1})"
@@ -1082,15 +1082,15 @@ msgstr "User menu"
msgid "Username"
msgstr "Username"
#: src/pages/Search.tsx:180
#: src/pages/Search.tsx:174
msgid "Users"
msgstr "Users"
#: src/pages/UserPublicProfile.tsx:1074
#: src/pages/UserPublicProfile.tsx:1117
#: src/pages/UserPublicProfile.tsx:1159
#: src/pages/UserPublicProfile.tsx:1352
#: src/pages/UserPublicProfile.tsx:1483
#: src/pages/UserPublicProfile.tsx:1087
#: src/pages/UserPublicProfile.tsx:1130
#: src/pages/UserPublicProfile.tsx:1172
#: src/pages/UserPublicProfile.tsx:1365
#: src/pages/UserPublicProfile.tsx:1496
msgid "View all →"
msgstr "View all →"
@@ -1103,8 +1103,8 @@ msgstr "View dump →"
msgid "What makes it worth it?"
msgstr "What makes it worth it?"
#: src/pages/UserPublicProfile.tsx:912
#: src/pages/UserPublicProfile.tsx:961
#: src/pages/UserPublicProfile.tsx:925
#: src/pages/UserPublicProfile.tsx:974
msgid "Who am I?"
msgstr "Who am I?"
@@ -1129,7 +1129,7 @@ msgstr "You'll be notified when someone follows your playlists, upvotes your dum
#: src/pages/index/HotFeed.tsx:69
#: src/pages/index/JournalFeed.tsx:82
#: src/pages/index/NewFeed.tsx:69
#: src/pages/Search.tsx:253
#: src/pages/Search.tsx:247
#: src/pages/UserDumps.tsx:98
#: src/pages/UserPlaylists.tsx:422
#: src/pages/UserPlaylists.tsx:457

File diff suppressed because one or more lines are too long

View File

@@ -42,7 +42,7 @@ msgstr "il y a {days}j"
msgid "{hrs}h ago"
msgstr "il y a {hrs}h"
#: src/pages/Search.tsx:182
#: src/pages/Search.tsx:176
msgid "{label} ({count})"
msgstr "{label} ({count})"
@@ -55,7 +55,7 @@ msgid "{visibleCount, plural, one {# comment} other {# comments}}"
msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}"
#: src/pages/PlaylistDetail.tsx:617
#: src/pages/UserPublicProfile.tsx:753
#: src/pages/UserPublicProfile.tsx:766
msgid "← Back"
msgstr "← Retour"
@@ -71,7 +71,7 @@ msgstr "← Retour à toutes les recos"
msgid "← Back to profile"
msgstr "← Retour au profil"
#: src/pages/UserPublicProfile.tsx:101
#: src/pages/UserPublicProfile.tsx:102
msgid "+ Invite someone"
msgstr "+ Inviter quelqu'un"
@@ -142,7 +142,7 @@ msgstr "un commentaire"
msgid "a post"
msgstr "une publication"
#: src/pages/UserPublicProfile.tsx:1204
#: src/pages/UserPublicProfile.tsx:1217
msgid "Account"
msgstr "Compte"
@@ -150,7 +150,7 @@ msgstr "Compte"
msgid "Add a comment…"
msgstr "Ajouter un commentaire…"
#: src/pages/UserPublicProfile.tsx:871
#: src/pages/UserPublicProfile.tsx:884
msgid "Add email…"
msgstr "Ajouter un e-mail…"
@@ -171,7 +171,7 @@ msgstr "Toutes les {0, plural, one {# reco votée} other {# recos votées}} char
msgid "Already have an account? <0>Log in</0>"
msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
#: src/pages/UserPublicProfile.tsx:1223
#: src/pages/UserPublicProfile.tsx:1236
msgid "Appearance"
msgstr "Apparence"
@@ -181,7 +181,7 @@ msgstr "Apparence"
msgid "At least {0} characters"
msgstr "Au moins {0} caractères"
#: src/pages/UserPublicProfile.tsx:1257
#: src/pages/UserPublicProfile.tsx:1270
msgid "Auto"
msgstr "Auto"
@@ -205,8 +205,8 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
#: src/pages/Dump.tsx:355
#: src/pages/DumpEdit.tsx:391
#: src/pages/PlaylistDetail.tsx:686
#: src/pages/UserPublicProfile.tsx:850
#: src/pages/UserPublicProfile.tsx:932
#: src/pages/UserPublicProfile.tsx:863
#: src/pages/UserPublicProfile.tsx:945
msgid "Cancel"
msgstr "Annuler"
@@ -218,7 +218,7 @@ msgstr "Annuler la suppression"
msgid "Cancel search"
msgstr "Annuler la recherche"
#: src/pages/UserPublicProfile.tsx:780
#: src/pages/UserPublicProfile.tsx:793
msgid "Change avatar"
msgstr "Changer l'avatar"
@@ -227,7 +227,7 @@ msgstr "Changer l'avatar"
msgid "Change password"
msgstr "Changer le mot de passe"
#: src/pages/UserPublicProfile.tsx:1216
#: src/pages/UserPublicProfile.tsx:1229
msgid "Change password…"
msgstr "Changer le mot de passe…"
@@ -240,7 +240,7 @@ msgstr "Vérification de l'invitation…"
msgid "Close"
msgstr "Fermer"
#: src/pages/UserPublicProfile.tsx:1249
#: src/pages/UserPublicProfile.tsx:1262
msgid "Color scheme"
msgstr "Thème de couleur"
@@ -249,11 +249,11 @@ msgstr "Thème de couleur"
msgid "Confirm new password"
msgstr "Confirmer le nouveau mot de passe"
#: src/pages/UserPublicProfile.tsx:92
#: src/pages/UserPublicProfile.tsx:93
msgid "Copied!"
msgstr "Copié !"
#: src/pages/UserPublicProfile.tsx:92
#: src/pages/UserPublicProfile.tsx:93
msgid "Copy"
msgstr "Copier"
@@ -298,7 +298,7 @@ msgstr "Création…"
msgid "Current password"
msgstr "Mot de passe actuel"
#: src/pages/UserPublicProfile.tsx:1271
#: src/pages/UserPublicProfile.tsx:1284
msgid "Dark"
msgstr "Sombre"
@@ -362,15 +362,15 @@ msgstr "Recommander"
msgid "Dumped!"
msgstr "Recommandé !"
#: src/pages/Search.tsx:178
#: src/pages/Search.tsx:172
#: src/pages/UserDumps.tsx:107
#: src/pages/UserPublicProfile.tsx:976
#: src/pages/UserPublicProfile.tsx:989
msgid "Dumps"
msgstr "Recos"
#. placeholder {0}: dumps.items.length
#. placeholder {1}: dumps.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:993
#: src/pages/UserPublicProfile.tsx:1006
msgid "Dumps ({0}{1})"
msgstr "Recos ({0}{1})"
@@ -414,7 +414,7 @@ msgstr "Modification"
msgid "Email address"
msgstr "Adresse e-mail"
#: src/pages/Search.tsx:211
#: src/pages/Search.tsx:205
msgid "Enter a query to search."
msgstr "Saisissez une recherche."
@@ -427,9 +427,9 @@ msgstr "Impossible de changer le mot de passe"
msgid "Failed to create playlist"
msgstr "Impossible de créer la collection"
#: src/pages/UserPublicProfile.tsx:73
#: src/pages/UserPublicProfile.tsx:76
#: src/pages/UserPublicProfile.tsx:104
#: src/pages/UserPublicProfile.tsx:74
#: src/pages/UserPublicProfile.tsx:77
#: src/pages/UserPublicProfile.tsx:105
msgid "Failed to generate invite"
msgstr "Impossible de générer une invitation"
@@ -438,9 +438,9 @@ msgstr "Impossible de générer une invitation"
#: src/pages/index/JournalFeed.tsx:48
#: src/pages/index/NewFeed.tsx:36
#: src/pages/Notifications.tsx:367
#: src/pages/UserPublicProfile.tsx:1095
#: src/pages/UserPublicProfile.tsx:1137
#: src/pages/UserPublicProfile.tsx:1182
#: src/pages/UserPublicProfile.tsx:1108
#: src/pages/UserPublicProfile.tsx:1150
#: src/pages/UserPublicProfile.tsx:1195
msgid "Failed to load"
msgstr "Chargement échoué"
@@ -457,10 +457,10 @@ msgid "Failed to post reply"
msgstr "Impossible de publier la réponse"
#: src/pages/PlaylistDetail.tsx:795
#: src/pages/UserPublicProfile.tsx:688
#: src/pages/UserPublicProfile.tsx:726
#: src/pages/UserPublicProfile.tsx:855
#: src/pages/UserPublicProfile.tsx:935
#: src/pages/UserPublicProfile.tsx:701
#: src/pages/UserPublicProfile.tsx:739
#: src/pages/UserPublicProfile.tsx:868
#: src/pages/UserPublicProfile.tsx:948
msgid "Failed to save"
msgstr "Enregistrement échoué"
@@ -468,7 +468,7 @@ msgstr "Enregistrement échoué"
msgid "Failed to save edit"
msgstr "Impossible d'enregistrer la modification"
#: src/pages/UserPublicProfile.tsx:880
#: src/pages/UserPublicProfile.tsx:893
msgid "Failed to update avatar"
msgstr "Impossible de mettre à jour l'avatar"
@@ -510,8 +510,8 @@ msgstr "Suivez des collections publiques pour voir leurs recos ici."
msgid "Follow some users to see their dumps here."
msgstr "Suivez des utilisateurs pour voir leurs recos ici."
#: src/components/FeedTabBar.tsx:20
#: src/pages/UserPublicProfile.tsx:978
#: src/components/FeedTabBar.tsx:16
#: src/pages/UserPublicProfile.tsx:991
msgid "Followed"
msgstr "Suivi"
@@ -521,13 +521,13 @@ msgstr "Suivi"
msgid "Followed ({0}{1})"
msgstr "Suivies ({0}{1})"
#: src/pages/UserPublicProfile.tsx:1126
#: src/pages/UserPublicProfile.tsx:1139
msgid "Followed playlists"
msgstr "Collections suivies"
#: src/components/FollowButton.tsx:37
#: src/components/FollowButton.tsx:64
#: src/pages/UserPublicProfile.tsx:1084
#: src/pages/UserPublicProfile.tsx:1097
msgid "Following"
msgstr "Abonné"
@@ -551,7 +551,7 @@ msgstr "Accueil"
msgid "Go to login"
msgstr "Aller à la connexion"
#: src/components/FeedTabBar.tsx:16
#: src/components/FeedTabBar.tsx:12
msgid "Hot"
msgstr "Tendances"
@@ -567,16 +567,16 @@ msgstr "Invitation invalide"
msgid "Invalid link"
msgstr "Lien invalide"
#: src/pages/UserPublicProfile.tsx:799
#: src/pages/UserPublicProfile.tsx:812
msgid "invited by"
msgstr "invité par"
#: src/pages/UserPublicProfile.tsx:979
#: src/pages/UserPublicProfile.tsx:1171
#: src/pages/UserPublicProfile.tsx:992
#: src/pages/UserPublicProfile.tsx:1184
msgid "Invitees"
msgstr "Invités"
#: src/components/FeedTabBar.tsx:18
#: src/components/FeedTabBar.tsx:14
msgid "Journal"
msgstr "Journal"
@@ -584,7 +584,7 @@ msgstr "Journal"
msgid "just now"
msgstr "à l'instant"
#: src/pages/UserPublicProfile.tsx:1264
#: src/pages/UserPublicProfile.tsx:1277
msgid "Light"
msgstr "Clair"
@@ -614,7 +614,7 @@ msgstr "Chargement de la reco…"
#: src/pages/index/HotFeed.tsx:64
#: src/pages/index/JournalFeed.tsx:77
#: src/pages/index/NewFeed.tsx:64
#: src/pages/Search.tsx:248
#: src/pages/Search.tsx:242
#: src/pages/UserDumps.tsx:93
#: src/pages/UserPlaylists.tsx:417
#: src/pages/UserPlaylists.tsx:452
@@ -626,7 +626,7 @@ msgstr "Chargement…"
msgid "Loading playlist…"
msgstr "Chargement de la collection…"
#: src/pages/UserPublicProfile.tsx:736
#: src/pages/UserPublicProfile.tsx:749
msgid "Loading profile…"
msgstr "Chargement du profil…"
@@ -642,9 +642,9 @@ msgstr "Chargement du profil…"
#: src/pages/Notifications.tsx:439
#: src/pages/UserDumps.tsx:51
#: src/pages/UserPlaylists.tsx:342
#: src/pages/UserPublicProfile.tsx:1089
#: src/pages/UserPublicProfile.tsx:1131
#: src/pages/UserPublicProfile.tsx:1176
#: src/pages/UserPublicProfile.tsx:1102
#: src/pages/UserPublicProfile.tsx:1144
#: src/pages/UserPublicProfile.tsx:1189
#: src/pages/UserUpvoted.tsx:122
msgid "Loading…"
msgstr "Chargement…"
@@ -663,8 +663,8 @@ msgstr "Se connecter pour aimer"
msgid "Log in to vote"
msgstr "Se connecter pour voter"
#: src/pages/UserPublicProfile.tsx:757
#: src/pages/UserPublicProfile.tsx:894
#: src/pages/UserPublicProfile.tsx:770
#: src/pages/UserPublicProfile.tsx:907
msgid "Log out"
msgstr "Se déconnecter"
@@ -684,13 +684,13 @@ msgstr "Max 50 Mo"
msgid "new"
msgstr "nouveau"
#: src/components/FeedTabBar.tsx:17
#: src/components/FeedTabBar.tsx:13
msgid "New"
msgstr "Nouveau"
#: src/components/DumpCreateModal.tsx:302
#: src/pages/UserDumps.tsx:115
#: src/pages/UserPublicProfile.tsx:1320
#: src/pages/UserPublicProfile.tsx:1333
msgid "New dump"
msgstr "Nouvelle reco"
@@ -708,7 +708,7 @@ msgstr "Nouvelle collection"
msgid "No dumps in this playlist yet."
msgstr "Aucune reco dans cette collection pour l'instant."
#: src/pages/Search.tsx:228
#: src/pages/Search.tsx:222
msgid "No dumps match \"{q}\"."
msgstr "Aucune reco ne correspond à « {q} »."
@@ -723,11 +723,11 @@ msgid "No emoji found."
msgstr "Aucun emoji trouvé."
#: src/pages/UserPlaylists.tsx:439
#: src/pages/UserPublicProfile.tsx:1144
#: src/pages/UserPublicProfile.tsx:1157
msgid "No followed playlists yet."
msgstr "Pas encore de collections suivies."
#: src/pages/UserPublicProfile.tsx:1189
#: src/pages/UserPublicProfile.tsx:1202
msgid "No invitees yet."
msgstr "Aucun invité pour le moment."
@@ -735,28 +735,28 @@ msgstr "Aucun invité pour le moment."
msgid "No one yet."
msgstr "Personne pour le moment."
#: src/pages/Search.tsx:287
#: src/pages/Search.tsx:281
msgid "No playlists match \"{q}\"."
msgstr "Aucune collection ne correspond à « {q} »."
#: src/components/PlaylistMembershipPanel.tsx:34
#: src/pages/UserPlaylists.tsx:397
#: src/pages/UserPublicProfile.tsx:1055
#: src/pages/UserPublicProfile.tsx:1068
msgid "No playlists yet."
msgstr "Pas encore de collections."
#: src/pages/Search.tsx:261
#: src/pages/Search.tsx:255
msgid "No users match \"{q}\"."
msgstr "Aucun utilisateur ne correspond à « {q} »."
#: src/pages/UserPublicProfile.tsx:1102
#: src/pages/UserPublicProfile.tsx:1115
msgid "Not following anyone yet."
msgstr "Aucun abonnement pour le moment."
#: src/pages/Notifications.tsx:374
#: src/pages/UserDumps.tsx:125
#: src/pages/UserPublicProfile.tsx:1331
#: src/pages/UserPublicProfile.tsx:1453
#: src/pages/UserPublicProfile.tsx:1344
#: src/pages/UserPublicProfile.tsx:1466
#: src/pages/UserUpvoted.tsx:194
msgid "Nothing here yet."
msgstr "Rien ici pour l'instant."
@@ -779,7 +779,7 @@ msgid "or <0>browse files</0>"
msgstr "ou <0>parcourir les fichiers</0>"
#: src/pages/UserLogin.tsx:106
#: src/pages/UserPublicProfile.tsx:1209
#: src/pages/UserPublicProfile.tsx:1222
msgid "Password"
msgstr "Mot de passe"
@@ -803,15 +803,15 @@ msgstr "Les mots de passe ne correspondent pas"
#: src/components/AppHeader.tsx:85
#: src/components/UserMenu.tsx:62
#: src/pages/Search.tsx:181
#: src/pages/Search.tsx:175
#: src/pages/UserPlaylists.tsx:368
#: src/pages/UserPublicProfile.tsx:977
#: src/pages/UserPublicProfile.tsx:990
msgid "Playlists"
msgstr "Collections"
#. placeholder {0}: playlists.items.length
#. placeholder {1}: playlists.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1024
#: src/pages/UserPublicProfile.tsx:1037
msgid "Playlists ({0}{1})"
msgstr "Collections ({0}{1})"
@@ -883,7 +883,7 @@ msgstr "Inscription échouée"
#: src/pages/Dump.tsx:467
msgid "Related"
msgstr "Apparentés"
msgstr "Connexe"
#: src/components/FileDropZone.tsx:115
msgid "Remove file"
@@ -930,8 +930,8 @@ msgstr "Réessayer"
#: src/pages/Dump.tsx:347
#: src/pages/DumpEdit.tsx:400
#: src/pages/PlaylistDetail.tsx:679
#: src/pages/UserPublicProfile.tsx:842
#: src/pages/UserPublicProfile.tsx:924
#: src/pages/UserPublicProfile.tsx:855
#: src/pages/UserPublicProfile.tsx:937
msgid "Save"
msgstr "Enregistrer"
@@ -940,8 +940,8 @@ msgstr "Enregistrer"
#: src/pages/Dump.tsx:347
#: src/pages/PlaylistDetail.tsx:679
#: src/pages/ResetPassword.tsx:152
#: src/pages/UserPublicProfile.tsx:841
#: src/pages/UserPublicProfile.tsx:924
#: src/pages/UserPublicProfile.tsx:854
#: src/pages/UserPublicProfile.tsx:937
msgid "Saving…"
msgstr "Enregistrement…"
@@ -954,11 +954,11 @@ msgstr "Rechercher"
msgid "Search dumps, users, playlists…"
msgstr "Rechercher des recos, utilisateurs, collections…"
#: src/pages/Search.tsx:222
#: src/pages/Search.tsx:216
msgid "Search failed"
msgstr "Recherche échouée"
#: src/pages/Search.tsx:217
#: src/pages/Search.tsx:211
msgid "Searching…"
msgstr "Recherche…"
@@ -979,7 +979,7 @@ msgstr "Serveur inaccessible"
msgid "Set new password"
msgstr "Définir un nouveau mot de passe"
#: src/pages/UserPublicProfile.tsx:981
#: src/pages/UserPublicProfile.tsx:994
msgid "Settings"
msgstr "Paramètres"
@@ -987,7 +987,7 @@ msgstr "Paramètres"
msgid "Something went wrong"
msgstr "Une erreur est survenue"
#: src/pages/UserPublicProfile.tsx:1228
#: src/pages/UserPublicProfile.tsx:1241
msgid "Style"
msgstr "Style"
@@ -1042,7 +1042,7 @@ msgstr "Ne plus suivre la collection"
msgid "Unknown error"
msgstr "Erreur inconnue"
#: src/pages/UserPublicProfile.tsx:657
#: src/pages/UserPublicProfile.tsx:670
msgid "Upload failed"
msgstr "Envoi échoué"
@@ -1060,7 +1060,7 @@ msgstr "Voté"
#. placeholder {0}: votes.items.length
#. placeholder {1}: votes.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1004
#: src/pages/UserPublicProfile.tsx:1017
msgid "Upvoted ({0}{1})"
msgstr "Votés ({0}{1})"
@@ -1082,15 +1082,15 @@ msgstr "Menu utilisateur"
msgid "Username"
msgstr "Nom d'utilisateur"
#: src/pages/Search.tsx:180
#: src/pages/Search.tsx:174
msgid "Users"
msgstr "Utilisateurs"
#: src/pages/UserPublicProfile.tsx:1074
#: src/pages/UserPublicProfile.tsx:1117
#: src/pages/UserPublicProfile.tsx:1159
#: src/pages/UserPublicProfile.tsx:1352
#: src/pages/UserPublicProfile.tsx:1483
#: src/pages/UserPublicProfile.tsx:1087
#: src/pages/UserPublicProfile.tsx:1130
#: src/pages/UserPublicProfile.tsx:1172
#: src/pages/UserPublicProfile.tsx:1365
#: src/pages/UserPublicProfile.tsx:1496
msgid "View all →"
msgstr "Tout voir →"
@@ -1103,8 +1103,8 @@ msgstr "Voir la reco →"
msgid "What makes it worth it?"
msgstr "Pourquoi on en voudrait ?"
#: src/pages/UserPublicProfile.tsx:912
#: src/pages/UserPublicProfile.tsx:961
#: src/pages/UserPublicProfile.tsx:925
#: src/pages/UserPublicProfile.tsx:974
msgid "Who am I?"
msgstr "Qui suis-je ?"
@@ -1129,7 +1129,7 @@ msgstr "Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vo
#: src/pages/index/HotFeed.tsx:69
#: src/pages/index/JournalFeed.tsx:82
#: src/pages/index/NewFeed.tsx:69
#: src/pages/Search.tsx:253
#: src/pages/Search.tsx:247
#: src/pages/UserDumps.tsx:98
#: src/pages/UserPlaylists.tsx:422
#: src/pages/UserPlaylists.tsx:457

View File

@@ -11,7 +11,8 @@ import { useLocation } from "react-router";
import { AppHeader } from "../components/AppHeader.tsx";
import { PresenceRow } from "../components/PresenceRow.tsx";
import { FeedTabBar } from "../components/FeedTabBar.tsx";
import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts";
import { type FeedTab, FEED_TABS } from "../config/feedTabs.ts";
import { useTabParam } from "../hooks/useTabParam.ts";
import { API_URL, DEFAULT_PAGE_SIZE } from "../config/api.ts";
@@ -82,20 +83,20 @@ export function Index() {
);
const mainFetchDone = useRef(false);
const searchParams = new URLSearchParams(location.search);
const rawTab = searchParams.get("tab") ?? "hot";
const tab: FeedTab = VALID_TABS.has(rawTab) ? rawTab as FeedTab : "hot";
const [tab] = useTabParam<FeedTab>(FEED_TABS, "hot");
// Web Share Target: Android share sheet navigates to /?share_url=...
const searchParams = new URLSearchParams(location.search);
const shareUrl = searchParams.get("share_url") ??
searchParams.get("share_text") ?? "";
useEffect(() => {
if (!shareUrl) return;
// Clean share params from the URL so a refresh doesn't re-open the modal
const clean = tab !== "hot" ? `?tab=${tab}` : "";
globalThis.history.replaceState({}, "", location.pathname + clean);
}, [shareUrl, tab, location.pathname]);
// Clean share params from the URL so a refresh doesn't re-open the modal.
// The active tab already lives in the pathname (e.g. /~/new), so just drop
// the query string.
globalThis.history.replaceState({}, "", location.pathname);
}, [shareUrl, location.pathname]);
// ── Main feed fetch ──

View File

@@ -70,7 +70,7 @@ export function ResetPassword() {
);
}
const handleSubmit = async (e: React.FormEvent) => {
const handleSubmit = async (e: React.SubmitEvent) => {
e.preventDefault();
if (mismatch || tooShort || !newPassword) return;

View File

@@ -10,6 +10,7 @@ import { ErrorCard } from "../components/ErrorCard.tsx";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
import { useTabParam } from "../hooks/useTabParam.ts";
import {
deserializeDump,
deserializePlaylist,
@@ -23,7 +24,8 @@ import {
} from "../model.ts";
import { DEFAULT_PAGE_SIZE, SEARCH_URL } from "../config/api.ts";
type Tab = "dumps" | "users" | "playlists";
const SEARCH_TABS = ["dumps", "users", "playlists"] as const;
type Tab = (typeof SEARCH_TABS)[number];
type SearchState =
| { status: "idle" }
@@ -44,9 +46,9 @@ type SearchState =
};
export function Search() {
const [searchParams, setSearchParams] = useSearchParams();
const [searchParams] = useSearchParams();
const q = searchParams.get("q") ?? "";
const tab = (searchParams.get("tab") ?? "dumps") as Tab;
const [tab, setTab] = useTabParam<Tab>(SEARCH_TABS, "dumps");
const { token, user } = useAuth();
const { voteCounts, myVotes, castVote, removeVote } = useWS();
@@ -159,14 +161,6 @@ export function Search() {
!state.dumps.loadingMore,
);
function setTab(tab: Tab) {
setSearchParams((prev) => {
const next = new URLSearchParams(prev);
next.set("tab", tab);
return next;
}, { replace: true });
}
const dumpCount = state.status === "loaded" ? state.dumps.total : null;
const userCount = state.status === "loaded" ? state.users.length : null;
const playlistCount = state.status === "loaded"
@@ -188,7 +182,7 @@ export function Search() {
<main className="search-page">
{q && (
<TabBar
tabs={(["dumps", "users", "playlists"] as Tab[]).map((key) => ({
tabs={SEARCH_TABS.map((key) => ({
key,
label: tabLabel(
key,

View File

@@ -64,7 +64,7 @@ export function UserLogin() {
}
};
const handleResetRequest = async (e: React.FormEvent) => {
const handleResetRequest = async (e: React.SubmitEvent) => {
e.preventDefault();
if (!resetEmail.trim()) return;
setResetState({ status: "submitting" });

View File

@@ -54,6 +54,7 @@ import { TextEditor } from "../components/TextEditor.tsx";
import { Markdown } from "../components/Markdown.tsx";
import { ChangePasswordModal } from "../components/ChangePasswordModal.tsx";
import { TabBar } from "../components/TabBar.tsx";
import { useTabParam } from "../hooks/useTabParam.ts";
function InviteButton() {
const { authFetch } = useAuth();
@@ -146,7 +147,14 @@ type InviteTreeState =
| { status: "loaded"; entries: InviteTreeEntry[] };
type InviteTreeNode = InviteTreeEntry & { children: InviteTreeNode[] };
type ProfileTab = "dumps" | "playlists" | "followed" | "invitees" | "settings";
const PROFILE_TABS = [
"dumps",
"playlists",
"followed",
"invitees",
"settings",
] as const;
type ProfileTab = (typeof PROFILE_TABS)[number];
function buildInviteTree(
entries: InviteTreeEntry[],
@@ -193,6 +201,13 @@ export function UserPublicProfile() {
const profileUserId = state.status === "loaded" ? state.user.id : null;
const isOwnProfile = me?.id === profileUserId;
// Active tab is driven by the `/~/<tab>` URL path (linkable / refresh-safe).
// `settings` is only valid on your own profile; otherwise it falls back to "dumps".
const availableTabs: ProfileTab[] = isOwnProfile
? [...PROFILE_TABS]
: PROFILE_TABS.filter((t) => t !== "settings");
const [tab, setTab] = useTabParam<ProfileTab>(availableTabs, "dumps");
const setDumps = useCallback((fn: (prev: Dump[]) => Dump[]) => {
setState((s) =>
s.status !== "loaded"
@@ -287,7 +302,6 @@ export function UserPublicProfile() {
const [emailError, setEmailError] = useState<string | null>(null);
const prevMyVotesRef = useRef<Set<string> | null>(null);
const [tab, setTab] = useState<ProfileTab>("dumps");
const [changePasswordOpen, setChangePasswordOpen] = useState(false);
const [followedState, setFollowedState] = useState<FollowedState>(null);
const [inviteTreeState, setInviteTreeState] = useState<InviteTreeState>(null);
@@ -301,7 +315,6 @@ export function UserPublicProfile() {
if (prevUsername !== username) {
setPrevUsername(username);
setState({ status: "loading" });
setTab("dumps");
setFollowedState(null);
setInviteTreeState(null);
}