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:
Dhruwang Jariwala
2023-08-16 14:26:15 +05:30
committed by GitHub
parent de90e10b91
commit 15fde11804
17 changed files with 82 additions and 83 deletions
+12 -15
View File
@@ -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>
</>
+1 -1
View File
@@ -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" />,
-43
View File
@@ -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 />
</>
);
}
+10
View File
@@ -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>
);
}
+30 -3
View File
@@ -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 />
</>
)}
</>
);
}
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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;
};
+8 -8
View File
@@ -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 (
+1 -1
View File
@@ -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;