v3: localization fixes, char counters & limits on all text fields, ux fixes
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { t } from "@lingui/core/macro"
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
|
||||
import { API_URL } from "../config/api.ts";
|
||||
import { API_URL, VALIDATION } from "../config/api.ts";
|
||||
import type {
|
||||
CreateUrlDumpRequest,
|
||||
Dump,
|
||||
@@ -51,16 +51,20 @@ type UrlPreview =
|
||||
| { status: "done"; richContent: RichContent | null };
|
||||
|
||||
function LocalFilePreview({ file }: { file: File }) {
|
||||
// useRef instead of useMemo+useEffect: StrictMode double-invokes effect
|
||||
// cleanups, which would revoke the blob URL before the video element can use it.
|
||||
const blobRef = useRef<{ file: File; url: string } | null>(null);
|
||||
if (blobRef.current?.file !== file) {
|
||||
if (blobRef.current) URL.revokeObjectURL(blobRef.current.url);
|
||||
blobRef.current = { file, url: URL.createObjectURL(file) };
|
||||
}
|
||||
const src = blobRef.current.url;
|
||||
const mime = file.type;
|
||||
const [src, setSrc] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const url = URL.createObjectURL(file);
|
||||
// Blob URL lifecycle requires setState in effect — no synchronous alternative.
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setSrc(url);
|
||||
return () => {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
}, [file]);
|
||||
|
||||
if (!src) return null;
|
||||
const mime = file.type;
|
||||
if (mime.startsWith("image/")) {
|
||||
return <img src={src} alt={file.name} className="local-preview-image" />;
|
||||
}
|
||||
@@ -75,9 +79,12 @@ function LocalFilePreview({ file }: { file: File }) {
|
||||
|
||||
interface DumpCreateModalProps {
|
||||
onClose: () => void;
|
||||
initialUrl?: string;
|
||||
}
|
||||
|
||||
export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
export function DumpCreateModal(
|
||||
{ onClose, initialUrl = "" }: DumpCreateModalProps,
|
||||
) {
|
||||
const { authFetch } = useAuth();
|
||||
const { injectDump } = useWS();
|
||||
|
||||
@@ -86,7 +93,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
|
||||
// Create phase state
|
||||
const [mode, setMode] = useState<Mode>("url");
|
||||
const [url, setUrl] = useState("");
|
||||
const [url, setUrl] = useState(initialUrl);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [comment, setComment] = useState("");
|
||||
const [isPrivate, setIsPrivate] = useState(false);
|
||||
@@ -166,6 +173,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
|
||||
const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (comment.length > VALIDATION.DUMP_COMMENT_MAX) return;
|
||||
setSubmitState({ status: "submitting" });
|
||||
|
||||
try {
|
||||
@@ -320,7 +328,9 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
? (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<label htmlFor="dc-url"><Trans>URL</Trans></label>
|
||||
<label htmlFor="dc-url">
|
||||
<Trans>URL</Trans>
|
||||
</label>
|
||||
<input
|
||||
id="dc-url"
|
||||
type="url"
|
||||
@@ -345,7 +355,9 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
/>
|
||||
</div>
|
||||
{urlPreview.status === "loading" && (
|
||||
<p className="preview-loading"><Trans>Fetching preview…</Trans></p>
|
||||
<p className="preview-loading">
|
||||
<Trans>Fetching preview…</Trans>
|
||||
</p>
|
||||
)}
|
||||
{urlPreview.status === "done" &&
|
||||
urlPreview.richContent && (
|
||||
@@ -377,6 +389,7 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
disabled={submitting}
|
||||
placeholder={t`Tell the community what makes this worth their time...`}
|
||||
rows={3}
|
||||
maxLength={VALIDATION.DUMP_COMMENT_MAX}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -411,7 +424,8 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
disabled={submitting}
|
||||
disabled={submitting ||
|
||||
comment.length > VALIDATION.DUMP_COMMENT_MAX}
|
||||
>
|
||||
{submitting
|
||||
? (mode === "url"
|
||||
|
||||
Reference in New Issue
Block a user