chore: vercel style guide part 3 (#4530)

This commit is contained in:
Dhruwang Jariwala
2025-01-06 10:04:35 +05:30
committed by GitHub
parent 2c00c55e5d
commit 10738a7af0
14 changed files with 105 additions and 105 deletions

View File

@@ -1,4 +1,4 @@
import { rateLimit } from "@/app/middleware/rateLimit";
import { rateLimit } from "@/app/middleware/rate-limit";
import {
CLIENT_SIDE_API_RATE_LIMIT,
FORGET_PASSWORD_RATE_LIMIT,

View File

@@ -15,7 +15,7 @@ export const isClientSideApiRoute = (url: string): boolean => {
};
export const isShareUrlRoute = (url: string): boolean => {
const regex = /\/share\/[A-Za-z0-9]+\/(summary|responses)/;
const regex = /\/share\/[A-Za-z0-9]+\/(?:summary|responses)/;
return regex.test(url);
};
@@ -29,7 +29,7 @@ export const isAuthProtectedRoute = (url: string): boolean => {
export const isSyncWithUserIdentificationEndpoint = (
url: string
): { environmentId: string; userId: string } | false => {
const regex = /\/api\/v1\/client\/([^/]+)\/app\/sync\/([^/]+)/;
const regex = /\/api\/v1\/client\/(?<environmentId>[^/]+)\/app\/sync\/(?<userId>[^/]+)/;
const match = url.match(regex);
return match ? { environmentId: match[1], userId: match[2] } : false;
return match ? { environmentId: match.groups!.environmentId, userId: match.groups!.userId } : false;
};

View File

@@ -1,10 +1,10 @@
import { LRUCache } from "lru-cache";
import { ENTERPRISE_LICENSE_KEY, REDIS_HTTP_URL } from "@formbricks/lib/constants";
type Options = {
interface Options {
interval: number;
allowedPerInterval: number;
};
}
const inMemoryRateLimiter = (options: Options) => {
const tokenCache = new LRUCache<string, number>({
@@ -12,8 +12,8 @@ const inMemoryRateLimiter = (options: Options) => {
ttl: options.interval * 1000, // converts to expected input of milliseconds
});
return async (token: string) => {
const currentUsage = tokenCache.get(token) || 0;
return (token: string) => {
const currentUsage = tokenCache.get(token) ?? 0;
if (currentUsage >= options.allowedPerInterval) {
throw new Error("Rate limit exceeded");
}
@@ -23,15 +23,19 @@ const inMemoryRateLimiter = (options: Options) => {
const redisRateLimiter = (options: Options) => async (token: string) => {
try {
if (!REDIS_HTTP_URL) {
throw new Error("Redis HTTP URL is not set");
}
const tokenCountResponse = await fetch(`${REDIS_HTTP_URL}/INCR/${token}`);
if (!tokenCountResponse.ok) {
// eslint-disable-next-line no-console -- need for debugging
console.error("Failed to increment token count in Redis", tokenCountResponse);
return;
}
const { INCR } = await tokenCountResponse.json();
if (INCR === 1) {
await fetch(`${REDIS_HTTP_URL}/EXPIRE/${token}/${options.interval}`);
await fetch(`${REDIS_HTTP_URL}/EXPIRE/${token}/${options.interval.toString()}`);
} else if (INCR > options.allowedPerInterval) {
throw new Error();
}

View File

@@ -33,10 +33,10 @@ const Page = async () => {
}
let environmentId: string | null = null;
environmentId = await getFirstEnvironmentIdByUserId(session?.user.id);
environmentId = await getFirstEnvironmentIdByUserId(session.user.id);
const currentUserMembership = await getMembershipByUserIdOrganizationId(
session?.user.id,
session.user.id,
userOrganizations[0].id
);
const { isManager, isOwner } = getAccessFlags(currentUserMembership?.role);

View File

@@ -45,7 +45,9 @@ export const validateSurveyPinAction = actionClient
const originalPin = survey.pin?.toString();
if (!originalPin) return { survey };
if (originalPin !== parsedInput.pin) throw new InvalidInputError("Invalid pin");
if (originalPin !== parsedInput.pin) {
throw new InvalidInputError("INVALID_PIN");
}
return { survey };
});

View File

@@ -1,5 +1,5 @@
import { LegalFooter } from "@/app/s/[surveyId]/components/LegalFooter";
import { SurveyLoadingAnimation } from "@/app/s/[surveyId]/components/SurveyLoadingAnimation";
import { LegalFooter } from "@/app/s/[surveyId]/components/legal-footer";
import { SurveyLoadingAnimation } from "@/app/s/[surveyId]/components/survey-loading-animation";
import { ClientLogo } from "@/modules/ui/components/client-logo";
import { MediaBackground } from "@/modules/ui/components/media-background";
import { ResetProgressButton } from "@/modules/ui/components/reset-progress-button";

View File

@@ -1,7 +1,7 @@
"use client";
import { LinkSurveyWrapper } from "@/app/s/[surveyId]/components/LinkSurveyWrapper";
import { SurveyLinkUsed } from "@/app/s/[surveyId]/components/SurveyLinkUsed";
import { LinkSurveyWrapper } from "@/app/s/[surveyId]/components/link-survey-wrapper";
import { SurveyLinkUsed } from "@/app/s/[surveyId]/components/survey-link-used";
import { VerifyEmail } from "@/app/s/[surveyId]/components/verify-email";
import { getPrefillValue } from "@/app/s/[surveyId]/lib/prefilling";
import { SurveyInline } from "@/modules/ui/components/survey";
@@ -68,19 +68,19 @@ export const LinkSurvey = ({
const t = useTranslations();
const responseId = singleUseResponse?.id;
const searchParams = useSearchParams();
const skipPrefilled = searchParams?.get("skipPrefilled") === "true";
const sourceParam = searchParams?.get("source");
const suId = searchParams?.get("suId");
const defaultLanguageCode = survey.languages?.find((surveyLanguage) => {
return surveyLanguage.default === true;
const skipPrefilled = searchParams.get("skipPrefilled") === "true";
const sourceParam = searchParams.get("source");
const suId = searchParams.get("suId");
const defaultLanguageCode = survey.languages.find((surveyLanguage) => {
return surveyLanguage.default;
})?.language.code;
const startAt = searchParams?.get("startAt");
const startAt = searchParams.get("startAt");
const isStartAtValid = useMemo(() => {
if (!startAt) return false;
if (survey?.welcomeCard.enabled && startAt === "start") return true;
if (survey.welcomeCard.enabled && startAt === "start") return true;
const isValid = survey?.questions.some((question) => question.id === startAt);
const isValid = survey.questions.some((question) => question.id === startAt);
// To remove startAt query param from URL if it is not valid:
if (!isValid && typeof window !== "undefined") {
@@ -120,11 +120,11 @@ export const LinkSurvey = ({
);
const [autoFocus, setAutofocus] = useState(false);
const hasFinishedSingleUseResponse = useMemo(() => {
if (singleUseResponse && singleUseResponse.finished) {
if (singleUseResponse?.finished) {
return true;
}
return false;
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps -- only run once
}, []);
// Not in an iframe, enable autofocus on input fields.
@@ -132,21 +132,21 @@ export const LinkSurvey = ({
if (window.self === window.top) {
setAutofocus(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps -- only run once
}, []);
const hiddenFieldsRecord = useMemo<TResponseHiddenFieldValue>(() => {
const fieldsRecord: TResponseHiddenFieldValue = {};
survey.hiddenFields?.fieldIds?.forEach((field) => {
const answer = searchParams?.get(field);
survey.hiddenFields.fieldIds?.forEach((field) => {
const answer = searchParams.get(field);
if (answer) {
fieldsRecord[field] = answer;
}
});
return fieldsRecord;
}, [searchParams, survey.hiddenFields?.fieldIds]);
}, [searchParams, survey.hiddenFields.fieldIds]);
const getVerifiedEmail = useMemo<Record<string, string> | null>(() => {
if (survey.isVerifyEmailEnabled && verifiedEmail) {
@@ -191,27 +191,17 @@ export const LinkSurvey = ({
}
const determineStyling = () => {
// allow style overwrite is disabled from the project
// Check if style overwrite is disabled at the project level
if (!project.styling.allowStyleOverwrite) {
return project.styling;
}
// allow style overwrite is enabled from the project
if (project.styling.allowStyleOverwrite) {
// survey style overwrite is disabled
if (!survey.styling?.overwriteThemeStyling) {
return project.styling;
}
// survey style overwrite is enabled
return survey.styling;
}
return project.styling;
// Return survey styling if survey overwrites are enabled, otherwise return project styling
return survey.styling?.overwriteThemeStyling ? survey.styling : project.styling;
};
const handleResetSurvey = () => {
setQuestionId(survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id);
setQuestionId(survey.welcomeCard.enabled ? "start" : survey.questions[0].id);
setResponseData({});
};
@@ -246,27 +236,29 @@ export const LinkSurvey = ({
}
onRetry={() => {
setIsError(false);
responseQueue.processQueue();
void responseQueue.processQueue();
}}
onDisplay={async () => {
onDisplay={() => {
if (!isPreview) {
const api = new FormbricksAPI({
apiHost: webAppUrl,
environmentId: survey.environmentId,
});
void (async () => {
const api = new FormbricksAPI({
apiHost: webAppUrl,
environmentId: survey.environmentId,
});
const res = await api.client.display.create({
surveyId: survey.id,
});
const res = await api.client.display.create({
surveyId: survey.id,
});
if (!res.ok) {
throw new Error(t("s.could_not_create_display"));
}
if (!res.ok) {
throw new Error(t("s.could_not_create_display"));
}
const { id } = res.data;
const { id } = res.data;
surveyState.updateDisplayId(id);
responseQueue.updateSurveyState(surveyState);
surveyState.updateDisplayId(id);
responseQueue.updateSurveyState(surveyState);
})();
}
}}
onResponse={(responseUpdate: TResponseUpdate) => {
@@ -286,7 +278,7 @@ export const LinkSurvey = ({
: responseUpdate.language,
meta: {
url: window.location.href,
source: sourceParam || "",
source: typeof sourceParam === "string" ? sourceParam : "",
},
variables: responseUpdate.variables,
displayId: surveyState.displayId,
@@ -302,6 +294,7 @@ export const LinkSurvey = ({
const uploadedUrl = await api.client.storage.uploadFile(file, params);
return uploadedUrl;
}}
// eslint-disable-next-line jsx-a11y/no-autofocus -- need it as focus behaviour is different in normal surveys and survey preview
autoFocus={autoFocus}
prefillResponseData={prefillValue}
skipPrefilled={skipPrefilled}
@@ -313,7 +306,7 @@ export const LinkSurvey = ({
setResponseData = f;
}}
startAtQuestionId={startAt && isStartAtValid ? startAt : undefined}
fullSizeCards={isEmbed ? true : false}
fullSizeCards={isEmbed}
hiddenFieldsRecord={hiddenFieldsRecord}
/>
</LinkSurveyWrapper>

View File

@@ -1,7 +1,7 @@
"use client";
import { validateSurveyPinAction } from "@/app/s/[surveyId]/actions";
import { LinkSurvey } from "@/app/s/[surveyId]/components/LinkSurvey";
import { LinkSurvey } from "@/app/s/[surveyId]/components/link-survey";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { OTPInput } from "@/modules/ui/components/otp-input";
import { useTranslations } from "next-intl";
@@ -55,19 +55,6 @@ export const PinScreen = (props: PinScreenProps) => {
const [error, setError] = useState("");
const [survey, setSurvey] = useState<TSurvey>();
const _validateSurveyPinAsync = useCallback(async (surveyId: string, pin: string) => {
const response = await validateSurveyPinAction({ surveyId, pin });
if (response?.data) {
setSurvey(response.data.survey);
} else {
const errorMessage = getFormattedErrorMessage(response);
setError(errorMessage);
}
setLoading(false);
}, []);
const resetState = useCallback(() => {
setError("");
setLoading(false);
@@ -76,7 +63,9 @@ export const PinScreen = (props: PinScreenProps) => {
useEffect(() => {
if (error) {
const timeout = setTimeout(() => resetState(), 2 * 1000);
const timeout = setTimeout(() => {
resetState();
}, 2 * 1000);
return () => {
clearTimeout(timeout);
};
@@ -84,19 +73,24 @@ export const PinScreen = (props: PinScreenProps) => {
}, [error, resetState]);
useEffect(() => {
const validPinRegex = /^\d{4}$/;
const isValidPin = validPinRegex.test(localPinEntry);
const validateSurveyPin = async () => {
const validPinRegex = /^\d{4}$/;
const isValidPin = validPinRegex.test(localPinEntry);
if (isValidPin) {
setLoading(true);
const response = await validateSurveyPinAction({ surveyId, pin: localPinEntry });
if (response?.data) {
setSurvey(response.data.survey);
} else {
const errorMessage = getFormattedErrorMessage(response);
setError(errorMessage);
}
setLoading(false);
}
};
if (isValidPin) {
// Show loading and check against the server
setLoading(true);
_validateSurveyPinAsync(surveyId, localPinEntry);
return;
}
setError("");
setLoading(false);
}, [_validateSurveyPinAsync, localPinEntry, surveyId]);
void validateSurveyPin();
}, [localPinEntry, surveyId]);
if (!survey) {
return (
@@ -108,7 +102,9 @@ export const PinScreen = (props: PinScreenProps) => {
<OTPInput
disabled={Boolean(error) || loading}
value={localPinEntry}
onChange={(value) => setLocalPinEntry(value)}
onChange={(value) => {
setLocalPinEntry(value);
}}
valueLength={4}
inputBoxClassName={cn({ "border-red-400": Boolean(error) })}
/>

View File

@@ -49,7 +49,7 @@ export const SurveyInactive = async ({
</div>
<div>
<Link href="https://formbricks.com">
<Image src={footerLogo} alt="Brand logo" className="mx-auto w-40" />
<Image src={footerLogo as string} alt="Brand logo" className="mx-auto w-40" />
</Link>
</div>
</div>

View File

@@ -7,9 +7,9 @@ import Link from "next/link";
import { TSurveySingleUse } from "@formbricks/types/surveys/types";
import footerLogo from "../lib/footerlogo.svg";
type SurveyLinkUsedProps = {
interface SurveyLinkUsedProps {
singleUseMessage: TSurveySingleUse | null;
};
}
export const SurveyLinkUsed = ({ singleUseMessage }: SurveyLinkUsedProps) => {
const t = useTranslations();
@@ -20,16 +20,14 @@ export const SurveyLinkUsed = ({ singleUseMessage }: SurveyLinkUsedProps) => {
<div></div>
<div className="flex flex-col items-center space-y-3 text-slate-300">
<CheckCircle2Icon className="h-20 w-20" />
<h1 className="text-4xl font-bold text-slate-800">
{!!singleUseMessage?.heading ? singleUseMessage?.heading : defaultHeading}
</h1>
<h1 className="text-4xl font-bold text-slate-800">{singleUseMessage?.heading ?? defaultHeading}</h1>
<p className="text-lg leading-10 text-slate-500">
{!!singleUseMessage?.subheading ? singleUseMessage?.subheading : defaultSubheading}
{singleUseMessage?.subheading ?? defaultSubheading}
</p>
</div>
<div>
<Link href="https://formbricks.com">
<Image src={footerLogo} alt="Brand logo" className="mx-auto w-40" />
<Image src={footerLogo as string} alt="Brand logo" className="mx-auto w-40" />
</Link>
</div>
</div>

View File

@@ -41,9 +41,10 @@ export const SurveyLoadingAnimation = ({
checkMediaLoaded();
const mediaElements = document.querySelectorAll(`#${cardId} img, #${cardId} iframe`);
const handleLoad = () => checkMediaLoaded();
const handleLoad = () => {
checkMediaLoaded();
};
const handleError = () => {
console.error("Media failed to load");
setIsMediaLoaded(true);
};
@@ -73,7 +74,9 @@ export const SurveyLoadingAnimation = ({
setIsHidden(true);
}, 500);
return () => clearTimeout(hideTimer);
return () => {
clearTimeout(hideTimer);
};
} else {
setIsHidden(false);
}
@@ -121,7 +124,11 @@ export const SurveyLoadingAnimation = ({
isReadyToTransition ? "animate-surveyExit" : "animate-surveyLoading"
)}>
{isBrandingEnabled && (
<Image src={Logo} alt="Logo" className={cn("w-32 transition-all duration-1000 md:w-40")} />
<Image
src={Logo as string}
alt="Logo"
className={cn("w-32 transition-all duration-1000 md:w-40")}
/>
)}
<LoadingSpinner />
</div>

View File

@@ -1,7 +1,7 @@
import { validateSurveySingleUseId } from "@/app/lib/singleUseSurveys";
import { LinkSurvey } from "@/app/s/[surveyId]/components/LinkSurvey";
import { PinScreen } from "@/app/s/[surveyId]/components/PinScreen";
import { SurveyInactive } from "@/app/s/[surveyId]/components/SurveyInactive";
import { LinkSurvey } from "@/app/s/[surveyId]/components/link-survey";
import { PinScreen } from "@/app/s/[surveyId]/components/pin-screen";
import { SurveyInactive } from "@/app/s/[surveyId]/components/survey-inactive";
import { getMetadataForLinkSurvey } from "@/app/s/[surveyId]/metadata";
import { getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils";
import type { Metadata } from "next";

View File

@@ -16,7 +16,7 @@ import {
isSignupRoute,
isSyncWithUserIdentificationEndpoint,
isVerifyEmailRoute,
} from "@/app/middleware/endpointValidator";
} from "@/app/middleware/endpoint-validator";
import { ipAddress } from "@vercel/functions";
import { getToken } from "next-auth/jwt";
import type { NextRequest } from "next/server";