Merge branch 'main' into surveyBg

This commit is contained in:
Anjy Gupta
2023-11-15 19:05:21 +00:00
55 changed files with 514 additions and 361 deletions

View File

@@ -1,3 +1,26 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Example on overriding packages/js colors */
.dark {
--fb-brand-color: red;
--fb-brand-text-color: white;
--fb-border-color: green;
--fb-border-color-highlight: var(--slate-500);
--fb-focus-color: red;
--fb-heading-color: yellow;
--fb-subheading-color: green;
--fb-info-text-color: orange;
--fb-signature-text-color: blue;
--fb-survey-background-color: black;
--fb-accent-background-color: rgb(13, 13, 12);
--fb-accent-background-color-selected: red;
--fb-placeholder-color: white;
--fb-shadow-color: yellow;
--fb-rating-fill: var(--yellow-300);
--fb-rating-hover: var(--yellow-500);
--fb-back-btn-border: currentColor;
--fb-submit-btn-border: transparent;
--fb-rating-selected: black;
}

View File

@@ -0,0 +1,83 @@
"use client";
import { TProduct, TProductUpdateInput } from "@formbricks/types/product";
import { Alert, AlertDescription } from "@formbricks/ui/Alert";
import { Label } from "@formbricks/ui/Label";
import { Switch } from "@formbricks/ui/Switch";
import Link from "next/link";
import { useState } from "react";
import toast from "react-hot-toast";
import { updateProductAction } from "../actions";
interface EditFormbricksBrandingProps {
type: "linkSurvey" | "inAppSurvey";
product: TProduct;
canRemoveBranding: boolean;
environmentId: string;
}
export function EditFormbricksBranding({
type,
product,
canRemoveBranding,
environmentId,
}: EditFormbricksBrandingProps) {
const [isBrandingEnabled, setIsBrandingEnabled] = useState(
type === "linkSurvey" ? product.linkSurveyBranding : product.inAppSurveyBranding
);
const [updatingBranding, setUpdatingBranding] = useState(false);
const toggleBranding = async () => {
try {
setUpdatingBranding(true);
const newBrandingState = !isBrandingEnabled;
setIsBrandingEnabled(newBrandingState);
let inputProduct: Partial<TProductUpdateInput> = {
[type === "linkSurvey" ? "linkSurveyBranding" : "inAppSurveyBranding"]: newBrandingState,
};
await updateProductAction(product.id, inputProduct);
toast.success(
newBrandingState ? "Formbricks branding will be shown." : "Formbricks branding will now be hidden."
);
} catch (error) {
toast.error(`Error: ${error.message}`);
} finally {
setUpdatingBranding(false);
}
};
return (
<div className="w-full items-center">
{!canRemoveBranding && (
<div className="mb-4">
<Alert>
<AlertDescription>
To remove the Formbricks branding from the <span className="font-semibold">{type} surveys</span>
, please{" "}
{type === "linkSurvey" ? (
<span className="underline">
<Link href={`/environments/${environmentId}/settings/billing`}>upgrade your plan.</Link>
</span>
) : (
<span className="underline">
<Link href={`/environments/${environmentId}/settings/billing`}>add your creditcard.</Link>
</span>
)}
</AlertDescription>
</Alert>
</div>
)}
<div className="mb-6 flex items-center space-x-2">
<Switch
id={`branding-${type}`}
checked={isBrandingEnabled}
onCheckedChange={toggleBranding}
disabled={!canRemoveBranding || updatingBranding}
/>
<Label htmlFor={`branding-${type}`}>
Show Formbricks Branding in {type === "linkSurvey" ? "Link" : "In-App"} Surveys
</Label>
</div>
</div>
);
}

View File

@@ -1,67 +0,0 @@
"use client";
import { Alert, AlertDescription } from "@formbricks/ui/Alert";
import { updateProductAction } from "../actions";
import { TProduct, TProductUpdateInput } from "@formbricks/types/product";
import { Label } from "@formbricks/ui/Label";
import { Switch } from "@formbricks/ui/Switch";
import { useState } from "react";
import toast from "react-hot-toast";
import Link from "next/link";
interface EditSignatureProps {
product: TProduct;
canRemoveSignature: boolean;
environmentId: string;
}
export function EditFormbricksSignature({ product, canRemoveSignature, environmentId }: EditSignatureProps) {
const [formbricksSignature, setFormbricksSignature] = useState(product.formbricksSignature);
const [updatingSignature, setUpdatingSignature] = useState(false);
const toggleSignature = async () => {
try {
setUpdatingSignature(true);
const newSignatureState = !formbricksSignature;
setFormbricksSignature(newSignatureState);
let inputProduct: Partial<TProductUpdateInput> = {
formbricksSignature: newSignatureState,
};
await updateProductAction(product.id, inputProduct);
toast.success(
newSignatureState ? "Formbricks signature will be shown." : "Formbricks signature will now be hidden."
);
} catch (error) {
toast.error(`Error: ${error.message}`);
} finally {
setUpdatingSignature(false);
}
};
return (
<div className="w-full items-center">
{!canRemoveSignature && (
<div className="mb-4">
<Alert>
<AlertDescription>
To remove the Formbricks branding from the link surveys, please{" "}
<span className="underline">
<Link href={`/environments/${environmentId}/settings/billing`}>upgrade</Link>
</span>{" "}
your plan.
</AlertDescription>
</Alert>
</div>
)}
<div className="flex items-center space-x-2">
<Switch
id="signature"
checked={formbricksSignature}
onCheckedChange={toggleSignature}
disabled={!canRemoveSignature || updatingSignature}
/>
<Label htmlFor="signature">Show &apos;Powered by Formbricks&apos; Signature in Link Surveys</Label>
</div>
</div>
);
}

View File

