mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-01 03:33:40 -05:00
Use Server Components for Link Survey to improve loading speed (#676)
* moved link LinkSurvey to RSC * made some code refactors * made requested changes * ran pnpm build and added configured inactive survey * fixed a build issue * made some code refactors --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
de90e10b91
commit
15fde11804
@@ -5,24 +5,21 @@ import Progress from "@/components/preview/Progress";
|
||||
import QuestionConditional from "@/components/preview/QuestionConditional";
|
||||
import ThankYouCard from "@/components/preview/ThankYouCard";
|
||||
import ContentWrapper from "@/components/shared/ContentWrapper";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useLinkSurveyUtils } from "@/lib/linkSurvey/linkSurvey";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { Confetti } from "@formbricks/ui";
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type EnhancedSurvey = Survey & {
|
||||
brandColor: string;
|
||||
formbricksSignature: boolean;
|
||||
};
|
||||
import { TSurvey } from "@formbricks/types/v1/surveys";
|
||||
import Loading from "@/app/s/[surveyId]/loading";
|
||||
import { TProduct } from "@formbricks/types/v1/product";
|
||||
|
||||
interface LinkSurveyProps {
|
||||
survey: EnhancedSurvey;
|
||||
survey: TSurvey;
|
||||
product: TProduct;
|
||||
}
|
||||
|
||||
export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
export default function LinkSurvey({ survey, product }: LinkSurveyProps) {
|
||||
const {
|
||||
currentQuestion,
|
||||
finished,
|
||||
@@ -61,7 +58,7 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
if (!currentQuestion || prefilling) {
|
||||
return (
|
||||
<div className="flex h-full flex-1 items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -88,18 +85,18 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
)}
|
||||
{finished ? (
|
||||
<div>
|
||||
<Confetti colors={[survey.brandColor, "#eee"]} />
|
||||
<Confetti colors={[product.brandColor, "#eee"]} />
|
||||
<ThankYouCard
|
||||
headline={survey.thankYouCard.headline || "Thank you!"}
|
||||
subheader={survey.thankYouCard.subheader || "Your response has been recorded."}
|
||||
brandColor={survey.brandColor}
|
||||
brandColor={product.brandColor}
|
||||
initiateCountdown={initiateCountdown}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<QuestionConditional
|
||||
question={currentQuestion}
|
||||
brandColor={survey.brandColor}
|
||||
brandColor={product.brandColor}
|
||||
lastQuestion={lastQuestion}
|
||||
onSubmit={submitResponse}
|
||||
storedResponseValue={storedResponseValue}
|
||||
@@ -112,8 +109,8 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
|
||||
</div>
|
||||
<div className="top-0 z-10 w-full border-b bg-white">
|
||||
<div className="mx-auto max-w-md space-y-6 p-6">
|
||||
<Progress progress={progress} brandColor={survey.brandColor} />
|
||||
{survey.formbricksSignature && <FormbricksSignature />}
|
||||
<Progress progress={progress} brandColor={product.brandColor} />
|
||||
{product.formbricksSignature && <FormbricksSignature />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,7 @@ const SurveyInactive = ({
|
||||
surveyClosedMessage,
|
||||
}: {
|
||||
status: string;
|
||||
surveyClosedMessage?: { heading: string; subheading: string };
|
||||
surveyClosedMessage?: { heading?: string | undefined; subheading?: string | undefined };
|
||||
}) => {
|
||||
const icons = {
|
||||
"not found": <QuestionMarkCircleIcon className="h-20 w-20" />,
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import LinkSurvey from "@/app/s/[surveyId]/LinkSurvey";
|
||||
import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
|
||||
import LegalFooter from "@/components/shared/LegalFooter";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useLinkSurvey } from "@/lib/linkSurvey/linkSurvey";
|
||||
|
||||
interface SurveyPageProps {
|
||||
surveyId: string;
|
||||
}
|
||||
|
||||
export default function SurveyPage({ surveyId }: SurveyPageProps) {
|
||||
const { survey, isLoadingSurvey, isErrorSurvey } = useLinkSurvey(surveyId);
|
||||
|
||||
if (isLoadingSurvey) {
|
||||
return (
|
||||
<div className="flex h-full flex-1 items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorSurvey && isErrorSurvey.status === 404) {
|
||||
return <SurveyInactive status="not found" />;
|
||||
}
|
||||
|
||||
if (isErrorSurvey && isErrorSurvey.status === 403) {
|
||||
return (
|
||||
<SurveyInactive
|
||||
status={isErrorSurvey.info.reason}
|
||||
surveyClosedMessage={isErrorSurvey.info?.surveyClosedMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<LinkSurvey survey={survey} />
|
||||
<LegalFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="flex h-1/2 w-1/4 flex-col">
|
||||
<div className="ph-no-capture h-16 w-1/3 animate-pulse rounded-lg bg-gray-200 font-medium text-slate-900"></div>
|
||||
<div className="ph-no-capture mt-4 h-full animate-pulse rounded-lg bg-gray-200 text-slate-900"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,32 @@
|
||||
import SurveyPage from "./SurveyPage";
|
||||
export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
export default function LinkSurveyPage({ params }) {
|
||||
return <SurveyPage surveyId={params.surveyId} />;
|
||||
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
|
||||
import LinkSurvey from "@/app/s/[surveyId]/LinkSurvey";
|
||||
import LegalFooter from "@/app/s/[surveyId]/LegalFooter";
|
||||
import { getSurvey } from "@formbricks/lib/services/survey";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
|
||||
import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
|
||||
|
||||
export default async function LinkSurveyPage({ params }) {
|
||||
|
||||
const [survey, product] = await Promise.all([getSurvey(params.surveyId), getProductByEnvironmentId(params.environmentId)]);
|
||||
|
||||
if (survey && survey.status !== "inProgress") {
|
||||
return <SurveyInactive status={survey.status} surveyClosedMessage={survey.surveyClosedMessage} />;
|
||||
}
|
||||
|
||||
if (survey === null) {
|
||||
return <SurveyInactive status="not found" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{survey && (
|
||||
<>
|
||||
<LinkSurvey survey={survey} product={product} />
|
||||
<LegalFooter />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ import { cn } from "@/../../packages/lib/cn";
|
||||
import { isLight } from "@/lib/utils";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyCTAQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface CTAQuestionProps {
|
||||
question: CTAQuestion;
|
||||
question: CTAQuestion | TSurveyCTAQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -6,9 +6,10 @@ import type { ConsentQuestion } from "@formbricks/types/questions";
|
||||
import { useEffect, useState } from "react";
|
||||
import Headline from "./Headline";
|
||||
import HtmlBody from "./HtmlBody";
|
||||
import { TSurveyConsentQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface ConsentQuestionProps {
|
||||
question: ConsentQuestion;
|
||||
question: ConsentQuestion | TSurveyConsentQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -4,14 +4,15 @@ import { shuffleArray } from "@/lib/utils";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { symmetricDifference } from "@formbricks/lib/utils/array";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import type { Choice, MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import type { MultipleChoiceMultiQuestion } from "@formbricks/types/questions";
|
||||
import { TSurveyChoice, TSurveyMultipleChoiceMultiQuestion } from "@formbricks/types/v1/surveys";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
|
||||
interface MultipleChoiceMultiProps {
|
||||
question: MultipleChoiceMultiQuestion;
|
||||
question: MultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
@@ -57,7 +58,7 @@ export default function MultipleChoiceMultiQuestion({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [storedResponseValue, question.id]);
|
||||
|
||||
const [questionChoices, setQuestionChoices] = useState<Choice[]>(
|
||||
const [questionChoices, setQuestionChoices] = useState<TSurveyChoice[]>(
|
||||
question.choices
|
||||
? question.shuffleOption !== "none"
|
||||
? shuffleArray(question.choices, question.shuffleOption)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { shuffleArray } from "@/lib/utils";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { MultipleChoiceSingleQuestion } from "@formbricks/types/questions";
|
||||
import { TSurveyChoice } from "@formbricks/types/v1/surveys";
|
||||
import { TSurveyChoice, TSurveyMultipleChoiceSingleQuestion } from "@formbricks/types/v1/surveys";
|
||||
import { Input } from "@formbricks/ui";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Headline from "./Headline";
|
||||
@@ -11,7 +11,7 @@ import Subheader from "./Subheader";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
|
||||
interface MultipleChoiceSingleProps {
|
||||
question: MultipleChoiceSingleQuestion;
|
||||
question: MultipleChoiceSingleQuestion | TSurveyMultipleChoiceSingleQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -6,9 +6,10 @@ import Subheader from "./Subheader";
|
||||
import SubmitButton from "@/components/preview/SubmitButton";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyNPSQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface NPSQuestionProps {
|
||||
question: NPSQuestion;
|
||||
question: NPSQuestion | TSurveyNPSQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -5,9 +5,10 @@ import Subheader from "./Subheader";
|
||||
import SubmitButton from "@/components/preview/SubmitButton";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyOpenTextQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface OpenTextQuestionProps {
|
||||
question: OpenTextQuestion;
|
||||
question: TSurveyOpenTextQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -6,9 +6,10 @@ import NPSQuestion from "./NPSQuestion";
|
||||
import CTAQuestion from "./CTAQuestion";
|
||||
import RatingQuestion from "./RatingQuestion";
|
||||
import ConsentQuestion from "./ConsentQuestion";
|
||||
import { TSurveyQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface QuestionConditionalProps {
|
||||
question: Question;
|
||||
question: Question | TSurveyQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -21,9 +21,10 @@ import SubmitButton from "@/components/preview/SubmitButton";
|
||||
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { BackButton } from "@/components/preview/BackButton";
|
||||
import { TSurveyRatingQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface RatingQuestionProps {
|
||||
question: RatingQuestion;
|
||||
question: RatingQuestion | TSurveyRatingQuestion;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { cn } from "@/../../packages/lib/cn";
|
||||
import { isLight } from "@/lib/utils";
|
||||
import { Question } from "@formbricks/types/questions";
|
||||
import { TSurveyQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
type SubmitButtonProps = {
|
||||
question: Question;
|
||||
question: Question | TSurveyQuestion;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
};
|
||||
|
||||
@@ -2,13 +2,13 @@ import { createDisplay, markDisplayResponded } from "@formbricks/lib/client/disp
|
||||
import { createResponse, updateResponse } from "@formbricks/lib/client/response";
|
||||
import { fetcher } from "@formbricks/lib/fetcher";
|
||||
import { Response } from "@formbricks/types/js";
|
||||
import { QuestionType, type Logic, type Question } from "@formbricks/types/questions";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Question, QuestionType } from "@formbricks/types/questions";
|
||||
import { TResponseInput } from "@formbricks/types/v1/responses";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useGetOrCreatePerson } from "../people/people";
|
||||
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/v1/surveys";
|
||||
|
||||
interface StoredResponse {
|
||||
id: string | null;
|
||||
@@ -27,8 +27,8 @@ export const useLinkSurvey = (surveyId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useLinkSurveyUtils = (survey: Survey) => {
|
||||
const [currentQuestion, setCurrentQuestion] = useState<Question | null>(null);
|
||||
export const useLinkSurveyUtils = (survey: TSurvey) => {
|
||||
const [currentQuestion, setCurrentQuestion] = useState<TSurveyQuestion | Question | null>(null);
|
||||
const [prefilling, setPrefilling] = useState(true);
|
||||
const [progress, setProgress] = useState(0); // [0, 1]
|
||||
const [finished, setFinished] = useState(false);
|
||||
@@ -39,7 +39,7 @@ export const useLinkSurveyUtils = (survey: Survey) => {
|
||||
const [initiateCountdown, setinitiateCountdown] = useState<boolean>(false);
|
||||
const [storedResponseValue, setStoredResponseValue] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const URLParams = new URLSearchParams(window.location.search);
|
||||
const URLParams = new URLSearchParams(typeof window !== "undefined" ? window.location.search : "");
|
||||
const isPreview = URLParams.get("preview") === "true";
|
||||
const hasFirstQuestionPrefill = URLParams.has(survey.questions[0].id);
|
||||
const firstQuestionPrefill = hasFirstQuestionPrefill ? URLParams.get(survey.questions[0].id) : null;
|
||||
@@ -331,7 +331,7 @@ const clearStoredResponses = (surveyId: string) => {
|
||||
localStorage.removeItem(`formbricks-${surveyId}-response`);
|
||||
};
|
||||
|
||||
const checkValidity = (question: Question, answer: any): boolean => {
|
||||
const checkValidity = (question: TSurveyQuestion | Question, answer: any): boolean => {
|
||||
if (question.required && (!answer || answer === "")) return false;
|
||||
try {
|
||||
switch (question.type) {
|
||||
@@ -388,7 +388,7 @@ const checkValidity = (question: Question, answer: any): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
const createAnswer = (question: Question, answer: string): string | number | string[] => {
|
||||
const createAnswer = (question: TSurveyQuestion | Question, answer: string): string | number | string[] => {
|
||||
switch (question.type) {
|
||||
case QuestionType.OpenText:
|
||||
case QuestionType.MultipleChoiceSingle:
|
||||
@@ -421,7 +421,7 @@ const createAnswer = (question: Question, answer: string): string | number | str
|
||||
}
|
||||
};
|
||||
|
||||
const evaluateCondition = (logic: Logic, responseValue: any): boolean => {
|
||||
const evaluateCondition = (logic: TSurveyLogic, responseValue: any): boolean => {
|
||||
switch (logic.condition) {
|
||||
case "equals":
|
||||
return (
|
||||
|
||||
@@ -53,7 +53,7 @@ const shuffle = (array: any[]) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const shuffleArray = (array: any[], shuffleOption: string) => {
|
||||
export const shuffleArray = (array: any[], shuffleOption: string | undefined) => {
|
||||
const arrayCopy = [...array];
|
||||
const otherIndex = arrayCopy.findIndex((element) => element.id === "other");
|
||||
const otherElement = otherIndex !== -1 ? arrayCopy.splice(otherIndex, 1)[0] : null;
|
||||
|
||||
Reference in New Issue
Block a user