mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-08 08:50:25 -06:00
fix: preview file name and content type (#1678)
This commit is contained in:
@@ -52,10 +52,10 @@ export const handleFileUpload = async (
|
||||
|
||||
requestHeaders = {
|
||||
"X-File-Type": file.type,
|
||||
"X-File-Name": file.name,
|
||||
"X-File-Name": encodeURIComponent(file.name),
|
||||
"X-Environment-ID": environmentId ?? "",
|
||||
"X-Signature": signature,
|
||||
"X-Timestamp": timestamp,
|
||||
"X-Timestamp": String(timestamp),
|
||||
"X-UUID": uuid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ export default function SurveyEditor({
|
||||
product={product}
|
||||
environment={environment}
|
||||
previewType={localSurvey.type === "web" ? "modal" : "fullwidth"}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { Variants, motion } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
|
||||
type TPreviewType = "modal" | "fullwidth" | "email";
|
||||
|
||||
@@ -27,6 +28,7 @@ interface PreviewSurveyProps {
|
||||
previewType?: TPreviewType;
|
||||
product: TProduct;
|
||||
environment: TEnvironment;
|
||||
onFileUpload: (file: File, config?: TUploadFileConfig) => Promise<string>;
|
||||
}
|
||||
|
||||
let surveyNameTemp;
|
||||
@@ -64,6 +66,7 @@ export default function PreviewSurvey({
|
||||
previewType,
|
||||
product,
|
||||
environment,
|
||||
onFileUpload,
|
||||
}: PreviewSurveyProps) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(true);
|
||||
const [isFullScreenPreview, setIsFullScreenPreview] = useState(false);
|
||||
@@ -207,7 +210,7 @@ export default function PreviewSurvey({
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={async () => ""}
|
||||
onFileUpload={onFileUpload}
|
||||
/>
|
||||
</Modal>
|
||||
) : (
|
||||
@@ -222,7 +225,7 @@ export default function PreviewSurvey({
|
||||
activeQuestionId={activeQuestionId || undefined}
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
onFileUpload={async () => ""}
|
||||
onFileUpload={onFileUpload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,7 +282,7 @@ export default function PreviewSurvey({
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={async () => ""}
|
||||
onFileUpload={onFileUpload}
|
||||
/>
|
||||
</Modal>
|
||||
) : (
|
||||
@@ -293,7 +296,7 @@ export default function PreviewSurvey({
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={async () => ""}
|
||||
onFileUpload={onFileUpload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +77,7 @@ export default function TemplateContainerWithPreview({
|
||||
product={product}
|
||||
environment={environment}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
onFileUpload={async (file) => file.name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function POST(req: NextRequest, context: Context): Promise<NextResp
|
||||
const headersList = headers();
|
||||
|
||||
const fileType = headersList.get("X-File-Type");
|
||||
const fileName = headersList.get("X-File-Name");
|
||||
const encodedFileName = headersList.get("X-File-Name");
|
||||
const surveyId = headersList.get("X-Survey-ID");
|
||||
|
||||
const signedSignature = headersList.get("X-Signature");
|
||||
@@ -49,7 +49,7 @@ export async function POST(req: NextRequest, context: Context): Promise<NextResp
|
||||
return responses.badRequestResponse("contentType is required");
|
||||
}
|
||||
|
||||
if (!fileName) {
|
||||
if (!encodedFileName) {
|
||||
return responses.badRequestResponse("fileName is required");
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ export async function POST(req: NextRequest, context: Context): Promise<NextResp
|
||||
return responses.notFoundResponse("TeamByEnvironmentId", environmentId);
|
||||
}
|
||||
|
||||
const fileName = decodeURIComponent(encodedFileName);
|
||||
|
||||
// validate signature
|
||||
|
||||
const validated = validateLocalSignedUrl(
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
const headersList = headers();
|
||||
|
||||
const fileType = headersList.get("X-File-Type");
|
||||
const fileName = headersList.get("X-File-Name");
|
||||
const encodedFileName = headersList.get("X-File-Name");
|
||||
const environmentId = headersList.get("X-Environment-ID");
|
||||
|
||||
const signedSignature = headersList.get("X-Signature");
|
||||
@@ -28,7 +28,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
return responses.badRequestResponse("fileType is required");
|
||||
}
|
||||
|
||||
if (!fileName) {
|
||||
if (!encodedFileName) {
|
||||
return responses.badRequestResponse("fileName is required");
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
return responses.unauthorizedResponse();
|
||||
}
|
||||
|
||||
const fileName = decodeURIComponent(encodedFileName);
|
||||
|
||||
// validate signature
|
||||
|
||||
const validated = validateLocalSignedUrl(
|
||||
|
||||
@@ -15,7 +15,7 @@ const getFile = async (environmentId: string, accessType: string, fileName: stri
|
||||
return new Response(fileBuffer, {
|
||||
headers: {
|
||||
"Content-Type": metaData.contentType,
|
||||
"Content-Disposition": "inline",
|
||||
"Content-Disposition": "attachment",
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -22,7 +22,9 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
|
||||
const { environmentId, accessType, fileName } = params;
|
||||
const { environmentId, accessType, fileName: fileNameOG } = params;
|
||||
|
||||
const fileName = decodeURIComponent(fileNameOG);
|
||||
|
||||
if (accessType === "public") {
|
||||
return await getFile(environmentId, accessType, fileName);
|
||||
@@ -42,7 +44,8 @@ export async function GET(
|
||||
return responses.unauthorizedResponse();
|
||||
}
|
||||
|
||||
return await getFile(environmentId, accessType, fileName);
|
||||
const file = await getFile(environmentId, accessType, fileName);
|
||||
return file;
|
||||
}
|
||||
|
||||
export async function DELETE(_: NextRequest, { params }: { params: { fileName: string } }) {
|
||||
|
||||
@@ -51,10 +51,10 @@ export class StorageAPI {
|
||||
|
||||
requestHeaders = {
|
||||
"X-File-Type": file.type,
|
||||
"X-File-Name": file.name,
|
||||
"X-File-Name": encodeURIComponent(file.name),
|
||||
"X-Survey-ID": surveyId ?? "",
|
||||
"X-Signature": signature,
|
||||
"X-Timestamp": timestamp,
|
||||
"X-Timestamp": String(timestamp),
|
||||
"X-UUID": uuid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
export const uploadFile = async (
|
||||
file: File | Blob,
|
||||
allowedFileExtensions: string[] | undefined,
|
||||
surveyId: string | undefined,
|
||||
environmentId: string | undefined
|
||||
) => {
|
||||
if (!(file instanceof Blob) || !(file instanceof File)) {
|
||||
throw new Error(`Invalid file type. Expected Blob or File, but received ${typeof file}`);
|
||||
}
|
||||
|
||||
const payload = {
|
||||
fileName: file.name,
|
||||
fileType: file.type,
|
||||
allowedFileExtensions: allowedFileExtensions,
|
||||
surveyId: surveyId,
|
||||
};
|
||||
|
||||
const response = await fetch(`/api/v1/client/${environmentId}/storage`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed with status: ${response.status}`);
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
const { data } = json;
|
||||
const { signedUrl, fileUrl, signingData, presignedFields } = data;
|
||||
|
||||
let requestHeaders: Record<string, string> = {};
|
||||
|
||||
if (signingData) {
|
||||
const { signature, timestamp, uuid } = signingData;
|
||||
|
||||
requestHeaders = {
|
||||
fileType: file.type,
|
||||
fileName: file.name,
|
||||
surveyId: surveyId ?? "",
|
||||
signature,
|
||||
timestamp,
|
||||
uuid,
|
||||
};
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
if (presignedFields) {
|
||||
Object.keys(presignedFields).forEach((key) => {
|
||||
formData.append(key, presignedFields[key]);
|
||||
});
|
||||
}
|
||||
|
||||
// Add the actual file to be uploaded
|
||||
formData.append("file", file);
|
||||
|
||||
const uploadResponse = await fetch(signedUrl, {
|
||||
method: "POST",
|
||||
...(signingData ? { headers: requestHeaders } : {}),
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
const uploadJson = await uploadResponse.json();
|
||||
console.log(uploadJson);
|
||||
throw new Error(`${uploadJson.message}`);
|
||||
}
|
||||
|
||||
return {
|
||||
uploaded: true,
|
||||
url: fileUrl,
|
||||
};
|
||||
};
|
||||
@@ -54,12 +54,12 @@ const uploadFile = async (
|
||||
const { signature, timestamp, uuid } = signingData;
|
||||
|
||||
requestHeaders = {
|
||||
fileType: file.type,
|
||||
fileName: file.name,
|
||||
environmentId: environmentId ?? "",
|
||||
signature,
|
||||
timestamp,
|
||||
uuid,
|
||||
"X-File-Type": file.type,
|
||||
"X-File-Name": encodeURIComponent(file.name),
|
||||
"X-Environment-ID": environmentId ?? "",
|
||||
"X-Signature": signature,
|
||||
"X-Timestamp": String(timestamp),
|
||||
"X-UUID": uuid,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,15 @@ export const PictureSelectionResponse = ({ choices, selected }: PictureSelection
|
||||
<div className="my-1 flex flex-wrap gap-x-5 gap-y-4">
|
||||
{selected.map((id) => (
|
||||
<div className="relative h-32 w-56">
|
||||
<Image
|
||||
src={choiceImageMapping[id]}
|
||||
alt={choiceImageMapping[id].split("/").pop() || "Image"}
|
||||
fill
|
||||
style={{ objectFit: "cover" }}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
{choiceImageMapping[id] && (
|
||||
<Image
|
||||
src={choiceImageMapping[id]}
|
||||
alt={choiceImageMapping[id].split("/").pop() || "Image"}
|
||||
fill
|
||||
style={{ objectFit: "cover" }}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user