diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx
index 7f66533c6e..11f5374014 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx
@@ -52,7 +52,7 @@ export default function Modal({
return {
transform: `scale(${scaleValue})`,
- "transform-origin": placementClass,
+ transformOrigin: placementClass,
};
};
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 aba7be092f..06c2b98698 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx
@@ -241,8 +241,9 @@ export default function PreviewSurvey({
-
- {previewType === "modal" ? "Your web app" : "Preview"}
+
+
{previewType === "modal" ? "Your web app" : "Preview"}
+
-
+
{previewType === "modal" ? (
diff --git a/packages/ui/Input/index.tsx b/packages/ui/Input/index.tsx
index 62d08787ea..58d0170ad3 100644
--- a/packages/ui/Input/index.tsx
+++ b/packages/ui/Input/index.tsx
@@ -11,13 +11,13 @@ export interface InputProps
isInvalid?: boolean;
}
-const Input = React.forwardRef(({ className, ...props }, ref) => {
+const Input = React.forwardRef(({ className, isInvalid, ...props }, ref) => {
return (
;
}
-const QuestionFormInput = ({
- localSurvey,
- questionId,
- questionIdx,
- updateQuestion,
- updateSurvey,
- isInvalid,
- environmentId,
- type,
-}: QuestionFormInputProps) => {
- const isThankYouCard = questionId === "end";
- const question = useMemo(() => {
- return isThankYouCard
- ? localSurvey.thankYouCard
- : localSurvey.questions.find((question) => question.id === questionId)!;
- }, [isThankYouCard, localSurvey, questionId]);
+const QuestionFormInput = React.forwardRef(
+ (
+ {
+ localSurvey,
+ questionId,
+ questionIdx,
+ updateQuestion,
+ updateSurvey,
+ isInvalid,
+ environmentId,
+ type,
+ }: QuestionFormInputProps,
+ ref
+ ) => {
+ const isThankYouCard = questionId === "end";
+ const question = useMemo(() => {
+ return isThankYouCard
+ ? localSurvey.thankYouCard
+ : localSurvey.questions.find((question) => question.id === questionId)!;
+ }, [isThankYouCard, localSurvey, questionId]);
- const getQuestionTextBasedOnType = (): string => {
- return question[type as keyof typeof question] || "";
- };
+ const getQuestionTextBasedOnType = (): string => {
+ return question[type as keyof typeof question] || "";
+ };
- const [text, setText] = useState(getQuestionTextBasedOnType() ?? "");
- const [renderedText, setRenderedText] = useState();
+ const [text, setText] = useState(getQuestionTextBasedOnType() ?? "");
+ const [renderedText, setRenderedText] = useState();
- const highlightContainerRef = useRef(null);
- const fallbackInputRef = useRef(null);
- const inputRef = useRef(null);
- const [showImageUploader, setShowImageUploader] = useState(
- questionId === "end"
- ? localSurvey.thankYouCard.imageUrl
- ? true
- : false
- : !!(question as TSurveyQuestion).imageUrl
- );
- const [showQuestionSelect, setShowQuestionSelect] = useState(false);
- const [showFallbackInput, setShowFallbackInput] = useState(false);
- const [recallQuestions, setRecallQuestions] = useState(
- text.includes("#recall:") ? getRecallQuestions(text, localSurvey) : []
- );
- const filteredRecallQuestions = Array.from(new Set(recallQuestions.map((q) => q.id))).map((id) => {
- return recallQuestions.find((q) => q.id === id);
- });
- const [fallbacks, setFallbacks] = useState<{ [type: string]: string }>(
- text.includes("/fallback:") ? getFallbackValues(text) : {}
- );
-
- // Hook to synchronize the horizontal scroll position of highlightContainerRef and inputRef.
- useSyncScroll(highlightContainerRef, inputRef, text);
-
- useEffect(() => {
- // Generates an array of headlines from recallQuestions, replacing nested recall questions with '___' .
- const recallQuestionHeadlines = recallQuestions.flatMap((recallQuestion) => {
- if (!recallQuestion.headline.includes("#recall:")) {
- return [recallQuestion.headline];
- }
- const recallQuestionText = (recallQuestion[type as keyof typeof recallQuestion] as string) || "";
- const recallInfo = extractRecallInfo(recallQuestionText);
-
- if (recallInfo) {
- const recallQuestionId = extractId(recallInfo);
- const recallQuestion = localSurvey.questions.find((question) => question.id === recallQuestionId);
-
- if (recallQuestion) {
- return [recallQuestionText.replace(recallInfo, `___`)];
- }
- }
- return [];
+ const highlightContainerRef = useRef(null);
+ const fallbackInputRef = useRef(null);
+ const inputRef = useRef(null);
+ const [showImageUploader, setShowImageUploader] = useState(
+ questionId === "end"
+ ? localSurvey.thankYouCard.imageUrl
+ ? true
+ : false
+ : !!(question as TSurveyQuestion).imageUrl
+ );
+ const [showQuestionSelect, setShowQuestionSelect] = useState(false);
+ const [showFallbackInput, setShowFallbackInput] = useState(false);
+ const [recallQuestions, setRecallQuestions] = useState(
+ text.includes("#recall:") ? getRecallQuestions(text, localSurvey) : []
+ );
+ const filteredRecallQuestions = Array.from(new Set(recallQuestions.map((q) => q.id))).map((id) => {
+ return recallQuestions.find((q) => q.id === id);
});
+ const [fallbacks, setFallbacks] = useState<{ [type: string]: string }>(
+ text.includes("/fallback:") ? getFallbackValues(text) : {}
+ );
- // Constructs an array of JSX elements representing segmented parts of text, interspersed with special formatted spans for recall headlines.
- const processInput = (): JSX.Element[] => {
- const parts: JSX.Element[] = [];
- let remainingText: string = text ?? "";
- remainingText = recallToHeadline(remainingText, localSurvey, false);
- filterRecallQuestions(remainingText);
- recallQuestionHeadlines.forEach((headline) => {
- const index = remainingText.indexOf("@" + headline);
- if (index !== -1) {
- if (index > 0) {
+ // Hook to synchronize the horizontal scroll position of highlightContainerRef and inputRef.
+ useSyncScroll(highlightContainerRef, inputRef, text);
+
+ useEffect(() => {
+ // Generates an array of headlines from recallQuestions, replacing nested recall questions with '___' .
+ const recallQuestionHeadlines = recallQuestions.flatMap((recallQuestion) => {
+ if (!recallQuestion.headline.includes("#recall:")) {
+ return [recallQuestion.headline];
+ }
+ const recallQuestionText = (recallQuestion[type as keyof typeof recallQuestion] as string) || "";
+ const recallInfo = extractRecallInfo(recallQuestionText);
+
+ if (recallInfo) {
+ const recallQuestionId = extractId(recallInfo);
+ const recallQuestion = localSurvey.questions.find((question) => question.id === recallQuestionId);
+
+ if (recallQuestion) {
+ return [recallQuestionText.replace(recallInfo, `___`)];
+ }
+ }
+ return [];
+ });
+
+ // Constructs an array of JSX elements representing segmented parts of text, interspersed with special formatted spans for recall headlines.
+ const processInput = (): JSX.Element[] => {
+ const parts: JSX.Element[] = [];
+ let remainingText: string = text ?? "";
+ remainingText = recallToHeadline(remainingText, localSurvey, false);
+ filterRecallQuestions(remainingText);
+ recallQuestionHeadlines.forEach((headline) => {
+ const index = remainingText.indexOf("@" + headline);
+ if (index !== -1) {
+ if (index > 0) {
+ parts.push(
+
+ {remainingText.substring(0, index)}
+
+ );
+ }
parts.push(
-
- {remainingText.substring(0, index)}
+
+ {"@" + headline}
);
+ remainingText = remainingText.substring(index + headline.length + 1);
}
+ });
+ if (remainingText.length) {
parts.push(
-
- {"@" + headline}
+
+ {remainingText}
);
- remainingText = remainingText.substring(index + headline.length + 1);
+ }
+ return parts;
+ };
+ setRenderedText(processInput());
+ }, [text]);
+
+ useEffect(() => {
+ if (fallbackInputRef.current) {
+ fallbackInputRef.current.focus();
+ }
+ }, [showFallbackInput]);
+
+ const checkForRecallSymbol = () => {
+ const pattern = /(^|\s)@(\s|$)/;
+ if (pattern.test(text)) {
+ setShowQuestionSelect(true);
+ } else {
+ setShowQuestionSelect(false);
+ }
+ };
+
+ // Adds a new recall question to the recallQuestions array, updates fallbacks, modifies the text with recall details.
+ const addRecallQuestion = (recallQuestion: TSurveyQuestion) => {
+ let recallQuestionTemp = { ...recallQuestion };
+ recallQuestionTemp = replaceRecallInfoWithUnderline(recallQuestionTemp);
+ setRecallQuestions((prevQuestions) => {
+ const updatedQuestions = [...prevQuestions, recallQuestionTemp];
+ return updatedQuestions;
+ });
+ if (!Object.keys(fallbacks).includes(recallQuestion.id)) {
+ setFallbacks((prevFallbacks) => ({
+ ...prevFallbacks,
+ [recallQuestion.id]: "",
+ }));
+ }
+ setShowQuestionSelect(false);
+ const modifiedHeadlineWithId = getQuestionTextBasedOnType().replace(
+ "@",
+ `#recall:${recallQuestion.id}/fallback:# `
+ );
+ updateQuestionDetails(modifiedHeadlineWithId);
+
+ const modifiedHeadlineWithName = recallToHeadline(modifiedHeadlineWithId, localSurvey, false);
+ setText(modifiedHeadlineWithName);
+ setShowFallbackInput(true);
+ };
+
+ // Filters and updates the list of recall questions based on their presence in the given text, also managing related text and fallback states.
+ const filterRecallQuestions = (text: string) => {
+ let includedQuestions: TSurveyQuestion[] = [];
+ recallQuestions.forEach((recallQuestion) => {
+ if (text.includes(`@${recallQuestion.headline}`)) {
+ includedQuestions.push(recallQuestion);
+ } else {
+ const questionToRemove = recallQuestion.headline.slice(0, -1);
+ const newText = text.replace(`@${questionToRemove}`, "");
+ setText(newText);
+ updateQuestionDetails(newText);
+ let updatedFallback = { ...fallbacks };
+ delete updatedFallback[recallQuestion.id];
+ setFallbacks(updatedFallback);
}
});
- if (remainingText.length) {
- parts.push(
-
- {remainingText}
-
- );
- }
- return parts;
+ setRecallQuestions(includedQuestions);
};
- setRenderedText(processInput());
- }, [text]);
- useEffect(() => {
- if (fallbackInputRef.current) {
- fallbackInputRef.current.focus();
- }
- }, [showFallbackInput]);
+ const addFallback = () => {
+ let headlineWithFallback = getQuestionTextBasedOnType();
+ filteredRecallQuestions.forEach((recallQuestion) => {
+ if (recallQuestion) {
+ const recallInfo = findRecallInfoById(getQuestionTextBasedOnType(), recallQuestion!.id);
+ if (recallInfo) {
+ let fallBackValue = fallbacks[recallQuestion.id].trim();
+ fallBackValue = fallBackValue.replace(/ /g, "nbsp");
+ let updatedFallback = { ...fallbacks };
+ updatedFallback[recallQuestion.id] = fallBackValue;
+ setFallbacks(updatedFallback);
+ headlineWithFallback = headlineWithFallback.replace(
+ recallInfo,
+ `#recall:${recallQuestion?.id}/fallback:${fallBackValue}#`
+ );
+ updateQuestionDetails(headlineWithFallback);
+ }
+ }
+ });
+ setShowFallbackInput(false);
+ inputRef.current?.focus();
+ };
- const checkForRecallSymbol = () => {
- const pattern = /(^|\s)@(\s|$)/;
- if (pattern.test(text)) {
- setShowQuestionSelect(true);
- } else {
- setShowQuestionSelect(false);
- }
- };
+ useEffect(() => {
+ checkForRecallSymbol();
+ }, [text]);
- // Adds a new recall question to the recallQuestions array, updates fallbacks, modifies the text with recall details.
- const addRecallQuestion = (recallQuestion: TSurveyQuestion) => {
- let recallQuestionTemp = { ...recallQuestion };
- recallQuestionTemp = replaceRecallInfoWithUnderline(recallQuestionTemp);
- setRecallQuestions((prevQuestions) => {
- const updatedQuestions = [...prevQuestions, recallQuestionTemp];
- return updatedQuestions;
- });
- if (!Object.keys(fallbacks).includes(recallQuestion.id)) {
- setFallbacks((prevFallbacks) => ({
- ...prevFallbacks,
- [recallQuestion.id]: "",
- }));
- }
- setShowQuestionSelect(false);
- const modifiedHeadlineWithId = getQuestionTextBasedOnType().replace(
- "@",
- `#recall:${recallQuestion.id}/fallback:# `
- );
- updateQuestionDetails(modifiedHeadlineWithId);
-
- const modifiedHeadlineWithName = recallToHeadline(modifiedHeadlineWithId, localSurvey, false);
- setText(modifiedHeadlineWithName);
- setShowFallbackInput(true);
- };
-
- // Filters and updates the list of recall questions based on their presence in the given text, also managing related text and fallback states.
- const filterRecallQuestions = (text: string) => {
- let includedQuestions: TSurveyQuestion[] = [];
- recallQuestions.forEach((recallQuestion) => {
- if (text.includes(`@${recallQuestion.headline}`)) {
- includedQuestions.push(recallQuestion);
+ // updation of questions and Thank You Card is done in a different manner, so for question we use updateQuestion and for ThankYouCard we use updateSurvey
+ const updateQuestionDetails = (updatedText: string) => {
+ if (isThankYouCard) {
+ if (updateSurvey) {
+ updateSurvey({ [type]: updatedText });
+ }
} else {
- const questionToRemove = recallQuestion.headline.slice(0, -1);
- const newText = text.replace(`@${questionToRemove}`, "");
- setText(newText);
- updateQuestionDetails(newText);
- let updatedFallback = { ...fallbacks };
- delete updatedFallback[recallQuestion.id];
- setFallbacks(updatedFallback);
- }
- });
- setRecallQuestions(includedQuestions);
- };
-
- const addFallback = () => {
- let headlineWithFallback = getQuestionTextBasedOnType();
- filteredRecallQuestions.forEach((recallQuestion) => {
- if (recallQuestion) {
- const recallInfo = findRecallInfoById(getQuestionTextBasedOnType(), recallQuestion!.id);
- if (recallInfo) {
- let fallBackValue = fallbacks[recallQuestion.id].trim();
- fallBackValue = fallBackValue.replace(/ /g, "nbsp");
- let updatedFallback = { ...fallbacks };
- updatedFallback[recallQuestion.id] = fallBackValue;
- setFallbacks(updatedFallback);
- headlineWithFallback = headlineWithFallback.replace(
- recallInfo,
- `#recall:${recallQuestion?.id}/fallback:${fallBackValue}#`
- );
- updateQuestionDetails(headlineWithFallback);
+ if (updateQuestion) {
+ updateQuestion(questionIdx, {
+ [type]: updatedText,
+ });
}
}
- });
- setShowFallbackInput(false);
- inputRef.current?.focus();
- };
+ };
- useEffect(() => {
- checkForRecallSymbol();
- }, [text]);
-
- // updation of questions and Thank You Card is done in a different manner, so for question we use updateQuestion and for ThankYouCard we use updateSurvey
- const updateQuestionDetails = (updatedText: string) => {
- if (isThankYouCard) {
- if (updateSurvey) {
- updateSurvey({ [type]: updatedText });
- }
- } else {
- if (updateQuestion) {
- updateQuestion(questionIdx, {
- [type]: updatedText,
- });
- }
- }
- };
-
- return (
-
-
-
- {showImageUploader && type === "headline" && (
-
{
- if (isThankYouCard && updateSurvey && url) {
- updateSurvey({ imageUrl: url[0] });
- } else if (updateQuestion && url) {
- updateQuestion(questionIdx, { imageUrl: url[0] });
- }
- }}
- fileUrl={
- isThankYouCard ? localSurvey.thankYouCard.imageUrl : (question as TSurveyQuestion).imageUrl
- }
- />
- )}
-
-
-
-
- {renderedText}
-
- {getQuestionTextBasedOnType().includes("recall:") && (
-
- )}
-
{
- setText(recallToHeadline(e.target.value ?? "", localSurvey, false));
- updateQuestionDetails(headlineToRecall(e.target.value, recallQuestions, fallbacks));
+ return (
+
+
+
+ {showImageUploader && type === "headline" && (
+
{
+ if (isThankYouCard && updateSurvey && url) {
+ updateSurvey({ imageUrl: url[0] });
+ } else if (updateQuestion && url) {
+ updateQuestion(questionIdx, { imageUrl: url[0] });
+ }
}}
- isInvalid={isInvalid && text.trim() === ""}
+ fileUrl={
+ isThankYouCard ? localSurvey.thankYouCard.imageUrl : (question as TSurveyQuestion).imageUrl
+ }
/>
- {!showQuestionSelect && showFallbackInput && recallQuestions.length > 0 && (
-
+
+
+
+ {renderedText}
+
+ {getQuestionTextBasedOnType().includes("recall:") && (
+
+ )}
+
| undefined}
+ id={type}
+ name={type}
+ aria-label={type === "headline" ? "Question" : "Description"}
+ autoComplete={showQuestionSelect ? "off" : "on"}
+ value={recallToHeadline(text ?? "", localSurvey, false)}
+ onChange={(e) => {
+ setText(recallToHeadline(e.target.value ?? "", localSurvey, false));
+ updateQuestionDetails(headlineToRecall(e.target.value, recallQuestions, fallbacks));
+ }}
+ isInvalid={isInvalid && text.trim() === ""}
+ />
+ {!showQuestionSelect && showFallbackInput && recallQuestions.length > 0 && (
+
+ )}
+
+ {type === "headline" && (
+ setShowImageUploader((prev) => !prev)}
/>
)}
- {type === "headline" && (
-
setShowImageUploader((prev) => !prev)}
- />
- )}
+ {showQuestionSelect && (
+
+ )}
- {showQuestionSelect && (
-
- )}
-
- );
-};
+ );
+ }
+);
+QuestionFormInput.displayName = "QuestionFormInput";
+
export default QuestionFormInput;
diff --git a/packages/ui/SurveysList/index.tsx b/packages/ui/SurveysList/index.tsx
index a9188ce1dc..01b20fafa2 100644
--- a/packages/ui/SurveysList/index.tsx
+++ b/packages/ui/SurveysList/index.tsx
@@ -28,12 +28,16 @@ export default function SurveysList({
userId,
}: SurveysListProps) {
const [filteredSurveys, setFilteredSurveys] = useState(surveys);
- // Initialize orientation state from localStorage or default to 'grid'
- const [orientation, setOrientation] = useState(() => localStorage.getItem("surveyOrientation") || "grid");
+ // Initialize orientation state with a function that checks if window is defined
+ const [orientation, setOrientation] = useState(() =>
+ typeof window !== "undefined" ? localStorage.getItem("surveyOrientation") || "grid" : "grid"
+ );
// Save orientation to localStorage
useEffect(() => {
- localStorage.setItem("surveyOrientation", orientation);
+ if (typeof window !== "undefined") {
+ localStorage.setItem("surveyOrientation", orientation);
+ }
}, [orientation]);
return (