v3: added password change/reset feature
This commit is contained in:
45
src/App.css
45
src/App.css
@@ -2021,6 +2021,51 @@ body.has-player .fab-new {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-link-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--color-accent);
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.auth-link-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.auth-reset-panel {
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
padding-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.auth-reset-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-reset-sent {
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.auth-field-hint {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.auth-field-hint--error {
|
||||
color: var(--color-error, #e53e3e);
|
||||
}
|
||||
|
||||
/* ── Form pages (DumpCreate / DumpEdit) ── */
|
||||
@keyframes page-enter {
|
||||
from {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { UserPlaylists } from "./pages/UserPlaylists.tsx";
|
||||
import { PlaylistDetail } from "./pages/PlaylistDetail.tsx";
|
||||
import { Notifications } from "./pages/Notifications.tsx";
|
||||
import { Search } from "./pages/Search.tsx";
|
||||
import { ResetPassword } from "./pages/ResetPassword.tsx";
|
||||
|
||||
import { AuthProvider } from "./contexts/AuthProvider.tsx";
|
||||
import { PlayerProvider } from "./contexts/PlayerProvider.tsx";
|
||||
@@ -62,6 +63,7 @@ function AppRoutes() {
|
||||
/>
|
||||
<Route path="/playlists/:playlistId" element={<PlaylistDetail />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/reset-password" element={<ResetPassword />} />
|
||||
<Route
|
||||
path="/notifications"
|
||||
element={
|
||||
|
||||
186
src/components/ChangePasswordModal.tsx
Normal file
186
src/components/ChangePasswordModal.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useState } from "react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { API_URL, VALIDATION } from "../config/api.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { ErrorCard } from "./ErrorCard.tsx";
|
||||
import { Modal } from "./Modal.tsx";
|
||||
|
||||
interface ChangePasswordModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function ChangePasswordModal({ onClose }: ChangePasswordModalProps) {
|
||||
const { authFetch } = useAuth();
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [done, setDone] = useState(false);
|
||||
|
||||
const mismatch = confirmPassword.length > 0 &&
|
||||
newPassword !== confirmPassword;
|
||||
const tooShort = newPassword.length > 0 &&
|
||||
newPassword.length < VALIDATION.PASSWORD_MIN;
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (mismatch || tooShort || !currentPassword || !newPassword) return;
|
||||
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await authFetch(
|
||||
`${API_URL}/api/users/me/change-password`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ currentPassword, newPassword }),
|
||||
},
|
||||
);
|
||||
const body = await res.json();
|
||||
if (!body.success) {
|
||||
setError(body.error?.message ?? t`Unknown error`);
|
||||
return;
|
||||
}
|
||||
setDone(true);
|
||||
} catch {
|
||||
setError(t`Failed to change password`);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title={t`Change password`} onClose={onClose}>
|
||||
{done
|
||||
? (
|
||||
<div className="modal-new-playlist-form">
|
||||
<p style={{ color: "var(--color-success, green)" }}>
|
||||
<Trans>Password changed successfully.</Trans>
|
||||
</p>
|
||||
<div className="form-actions">
|
||||
<div className="form-actions-right">
|
||||
<button type="button" className="btn-primary" onClick={onClose}>
|
||||
<Trans>Close</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<form className="modal-new-playlist-form" onSubmit={handleSubmit}>
|
||||
<label>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
marginBottom: "0.25rem",
|
||||
fontSize: "0.85rem",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
<Trans>Current password</Trans>
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
autoComplete="current-password"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
marginBottom: "0.25rem",
|
||||
fontSize: "0.85rem",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
<Trans>New password</Trans>
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
minLength={VALIDATION.PASSWORD_MIN}
|
||||
maxLength={VALIDATION.PASSWORD_MAX}
|
||||
required
|
||||
/>
|
||||
{tooShort && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.8rem",
|
||||
color: "var(--color-error, red)",
|
||||
marginTop: "0.2rem",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
<Trans>At least {VALIDATION.PASSWORD_MIN} characters</Trans>
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
<label>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
marginBottom: "0.25rem",
|
||||
fontSize: "0.85rem",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
<Trans>Confirm new password</Trans>
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
/>
|
||||
{mismatch && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.8rem",
|
||||
color: "var(--color-error, red)",
|
||||
marginTop: "0.2rem",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
<Trans>Passwords do not match</Trans>
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
{error && (
|
||||
<ErrorCard title={t`Could not change password`} message={error} />
|
||||
)}
|
||||
<div className="form-actions">
|
||||
<div className="form-actions-right">
|
||||
<button
|
||||
type="button"
|
||||
className="form-cancel"
|
||||
onClick={onClose}
|
||||
>
|
||||
<Trans>Cancel</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
disabled={submitting || mismatch || tooShort ||
|
||||
!currentPassword || !newPassword || !confirmPassword}
|
||||
>
|
||||
{submitting
|
||||
? <Trans>Saving…</Trans>
|
||||
: <Trans>Change password</Trans>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
} from "../model.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { dumpUrl } from "../utils/urls.ts";
|
||||
import { dumpUrl, normalizeUrl } from "../utils/urls.ts";
|
||||
import { MAX_FILE_SIZE } from "../config/upload.ts";
|
||||
import RichContentCard from "./RichContentCard.tsx";
|
||||
import { MediaPlayer } from "./MediaPlayer.tsx";
|
||||
import type { RichContent } from "../model.ts";
|
||||
@@ -29,14 +30,6 @@ import { Modal } from "./Modal.tsx";
|
||||
import { PlaylistMembershipPanel } from "./PlaylistMembershipPanel.tsx";
|
||||
import { friendlyFetchError } from "../utils/apiError.ts";
|
||||
|
||||
function normalizeUrl(input: string): string {
|
||||
const s = input.trim();
|
||||
if (!s || /^https?:\/\//i.test(s)) return s;
|
||||
if (s.startsWith("//")) return `https:${s}`;
|
||||
return `https://${s}`;
|
||||
}
|
||||
import { MAX_FILE_SIZE } from "../config/upload.ts";
|
||||
|
||||
type Mode = "url" | "file";
|
||||
type Phase = "create" | "playlist";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from "react";
|
||||
import { API_URL } from "../config/api.ts";
|
||||
import type { Dump } from "../model.ts";
|
||||
import { formatBytes } from "../utils/format.ts";
|
||||
import { MediaPlayer } from "./MediaPlayer.tsx";
|
||||
import { IconPause, IconPlay, MediaPlayer } from "./MediaPlayer.tsx";
|
||||
import { PlayerContext } from "../contexts/PlayerContext.ts";
|
||||
import {
|
||||
BAR_GAP,
|
||||
@@ -76,26 +76,7 @@ function AudioFilePreview(
|
||||
onClick={handlePlayBtn}
|
||||
aria-label={isPlaying ? "Pause" : "Play"}
|
||||
>
|
||||
{isPlaying
|
||||
? (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
style={{ padding: "1px" }}
|
||||
>
|
||||
<rect x="5" y="3" width="4" height="18" rx="1" />
|
||||
<rect x="15" y="3" width="4" height="18" rx="1" />
|
||||
</svg>
|
||||
)
|
||||
: (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
style={{ marginLeft: "2px" }}
|
||||
>
|
||||
<polygon points="6,3 20,12 6,21" />
|
||||
</svg>
|
||||
)}
|
||||
{isPlaying ? <IconPause /> : <IconPlay />}
|
||||
</button>
|
||||
{peaks
|
||||
? (
|
||||
|
||||
@@ -15,13 +15,13 @@ function fmt(s: number): string {
|
||||
return `${m}:${sec.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
const IconPlay = () => (
|
||||
export const IconPlay = () => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" style={{ marginLeft: "2px" }}>
|
||||
<polygon points="6,3 20,12 6,21" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const IconPause = () => (
|
||||
export const IconPause = () => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" style={{ padding: "1px" }}>
|
||||
<rect x="5" y="3" width="4" height="18" rx="1" />
|
||||
<rect x="15" y="3" width="4" height="18" rx="1" />
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -54,7 +54,7 @@ msgid "{visibleCount, plural, one {# comment} other {# comments}}"
|
||||
msgstr "{visibleCount, plural, one {# comment} other {# comments}}"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:611
|
||||
#: src/pages/UserPublicProfile.tsx:728
|
||||
#: src/pages/UserPublicProfile.tsx:745
|
||||
msgid "← Back"
|
||||
msgstr "← Back"
|
||||
|
||||
@@ -70,7 +70,7 @@ msgstr "← Back to all dumps"
|
||||
msgid "← Back to profile"
|
||||
msgstr "← Back to profile"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:93
|
||||
#: src/pages/UserPublicProfile.tsx:100
|
||||
msgid "+ Invite someone"
|
||||
msgstr "+ Invite someone"
|
||||
|
||||
@@ -79,7 +79,7 @@ msgid "+ New"
|
||||
msgstr "+ New"
|
||||
|
||||
#: src/pages/UserDumps.tsx:114
|
||||
#: src/pages/UserPublicProfile.tsx:1282
|
||||
#: src/pages/UserPublicProfile.tsx:1330
|
||||
msgid "+ New dump"
|
||||
msgstr "+ New dump"
|
||||
|
||||
@@ -134,7 +134,11 @@ msgstr "a comment"
|
||||
msgid "a post"
|
||||
msgstr "a post"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:931
|
||||
#: src/pages/UserPublicProfile.tsx:1215
|
||||
msgid "Account"
|
||||
msgstr "Account"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:948
|
||||
msgid "Add a bio…"
|
||||
msgstr "Add a bio…"
|
||||
|
||||
@@ -142,12 +146,12 @@ msgstr "Add a bio…"
|
||||
msgid "Add a comment…"
|
||||
msgstr "Add a comment…"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:842
|
||||
#: src/pages/UserPublicProfile.tsx:859
|
||||
msgid "Add email…"
|
||||
msgstr "Add email…"
|
||||
|
||||
#: src/components/AddToPlaylistModal.tsx:64
|
||||
#: src/components/DumpCreateModal.tsx:284
|
||||
#: src/components/DumpCreateModal.tsx:277
|
||||
msgid "Add to playlist"
|
||||
msgstr "Add to playlist"
|
||||
|
||||
@@ -167,29 +171,41 @@ 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:1186
|
||||
#: src/pages/UserPublicProfile.tsx:1234
|
||||
msgid "Appearance"
|
||||
msgstr "Appearance"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1220
|
||||
#. placeholder {0}: VALIDATION.PASSWORD_MIN
|
||||
#: src/components/ChangePasswordModal.tsx:101
|
||||
#: src/pages/ResetPassword.tsx:113
|
||||
msgid "At least {0} characters"
|
||||
msgstr "At least {0} characters"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1268
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:36
|
||||
#: src/pages/ResetPassword.tsx:146
|
||||
msgid "Back to login"
|
||||
msgstr "Back to login"
|
||||
|
||||
#: src/contexts/WSProvider.tsx:168
|
||||
#: src/contexts/WSProvider.tsx:360
|
||||
msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
||||
msgstr "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:132
|
||||
#: src/components/CommentThread.tsx:281
|
||||
#: src/components/CommentThread.tsx:373
|
||||
#: src/components/CommentThread.tsx:510
|
||||
#: src/components/ConfirmModal.tsx:32
|
||||
#: src/components/DumpCreateModal.tsx:422
|
||||
#: src/components/DumpCreateModal.tsx:415
|
||||
#: src/components/PlaylistCreateForm.tsx:112
|
||||
#: src/pages/DumpEdit.tsx:299
|
||||
#: src/pages/PlaylistDetail.tsx:680
|
||||
#: src/pages/UserPublicProfile.tsx:824
|
||||
#: src/pages/UserPublicProfile.tsx:902
|
||||
#: src/pages/UserPublicProfile.tsx:841
|
||||
#: src/pages/UserPublicProfile.tsx:919
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
@@ -201,19 +217,29 @@ msgstr "Cancel removal"
|
||||
#~ msgid "Cannot edit a deleted comment"
|
||||
#~ msgstr "Cannot edit a deleted comment"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:755
|
||||
#: src/pages/UserPublicProfile.tsx:772
|
||||
msgid "Change avatar"
|
||||
msgstr "Change avatar"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:55
|
||||
#: src/components/ChangePasswordModal.tsx:142
|
||||
msgid "Change password"
|
||||
msgstr "Change password"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1227
|
||||
msgid "Change password…"
|
||||
msgstr "Change password…"
|
||||
|
||||
#: src/pages/UserRegister.tsx:95
|
||||
msgid "Checking invite…"
|
||||
msgstr "Checking invite…"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:65
|
||||
#: src/components/Modal.tsx:45
|
||||
msgid "Close"
|
||||
msgstr "Close"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1212
|
||||
#: src/pages/UserPublicProfile.tsx:1260
|
||||
msgid "Color scheme"
|
||||
msgstr "Color scheme"
|
||||
|
||||
@@ -221,14 +247,28 @@ msgstr "Color scheme"
|
||||
#~ msgid "Comment not found"
|
||||
#~ msgstr "Comment not found"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:84
|
||||
#: src/components/ChangePasswordModal.tsx:107
|
||||
#: src/pages/ResetPassword.tsx:120
|
||||
msgid "Confirm new password"
|
||||
msgstr "Confirm new password"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:91
|
||||
msgid "Copied!"
|
||||
msgstr "Copied!"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:84
|
||||
#: src/pages/UserPublicProfile.tsx:91
|
||||
msgid "Copy"
|
||||
msgstr "Copy"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:123
|
||||
msgid "Could not change password"
|
||||
msgstr "Could not change password"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:84
|
||||
#: src/pages/UserLogin.tsx:79
|
||||
msgid "Could not connect to server"
|
||||
msgstr "Could not connect to server"
|
||||
|
||||
#: src/components/CommentThread.tsx:111
|
||||
#: src/components/CommentThread.tsx:153
|
||||
#: src/components/CommentThread.tsx:448
|
||||
@@ -253,7 +293,11 @@ msgstr "Created ({0}{1})"
|
||||
msgid "Creating…"
|
||||
msgstr "Creating…"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1234
|
||||
#: src/components/ChangePasswordModal.tsx:75
|
||||
msgid "Current password"
|
||||
msgstr "Current password"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1282
|
||||
msgid "Dark"
|
||||
msgstr "Dark"
|
||||
|
||||
@@ -293,7 +337,7 @@ msgstr "Delete this playlist? This cannot be undone."
|
||||
msgid "Description (optional)"
|
||||
msgstr "Description (optional)"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:468
|
||||
#: src/components/DumpCreateModal.tsx:461
|
||||
msgid "Done"
|
||||
msgstr "Done"
|
||||
|
||||
@@ -305,7 +349,7 @@ msgstr "Drop a file here"
|
||||
msgid "Drop a replacement here"
|
||||
msgstr "Drop a replacement here"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:434
|
||||
#: src/components/DumpCreateModal.tsx:427
|
||||
msgid "Dump it"
|
||||
msgstr "Dump it"
|
||||
|
||||
@@ -313,19 +357,19 @@ msgstr "Dump it"
|
||||
#~ msgid "Dump not found"
|
||||
#~ msgstr "Dump not found"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:445
|
||||
#: src/components/DumpCreateModal.tsx:438
|
||||
msgid "Dumped!"
|
||||
msgstr "Dumped!"
|
||||
|
||||
#: src/pages/Search.tsx:172
|
||||
#: src/pages/UserDumps.tsx:107
|
||||
#: src/pages/UserPublicProfile.tsx:950
|
||||
#: src/pages/UserPublicProfile.tsx:967
|
||||
msgid "Dumps"
|
||||
msgstr "Dumps"
|
||||
|
||||
#. placeholder {0}: dumps.items.length
|
||||
#. placeholder {1}: dumps.hasMore ? "+" : ""
|
||||
#: src/pages/UserPublicProfile.tsx:987
|
||||
#: src/pages/UserPublicProfile.tsx:1004
|
||||
msgid "Dumps ({0}{1})"
|
||||
msgstr "Dumps ({0}{1})"
|
||||
|
||||
@@ -369,14 +413,18 @@ msgstr "Email address"
|
||||
msgid "Enter a query to search."
|
||||
msgstr "Enter a query to search."
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:48
|
||||
msgid "Failed to change password"
|
||||
msgstr "Failed to change password"
|
||||
|
||||
#: src/components/PlaylistCreateForm.tsx:62
|
||||
#: src/components/PlaylistCreateForm.tsx:103
|
||||
msgid "Failed to create playlist"
|
||||
msgstr "Failed to create playlist"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:65
|
||||
#: src/pages/UserPublicProfile.tsx:68
|
||||
#: src/pages/UserPublicProfile.tsx:96
|
||||
#: src/pages/UserPublicProfile.tsx:72
|
||||
#: src/pages/UserPublicProfile.tsx:75
|
||||
#: src/pages/UserPublicProfile.tsx:103
|
||||
msgid "Failed to generate invite"
|
||||
msgstr "Failed to generate invite"
|
||||
|
||||
@@ -385,13 +433,13 @@ msgstr "Failed to generate invite"
|
||||
#: src/pages/index/JournalFeed.tsx:48
|
||||
#: src/pages/index/NewFeed.tsx:36
|
||||
#: src/pages/Notifications.tsx:323
|
||||
#: src/pages/UserPublicProfile.tsx:1081
|
||||
#: src/pages/UserPublicProfile.tsx:1118
|
||||
#: src/pages/UserPublicProfile.tsx:1160
|
||||
#: src/pages/UserPublicProfile.tsx:1106
|
||||
#: src/pages/UserPublicProfile.tsx:1148
|
||||
#: src/pages/UserPublicProfile.tsx:1193
|
||||
msgid "Failed to load"
|
||||
msgstr "Failed to load"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:322
|
||||
#: src/components/DumpCreateModal.tsx:315
|
||||
msgid "Failed to post"
|
||||
msgstr "Failed to post"
|
||||
|
||||
@@ -404,10 +452,10 @@ msgid "Failed to post reply"
|
||||
msgstr "Failed to post reply"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:789
|
||||
#: src/pages/UserPublicProfile.tsx:663
|
||||
#: src/pages/UserPublicProfile.tsx:701
|
||||
#: src/pages/UserPublicProfile.tsx:828
|
||||
#: src/pages/UserPublicProfile.tsx:905
|
||||
#: src/pages/UserPublicProfile.tsx:680
|
||||
#: src/pages/UserPublicProfile.tsx:718
|
||||
#: src/pages/UserPublicProfile.tsx:845
|
||||
#: src/pages/UserPublicProfile.tsx:922
|
||||
msgid "Failed to save"
|
||||
msgstr "Failed to save"
|
||||
|
||||
@@ -415,19 +463,19 @@ msgstr "Failed to save"
|
||||
msgid "Failed to save edit"
|
||||
msgstr "Failed to save edit"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:851
|
||||
#: src/pages/UserPublicProfile.tsx:868
|
||||
msgid "Failed to update avatar"
|
||||
msgstr "Failed to update avatar"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:359
|
||||
#: src/components/DumpCreateModal.tsx:352
|
||||
msgid "Fetching preview…"
|
||||
msgstr "Fetching preview…"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:432
|
||||
#: src/components/DumpCreateModal.tsx:425
|
||||
msgid "Fetching…"
|
||||
msgstr "Fetching…"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:315
|
||||
#: src/components/DumpCreateModal.tsx:308
|
||||
#: src/components/FileDropZone.tsx:31
|
||||
msgid "File"
|
||||
msgstr "File"
|
||||
@@ -444,7 +492,7 @@ msgstr "File"
|
||||
#~ msgid "File too large (max 50 MB)"
|
||||
#~ msgstr "File too large (max 50 MB)"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:209
|
||||
#: src/components/DumpCreateModal.tsx:202
|
||||
msgid "File too large (max 50 MB)."
|
||||
msgstr "File too large (max 50 MB)."
|
||||
|
||||
@@ -470,7 +518,7 @@ msgid "Follow some users to see their dumps here."
|
||||
msgstr "Follow some users to see their dumps here."
|
||||
|
||||
#: src/components/FeedTabBar.tsx:47
|
||||
#: src/pages/UserPublicProfile.tsx:964
|
||||
#: src/pages/UserPublicProfile.tsx:981
|
||||
msgid "Followed"
|
||||
msgstr "Followed"
|
||||
|
||||
@@ -480,13 +528,13 @@ msgstr "Followed"
|
||||
msgid "Followed ({0}{1})"
|
||||
msgstr "Followed ({0}{1})"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1109
|
||||
#: src/pages/UserPublicProfile.tsx:1137
|
||||
msgid "Followed playlists"
|
||||
msgstr "Followed playlists"
|
||||
|
||||
#: src/components/FollowButton.tsx:37
|
||||
#: src/components/FollowButton.tsx:64
|
||||
#: src/pages/UserPublicProfile.tsx:1072
|
||||
#: src/pages/UserPublicProfile.tsx:1095
|
||||
msgid "Following"
|
||||
msgstr "Following"
|
||||
|
||||
@@ -494,6 +542,10 @@ msgstr "Following"
|
||||
#~ msgid "Forbidden"
|
||||
#~ msgstr "Forbidden"
|
||||
|
||||
#: src/pages/UserLogin.tsx:131
|
||||
msgid "Forgot password?"
|
||||
msgstr "Forgot password?"
|
||||
|
||||
#: src/pages/index/FollowedFeed.tsx:337
|
||||
msgid "From people"
|
||||
msgstr "From people"
|
||||
@@ -502,10 +554,18 @@ msgstr "From people"
|
||||
msgid "From playlists"
|
||||
msgstr "From playlists"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:56
|
||||
msgid "Go to login"
|
||||
msgstr "Go to login"
|
||||
|
||||
#: src/components/FeedTabBar.tsx:25
|
||||
msgid "Hot"
|
||||
msgstr "Hot"
|
||||
|
||||
#: src/pages/UserLogin.tsx:140
|
||||
msgid "If that address is registered you'll receive a reset link shortly."
|
||||
msgstr "If that address is registered you'll receive a reset link shortly."
|
||||
|
||||
#: api/auth:
|
||||
#~ msgid "Invalid email address"
|
||||
#~ msgstr "Invalid email address"
|
||||
@@ -514,6 +574,10 @@ msgstr "Hot"
|
||||
msgid "Invalid invite"
|
||||
msgstr "Invalid invite"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:33
|
||||
msgid "Invalid link"
|
||||
msgstr "Invalid link"
|
||||
|
||||
#: api/invites:
|
||||
#~ msgid "Invalid or expired invite"
|
||||
#~ msgstr "Invalid or expired invite"
|
||||
@@ -531,12 +595,12 @@ msgstr "Invalid invite"
|
||||
#~ msgid "Invite already used"
|
||||
#~ msgstr "Invite already used"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:773
|
||||
#: src/pages/UserPublicProfile.tsx:790
|
||||
msgid "invited by"
|
||||
msgstr "invited by"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:971
|
||||
#: src/pages/UserPublicProfile.tsx:1149
|
||||
#: src/pages/UserPublicProfile.tsx:988
|
||||
#: src/pages/UserPublicProfile.tsx:1182
|
||||
msgid "Invitees"
|
||||
msgstr "Invitees"
|
||||
|
||||
@@ -548,7 +612,7 @@ msgstr "Journal"
|
||||
msgid "just now"
|
||||
msgstr "just now"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1227
|
||||
#: src/pages/UserPublicProfile.tsx:1275
|
||||
msgid "Light"
|
||||
msgstr "Light"
|
||||
|
||||
@@ -585,7 +649,7 @@ msgstr "Loading more…"
|
||||
msgid "Loading playlist…"
|
||||
msgstr "Loading playlist…"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:711
|
||||
#: src/pages/UserPublicProfile.tsx:728
|
||||
msgid "Loading profile…"
|
||||
msgstr "Loading profile…"
|
||||
|
||||
@@ -599,29 +663,29 @@ msgstr "Loading profile…"
|
||||
#: src/pages/Notifications.tsx:395
|
||||
#: src/pages/UserDumps.tsx:51
|
||||
#: src/pages/UserPlaylists.tsx:342
|
||||
#: src/pages/UserPublicProfile.tsx:1077
|
||||
#: src/pages/UserPublicProfile.tsx:1114
|
||||
#: src/pages/UserPublicProfile.tsx:1154
|
||||
#: src/pages/UserPublicProfile.tsx:1100
|
||||
#: src/pages/UserPublicProfile.tsx:1142
|
||||
#: src/pages/UserPublicProfile.tsx:1187
|
||||
#: src/pages/UserUpvoted.tsx:123
|
||||
msgid "Loading…"
|
||||
msgstr "Loading…"
|
||||
|
||||
#: src/components/AppHeader.tsx:74
|
||||
#: src/pages/UserLogin.tsx:63
|
||||
#: src/pages/UserLogin.tsx:93
|
||||
#: src/pages/UserLogin.tsx:87
|
||||
#: src/pages/UserLogin.tsx:117
|
||||
msgid "Log in"
|
||||
msgstr "Log in"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:732
|
||||
#: src/pages/UserPublicProfile.tsx:865
|
||||
#: src/pages/UserPublicProfile.tsx:749
|
||||
#: src/pages/UserPublicProfile.tsx:882
|
||||
msgid "Log out"
|
||||
msgstr "Log out"
|
||||
|
||||
#: src/pages/UserLogin.tsx:92
|
||||
#: src/pages/UserLogin.tsx:116
|
||||
msgid "Logging in…"
|
||||
msgstr "Logging in…"
|
||||
|
||||
#: src/pages/UserLogin.tsx:67
|
||||
#: src/pages/UserLogin.tsx:91
|
||||
msgid "Login failed"
|
||||
msgstr "Login failed"
|
||||
|
||||
@@ -637,10 +701,15 @@ msgstr "new"
|
||||
msgid "New"
|
||||
msgstr "New"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:284
|
||||
#: src/components/DumpCreateModal.tsx:277
|
||||
msgid "New dump"
|
||||
msgstr "New dump"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:88
|
||||
#: src/pages/ResetPassword.tsx:101
|
||||
msgid "New password"
|
||||
msgstr "New password"
|
||||
|
||||
#: src/components/NewPlaylistForm.tsx:34
|
||||
msgid "New playlist"
|
||||
msgstr "New playlist"
|
||||
@@ -664,11 +733,11 @@ msgid "No emoji found."
|
||||
msgstr "No emoji found."
|
||||
|
||||
#: src/pages/UserPlaylists.tsx:439
|
||||
#: src/pages/UserPublicProfile.tsx:1122
|
||||
#: src/pages/UserPublicProfile.tsx:1155
|
||||
msgid "No followed playlists yet."
|
||||
msgstr "No followed playlists yet."
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1167
|
||||
#: src/pages/UserPublicProfile.tsx:1200
|
||||
msgid "No invitees yet."
|
||||
msgstr "No invitees yet."
|
||||
|
||||
@@ -678,7 +747,7 @@ msgstr "No playlists match \"{q}\"."
|
||||
|
||||
#: src/components/PlaylistMembershipPanel.tsx:34
|
||||
#: src/pages/UserPlaylists.tsx:397
|
||||
#: src/pages/UserPublicProfile.tsx:1043
|
||||
#: src/pages/UserPublicProfile.tsx:1066
|
||||
msgid "No playlists yet."
|
||||
msgstr "No playlists yet."
|
||||
|
||||
@@ -690,14 +759,14 @@ msgstr "No users match \"{q}\"."
|
||||
#~ msgid "Not authenticated"
|
||||
#~ msgstr "Not authenticated"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1085
|
||||
#: src/pages/UserPublicProfile.tsx:1113
|
||||
msgid "Not following anyone yet."
|
||||
msgstr "Not following anyone yet."
|
||||
|
||||
#: src/pages/Notifications.tsx:330
|
||||
#: src/pages/UserDumps.tsx:123
|
||||
#: src/pages/UserPublicProfile.tsx:1292
|
||||
#: src/pages/UserPublicProfile.tsx:1415
|
||||
#: src/pages/UserPublicProfile.tsx:1340
|
||||
#: src/pages/UserPublicProfile.tsx:1463
|
||||
#: src/pages/UserUpvoted.tsx:195
|
||||
msgid "Nothing here yet."
|
||||
msgstr "Nothing here yet."
|
||||
@@ -719,7 +788,8 @@ msgstr "Open search"
|
||||
msgid "or <0>browse files</0>"
|
||||
msgstr "or <0>browse files</0>"
|
||||
|
||||
#: src/pages/UserLogin.tsx:82
|
||||
#: src/pages/UserLogin.tsx:106
|
||||
#: src/pages/UserPublicProfile.tsx:1220
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
@@ -728,6 +798,10 @@ msgstr "Password"
|
||||
msgid "Password (min. {0} characters)"
|
||||
msgstr "Password (min. {0} characters)"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:60
|
||||
msgid "Password changed successfully."
|
||||
msgstr "Password changed successfully."
|
||||
|
||||
#: api/auth:
|
||||
#~ msgid "Password must be at least 8 characters"
|
||||
#~ msgstr "Password must be at least 8 characters"
|
||||
@@ -736,6 +810,15 @@ msgstr "Password (min. {0} characters)"
|
||||
#~ msgid "Password must be at most 128 characters"
|
||||
#~ msgstr "Password must be at most 128 characters"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:47
|
||||
msgid "Password updated"
|
||||
msgstr "Password updated"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:118
|
||||
#: src/pages/ResetPassword.tsx:129
|
||||
msgid "Passwords do not match"
|
||||
msgstr "Passwords do not match"
|
||||
|
||||
#: api/playlists:
|
||||
#~ msgid "Playlist not found"
|
||||
#~ msgstr "Playlist not found"
|
||||
@@ -744,17 +827,17 @@ msgstr "Password (min. {0} characters)"
|
||||
#: src/components/UserMenu.tsx:62
|
||||
#: src/pages/Search.tsx:175
|
||||
#: src/pages/UserPlaylists.tsx:368
|
||||
#: src/pages/UserPublicProfile.tsx:957
|
||||
#: src/pages/UserPublicProfile.tsx:974
|
||||
msgid "Playlists"
|
||||
msgstr "Playlists"
|
||||
|
||||
#. placeholder {0}: playlists.items.length
|
||||
#. placeholder {1}: playlists.hasMore ? "+" : ""
|
||||
#: src/pages/UserPublicProfile.tsx:1016
|
||||
#: src/pages/UserPublicProfile.tsx:1035
|
||||
msgid "Playlists ({0}{1})"
|
||||
msgstr "Playlists ({0}{1})"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:202
|
||||
#: src/components/DumpCreateModal.tsx:195
|
||||
msgid "Please select a file."
|
||||
msgstr "Please select a file."
|
||||
|
||||
@@ -779,7 +862,7 @@ msgstr "Posting…"
|
||||
msgid "private"
|
||||
msgstr "private"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:411
|
||||
#: src/components/DumpCreateModal.tsx:404
|
||||
#: src/components/PlaylistCreateForm.tsx:99
|
||||
#: src/pages/DumpEdit.tsx:285
|
||||
#: src/pages/PlaylistDetail.tsx:746
|
||||
@@ -791,7 +874,7 @@ msgstr "Private"
|
||||
msgid "public"
|
||||
msgstr "public"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:403
|
||||
#: src/components/DumpCreateModal.tsx:396
|
||||
#: src/components/PlaylistCreateForm.tsx:92
|
||||
#: src/pages/DumpEdit.tsx:278
|
||||
#: src/pages/PlaylistDetail.tsx:739
|
||||
@@ -835,6 +918,14 @@ msgstr "Replace file"
|
||||
msgid "Reply"
|
||||
msgstr "Reply"
|
||||
|
||||
#: src/pages/UserLogin.tsx:150
|
||||
msgid "Request failed"
|
||||
msgstr "Request failed"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:94
|
||||
msgid "Reset failed"
|
||||
msgstr "Reset failed"
|
||||
|
||||
#: src/pages/Dump.tsx:211
|
||||
#: src/pages/DumpEdit.tsx:163
|
||||
msgid "Retry"
|
||||
@@ -843,15 +934,17 @@ msgstr "Retry"
|
||||
#: src/components/CommentThread.tsx:270
|
||||
#: src/pages/DumpEdit.tsx:306
|
||||
#: src/pages/PlaylistDetail.tsx:673
|
||||
#: src/pages/UserPublicProfile.tsx:816
|
||||
#: src/pages/UserPublicProfile.tsx:894
|
||||
#: src/pages/UserPublicProfile.tsx:833
|
||||
#: src/pages/UserPublicProfile.tsx:911
|
||||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:141
|
||||
#: src/components/CommentThread.tsx:269
|
||||
#: src/pages/PlaylistDetail.tsx:673
|
||||
#: src/pages/UserPublicProfile.tsx:815
|
||||
#: src/pages/UserPublicProfile.tsx:894
|
||||
#: src/pages/ResetPassword.tsx:140
|
||||
#: src/pages/UserPublicProfile.tsx:832
|
||||
#: src/pages/UserPublicProfile.tsx:911
|
||||
msgid "Saving…"
|
||||
msgstr "Saving…"
|
||||
|
||||
@@ -871,11 +964,24 @@ msgstr "Search failed"
|
||||
msgid "Searching…"
|
||||
msgstr "Searching…"
|
||||
|
||||
#: src/pages/UserLogin.tsx:175
|
||||
msgid "Send reset link"
|
||||
msgstr "Send reset link"
|
||||
|
||||
#: src/pages/UserLogin.tsx:174
|
||||
msgid "Sending…"
|
||||
msgstr "Sending…"
|
||||
|
||||
#: src/components/AppHeader.tsx:65
|
||||
msgid "Server unreachable"
|
||||
msgstr "Server unreachable"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:979
|
||||
#: src/pages/ResetPassword.tsx:91
|
||||
#: src/pages/ResetPassword.tsx:141
|
||||
msgid "Set new password"
|
||||
msgstr "Set new password"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:996
|
||||
msgid "Settings"
|
||||
msgstr "Settings"
|
||||
|
||||
@@ -883,7 +989,7 @@ msgstr "Settings"
|
||||
msgid "Something went wrong"
|
||||
msgstr "Something went wrong"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1191
|
||||
#: src/pages/UserPublicProfile.tsx:1239
|
||||
msgid "Style"
|
||||
msgstr "Style"
|
||||
|
||||
@@ -891,11 +997,11 @@ msgstr "Style"
|
||||
msgid "Submit search"
|
||||
msgstr "Submit search"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:882
|
||||
#: src/pages/UserPublicProfile.tsx:899
|
||||
msgid "Tell people about yourself…"
|
||||
msgstr "Tell people about yourself…"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:390
|
||||
#: src/components/DumpCreateModal.tsx:383
|
||||
#: src/pages/DumpEdit.tsx:266
|
||||
msgid "Tell the community what makes this worth their time..."
|
||||
msgstr "Tell the community what makes this worth their time..."
|
||||
@@ -904,10 +1010,14 @@ msgstr "Tell the community what makes this worth their time..."
|
||||
msgid "This invite link is missing, expired, or already used."
|
||||
msgstr "This invite link is missing, expired, or already used."
|
||||
|
||||
#: src/pages/UserLogin.tsx:98
|
||||
#: src/pages/UserLogin.tsx:184
|
||||
msgid "This is a mirage."
|
||||
msgstr "This is a mirage."
|
||||
|
||||
#: src/pages/ResetPassword.tsx:34
|
||||
msgid "This reset link is missing or malformed."
|
||||
msgstr "This reset link is missing or malformed."
|
||||
|
||||
#: src/components/PlaylistCreateForm.tsx:72
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
@@ -932,11 +1042,16 @@ msgstr "Unfollow {targetUsername}"
|
||||
msgid "Unfollow playlist"
|
||||
msgstr "Unfollow playlist"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:632
|
||||
#: src/components/ChangePasswordModal.tsx:43
|
||||
#: src/pages/ResetPassword.tsx:80
|
||||
msgid "Unknown error"
|
||||
msgstr "Unknown error"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:649
|
||||
msgid "Upload failed"
|
||||
msgstr "Upload failed"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:433
|
||||
#: src/components/DumpCreateModal.tsx:426
|
||||
msgid "Uploading…"
|
||||
msgstr "Uploading…"
|
||||
|
||||
@@ -946,16 +1061,16 @@ msgstr "Upvoted"
|
||||
|
||||
#. placeholder {0}: votes.items.length
|
||||
#. placeholder {1}: votes.hasMore ? "+" : ""
|
||||
#: src/pages/UserPublicProfile.tsx:998
|
||||
#: src/pages/UserPublicProfile.tsx:1015
|
||||
msgid "Upvoted ({0}{1})"
|
||||
msgstr "Upvoted ({0}{1})"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:332
|
||||
#: src/components/DumpCreateModal.tsx:325
|
||||
#: src/pages/DumpEdit.tsx:230
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:185
|
||||
#: src/components/DumpCreateModal.tsx:178
|
||||
msgid "URL is required."
|
||||
msgstr "URL is required."
|
||||
|
||||
@@ -963,7 +1078,7 @@ msgstr "URL is required."
|
||||
msgid "User menu"
|
||||
msgstr "User menu"
|
||||
|
||||
#: src/pages/UserLogin.tsx:74
|
||||
#: src/pages/UserLogin.tsx:98
|
||||
#: src/pages/UserRegister.tsx:129
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
@@ -980,19 +1095,19 @@ msgstr "Username"
|
||||
msgid "Users"
|
||||
msgstr "Users"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1062
|
||||
#: src/pages/UserPublicProfile.tsx:1100
|
||||
#: src/pages/UserPublicProfile.tsx:1137
|
||||
#: src/pages/UserPublicProfile.tsx:1313
|
||||
#: src/pages/UserPublicProfile.tsx:1445
|
||||
#: src/pages/UserPublicProfile.tsx:1085
|
||||
#: src/pages/UserPublicProfile.tsx:1128
|
||||
#: src/pages/UserPublicProfile.tsx:1170
|
||||
#: src/pages/UserPublicProfile.tsx:1361
|
||||
#: src/pages/UserPublicProfile.tsx:1493
|
||||
msgid "View all →"
|
||||
msgstr "View all →"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:447
|
||||
#: src/components/DumpCreateModal.tsx:440
|
||||
msgid "View dump →"
|
||||
msgstr "View dump →"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:383
|
||||
#: src/components/DumpCreateModal.tsx:376
|
||||
#: src/pages/DumpEdit.tsx:260
|
||||
msgid "Why are you dumping this?"
|
||||
msgstr "Why are you dumping this?"
|
||||
@@ -1020,3 +1135,11 @@ msgstr "You'll be notified when someone follows your playlists, upvotes your dum
|
||||
#: src/pages/UserUpvoted.tsx:182
|
||||
msgid "You've reached the end."
|
||||
msgstr "You've reached the end."
|
||||
|
||||
#: src/pages/UserLogin.tsx:160
|
||||
msgid "Your email address"
|
||||
msgstr "Your email address"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:49
|
||||
msgid "Your password has been changed. You can now log in."
|
||||
msgstr "Your password has been changed. You can now log in."
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -54,7 +54,7 @@ msgid "{visibleCount, plural, one {# comment} other {# comments}}"
|
||||
msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:611
|
||||
#: src/pages/UserPublicProfile.tsx:728
|
||||
#: src/pages/UserPublicProfile.tsx:745
|
||||
msgid "← Back"
|
||||
msgstr "← Retour"
|
||||
|
||||
@@ -70,7 +70,7 @@ msgstr "← Retour à toutes les recos"
|
||||
msgid "← Back to profile"
|
||||
msgstr "← Retour au profil"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:93
|
||||
#: src/pages/UserPublicProfile.tsx:100
|
||||
msgid "+ Invite someone"
|
||||
msgstr "+ Inviter quelqu'un"
|
||||
|
||||
@@ -79,7 +79,7 @@ msgid "+ New"
|
||||
msgstr "+ Nouveau"
|
||||
|
||||
#: src/pages/UserDumps.tsx:114
|
||||
#: src/pages/UserPublicProfile.tsx:1282
|
||||
#: src/pages/UserPublicProfile.tsx:1330
|
||||
msgid "+ New dump"
|
||||
msgstr "+ Nouvelle reco"
|
||||
|
||||
@@ -134,7 +134,11 @@ msgstr "un commentaire"
|
||||
msgid "a post"
|
||||
msgstr "une publication"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:931
|
||||
#: src/pages/UserPublicProfile.tsx:1215
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:948
|
||||
msgid "Add a bio…"
|
||||
msgstr "Ajouter une bio…"
|
||||
|
||||
@@ -142,12 +146,12 @@ msgstr "Ajouter une bio…"
|
||||
msgid "Add a comment…"
|
||||
msgstr "Ajouter un commentaire…"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:842
|
||||
#: src/pages/UserPublicProfile.tsx:859
|
||||
msgid "Add email…"
|
||||
msgstr "Ajouter un e-mail…"
|
||||
|
||||
#: src/components/AddToPlaylistModal.tsx:64
|
||||
#: src/components/DumpCreateModal.tsx:284
|
||||
#: src/components/DumpCreateModal.tsx:277
|
||||
msgid "Add to playlist"
|
||||
msgstr "Ajouter à la collection"
|
||||
|
||||
@@ -163,29 +167,41 @@ 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:1186
|
||||
#: src/pages/UserPublicProfile.tsx:1234
|
||||
msgid "Appearance"
|
||||
msgstr "Apparence"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1220
|
||||
#. placeholder {0}: VALIDATION.PASSWORD_MIN
|
||||
#: src/components/ChangePasswordModal.tsx:101
|
||||
#: src/pages/ResetPassword.tsx:113
|
||||
msgid "At least {0} characters"
|
||||
msgstr "Au moins {0} caractères"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1268
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:36
|
||||
#: src/pages/ResetPassword.tsx:146
|
||||
msgid "Back to login"
|
||||
msgstr "Retour à la connexion"
|
||||
|
||||
#: src/contexts/WSProvider.tsx:168
|
||||
#: src/contexts/WSProvider.tsx:360
|
||||
msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
||||
msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion."
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:132
|
||||
#: src/components/CommentThread.tsx:281
|
||||
#: src/components/CommentThread.tsx:373
|
||||
#: src/components/CommentThread.tsx:510
|
||||
#: src/components/ConfirmModal.tsx:32
|
||||
#: src/components/DumpCreateModal.tsx:422
|
||||
#: src/components/DumpCreateModal.tsx:415
|
||||
#: src/components/PlaylistCreateForm.tsx:112
|
||||
#: src/pages/DumpEdit.tsx:299
|
||||
#: src/pages/PlaylistDetail.tsx:680
|
||||
#: src/pages/UserPublicProfile.tsx:824
|
||||
#: src/pages/UserPublicProfile.tsx:902
|
||||
#: src/pages/UserPublicProfile.tsx:841
|
||||
#: src/pages/UserPublicProfile.tsx:919
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
@@ -193,30 +209,54 @@ msgstr "Annuler"
|
||||
msgid "Cancel removal"
|
||||
msgstr "Annuler la suppression"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:755
|
||||
#: src/pages/UserPublicProfile.tsx:772
|
||||
msgid "Change avatar"
|
||||
msgstr "Changer l'avatar"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:55
|
||||
#: src/components/ChangePasswordModal.tsx:142
|
||||
msgid "Change password"
|
||||
msgstr "Changer le mot de passe"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1227
|
||||
msgid "Change password…"
|
||||
msgstr "Changer le mot de passe…"
|
||||
|
||||
#: src/pages/UserRegister.tsx:95
|
||||
msgid "Checking invite…"
|
||||
msgstr "Vérification de l'invitation…"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:65
|
||||
#: src/components/Modal.tsx:45
|
||||
msgid "Close"
|
||||
msgstr "Fermer"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1212
|
||||
#: src/pages/UserPublicProfile.tsx:1260
|
||||
msgid "Color scheme"
|
||||
msgstr "Thème de couleur"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:84
|
||||
#: src/components/ChangePasswordModal.tsx:107
|
||||
#: src/pages/ResetPassword.tsx:120
|
||||
msgid "Confirm new password"
|
||||
msgstr "Confirmer le nouveau mot de passe"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:91
|
||||
msgid "Copied!"
|
||||
msgstr "Copié !"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:84
|
||||
#: src/pages/UserPublicProfile.tsx:91
|
||||
msgid "Copy"
|
||||
msgstr "Copier"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:123
|
||||
msgid "Could not change password"
|
||||
msgstr "Impossible de changer le mot de passe"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:84
|
||||
#: src/pages/UserLogin.tsx:79
|
||||
msgid "Could not connect to server"
|
||||
msgstr "Impossible de contacter le serveur"
|
||||
|
||||
#: src/components/CommentThread.tsx:111
|
||||
#: src/components/CommentThread.tsx:153
|
||||
#: src/components/CommentThread.tsx:448
|
||||
@@ -241,7 +281,11 @@ msgstr "Créées ({0}{1})"
|
||||
msgid "Creating…"
|
||||
msgstr "Création…"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1234
|
||||
#: src/components/ChangePasswordModal.tsx:75
|
||||
msgid "Current password"
|
||||
msgstr "Mot de passe actuel"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1282
|
||||
msgid "Dark"
|
||||
msgstr "Sombre"
|
||||
|
||||
@@ -281,7 +325,7 @@ msgstr "Supprimer cette collection ? Cette action est irréversible."
|
||||
msgid "Description (optional)"
|
||||
msgstr "Description (facultatif)"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:468
|
||||
#: src/components/DumpCreateModal.tsx:461
|
||||
msgid "Done"
|
||||
msgstr "Terminé"
|
||||
|
||||
@@ -293,23 +337,23 @@ msgstr "Déposez un fichier ici"
|
||||
msgid "Drop a replacement here"
|
||||
msgstr "Déposez un fichier de remplacement ici"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:434
|
||||
#: src/components/DumpCreateModal.tsx:427
|
||||
msgid "Dump it"
|
||||
msgstr "Recommander"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:445
|
||||
#: src/components/DumpCreateModal.tsx:438
|
||||
msgid "Dumped!"
|
||||
msgstr "Recommandé !"
|
||||
|
||||
#: src/pages/Search.tsx:172
|
||||
#: src/pages/UserDumps.tsx:107
|
||||
#: src/pages/UserPublicProfile.tsx:950
|
||||
#: src/pages/UserPublicProfile.tsx:967
|
||||
msgid "Dumps"
|
||||
msgstr "Recos"
|
||||
|
||||
#. placeholder {0}: dumps.items.length
|
||||
#. placeholder {1}: dumps.hasMore ? "+" : ""
|
||||
#: src/pages/UserPublicProfile.tsx:987
|
||||
#: src/pages/UserPublicProfile.tsx:1004
|
||||
msgid "Dumps ({0}{1})"
|
||||
msgstr "Recos ({0}{1})"
|
||||
|
||||
@@ -353,14 +397,18 @@ msgstr "Adresse e-mail"
|
||||
msgid "Enter a query to search."
|
||||
msgstr "Saisissez une recherche."
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:48
|
||||
msgid "Failed to change password"
|
||||
msgstr "Impossible de changer le mot de passe"
|
||||
|
||||
#: src/components/PlaylistCreateForm.tsx:62
|
||||
#: src/components/PlaylistCreateForm.tsx:103
|
||||
msgid "Failed to create playlist"
|
||||
msgstr "Impossible de créer la collection"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:65
|
||||
#: src/pages/UserPublicProfile.tsx:68
|
||||
#: src/pages/UserPublicProfile.tsx:96
|
||||
#: src/pages/UserPublicProfile.tsx:72
|
||||
#: src/pages/UserPublicProfile.tsx:75
|
||||
#: src/pages/UserPublicProfile.tsx:103
|
||||
msgid "Failed to generate invite"
|
||||
msgstr "Impossible de générer une invitation"
|
||||
|
||||
@@ -369,13 +417,13 @@ msgstr "Impossible de générer une invitation"
|
||||
#: src/pages/index/JournalFeed.tsx:48
|
||||
#: src/pages/index/NewFeed.tsx:36
|
||||
#: src/pages/Notifications.tsx:323
|
||||
#: src/pages/UserPublicProfile.tsx:1081
|
||||
#: src/pages/UserPublicProfile.tsx:1118
|
||||
#: src/pages/UserPublicProfile.tsx:1160
|
||||
#: src/pages/UserPublicProfile.tsx:1106
|
||||
#: src/pages/UserPublicProfile.tsx:1148
|
||||
#: src/pages/UserPublicProfile.tsx:1193
|
||||
msgid "Failed to load"
|
||||
msgstr "Chargement échoué"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:322
|
||||
#: src/components/DumpCreateModal.tsx:315
|
||||
msgid "Failed to post"
|
||||
msgstr "Publication échouée"
|
||||
|
||||
@@ -388,10 +436,10 @@ msgid "Failed to post reply"
|
||||
msgstr "Impossible de publier la réponse"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:789
|
||||
#: src/pages/UserPublicProfile.tsx:663
|
||||
#: src/pages/UserPublicProfile.tsx:701
|
||||
#: src/pages/UserPublicProfile.tsx:828
|
||||
#: src/pages/UserPublicProfile.tsx:905
|
||||
#: src/pages/UserPublicProfile.tsx:680
|
||||
#: src/pages/UserPublicProfile.tsx:718
|
||||
#: src/pages/UserPublicProfile.tsx:845
|
||||
#: src/pages/UserPublicProfile.tsx:922
|
||||
msgid "Failed to save"
|
||||
msgstr "Enregistrement échoué"
|
||||
|
||||
@@ -399,24 +447,24 @@ msgstr "Enregistrement échoué"
|
||||
msgid "Failed to save edit"
|
||||
msgstr "Impossible d'enregistrer la modification"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:851
|
||||
#: src/pages/UserPublicProfile.tsx:868
|
||||
msgid "Failed to update avatar"
|
||||
msgstr "Impossible de mettre à jour l'avatar"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:359
|
||||
#: src/components/DumpCreateModal.tsx:352
|
||||
msgid "Fetching preview…"
|
||||
msgstr "Récupération de l'aperçu…"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:432
|
||||
#: src/components/DumpCreateModal.tsx:425
|
||||
msgid "Fetching…"
|
||||
msgstr "Récupération…"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:315
|
||||
#: src/components/DumpCreateModal.tsx:308
|
||||
#: src/components/FileDropZone.tsx:31
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:209
|
||||
#: src/components/DumpCreateModal.tsx:202
|
||||
msgid "File too large (max 50 MB)."
|
||||
msgstr "Fichier trop volumineux (max 50 Mo)."
|
||||
|
||||
@@ -442,7 +490,7 @@ msgid "Follow some users to see their dumps here."
|
||||
msgstr "Suivez des utilisateurs pour voir leurs recos ici."
|
||||
|
||||
#: src/components/FeedTabBar.tsx:47
|
||||
#: src/pages/UserPublicProfile.tsx:964
|
||||
#: src/pages/UserPublicProfile.tsx:981
|
||||
msgid "Followed"
|
||||
msgstr "Suivi"
|
||||
|
||||
@@ -452,16 +500,20 @@ msgstr "Suivi"
|
||||
msgid "Followed ({0}{1})"
|
||||
msgstr "Suivies ({0}{1})"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1109
|
||||
#: src/pages/UserPublicProfile.tsx:1137
|
||||
msgid "Followed playlists"
|
||||
msgstr "Collections suivies"
|
||||
|
||||
#: src/components/FollowButton.tsx:37
|
||||
#: src/components/FollowButton.tsx:64
|
||||
#: src/pages/UserPublicProfile.tsx:1072
|
||||
#: src/pages/UserPublicProfile.tsx:1095
|
||||
msgid "Following"
|
||||
msgstr "Abonné"
|
||||
|
||||
#: src/pages/UserLogin.tsx:131
|
||||
msgid "Forgot password?"
|
||||
msgstr "Mot de passe oublié ?"
|
||||
|
||||
#: src/pages/index/FollowedFeed.tsx:337
|
||||
msgid "From people"
|
||||
msgstr "De personnes"
|
||||
@@ -470,20 +522,32 @@ msgstr "De personnes"
|
||||
msgid "From playlists"
|
||||
msgstr "De collections"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:56
|
||||
msgid "Go to login"
|
||||
msgstr "Aller à la connexion"
|
||||
|
||||
#: src/components/FeedTabBar.tsx:25
|
||||
msgid "Hot"
|
||||
msgstr "Tendances"
|
||||
|
||||
#: src/pages/UserLogin.tsx:140
|
||||
msgid "If that address is registered you'll receive a reset link shortly."
|
||||
msgstr "Si cette adresse est enregistrée, vous recevrez un lien de réinitialisation sous peu."
|
||||
|
||||
#: src/pages/UserRegister.tsx:106
|
||||
msgid "Invalid invite"
|
||||
msgstr "Invitation invalide"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:773
|
||||
#: src/pages/ResetPassword.tsx:33
|
||||
msgid "Invalid link"
|
||||
msgstr "Lien invalide"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:790
|
||||
msgid "invited by"
|
||||
msgstr "invité par"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:971
|
||||
#: src/pages/UserPublicProfile.tsx:1149
|
||||
#: src/pages/UserPublicProfile.tsx:988
|
||||
#: src/pages/UserPublicProfile.tsx:1182
|
||||
msgid "Invitees"
|
||||
msgstr "Invités"
|
||||
|
||||
@@ -495,7 +559,7 @@ msgstr "Journal"
|
||||
msgid "just now"
|
||||
msgstr "à l'instant"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1227
|
||||
#: src/pages/UserPublicProfile.tsx:1275
|
||||
msgid "Light"
|
||||
msgstr "Clair"
|
||||
|
||||
@@ -532,7 +596,7 @@ msgstr "Chargement…"
|
||||
msgid "Loading playlist…"
|
||||
msgstr "Chargement de la collection…"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:711
|
||||
#: src/pages/UserPublicProfile.tsx:728
|
||||
msgid "Loading profile…"
|
||||
msgstr "Chargement du profil…"
|
||||
|
||||
@@ -546,29 +610,29 @@ msgstr "Chargement du profil…"
|
||||
#: src/pages/Notifications.tsx:395
|
||||
#: src/pages/UserDumps.tsx:51
|
||||
#: src/pages/UserPlaylists.tsx:342
|
||||
#: src/pages/UserPublicProfile.tsx:1077
|
||||
#: src/pages/UserPublicProfile.tsx:1114
|
||||
#: src/pages/UserPublicProfile.tsx:1154
|
||||
#: src/pages/UserPublicProfile.tsx:1100
|
||||
#: src/pages/UserPublicProfile.tsx:1142
|
||||
#: src/pages/UserPublicProfile.tsx:1187
|
||||
#: src/pages/UserUpvoted.tsx:123
|
||||
msgid "Loading…"
|
||||
msgstr "Chargement…"
|
||||
|
||||
#: src/components/AppHeader.tsx:74
|
||||
#: src/pages/UserLogin.tsx:63
|
||||
#: src/pages/UserLogin.tsx:93
|
||||
#: src/pages/UserLogin.tsx:87
|
||||
#: src/pages/UserLogin.tsx:117
|
||||
msgid "Log in"
|
||||
msgstr "Se connecter"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:732
|
||||
#: src/pages/UserPublicProfile.tsx:865
|
||||
#: src/pages/UserPublicProfile.tsx:749
|
||||
#: src/pages/UserPublicProfile.tsx:882
|
||||
msgid "Log out"
|
||||
msgstr "Se déconnecter"
|
||||
|
||||
#: src/pages/UserLogin.tsx:92
|
||||
#: src/pages/UserLogin.tsx:116
|
||||
msgid "Logging in…"
|
||||
msgstr "Connexion…"
|
||||
|
||||
#: src/pages/UserLogin.tsx:67
|
||||
#: src/pages/UserLogin.tsx:91
|
||||
msgid "Login failed"
|
||||
msgstr "Connexion échouée"
|
||||
|
||||
@@ -584,10 +648,15 @@ msgstr "nouveau"
|
||||
msgid "New"
|
||||
msgstr "Nouveau"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:284
|
||||
#: src/components/DumpCreateModal.tsx:277
|
||||
msgid "New dump"
|
||||
msgstr "Nouvelle reco"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:88
|
||||
#: src/pages/ResetPassword.tsx:101
|
||||
msgid "New password"
|
||||
msgstr "Nouveau mot de passe"
|
||||
|
||||
#: src/components/NewPlaylistForm.tsx:34
|
||||
msgid "New playlist"
|
||||
msgstr "Nouvelle collection"
|
||||
@@ -611,11 +680,11 @@ msgid "No emoji found."
|
||||
msgstr "Aucun emoji trouvé."
|
||||
|
||||
#: src/pages/UserPlaylists.tsx:439
|
||||
#: src/pages/UserPublicProfile.tsx:1122
|
||||
#: src/pages/UserPublicProfile.tsx:1155
|
||||
msgid "No followed playlists yet."
|
||||
msgstr "Pas encore de collections suivies."
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1167
|
||||
#: src/pages/UserPublicProfile.tsx:1200
|
||||
msgid "No invitees yet."
|
||||
msgstr "Aucun invité pour le moment."
|
||||
|
||||
@@ -625,7 +694,7 @@ msgstr "Aucune collection ne correspond à « {q} »."
|
||||
|
||||
#: src/components/PlaylistMembershipPanel.tsx:34
|
||||
#: src/pages/UserPlaylists.tsx:397
|
||||
#: src/pages/UserPublicProfile.tsx:1043
|
||||
#: src/pages/UserPublicProfile.tsx:1066
|
||||
msgid "No playlists yet."
|
||||
msgstr "Pas encore de collections."
|
||||
|
||||
@@ -633,14 +702,14 @@ msgstr "Pas encore de collections."
|
||||
msgid "No users match \"{q}\"."
|
||||
msgstr "Aucun utilisateur ne correspond à « {q} »."
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1085
|
||||
#: src/pages/UserPublicProfile.tsx:1113
|
||||
msgid "Not following anyone yet."
|
||||
msgstr "Aucun abonnement pour le moment."
|
||||
|
||||
#: src/pages/Notifications.tsx:330
|
||||
#: src/pages/UserDumps.tsx:123
|
||||
#: src/pages/UserPublicProfile.tsx:1292
|
||||
#: src/pages/UserPublicProfile.tsx:1415
|
||||
#: src/pages/UserPublicProfile.tsx:1340
|
||||
#: src/pages/UserPublicProfile.tsx:1463
|
||||
#: src/pages/UserUpvoted.tsx:195
|
||||
msgid "Nothing here yet."
|
||||
msgstr "Rien ici pour l'instant."
|
||||
@@ -662,7 +731,8 @@ msgstr "Ouvrir la recherche"
|
||||
msgid "or <0>browse files</0>"
|
||||
msgstr "ou <0>parcourir les fichiers</0>"
|
||||
|
||||
#: src/pages/UserLogin.tsx:82
|
||||
#: src/pages/UserLogin.tsx:106
|
||||
#: src/pages/UserPublicProfile.tsx:1220
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
@@ -671,21 +741,34 @@ msgstr "Mot de passe"
|
||||
msgid "Password (min. {0} characters)"
|
||||
msgstr "Mot de passe (min. {0} caractères)"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:60
|
||||
msgid "Password changed successfully."
|
||||
msgstr "Mot de passe modifié avec succès."
|
||||
|
||||
#: src/pages/ResetPassword.tsx:47
|
||||
msgid "Password updated"
|
||||
msgstr "Mot de passe mis à jour"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:118
|
||||
#: src/pages/ResetPassword.tsx:129
|
||||
msgid "Passwords do not match"
|
||||
msgstr "Les mots de passe ne correspondent pas"
|
||||
|
||||
#: src/components/AppHeader.tsx:50
|
||||
#: src/components/UserMenu.tsx:62
|
||||
#: src/pages/Search.tsx:175
|
||||
#: src/pages/UserPlaylists.tsx:368
|
||||
#: src/pages/UserPublicProfile.tsx:957
|
||||
#: src/pages/UserPublicProfile.tsx:974
|
||||
msgid "Playlists"
|
||||
msgstr "Collections"
|
||||
|
||||
#. placeholder {0}: playlists.items.length
|
||||
#. placeholder {1}: playlists.hasMore ? "+" : ""
|
||||
#: src/pages/UserPublicProfile.tsx:1016
|
||||
#: src/pages/UserPublicProfile.tsx:1035
|
||||
msgid "Playlists ({0}{1})"
|
||||
msgstr "Collections ({0}{1})"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:202
|
||||
#: src/components/DumpCreateModal.tsx:195
|
||||
msgid "Please select a file."
|
||||
msgstr "Veuillez sélectionner un fichier."
|
||||
|
||||
@@ -710,7 +793,7 @@ msgstr "Publication…"
|
||||
msgid "private"
|
||||
msgstr "privé"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:411
|
||||
#: src/components/DumpCreateModal.tsx:404
|
||||
#: src/components/PlaylistCreateForm.tsx:99
|
||||
#: src/pages/DumpEdit.tsx:285
|
||||
#: src/pages/PlaylistDetail.tsx:746
|
||||
@@ -722,7 +805,7 @@ msgstr "Privé"
|
||||
msgid "public"
|
||||
msgstr "public"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:403
|
||||
#: src/components/DumpCreateModal.tsx:396
|
||||
#: src/components/PlaylistCreateForm.tsx:92
|
||||
#: src/pages/DumpEdit.tsx:278
|
||||
#: src/pages/PlaylistDetail.tsx:739
|
||||
@@ -766,6 +849,14 @@ msgstr "Remplacer le fichier"
|
||||
msgid "Reply"
|
||||
msgstr "Répondre"
|
||||
|
||||
#: src/pages/UserLogin.tsx:150
|
||||
msgid "Request failed"
|
||||
msgstr "Échec de la demande"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:94
|
||||
msgid "Reset failed"
|
||||
msgstr "Échec de la réinitialisation"
|
||||
|
||||
#: src/pages/Dump.tsx:211
|
||||
#: src/pages/DumpEdit.tsx:163
|
||||
msgid "Retry"
|
||||
@@ -774,15 +865,17 @@ msgstr "Réessayer"
|
||||
#: src/components/CommentThread.tsx:270
|
||||
#: src/pages/DumpEdit.tsx:306
|
||||
#: src/pages/PlaylistDetail.tsx:673
|
||||
#: src/pages/UserPublicProfile.tsx:816
|
||||
#: src/pages/UserPublicProfile.tsx:894
|
||||
#: src/pages/UserPublicProfile.tsx:833
|
||||
#: src/pages/UserPublicProfile.tsx:911
|
||||
msgid "Save"
|
||||
msgstr "Enregistrer"
|
||||
|
||||
#: src/components/ChangePasswordModal.tsx:141
|
||||
#: src/components/CommentThread.tsx:269
|
||||
#: src/pages/PlaylistDetail.tsx:673
|
||||
#: src/pages/UserPublicProfile.tsx:815
|
||||
#: src/pages/UserPublicProfile.tsx:894
|
||||
#: src/pages/ResetPassword.tsx:140
|
||||
#: src/pages/UserPublicProfile.tsx:832
|
||||
#: src/pages/UserPublicProfile.tsx:911
|
||||
msgid "Saving…"
|
||||
msgstr "Enregistrement…"
|
||||
|
||||
@@ -802,11 +895,24 @@ msgstr "Recherche échouée"
|
||||
msgid "Searching…"
|
||||
msgstr "Recherche…"
|
||||
|
||||
#: src/pages/UserLogin.tsx:175
|
||||
msgid "Send reset link"
|
||||
msgstr "Envoyer le lien de réinitialisation"
|
||||
|
||||
#: src/pages/UserLogin.tsx:174
|
||||
msgid "Sending…"
|
||||
msgstr "Envoi…"
|
||||
|
||||
#: src/components/AppHeader.tsx:65
|
||||
msgid "Server unreachable"
|
||||
msgstr "Serveur inaccessible"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:979
|
||||
#: src/pages/ResetPassword.tsx:91
|
||||
#: src/pages/ResetPassword.tsx:141
|
||||
msgid "Set new password"
|
||||
msgstr "Définir un nouveau mot de passe"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:996
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
@@ -814,7 +920,7 @@ msgstr "Paramètres"
|
||||
msgid "Something went wrong"
|
||||
msgstr "Une erreur est survenue"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1191
|
||||
#: src/pages/UserPublicProfile.tsx:1239
|
||||
msgid "Style"
|
||||
msgstr "Style"
|
||||
|
||||
@@ -822,11 +928,11 @@ msgstr "Style"
|
||||
msgid "Submit search"
|
||||
msgstr "Lancer la recherche"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:882
|
||||
#: src/pages/UserPublicProfile.tsx:899
|
||||
msgid "Tell people about yourself…"
|
||||
msgstr "Parlez de vous…"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:390
|
||||
#: src/components/DumpCreateModal.tsx:383
|
||||
#: src/pages/DumpEdit.tsx:266
|
||||
msgid "Tell the community what makes this worth their time..."
|
||||
msgstr "Dites à la communauté pourquoi ça vaut le coup…"
|
||||
@@ -835,10 +941,14 @@ msgstr "Dites à la communauté pourquoi ça vaut le coup…"
|
||||
msgid "This invite link is missing, expired, or already used."
|
||||
msgstr "Ce lien d'invitation est manquant, expiré ou déjà utilisé."
|
||||
|
||||
#: src/pages/UserLogin.tsx:98
|
||||
#: src/pages/UserLogin.tsx:184
|
||||
msgid "This is a mirage."
|
||||
msgstr "C'est un mirage."
|
||||
|
||||
#: src/pages/ResetPassword.tsx:34
|
||||
msgid "This reset link is missing or malformed."
|
||||
msgstr "Ce lien de réinitialisation est absent ou malformé."
|
||||
|
||||
#: src/components/PlaylistCreateForm.tsx:72
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
@@ -859,11 +969,16 @@ msgstr "Ne plus suivre {targetUsername}"
|
||||
msgid "Unfollow playlist"
|
||||
msgstr "Ne plus suivre la collection"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:632
|
||||
#: src/components/ChangePasswordModal.tsx:43
|
||||
#: src/pages/ResetPassword.tsx:80
|
||||
msgid "Unknown error"
|
||||
msgstr "Erreur inconnue"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:649
|
||||
msgid "Upload failed"
|
||||
msgstr "Envoi échoué"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:433
|
||||
#: src/components/DumpCreateModal.tsx:426
|
||||
msgid "Uploading…"
|
||||
msgstr "Envoi…"
|
||||
|
||||
@@ -873,16 +988,16 @@ msgstr "Voté"
|
||||
|
||||
#. placeholder {0}: votes.items.length
|
||||
#. placeholder {1}: votes.hasMore ? "+" : ""
|
||||
#: src/pages/UserPublicProfile.tsx:998
|
||||
#: src/pages/UserPublicProfile.tsx:1015
|
||||
msgid "Upvoted ({0}{1})"
|
||||
msgstr "Votés ({0}{1})"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:332
|
||||
#: src/components/DumpCreateModal.tsx:325
|
||||
#: src/pages/DumpEdit.tsx:230
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:185
|
||||
#: src/components/DumpCreateModal.tsx:178
|
||||
msgid "URL is required."
|
||||
msgstr "L'URL est obligatoire."
|
||||
|
||||
@@ -890,7 +1005,7 @@ msgstr "L'URL est obligatoire."
|
||||
msgid "User menu"
|
||||
msgstr "Menu utilisateur"
|
||||
|
||||
#: src/pages/UserLogin.tsx:74
|
||||
#: src/pages/UserLogin.tsx:98
|
||||
#: src/pages/UserRegister.tsx:129
|
||||
msgid "Username"
|
||||
msgstr "Nom d'utilisateur"
|
||||
@@ -899,19 +1014,19 @@ msgstr "Nom d'utilisateur"
|
||||
msgid "Users"
|
||||
msgstr "Utilisateurs"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1062
|
||||
#: src/pages/UserPublicProfile.tsx:1100
|
||||
#: src/pages/UserPublicProfile.tsx:1137
|
||||
#: src/pages/UserPublicProfile.tsx:1313
|
||||
#: src/pages/UserPublicProfile.tsx:1445
|
||||
#: src/pages/UserPublicProfile.tsx:1085
|
||||
#: src/pages/UserPublicProfile.tsx:1128
|
||||
#: src/pages/UserPublicProfile.tsx:1170
|
||||
#: src/pages/UserPublicProfile.tsx:1361
|
||||
#: src/pages/UserPublicProfile.tsx:1493
|
||||
msgid "View all →"
|
||||
msgstr "Tout voir →"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:447
|
||||
#: src/components/DumpCreateModal.tsx:440
|
||||
msgid "View dump →"
|
||||
msgstr "Voir la reco →"
|
||||
|
||||
#: src/components/DumpCreateModal.tsx:383
|
||||
#: src/components/DumpCreateModal.tsx:376
|
||||
#: src/pages/DumpEdit.tsx:260
|
||||
msgid "Why are you dumping this?"
|
||||
msgstr "Pourquoi recommandez-vous ça ?"
|
||||
@@ -939,3 +1054,11 @@ msgstr "Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vo
|
||||
#: src/pages/UserUpvoted.tsx:182
|
||||
msgid "You've reached the end."
|
||||
msgstr "Vous avez tout lu, tout vu, tout bu."
|
||||
|
||||
#: src/pages/UserLogin.tsx:160
|
||||
msgid "Your email address"
|
||||
msgstr "Votre adresse e-mail"
|
||||
|
||||
#: src/pages/ResetPassword.tsx:49
|
||||
msgid "Your password has been changed. You can now log in."
|
||||
msgstr "Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter."
|
||||
|
||||
165
src/pages/ResetPassword.tsx
Normal file
165
src/pages/ResetPassword.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { useState } from "react";
|
||||
import { Link, useNavigate, useSearchParams } from "react-router";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
|
||||
import { API_URL, VALIDATION } from "../config/api.ts";
|
||||
import { ErrorCard } from "../components/ErrorCard.tsx";
|
||||
import { PageShell } from "../components/PageShell.tsx";
|
||||
|
||||
type State =
|
||||
| { status: "idle" }
|
||||
| { status: "submitting" }
|
||||
| { status: "done" }
|
||||
| { status: "error"; error: string };
|
||||
|
||||
export function ResetPassword() {
|
||||
const [params] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const token = params.get("token") ?? "";
|
||||
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [confirm, setConfirm] = useState("");
|
||||
const [state, setState] = useState<State>({ status: "idle" });
|
||||
|
||||
const mismatch = confirm.length > 0 && newPassword !== confirm;
|
||||
const tooShort = newPassword.length > 0 &&
|
||||
newPassword.length < VALIDATION.PASSWORD_MIN;
|
||||
|
||||
if (!token) {
|
||||
return (
|
||||
<PageShell centered>
|
||||
<div className="auth-card">
|
||||
<h1 className="auth-card-title">
|
||||
<Trans>Invalid link</Trans>
|
||||
</h1>
|
||||
<p>
|
||||
<Trans>This reset link is missing or malformed.</Trans>
|
||||
</p>
|
||||
<Link
|
||||
to="/login"
|
||||
className="btn-primary"
|
||||
style={{ marginTop: "1rem", display: "inline-block" }}
|
||||
>
|
||||
<Trans>Back to login</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.status === "done") {
|
||||
return (
|
||||
<PageShell centered>
|
||||
<div className="auth-card">
|
||||
<h1 className="auth-card-title">
|
||||
<Trans>Password updated</Trans>
|
||||
</h1>
|
||||
<p style={{ marginBottom: "1rem" }}>
|
||||
<Trans>Your password has been changed. You can now log in.</Trans>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-primary"
|
||||
onClick={() => navigate("/login")}
|
||||
>
|
||||
<Trans>Go to login</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (mismatch || tooShort || !newPassword) return;
|
||||
|
||||
setState({ status: "submitting" });
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/users/reset-password`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ token, newPassword }),
|
||||
});
|
||||
const body = await res.json();
|
||||
if (body.success) {
|
||||
setState({ status: "done" });
|
||||
} else {
|
||||
setState({
|
||||
status: "error",
|
||||
error: body.error?.message ?? t`Unknown error`,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setState({ status: "error", error: t`Could not connect to server` });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageShell centered>
|
||||
<div className="auth-card">
|
||||
<h1 className="auth-card-title">
|
||||
<Trans>Set new password</Trans>
|
||||
</h1>
|
||||
|
||||
{state.status === "error" && (
|
||||
<ErrorCard title={t`Reset failed`} message={state.error} />
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="auth-form">
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="password"
|
||||
placeholder={t`New password`}
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
minLength={VALIDATION.PASSWORD_MIN}
|
||||
maxLength={VALIDATION.PASSWORD_MAX}
|
||||
required
|
||||
autoFocus
|
||||
disabled={state.status === "submitting"}
|
||||
/>
|
||||
{tooShort && (
|
||||
<span className="auth-field-hint auth-field-hint--error">
|
||||
<Trans>At least {VALIDATION.PASSWORD_MIN} characters</Trans>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="password"
|
||||
placeholder={t`Confirm new password`}
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
disabled={state.status === "submitting"}
|
||||
/>
|
||||
{mismatch && (
|
||||
<span className="auth-field-hint auth-field-hint--error">
|
||||
<Trans>Passwords do not match</Trans>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
disabled={state.status === "submitting" || mismatch || tooShort ||
|
||||
!newPassword || !confirm}
|
||||
>
|
||||
{state.status === "submitting"
|
||||
? <Trans>Saving…</Trans>
|
||||
: <Trans>Set new password</Trans>}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="auth-card-footer">
|
||||
<Link to="/login">
|
||||
<Trans>Back to login</Trans>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
@@ -16,21 +16,29 @@ import { PageShell } from "../components/PageShell.tsx";
|
||||
import { ErrorCard } from "../components/ErrorCard.tsx";
|
||||
import { friendlyFetchError } from "../utils/apiError.ts";
|
||||
|
||||
type UserLoginState =
|
||||
type LoginState =
|
||||
| { status: "idle" }
|
||||
| { status: "submitting" }
|
||||
| { status: "error"; error: string };
|
||||
|
||||
type ResetState =
|
||||
| { status: "idle" }
|
||||
| { status: "submitting" }
|
||||
| { status: "sent" }
|
||||
| { status: "error"; error: string };
|
||||
|
||||
export function UserLogin() {
|
||||
const navigate = useNavigate();
|
||||
const { login } = useAuth();
|
||||
|
||||
const [state, setState] = useState<UserLoginState>({ status: "idle" });
|
||||
const [loginState, setLoginState] = useState<LoginState>({ status: "idle" });
|
||||
const [showReset, setShowReset] = useState(false);
|
||||
const [resetEmail, setResetEmail] = useState("");
|
||||
const [resetState, setResetState] = useState<ResetState>({ status: "idle" });
|
||||
|
||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
setState({ status: "submitting" });
|
||||
setLoginState({ status: "submitting" });
|
||||
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const username = formData.get("username") as string;
|
||||
@@ -49,10 +57,26 @@ export function UserLogin() {
|
||||
login(deserializeAuthResponse(apiResponse.data));
|
||||
navigate("/");
|
||||
} else {
|
||||
setState({ status: "error", error: apiResponse.error.message });
|
||||
setLoginState({ status: "error", error: apiResponse.error.message });
|
||||
}
|
||||
} catch (err) {
|
||||
setState({ status: "error", error: friendlyFetchError(err) });
|
||||
setLoginState({ status: "error", error: friendlyFetchError(err) });
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetRequest = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!resetEmail.trim()) return;
|
||||
setResetState({ status: "submitting" });
|
||||
try {
|
||||
await fetch(`${API_URL}/api/users/request-password-reset`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: resetEmail.trim() }),
|
||||
});
|
||||
setResetState({ status: "sent" });
|
||||
} catch {
|
||||
setResetState({ status: "error", error: t`Could not connect to server` });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,8 +87,8 @@ export function UserLogin() {
|
||||
<Trans>Log in</Trans>
|
||||
</h1>
|
||||
|
||||
{state.status === "error" && (
|
||||
<ErrorCard title={t`Login failed`} message={state.error} />
|
||||
{loginState.status === "error" && (
|
||||
<ErrorCard title={t`Login failed`} message={loginState.error} />
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="auth-form">
|
||||
@@ -73,7 +97,7 @@ export function UserLogin() {
|
||||
type="text"
|
||||
placeholder={t`Username`}
|
||||
required
|
||||
disabled={state.status === "submitting"}
|
||||
disabled={loginState.status === "submitting"}
|
||||
autoFocus
|
||||
/>
|
||||
<input
|
||||
@@ -81,19 +105,81 @@ export function UserLogin() {
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
required
|
||||
disabled={state.status === "submitting"}
|
||||
disabled={loginState.status === "submitting"}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
disabled={state.status === "submitting"}
|
||||
disabled={loginState.status === "submitting"}
|
||||
>
|
||||
{state.status === "submitting"
|
||||
{loginState.status === "submitting"
|
||||
? <Trans>Logging in…</Trans>
|
||||
: <Trans>Log in</Trans>}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="auth-card-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="auth-link-btn"
|
||||
onClick={() => {
|
||||
setShowReset((v) => !v);
|
||||
setResetState({ status: "idle" });
|
||||
setResetEmail("");
|
||||
}}
|
||||
>
|
||||
<Trans>Forgot password?</Trans>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
{showReset && (
|
||||
<div className="auth-reset-panel">
|
||||
{resetState.status === "sent"
|
||||
? (
|
||||
<p className="auth-reset-sent">
|
||||
<Trans>
|
||||
If that address is registered you'll receive a reset link
|
||||
shortly.
|
||||
</Trans>
|
||||
</p>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{resetState.status === "error" && (
|
||||
<ErrorCard
|
||||
title={t`Request failed`}
|
||||
message={resetState.error}
|
||||
/>
|
||||
)}
|
||||
<form
|
||||
onSubmit={handleResetRequest}
|
||||
className="auth-form auth-reset-form"
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
placeholder={t`Your email address`}
|
||||
value={resetEmail}
|
||||
onChange={(e) => setResetEmail(e.target.value)}
|
||||
required
|
||||
autoFocus
|
||||
disabled={resetState.status === "submitting"}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
disabled={resetState.status === "submitting" ||
|
||||
!resetEmail.trim()}
|
||||
>
|
||||
{resetState.status === "submitting"
|
||||
? <Trans>Sending…</Trans>
|
||||
: <Trans>Send reset link</Trans>}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="auth-card-footer">
|
||||
<Trans>This is a mirage.</Trans>
|
||||
</p>
|
||||
|
||||
@@ -52,6 +52,7 @@ import { ErrorCard } from "../components/ErrorCard.tsx";
|
||||
import { friendlyFetchError } from "../utils/apiError.ts";
|
||||
import { TextEditor } from "../components/TextEditor.tsx";
|
||||
import { Markdown } from "../components/Markdown.tsx";
|
||||
import { ChangePasswordModal } from "../components/ChangePasswordModal.tsx";
|
||||
|
||||
function InviteButton() {
|
||||
const { authFetch } = useAuth();
|
||||
@@ -284,6 +285,7 @@ export function UserPublicProfile() {
|
||||
const [tab, setTab] = useState<
|
||||
"dumps" | "playlists" | "followed" | "invitees" | "settings"
|
||||
>("dumps");
|
||||
const [changePasswordOpen, setChangePasswordOpen] = useState(false);
|
||||
const [followedState, setFollowedState] = useState<FollowedState>(null);
|
||||
const [inviteTreeState, setInviteTreeState] = useState<InviteTreeState>(null);
|
||||
|
||||
@@ -1202,8 +1204,31 @@ export function UserPublicProfile() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{changePasswordOpen && (
|
||||
<ChangePasswordModal onClose={() => setChangePasswordOpen(false)} />
|
||||
)}
|
||||
|
||||
{tab === "settings" && isOwnProfile && (
|
||||
<>
|
||||
<section className="profile-section">
|
||||
<h2 className="profile-section-title">
|
||||
<Trans>Account</Trans>
|
||||
</h2>
|
||||
<div className="profile-appearance-grid">
|
||||
<div className="profile-appearance-row">
|
||||
<span className="profile-appearance-label">
|
||||
<Trans>Password</Trans>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn--ghost"
|
||||
onClick={() => setChangePasswordOpen(true)}
|
||||
>
|
||||
<Trans>Change password…</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="profile-section">
|
||||
<h2 className="profile-section-title">
|
||||
<Trans>Appearance</Trans>
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
export function normalizeUrl(input: string): string {
|
||||
const s = input.trim();
|
||||
if (!s || /^https?:\/\//i.test(s)) return s;
|
||||
if (s.startsWith("//")) return `https:${s}`;
|
||||
return `https://${s}`;
|
||||
}
|
||||
|
||||
export function dumpUrl(dump: { id: string; slug?: string }): string {
|
||||
return `/dumps/${dump.slug ?? dump.id}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user