add js integration

This commit is contained in:
Matthias Nannt
2023-06-26 16:24:03 +02:00
parent ff001e7ea1
commit 59481a7f5b
20 changed files with 167 additions and 517 deletions

View File

@@ -29,6 +29,7 @@ import { useTeam } from "@/lib/teams/teams";
import { capitalizeFirstLetter, truncate } from "@/lib/utils";
import {
CustomersIcon,
DashboardIcon,
ErrorComponent,
FilterIcon,
FormIcon,
@@ -117,12 +118,12 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
icon: FilterIcon,
current: pathname?.includes("/events") || pathname?.includes("/attributes"),
},
/* {
{
name: "Integrations",
href: `/environments/${environmentId}/integrations/installation`,
icon: DashboardIcon,
current: pathname?.includes("/integrations"),
}, */
},
{
name: "Settings",
href: `/environments/${environmentId}/settings/profile`,

View File

@@ -2,7 +2,7 @@ import { Button } from "@formbricks/ui";
export default function DocsSidebar() {
return (
<div className="w-fit rounded-lg border border-slate-300 bg-slate-200 p-8 pr-16">
<div className="w-20 min-w-max rounded-lg border border-slate-200 bg-slate-100 p-8">
<p className="font-bold text-slate-700">Documentation</p>
<p className="text-xs text-slate-500">Get detailed instructions</p>
<Button className="my-2" href="https://formbricks.com/docs" target="_blank">

View File

@@ -0,0 +1,31 @@
import { BackIcon } from "@formbricks/ui";
import Link from "next/link";
interface IntegrationPageTitleProps {
title: string;
icon?: React.ReactNode;
environmentId: string;
}
const IntegrationPageTitle: React.FC<IntegrationPageTitleProps> = ({ title, icon, environmentId }) => {
return (
<div className="flex justify-between">
<div className="mb-8">
<Link className="inline-block" href={`/environments/${environmentId}/integrations/`}>
<BackIcon className="mb-2 h-6 w-6" />
</Link>
<div className="my-4 flex items-baseline">
{icon && <div className="h-6 w-6">{icon}</div>}
<h1 className="ml-3 text-2xl font-bold text-slate-600">{title}</h1>
</div>
</div>
{/* <div className="flex items-center space-x-2">
<Switch id="integration-enabled" />
<Label htmlFor="integration-enabled">Enabled</Label>
</div> */}
</div>
);
};
export default IntegrationPageTitle;

View File

@@ -1,11 +0,0 @@
import ContentWrapper from "@/components/shared/ContentWrapper";
import IntegrationsTabs from "@/components/integrations/IntegrationsTabs";
export default function SettingsLayout({ children, params }) {
return (
<>
<IntegrationsTabs activeId="alerts" environmentId={params.environmentId} />
<ContentWrapper>{children}</ContentWrapper>
</>
);
}

View File

@@ -1,35 +0,0 @@
import SlackLogo from "@/images/slacklogo.png";
import { Card, EmailIcon, PageTitle } from "@formbricks/ui";
import Image from "next/image";
import Link from "next/link";
export default function EventsAttributesPage({ params }) {
return (
<div>
<PageTitle>Team Alerts</PageTitle>
<div className="grid grid-cols-3 gap-6">
{/* <Card
href={`/environments/${params.environmentId}/integrations/alerts/email`}
title="Email Notifications"
description="Keep your team in the loop with email notifications."
icon={<EmailIcon />}
/> */}
<Card
href={`/environments/${params.environmentId}/integrations/alerts/slack`}
title="Slack"
description="Surface insights in dedicated Slack channels."
icon={<Image src={SlackLogo} alt="Slack Logo" />}
/>
<Link
href={`/environments/${params.environmentId}/settings/notifications`}
className="hover:ring-brand-dark cursor-pointer rounded-lg bg-slate-100 p-8 text-left shadow-sm transition-all duration-150 ease-in-out hover:ring-1">
<div className="mb-6 h-8 w-8">
<EmailIcon />
</div>
<h3 className="text-lg font-bold text-slate-800">Looking for email?</h3>
<p className="text-xs text-slate-500">Change your notification settings.</p>
</Link>
</div>
</div>
);
}

View File

@@ -1,82 +0,0 @@
"use client";
import Modal from "@/components/shared/Modal";
import { Button, Checkbox, Input, Label } from "@formbricks/ui";
type AddEmailAlertModalProps = {
open: boolean;
setOpen: (v: boolean) => void;
};
const AddEmailAlertModal: React.FC<AddEmailAlertModalProps> = ({ open, setOpen }) => {
const surveys = [
{ label: "Survey 1", id: "1" },
{ label: "Survey 2", id: "2" },
{ label: "Survey 3", id: "3" },
];
const onTest = () => {
throw Error("not implemented");
};
const onSave = () => {
throw Error("not implemented");
};
return (
<>
<Modal open={open} setOpen={setOpen} title="Add Slack Alert">
<form className="space-y-6">
<div>
<Label>Alert name</Label>
<Input type="text" placeholder="e.g. Product Team Info" />
</div>
<div>
<Label>End Point URL</Label>
<Input type="URL" placeholder="https://hooks.slack.com/service/ABC123/ASD213ADS" />
</div>
<div>
<Label className="block">Trigger Event</Label>
<Label className="font-normal text-slate-400">
Send message every time one of the surveys receives a response:
</Label>
<div className="mt-2 rounded bg-slate-50 p-6 ">
<div className="flex items-center space-x-2">
<Checkbox id="all" />
<label
htmlFor="all"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
All surveys
</label>
</div>
<hr className="my-2" />
{surveys.map((survey) => (
<div key={survey.id} className="flex items-center space-x-2">
<Checkbox className="my-1" id={survey.id} />
<label
htmlFor="all"
className="text-sm font-medium leading-none text-slate-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
{survey.label}
</label>
</div>
))}
</div>
</div>
<div className="flex justify-end space-x-2">
<Button variant="minimal" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button variant="secondary" onClick={onTest}>
Send Test
</Button>
<Button variant="darkCTA" onClick={onSave}>
Save
</Button>
</div>
</form>
</Modal>
</>
);
};
export default AddEmailAlertModal;

View File

@@ -1,56 +0,0 @@
"use client";
import { AddAlertButton } from "@/components/integrations/AddAlertButton";
import AlertCard from "@/components/integrations/AlertCard";
import IntegrationPageTitle from "@/components/integrations/IntegrationsPageTitle";
import SlackLogo from "@/images/slacklogo.png";
import Image from "next/image";
import AddSlackAlertModal from "./AddSlackAlertModal";
import DeleteDialog from "@/components/shared/DeleteDialog";
import { useState } from "react";
export default function SlackAlertPage({ params }) {
const exampleAlert = {
href: "/",
title: "Example Alert",
description: "This is an example alert",
};
const [isAlertModalOpen, setAlertModalOpen] = useState(false);
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
const handleAddAlertClick = async () => {
setAlertModalOpen(true);
};
const handleDeleteAlertClick = async () => {
setDeleteDialogOpen(true);
};
const deleteEmailAlert = async () => {
setDeleteDialogOpen(false);
};
return (
<div>
<IntegrationPageTitle environmentId={params.environmentId} title="Slack Alerts" goBackTo="alerts" />
<div className="grid grid-cols-3 gap-6">
<AlertCard
onDelete={handleDeleteAlertClick}
onEdit={handleAddAlertClick}
title={exampleAlert.title}
description={exampleAlert.description}
icon={<Image src={SlackLogo} alt="Slack Logo" />}
/>
<AddAlertButton channel="Slack" onClick={() => handleAddAlertClick()} />
</div>
<AddSlackAlertModal open={isAlertModalOpen} setOpen={setAlertModalOpen} />
<DeleteDialog
deleteWhat="Email Alert"
open={isDeleteDialogOpen}
setOpen={setDeleteDialogOpen}
onDelete={deleteEmailAlert}
/>
</div>
);
}

View File

@@ -1,11 +0,0 @@
import IntegrationsTabs from "@/components/integrations/IntegrationsTabs";
import ContentWrapper from "@/components/shared/ContentWrapper";
export default function EventsAttributesPage({ params }) {
return (
<div className="">
<IntegrationsTabs activeId="data" environmentId={params.environmentId} />
<ContentWrapper>Data</ContentWrapper>
</div>
);
}

View File

@@ -1,75 +0,0 @@
import DocsSidebar from "@/components/integrations/DocsSidebar";
import IntegrationPageTitle from "@/components/integrations/IntegrationsPageTitle";
import JSLogo from "@/images/jslogo.png";
import { Input } from "@formbricks/ui";
import Image from "next/image";
export default function JavaScriptPage({ params }) {
/* useEffect(() => {
Prism.highlightAll();
}, []); */
return (
<div>
<IntegrationPageTitle
environmentId={params.environmentId}
title="JavaScript Snippet"
icon={<Image src={JSLogo} alt="JavaScript Logo" />}
goBackTo="installation"
/>
<div className="grid grid-cols-3 gap-6">
<div className="col-span-2">
<div>
<h3 className="text-xl font-bold text-slate-800">Quick Start</h3>
<ol className="my-4 ml-2 list-decimal text-slate-900">
<li>Copy the Javascript snippet below into the HEAD of your HTML file.</li>
<li>Set up a button with the onClick handler below to let your users open the widget.</li>
<li>PLACEHOLDER</li>
</ol>
<div className="flex">
<div className="mr-6">
<p className="font-bold text-slate-600">Production ID</p>
<Input type="text" className="rounded border border-slate-200 bg-slate-100" />
</div>
<div>
<p className="font-bold text-slate-600">Development ID</p>
<Input type="text" className="rounded border border-slate-200 bg-slate-100" />
</div>
</div>
</div>
<div>
<h3 className="mt-12 text-xl font-bold text-slate-800">JavaScript Snippet</h3>
<div className="col-span-3 rounded-md bg-black p-4 text-sm font-light text-slate-200">
<pre>
<code className="language-html whitespace-pre-wrap">
{`<!--HTML header script -->
<script src="https://cdn.jsdelivr.net/npm/@formbricks/feedback@0.2" defer></script>
<script>
window.formbricks = {
...window.formbricks,
config: {
hqUrl: "https://app.formbricks.com",
formId: "YOUR FEEDBACK BOX ID HERE", // copy from Formbricks dashboard
contact: {
name: "Matti",
position: "Co-Founder",
imgUrl: "https://avatars.githubusercontent.com/u/675065?s=128&v=4",
},
},
}
</script>
`}
</code>
</pre>
</div>
</div>
</div>
<div className="col-span-1">
<DocsSidebar />
</div>
</div>
</div>
);
}

View File

@@ -1,11 +0,0 @@
import ContentWrapper from "@/components/shared/ContentWrapper";
import IntegrationsTabs from "@/components/integrations/IntegrationsTabs";
export default function InstallationsLayout({ children, params }) {
return (
<>
<IntegrationsTabs activeId="installation" environmentId={params.environmentId} />
<ContentWrapper>{children}</ContentWrapper>
</>
);
}

View File

@@ -1,75 +0,0 @@
import DocsSidebar from "@/components/integrations/DocsSidebar";
import IntegrationPageTitle from "@/components/integrations/IntegrationsPageTitle";
import NPMLogo from "@/images/npmlogo.png";
import { Input } from "@formbricks/ui";
import Image from "next/image";
export default function NPMPage({ params }) {
/* useEffect(() => {
Prism.highlightAll();
}, []); */
return (
<div>
<IntegrationPageTitle
environmentId={params.environmentId}
title="NPM Install"
icon={<Image src={NPMLogo} alt="NPM Logo" />}
goBackTo="installation"
/>
<div className="grid grid-cols-3 gap-6">
<div className="col-span-2">
<div>
<h3 className="text-xl font-bold text-slate-800">Quick Start</h3>
<ol className="my-4 ml-2 list-decimal text-slate-900">
<li>Copy the Javascript snippet below into the HEAD of your HTML file.</li>
<li>Set up a button with the onClick handler below to let your users open the widget.</li>
<li>PLACEHOLDER</li>
</ol>
<div className="flex">
<div className="mr-6">
<p className="font-bold text-slate-600">Production ID</p>
<Input type="text" className="rounded border border-slate-200 bg-slate-100" />
</div>
<div>
<p className="font-bold text-slate-600">Development ID</p>
<Input type="text" className="rounded border border-slate-200 bg-slate-100" />
</div>
</div>
</div>
<div>
<h3 className="mt-12 text-xl font-bold text-slate-800">JavaScript Snippet</h3>
<div className="col-span-3 rounded-md bg-black p-4 text-sm font-light text-slate-200">
<pre>
<code className="language-html whitespace-pre-wrap">
{`<!--HTML header script -->
<script src="https://cdn.jsdelivr.net/npm/@formbricks/feedback@0.2" defer></script>
<script>
window.formbricks = {
...window.formbricks,
config: {
hqUrl: "https://app.formbricks.com",
formId: "YOUR FEEDBACK BOX ID HERE", // copy from Formbricks dashboard
contact: {
name: "Matti",
position: "Co-Founder",
imgUrl: "https://avatars.githubusercontent.com/u/675065?s=128&v=4",
},
},
}
</script>
`}
</code>
</pre>
</div>
</div>
</div>
<div className="col-span-1">
<DocsSidebar />
</div>
</div>
</div>
);
}

View File

@@ -1,26 +0,0 @@
import JSLogo from "@/images/jslogo.png";
import NPMLogo from "@/images/npmlogo.png";
import { Card, PageTitle } from "@formbricks/ui";
import Image from "next/image";
export default function InstallationsPage({ params }) {
return (
<div>
<PageTitle>Installation</PageTitle>
<div className="grid grid-cols-3 gap-6">
<Card
href={`/environments/${params.environmentId}/integrations/installation/javascript`}
title="JavaScript"
description="Copy the Formbricks snippet into your HTML <head>."
icon={<Image src={JSLogo} alt="JavaScript Logo" />}
/>
<Card
href={`/environments/${params.environmentId}/integrations/installation/npm`}
title="NPM"
description="Use NPM or yarn to install the Formbricks SDK."
icon={<Image src={NPMLogo} alt="NPM Logo" />}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,103 @@
import DocsSidebar from "../DocsSidebar";
import IntegrationPageTitle from "../IntegrationsPageTitle";
import CodeBlock from "@/components/shared/CodeBlock";
import JsLogo from "@/images/jslogo.png";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import Image from "next/image";
import Link from "next/link";
export default function JsIntegrationPage({ params }) {
return (
<div>
<IntegrationPageTitle
environmentId={params.environmentId}
title="Javascript Integration"
icon={<Image src={JsLogo} alt="Javascript Logo" />}
/>
<div className="flex justify-between gap-8">
<div>
<p className="mb-10 text-slate-800">
The Formbricks Javascript Widget is the easiest way to integrate Formbricks into your web
application. Once embedded, the SDK allows you to use all the Formbricks features like no code
actions, show in-app surveys and synchronizing your user data with Formbricks.
</p>
<div className="prose prose-slate">
<p className="text-lg font-semibold text-slate-800">Step 1: NPM Install</p>
<CodeBlock language="sh">npm install @formbricks/js --save</CodeBlock>
<p className="pt-4 text-lg font-semibold text-slate-800">Step 2: Initialize widget</p>
<p>Import Formbricks and initialize the widget in your Component (e.g. App.tsx):</p>
<CodeBlock language="js">{`import formbricks from "@formbricks/js";
if (typeof window !== "undefined") {
formbricks.init({
environmentId: "${params.environmentId}",
apiHost: "${WEBAPP_URL}",
logLevel: "debug", // remove when in production
});
}`}</CodeBlock>
<ul className="list-disc">
<li>
<span className="font-semibold">environmentId:</span> Used to identify the correct
environment: {params.environmentId} is yours.
</li>
<li>
<span className="font-semibold">apiHost:</span> This is the URL of your Formbricks backend.
</li>
</ul>
<p className="text-lg font-semibold text-slate-800">You&apos;re done 🎉</p>
<p>
Your app now communicates with Formbricks - sending events, and loading surveys automatically!
</p>
<ul className="list-disc text-slate-700">
<li>
<span className="font-semibold">Does your widget work? </span>
<span>Scroll to the top!</span>
</li>
<li>
<span className="font-semibold">
Need a more detailed setup guide for React, Next.js or Vue.js?
</span>{" "}
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/getting-started/quickstart"
target="_blank">
Check out the docs.
</Link>
</li>
<li>
<span className="font-semibold">Have a problem?</span>{" "}
<Link
className="decoration-brand-dark"
target="_blank"
href="https://github.com/formbricks/formbricks/issues">
Open an issue on GitHub
</Link>{" "}
or{" "}
<Link className="decoration-brand-dark" href="https://formbricks.com/discord" target="_blank">
join Discord.
</Link>
</li>
<li>
<span className="font-semibold">
Want to learn how to add user attributes, custom events and more?
</span>{" "}
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/attributes/why"
target="_blank">
Dive into the docs.
</Link>
</li>
</ul>
</div>
</div>
<div>
<DocsSidebar />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import ContentWrapper from "@/components/shared/ContentWrapper";
export default function IntegrationsLayout({ children }) {
return (
<>
<ContentWrapper>{children}</ContentWrapper>
</>
);
}

View File

@@ -1,3 +1,19 @@
export default function IntegrationsPage() {
return <div>Integrations</div>;
import { Card, PageTitle } from "@formbricks/ui";
import Image from "next/image";
import JsLogo from "@/images/jslogo.png";
export default function IntegrationsPage({ params }) {
return (
<div>
<PageTitle>Integrations</PageTitle>
<div className="grid grid-cols-3 gap-6">
<Card
href={`/environments/${params.environmentId}/integrations/js`}
title="Javascript Widget"
description="Integrate Formbricks into your Webapp"
icon={<Image src={JsLogo} alt="Javascript Logo" />}
/>
</div>
</div>
);
}

View File

@@ -1,24 +0,0 @@
"use client";
import { PlusCircleIcon } from "@heroicons/react/24/solid";
interface AddAlertButtonProps {
channel: string;
onClick?: () => void;
}
export const AddAlertButton: React.FC<AddAlertButtonProps> = ({ channel, onClick = () => {} }) => {
return (
<button
onClick={onClick}
className="hover:border-brand-dark cursor-pointer rounded-lg border-2 border-dashed border-slate-300 p-8 transition-all duration-150 ease-in-out">
<div className="flex w-full justify-center">
<div className="mb-4 h-10 w-10 text-center">
<PlusCircleIcon className="text-brand-dark" />
</div>
</div>
<h3 className="text-lg font-bold text-slate-600">Add {channel} Alert</h3>
<p className="text-xs text-slate-400">Keep your team in the loop.</p>
</button>
);
};

View File

@@ -1,42 +0,0 @@
import React from "react";
import { Card } from "@formbricks/ui";
import type { CardProps } from "@formbricks/ui";
import { TrashIcon, PencilSquareIcon } from "@heroicons/react/24/outline";
interface AlertCardProps extends CardProps {
onDelete?: () => void;
onEdit?: () => void;
}
const AlertCard: React.FC<AlertCardProps> = ({ title, description, icon, onDelete, onEdit }) => (
<div className="relative">
<div className="absolute right-6 top-6">
{onDelete && (
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDelete();
}}>
<TrashIcon className="mr-2 h-7 w-7 p-1 text-slate-500 hover:text-red-600" />
</button>
)}
{onEdit && (
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onEdit();
}}>
<PencilSquareIcon className="h-7 w-7 p-1 text-slate-500 hover:text-slate-800" />
</button>
)}
</div>
<Card onClick={onEdit} title={title} description={description} icon={icon} className="w-full" />
</div>
);
export default AlertCard;

View File

@@ -1,31 +0,0 @@
import { BackIcon } from "@formbricks/ui";
import Link from "next/link";
interface IntegrationPageTitleProps {
title: string;
icon?: React.ReactNode;
goBackTo: string;
environmentId: string;
}
const IntegrationPageTitle: React.FC<IntegrationPageTitleProps> = ({
title,
icon,
goBackTo,
environmentId,
}) => {
return (
<div className="mb-8">
<Link className="inline-block" href={`/environments/${environmentId}/integrations/${goBackTo}`}>
<BackIcon className="mb-2 h-6 w-6" />
</Link>
<div className="my-4 flex items-baseline">
{icon && <div className="h-6 w-6">{icon}</div>}
<h1 className="ml-3 text-2xl font-bold text-slate-600">{title}</h1>
</div>
</div>
);
};
export default IntegrationPageTitle;

View File

@@ -1,32 +0,0 @@
import SecondNavbar from "../environments/SecondNavBar";
import { CodeBracketSquareIcon, MegaphoneIcon, ArrowPathIcon } from "@heroicons/react/24/solid";
interface IntegrationsTabs {
activeId: string;
environmentId: string;
}
export default function PeopleGroupsTabs({ activeId, environmentId }: IntegrationsTabs) {
const tabs = [
{
id: "installation",
label: "Installation",
icon: <CodeBracketSquareIcon />,
href: `/environments/${environmentId}/integrations/installation`,
},
{
id: "alerts",
label: "Team Alerts",
icon: <MegaphoneIcon />,
href: `/environments/${environmentId}/integrations/alerts`,
},
{
id: "data",
label: "Data Sync",
icon: <ArrowPathIcon />,
href: `/environments/${environmentId}/integrations/data`,
},
];
return <SecondNavbar tabs={tabs} activeId={activeId} />;
}

View File

@@ -1,4 +1,5 @@
// components/ui/CodeBlock.tsx
"use client";
import { DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import Prism from "prismjs";
import "prismjs/themes/prism.css";