feat: add toggle for in-app formbricks branding (#1616)

Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: review-agent-prime[bot] <147289438+review-agent-prime[bot]@users.noreply.github.com>
This commit is contained in:
Matti Nannt
2023-11-14 17:39:30 +01:00
committed by GitHub
parent 73711b4631
commit 888dbbcfd2
21 changed files with 153 additions and 115 deletions

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

@@ -204,7 +204,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
/>
@@ -219,7 +219,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
/>
</div>
@@ -274,7 +274,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
/>
@@ -287,7 +287,7 @@ export default function PreviewSurvey({
survey={survey}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
/>

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

@@ -397,7 +397,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

@@ -1,4 +1,4 @@
export default function FormbricksSignature() {
export default function FormbricksBranding() {
return (
<a
href="https://formbricks.com?utm_source=survey_branding"

View File

@@ -4,7 +4,7 @@ import { evaluateCondition } from "../lib/logicEvaluator";
import { cn } from "../lib/utils";
import { SurveyBaseProps } from "../types/props";
import { AutoCloseWrapper } from "./AutoCloseWrapper";
import FormbricksSignature from "./FormbricksSignature";
import FormbricksBranding from "./FormbricksBranding";
import ProgressBar from "./ProgressBar";
import QuestionConditional from "./QuestionConditional";
import ThankYouCard from "./ThankYouCard";
@@ -13,7 +13,7 @@ import WelcomeCard from "./WelcomeCard";
export function Survey({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
onDisplay = () => {},
onActiveQuestionChange = () => {},
@@ -180,7 +180,7 @@ export function Survey({
)}
</div>
<div className="mt-8">
{formbricksSignature && <FormbricksSignature />}
{isBrandingEnabled && <FormbricksBranding />}
<ProgressBar survey={survey} questionId={questionId} brandColor={brandColor} />
</div>
</div>

View File

@@ -4,7 +4,7 @@ import { Survey } from "./Survey";
export function SurveyInline({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
onDisplay = () => {},
onActiveQuestionChange = () => {},
@@ -18,7 +18,7 @@ export function SurveyInline({
<Survey
survey={survey}
brandColor={brandColor}
formbricksSignature={formbricksSignature}
isBrandingEnabled={isBrandingEnabled}
activeQuestionId={activeQuestionId}
onDisplay={onDisplay}
onActiveQuestionChange={onActiveQuestionChange}

View File

@@ -6,7 +6,7 @@ import { Survey } from "./Survey";
export function SurveyModal({
survey,
brandColor,
formbricksSignature,
isBrandingEnabled,
activeQuestionId,
placement,
clickOutside,
@@ -40,7 +40,7 @@ export function SurveyModal({
<Survey
survey={survey}
brandColor={brandColor}
formbricksSignature={formbricksSignature}
isBrandingEnabled={isBrandingEnabled}
activeQuestionId={activeQuestionId}
onDisplay={onDisplay}
onActiveQuestionChange={onActiveQuestionChange}

View File

@@ -4,7 +4,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

@@ -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,