Formbricks Branding Signature (#305)

* Add Formbricks Signature Branding (can be deactivated in Look & Feel Settings)

---------

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Johannes
2023-05-29 11:46:07 +02:00
committed by GitHub
parent 2912b8bd8d
commit cda8513410
18 changed files with 142 additions and 91 deletions
@@ -18,8 +18,8 @@ export default function SettingsCard({
<div className="my-4 w-full bg-white shadow sm:rounded-lg">
<div className="rounded-t-lg border-b border-slate-200 bg-slate-100 px-6 py-5">
<div className="flex">
<h3 className="text-lg font-medium leading-6 text-slate-900">{title}</h3>
{soon && <Badge text="coming soon" size="normal" type="warning" />}
<h3 className="mr-2 text-lg font-medium leading-6 text-slate-900">{title}</h3>
{soon && <Badge text="coming soon" size="normal" type="success" />}
</div>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
@@ -1,5 +1,6 @@
"use client";
import { cn } from "@formbricks/lib/cn";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import { useEnvironment } from "@/lib/environments/environments";
import { useProductMutation } from "@/lib/products/mutateProducts";
@@ -18,7 +19,6 @@ import toast from "react-hot-toast";
export function EditBrandColor({ environmentId }) {
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
const { triggerProductMutate, isMutatingProduct } = useProductMutation(environmentId);
const [color, setColor] = useState("");
@@ -54,7 +54,6 @@ export function EditBrandColor({ environmentId }) {
}}>
Save
</Button>
{/* <div className="whitespace-pre-wrap">{JSON.stringify(environment, null, 2)}</div>; */}
</div>
);
}
@@ -89,7 +88,11 @@ export function EditPlacement({ environmentId }) {
checked={placement.default}
disabled={placement.disabled}
/>
<Label htmlFor={placement.value}>{placement.name}</Label>
<Label
htmlFor={placement.value}
className={cn(placement.disabled ? "cursor-not-allowed text-slate-500" : "text-slate-900")}>
{placement.name}
</Label>
</div>
))}
</RadioGroup>
@@ -100,31 +103,58 @@ export function EditPlacement({ environmentId }) {
<Button type="submit" variant="darkCTA" className="mt-4" disabled>
Save
</Button>
{/* <div className="whitespace-pre-wrap">{JSON.stringify(environment, null, 2)}</div>; */}
</div>
);
}
export function EditFormbricksSignature({ environmentId }) {
const { isLoadingEnvironment, isErrorEnvironment } = useEnvironment(environmentId);
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
const { triggerProductMutate, isMutatingProduct } = useProductMutation(environmentId);
if (isLoadingEnvironment) {
const [formbricksSignature, setFormbricksSignature] = useState(false);
useEffect(() => {
if (product) {
setFormbricksSignature(product.formbricksSignature);
}
}, [product]);
const toggleSignature = () => {
const newSignatureState = !formbricksSignature;
setFormbricksSignature(newSignatureState);
triggerProductMutate({ formbricksSignature: newSignatureState })
.then(() => {
toast.success(newSignatureState ? "Formbricks signature shown." : "Formbricks signature hidden.");
})
.catch((error) => {
toast.error(`Error: ${error.message}`);
});
};
if (isLoadingEnvironment || isLoadingProduct) {
return <LoadingSpinner />;
}
if (isErrorEnvironment) {
if (isErrorEnvironment || isErrorProduct) {
return <ErrorComponent />;
}
return (
<div className="w-full items-center">
<div className="flex items-center space-x-2">
<Switch disabled id="signature" />
<Label htmlFor="signature">Show Formbricks Signature</Label>
if (formbricksSignature !== null) {
return (
<div className="w-full items-center">
<div className="flex items-center space-x-2">
<Switch
id="signature"
checked={formbricksSignature}
onCheckedChange={toggleSignature}
disabled={isMutatingProduct}
/>
<Label htmlFor="signature">Show &apos;Powered by Formbricks&apos; Signature</Label>
</div>
</div>
<Button type="submit" variant="darkCTA" className="mt-4" disabled>
Save
</Button>
{/* <div className="whitespace-pre-wrap">{JSON.stringify(environment, null, 2)}</div>; */}
</div>
);
);
}
return null;
}
@@ -11,14 +11,13 @@ export default function ProfileSettingsPage({ params }: { params: { environmentI
</SettingsCard>
<SettingsCard
soon
title="Survey Placement"
description="Change where surveys will be shown in your product.">
title="In-app Survey Placement"
description="Change where surveys will be shown in your web app.">
<EditPlacement environmentId={params.environmentId} />
</SettingsCard>
<SettingsCard
soon
title="Formbricks Signature"
description="As of now, there is no Formbricks branding on your surveys.">
description="We love your support but understand if you toggle it off.">
<EditFormbricksSignature environmentId={params.environmentId} />
</SettingsCard>
</div>
@@ -104,7 +104,7 @@ export default function EditAlerts({ memberships, user, environmentId }: EditAle
</div>
<p className="pb-3 pl-4 text-xs text-slate-400">
Want to loop in team mates?{" "}
<Link className="font-semibold" href={`/environments/${environmentId}/settings/notifications`}>
<Link className="font-semibold" href={`/environments/${environmentId}/settings/members`}>
Invite them.
</Link>
</p>
@@ -3,9 +3,11 @@ import Progress from "@/components/preview/Progress";
import QuestionConditional from "@/components/preview/QuestionConditional";
import ThankYouCard from "@/components/preview/ThankYouCard";
import { useEnvironment } from "@/lib/environments/environments";
import { useProduct } from "@/lib/products/products";
import type { Logic, Question } from "@formbricks/types/questions";
import { Survey } from "@formbricks/types/surveys";
import { useEffect, useState } from "react";
import FormbricksSignature from "@/components/preview/FormbricksSignature";
interface PreviewSurveyProps {
setActiveQuestionId: (id: string | null) => void;
@@ -28,11 +30,20 @@ export default function PreviewSurvey({
thankYouCard,
previewType,
}: PreviewSurveyProps) {
const { environment } = useEnvironment(environmentId);
const { product } = useProduct(environmentId);
const [isModalOpen, setIsModalOpen] = useState(true);
const [progress, setProgress] = useState(0); // [0, 1]
const [widgetSetupCompleted, setWidgetSetupCompleted] = useState(false);
const { environment } = useEnvironment(environmentId);
const [lastActiveQuestionId, setLastActiveQuestionId] = useState("");
const [showFormbricksSignature, setShowFormbricksSignature] = useState(false);
useEffect(() => {
if (product) {
setShowFormbricksSignature(product.formbricksSignature);
}
}, [product]);
useEffect(() => {
if (activeQuestionId) {
@@ -155,15 +166,6 @@ export default function PreviewSurvey({
}
};
/* const resetPreview = () => {
setIsModalOpen(false);
setTimeout(() => {
setActiveQuestionId(questions[0].id);
setIsModalOpen(true);
}, 500);
};
*/
useEffect(() => {
if (environment && environment.widgetSetupCompleted) {
setWidgetSetupCompleted(true);
@@ -217,6 +219,7 @@ export default function PreviewSurvey({
) : null
)
)}
{showFormbricksSignature && <FormbricksSignature />}
</div>
<Progress progress={progress} brandColor={brandColor} />
</Modal>
@@ -246,8 +249,9 @@ export default function PreviewSurvey({
</div>
</div>
<div className="z-10 w-full rounded-b-lg bg-white">
<div className="mx-auto max-w-md p-6 pt-4">
<div className="mx-auto max-w-md space-y-6 p-6 pt-4">
<Progress progress={progress} brandColor={brandColor} />
{showFormbricksSignature && <FormbricksSignature />}
</div>
</div>
</div>
+4 -1
View File
@@ -1,5 +1,6 @@
"use client";
import FormbricksSignature from "@/components/preview/FormbricksSignature";
import Progress from "@/components/preview/Progress";
import QuestionConditional from "@/components/preview/QuestionConditional";
import ThankYouCard from "@/components/preview/ThankYouCard";
@@ -16,6 +17,7 @@ import { useEffect, useState } from "react";
type EnhancedSurvey = Survey & {
brandColor: string;
formbricksSignature: boolean;
};
interface LinkSurveyProps {
@@ -235,8 +237,9 @@ export default function LinkSurvey({ survey }: LinkSurveyProps) {
</ContentWrapper>
</div>
<div className="top-0 z-10 w-full border-b bg-white">
<div className="mx-auto max-w-md p-6">
<div className="mx-auto max-w-md space-y-6 p-6">
<Progress progress={progress} brandColor={survey.brandColor} />
{survey.formbricksSignature && <FormbricksSignature />}
</div>
</div>
</>
@@ -0,0 +1,15 @@
export default function FormbricksSignature() {
return (
<a
href="https://formbricks.com?utm_source=survey_branding"
target="_blank"
className="mt-4 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>
);
}
@@ -32,21 +32,6 @@ export default function ThankYouCard({ headline, subheader, brandColor }: ThankY
<Headline headline={headline} questionId="thankYouCard" />
<Subheader subheader={subheader} questionId="thankYouCard" />
</div>
{/* <span
className="mb-[10px] mt-[35px] inline-block h-[2px] w-4/5 rounded-full opacity-25"
style={{ backgroundColor: brandColor }}></span>
<div>
<p className="text-xs text-slate-500">
Powered by{" "}
<b>
<a href="https://formbricks.com" target="_blank" className="hover:text-slate-700">
Formbricks
</a>
</b>
</p>
</div> */}
</div>
);
}
+3 -1
View File
@@ -202,12 +202,14 @@ export const getSettings = async (environmentId: string, personId: string): Prom
product: {
select: {
brandColor: true,
formbricksSignature: true,
},
},
},
});
const formbricksSignature = environmentProdut?.product.formbricksSignature;
const brandColor = environmentProdut?.product.brandColor;
return { surveys, noCodeEvents, brandColor };
return { surveys, noCodeEvents, brandColor, formbricksSignature };
};
@@ -46,17 +46,27 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
},
select: {
brandColor: true,
formbricksSignature: true,
},
});
if (survey.status !== "inProgress") {
return res
.status(403)
.json({ message: "Survey not running", reason: survey.status, brandColor: product?.brandColor });
return res.status(403).json({
message: "Survey not running",
reason: survey.status,
brandColor: product?.brandColor,
formbricksSignature: product?.formbricksSignature,
});
}
// if survey exists, return survey
return res.status(200).json({ ...survey, brandColor: product?.brandColor });
return res
.status(200)
.json({
...survey,
brandColor: product?.brandColor,
formbricksSignature: product?.formbricksSignature,
});
}
// Unknown HTTP Method
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Product" ADD COLUMN "formbricksSignature" BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Product" ALTER COLUMN "formbricksSignature" SET DEFAULT true;
+10 -9
View File
@@ -238,15 +238,16 @@ model Environment {
}
model Product {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId String
environments Environment[]
brandColor String @default("#64748b")
recontactDays Int @default(7)
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId String
environments Environment[]
brandColor String @default("#64748b")
recontactDays Int @default(7)
formbricksSignature Boolean @default(true)
}
enum Plan {
+1 -7
View File
@@ -25,13 +25,7 @@ export default function App({ config, survey, closeSurvey, errorHandler }: AppPr
return (
<div id="fbjs">
<Modal isOpen={isOpen} close={close}>
<SurveyView
config={config}
survey={survey}
close={close}
brandColor={config.settings?.brandColor}
errorHandler={errorHandler}
/>
<SurveyView config={config} survey={survey} close={close} errorHandler={errorHandler} />
</Modal>
</div>
);
@@ -0,0 +1,17 @@
import { h } from "preact";
export default function FormbricksSignature() {
return (
<a
href="https://formbricks.com?utm_source=survey_branding"
target="_blank"
className="fb-mb-5 fb-mt-2 fb-flex fb-justify-center">
<p className="fb-text-xs fb-text-slate-400">
Powered by{" "}
<b>
<span className="fb-text-slate-500 fb-hover:text-slate-700">Formbricks</span>
</b>
</p>
</a>
);
}
+5 -4
View File
@@ -10,16 +10,16 @@ import { cn } from "../lib/utils";
import Progress from "./Progress";
import QuestionConditional from "./QuestionConditional";
import ThankYouCard from "./ThankYouCard";
import FormbricksSignature from "./FormbricksSignature";
interface SurveyViewProps {
config: JsConfig;
survey: Survey;
close: () => void;
brandColor: string;
errorHandler: IErrorHandler;
}
export default function SurveyView({ config, survey, close, brandColor, errorHandler }: SurveyViewProps) {
export default function SurveyView({ config, survey, close, errorHandler }: SurveyViewProps) {
const [activeQuestionId, setActiveQuestionId] = useState(survey.questions[0].id);
const [progress, setProgress] = useState(0); // [0, 1]
const [responseId, setResponseId] = useState<string | null>(null);
@@ -185,7 +185,7 @@ export default function SurveyView({ config, survey, close, brandColor, errorHan
activeQuestionId === question.id && (
<QuestionConditional
key={question.id}
brandColor={brandColor}
brandColor={config.settings?.brandColor}
lastQuestion={idx === survey.questions.length - 1}
onSubmit={submitResponse}
question={question}
@@ -194,7 +194,8 @@ export default function SurveyView({ config, survey, close, brandColor, errorHan
)
)}
</div>
<Progress progress={progress} brandColor={brandColor} />
{config.settings?.formbricksSignature && <FormbricksSignature />}
<Progress progress={progress} brandColor={config.settings?.brandColor} />
</div>
);
}
@@ -33,21 +33,6 @@ export default function ThankYouCard({ headline, subheader, brandColor }: ThankY
<Headline headline={headline} questionId="thankYouCard" style={{ "margin-right": 0 }} />
<Subheader subheader={subheader} questionId="thankYouCard" />
</div>
{/* <span
className="fb-inline-block fb-w-4/5 fb-h-[2px] fb-mt-[35px] fb-mb-[10px]"
style={{ backgroundColor: brandColor }}></span>
<div>
<p className="fb-text-xs fb-text-slate-500">
Powered by{" "}
<b>
<a href="https://formbricks.com" target="_blank" className="hover:text-slate-700">
Formbricks
</a>
</b>
</p>
</div> */}
</div>
);
}
+1
View File
@@ -52,6 +52,7 @@ export interface Settings {
surveys?: Survey[];
noCodeEvents?: any[];
brandColor?: string;
formbricksSignature?: boolean;
}
export interface JsConfig {