Add Integrations Page UI

Add Integrations Page UI
This commit is contained in:
Johannes
2023-07-12 12:25:37 -05:00
committed by GitHub
23 changed files with 96 additions and 563 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,
@@ -120,12 +121,12 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
icon: FilterIcon,
current: pathname?.includes("/events") || pathname?.includes("/attributes"),
},
/* {
{
name: "Integrations",
href: `/environments/${environmentId}/integrations/installation`,
href: `/environments/${environmentId}/integrations`,
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-800">{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,9 @@
import ContentWrapper from "@/components/shared/ContentWrapper";
export default function IntegrationsLayout({ children }) {
return (
<>
<ContentWrapper>{children}</ContentWrapper>
</>
);
}

View File

@@ -1,3 +1,28 @@
import { Card } from "@formbricks/ui";
import Image from "next/image";
import JsLogo from "@/images/jslogo.png";
import ZapierLogo from "@/images/zapier-small.png";
export default function IntegrationsPage() {
return <div>Integrations</div>;
return (
<div>
<h1 className="my-2 text-3xl font-bold text-slate-800">Integrations</h1>
<p className="mb-6 text-slate-500">Connect Formbricks with your favorite tools.</p>
<div className="grid grid-cols-3 gap-6">
<Card
docsHref="https://formbricks.com/docs/getting-started/nextjs-app"
label="Javascript Widget"
description="Integrate Formbricks into your Webapp"
icon={<Image src={JsLogo} alt="Javascript Logo" />}
/>
<Card
docsHref="https://formbricks.com/docs/integrations/zapier"
connectHref="https://zapier.com/apps/formbricks/integrations"
label="Zapier"
description="Integrate Formbricks with 5000+ apps via Zapier"
icon={<Image src={ZapierLogo} alt="Zapier 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";

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1,42 +1,31 @@
import Link from "next/link";
import { Button } from "./Button";
interface CardProps {
onClick?: () => void;
href?: string;
title: string;
connectHref?: string;
docsHref?: string;
label: string;
description: string;
icon?: React.ReactNode;
className?: string;
children?: React.ReactNode;
}
export type { CardProps };
export const Card: React.FC<CardProps> = ({
href,
onClick,
title,
description,
icon,
className = "",
children,
}) =>
href ? (
<Link
href={href}
className={`hover:ring-brand-dark cursor-pointer rounded-lg bg-white p-8 text-left shadow-sm transition-all duration-150 ease-in-out hover:ring-1 ${className}`}>
<div className="float-right">{children}</div>
{icon && <div className="mb-6 h-8 w-8">{icon}</div>}
<h3 className="text-lg font-bold text-slate-800">{title}</h3>
<p className="text-xs text-slate-500">{description}</p>
</Link>
) : (
<button
onClick={onClick}
className={`hover:ring-brand-dark cursor-pointer rounded-lg bg-white p-8 text-left shadow-sm transition-all duration-150 ease-in-out hover:ring-1 ${className}`}>
<div className="float-right">{children}</div>
{icon && <div className="mb-6 h-8 w-8">{icon}</div>}
<h3 className="text-lg font-bold text-slate-800">{title}</h3>
<p className="text-xs text-slate-500">{description}</p>
</button>
);
export const Card: React.FC<CardProps> = ({ connectHref, docsHref, label, description, icon }) => (
<div className="rounded-lg bg-white p-8 text-left shadow-sm ">
{icon && <div className="mb-6 h-8 w-8">{icon}</div>}
<h3 className="text-lg font-bold text-slate-800">{label}</h3>
<p className="text-xs text-slate-500">{description}</p>
<div className="mt-4 flex space-x-2">
{connectHref && (
<Button href={connectHref} target="_blank" size="sm" variant="darkCTA">
Connect
</Button>
)}
{docsHref && (
<Button href={docsHref} target="_blank" size="sm" variant="secondary">
Docs
</Button>
)}
</div>
</div>
);

View File

@@ -1,11 +0,0 @@
interface PageTitleProps {
children: React.ReactNode;
}
export const PageTitle: React.FC<PageTitleProps> = ({ children }: { children: React.ReactNode }) => {
return (
<div className="mb-6 text-3xl font-bold text-slate-600">
<h1>{children}</h1>
</div>
);
};

View File

@@ -29,7 +29,6 @@ export { ErrorComponent } from "./components/ErrorComponent";
export { Input } from "./components/Input";
export { PasswordInput } from "./components/PasswordInput";
export { Label } from "./components/Label";
export { PageTitle } from "./components/PageTitle";
export { Popover, PopoverTrigger, PopoverContent } from "./components/Popover";
export { ProgressBar, HalfCircle } from "./components/ProgressBar";
export { RadioGroup, RadioGroupItem } from "./components/RadioGroup";