mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 05:40:02 -06:00
Merge branch 'main' into surveyBg
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 'Powered by Formbricks' Signature in Link Surveys</Label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -79,7 +79,8 @@ export const mockInitResponse = () => {
|
||||
product: {
|
||||
noCodeEvents: [],
|
||||
brandColor: "#20b398",
|
||||
formbricksSignature: true,
|
||||
linkSurveyBranding: true,
|
||||
inAppBranding: true,
|
||||
placement: "bottomRight",
|
||||
darkOverlay: false,
|
||||
clickOutsideClose: true,
|
||||
|
||||
@@ -25,7 +25,8 @@ const selectProduct = {
|
||||
brandColor: true,
|
||||
highlightBorderColor: true,
|
||||
recontactDays: true,
|
||||
formbricksSignature: true,
|
||||
linkSurveyBranding: true,
|
||||
inAppSurveyBranding: true,
|
||||
placement: true,
|
||||
clickOutsideClose: true,
|
||||
darkOverlay: true,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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"}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
9
packages/surveys/src/components/general/Progress.tsx
Normal file
9
packages/surveys/src/components/general/Progress.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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're redirected in </span>
|
||||
<span>{timeRemaining}</span>
|
||||
</div>
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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" }} />
|
||||
@@ -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 && (
|
||||
@@ -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) {
|
||||
@@ -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={() => {}}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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={() => {}}
|
||||
/>
|
||||
)}
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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>
|
||||
@@ -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} />,
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact"
|
||||
"jsxImportSource": "preact",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()],
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
37
pnpm-lock.yaml
generated
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user