diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/lib.ts b/apps/web/app/(app)/environments/[environmentId]/settings/profile/lib.ts index 1d53e6643e..3cf00bad19 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/lib.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/profile/lib.ts @@ -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, }; } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyEditor.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyEditor.tsx index 9b0bc85a2e..1aa9beb1bb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyEditor.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyEditor.tsx @@ -110,6 +110,7 @@ export default function SurveyEditor({ product={product} environment={environment} previewType={localSurvey.type === "web" ? "modal" : "fullwidth"} + onFileUpload={async (file) => file.name} /> diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx index d968eebe6a..fcb5b3c5ea 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx @@ -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; } 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} /> ) : ( @@ -222,7 +225,7 @@ export default function PreviewSurvey({ activeQuestionId={activeQuestionId || undefined} isBrandingEnabled={product.linkSurveyBranding} onActiveQuestionChange={setActiveQuestionId} - onFileUpload={async () => ""} + onFileUpload={onFileUpload} /> @@ -279,7 +282,7 @@ export default function PreviewSurvey({ isBrandingEnabled={product.linkSurveyBranding} onActiveQuestionChange={setActiveQuestionId} isRedirectDisabled={true} - onFileUpload={async () => ""} + onFileUpload={onFileUpload} /> ) : ( @@ -293,7 +296,7 @@ export default function PreviewSurvey({ isBrandingEnabled={product.linkSurveyBranding} onActiveQuestionChange={setActiveQuestionId} isRedirectDisabled={true} - onFileUpload={async () => ""} + onFileUpload={onFileUpload} /> diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx index 57990b5754..30b9bcd15c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/TemplateContainer.tsx @@ -77,6 +77,7 @@ export default function TemplateContainerWithPreview({ product={product} environment={environment} setActiveQuestionId={setActiveQuestionId} + onFileUpload={async (file) => file.name} /> )} diff --git a/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts b/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts index 0bc10e01fe..10afbb4053 100644 --- a/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts +++ b/apps/web/app/api/v1/client/[environmentId]/storage/local/route.ts @@ -38,7 +38,7 @@ export async function POST(req: NextRequest, context: Context): Promise { 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 { 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 { return responses.unauthorizedResponse(); } + const fileName = decodeURIComponent(encodedFileName); + // validate signature const validated = validateLocalSignedUrl( diff --git a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/getFile.ts b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/getFile.ts index aa2bb82f78..73e5c378d9 100644 --- a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/getFile.ts +++ b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/getFile.ts @@ -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) { diff --git a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts index a99d791838..47d14b8c53 100644 --- a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts +++ b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts @@ -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 } }) { diff --git a/packages/api/src/api/client/storage.ts b/packages/api/src/api/client/storage.ts index 9987acf2a1..1bc9a75276 100644 --- a/packages/api/src/api/client/storage.ts +++ b/packages/api/src/api/client/storage.ts @@ -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, }; } diff --git a/packages/surveys/src/lib/uploadFile.ts b/packages/surveys/src/lib/uploadFile.ts deleted file mode 100644 index 10a1091ee7..0000000000 --- a/packages/surveys/src/lib/uploadFile.ts +++ /dev/null @@ -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 = {}; - - 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, - }; -}; diff --git a/packages/ui/FileInput/lib/fileUpload.ts b/packages/ui/FileInput/lib/fileUpload.ts index f88965526a..f85b8ddfc8 100644 --- a/packages/ui/FileInput/lib/fileUpload.ts +++ b/packages/ui/FileInput/lib/fileUpload.ts @@ -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, }; } diff --git a/packages/ui/PictureSelectionResponse/index.tsx b/packages/ui/PictureSelectionResponse/index.tsx index 4152e25b79..d84c3c26d5 100644 --- a/packages/ui/PictureSelectionResponse/index.tsx +++ b/packages/ui/PictureSelectionResponse/index.tsx @@ -18,13 +18,15 @@ export const PictureSelectionResponse = ({ choices, selected }: PictureSelection
{selected.map((id) => (
- {choiceImageMapping[id].split("/").pop() + {choiceImageMapping[id] && ( + {choiceImageMapping[id].split("/").pop() + )}
))}