|
|
@ -4,7 +4,7 @@ import Image from "next/image"; |
|
|
import { type ReactNode, useCallback, useEffect, useId, useRef, useState } from "react"; |
|
|
import { type ReactNode, useCallback, useEffect, useId, useRef, useState } from "react"; |
|
|
import type { QuestionField } from "@/lib/schema-adapter"; |
|
|
import type { QuestionField } from "@/lib/schema-adapter"; |
|
|
import { useUploadTmpMediaMutation } from "@/hooks/marriage/use-upload-tmp-media"; |
|
|
import { useUploadTmpMediaMutation } from "@/hooks/marriage/use-upload-tmp-media"; |
|
|
import { getApiRequestUrl } from "@/lib/http"; |
|
|
|
|
|
|
|
|
import { resolveMediaUrl } from "@/lib/http"; |
|
|
import { isInFlutterWebView, uploadFile } from "@/lib/webview-actions"; |
|
|
import { isInFlutterWebView, uploadFile } from "@/lib/webview-actions"; |
|
|
import { useI18n } from "@/translations/provider"; |
|
|
import { useI18n } from "@/translations/provider"; |
|
|
import ErrorToast from "./error-toast"; |
|
|
import ErrorToast from "./error-toast"; |
|
|
@ -99,19 +99,16 @@ export function QuestionPhoto({ |
|
|
URL.revokeObjectURL(activeBlobUrlRef.current); |
|
|
URL.revokeObjectURL(activeBlobUrlRef.current); |
|
|
activeBlobUrlRef.current = null; |
|
|
activeBlobUrlRef.current = null; |
|
|
} |
|
|
} |
|
|
setLocalPreviewUrl(null); |
|
|
|
|
|
// Do NOT clear the stored answer — the file was uploaded successfully,
|
|
|
|
|
|
// only the preview failed to load (proxy timing, network glitch, etc.).
|
|
|
|
|
|
// This matches yesterday's behavior where errors never wiped the answer.
|
|
|
|
|
|
}, []); |
|
|
}, []); |
|
|
|
|
|
|
|
|
const uploadTmpMediaMutation = useUploadTmpMediaMutation({ |
|
|
const uploadTmpMediaMutation = useUploadTmpMediaMutation({ |
|
|
onSuccess: (response) => { |
|
|
onSuccess: (response) => { |
|
|
if (response?.path) { |
|
|
if (response?.path) { |
|
|
setAnswerValue(question, response.path); |
|
|
|
|
|
|
|
|
const resolved = resolveMediaUrl(response.path) ?? response.path; |
|
|
|
|
|
setAnswerValue(question, resolved); |
|
|
setHasImageError(false); |
|
|
setHasImageError(false); |
|
|
if (!activeBlobUrlRef.current) { |
|
|
if (!activeBlobUrlRef.current) { |
|
|
setLocalPreviewUrl(response.path); |
|
|
|
|
|
|
|
|
setLocalPreviewUrl(resolved); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
handleUploadFailure(); |
|
|
handleUploadFailure(); |
|
|
@ -144,22 +141,26 @@ export function QuestionPhoto({ |
|
|
setIsUploading(true); |
|
|
setIsUploading(true); |
|
|
setHasImageError(false); |
|
|
setHasImageError(false); |
|
|
setToastMessage(null); |
|
|
setToastMessage(null); |
|
|
|
|
|
if (event.data?.files?.[0]?.base64) { |
|
|
|
|
|
setLocalPreviewUrl(event.data.files[0].base64); |
|
|
|
|
|
} |
|
|
break; |
|
|
break; |
|
|
case "completed": { |
|
|
case "completed": { |
|
|
const file = event.data?.files?.[0]; |
|
|
const file = event.data?.files?.[0]; |
|
|
const remoteUrl = |
|
|
|
|
|
file?.path || |
|
|
|
|
|
|
|
|
const rawUrl = |
|
|
file?.url || |
|
|
file?.url || |
|
|
(file?.data?.path as string | undefined) || |
|
|
|
|
|
(file?.data?.url as string | undefined); |
|
|
|
|
|
|
|
|
file?.path || |
|
|
|
|
|
(file?.data?.url as string | undefined) || |
|
|
|
|
|
(file?.data?.path as string | undefined); |
|
|
|
|
|
|
|
|
if (remoteUrl) { |
|
|
|
|
|
|
|
|
if (rawUrl) { |
|
|
|
|
|
const resolved = resolveMediaUrl(rawUrl) ?? rawUrl; |
|
|
setIsUploading(false); |
|
|
setIsUploading(false); |
|
|
isInitiatorRef.current = false; |
|
|
isInitiatorRef.current = false; |
|
|
setAnswerValue(question, remoteUrl); |
|
|
|
|
|
setLocalPreviewUrl((prev) => prev || remoteUrl); |
|
|
|
|
|
|
|
|
setAnswerValue(question, resolved); |
|
|
|
|
|
setLocalPreviewUrl(resolved); |
|
|
setHasImageError(false); |
|
|
setHasImageError(false); |
|
|
pendingDeferredRef.current?.resolve(remoteUrl); |
|
|
|
|
|
|
|
|
pendingDeferredRef.current?.resolve(resolved); |
|
|
pendingDeferredRef.current = null; |
|
|
pendingDeferredRef.current = null; |
|
|
} else if (file?.base64) { |
|
|
} else if (file?.base64) { |
|
|
setIsUploading(true); |
|
|
setIsUploading(true); |
|
|
@ -178,12 +179,13 @@ export function QuestionPhoto({ |
|
|
.mutateAsync(f) |
|
|
.mutateAsync(f) |
|
|
.then((res) => { |
|
|
.then((res) => { |
|
|
if (res?.path) { |
|
|
if (res?.path) { |
|
|
setAnswerValue(question, res.path); |
|
|
|
|
|
|
|
|
const resolved = resolveMediaUrl(res.path) ?? res.path; |
|
|
|
|
|
setAnswerValue(question, resolved); |
|
|
setHasImageError(false); |
|
|
setHasImageError(false); |
|
|
if (!activeBlobUrlRef.current) { |
|
|
if (!activeBlobUrlRef.current) { |
|
|
setLocalPreviewUrl(res.path); |
|
|
|
|
|
|
|
|
setLocalPreviewUrl(resolved); |
|
|
} |
|
|
} |
|
|
pendingDeferredRef.current?.resolve(res.path); |
|
|
|
|
|
|
|
|
pendingDeferredRef.current?.resolve(resolved); |
|
|
} else { |
|
|
} else { |
|
|
handleUploadFailure(); |
|
|
handleUploadFailure(); |
|
|
pendingDeferredRef.current?.resolve(); |
|
|
pendingDeferredRef.current?.resolve(); |
|
|
@ -331,10 +333,11 @@ export function QuestionPhoto({ |
|
|
.mutateAsync(file) |
|
|
.mutateAsync(file) |
|
|
.then((res) => { |
|
|
.then((res) => { |
|
|
if (res?.path) { |
|
|
if (res?.path) { |
|
|
setAnswerValue(question, res.path); |
|
|
|
|
|
|
|
|
const resolved = resolveMediaUrl(res.path) ?? res.path; |
|
|
|
|
|
setAnswerValue(question, resolved); |
|
|
setHasImageError(false); |
|
|
setHasImageError(false); |
|
|
if (!activeBlobUrlRef.current) { |
|
|
if (!activeBlobUrlRef.current) { |
|
|
setLocalPreviewUrl(res.path); |
|
|
|
|
|
|
|
|
setLocalPreviewUrl(resolved); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
handleUploadFailure(); |
|
|
handleUploadFailure(); |
|
|
@ -368,22 +371,7 @@ export function QuestionPhoto({ |
|
|
candidateUrl = storedValue.trim(); |
|
|
candidateUrl = storedValue.trim(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Display URL: absolute URLs (https://...) used directly; relative paths proxied via getApiRequestUrl.
|
|
|
|
|
|
// This matches yesterday's working logic — do NOT use resolveMediaUrl which forcefully proxies
|
|
|
|
|
|
// public /static/ URLs, breaking display on mobile where the proxy returns 502.
|
|
|
|
|
|
let displayUrl: string | null = null; |
|
|
|
|
|
if (candidateUrl) { |
|
|
|
|
|
if ( |
|
|
|
|
|
candidateUrl.startsWith("http://") || |
|
|
|
|
|
candidateUrl.startsWith("https://") || |
|
|
|
|
|
candidateUrl.startsWith("blob:") || |
|
|
|
|
|
candidateUrl.startsWith("data:") |
|
|
|
|
|
) { |
|
|
|
|
|
displayUrl = candidateUrl; |
|
|
|
|
|
} else { |
|
|
|
|
|
displayUrl = getApiRequestUrl(candidateUrl); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const displayUrl = resolveMediaUrl(candidateUrl); |
|
|
const hasAnswer = Boolean(displayUrl); |
|
|
const hasAnswer = Boolean(displayUrl); |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
|