Browse Source

Merge branch 'staging' of https://git.nwhco.ir/sina_sajjadi/marriage into staging

staging
Ali Alavi 4 days ago
parent
commit
83291d7229
  1. 31
      src/components/Componentes/question-file.tsx
  2. 119
      src/components/Componentes/question-photo.tsx

31
src/components/Componentes/question-file.tsx

@ -164,6 +164,20 @@ function isImageFile(
return /\.(jpg|jpeg|png|webp|gif|svg|bmp|avif)$/i.test(nameToCheck);
}
function base64ToFile(dataUrl: string, filename = "document"): File {
const parts = dataUrl.split(",");
const mimeMatch = parts[0]?.match(/:(.*?);/);
const mime = mimeMatch ? mimeMatch[1] : "application/octet-stream";
const base64Data = parts[1] || parts[0];
const binaryString = atob(base64Data);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new File([bytes], filename, { type: mime });
}
export type UploadedDoc = {
url: string;
name: string;
@ -335,15 +349,12 @@ export function QuestionFile({
previewUrl: resolved,
});
} else if (f?.base64) {
const b64 = f.base64;
fetch(b64)
.then((res) => res.blob())
.then((blob) => {
const fileObj = new File([blob], f.name ?? "document", {
type: blob.type,
});
uploadTmpMediaMutation.mutate(fileObj);
});
try {
const fileObj = base64ToFile(f.base64, f.name ?? "document");
uploadTmpMediaMutation.mutate(fileObj);
} catch (e) {
console.error("Failed to decode base64 file:", e);
}
}
}
@ -618,7 +629,7 @@ export function QuestionFile({
{isImg && doc.previewUrl && !hasThumbError ? (
<img
src={doc.previewUrl}
alt=""
alt={doc.name}
onError={() => {
setImgErrorSet((prev) => new Set(prev).add(index));
}}

119
src/components/Componentes/question-photo.tsx

@ -17,6 +17,20 @@ type QuestionPhotoProps = {
disabled?: boolean;
};
function base64ToFile(dataUrl: string, filename = "photo.jpg"): File {
const parts = dataUrl.split(",");
const mimeMatch = parts[0]?.match(/:(.*?);/);
const mime = mimeMatch ? mimeMatch[1] : "image/jpeg";
const base64Data = parts[1] || parts[0];
const binaryString = atob(base64Data);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new File([bytes], filename, { type: mime });
}
export function QuestionPhoto({
question,
description,
@ -37,7 +51,6 @@ export function QuestionPhoto({
? question.extras.options.join(",")
: "image/*";
const { getAnswerValue, setAnswerValue, registerPendingUpload } =
useQuestionAnswers();
const storedValue = getAnswerValue(question);
@ -61,6 +74,7 @@ export function QuestionPhoto({
const handleUploadFailure = useCallback(
(errorMessage?: string) => {
setIsUploading(false);
isInitiatorRef.current = false;
if (activeBlobUrlRef.current) {
URL.revokeObjectURL(activeBlobUrlRef.current);
activeBlobUrlRef.current = null;
@ -92,15 +106,11 @@ export function QuestionPhoto({
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
// Only show the toast if the user was actively uploading;
// stale stored URLs from previous sessions fail silently.
if (isUploading || isInitiatorRef.current) {
setToastMessage(
t["Failed to load image. Please try uploading again."] ??
"Failed to load image. Please try uploading again.",
);
}
}, [question, setAnswerValue, t, isUploading]);
setToastMessage(
t["Failed to load image. Please try uploading again."] ??
"Failed to load image. Please try uploading again.",
);
}, [question, setAnswerValue, t]);
const uploadTmpMediaMutation = useUploadTmpMediaMutation({
onSuccess: (response) => {
@ -114,6 +124,7 @@ export function QuestionPhoto({
handleUploadFailure();
}
setIsUploading(false);
isInitiatorRef.current = false;
},
onError: (error) => {
console.error("Photo upload error:", error);
@ -140,57 +151,70 @@ export function QuestionPhoto({
setIsUploading(true);
setHasImageError(false);
setToastMessage(null);
if (event.data?.files?.[0]?.base64) {
const b64 = event.data.files[0].base64;
setLocalPreviewUrl(b64);
}
break;
case "completed": {
setIsUploading(false);
isInitiatorRef.current = false;
const file = event.data?.files?.[0];
const remoteUrl =
file?.path ||
file?.url ||
(file?.data?.path as string | undefined) ||
(file?.data?.url as string | undefined);
if (remoteUrl) {
setIsUploading(false);
isInitiatorRef.current = false;
setAnswerValue(question, remoteUrl);
setLocalPreviewUrl((prev) => prev || remoteUrl);
setHasImageError(false);
pendingDeferredRef.current?.resolve(remoteUrl);
pendingDeferredRef.current = null;
} else if (file?.base64) {
const b64 = file.base64;
setLocalPreviewUrl(b64);
const uploadPromise = fetch(b64)
.then((res) => res.blob())
.then((blob) => {
const f = new File([blob], file.name ?? "photo.jpg", {
type: blob.type || "image/jpeg",
});
return uploadTmpMediaMutation.mutateAsync(f);
})
.then((res) => {
if (res?.path) {
setAnswerValue(question, res.path);
setLocalPreviewUrl((prev) => prev || res.path);
setHasImageError(false);
pendingDeferredRef.current?.resolve(res.path);
} else {
setIsUploading(true);
setHasImageError(false);
setToastMessage(null);
try {
const f = base64ToFile(file.base64, file.name ?? "photo.jpg");
if (activeBlobUrlRef.current) {
URL.revokeObjectURL(activeBlobUrlRef.current);
}
const objectUrl = URL.createObjectURL(f);
activeBlobUrlRef.current = objectUrl;
setLocalPreviewUrl(objectUrl);
const uploadPromise = uploadTmpMediaMutation
.mutateAsync(f)
.then((res) => {
if (res?.path) {
setAnswerValue(question, res.path);
setHasImageError(false);
if (!activeBlobUrlRef.current) {
setLocalPreviewUrl(res.path);
}
pendingDeferredRef.current?.resolve(res.path);
} else {
handleUploadFailure();
pendingDeferredRef.current?.resolve();
}
setIsUploading(false);
isInitiatorRef.current = false;
pendingDeferredRef.current = null;
})
.catch((err) => {
console.error("Flutter photo upload to server error:", err);
handleUploadFailure();
pendingDeferredRef.current?.resolve();
}
pendingDeferredRef.current = null;
})
.catch((err) => {
console.error("Flutter photo upload error:", err);
handleUploadFailure();
pendingDeferredRef.current?.reject(err);
pendingDeferredRef.current = null;
});
registerPendingUpload?.(question.id, uploadPromise);
pendingDeferredRef.current?.reject(err);
pendingDeferredRef.current = null;
});
registerPendingUpload?.(question.id, uploadPromise);
} catch (convErr) {
console.error("Failed to decode base64 photo:", convErr);
handleUploadFailure();
pendingDeferredRef.current?.reject(convErr);
pendingDeferredRef.current = null;
}
} else {
setIsUploading(false);
isInitiatorRef.current = false;
pendingDeferredRef.current?.resolve();
pendingDeferredRef.current = null;
}
@ -208,7 +232,7 @@ export function QuestionPhoto({
case "failed":
setIsUploading(false);
isInitiatorRef.current = false;
handleUploadFailure();
handleUploadFailure(event.message || event.data?.message);
pendingDeferredRef.current?.reject(new Error("Upload failed"));
pendingDeferredRef.current = null;
break;
@ -265,6 +289,9 @@ export function QuestionPhoto({
allowedExtensions:
extensions.length > 0 ? extensions : ["jpg", "jpeg", "png", "webp"],
maxBytes: 10_485_760, // 10 MB
maxWidth: 1024,
maxHeight: 1024,
imageQuality: 85,
title:
typeof question.title === "string" ? question.title : "Upload Photo",
});
@ -374,7 +401,7 @@ export function QuestionPhoto({
className="sr-only"
/>
<label
htmlFor={inputId}
htmlFor={isInFlutterWebView() ? undefined : inputId}
onClick={handleLabelClick}
className="flex w-full cursor-pointer flex-col items-center"
>

Loading…
Cancel
Save