mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-20 05:49:07 -06:00
chore: vercel style guide part 3 (#4530)
This commit is contained in:
committed by
GitHub
parent
2c00c55e5d
commit
10738a7af0
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
@@ -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>
|
||||
@@ -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) })}
|
||||
/>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user