diff --git a/src/app/questions-list/questions-list-client.tsx b/src/app/questions-list/questions-list-client.tsx index 9a5f6a3..2cec762 100644 --- a/src/app/questions-list/questions-list-client.tsx +++ b/src/app/questions-list/questions-list-client.tsx @@ -147,7 +147,7 @@ export default function QuestionsListClient() { if (typeof window !== "undefined") { const url = new URL(window.location.href); url.searchParams.set("section", slug); - window.history.pushState({ section: slug }, "", url.toString()); + window.history.replaceState({ section: slug }, "", url.toString()); } }, []); diff --git a/src/components/Componentes/question-file.tsx b/src/components/Componentes/question-file.tsx index b40521c..63c0015 100644 --- a/src/components/Componentes/question-file.tsx +++ b/src/components/Componentes/question-file.tsx @@ -234,9 +234,17 @@ export function QuestionFile({ parseInitialDocs(storedValue), ); const [isUploading, setIsUploading] = useState(false); + const [uploadProgress, setUploadProgress] = useState(null); const isInitiatorRef = useRef(false); const fileInputRef = useRef(null); + // Sync state when answers load asynchronously from server/cache + useEffect(() => { + if (storedValue !== undefined && storedValue !== null) { + setUploadedDocs(parseInitialDocs(storedValue)); + } + }, [storedValue, parseInitialDocs]); + const acceptedFiles = (question.extras?.options ?? []) .map((option) => option.replace(/^\./, "")) .join(", "); @@ -262,10 +270,12 @@ export function QuestionFile({ }); } setIsUploading(false); + setUploadProgress(null); }, onError: (error) => { console.error("File upload error:", error); setIsUploading(false); + setUploadProgress(null); }, }); @@ -284,11 +294,20 @@ export function QuestionFile({ case "picking": break; case "picked": - case "progress": setIsUploading(true); + setUploadProgress(0); + break; + case "progress": { + setIsUploading(true); + const p = (event.data as any)?.progress; + if (typeof p === "number") { + setUploadProgress(Math.round(p)); + } break; + } case "completed": { setIsUploading(false); + setUploadProgress(null); isInitiatorRef.current = false; const incomingFiles = event.data?.files || []; const newDocs: UploadedDoc[] = []; @@ -341,6 +360,7 @@ export function QuestionFile({ case "cancelled": case "failed": setIsUploading(false); + setUploadProgress(null); isInitiatorRef.current = false; break; } @@ -380,12 +400,13 @@ export function QuestionFile({ if (!files || files.length === 0) return; setIsUploading(true); + setUploadProgress(0); const newDocs: UploadedDoc[] = []; for (let i = 0; i < files.length; i++) { const file = files[i]; try { - const res = await uploadTmpMedia(file); + const res = await uploadTmpMedia(file, (p) => setUploadProgress(p)); if (res.path) { const resolved = res.path.startsWith("http") ? res.path @@ -402,6 +423,7 @@ export function QuestionFile({ } setIsUploading(false); + setUploadProgress(null); if (newDocs.length > 0) { setUploadedDocs((prev) => { @@ -484,10 +506,10 @@ export function QuestionFile({ : undefined } className={[ - "relative flex w-full flex-col items-center justify-center rounded-[29px] border-2 border-dashed border-[#8D8D8D] bg-[#F7F7F7] text-center transition-all duration-200 focus-within:outline-2 focus-within:outline-offset-4 focus-within:outline-[#6F6F6F] hover:border-[#777777] active:border-[#111111] overflow-hidden p-4", + "relative flex w-full flex-col items-center justify-center rounded-[29px] border-2 border-dashed border-[#8D8D8D] bg-[#F7F7F7] text-center transition-colors duration-200 hover:border-[#6F6F6F] overflow-hidden select-none", hasDocuments - ? "min-h-[156px]" - : "aspect-[727/330] min-h-[156px] cursor-pointer", + ? "min-h-[156px] p-3.5" + : "aspect-[727/330] min-h-[156px] cursor-pointer p-4", ].join(" ")} > {hasDocuments ? ( @@ -608,12 +630,21 @@ export function QuestionFile({ )} - {/* Smooth Circular Loading Spinner Overlay (Matching Flutter ProfileAvatar) */} + {/* Smooth Circular Loading Spinner Overlay with Upload Progress */} {isPending && ( -
-
+
+
+
+ {uploadProgress !== null && uploadProgress > 0 && ( + + {uploadProgress}% + + )} +
- {t.uploading} + {uploadProgress !== null && uploadProgress > 0 + ? `${t.uploading} (${uploadProgress}%)` + : t.uploading}
)} diff --git a/src/hooks/marriage/use-upload-tmp-media.ts b/src/hooks/marriage/use-upload-tmp-media.ts index 78294ca..50ad895 100644 --- a/src/hooks/marriage/use-upload-tmp-media.ts +++ b/src/hooks/marriage/use-upload-tmp-media.ts @@ -8,7 +8,10 @@ import type { UploadTmpMediaResponse } from "./types"; const CSRF_TOKEN = "53kqNKySTv3q4K3OolQqLEgaeF9pdPdAEnxrMARaUfvFrIGK57Qje67ifYUDMUQP"; -export async function uploadTmpMedia(file: File) { +export async function uploadTmpMedia( + file: File, + onProgress?: (progressPercent: number) => void, +) { const formData = new FormData(); formData.append("file", file); @@ -20,6 +23,14 @@ export async function uploadTmpMedia(file: File) { Accept: "application/json", "X-CSRFToken": CSRF_TOKEN, }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total && onProgress) { + const percent = Math.round( + (progressEvent.loaded * 100) / progressEvent.total, + ); + onProgress(percent); + } + }, }, ); @@ -31,6 +42,6 @@ export function useUploadTmpMediaMutation( ) { return useMutation({ ...options, - mutationFn: uploadTmpMedia, + mutationFn: (file: File) => uploadTmpMedia(file), }); }