mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-11 17:19:33 -06:00
fix: File upload issues and tweaks (#2040)
Co-authored-by: Johannes <johannes@formbricks.com>
This commit is contained in:
committed by
GitHub
parent
831bf148d8
commit
0bccee23e9
@@ -110,11 +110,12 @@ export const withEmailTemplate = (content: string) =>
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00c4b8;
|
||||
color: #000000;
|
||||
}
|
||||
a:hover {
|
||||
color: #00e6ca;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TSurveyQuestion } from "@formbricks/types/surveys";
|
||||
import { TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys";
|
||||
|
||||
import {
|
||||
DEBUG,
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "../constants";
|
||||
import { createInviteToken, createToken, createTokenForLinkSurvey } from "../jwt";
|
||||
import { getQuestionResponseMapping } from "../responses";
|
||||
import { getOriginalFileNameFromUrl } from "../storage/utils";
|
||||
import { getTeamByEnvironmentId } from "../team/service";
|
||||
import { withEmailTemplate } from "./email-template";
|
||||
|
||||
@@ -176,7 +177,7 @@ export const sendResponseFinishedEmail = async (
|
||||
html: withEmailTemplate(`
|
||||
<h1>Hey 👋</h1>
|
||||
<p>Congrats, you received a new response to your survey!
|
||||
Someone just completed your survey <strong>${survey.name}</strong><br/></p>
|
||||
Someone just completed your survey <strong>${survey.name}:</strong><br/></p>
|
||||
|
||||
<hr/>
|
||||
|
||||
@@ -186,7 +187,29 @@ export const sendResponseFinishedEmail = async (
|
||||
question.answer &&
|
||||
`<div style="margin-top:1em;">
|
||||
<p style="margin:0px;">${question.question}</p>
|
||||
<p style="font-weight: 500; margin:0px; white-space:pre-wrap">${question.answer}</p>
|
||||
${
|
||||
question.type === TSurveyQuestionType.FileUpload
|
||||
? typeof question.answer !== "string" &&
|
||||
question.answer
|
||||
.map((answer) => {
|
||||
return `
|
||||
<div style="position: relative; display: flex; width: 15rem; flex-direction: column; align-items: center; justify-content: center; border-radius: 0.5rem; background-color: #e2e8f0; color: black; margin-top:8px;">
|
||||
<div style="margin-top: 1rem; color: black;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" className="lucide lucide-file">
|
||||
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
|
||||
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
||||
</svg>
|
||||
</div>
|
||||
<p style="margin-top: 0.5rem; width: 80%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 1rem; font-size: 0.875rem; color: black;">
|
||||
${getOriginalFileNameFromUrl(answer)}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("")
|
||||
: `<p style="margin:0px; white-space:pre-wrap"><b>${question.answer}</b></p>`
|
||||
}
|
||||
|
||||
</div>`
|
||||
)
|
||||
.join("")}
|
||||
@@ -195,19 +218,12 @@ export const sendResponseFinishedEmail = async (
|
||||
survey.id
|
||||
}/responses?utm_source=email_notification&utm_medium=email&utm_content=view_responses_CTA">${responseCount > 1 ? `View ${responseCount - 1} more ${responseCount === 2 ? "response" : "responses"}` : `View survey summary`}</a>
|
||||
|
||||
<div class="tooltip">
|
||||
<p class='brandcolor'><strong>Start a conversation 💡</strong></p>
|
||||
${
|
||||
personEmail
|
||||
? `<p>Hit 'Reply' or reach out manually: ${personEmail}</p>`
|
||||
: "<p>If you set the email address as an attribute in in-app surveys, you can reply directly to the respondent.</p>"
|
||||
}
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p><b>Don't want to get these emails?</b></p>
|
||||
<div style="margin-top:0.8em; background-color:#f1f5f9; border-radius:8px; padding:0.01em 1.6em; text-align:center; font-size:0.8em; line-height:1.2em;"><p><i>Turn off notifications for <a href="${WEBAPP_URL}/environments/${environmentId}/settings/notifications?type=alert&elementId=${survey.id}">this form</a>. <br/> Turn off notifications for <a href="${WEBAPP_URL}/environments/${environmentId}/settings/notifications?type=unsubscribedTeamIds&elementId=${team?.id}">all newly created forms</a>.</i></p></div>
|
||||
|
||||
<div style="margin-top:0.8em; padding:0.01em 1.6em; text-align:center; font-size:0.8em; line-height:1.2em;">
|
||||
<p><b>Don't want to get these notifications?</b></p>
|
||||
<p>Turn off notifications for <a href="${WEBAPP_URL}/environments/${environmentId}/settings/notifications?type=alert&elementId=${survey.id}">this form</a>.
|
||||
<br/> Turn off notifications for <a href="${WEBAPP_URL}/environments/${environmentId}/settings/notifications?type=unsubscribedTeamIds&elementId=${team?.id}">all newly created forms</a>.</p></div>
|
||||
`),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TSurveyQuestion } from "@formbricks/types/surveys";
|
||||
import { TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys";
|
||||
|
||||
export const getQuestionResponseMapping = (
|
||||
survey: { questions: TSurveyQuestion[] },
|
||||
response: TResponse
|
||||
): { question: string; answer: string }[] => {
|
||||
const questionResponseMapping: { question: string; answer: string }[] = [];
|
||||
): { question: string; answer: string | string[]; type: TSurveyQuestionType }[] => {
|
||||
const questionResponseMapping: {
|
||||
question: string;
|
||||
answer: string | string[];
|
||||
type: TSurveyQuestionType;
|
||||
}[] = [];
|
||||
|
||||
for (const question of survey.questions) {
|
||||
const answer = response.data[question.id];
|
||||
|
||||
const getAnswer = () => {
|
||||
if (!answer) return "";
|
||||
else {
|
||||
if (question.type === "fileUpload") {
|
||||
if (typeof answer === "string") {
|
||||
return [answer];
|
||||
} else {
|
||||
return answer as string[];
|
||||
// as array
|
||||
}
|
||||
} else {
|
||||
return answer.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
questionResponseMapping.push({
|
||||
question: question.headline,
|
||||
answer: typeof answer !== "undefined" ? answer.toString() : "",
|
||||
answer: getAnswer(),
|
||||
type: question.type,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
export const getOriginalFileNameFromUrl = (fileURL: string) => {
|
||||
const fileNameFromURL = new URL(fileURL).pathname.split("/").pop();
|
||||
const fileExt = fileNameFromURL?.split(".").pop();
|
||||
const originalFileName = fileNameFromURL?.split("--fid--")[0];
|
||||
const fileId = fileNameFromURL?.split("--fid--")[1];
|
||||
try {
|
||||
const fileNameFromURL = new URL(fileURL).pathname.split("/").pop();
|
||||
const fileExt = fileNameFromURL?.split(".").pop();
|
||||
const originalFileName = fileNameFromURL?.split("--fid--")[0];
|
||||
const fileId = fileNameFromURL?.split("--fid--")[1];
|
||||
|
||||
if (!fileId) {
|
||||
const fileName = originalFileName ? decodeURIComponent(originalFileName || "") : "";
|
||||
if (!fileId) {
|
||||
const fileName = originalFileName ? decodeURIComponent(originalFileName || "") : "";
|
||||
return fileName;
|
||||
}
|
||||
|
||||
const fileName = originalFileName ? decodeURIComponent(`${originalFileName}.${fileExt}` || "") : "";
|
||||
return fileName;
|
||||
} catch (error) {
|
||||
console.error("Error parsing file URL:", error);
|
||||
}
|
||||
|
||||
const fileName = originalFileName ? decodeURIComponent(`${originalFileName}.${fileExt}` || "") : "";
|
||||
return fileName;
|
||||
};
|
||||
|
||||
@@ -202,8 +202,8 @@ export default function FileInput({
|
||||
}, [allowMultipleFiles, fileUrls, isUploading]);
|
||||
|
||||
return (
|
||||
<div className="items-left relative mt-3 flex w-full cursor-pointer flex-col justify-center rounded-lg border-2 border-dashed border-slate-300 bg-slate-50 hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800">
|
||||
<div>
|
||||
<div className="items-left relative mt-3 flex w-full cursor-pointer flex-col justify-center rounded-lg border-2 border-dashed border-slate-300 bg-slate-50 hover:cursor-pointer hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800">
|
||||
<div className="max-h-[40vh] overflow-auto">
|
||||
{fileUrls &&
|
||||
fileUrls?.map((file, index) => {
|
||||
const fileName = getOriginalFileNameFromUrl(file);
|
||||
@@ -241,7 +241,9 @@ export default function FileInput({
|
||||
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">{fileName}</p>
|
||||
<p className="mt-1 w-full overflow-hidden overflow-ellipsis whitespace-nowrap px-2 text-center text-sm text-slate-600 dark:text-slate-400">
|
||||
{fileName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function FileUploadQuestion({
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="w-full">
|
||||
className="w-full ">
|
||||
{question.imageUrl && <QuestionImage imgUrl={question.imageUrl} />}
|
||||
<Headline headline={question.headline} questionId={question.id} required={question.required} />
|
||||
<Subheader subheader={question.subheader} questionId={question.id} />
|
||||
|
||||
@@ -37,7 +37,7 @@ export const FileUploadResponse = ({ selected }: FileUploadResponseProps) => {
|
||||
|
||||
<div className="flex flex-col items-center justify-center p-2">
|
||||
<FileIcon className="h-6 text-slate-500" />
|
||||
<p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
|
||||
<p className="mt-2 w-full overflow-hidden overflow-ellipsis whitespace-nowrap px-2 text-sm text-slate-500 dark:text-slate-400">
|
||||
{selected && typeof selected === "string" && decodeURIComponent(selected).split("/").pop()}
|
||||
</p>
|
||||
</div>
|
||||
@@ -77,9 +77,11 @@ export const FileUploadResponse = ({ selected }: FileUploadResponseProps) => {
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div className="flex flex-col items-center justify-center p-2">
|
||||
<div className="flex flex-col items-center justify-center p-2 text-center">
|
||||
<FileIcon className="h-6 text-slate-500" />
|
||||
<p className="mt-2 text-sm text-slate-500 dark:text-slate-400">{fileName}</p>
|
||||
<p className="mt-2 w-full overflow-hidden overflow-ellipsis whitespace-nowrap px-1 text-sm text-slate-500 dark:text-slate-400">
|
||||
{fileName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user