@@ -1,20 +1,19 @@
export const revalidate = REVALIDATION_INTERVAL;
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import SettingsCard from "../components/SettingsCard";
import SettingsTitle from "../components/SettingsTitle";
import { EditFormbricksSignature } from "./components/EditSignature";
import { EditBrandColor } from "./components/EditBrandColor";
import { EditPlacement } from "./components/EditPlacement";
import { EditHighlightBorder } from "./components/EditHighlightBorder";
import { DEFAULT_BRAND_COLOR } from "@formbricks/lib/constants";
import { authOptions } from "@formbricks/lib/authOptions";
import { getServerSession } from "next-auth";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { DEFAULT_BRAND_COLOR, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { ErrorComponent } from "@formbricks/ui/ErrorComponent";
import { getServerSession } from "next-auth";
import SettingsCard from "../components/SettingsCard";
import SettingsTitle from "../components/SettingsTitle";
import { EditBrandColor } from "./components/EditBrandColor";
import { EditHighlightBorder } from "./components/EditHighlightBorder";
import { EditPlacement } from "./components/EditPlacement";
import { EditFormbricksBranding } from "./components/EditBranding";
export default async function ProfileSettingsPage({ params }: { params: { environmentId: string } }) {
const [session, team, product] = await Promise.all([
@@ -33,8 +32,10 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro
throw new Error("Team not found");
}
const canRemoveLinkBranding = team.billing.features.linkSurvey.status !== "inactive";
const canRemoveInAppBranding = team.billing.features.inAppSurvey.status !== "inactive";
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
const canRemoveSignature = team.billing.features.linkSurvey.status !== "inactive";
const { isDeveloper, isViewer } = getAccessFlags(currentUserMembership?.role);
const isBrandColorEditDisabled = isDeveloper ? true : isViewer;
@@ -68,11 +69,18 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro
/>
</SettingsCard>
<SettingsCard
title="Formbricks Signature"
title="Formbricks Branding"
description="We love your support but understand if you toggle it off.">
<EditFormbricksSignature
<EditFormbricksBranding
type="linkSurvey"
product={product}
canRemoveSignature={canRemoveSignature}
canRemoveBranding={canRemoveLinkBranding}
environmentId={params.environmentId}
/>
<EditFormbricksBranding
type="inAppSurvey"
product={product}
canRemoveBranding={canRemoveInAppBranding}
environmentId={params.environmentId}
/>
</SettingsCard>

View File

@@ -56,7 +56,7 @@ export default function LinkTab({ surveyUrl, survey, brandColor }: EmailTabProps
<SurveyInline
brandColor={brandColor}
survey={survey}
formbricksSignature={false}
isBrandingEnabled={false}
autoFocus={false}
isRedirectDisabled={false}
key={survey.id}

View File

@@ -220,7 +220,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
/>
@@ -231,7 +231,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
/>
</div>
@@ -284,7 +284,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
/>
@@ -296,7 +296,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
/>

View File

@@ -10,6 +10,7 @@ import { Label } from "@formbricks/ui/Label";
import { ErrorComponent } from "@formbricks/ui/ErrorComponent";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { isLight } from "@/app/lib/utils";
type Product = {
done: () => void;
@@ -73,6 +74,10 @@ const Product: React.FC<Product> = ({ done, isLoading, environmentId, product })
if (!product) {
return <ErrorComponent />;
}
const buttonStyle = {
backgroundColor: color,
color: isLight(color) ? "black" : "white",
};
return (
<div className="flex w-full max-w-xl flex-col gap-8 px-8">
@@ -140,7 +145,7 @@ const Product: React.FC<Product> = ({ done, isLoading, environmentId, product })
</fieldset>
</div>
<div className="mt-4 flex w-full justify-end">
<Button className="pointer-events-none" style={{ backgroundColor: color }}>
<Button className="pointer-events-none" style={buttonStyle}>
Next
</Button>
</div>

View File

@@ -208,7 +208,7 @@ export const getSettings = async (environmentId: string, personId: string): Prom
product: {
select: {
brandColor: true,
formbricksSignature: true,
linkSurveyBranding: true,
placement: true,
darkOverlay: true,
clickOutsideClose: true,
@@ -217,7 +217,7 @@ export const getSettings = async (environmentId: string, personId: string): Prom
},
});
const formbricksSignature = environmentProdut?.product.formbricksSignature;
const formbricksSignature = environmentProdut?.product.linkSurveyBranding;
const brandColor = environmentProdut?.product.brandColor;
const placement = environmentProdut?.product.placement;
const darkOverlay = environmentProdut?.product.darkOverlay;

View File

@@ -136,7 +136,7 @@ export default function LinkSurvey({
<SurveyInline
survey={survey}
brandColor={brandColor}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onDisplay={async () => {
if (!isPreview) {
const api = new FormbricksAPI({

View File

@@ -48,7 +48,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
},
select: {
brandColor: true,
formbricksSignature: true,
linkSurveyBranding: true,
},
});
@@ -57,7 +57,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
message: "Survey not running",
reason: survey.status,
brandColor: product?.brandColor,
formbricksSignature: product?.formbricksSignature,
formbricksSignature: product?.linkSurveyBranding,
surveyClosedMessage: survey?.surveyClosedMessage,
});
}
@@ -66,7 +66,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
return res.status(200).json({
...survey,
brandColor: product?.brandColor,
formbricksSignature: product?.formbricksSignature,
formbricksSignature: product?.linkSurveyBranding,
});
}

View File

@@ -0,0 +1,9 @@
/*
Warnings:
- You are about to drop the column `formbricksSignature` on the `Product` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Product" RENAME COLUMN "formbricksSignature" TO "linkSurveyBranding";
ALTER TABLE "Product" ADD COLUMN "inAppSurveyBranding" BOOLEAN NOT NULL DEFAULT true;

View File

@@ -400,7 +400,8 @@ model Product {
brandColor String @default("#64748b")
highlightBorderColor String?
recontactDays Int @default(7)
formbricksSignature Boolean @default(true)
linkSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in link surveys
inAppSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in in-app surveys
placement WidgetPlacement @default(bottomRight)
clickOutsideClose Boolean @default(true)
darkOverlay Boolean @default(false)

View File

@@ -1,7 +1,7 @@
{
"name": "@formbricks/js",
"license": "MIT",
"version": "1.2.0",
"version": "1.2.1",
"description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
"keywords": [
"Formbricks",

View File

@@ -48,12 +48,13 @@ export const renderWidget = (survey: TSurveyWithTriggers) => {
const clickOutside = productOverwrites.clickOutside ?? product.clickOutsideClose;
const darkOverlay = productOverwrites.darkOverlay ?? product.darkOverlay;
const placement = productOverwrites.placement ?? product.placement;
const isBrandingEnabled = product.inAppSurveyBranding;
setTimeout(() => {
renderSurveyModal({
survey: survey,
brandColor,
formbricksSignature: true,
isBrandingEnabled: isBrandingEnabled,
clickOutside,
darkOverlay,
highlightBorderColor,

View File

@@ -79,7 +79,8 @@ export const mockInitResponse = () => {
product: {
noCodeEvents: [],
brandColor: "#20b398",
formbricksSignature: true,
linkSurveyBranding: true,
inAppBranding: true,
placement: "bottomRight",
darkOverlay: false,
clickOutsideClose: true,

View File

@@ -25,7 +25,8 @@ const selectProduct = {
brandColor: true,
highlightBorderColor: true,
recontactDays: true,
formbricksSignature: true,
linkSurveyBranding: true,
inAppSurveyBranding: true,
placement: true,
clickOutsideClose: true,
darkOverlay: true,

View File

@@ -33,6 +33,7 @@
"tailwindcss": "^3.3.3",
"terser": "^5.22.0",
"vite": "^4.4.11",
"vite-plugin-dts": "^3.6.0"
"vite-plugin-dts": "^3.6.0",
"vite-tsconfig-paths": "^4.2.1"
}
}

View File

@@ -1,16 +0,0 @@
export default function FormbricksSignature() {
return (
<a
href="https://formbricks.com?utm_source=survey_branding"
target="_blank"
tabIndex={-1}
className="mb-3 mt-2 flex justify-center">
<p className="text-xs text-slate-400">
Powered by{" "}
<b>
<span className="text-slate-500 hover:text-slate-700">Formbricks</span>
</b>
</p>
</a>
);
}

View File

@@ -1,9 +0,0 @@
export default function Progress({ progress, brandColor }: { progress: number; brandColor: string }) {
return (
<div className="h-2 w-full rounded-full bg-slate-200">
<div
className="transition-width z-20 h-2 rounded-full duration-500"
style={{ backgroundColor: brandColor, width: `${Math.floor(progress * 100)}%` }}></div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { cn } from "../../../lib/cn";
import { cn } from "@/lib/utils";
interface BackButtonProps {
onClick: () => void;
@@ -12,7 +12,7 @@ export function BackButton({ onClick, backButtonLabel, tabIndex = 2 }: BackButto
tabIndex={tabIndex}
type={"button"}
className={cn(
"flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2"
"border-back-button-border text-heading focus:ring-focus flex items-center rounded-md border px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2"
)}
onClick={onClick}>
{backButtonLabel || "Back"}

View File

@@ -1,11 +1,8 @@
import { useCallback } from "preact/hooks";
import { cn } from "../../../lib/cn";
import { isLight } from "../lib/utils";
interface SubmitButtonProps {
buttonLabel: string | undefined;
isLastQuestion: boolean;
brandColor: string;
onClick: () => void;
focus?: boolean;
tabIndex?: number;
@@ -15,7 +12,6 @@ interface SubmitButtonProps {
function SubmitButton({
buttonLabel,
isLastQuestion,
brandColor,
onClick,
tabIndex = 1,
focus = false,
@@ -38,11 +34,7 @@ function SubmitButton({
type={type}
tabIndex={tabIndex}
autoFocus={focus}
className={cn(
"flex items-center rounded-md border border-transparent px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2",
isLight(brandColor) ? "text-black" : "text-white"
)}
style={{ backgroundColor: brandColor }}
className="bg-brand border-submit-button-border text-on-brand focus:ring-focus flex items-center rounded-md border px-3 py-3 text-base font-medium leading-4 shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2"
onClick={onClick}>
{buttonLabel || (isLastQuestion ? "Finish" : "Next")}
</button>

View File

@@ -0,0 +1,16 @@
export default function FormbricksBranding() {
return (
<a
href="https://formbricks.com?utm_source=survey_branding"
target="_blank"
tabIndex={-1}
className="mb-5 mt-2 flex justify-center">
<p className="text-signature text-xs">
Powered by{" "}
<b>
<span className="text-info-text hover:text-heading">Formbricks</span>
</b>
</p>
</a>
);
}

View File

@@ -7,11 +7,14 @@ interface HeadlineProps {
export default function Headline({ headline, questionId, style, required = true }: HeadlineProps) {
return (
<label htmlFor={questionId} className="mb-1.5 block text-base font-semibold leading-6 text-slate-900">
<div className={"flex justify-between gap-4"} style={style}>
<label
htmlFor={questionId}
className="text-heading mb-1.5 block text-base font-semibold leading-6"
style={style}>
<div className={"mr-[3ch] flex items-center justify-between"} style={style}>
{headline}
{!required && (
<span className="self-start text-sm font-normal leading-7 text-slate-400" tabIndex={-1}>
<span className="text-info-text self-start text-sm font-normal leading-7" tabIndex={-1}>
Optional
</span>
)}

View File

@@ -1,11 +1,11 @@
import { cleanHtml } from "../lib/cleanHtml";
import { cleanHtml } from "@/lib/cleanHtml";
export default function HtmlBody({ htmlString, questionId }: { htmlString?: string; questionId: string }) {
if (!htmlString) return null;
return (
<label
htmlFor={questionId}
className="block text-sm font-normal leading-6 text-slate-600"
className="fb-htmlbody" // styles are in global.css
dangerouslySetInnerHTML={{ __html: cleanHtml(htmlString) }}></label>
);
}

View File

@@ -0,0 +1,9 @@
export default function Progress({ progress }: { progress: number }) {
return (
<div className="bg-accent-bg h-2 w-full rounded-full">
<div
className="transition-width bg-brand z-20 h-2 rounded-full duration-500"
style={{ width: `${Math.floor(progress * 100)}%` }}></div>
</div>
);
}

View File

@@ -1,17 +1,16 @@
import { TSurveyWithTriggers } from "@formbricks/types/js";
import { useEffect, useState } from "preact/hooks";
import Progress from "./Progress";
import { calculateElementIdx } from "../lib/utils";
import { calculateElementIdx } from "@/lib/utils";
interface ProgressBarProps {
survey: TSurveyWithTriggers;
questionId: string;
brandColor: string;
}
const PROGRESS_INCREMENT = 0.1;
export default function ProgressBar({ survey, questionId, brandColor }: ProgressBarProps) {
export default function ProgressBar({ survey, questionId }: ProgressBarProps) {
const [progress, setProgress] = useState(0); // [0, 1]
const [prevQuestionIdx, setPrevQuestionIdx] = useState(0); // [0, survey.questions.length
const [prevQuestionId, setPrevQuestionId] = useState(""); // [0, survey.questions.length
@@ -48,5 +47,5 @@ export default function ProgressBar({ survey, questionId, brandColor }: Progress
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [questionId, survey, setPrevQuestionIdx]);
return <Progress progress={progress} brandColor={brandColor} />;
return <Progress progress={progress} />;
}

View File

@@ -1,14 +1,13 @@
import { TResponseData } from "@formbricks/types/responses";
import { TSurveyQuestion } from "@formbricks/types/surveys";
import { TSurveyQuestionType } from "@formbricks/types/surveys";
import CTAQuestion from "./CTAQuestion";
import ConsentQuestion from "./ConsentQuestion";
import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
import NPSQuestion from "./NPSQuestion";
import OpenTextQuestion from "./OpenTextQuestion";
import RatingQuestion from "./RatingQuestion";
import PictureSelectionQuestion from "./PictureSelectionQuestion";
import { TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys";
import CTAQuestion from "@/components/questions/CTAQuestion";
import ConsentQuestion from "@/components/questions/ConsentQuestion";
import MultipleChoiceMultiQuestion from "@/components/questions/MultipleChoiceMultiQuestion";
import MultipleChoiceSingleQuestion from "@/components/questions/MultipleChoiceSingleQuestion";
import NPSQuestion from "@/components/questions/NPSQuestion";
import OpenTextQuestion from "@/components/questions/OpenTextQuestion";
import PictureSelectionQuestion from "@/components/questions/PictureSelectionQuestion";
import RatingQuestion from "@/components/questions/RatingQuestion";
interface QuestionConditionalProps {
question: TSurveyQuestion;
@@ -18,7 +17,6 @@ interface QuestionConditionalProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
autoFocus?: boolean;
}
@@ -30,7 +28,6 @@ export default function QuestionConditional({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
autoFocus = true,
}: QuestionConditionalProps) {
return question.type === TSurveyQuestionType.OpenText ? (
@@ -42,7 +39,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
autoFocus={autoFocus}
/>
) : question.type === TSurveyQuestionType.MultipleChoiceSingle ? (
@@ -54,7 +50,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : question.type === TSurveyQuestionType.MultipleChoiceMulti ? (
<MultipleChoiceMultiQuestion
@@ -65,7 +60,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : question.type === TSurveyQuestionType.NPS ? (
<NPSQuestion
@@ -76,7 +70,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : question.type === TSurveyQuestionType.CTA ? (
<CTAQuestion
@@ -87,7 +80,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : question.type === TSurveyQuestionType.Rating ? (
<RatingQuestion
@@ -98,7 +90,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : question.type === TSurveyQuestionType.Consent ? (
<ConsentQuestion
@@ -109,7 +100,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : question.type === TSurveyQuestionType.PictureSelection ? (
<PictureSelectionQuestion
@@ -120,7 +110,6 @@ export default function QuestionConditional({
onBack={onBack}
isFirstQuestion={isFirstQuestion}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
/>
) : null;
}

View File

@@ -35,7 +35,7 @@ export default function RedirectCountDown({ redirectUrl, isRedirectDisabled }: R
return (
<div>
<div className="mt-10 rounded-md bg-slate-100 p-2 text-sm">
<div className="bg-accent-bg text-subheading mt-10 rounded-md p-2 text-sm">
<span>You&apos;re redirected in </span>
<span>{timeRemaining}</span>
</div>

View File

@@ -1,6 +1,6 @@
export default function Subheader({ subheader, questionId }: { subheader?: string; questionId: string }) {
return (
<label htmlFor={questionId} className="block text-sm font-normal leading-6 text-slate-600">
<label htmlFor={questionId} className="text-subheading block text-sm font-normal leading-6">
{subheader}
</label>
);

View File

@@ -1,10 +1,10 @@
import FormbricksBranding from "@/components/general/FormbricksBranding";
import { AutoCloseWrapper } from "@/components/wrappers/AutoCloseWrapper";
import { evaluateCondition } from "@/lib/logicEvaluator";
import { cn } from "@/lib/utils";
import { SurveyBaseProps } from "@/types/props";
import type { TResponseData } from "@formbricks/types/responses";
import { useEffect, useRef, useState } from "preact/hooks";
import { evaluateCondition } from "../lib/logicEvaluator";
import { cn } from "../lib/utils";
import { SurveyBaseProps } from "../types/props";
import { AutoCloseWrapper } from "./AutoCloseWrapper";
import FormbricksSignature from "./FormbricksSignature";
import ProgressBar from "./ProgressBar";
import QuestionConditional from "./QuestionConditional";
import ThankYouCard from "./ThankYouCard";
@@ -12,8 +12,7 @@ import WelcomeCard from "./WelcomeCard";
export function Survey({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
onDisplay = () => {},
onActiveQuestionChange = () => {},
@@ -129,7 +128,6 @@ export function Survey({
fileUrl={survey.welcomeCard.fileUrl}
buttonLabel={survey.welcomeCard.buttonLabel}
timeToFinish={survey.welcomeCard.timeToFinish}
brandColor={brandColor}
onSubmit={onSubmit}
survey={survey}
/>
@@ -139,7 +137,6 @@ export function Survey({
<ThankYouCard
headline={survey.thankYouCard.headline}
subheader={survey.thankYouCard.subheader}
brandColor={brandColor}
redirectUrl={survey.redirectUrl}
isRedirectDisabled={isRedirectDisabled}
/>
@@ -160,7 +157,6 @@ export function Survey({
: currQues.id === survey?.questions[0]?.id
}
isLastQuestion={currQues.id === survey.questions[survey.questions.length - 1].id}
brandColor={brandColor}
/>
)
);
@@ -169,8 +165,8 @@ export function Survey({
return (
<>
<AutoCloseWrapper survey={survey} brandColor={brandColor} onClose={onClose}>
<div className="flex h-full w-full flex-col justify-between rounded-lg bg-white px-6 pb-3 pt-6 shadow-lg">
<AutoCloseWrapper survey={survey} onClose={onClose}>
<div className="flex h-full w-full flex-col justify-between bg-[--fb-survey-background-color] px-6 pb-3 pt-6">
<div ref={contentRef} className={cn(loadingElement ? "animate-pulse opacity-60" : "", "my-auto")}>
{survey.questions.length === 0 && !survey.welcomeCard.enabled && !survey.thankYouCard.enabled ? (
// Handle the case when there are no questions and both welcome and thank you cards are disabled
@@ -180,8 +176,8 @@ export function Survey({
)}
</div>
<div className="mt-8">
{formbricksSignature && <FormbricksSignature />}
<ProgressBar survey={survey} questionId={questionId} brandColor={brandColor} />
{isBrandingEnabled && <FormbricksBranding />}
<ProgressBar survey={survey} questionId={questionId} />
</div>
</div>
</AutoCloseWrapper>

View File

@@ -1,10 +1,9 @@
import { SurveyBaseProps } from "../types/props";
import { SurveyBaseProps } from "@/types/props";
import { Survey } from "./Survey";
export function SurveyInline({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
onDisplay = () => {},
onActiveQuestionChange = () => {},
@@ -14,11 +13,10 @@ export function SurveyInline({
isRedirectDisabled = false,
}: SurveyBaseProps) {
return (
<div id="fbjs" className="h-full w-full">
<div id="fbjs" className="formbricks-form h-full w-full">
<Survey
survey={survey}
brandColor={brandColor}
formbricksSignature={formbricksSignature}
isBrandingEnabled={isBrandingEnabled}
activeQuestionId={activeQuestionId}
onDisplay={onDisplay}
onActiveQuestionChange={onActiveQuestionChange}

View File

@@ -1,12 +1,11 @@
import { useState } from "preact/hooks";
import { SurveyModalProps } from "../types/props";
import Modal from "./Modal";
import { SurveyModalProps } from "@/types/props";
import Modal from "@/components/wrappers/Modal";
import { Survey } from "./Survey";
export function SurveyModal({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
placement,
clickOutside,
@@ -29,7 +28,7 @@ export function SurveyModal({
};
return (
<div id="fbjs">
<div id="fbjs" className="formbricks-form">
<Modal
placement={placement}
clickOutside={clickOutside}
@@ -39,8 +38,7 @@ export function SurveyModal({
onClose={close}>
<Survey
survey={survey}
brandColor={brandColor}
formbricksSignature={formbricksSignature}
isBrandingEnabled={isBrandingEnabled}
activeQuestionId={activeQuestionId}
onDisplay={onDisplay}
onActiveQuestionChange={onActiveQuestionChange}

View File

@@ -1,11 +1,10 @@
import Headline from "./Headline";
import RedirectCountDown from "./RedirectCountdown";
import Subheader from "./Subheader";
import Headline from "@/components/general/Headline";
import RedirectCountDown from "@/components/general/RedirectCountdown";
import Subheader from "@/components/general/Subheader";
interface ThankYouCardProps {
headline?: string;
subheader?: string;
brandColor: string;
redirectUrl: string | null;
isRedirectDisabled: boolean;
}
@@ -13,13 +12,12 @@ interface ThankYouCardProps {
export default function ThankYouCard({
headline,
subheader,
brandColor,
redirectUrl,
isRedirectDisabled,
}: ThankYouCardProps) {
return (
<div className="text-center">
<div className="flex items-center justify-center" style={{ color: brandColor }}>
<div className="text-brand flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -35,7 +33,7 @@ export default function ThankYouCard({
</svg>
</div>
<span className="mb-[10px] inline-block h-1 w-16 rounded-[100%] bg-slate-300"></span>
<span className="bg-shadow mb-[10px] inline-block h-1 w-16 rounded-[100%]"></span>
<div>
<Headline headline={headline} questionId="thankYouCard" style={{ "justify-content": "center" }} />

View File

@@ -1,8 +1,8 @@
import SubmitButton from "@/components/buttons/SubmitButton";
import { calculateElementIdx } from "@/lib/utils";
import { TSurveyWithTriggers } from "@formbricks/types/js";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
import SubmitButton from "./SubmitButton";
import { calculateElementIdx } from "../lib/utils";
import { TSurveyWithTriggers } from "@formbricks/types/js";
interface WelcomeCardProps {
headline?: string;
@@ -10,7 +10,6 @@ interface WelcomeCardProps {
fileUrl?: string;
buttonLabel?: string;
timeToFinish?: boolean;
brandColor: string;
onSubmit: (data: { [x: string]: any }) => void;
survey: TSurveyWithTriggers;
}
@@ -38,7 +37,6 @@ export default function WelcomeCard({
fileUrl,
buttonLabel,
timeToFinish,
brandColor,
onSubmit,
survey,
}: WelcomeCardProps) {
@@ -85,14 +83,13 @@ export default function WelcomeCard({
<SubmitButton
buttonLabel={buttonLabel}
isLastQuestion={false}
brandColor={brandColor}
focus={true}
onClick={() => {
onSubmit({ ["welcomeCard"]: "clicked" });
}}
type="button"
/>
<div className="flex items-center text-xs text-slate-600">Press Enter </div>
<div className="text-subheading flex items-center text-xs">Press Enter </div>
</div>
</div>
{timeToFinish && (

View File

@@ -1,9 +1,9 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import HtmlBody from "@/components/general/HtmlBody";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyCTAQuestion } from "@formbricks/types/surveys";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
import SubmitButton from "./SubmitButton";
interface CTAQuestionProps {
question: TSurveyCTAQuestion;
@@ -13,7 +13,6 @@ interface CTAQuestionProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function CTAQuestion({
@@ -22,7 +21,6 @@ export default function CTAQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: CTAQuestionProps) {
return (
<div>
@@ -47,14 +45,13 @@ export default function CTAQuestion({
onClick={() => {
onSubmit({ [question.id]: "dismissed" });
}}
className="mr-4 flex items-center rounded-md px-3 py-3 text-base font-medium leading-4 hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 dark:text-slate-400">
className="text-heading focus:ring-focus mr-4 flex items-center rounded-md px-3 py-3 text-base font-medium leading-4 hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2">
{question.dismissButtonLabel || "Skip"}
</button>
)}
<SubmitButton
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
focus={true}
onClick={() => {
if (question.buttonExternal && question.buttonUrl) {

View File

@@ -1,9 +1,9 @@
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyConsentQuestion } from "@formbricks/types/surveys";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
import SubmitButton from "./SubmitButton";
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import HtmlBody from "@/components/general/HtmlBody";
interface ConsentQuestionProps {
question: TSurveyConsentQuestion;
@@ -13,7 +13,6 @@ interface ConsentQuestionProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function ConsentQuestion({
@@ -24,7 +23,6 @@ export default function ConsentQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: ConsentQuestionProps) {
return (
<div>
@@ -49,7 +47,7 @@ export default function ConsentQuestion({
onChange({ [question.id]: "accepted" });
}
}}
className="relative z-10 mt-4 flex w-full cursor-pointer items-center rounded-md border border-gray-200 p-4 text-sm text-slate-800 hover:bg-slate-50 focus:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2">
className="border-border bg-survey-bg text-heading hover:bg-accent-bg focus:bg-accent-bg focus:ring-border-highlight relative z-10 mt-4 flex w-full cursor-pointer items-center rounded-md border p-4 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2">
<input
type="checkbox"
id={question.id}
@@ -63,9 +61,8 @@ export default function ConsentQuestion({
}
}}
checked={value === "accepted"}
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${question.id}-label`}
style={{ borderColor: brandColor, color: brandColor }}
required={question.required}
/>
<span id={`${question.id}-label`} className="ml-3 font-medium">
@@ -80,7 +77,6 @@ export default function ConsentQuestion({
<div />
<SubmitButton
tabIndex={2}
brandColor={brandColor}
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
onClick={() => {}}

View File

@@ -1,11 +1,11 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import Subheader from "@/components/general/Subheader";
import { cn, shuffleQuestions } from "@/lib/utils";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyMultipleChoiceMultiQuestion } from "@formbricks/types/surveys";
import { useMemo, useRef, useState, useEffect, useCallback } from "preact/hooks";
import { cn, shuffleQuestions } from "../lib/utils";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import Subheader from "./Subheader";
import SubmitButton from "./SubmitButton";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
interface MultipleChoiceMultiProps {
question: TSurveyMultipleChoiceMultiQuestion;
@@ -15,7 +15,6 @@ interface MultipleChoiceMultiProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function MultipleChoiceMultiQuestion({
@@ -26,7 +25,6 @@ export default function MultipleChoiceMultiQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: MultipleChoiceMultiProps) {
const getChoicesWithoutOtherLabels = useCallback(
() => question.choices.filter((choice) => choice.id !== "other").map((item) => item.label),
@@ -104,7 +102,7 @@ export default function MultipleChoiceMultiQuestion({
<div className="mt-4">
<fieldset>
<legend className="sr-only">Options</legend>
<div className="relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md bg-white py-0.5 pr-2">
<div className="bg-survey-bg relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2">
{questionChoices.map((choice, idx) => (
<label
key={choice.id}
@@ -119,8 +117,10 @@ export default function MultipleChoiceMultiQuestion({
}
}}
className={cn(
value === choice.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 hover:bg-slate-50 focus:bg-slate-50 focus:outline-none "
value === choice.label
? "border-border-highlight bg-accent-selected-bg z-10"
: "border-border",
"text-heading focus-within:border-border-highlight hover:bg-accent-bg focus:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
)}>
<span className="flex items-center text-sm">
<input
@@ -129,7 +129,7 @@ export default function MultipleChoiceMultiQuestion({
name={question.id}
tabIndex={-1}
value={choice.label}
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${choice.id}-label`}
onChange={(e) => {
if ((e.target as HTMLInputElement)?.checked) {
@@ -139,7 +139,6 @@ export default function MultipleChoiceMultiQuestion({
}
}}
checked={Array.isArray(value) && value.includes(choice.label)}
style={{ borderColor: brandColor, color: brandColor }}
required={
question.required && Array.isArray(value) && value.length ? false : question.required
}
@@ -154,8 +153,10 @@ export default function MultipleChoiceMultiQuestion({
<label
tabIndex={questionChoices.length + 1}
className={cn(
value === otherOption.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 focus-within:bg-slate-50 hover:bg-slate-50 focus:outline-none"
value === otherOption.label
? "border-border-highlight bg-accent-selected-bg z-10"
: "border-border",
"text-heading focus-within:border-border-highlight focus-within:bg-accent-bg hover:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
)}
onKeyDown={(e) => {
if (e.key == "Enter") {
@@ -169,7 +170,7 @@ export default function MultipleChoiceMultiQuestion({
id={otherOption.id}
name={question.id}
value={otherOption.label}
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${otherOption.id}-label`}
onChange={(e) => {
setOtherSelected(!otherSelected);
@@ -181,7 +182,6 @@ export default function MultipleChoiceMultiQuestion({
}
}}
checked={otherSelected}
style={{ borderColor: brandColor, color: brandColor }}
/>
<span id={`${otherOption.id}-label`} className="ml-3 font-medium">
{otherOption.label}
@@ -206,7 +206,7 @@ export default function MultipleChoiceMultiQuestion({
}
}}
placeholder="Please specify"
className="mt-3 flex h-10 w-full rounded-md border border-slate-300 bg-transparent bg-white px-3 py-2 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300"
className="placeholder:text-placeholder border-border bg-survey-bg text-heading focus:ring-focus mt-3 flex h-10 w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
required={question.required}
aria-labelledby={`${otherOption.id}-label`}
/>
@@ -229,7 +229,6 @@ export default function MultipleChoiceMultiQuestion({
tabIndex={questionChoices.length + 2}
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
onClick={() => {}}
/>
</div>

View File

@@ -1,11 +1,11 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import Subheader from "@/components/general/Subheader";
import { cn, shuffleQuestions } from "@/lib/utils";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyMultipleChoiceSingleQuestion } from "@formbricks/types/surveys";
import { useMemo, useRef, useState, useEffect } from "preact/hooks";
import { cn, shuffleQuestions } from "../lib/utils";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import Subheader from "./Subheader";
import SubmitButton from "./SubmitButton";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
interface MultipleChoiceSingleProps {
question: TSurveyMultipleChoiceSingleQuestion;
@@ -15,7 +15,6 @@ interface MultipleChoiceSingleProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function MultipleChoiceSingleQuestion({
@@ -26,7 +25,6 @@ export default function MultipleChoiceSingleQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: MultipleChoiceSingleProps) {
const [otherSelected, setOtherSelected] = useState(
!!value && !question.choices.find((c) => c.label === value)
@@ -73,8 +71,9 @@ export default function MultipleChoiceSingleQuestion({
<div className="mt-4">
<fieldset>
<legend className="sr-only">Options</legend>
<div
className="relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md bg-white py-0.5 pr-2"
className="bg-survey-bg relative max-h-[42vh] space-y-2 overflow-y-auto rounded-md py-0.5 pr-2"
role="radiogroup">
{questionChoices.map((choice, idx) => (
<label
@@ -89,8 +88,10 @@ export default function MultipleChoiceSingleQuestion({
}
}}
className={cn(
value === choice.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 focus-within:bg-slate-50 hover:bg-slate-50 focus:outline-none "
value === choice.label
? "border-border-highlight bg-accent-selected-bg z-10"
: "border-border",
"text-heading focus-within:border-border-highlight focus-within:bg-accent-bg hover:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
)}>
<span className="flex items-center text-sm">
<input
@@ -99,14 +100,13 @@ export default function MultipleChoiceSingleQuestion({
id={choice.id}
name={question.id}
value={choice.label}
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${choice.id}-label`}
onChange={() => {
setOtherSelected(false);
onChange({ [question.id]: choice.label });
}}
checked={value === choice.label}
style={{ borderColor: brandColor, color: brandColor }}
required={question.required && idx === 0}
/>
<span id={`${choice.id}-label`} className="ml-3 font-medium">
@@ -119,8 +119,10 @@ export default function MultipleChoiceSingleQuestion({
<label
tabIndex={questionChoices.length + 1}
className={cn(
value === otherOption.label ? "z-10 border-slate-400 bg-slate-50" : "border-gray-200",
"relative flex cursor-pointer flex-col rounded-md border p-4 text-slate-800 focus-within:border-slate-400 focus-within:bg-slate-50 hover:bg-slate-50 focus:outline-none"
value === otherOption.label
? "border-border-highlight bg-accent-selected-bg z-10"
: "border-border",
"text-heading focus-within:border-border-highlight focus-within:bg-accent-bg hover:bg-accent-bg relative flex cursor-pointer flex-col rounded-md border p-4 focus:outline-none"
)}
onKeyDown={(e) => {
if (e.key == "Enter") {
@@ -135,14 +137,13 @@ export default function MultipleChoiceSingleQuestion({
tabIndex={-1}
name={question.id}
value={otherOption.label}
className="h-4 w-4 border border-slate-300 focus:ring-0 focus:ring-offset-0"
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${otherOption.id}-label`}
onChange={() => {
setOtherSelected(!otherSelected);
onChange({ [question.id]: "" });
}}
checked={otherSelected}
style={{ borderColor: brandColor, color: brandColor }}
/>
<span id={`${otherOption.id}-label`} className="ml-3 font-medium">
{otherOption.label}
@@ -166,7 +167,7 @@ export default function MultipleChoiceSingleQuestion({
}
}}
placeholder="Please specify"
className="mt-3 flex h-10 w-full rounded-md border border-slate-300 bg-transparent bg-white px-3 py-2 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300"
className="placeholder:text-placeholder border-border bg-survey-bg text-heading focus:ring-focus mt-3 flex h-10 w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
required={question.required}
aria-labelledby={`${otherOption.id}-label`}
/>
@@ -189,7 +190,6 @@ export default function MultipleChoiceSingleQuestion({
tabIndex={questionChoices.length + 2}
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
onClick={() => {}}
/>
</div>

View File

@@ -1,10 +1,10 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import Subheader from "@/components/general/Subheader";
import { cn } from "@/lib/utils";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyNPSQuestion } from "@formbricks/types/surveys";
import { cn } from "../lib/utils";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import Subheader from "./Subheader";
import SubmitButton from "./SubmitButton";
interface NPSQuestionProps {
question: TSurveyNPSQuestion;
@@ -14,7 +14,6 @@ interface NPSQuestionProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function NPSQuestion({
@@ -25,7 +24,6 @@ export default function NPSQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: NPSQuestionProps) {
return (
<form
@@ -55,8 +53,8 @@ export default function NPSQuestion({
}
}}
className={cn(
value === number ? "z-10 border-slate-400 bg-slate-50" : "",
"relative h-10 flex-1 cursor-pointer border bg-white text-center text-sm leading-10 text-slate-800 first:rounded-l-md last:rounded-r-md hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
value === number ? "border-border-highlight bg-accent-selected-bg z-10" : "border-border",
"bg-survey-bg text-heading hover:bg-accent-bg relative h-10 flex-1 cursor-pointer border text-center text-sm leading-10 first:rounded-l-md last:rounded-r-md focus:outline-none"
)}>
<input
type="radio"
@@ -78,7 +76,7 @@ export default function NPSQuestion({
</label>
))}
</div>
<div className="flex justify-between px-1.5 text-xs leading-6 text-slate-500">
<div className="text-info-text flex justify-between px-1.5 text-xs leading-6">
<p>{question.lowerLabel}</p>
<p>{question.upperLabel}</p>
</div>
@@ -101,7 +99,6 @@ export default function NPSQuestion({
tabIndex={12}
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
onClick={() => {}}
/>
)}

View File

@@ -1,9 +1,9 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import Subheader from "@/components/general/Subheader";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyOpenTextQuestion } from "@formbricks/types/surveys";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import Subheader from "./Subheader";
import SubmitButton from "./SubmitButton";
import { useCallback } from "react";
interface OpenTextQuestionProps {
@@ -14,7 +14,6 @@ interface OpenTextQuestionProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
autoFocus?: boolean;
}
@@ -26,7 +25,6 @@ export default function OpenTextQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
autoFocus = true,
}: OpenTextQuestionProps) {
const handleInputChange = (inputValue: string) => {
@@ -40,8 +38,11 @@ export default function OpenTextQuestion({
currentElement.focus();
}
},
[autoFocus]
[question.id]
);
const isInputEmpty = (value: string) => {
return question.required && !value?.trim();
};
return (
<form
@@ -73,14 +74,16 @@ export default function OpenTextQuestion({
type={question.inputType}
onInput={(e) => handleInputChange(e.currentTarget.value)}
autoFocus={autoFocus}
className="border-border bg-survey-bg focus:border-border-highlight block w-full rounded-md border p-2 shadow-sm focus:outline-none focus:ring-0 sm:text-sm"
onKeyDown={(e) => {
if (e.key == "Enter") onSubmit({ [question.id]: value });
if (e.key === "Enter" && isInputEmpty(value as string)) {
e.preventDefault(); // Prevent form submission
} else if (e.key === "Enter") {
onSubmit({ [question.id]: value });
}
}}
pattern={question.inputType === "phone" ? "[+][0-9 ]+" : ".*"}
title={question.inputType === "phone" ? "Enter a valid phone number" : undefined}
className={`block w-full rounded-md border
border-slate-100
bg-slate-50 p-2 shadow-sm focus:border-slate-500 focus:outline-none focus:ring-0 sm:text-sm`}
/>
) : (
<textarea
@@ -95,11 +98,10 @@ export default function OpenTextQuestion({
type={question.inputType}
onInput={(e) => handleInputChange(e.currentTarget.value)}
autoFocus={autoFocus}
className="border-border bg-survey-bg text-subheading focus:border-border-highlight block w-full rounded-md border p-2 shadow-sm focus:ring-0 sm:text-sm"
pattern={question.inputType === "phone" ? "[+][0-9 ]+" : ".*"}
title={question.inputType === "phone" ? "Please enter a valid phone number" : undefined}
className={`block w-full rounded-md border
border-slate-100
bg-slate-50 p-2 shadow-sm focus:border-slate-500 focus:outline-none focus:ring-0 sm:text-sm`}></textarea>
/>
)}
</div>
@@ -113,12 +115,7 @@ export default function OpenTextQuestion({
/>
)}
<div></div>
<SubmitButton
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
onClick={() => {}}
/>
<SubmitButton buttonLabel={question.buttonLabel} isLastQuestion={isLastQuestion} onClick={() => {}} />
</div>
</form>
);

View File

@@ -1,11 +1,11 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import Subheader from "@/components/general/Subheader";
import { cn } from "@/lib/utils";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyPictureSelectionQuestion } from "@formbricks/types/surveys";
import { useEffect } from "preact/hooks";
import { cn } from "../lib/utils";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import Subheader from "./Subheader";
import SubmitButton from "./SubmitButton";
interface PictureSelectionProps {
question: TSurveyPictureSelectionQuestion;
@@ -15,7 +15,6 @@ interface PictureSelectionProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function PictureSelectionQuestion({
@@ -26,7 +25,6 @@ export default function PictureSelectionQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: PictureSelectionProps) {
const addItem = (item: string) => {
let values: string[] = [];
@@ -95,7 +93,7 @@ export default function PictureSelectionQuestion({
<div className="mt-4">
<fieldset>
<legend className="sr-only">Options</legend>
<div className="relative grid max-h-[42vh] grid-cols-2 gap-x-5 gap-y-4 overflow-y-auto rounded-md bg-white pr-2.5">
<div className="rounded-m bg-survey-bg relative grid max-h-[42vh] grid-cols-2 gap-x-5 gap-y-4 overflow-y-auto pr-2.5">
{questionChoices.map((choice, idx) => (
<label
key={choice.id}
@@ -106,17 +104,12 @@ export default function PictureSelectionQuestion({
handleChange(choice.id);
}
}}
style={{
borderColor:
Array.isArray(value) && value.includes(choice.id) ? brandColor : "border-slate-400",
color: brandColor,
}}
onClick={() => handleChange(choice.id)}
className={cn(
Array.isArray(value) && value.includes(choice.id)
? `z-10 border-4 shadow-xl focus:border-4`
? `border-brand text-brand z-10 border-4 shadow-xl focus:border-4`
: "",
"relative box-border inline-block h-28 w-full overflow-hidden rounded-xl border border-slate-400 focus:border-slate-600 focus:bg-slate-50 focus:outline-none"
"border-border focus:border-border-highlight focus:bg-accent-selected-bg relative box-border inline-block h-28 w-full overflow-hidden rounded-xl border focus:outline-none"
)}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
@@ -132,8 +125,10 @@ export default function PictureSelectionQuestion({
type="checkbox"
tabindex={-1}
checked={Array.isArray(value) && value.includes(choice.id)}
style={{ borderColor: brandColor, color: brandColor }}
className="pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded border border-slate-400"
className={cn(
"border-border pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded border",
Array.isArray(value) && value.includes(choice.id) ? "border-brand text-brand" : ""
)}
required={
question.required && Array.isArray(value) && value.length ? false : question.required
}
@@ -145,8 +140,10 @@ export default function PictureSelectionQuestion({
type="radio"
tabindex={-1}
checked={Array.isArray(value) && value.includes(choice.id)}
style={{ borderColor: brandColor, color: brandColor }}
className="pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 "
className={cn(
"border-border pointer-events-none absolute right-2 top-2 z-20 h-5 w-5 rounded-full border",
Array.isArray(value) && value.includes(choice.id) ? "border-brand text-brand" : ""
)}
required={
question.required && Array.isArray(value) && value.length ? false : question.required
}
@@ -170,7 +167,6 @@ export default function PictureSelectionQuestion({
tabIndex={questionChoices.length + 2}
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
onClick={() => {}}
/>
</div>

View File

@@ -1,9 +1,10 @@
import { BackButton } from "@/components/buttons/BackButton";
import SubmitButton from "@/components/buttons/SubmitButton";
import Headline from "@/components/general/Headline";
import { cn } from "@/lib/utils";
import { TResponseData } from "@formbricks/types/responses";
import type { TSurveyRatingQuestion } from "@formbricks/types/surveys";
import { useState } from "preact/hooks";
import { cn } from "../lib/utils";
import { BackButton } from "./BackButton";
import Headline from "./Headline";
import {
ConfusedFace,
FrowningFace,
@@ -15,9 +16,8 @@ import {
SmilingFaceWithSmilingEyes,
TiredFace,
WearyFace,
} from "./Smileys";
import Subheader from "./Subheader";
import SubmitButton from "./SubmitButton";
} from "../general/Smileys";
import Subheader from "../general/Subheader";
interface RatingQuestionProps {
question: TSurveyRatingQuestion;
@@ -27,7 +27,6 @@ interface RatingQuestionProps {
onBack: () => void;
isFirstQuestion: boolean;
isLastQuestion: boolean;
brandColor: string;
}
export default function RatingQuestion({
@@ -38,7 +37,6 @@ export default function RatingQuestion({
onBack,
isFirstQuestion,
isLastQuestion,
brandColor,
}: RatingQuestionProps) {
const [hoveredNumber, setHoveredNumber] = useState(0);
@@ -87,7 +85,7 @@ export default function RatingQuestion({
key={number}
onMouseOver={() => setHoveredNumber(number)}
onMouseLeave={() => setHoveredNumber(0)}
className="max-w-10 relative max-h-10 flex-1 cursor-pointer bg-white text-center text-sm leading-10">
className="max-w-10 bg-survey-bg relative max-h-10 flex-1 cursor-pointer text-center text-sm leading-10">
{question.scale === "number" ? (
<label
tabIndex={i + 1}
@@ -97,10 +95,10 @@ export default function RatingQuestion({
}
}}
className={cn(
value === number ? "z-10 border-slate-400 bg-slate-50" : "",
value === number ? "bg-accent-selected-bg border-border-highlight z-10" : "",
a.length === number ? "rounded-r-md" : "",
number === 1 ? "rounded-l-md" : "",
"block h-full w-full border text-slate-800 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
"text-heading hover:bg-accent-bg focus:bg-accent-bg block h-full w-full border focus:outline-none"
)}>
<HiddenRadioInput number={number} />
{number}
@@ -114,14 +112,14 @@ export default function RatingQuestion({
}
}}
className={cn(
number <= hoveredNumber ? "text-yellow-500" : "",
"flex h-full w-full justify-center focus:text-yellow-500 focus:outline-none"
number <= hoveredNumber ? "text-rating-focus" : "text-heading",
"focus:text-rating-focus flex h-full w-full justify-center focus:outline-none"
)}
onFocus={() => setHoveredNumber(number)}
onBlur={() => setHoveredNumber(0)}>
<HiddenRadioInput number={number} />
{typeof value === "number" && value >= number ? (
<span className="text-yellow-300">
<span className="text-rating-fill">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
@@ -152,13 +150,18 @@ export default function RatingQuestion({
</label>
) : (
<label
className={cn(
"flex h-full w-full justify-center",
value === number || hoveredNumber === number
? "stroke-rating-selected text-rating-selected"
: "stroke-heading text-heading"
)}
tabIndex={i + 1}
onKeyDown={(e) => {
if (e.key == "Enter") {
handleSelect(number);
}
}}
className="flex h-full w-full justify-center text-slate-800 focus:outline-none"
onFocus={() => setHoveredNumber(number)}
onBlur={() => setHoveredNumber(0)}>
<HiddenRadioInput number={number} />
@@ -172,7 +175,7 @@ export default function RatingQuestion({
</span>
))}
</div>
<div className="flex justify-between px-1.5 text-xs leading-6 text-slate-500">
<div className="text-subheading flex justify-between px-1.5 text-xs leading-6">
<p className="w-1/2 text-left">{question.lowerLabel}</p>
<p className="w-1/2 text-right">{question.upperLabel}</p>
</div>
@@ -195,7 +198,6 @@ export default function RatingQuestion({
tabIndex={question.range + 1}
buttonLabel={question.buttonLabel}
isLastQuestion={isLastQuestion}
brandColor={brandColor}
onClick={() => {}}
/>
)}
@@ -211,7 +213,7 @@ interface RatingSmileyProps {
}
function RatingSmiley({ active, idx, range }: RatingSmileyProps): JSX.Element {
const activeColor = "fill-yellow-500";
const activeColor = "fill-rating-fill";
const inactiveColor = "fill-none";
let icons = [
<TiredFace className={active ? activeColor : inactiveColor} />,

View File

@@ -1,15 +1,14 @@
import { TSurveyWithTriggers } from "@formbricks/types/js";
import { useEffect, useRef, useState } from "preact/hooks";
import Progress from "./Progress";
import Progress from "../general/Progress";
interface AutoCloseProps {
survey: TSurveyWithTriggers;
brandColor: string;
onClose: () => void;
children: any;
}
export function AutoCloseWrapper({ survey, brandColor, onClose, children }: AutoCloseProps) {
export function AutoCloseWrapper({ survey, onClose, children }: AutoCloseProps) {
const [countdownProgress, setCountdownProgress] = useState(100);
const [countdownStop, setCountdownStop] = useState(false);
const startRef = useRef(performance.now());
@@ -49,9 +48,7 @@ export function AutoCloseWrapper({ survey, brandColor, onClose, children }: Auto
return (
<>
{!countdownStop && survey.autoClose && (
<Progress progress={countdownProgress} brandColor={brandColor} />
)}
{!countdownStop && survey.autoClose && <Progress progress={countdownProgress} />}
<div onClick={handleStopCountdown} onMouseOver={handleStopCountdown} className="h-full w-full">
{children}
</div>

View File

@@ -1,7 +1,7 @@
import { cn } from "@/lib/utils";
import { TPlacement } from "@formbricks/types/common";
import { VNode } from "preact";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import { cn } from "../lib/utils";
interface ModalProps {
children: VNode;
@@ -108,7 +108,7 @@ export default function Modal({
<button
type="button"
onClick={onClose}
class="relative rounded-md text-slate-400 hover:text-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2">
class="text-close-button hover:text-close-button-focus focus:ring-close-button-focus relative rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2">
<span class="sr-only">Close</span>
<svg
class="h-4 w-4"

View File

@@ -1,11 +1,13 @@
import { SurveyInline } from "@/components/general/SurveyInline";
import { SurveyModal } from "@/components/general/SurveyModal";
import { addCustomThemeToDom, addStylesToDom } from "@/lib/styles";
import { SurveyInlineProps, SurveyModalProps } from "@/types/props";
import { h, render } from "preact";
import { SurveyModal } from "./components/SurveyModal";
import { addStylesToDom } from "./lib/styles";
import { SurveyInlineProps, SurveyModalProps } from "./types/props";
import { SurveyInline } from "./components/SurveyInline";
export const renderSurveyInline = (props: SurveyInlineProps) => {
export const renderSurveyInline = (props: SurveyInlineProps & { brandColor: string }) => {
addStylesToDom();
addCustomThemeToDom({ brandColor: props.brandColor });
const { containerId, ...surveyProps } = props;
const element = document.getElementById(containerId);
if (!element) {
@@ -14,8 +16,10 @@ export const renderSurveyInline = (props: SurveyInlineProps) => {
render(h(SurveyInline, surveyProps), element);
};
export const renderSurveyModal = (props: SurveyModalProps) => {
export const renderSurveyModal = (props: SurveyModalProps & { brandColor: string }) => {
addStylesToDom();
addCustomThemeToDom({ brandColor: props.brandColor });
// add container element to DOM
const element = document.createElement("div");
element.id = "formbricks-modal-container";

View File

@@ -1,5 +1,5 @@
import global from "../styles/global.css?inline";
import preflight from "../styles/preflight.css?inline";
import global from "@/styles/global.css?inline";
import preflight from "@/styles/preflight.css?inline";
import editorCss from "../../../ui/Editor/stylesEditorFrontend.css?inline";
export const addStylesToDom = () => {
@@ -10,3 +10,16 @@ export const addStylesToDom = () => {
document.head.appendChild(styleElement);
}
};
export const addCustomThemeToDom = ({ brandColor }: { brandColor: string }) => {
if (document.getElementById("formbricks__css") === null) return;
const styleElement = document.createElement("style");
styleElement.id = "formbricks__css__custom";
styleElement.innerHTML = `
:root {
--fb-brand-color: ${brandColor};
}
`;
document.head.appendChild(styleElement);
};

View File

@@ -2,6 +2,8 @@
@tailwind components;
@tailwind utilities;
/* @import "./survey.css"; */
/* Firefox */
#fbjs * {
scrollbar-width: thin;
@@ -24,3 +26,55 @@
border: 3px solid #cbd5e1;
border-radius: 99px;
}
/* this is for styling the HtmlBody component */
.fb-htmlbody {
@apply block text-sm font-normal leading-6;
/* need to use !important because in packages/ui/components/editor/stylesEditorFrontend.css the color is defined for some classes */
color: var(--fb-subheading-color) !important;
}
/* without this, it wont override the color */
p.fb-editor-paragraph {
color: var(--fb-subheading-color) !important;
}
/* theming */
:root {
--slate-50: rgb(248 250 252);
--slate-100: rgb(241 245 249);
--slate-200: rgb(226 232 240);
--slate-300: rgb(203 213 225);
--slate-400: rgb(148 163 184);
--slate-500: rgb(100 116 139);
--slate-600: rgb(71 85 105);
--slate-700: rgb(51 65 85);
--slate-800: rgb(30 41 59);
--slate-900: rgb(15 23 42);
--gray-100: rgb(243 244 246);
--gray-200: rgb(229 231 235);
--yellow-300: rgb(253 224 71);
--yellow-500: rgb(234 179 8);
/* Default Light Theme, you can override everything by changing these values */
--fb-brand-color: rgb(255, 255, 255);
--fb-brand-text-color: black;
--fb-border-color: var(--slate-300);
--fb-border-color-highlight: var(--slate-500);
--fb-focus-color: var(--slate-500);
--fb-heading-color: var(--slate-900);
--fb-subheading-color: var(--slate-700);
--fb-info-text-color: var(--slate-500);
--fb-signature-text-color: var(--slate-400);
--fb-survey-background-color: white;
--fb-accent-background-color: var(--slate-200);
--fb-accent-background-color-selected: var(--slate-100);
--fb-placeholder-color: var(--slate-400);
--fb-shadow-color: var(--slate-300);
--fb-rating-fill: var(--yellow-300);
--fb-rating-hover: var(--yellow-500);
--fb-back-btn-border: transparent;
--fb-submit-btn-border: transparent;
--fb-rating-selected: black;
--fb-close-btn-color: var(--slate-500);
--fb-close-btn-color-hover: var(--slate-700);
}

View File

@@ -3,8 +3,7 @@ import { TResponseData, TResponseUpdate } from "@formbricks/types/responses";
export interface SurveyBaseProps {
survey: TSurveyWithTriggers;
brandColor: string;
formbricksSignature: boolean;
isBrandingEnabled: boolean;
activeQuestionId?: string;
onDisplay?: () => void;
onResponse?: (response: TResponseUpdate) => void;

View File

@@ -7,6 +7,29 @@ module.exports = {
},
content: ["./src/**/*.{tsx,ts,jsx,js}"],
theme: {
colors: {
brand: "var(--fb-brand-color)",
"on-brand": "var(--fb-brand-text-color)",
border: "var(--fb-border-color)",
"border-highlight": "var(--fb-border-color-highlight)",
focus: "var(--fb-focus-color)",
heading: "var(--fb-heading-color)",
subheading: "var(--fb-subheading-color)",
"info-text": "var(--fb-info-text-color)",
signature: "var(--fb-signature-text-color)",
"survey-bg": "var(--fb-survey-background-color)",
"accent-bg": "var(--fb-accent-background-color)",
"accent-selected-bg": "var(--fb-accent-background-color-selected)",
placeholder: "var(--fb-placeholder-color)",
shadow: "var(--fb-shadow-color)",
"rating-fill": "var(--fb-rating-fill)",
"rating-focus": "var(--fb-rating-hover)",
"rating-selected": "var(--fb-rating-selected)",
"back-button-border": "var(--fb-back-btn-border)",
"submit-button-border": "var(--fb-submit-btn-border)",
"close-button": "var(--fb-close-btn-color)",
"close-button-focus": "var(--fb-close-btn-hover-color)",
},
extend: {
zIndex: {
999999: "999999",

View File

@@ -6,6 +6,10 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
"jsxImportSource": "preact",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@@ -2,6 +2,7 @@ import { resolve } from "path";
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
import dts from "vite-plugin-dts";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
@@ -18,5 +19,5 @@ export default defineConfig({
fileName: "index",
},
},
plugins: [preact(), dts({ rollupTypes: true })],
plugins: [preact(), dts({ rollupTypes: true }), tsconfigPaths()],
});

View File

@@ -11,7 +11,8 @@ export const ZProduct = z.object({
brandColor: ZColor,
highlightBorderColor: ZColor.nullable(),
recontactDays: z.number().int(),
formbricksSignature: z.boolean(),
inAppSurveyBranding: z.boolean(),
linkSurveyBranding: z.boolean(),
placement: ZPlacement,
clickOutsideClose: z.boolean(),
darkOverlay: z.boolean(),

View File

@@ -8,7 +8,7 @@ const createContainerId = () => `formbricks-survey-container`;
interface SurveyProps {
survey: TSurvey;
brandColor: string;
formbricksSignature: boolean;
isBrandingEnabled: boolean;
activeQuestionId?: string;
onDisplay?: () => void;
onResponse?: (response: TResponseUpdate) => void;
@@ -30,7 +30,7 @@ interface SurveyModalProps extends SurveyProps {
export const SurveyInline = ({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
onDisplay = () => {},
onResponse = () => {},
@@ -45,7 +45,7 @@ export const SurveyInline = ({
renderSurveyInline({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
containerId,
onDisplay,
onResponse,
@@ -60,7 +60,7 @@ export const SurveyInline = ({
activeQuestionId,
brandColor,
containerId,
formbricksSignature,
isBrandingEnabled,
onActiveQuestionChange,
onClose,
onDisplay,
@@ -76,7 +76,7 @@ export const SurveyInline = ({
export const SurveyModal = ({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
placement = "bottomRight",
clickOutside = false,
@@ -93,7 +93,7 @@ export const SurveyModal = ({
renderSurveyModal({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
placement,
clickOutside,
darkOverlay,
@@ -111,7 +111,7 @@ export const SurveyModal = ({
brandColor,
clickOutside,
darkOverlay,
formbricksSignature,
isBrandingEnabled,
highlightBorderColor,
onActiveQuestionChange,
onClose,

37
pnpm-lock.yaml generated
View File

@@ -744,6 +744,9 @@ importers:
vite-plugin-dts:
specifier: ^3.6.0
version: 3.6.2(typescript@5.2.2)(vite@4.5.0)
vite-tsconfig-paths:
specifier: ^4.2.1
version: 4.2.1(typescript@5.2.2)(vite@4.5.0)
packages/tailwind-config:
devDependencies:
@@ -14296,6 +14299,10 @@ packages:
merge2: 1.4.1
slash: 3.0.0
/globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
dev: true
/glogg@1.0.2:
resolution: {integrity: sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==}
engines: {node: '>= 0.10'}
@@ -22123,6 +22130,19 @@ packages:
resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==}
dev: true
/tsconfck@2.1.2(typescript@5.2.2):
resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==}
engines: {node: ^14.13.1 || ^16 || >=18}
hasBin: true
peerDependencies:
typescript: ^4.3.5 || ^5.0.0
peerDependenciesMeta:
typescript:
optional: true
dependencies:
typescript: 5.2.2
dev: true
/tsconfig-paths@3.14.2:
resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
dependencies:
@@ -23063,6 +23083,23 @@ packages:
- supports-color
dev: true
/vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@4.5.0):
resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==}
peerDependencies:
vite: '*'
peerDependenciesMeta:
vite:
optional: true
dependencies:
debug: 4.3.4
globrex: 0.1.2
tsconfck: 2.1.2(typescript@5.2.2)
vite: 4.5.0(terser@5.22.0)
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/vite@4.5.0(terser@5.22.0):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
engines: {node: ^14.18.0 || >=16.0.0}