mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-23 14:40:44 -06:00
Compare commits
7 Commits
increase-l
...
feature/si
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94f68480bf | ||
|
|
53850c96db | ||
|
|
d0adbcb39b | ||
|
|
ae2cb15055 | ||
|
|
8bf1e096c0 | ||
|
|
0052dc88f0 | ||
|
|
d67d62df45 |
27
.github/workflows/labeler.yml
vendored
27
.github/workflows/labeler.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: Pull Request Labeler
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
|
||||
sync-labels: ""
|
||||
@@ -18,8 +18,9 @@ FROM node:22-alpine3.21 AS base
|
||||
FROM base AS installer
|
||||
|
||||
# Enable corepack and prepare pnpm
|
||||
RUN npm install -g corepack@latest
|
||||
RUN npm install --ignore-scripts -g corepack@latest
|
||||
RUN corepack enable
|
||||
RUN corepack prepare pnpm@9.15.0 --activate
|
||||
|
||||
# Install necessary build tools and compilers
|
||||
RUN apk update && apk add --no-cache cmake g++ gcc jq make openssl-dev python3
|
||||
@@ -59,7 +60,7 @@ COPY . .
|
||||
RUN touch apps/web/.env
|
||||
|
||||
# Install the dependencies
|
||||
RUN pnpm install
|
||||
RUN pnpm install --ignore-scripts
|
||||
|
||||
# Build the project using our secret reader script
|
||||
# This mounts the secrets only during this build step without storing them in layers
|
||||
@@ -75,8 +76,9 @@ RUN jq -r '.devDependencies.prisma' packages/database/package.json > /prisma_ver
|
||||
#
|
||||
FROM base AS runner
|
||||
|
||||
RUN npm install -g corepack@latest
|
||||
RUN npm install --ignore-scripts -g corepack@latest
|
||||
RUN corepack enable
|
||||
RUN corepack prepare pnpm@9.15.0 --activate
|
||||
|
||||
RUN apk add --no-cache curl \
|
||||
&& apk add --no-cache supercronic \
|
||||
@@ -141,12 +143,12 @@ RUN chmod -R 755 ./node_modules/@noble/hashes
|
||||
COPY --from=installer /app/node_modules/zod ./node_modules/zod
|
||||
RUN chmod -R 755 ./node_modules/zod
|
||||
|
||||
RUN npm install -g tsx typescript prisma pino-pretty
|
||||
RUN npm install --ignore-scripts -g tsx typescript prisma
|
||||
|
||||
EXPOSE 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
ENV NODE_ENV="production"
|
||||
# USER nextjs
|
||||
USER nextjs
|
||||
|
||||
# Prepare volume for uploads
|
||||
RUN mkdir -p /home/nextjs/apps/web/uploads/
|
||||
|
||||
@@ -33,7 +33,7 @@ const Loading = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 my-auto flex justify-center text-center text-sm whitespace-nowrap text-slate-500">
|
||||
<div className="col-span-2 my-auto flex justify-center whitespace-nowrap text-center text-sm text-slate-500">
|
||||
<div className="h-4 w-28 animate-pulse rounded-full bg-slate-200"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -264,7 +264,7 @@ export const MainNavigation = ({
|
||||
size="icon"
|
||||
onClick={toggleSidebar}
|
||||
className={cn(
|
||||
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:ring-0 focus:ring-transparent focus:outline-none"
|
||||
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:outline-none focus:ring-0 focus:ring-transparent"
|
||||
)}>
|
||||
{isCollapsed ? (
|
||||
<PanelLeftOpenIcon strokeWidth={1.5} />
|
||||
|
||||
@@ -118,7 +118,7 @@ const Page = async (props) => {
|
||||
<div className="relative isolate mt-8 overflow-hidden rounded-lg bg-slate-900 px-3 pt-8 shadow-2xl sm:px-8 md:pt-12 lg:flex lg:gap-x-10 lg:px-12 lg:pt-0">
|
||||
<svg
|
||||
viewBox="0 0 1024 1024"
|
||||
className="absolute top-1/2 left-1/2 -z-10 h-[64rem] w-[64rem] -translate-y-1/2 [mask-image:radial-gradient(closest-side,white,transparent)] sm:left-full sm:-ml-80 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2 lg:translate-y-0"
|
||||
className="absolute left-1/2 top-1/2 -z-10 h-[64rem] w-[64rem] -translate-y-1/2 [mask-image:radial-gradient(closest-side,white,transparent)] sm:left-full sm:-ml-80 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2 lg:translate-y-0"
|
||||
aria-hidden="true">
|
||||
<circle
|
||||
cx={512}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const ResponseTableCell = ({
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand response"
|
||||
className="hidden flex-shrink-0 cursor-pointer items-center rounded-md border border-slate-200 bg-white p-2 group-hover:flex hover:border-slate-300 focus:outline-none"
|
||||
className="hidden flex-shrink-0 cursor-pointer items-center rounded-md border border-slate-200 bg-white p-2 hover:border-slate-300 focus:outline-none group-hover:flex"
|
||||
onClick={handleCellClick}>
|
||||
<Maximize2Icon className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ConsentSummary = ({ questionSummary, survey, setFilter }: ConsentSu
|
||||
return (
|
||||
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<QuestionSummaryHeader questionSummary={questionSummary} survey={survey} />
|
||||
<div className="space-y-5 px-4 pt-4 pb-6 text-sm md:px-6 md:text-base">
|
||||
<div className="space-y-5 px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{summaryItems.map((summaryItem) => {
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -80,7 +80,7 @@ export const FileUploadSummary = ({
|
||||
return (
|
||||
<div className="relative m-2 rounded-lg bg-slate-200" key={fileUrl}>
|
||||
<a href={fileUrl} key={fileUrl} target="_blank" rel="noopener noreferrer">
|
||||
<div className="absolute top-0 right-0 m-2">
|
||||
<div className="absolute right-0 top-0 m-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-50 hover:bg-white">
|
||||
<DownloadIcon className="h-6 text-slate-500" />
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ export const MatrixQuestionSummary = ({ questionSummary, survey, setFilter }: Ma
|
||||
<table className="mx-auto border-collapse cursor-default text-left">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-4 pt-0 pb-3 font-medium text-slate-400 dark:border-slate-600 dark:text-slate-200"></th>
|
||||
<th className="p-4 pb-3 pt-0 font-medium text-slate-400 dark:border-slate-600 dark:text-slate-200"></th>
|
||||
{columns.map((column) => (
|
||||
<th key={column} className="text-center font-medium">
|
||||
<TooltipRenderer tooltipContent={getTooltipContent(column)} shouldRender={true}>
|
||||
@@ -65,7 +65,7 @@ export const MatrixQuestionSummary = ({ questionSummary, survey, setFilter }: Ma
|
||||
<tbody>
|
||||
{questionSummary.data.map(({ rowLabel, columnPercentages }, rowIndex) => (
|
||||
<tr key={rowLabel}>
|
||||
<td className="max-w-60 overflow-hidden p-4 text-ellipsis whitespace-nowrap">
|
||||
<td className="max-w-60 overflow-hidden text-ellipsis whitespace-nowrap p-4">
|
||||
<TooltipRenderer tooltipContent={getTooltipContent(rowLabel)} shouldRender={true}>
|
||||
<p className="max-w-40 overflow-hidden text-ellipsis whitespace-nowrap">{rowLabel}</p>
|
||||
</TooltipRenderer>
|
||||
|
||||
@@ -83,7 +83,7 @@ export const MultipleChoiceSummary = ({
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
<div className="space-y-5 px-4 pt-4 pb-6 text-sm md:px-6 md:text-base">
|
||||
<div className="space-y-5 px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{results.map((result, resultsIdx) => (
|
||||
<Fragment key={result.value}>
|
||||
<button
|
||||
|
||||
@@ -62,7 +62,7 @@ export const NPSSummary = ({ questionSummary, survey, setFilter }: NPSSummaryPro
|
||||
return (
|
||||
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<QuestionSummaryHeader questionSummary={questionSummary} survey={survey} />
|
||||
<div className="space-y-5 px-4 pt-4 pb-6 text-sm md:px-6 md:text-base">
|
||||
<div className="space-y-5 px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{["promoters", "passives", "detractors", "dismissed"].map((group) => (
|
||||
<button
|
||||
className="w-full cursor-pointer hover:opacity-80"
|
||||
@@ -72,7 +72,7 @@ export const NPSSummary = ({ questionSummary, survey, setFilter }: NPSSummaryPro
|
||||
className={`mb-2 flex justify-between ${group === "dismissed" ? "mb-2 border-t bg-white pt-4 text-sm md:text-base" : ""}`}>
|
||||
<div className="mr-8 flex space-x-1">
|
||||
<p
|
||||
className={`font-semibold text-slate-700 capitalize ${group === "dismissed" ? "" : "text-slate-700"}`}>
|
||||
className={`font-semibold capitalize text-slate-700 ${group === "dismissed" ? "" : "text-slate-700"}`}>
|
||||
{group}
|
||||
</p>
|
||||
<div>
|
||||
@@ -94,7 +94,7 @@ export const NPSSummary = ({ questionSummary, survey, setFilter }: NPSSummaryPro
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center pt-4 pb-4">
|
||||
<div className="flex justify-center pb-4 pt-4">
|
||||
<HalfCircle value={questionSummary.score} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ export const PictureChoiceSummary = ({ questionSummary, survey, setFilter }: Pic
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
<div className="space-y-5 px-4 pt-4 pb-6 text-sm md:px-6 md:text-base">
|
||||
<div className="space-y-5 px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{results.map((result, index) => (
|
||||
<button
|
||||
className="w-full cursor-pointer hover:opacity-80"
|
||||
|
||||
@@ -50,7 +50,7 @@ export const RatingSummary = ({ questionSummary, survey, setFilter }: RatingSumm
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="space-y-5 px-4 pt-4 pb-6 text-sm md:px-6 md:text-base">
|
||||
<div className="space-y-5 px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{questionSummary.choices.map((result) => (
|
||||
<button
|
||||
className="w-full cursor-pointer hover:opacity-80"
|
||||
|
||||
@@ -91,7 +91,7 @@ export const QuestionFilterComboBox = ({
|
||||
key={`${o}-${index}`}
|
||||
type="button"
|
||||
onClick={() => handleRemoveMultiSelect(filterComboBoxValue.filter((i) => i !== o))}
|
||||
className="flex w-30 items-center bg-slate-100 px-2 whitespace-nowrap text-slate-600">
|
||||
className="w-30 flex items-center whitespace-nowrap bg-slate-100 px-2 text-slate-600">
|
||||
{o}
|
||||
<X width={14} height={14} className="ml-2" />
|
||||
</button>
|
||||
@@ -129,7 +129,7 @@ export const QuestionFilterComboBox = ({
|
||||
<DropdownMenuTrigger
|
||||
disabled={disabled}
|
||||
className={clsx(
|
||||
"h-9 max-w-fit rounded-md rounded-r-none border-r-[1px] border-slate-300 bg-white p-2 text-sm text-slate-600 focus:ring-0 focus:outline-transparent",
|
||||
"h-9 max-w-fit rounded-md rounded-r-none border-r-[1px] border-slate-300 bg-white p-2 text-sm text-slate-600 focus:outline-transparent focus:ring-0",
|
||||
!disabled ? "cursor-pointer" : "opacity-50"
|
||||
)}>
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -164,7 +164,7 @@ export const QuestionsComboBox = ({ options, selected, onChangeValue }: Question
|
||||
value={inputValue}
|
||||
onValueChange={setInputValue}
|
||||
placeholder={t("common.search") + "..."}
|
||||
className="h-5 border-none border-transparent p-0 shadow-none ring-offset-transparent outline-0 focus:border-none focus:border-transparent focus:shadow-none focus:ring-offset-transparent focus:outline-0"
|
||||
className="h-5 border-none border-transparent p-0 shadow-none outline-0 ring-offset-transparent focus:border-none focus:border-transparent focus:shadow-none focus:outline-0 focus:ring-offset-transparent"
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
|
||||
@@ -82,7 +82,7 @@ export const AIRTABLE_CLIENT_ID = env.AIRTABLE_CLIENT_ID;
|
||||
|
||||
export const SMTP_HOST = env.SMTP_HOST;
|
||||
export const SMTP_PORT = env.SMTP_PORT;
|
||||
export const SMTP_SECURE_ENABLED = env.SMTP_SECURE_ENABLED === "1";
|
||||
export const SMTP_SECURE_ENABLED = env.SMTP_SECURE_ENABLED === "1" || env.SMTP_PORT === "465";
|
||||
export const SMTP_USER = env.SMTP_USER;
|
||||
export const SMTP_PASSWORD = env.SMTP_PASSWORD;
|
||||
export const SMTP_AUTHENTICATED = env.SMTP_AUTHENTICATED !== "0";
|
||||
|
||||
@@ -202,7 +202,7 @@ const baseSurveyProperties = {
|
||||
autoComplete: 7,
|
||||
runOnDate: null,
|
||||
closeOnDate: currentDate,
|
||||
redirectUrl: "http://github.com/formbricks/formbricks",
|
||||
redirectUrl: "https://github.com/formbricks/formbricks",
|
||||
recontactDays: 3,
|
||||
displayLimit: 3,
|
||||
welcomeCard: mockWelcomeCard,
|
||||
|
||||
@@ -22,7 +22,7 @@ export const captureTelemetry = async (eventName: string, properties = {}) => {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
api_key: "phc_SoIFUJ8b9ufDm0YOnoOxJf6PXyuHpO7N6RztxFdZTy",
|
||||
api_key: "phc_SoIFUJ8b9ufDm0YOnoOxJf6PXyuHpO7N6RztxFdZTy", // NOSONAR // This is a public API key for telemetry and not a secret
|
||||
event: eventName,
|
||||
properties: {
|
||||
distinct_id: getTelemetryId(),
|
||||
|
||||
@@ -312,7 +312,7 @@ function AttributeSegmentFilter({
|
||||
}}
|
||||
value={attrKeyValue}>
|
||||
<SelectTrigger
|
||||
className="flex w-auto items-center justify-center bg-white whitespace-nowrap capitalize"
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white capitalize"
|
||||
hideArrow>
|
||||
<SelectValue>
|
||||
<div className={cn("flex items-center gap-2", !isCapitalized(attrKeyValue ?? "") && "lowercase")}>
|
||||
@@ -494,7 +494,7 @@ function PersonSegmentFilter({
|
||||
}}
|
||||
value={personIdentifier}>
|
||||
<SelectTrigger
|
||||
className="flex w-auto items-center justify-center bg-white whitespace-nowrap capitalize"
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white capitalize"
|
||||
hideArrow>
|
||||
<SelectValue>
|
||||
<div className="flex items-center gap-1 lowercase">
|
||||
@@ -643,7 +643,7 @@ function SegmentSegmentFilter({
|
||||
}}
|
||||
value={currentSegment?.id}>
|
||||
<SelectTrigger
|
||||
className="flex w-auto items-center justify-center bg-white whitespace-nowrap capitalize"
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white capitalize"
|
||||
hideArrow>
|
||||
<div className="flex items-center gap-1">
|
||||
<Users2Icon className="h-4 w-4 text-sm" />
|
||||
|
||||
@@ -207,7 +207,7 @@ export const TeamSettingsModal = ({
|
||||
<div className="sticky top-0 flex h-full flex-col rounded-lg">
|
||||
<button
|
||||
className={cn(
|
||||
"absolute top-0 right-0 hidden pt-4 pr-4 text-slate-400 hover:text-slate-500 focus:ring-0 focus:outline-none sm:block"
|
||||
"absolute right-0 top-0 hidden pr-4 pt-4 text-slate-400 hover:text-slate-500 focus:outline-none focus:ring-0 sm:block"
|
||||
)}
|
||||
onClick={closeSettingsModal}>
|
||||
<XIcon className="h-6 w-6 rounded-md bg-white" />
|
||||
|
||||
@@ -10,7 +10,7 @@ const LoadingCard = () => {
|
||||
return (
|
||||
<div className="w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 shadow-sm">
|
||||
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
|
||||
<h3 className="h-6 w-full max-w-56 animate-pulse rounded-lg bg-slate-100 text-lg leading-6 font-medium">
|
||||
<h3 className="h-6 w-full max-w-56 animate-pulse rounded-lg bg-slate-100 text-lg font-medium leading-6">
|
||||
<span className="sr-only">{t("common.loading")}</span>
|
||||
</h3>
|
||||
<p className="mt-3 h-4 w-full max-w-80 animate-pulse rounded-lg bg-slate-100 text-sm text-slate-500">
|
||||
|
||||
@@ -105,7 +105,7 @@ export const TemplateList = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="relative z-0 flex-1 overflow-y-auto px-6 pt-2 pb-6 focus:outline-none">
|
||||
<main className="relative z-0 flex-1 overflow-y-auto px-6 pb-6 pt-2 focus:outline-none">
|
||||
{showFilters && !templateSearch && (
|
||||
<TemplateFilters
|
||||
selectedFilter={selectedFilter}
|
||||
|
||||
@@ -88,7 +88,7 @@ export const AnimatedSurveyBg = ({ handleBgChange, background }: AnimatedSurveyB
|
||||
<source src={`${key}`} type="video/mp4" />
|
||||
</video>
|
||||
<input
|
||||
className="absolute top-2 right-2 h-4 w-4 rounded-sm bg-white"
|
||||
className="absolute right-2 top-2 h-4 w-4 rounded-sm bg-white"
|
||||
type="checkbox"
|
||||
checked={animation === value}
|
||||
onChange={() => handleBg(value)}
|
||||
|
||||
@@ -67,7 +67,7 @@ export const EditWelcomeCard = ({
|
||||
<div
|
||||
className={cn(
|
||||
open ? "bg-slate-50" : "",
|
||||
"flex w-10 items-center justify-center rounded-l-lg border-t border-b border-l group-aria-expanded:rounded-bl-none",
|
||||
"flex w-10 items-center justify-center rounded-l-lg border-b border-l border-t group-aria-expanded:rounded-bl-none",
|
||||
isInvalid ? "bg-red-400" : "bg-white group-hover:bg-slate-50"
|
||||
)}>
|
||||
<Hand className="h-4 w-4" />
|
||||
|
||||
@@ -178,7 +178,7 @@ export const FileUploadQuestionForm = ({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 mb-8 space-y-6">
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<AdvancedOptionToggle
|
||||
isChecked={question.allowMultipleFiles}
|
||||
onToggle={() => updateQuestion(questionIdx, { allowMultipleFiles: !question.allowMultipleFiles })}
|
||||
@@ -218,7 +218,7 @@ export const FileUploadQuestionForm = ({
|
||||
|
||||
updateQuestion(questionIdx, { maxSizeInMB: parseInt(e.target.value, 10) });
|
||||
}}
|
||||
className="mr-2 ml-2 inline w-20 bg-white text-center text-sm"
|
||||
className="ml-2 mr-2 inline w-20 bg-white text-center text-sm"
|
||||
/>
|
||||
MB
|
||||
</p>
|
||||
|
||||
@@ -113,7 +113,7 @@ export const HiddenFieldsCard = ({
|
||||
<div
|
||||
className={cn(
|
||||
open ? "bg-slate-50" : "bg-white group-hover:bg-slate-50",
|
||||
"flex w-10 items-center justify-center rounded-l-lg border-t border-b border-l group-aria-expanded:rounded-bl-none"
|
||||
"flex w-10 items-center justify-center rounded-l-lg border-b border-l border-t group-aria-expanded:rounded-bl-none"
|
||||
)}>
|
||||
<EyeOff className="h-4 w-4" />
|
||||
</div>
|
||||
@@ -161,7 +161,7 @@ export const HiddenFieldsCard = ({
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<p className="mt-2 text-sm text-slate-500 italic">
|
||||
<p className="mt-2 text-sm italic text-slate-500">
|
||||
{t("environments.surveys.edit.no_hidden_fields_yet_add_first_one_below")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -106,7 +106,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
|
||||
className="h-full w-full cursor-pointer"
|
||||
id="howToSendCardTrigger">
|
||||
<div className="inline-flex px-4 py-4">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<div className="flex items-center pl-2 pr-5">
|
||||
<CheckIcon
|
||||
strokeWidth={3}
|
||||
className="h-7 w-7 rounded-full border border-green-300 bg-green-100 p-1.5 text-green-600"
|
||||
|
||||
@@ -232,7 +232,7 @@ const getPlaceholderByInputType = (inputType: TSurveyOpenTextQuestionInputType)
|
||||
case "email":
|
||||
return "example@email.com";
|
||||
case "url":
|
||||
return "http://...";
|
||||
return "https://...";
|
||||
case "number":
|
||||
return "42";
|
||||
case "phone":
|
||||
|
||||
@@ -196,7 +196,7 @@ export const QuestionCard = ({
|
||||
)}>
|
||||
<div className="mt-3 flex w-full justify-center">{QUESTIONS_ICON_MAP[question.type]}</div>
|
||||
|
||||
<button className="opacity-0 group-hover:opacity-100 hover:cursor-move">
|
||||
<button className="opacity-0 hover:cursor-move group-hover:opacity-100">
|
||||
<GripIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -121,7 +121,7 @@ export const RecontactOptionsCard = ({
|
||||
className="h-full w-full cursor-pointer rounded-lg hover:bg-slate-50"
|
||||
id="recontactOptionsCardTrigger">
|
||||
<div className="inline-flex px-4 py-4">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<div className="flex items-center pl-2 pr-5">
|
||||
<CheckIcon
|
||||
strokeWidth={3}
|
||||
className="h-7 w-7 rounded-full border border-green-300 bg-green-100 p-1.5 text-green-600"
|
||||
@@ -256,7 +256,7 @@ export const RecontactOptionsCard = ({
|
||||
id="inputDays"
|
||||
value={inputDays === 0 ? 1 : inputDays}
|
||||
onChange={handleRecontactDaysChange}
|
||||
className="mr-2 ml-2 inline w-16 bg-white text-center text-sm"
|
||||
className="ml-2 mr-2 inline w-16 bg-white text-center text-sm"
|
||||
/>
|
||||
{t("environments.surveys.edit.days_before_showing_this_survey_again")}.
|
||||
</p>
|
||||
|
||||
@@ -318,7 +318,7 @@ export const ResponseOptionsCard = ({
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-4">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<div className="flex items-center pl-2 pr-5">
|
||||
<CheckIcon
|
||||
strokeWidth={3}
|
||||
className="h-7 w-7 rounded-full border border-green-300 bg-green-100 p-1.5 text-green-600"
|
||||
@@ -356,7 +356,7 @@ export const ResponseOptionsCard = ({
|
||||
value={localSurvey.autoComplete?.toString()}
|
||||
onChange={handleInputResponse}
|
||||
onBlur={handleInputResponseBlur}
|
||||
className="mr-2 ml-2 inline w-20 bg-white text-center text-sm"
|
||||
className="ml-2 mr-2 inline w-20 bg-white text-center text-sm"
|
||||
/>
|
||||
{t("environments.surveys.edit.completed_responses")}
|
||||
</p>
|
||||
@@ -451,7 +451,7 @@ export const ResponseOptionsCard = ({
|
||||
<Input
|
||||
autoFocus
|
||||
id="heading"
|
||||
className="mt-2 mb-4 bg-white"
|
||||
className="mb-4 mt-2 bg-white"
|
||||
name="heading"
|
||||
defaultValue={surveyClosedMessage.heading}
|
||||
onChange={(e) => handleClosedSurveyMessageChange({ heading: e.target.value })}
|
||||
@@ -506,7 +506,7 @@ export const ResponseOptionsCard = ({
|
||||
<Input
|
||||
autoFocus
|
||||
id="heading"
|
||||
className="mt-2 mb-4 bg-white"
|
||||
className="mb-4 mt-2 bg-white"
|
||||
name="heading"
|
||||
value={singleUseMessage.heading}
|
||||
onChange={(e) => handleSingleUseSurveyMessageChange({ heading: e.target.value })}
|
||||
@@ -514,7 +514,7 @@ export const ResponseOptionsCard = ({
|
||||
|
||||
<Label htmlFor="headline">{t("environments.surveys.edit.subheading")}</Label>
|
||||
<Input
|
||||
className="mt-2 mb-4 bg-white"
|
||||
className="mb-4 mt-2 bg-white"
|
||||
id="subheading"
|
||||
name="subheading"
|
||||
value={singleUseMessage.subheading}
|
||||
|
||||
@@ -64,7 +64,7 @@ export const SavedActionsTab = ({
|
||||
(actions, i) =>
|
||||
actions.length > 0 && (
|
||||
<div key={i} className="me-4">
|
||||
<h2 className="mt-4 mb-2 font-semibold">
|
||||
<h2 className="mb-2 mt-4 font-semibold">
|
||||
{i === 0 ? t("common.no_code") : t("common.code")}
|
||||
</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -329,7 +329,7 @@ export const SurveyMenuBar = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center gap-2 sm:mt-0 sm:ml-4">
|
||||
<div className="mt-3 flex items-center gap-2 sm:ml-4 sm:mt-0">
|
||||
{responseCount > 0 && (
|
||||
<div>
|
||||
<Alert variant="warning" size="small">
|
||||
|
||||
@@ -91,7 +91,7 @@ export const SurveyPlacementCard = ({
|
||||
asChild
|
||||
className="h-full w-full cursor-pointer rounded-lg hover:bg-slate-50">
|
||||
<div className="inline-flex px-4 py-4">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<div className="flex items-center pl-2 pr-5">
|
||||
<CheckIcon
|
||||
strokeWidth={3}
|
||||
className="h-7 w-7 rounded-full border border-green-300 bg-green-100 p-1.5 text-green-600"
|
||||
|
||||
@@ -41,7 +41,7 @@ export const SurveyVariablesCard = ({
|
||||
<div
|
||||
className={cn(
|
||||
open ? "bg-slate-50" : "bg-white group-hover:bg-slate-50",
|
||||
"flex w-10 items-center justify-center rounded-l-lg border-t border-b border-l group-aria-expanded:rounded-bl-none"
|
||||
"flex w-10 items-center justify-center rounded-l-lg border-b border-l border-t group-aria-expanded:rounded-bl-none"
|
||||
)}>
|
||||
<div className="flex w-full justify-center">
|
||||
<FileDigitIcon className="h-4 w-4" />
|
||||
@@ -75,7 +75,7 @@ export const SurveyVariablesCard = ({
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<p className="mt-2 text-sm text-slate-500 italic">
|
||||
<p className="mt-2 text-sm italic text-slate-500">
|
||||
{t("environments.surveys.edit.no_variables_yet_add_first_one_below")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const TargetingLockedCard = ({ isFormbricksCloud, environmentId }: Target
|
||||
asChild
|
||||
className="h-full w-full cursor-pointer rounded-lg hover:bg-slate-50">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<div className="flex items-center pl-2 pr-5">
|
||||
<div className="rounded-full border border-slate-300 bg-slate-100 p-1">
|
||||
<LockIcon className="h-4 w-4 text-slate-500" strokeWidth={3} />
|
||||
</div>
|
||||
|
||||
@@ -192,7 +192,7 @@ export const ImageFromUnsplashSurveyBg = ({ handleBgChange }: ImageFromUnsplashS
|
||||
return (
|
||||
<div className="relative mt-2 w-full">
|
||||
<div className="relative">
|
||||
<SearchIcon className="absolute top-1/2 left-2 h-6 w-4 -translate-y-1/2 text-slate-500" />
|
||||
<SearchIcon className="absolute left-2 top-1/2 h-6 w-4 -translate-y-1/2 text-slate-500" />
|
||||
<Input
|
||||
value={query}
|
||||
onChange={handleChange}
|
||||
@@ -215,7 +215,7 @@ export const ImageFromUnsplashSurveyBg = ({ handleBgChange }: ImageFromUnsplashS
|
||||
className="h-full cursor-pointer rounded-lg object-cover"
|
||||
/>
|
||||
{image.authorName && (
|
||||
<span className="bg-opacity-75 absolute right-1 bottom-1 hidden rounded bg-black px-2 py-1 text-xs text-white group-hover:block">
|
||||
<span className="absolute bottom-1 right-1 hidden rounded bg-black bg-opacity-75 px-2 py-1 text-xs text-white group-hover:block">
|
||||
{image.authorName}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -75,7 +75,7 @@ const FollowUpActionMultiEmailInput = ({
|
||||
<span className="text-slate-900">{email}</span>
|
||||
<button
|
||||
onClick={() => removeEmail(index)}
|
||||
className="px-1 text-lg leading-none font-medium text-slate-500">
|
||||
className="px-1 text-lg font-medium leading-none text-slate-500">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -98,11 +98,11 @@ export const CopySurveyForm = ({ defaultProjects, survey, onCancel, setOpen }: I
|
||||
field.onChange([...field.value, environment.id]);
|
||||
}
|
||||
}}
|
||||
className="focus:ring-opacity-50 mr-2 h-4 w-4 appearance-none border-slate-300 checked:border-transparent checked:bg-slate-500 checked:after:bg-slate-500 checked:hover:bg-slate-500 focus:ring-2 focus:ring-slate-500"
|
||||
className="mr-2 h-4 w-4 appearance-none border-slate-300 checked:border-transparent checked:bg-slate-500 checked:after:bg-slate-500 checked:hover:bg-slate-500 focus:ring-2 focus:ring-slate-500 focus:ring-opacity-50"
|
||||
id={environment.id}
|
||||
/>
|
||||
<Label htmlFor={environment.id}>
|
||||
<p className="text-sm font-medium text-slate-900 capitalize">
|
||||
<p className="text-sm font-medium capitalize text-slate-900">
|
||||
{environment.type}
|
||||
</p>
|
||||
</Label>
|
||||
@@ -121,8 +121,8 @@ export const CopySurveyForm = ({ defaultProjects, survey, onCancel, setOpen }: I
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="fixed right-0 bottom-0 left-0 z-10 flex w-full justify-end space-x-2 bg-white">
|
||||
<div className="flex w-full justify-end pr-4 pb-4">
|
||||
<div className="fixed bottom-0 left-0 right-0 z-10 flex w-full justify-end space-x-2 bg-white">
|
||||
<div className="flex w-full justify-end pb-4 pr-4">
|
||||
<Button type="button" onClick={onCancel} variant="ghost">
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
|
||||
@@ -32,7 +32,7 @@ vi.mock("@/modules/ui/components/checkbox", () => ({
|
||||
id={id}
|
||||
data-testid={id}
|
||||
name={props.name}
|
||||
className="focus:ring-opacity-50 mr-2 h-4 w-4 appearance-none border-slate-300 checked:border-transparent checked:bg-slate-500 checked:after:bg-slate-500 checked:hover:bg-slate-500 focus:ring-2 focus:ring-slate-500"
|
||||
className="mr-2 h-4 w-4 appearance-none border-slate-300 checked:border-transparent checked:bg-slate-500 checked:after:bg-slate-500 checked:hover:bg-slate-500 focus:ring-2 focus:ring-slate-500 focus:ring-opacity-50"
|
||||
onChange={() => {
|
||||
// Call onCheckedChange with true to simulate checkbox selection
|
||||
onCheckedChange(true);
|
||||
|
||||
@@ -37,7 +37,7 @@ export const TemplateContainerWithPreview = ({
|
||||
<MenuBar />
|
||||
<div className="relative z-0 flex flex-1 overflow-hidden">
|
||||
<div className="flex-1 flex-col overflow-auto bg-slate-50">
|
||||
<div className="mt-6 mb-3 ml-6 flex flex-col items-center justify-between md:flex-row md:items-end">
|
||||
<div className="mb-3 ml-6 mt-6 flex flex-col items-center justify-between md:flex-row md:items-end">
|
||||
<h1 className="text-2xl font-bold text-slate-800">
|
||||
{t("environments.surveys.templates.create_a_new_survey")}
|
||||
</h1>
|
||||
|
||||
@@ -57,7 +57,7 @@ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HT
|
||||
return (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn("text-2xl leading-none font-semibold tracking-tight", className)}
|
||||
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...props}>
|
||||
{headingContent}
|
||||
</h3>
|
||||
|
||||
@@ -13,7 +13,7 @@ const DialogOverlay = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
blur && "backdrop-blur-md",
|
||||
"bg-opacity-30 fixed inset-0 z-50",
|
||||
"fixed inset-0 z-50 bg-opacity-30",
|
||||
"data-[state='closed']:animate-fadeOut data-[state='open']:animate-fadeIn"
|
||||
)}
|
||||
{...props}
|
||||
@@ -58,8 +58,8 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-[50%] left-[50%] z-50 translate-x-[-50%] translate-y-[-50%] transform rounded-lg bg-white text-left shadow-xl transition-all sm:my-2 sm:w-full sm:max-w-xl",
|
||||
`${noPadding ? "" : "px-4 pt-5 pb-4 sm:p-6"}`,
|
||||
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%] transform rounded-lg bg-white text-left shadow-xl transition-all sm:my-2 sm:w-full sm:max-w-xl",
|
||||
`${noPadding ? "" : "px-4 pb-4 pt-5 sm:p-6"}`,
|
||||
"data-[state='closed']:animate-fadeOut data-[state='open']:animate-fadeIn",
|
||||
size && sizeClassName && sizeClassName[size],
|
||||
!restrictOverflow && "overflow-hidden",
|
||||
@@ -78,7 +78,7 @@ const DialogContent = React.forwardRef<
|
||||
{children}
|
||||
<DialogPrimitive.Close
|
||||
className={cn(
|
||||
"absolute top-0 right-0 hidden pt-4 pr-4 text-slate-400 hover:text-slate-500 focus:ring-0 focus:outline-none sm:block",
|
||||
"absolute right-0 top-0 hidden pr-4 pt-4 text-slate-400 hover:text-slate-500 focus:outline-none focus:ring-0 sm:block",
|
||||
hideCloseButton && "!hidden"
|
||||
)}>
|
||||
<XIcon className="h-6 w-6 rounded-md bg-white" />
|
||||
|
||||
@@ -246,10 +246,10 @@ export const PreviewSurvey = ({
|
||||
className="relative flex h-full w-[95%] items-center justify-center rounded-lg border border-slate-300 bg-slate-200">
|
||||
{previewMode === "mobile" && (
|
||||
<>
|
||||
<p className="absolute top-0 left-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
|
||||
<p className="absolute left-0 top-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
|
||||
Preview
|
||||
</p>
|
||||
<div className="absolute top-0 right-0 m-2">
|
||||
<div className="absolute right-0 top-0 m-2">
|
||||
<ResetProgressButton onClick={resetQuestionProgress} />
|
||||
</div>
|
||||
<MediaBackground
|
||||
@@ -284,7 +284,7 @@ export const PreviewSurvey = ({
|
||||
</Modal>
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col justify-center px-1">
|
||||
<div className="absolute top-5 left-5">
|
||||
<div className="absolute left-5 top-5">
|
||||
{!styling.isLogoHidden && (
|
||||
<ClientLogo environmentId={environment.id} projectLogo={project.logo} previewSurvey />
|
||||
)}
|
||||
@@ -392,7 +392,7 @@ export const PreviewSurvey = ({
|
||||
styling={styling}
|
||||
ContentRef={ContentRef as React.RefObject<HTMLDivElement>}
|
||||
isEditorView>
|
||||
<div className="absolute top-5 left-5">
|
||||
<div className="absolute left-5 top-5">
|
||||
{!styling.isLogoHidden && (
|
||||
<ClientLogo environmentId={environment.id} projectLogo={project.logo} previewSurvey />
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@9.15.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next coverage",
|
||||
@@ -73,15 +74,12 @@
|
||||
"@tailwindcss/forms": "0.5.10",
|
||||
"@tailwindcss/typography": "0.5.16",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@tolgee/cli": "2.10.2",
|
||||
"@tolgee/format-icu": "6.2.4",
|
||||
"@tolgee/react": "6.2.4",
|
||||
"@ungap/structured-clone": "1.3.0",
|
||||
"@unkey/ratelimit": "0.5.5",
|
||||
"@vercel/functions": "2.0.1",
|
||||
"@vercel/og": "0.6.8",
|
||||
"autoprefixer": "10.4.21",
|
||||
"bcryptjs": "3.0.2",
|
||||
"boring-avatars": "1.11.2",
|
||||
"class-variance-authority": "0.7.1",
|
||||
@@ -89,7 +87,6 @@
|
||||
"cmdk": "1.1.1",
|
||||
"csv-parse": "5.6.0",
|
||||
"date-fns": "4.1.0",
|
||||
"dotenv": "16.5.0",
|
||||
"file-loader": "6.2.0",
|
||||
"framer-motion": "12.9.7",
|
||||
"googleapis": "148.0.0",
|
||||
@@ -111,7 +108,6 @@
|
||||
"nodemailer": "7.0.2",
|
||||
"otplib": "12.0.1",
|
||||
"papaparse": "5.5.2",
|
||||
"postcss": "8.5.3",
|
||||
"posthog-js": "1.239.1",
|
||||
"posthog-node": "4.17.1",
|
||||
"prismjs": "1.30.0",
|
||||
@@ -137,14 +133,16 @@
|
||||
"uuid": "11.1.0",
|
||||
"webpack": "5.99.7",
|
||||
"xlsx": "0.18.5",
|
||||
"zod": "3.24.1",
|
||||
"zod": "3.24.4",
|
||||
"zod-openapi": "4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@neshca/cache-handler": "1.9.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@tolgee/cli": "2.10.2",
|
||||
"@types/bcryptjs": "2.4.6",
|
||||
"@types/heic-convert": "2.1.0",
|
||||
"@types/jsonwebtoken": "9.0.9",
|
||||
@@ -157,7 +155,9 @@
|
||||
"@types/testing-library__react": "10.2.0",
|
||||
"@types/ungap__structured-clone": "1.2.0",
|
||||
"@vitest/coverage-v8": "3.1.3",
|
||||
"autoprefixer": "10.4.21",
|
||||
"dotenv": "16.5.0",
|
||||
"postcss": "8.5.3",
|
||||
"ts-node": "10.9.2",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"vite": "6.3.5",
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "15.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.29.1",
|
||||
"@typescript-eslint/parser": "8.29.1",
|
||||
"@next/eslint-plugin-next": "15.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.32.0",
|
||||
"@typescript-eslint/parser": "8.32.0",
|
||||
"@vercel/style-guide": "6.0.0",
|
||||
"eslint-config-next": "15.3.0",
|
||||
"eslint-config-prettier": "10.1.1",
|
||||
"eslint-config-turbo": "2.5.0",
|
||||
"eslint-config-next": "15.3.1",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint-config-turbo": "2.5.2",
|
||||
"eslint-plugin-i18n-json": "4.0.1",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"eslint-plugin-react-refresh": "0.4.19",
|
||||
"@vitest/eslint-plugin": "1.1.42"
|
||||
"eslint-plugin-react-refresh": "0.4.20",
|
||||
"@vitest/eslint-plugin": "1.1.44"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"clean": "rimraf node_modules dist turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.15.3",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/node": "22.15.12",
|
||||
"@types/react": "19.1.3",
|
||||
"@types/react-dom": "19.1.3",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/database",
|
||||
"packageManager": "pnpm@9.15.0",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"main": "./src/index.ts",
|
||||
@@ -24,18 +25,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/logger": "workspace:*",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"@prisma/client": "6.7.0",
|
||||
"@prisma/extension-accelerate": "1.3.0",
|
||||
"dotenv-cli": "8.0.0",
|
||||
"zod-openapi": "4.2.4"
|
||||
"zod-openapi": "4.2.4",
|
||||
"zod": "3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"dotenv-cli": "8.0.0",
|
||||
"prisma": "6.7.0",
|
||||
"prisma-json-types-generator": "3.2.3",
|
||||
"ts-node": "10.9.2",
|
||||
"zod": "3.24.1"
|
||||
"prisma-json-types-generator": "3.3.1",
|
||||
"ts-node": "10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json",
|
||||
"build": "tsc && vite build"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"vite": "6.3.5",
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
|
||||
@@ -399,16 +399,27 @@ describe("utils.ts", () => {
|
||||
// ---------------------------------------------------------------------------------
|
||||
describe("shouldDisplayBasedOnPercentage()", () => {
|
||||
test("returns true if random number <= displayPercentage", () => {
|
||||
// We'll mock Math.random to return something
|
||||
const mockedRandom = vi.spyOn(Math, "random").mockReturnValue(0.2); // 0.2 => 20%
|
||||
// displayPercentage = 30 => 30% => we should display
|
||||
const mockGetRandomValues = vi
|
||||
.spyOn(crypto, "getRandomValues")
|
||||
.mockImplementation(<T extends ArrayBufferView | null>(array: T): T => {
|
||||
if (array instanceof Uint32Array) {
|
||||
array[0] = Math.floor((20 / 100) * 2 ** 32);
|
||||
return array;
|
||||
}
|
||||
return array;
|
||||
});
|
||||
expect(shouldDisplayBasedOnPercentage(30)).toBe(true);
|
||||
|
||||
mockedRandom.mockReturnValue(0.5); // 50%
|
||||
mockGetRandomValues.mockImplementation(<T extends ArrayBufferView | null>(array: T): T => {
|
||||
if (array instanceof Uint32Array) {
|
||||
array[0] = Math.floor((80 / 100) * 2 ** 32);
|
||||
return array;
|
||||
}
|
||||
return array;
|
||||
});
|
||||
expect(shouldDisplayBasedOnPercentage(30)).toBe(false);
|
||||
|
||||
// restore
|
||||
mockedRandom.mockRestore();
|
||||
mockGetRandomValues.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -183,8 +183,14 @@ export const getLanguageCode = (survey: TEnvironmentStateSurvey, language?: stri
|
||||
return selectedLanguage.language.code;
|
||||
};
|
||||
|
||||
export const getSecureRandom = (): number => {
|
||||
const u32 = new Uint32Array(1);
|
||||
crypto.getRandomValues(u32);
|
||||
return u32[0] / 2 ** 32; // Normalized to [0, 1)
|
||||
};
|
||||
|
||||
export const shouldDisplayBasedOnPercentage = (displayPercentage: number): boolean => {
|
||||
const randomNum = Math.floor(Math.random() * 10000) / 100; // NOSONAR typescript:S2245 // Math.random() is not used in a security context
|
||||
const randomNum = Math.floor(getSecureRandom() * 10000) / 100;
|
||||
return randomNum <= displayPercentage;
|
||||
};
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
},
|
||||
"author": "Formbricks <hola@formbricks.com>",
|
||||
"dependencies": {
|
||||
"zod": "3.24.2",
|
||||
"pino": "^9.6.0",
|
||||
"pino-pretty": "^13.0.0"
|
||||
"zod": "3.24.4",
|
||||
"pino": "9.6.0",
|
||||
"pino-pretty": "13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "6.3.5",
|
||||
|
||||
@@ -37,30 +37,30 @@
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/embed-snippet": "1.3.3",
|
||||
"@formkit/auto-animate": "0.8.2",
|
||||
"isomorphic-dompurify": "2.24.0",
|
||||
"preact": "10.26.5",
|
||||
"react-date-picker": "11.0.0",
|
||||
"react-calendar": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/embed-snippet": "1.3.2",
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@formbricks/i18n-utils": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@preact/preset-vite": "2.10.1",
|
||||
"@testing-library/preact": "3.2.4",
|
||||
"@types/react": "19.1.0",
|
||||
"@types/react": "19.1.3",
|
||||
"autoprefixer": "10.4.21",
|
||||
"concurrently": "9.1.2",
|
||||
"isomorphic-dompurify": "2.23.0",
|
||||
"postcss": "8.5.3",
|
||||
"preact": "10.26.5",
|
||||
"react-date-picker": "11.0.0",
|
||||
"serve": "14.2.4",
|
||||
"tailwindcss": "3.4.16",
|
||||
"terser": "5.39.0",
|
||||
"vite": "6.3.5",
|
||||
"vite-plugin-dts": "4.5.3",
|
||||
"vite-tsconfig-paths": "5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "0.8.2",
|
||||
"react-calendar": "5.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function LanguageSwitch({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fb-z-[1001] fb-flex fb-w-fit fb-items-center fb-mr-1">
|
||||
<div className="fb-z-[1001] fb-flex fb-w-fit fb-items-center fb-pr-1">
|
||||
<button
|
||||
title="Language switch"
|
||||
type="button"
|
||||
|
||||
@@ -15,9 +15,15 @@ export const cn = (...classes: string[]) => {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
};
|
||||
|
||||
export const getSecureRandom = (): number => {
|
||||
const u32 = new Uint32Array(1);
|
||||
crypto.getRandomValues(u32);
|
||||
return u32[0] / 2 ** 32; // Normalized to [0, 1)
|
||||
};
|
||||
|
||||
const shuffle = (array: unknown[]) => {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const j = Math.floor(Math.random() * (i + 1)); // NOSONAR typescript:S2245 // Math.random() is not used in a security context
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(getSecureRandom() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
|
||||
"clean": "rimraf node_modules .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "6.7.0",
|
||||
"zod": "3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/database": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "6.7.0",
|
||||
"zod": "3.24.1"
|
||||
}
|
||||
}
|
||||
|
||||
1518
pnpm-lock.yaml
generated
1518
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user