Update Docs, add Walkthrough Video to App & LP (#212)

* update LP with walk through

* update docs with attributes and events

* update team UI + form validation

* add walk through to app

* small fixes

---------

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Johannes
2023-03-30 17:24:04 +02:00
committed by GitHub
parent a01b01d065
commit fbcc9d5628
40 changed files with 378 additions and 94 deletions

View File

@@ -64,9 +64,9 @@ function Header({ navigation }: any) {
onClick={() => router.push("https://github.com/formbricks/formbricks")}>
View on Github
</Button>
<Button variant="highlight" className="ml-2" onClick={() => router.push("/waitlist")}>
{/* <Button variant="highlight" className="ml-2" onClick={() => router.push("/waitlist")}>
Get started
</Button>
</Button> */}
</div>
</header>
);
@@ -124,15 +124,6 @@ export function Layout({ children, meta }: LayoutProps) {
</header>
)}
<Prose className="">{children}</Prose>
<div className="mt-16 rounded-xl border-2 border-slate-200 bg-slate-300 p-8 dark:border-slate-700/50 dark:bg-slate-800">
<h4 className="text-3xl font-semibold text-slate-500 dark:text-slate-50">Need help?</h4>
<p className="my-4 text-slate-500 dark:text-slate-400">
Join our Discord and ask away. We&apos;re happy to help where we can!
</p>
<Button variant="highlight" href="/discord" target="_blank">
Join Discord
</Button>
</div>
</article>
<dl className="mt-12 flex border-t border-slate-200 pt-6 dark:border-slate-800">
{previousPage && (
@@ -164,6 +155,15 @@ export function Layout({ children, meta }: LayoutProps) {
</div>
)}
</dl>
<div className="mt-16 rounded-xl border-2 border-slate-200 bg-slate-300 p-8 dark:border-slate-700/50 dark:bg-slate-800">
<h4 className="text-3xl font-semibold text-slate-500 dark:text-slate-50">Need help?</h4>
<p className="my-4 text-slate-500 dark:text-slate-400">
Join our Discord and ask away. We&apos;re happy to help where we can!
</p>
<Button variant="highlight" href="/discord" target="_blank">
Join Discord
</Button>
</div>
</div>
</div>
</>

View File

@@ -1,8 +1,16 @@
import TemplateList from "../dummyUI/TemplateList";
import { Button } from "@formbricks/ui";
import { useState } from "react";
import { useRouter } from "next/router";
import VideoWalkThrough from "./VideoWalkThrough";
import { PlayCircleIcon } from "@heroicons/react/24/solid";
interface Props {}
export default function Hero({}: Props) {
const router = useRouter();
const [videoModal, setVideoModal] = useState(false);
return (
<div className="relative">
<div className="px-4 py-20 text-center sm:px-6 lg:px-8 lg:py-28">
@@ -21,18 +29,28 @@ export default function Hero({}: Props) {
Continuously measure what your customers think and feel. All open-source.
</span>
</p>
{/*
<div className="mx-auto mt-5 max-w-md sm:flex sm:justify-center md:mt-8">
<Button variant="secondary" className="" onClick={() => router.push("#best-practices")}>
Best practices
<Button
variant="highlight"
className="mr-3 px-6"
onClick={() => setVideoModal(true)}
EndIcon={PlayCircleIcon}
endIconClassName=" ml-2">
Watch video
</Button>
<Button variant="highlight" className="ml-3 px-6" onClick={() => router.push("/waitlist")}>
Get Access
</Button>
</div> */}
</div>
</div>
<TemplateList />
<VideoWalkThrough open={videoModal} setOpen={() => setVideoModal(false)} />
</div>
);
}
function GitHubIcon(props: any) {
return (
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
<path d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" />
</svg>
);
}

View File

@@ -0,0 +1,17 @@
import { ResponsiveVideo } from "@formbricks/ui";
import Modal from "../shared/Modal";
interface VideoWalkThroughProps {
open: boolean;
setOpen: (v: boolean) => void;
}
export default function VideoWalkThrough({ open, setOpen }: VideoWalkThroughProps) {
return (
<Modal open={open} setOpen={setOpen}>
<div className="mt-5">
<ResponsiveVideo src="/videos/walkthrough-v1.mp4" />
</div>
</Modal>
);
}

View File

@@ -46,8 +46,9 @@ export default function Header() {
<Button
variant="secondary"
EndIcon={GitHubIcon}
endIconClassName="fill-slate-800 ml-2"
onClick={() => router.push("https://github.com/formbricks/formbricks")}>
endIconClassName="fill-slate-800 ml-2 dark:fill-slate-200"
href="https://github.com/formbricks/formbricks"
target="_blank">
View on Github
</Button>
{/* <Button variant="highlight" className="ml-2" onClick={() => router.push("/waitlist")}>

View File

@@ -52,7 +52,7 @@ export function Search() {
className="group flex h-6 w-6 items-center justify-center sm:justify-start md:h-auto md:w-60 md:flex-none md:rounded-lg md:py-2.5 md:pl-4 md:pr-3.5 md:text-sm md:ring-1 md:ring-slate-200 md:hover:ring-slate-300 dark:md:bg-slate-800/75 dark:md:ring-inset dark:md:ring-white/5 dark:md:hover:bg-slate-700/40 dark:md:hover:ring-slate-500 xl:w-80"
onClick={onOpen}>
<SearchIcon className="h-5 w-5 flex-none fill-slate-400 group-hover:fill-slate-500 dark:fill-slate-500 md:group-hover:fill-slate-400" />
<span className="sr-only md:not-sr-only md:ml-2 md:text-slate-500 md:dark:text-slate-400">
<span className="sr-only md:not-sr-only md:pl-2 md:text-slate-500 md:dark:text-slate-400">
Search docs
</span>
{modifierKey && (

View File

@@ -13,7 +13,22 @@ const navigation = [
{ title: "Quickstart", href: "/docs/getting-started/quickstart" },
{ title: "Setup with Next.js", href: "/docs/getting-started/nextjs" },
{ title: "Setup with Vue.js", href: "/docs/getting-started/vuejs" },
{ title: "Identify users", href: "/docs/getting-started/identify-users" },
],
},
{
title: "Attributes",
links: [
{ title: "Why Attributes?", href: "/docs/attributes/why" },
{ title: "Custom Attributes", href: "/docs/attributes/custom-attributes" },
{ title: "Identify users", href: "/docs/attributes/identify-users" },
],
},
{
title: "Events",
links: [
{ title: "Why Events?", href: "/docs/events/why" },
{ title: "No-Code Events", href: "/docs/events/no-code" },
{ title: "Code Events", href: "/docs/events/code" },
],
},
{

View File

@@ -40,6 +40,7 @@
"@types/prismjs": "^1.26.0",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@types/react-responsive-embed": "^2.1.0",
"autoprefixer": "^10.4.14",
"eslint": "8.36.0",
"eslint-config-formbricks": "workspace:*",

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

View File

@@ -0,0 +1,47 @@
import Image from "next/image";
import LayoutMdx from "@/components/shared/LayoutMdx";
import FormbricksSneak from "./formbricks-sneak.png";
import ResponsiveEmbed from "react-responsive-embed";
export const meta = {
title: "Video: Walk-through of the new Formbricks",
description: "The new, powerful Formbricks is almost ready!",
date: "2023-03-30",
};
_The new, powerful Formbricks is almost ready!_
<Image src={FormbricksSneak} alt="Sneakpeek into what the new Formbricks can do" className="rounded-lg" />
We've been working hard on getting a revamped Formbricks ready - we're almost there!
What you can do with it:
1. Design **any survey** you want
2. Trigger at any point in your app both **No Code** (page view, element click) and **Code** (hook `formbricks.track` into your event)
3. Pass custom user attributes to Formbricks to **segment your user base**
## Have a look:
<ResponsiveEmbed
src="https://www.tella.tv/video/clfrymq2f00sk0fjqd9r6btf1/embed?b=0&title=0&a=1&loop=0&t=0&muted=0"
allowFullScreen
className="rounded-lg"
/>
Formbricks is a lot more powerful than ever before! :mechanical_arm:
You can create:
- Onboarding surveys,
- PMF surveys,
- Churn surveys,
- Feature chaser,
- Feedback box,
- Identify customer goals,
- Measure task completion,
- etc, etc.
## Stay tuned, Formbricks Cloud goes live soon!
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;

View File

@@ -0,0 +1,25 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "Setting attributes with code",
};
One way to send attributes to Formbricks is in your code. In Formbricks, there are two special attributes for [user identification](/docs/attributes/identify-users)(user ID & email) and custom attributes. An example:
### Setting Custom User Attributes
You can use the setAttribute function to set any custom attribute for the user (e.g. name, plan, etc.):
```javascript
formbricks.setAttribute("Plan", "Pro");
```
Generally speaking, the setAttribute function works like this:
```javascript
formbricks.setAttribute("attribute_key", "attribute_value");
```
Where `attributeName` is the name of the attribute you want to set, and `attributeValue` is the value of the attribute you want to set.
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -0,0 +1,21 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "What are attributes and why are they useful?",
};
Surveying your user base without segmentation leads to weak results and survey fatigue. Attributes help you segment your users into groups.
## What are attributes?
Attributes are key-value pairs that you can set for each person individually. For example, the attribute "Plan" can be set to "Free" or "Paid".
## How do attributes work?
Attributes are sent from your application to Formbricks and are associated with the current user. We store it in our database and allow you to use it the next time you create a survey.
## Why are attributes useful?
Attributes help show surveys to the right group of people. For example, you can show a survey to all users who have a "Plan" attribute set to "Paid".
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -0,0 +1,23 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "Code Events",
};
Events can also be set in the code base. You can fire an event using `formbricks.track()`
```javascript
formbricks.track("Event Name");
```
Here is an example of how to fire an event when a user clicks a button:
```javascript
const handleClick = () => {
formbricks.track("Button Clicked");
};
return <button onClick={handleClick}>Click Me</button>;
```
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -0,0 +1,28 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "No-Code Events",
};
No-Code events can be set up within Formbricks with just a few clicks. There are three types of No-Code events:
## Page URL Event
The page URL event is triggered, when a user visits a specific page in your application. There are several match conditions:
- `exactMatch`: The URL should exactly match the provided string.
- `contains`: The URL should contain the specified string as a substring.
- `startsWith`: The URL should start with the specified string.
- `endsWith`: The URL should end with the specified string.
- `notMatch`: The URL should not match the specified condition.
- `notContains`: The URL should not contain the specified string as a substring.
## innerText Event (coming soon)
The innerText event checks if the `innerText` of a clicked HTML element matches a specific text, e.g. the label of a button.
## CSS Selector Event (coming soon)
The CSS Selector event checks if the provided CSS selector matches the selector of a clicked HTML element. The CSS selector can be a class, id or any other CSS selector within your website.
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -0,0 +1,21 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "What are events and why are they useful?",
};
You want to understand what your users think and feel during specific moments in the user journey. To be able to ask at exactly the right point in time, you need events.
## What are events?
Events are a little notification sent from your application to Formbricks. You decide which events are sent either in your [Code](/docs/events/code) or by setting up a [No-Code](/docs/events/no-code) event within Formbricks.
## How do events work?
When a predefined event happens in your app, the Formbricks widget notices. This event can then trigger a survey to be shown to the user and is stored in the database.
## Why are events useful?
Events help you to display your surveys at the right time. Later on, you will be able to segment your users based on the events they have triggered in the past. This way, you can create much more granular user segments, e.g. only target users that already have used a specific feature.
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -1,11 +1,9 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "Quickstart",
title: "Setting up Formbricks SDK with Next.js",
};
# Setting up Formbricks SDK with Next.js
This guide will walk you through the process of integrating the Formbricks SDK into a Next.js application. As the Formbricks SDK only works on the client side, it's essential to ensure proper integration to avoid any issues.
## Introduction

View File

@@ -80,8 +80,11 @@ For more detailed guides for different frameworks, check out our [Next.js](/docs
## Step 5: Verify your setup
After setting up the widget, head back to the Formbricks dashboard: 1. Navigate to **Settings** in the top menubar. 2. Check the **Setup Checklist** to ensure everything is working
correctly. If all items in the checklist are marked as complete, congratulations! You've successfully set up
Formbricks, and you're ready to start creating and customizing your in-product surveys.
After setting up the widget, head back to the Formbricks dashboard:
1. Navigate to **Settings** in the top menubar.
2. Check the **Setup Checklist** to ensure everything is working correctly.
If you see confetti and a green box saying "Receiving data" you've successfully set up Formbricks. You're ready to start creating and customizing your in-product surveys.
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -1,11 +1,9 @@
import { Layout } from "@/components/docs/Layout";
export const meta = {
title: "Quickstart",
title: "Setting up Formbricks SDK with Vue.js",
};
# Setting up Formbricks SDK with Vue.js
In this guide, we will go through the steps to set up the Formbricks SDK in a Vue.js application. This will allow you to create and customize in-product micro-surveys to gather valuable feedback from your users and improve your product experience.
## Introduction

View File

@@ -5,13 +5,11 @@ export const meta = {
title: "How Formbricks works",
};
Formbricks is a powerful platform designed to help you create and manage in-product micro-surveys for SaaS and digital products. In this section, we'll provide a high-level overview of the different components that make up the Formbricks platform.
Formbricks is a powerful platform designed to help you create and manage in-product micro-surveys for SaaS and digital products. Here is an overview:
## Overview
## Four components
The Formbricks platform consists of four main components:
1. **Form Builder**: Create and customize your survey forms with a user-friendly, no-code interface.
1. **Form Builder**: Create and customize your surveys with a user-friendly, no-code interface.
2. **Targeting & Triggers**: Define specific user segments and set event-based triggers to display your surveys to the right users at the right time.
3. **Integration**: Seamlessly integrate Formbricks into your web or mobile application using the provided SDKs or the HTML snippet.
4. **Analytics & Insights**: Analyze user responses and gain actionable insights to make informed product decisions.
@@ -32,6 +30,4 @@ Integrating Formbricks into your web or mobile application is a breeze. With SDK
Formbricks provides powerful analytics and insights to help you understand user responses and make data-driven decisions. The platform aggregates survey results and presents them in an easy-to-understand format, enabling you to identify trends, spot issues, and uncover opportunities for improvement. With Formbricks, you're always one step ahead in understanding your users and optimizing your product experience.
By combining these components, Formbricks offers an end-to-end solution for creating, managing, and analyzing in-product micro-surveys. Get started with Formbricks today and unlock valuable insights to grow your digital product. 🚀
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

View File

@@ -18,6 +18,6 @@ Formbricks outshines other survey tools by specializing in in-product micro-surv
| Event-based triggers | ❌ | ✅ |
| User segmentation | ❌ | ✅ |
With Formbricks, you're not just getting another survey tool, but an in-depth, data-driven solution tailor-made for digital products. Start experiencing the difference with Formbricks today! 🎉
With Formbricks, you're not just getting another survey tool, but an in-depth, data-driven solution tailor-made for digital products 🎉
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;

Binary file not shown.

View File

@@ -34,3 +34,11 @@
background-color: #cbd5e1;
border: 3px solid #cbd5e1;
}
.DocSearch-Input {
@apply px-11 text-gray-900 bg-white;
}
.dark .DocSearch-Input {
@apply text-gray-200 bg-gray-800;
}

View File

@@ -33,6 +33,7 @@ import {
PaintBrushIcon,
PlusIcon,
UserCircleIcon,
UsersIcon,
} from "@heroicons/react/24/solid";
import clsx from "clsx";
import type { Session } from "next-auth";
@@ -122,8 +123,8 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
icon: UserCircleIcon,
label: "Profile",
href: `/environments/${environmentId}/settings/profile`,
} /*
{ icon: UsersIcon, label: "Team", href: `/environments/${environmentId}/settings/team` }, */,
},
{ icon: UsersIcon, label: "Team", href: `/environments/${environmentId}/settings/team` },
{
icon: CreditCardIcon,
label: "Billing & Plan",

View File

@@ -33,7 +33,10 @@ export default function AttributeClassesList({ environmentId }: { environmentId:
return (
<>
<div className="mb-6 text-right">
<Button variant="secondary" href="https://formbricks.com/docs" target="_blank">
<Button
variant="secondary"
href="http://formbricks.com/docs/attributes/custom-attributes"
target="_blank">
<QuestionMarkCircleIcon className="mr-2 h-4 w-4" />
How to add attributes
</Button>

View File

@@ -70,7 +70,10 @@ export default function AttributeSettingsTab({
</div>
<div className="flex justify-between border-t border-slate-200 pt-6">
<div>
<Button variant="secondary" href="https://formbricks.com/docs" target="_blank">
<Button
variant="secondary"
href="https://formbricks.com/docs/getting-started/identify-users"
target="_blank">
Read Docs
</Button>
</div>

View File

@@ -41,7 +41,7 @@ export default function AddMemberModal({ open, setOpen, onSubmit }: MemberModalP
</div>
<div>
<Label>Email Adress</Label>
<Input placeholder="hans@wurst.com" {...register("email")} />
<Input type="email" placeholder="hans@wurst.com" {...register("email", { required: true })} />
</div>
</div>
</div>
@@ -55,12 +55,7 @@ export default function AddMemberModal({ open, setOpen, onSubmit }: MemberModalP
}}>
Cancel
</Button>
<Button
variant="primary"
type="submit"
onClick={() => {
setOpen(false);
}}>
<Button variant="primary" type="submit">
Send Invitation
</Button>
</div>

View File

@@ -14,6 +14,8 @@ import {
import { PaperAirplaneIcon, TrashIcon } from "@heroicons/react/24/outline";
import { useState } from "react";
import AddMemberModal from "./AddMemberModal";
import { Badge } from "@formbricks/ui";
import toast from "react-hot-toast";
export function EditMemberships({ environmentId }) {
const { team, isErrorTeam, isLoadingTeam, mutateTeam } = useTeam(environmentId);
@@ -69,7 +71,7 @@ export function EditMemberships({ environmentId }) {
</Button>
</div>
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-7 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="grid h-12 grid-cols-7 content-center rounded-t-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="px-6"></div>
<div className="col-span-2 ">Fullname</div>
<div className="col-span-2">Email</div>
@@ -78,7 +80,7 @@ export function EditMemberships({ environmentId }) {
<div className="grid-cols-7">
{[...team.members, ...team.invitees].map((member) => (
<div
className="grid h-12 w-full grid-cols-7 content-center rounded-lg py-2 text-left text-sm text-slate-900 hover:bg-slate-100"
className="grid h-12 w-full grid-cols-7 content-center rounded-lg p-0.5 py-2 text-left text-sm text-slate-900"
key={member.email}>
<div className="h-58 px-6 ">
<ProfileAvatar userId={member.userId} />
@@ -88,22 +90,22 @@ export function EditMemberships({ environmentId }) {
</div>
<div className="col-span-2 flex flex-col justify-center">{member.email}</div>
<div className="col-span-2 flex items-center justify-end gap-x-6 pr-6">
{!member.accepted && (
<p className="rounded-md border-2 border-amber-500 bg-amber-50 px-2 py-px text-xs text-amber-500">
Pending
</p>
)}
{!member.accepted && <Badge type="warning" text="Pending" size="tiny" />}
{member.role !== "owner" && (
<button onClick={(e) => handleOpenDeleteMemberModal(e, member)}>
<TrashIcon className="h-5 w-5 text-black" />
<TrashIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
</button>
)}
{!member.accepted && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button onClick={() => handleResendInvite(member.inviteId)}>
<PaperAirplaneIcon className="h-5 w-5 text-black" />
<button
onClick={() => {
handleResendInvite(member.inviteId);
toast.success("Invitation sent once more.");
}}>
<PaperAirplaneIcon className="h-5 w-5 text-slate-700 hover:text-slate-500" />
</button>
</TooltipTrigger>
<TooltipContent className="TooltipContent" sideOffset={5}>

View File

@@ -1,7 +1,6 @@
import SettingsCard from "../SettingsCard";
import SettingsTitle from "../SettingsTitle";
import { EditMemberships } from "./EditMemberships";
import { EditTeamName } from "./EditTeamName";
export default function MembersSettingsPage({ params }) {
return (
@@ -10,9 +9,9 @@ export default function MembersSettingsPage({ params }) {
<SettingsCard title="Manage members" description="Add or remove members in your team.">
<EditMemberships environmentId={params.environmentId} />
</SettingsCard>
<SettingsCard title="Team Name" description="Change the name of your team. Just in case.">
<EditTeamName />
</SettingsCard>
{/* <SettingsCard title="Team Name" description="Change the name of your team. Just in case.">
<EditTeamName />
</SettingsCard>*/}
</div>
);
}

View File

@@ -62,7 +62,10 @@ if (typeof window !== "undefined") {
<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" target="_blank">
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/getting-started/quickstart"
target="_blank">
Check out the docs.
</Link>
</li>
@@ -83,7 +86,10 @@ if (typeof window !== "undefined") {
<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" target="_blank">
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/attributes/why"
target="_blank">
Dive into the docs.
</Link>
</li>
@@ -96,7 +102,7 @@ if (typeof window !== "undefined") {
Insert this code into the <code>{`<head>`}</code> tag of your website:
</p>
<CodeBlock language="js">{`<script type="text/javascript">
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="./dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init("${environmentId}","${window.location.protocol}//${window.location.host}")},500)}();
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="./dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
</script>`}</CodeBlock>
<p className="text-lg font-semibold text-slate-800">You&apos;re done 🎉</p>
<p>
@@ -125,7 +131,10 @@ if (typeof window !== "undefined") {
<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" target="_blank">
<Link
className="decoration-brand-dark"
href="https://formbricks.com/docs/attributes/why"
target="_blank">
Dive into the docs.
</Link>
</li>

View File

@@ -9,9 +9,10 @@ import {
DropdownMenuTrigger,
} from "@/components/shared/DropdownMenu";
import LoadingSpinner from "@/components/shared/LoadingSpinner";
import Modal from "@/components/shared/Modal";
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
import { ErrorComponent } from "@formbricks/ui";
import { deleteSurvey, useSurveys } from "@/lib/surveys/surveys";
import { Button, ErrorComponent, ResponsiveVideo } from "@formbricks/ui";
import { PlusIcon } from "@heroicons/react/24/outline";
import { EllipsisHorizontalIcon, PencilSquareIcon, TrashIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
@@ -24,6 +25,7 @@ export default function SurveysList({ environmentId }) {
const { surveys, mutateSurveys, isLoadingSurveys, isErrorSurveys } = useSurveys(environmentId);
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isVideoDialogOpen, setVideoDialogOpen] = useState(false);
const [activeSurvey, setActiveSurvey] = useState("" as any);
const [activeSurveyIdx, setActiveSurveyIdx] = useState("" as any);
@@ -67,6 +69,25 @@ export default function SurveysList({ environmentId }) {
</div>
</li>
</button>
{surveys.length === 0 && (
<div className="flex flex-col items-center justify-center space-y-4 rounded-lg border border-slate-200 bg-slate-100 p-2">
<p className="text-center text-xs text-slate-500">Kinda lost?</p>
<video width="100" height="120" loop autoPlay className="rounded">
<source src="/video/lost-sw.mp4" type="video/mp4" />
No GIF support
</video>
<Button
variant="secondary"
onClick={() => setVideoDialogOpen(true)}
className="hover:bg-slate-300">
Play video
</Button>
<Modal open={isVideoDialogOpen} setOpen={setVideoDialogOpen}>
<ResponsiveVideo src="/video/walkthrough-v1.mp4" />
</Modal>
</div>
)}
{surveys
.sort((a, b) => b.updatedAt - a.updatedAt)
.map((survey, surveyIdx) => (

View File

@@ -73,6 +73,7 @@ export default function SurveyMenuBar({
<Button
disabled={localSurvey.triggers[0] === "" || localSurvey.triggers.length === 0}
variant="highlight"
loading={isMutatingSurvey}
onClick={() => {
triggerSurveyMutate({ ...localSurvey, status: "inProgress" });
router.push(`/environments/${environmentId}/surveys/${localSurvey.id}/summary?success=true`);

View File

@@ -4,6 +4,7 @@ import { Button } from "@formbricks/ui";
import type { Template } from "@formbricks/types/templates";
import { createSurvey } from "@/lib/surveys/surveys";
import { useRouter } from "next/navigation";
import { useState } from "react";
interface TemplateMenuBarProps {
activeTemplate: Template | null;
@@ -12,8 +13,9 @@ interface TemplateMenuBarProps {
export default function TemplateMenuBar({ activeTemplate, environmentId }: TemplateMenuBarProps) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const addSurvey = async (activeTemplate) => {
setLoading(true);
const survey = await createSurvey(environmentId, activeTemplate.preset);
router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`);
};
@@ -28,6 +30,7 @@ export default function TemplateMenuBar({ activeTemplate, environmentId }: Templ
<Button
variant="highlight"
disabled={activeTemplate === null}
loading={loading}
onClick={() => addSurvey(activeTemplate)}>
Create Survey
</Button>

View File

@@ -17,7 +17,7 @@ const ContentLayout = ({ headline, description, children }) => {
export const NotLoggedInContent = ({ email, token, redirectUrl }) => {
email = encodeURIComponent(email);
return (
<ContentLayout headline="Happy to have you 🫶" description="Please create an account or login.">
<ContentLayout headline="Happy to have you 🤗" description="Please create an account or login.">
<Button variant="secondary" href={`/auth/signup?inviteToken=${token}&email=${email}`}>
Create account
</Button>

Binary file not shown.

Binary file not shown.

View File

@@ -30,7 +30,7 @@ export function Badge({ text, type, size }: BadgeProps) {
return (
<span
className={cn(
"ml-2 inline-flex items-center rounded-full font-medium",
"ml-2 inline-flex cursor-default items-center rounded-full font-medium",
bgColor[type],
textColor[type],
padding[size],

View File

@@ -87,7 +87,7 @@ export const Button: React.ForwardRefExoticComponent<
variant === "secondary" &&
(disabled
? "text-slate-400 dark:text-slate-500 bg-slate-200 dark:bg-slate-800"
: "text-slate-600 hover:text-slate-500 bg-slate-200 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-slate-700 focus:ring-neutral-500"),
: "text-slate-600 hover:text-slate-500 bg-slate-200 hover:bg-slate-100 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-slate-700 focus:ring-neutral-500"),
variant === "warn" &&
(disabled
? "text-slate-400 bg-transparent"

View File

@@ -0,0 +1,18 @@
interface ResponsiveVideoProps {
src: string;
title?: string;
}
export function ResponsiveVideo({ src, title }: ResponsiveVideoProps) {
return (
<div className="relative" style={{ paddingTop: "56.25%" }}>
<iframe
className="absolute top-0 left-0 h-full w-full rounded"
src={src}
title={title}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen></iframe>
</div>
);
}

View File

@@ -30,6 +30,7 @@ export { PageTitle } from "./components/PageTitle";
export { Popover, PopoverTrigger, PopoverContent } from "./components/Popover";
export { ProgressBar } from "./components/ProgressBar";
export { RadioGroup, RadioGroupItem } from "./components/RadioGroup";
export { ResponsiveVideo } from "./components/ResponsiveVideo";
export {
Select,
SelectGroup,

50
pnpm-lock.yaml generated
View File

@@ -12,7 +12,7 @@ importers:
'@changesets/cli': 2.25.0
prettier: 2.8.7
tsx: 3.9.0
turbo: 1.8.6
turbo: 1.8.8
apps/demo:
specifiers:
@@ -65,6 +65,7 @@ importers:
'@types/prismjs': ^1.26.0
'@types/react': 18.0.28
'@types/react-dom': 18.0.11
'@types/react-responsive-embed': ^2.1.0
add: ^2.0.6
autoprefixer: ^10.4.14
clsx: ^1.2.1
@@ -115,6 +116,7 @@ importers:
'@types/prismjs': 1.26.0
'@types/react': 18.0.28
'@types/react-dom': 18.0.11
'@types/react-responsive-embed': 2.1.0
autoprefixer: 10.4.14_postcss@8.4.21
eslint: 8.36.0
eslint-config-formbricks: link:../../packages/eslint-config-formbricks
@@ -4586,6 +4588,12 @@ packages:
redux: 4.2.1
dev: false
/@types/react-responsive-embed/2.1.0:
resolution: {integrity: sha512-VZ921rT7Kf5Zq2GyUxaFXWXQd/YqSMwpwOO5BRJJyzcZoH34YbeX442Dk/nDwXL5QQkz1Zp7hmD80Ris1OHPCA==}
dependencies:
'@types/react': 18.0.31
dev: true
/@types/react/18.0.27:
resolution: {integrity: sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==}
dependencies:
@@ -18638,65 +18646,65 @@ packages:
dependencies:
safe-buffer: 5.2.1
/turbo-darwin-64/1.8.6:
resolution: {integrity: sha512-VlXkQR0TEBAEyBRsvAXBax+fj1EdPKPliwBaCnRLiDUcA/8wYlKte/Kk6ubmj9E0n7U/B4keCxxHiJZqW/5Rqg==}
/turbo-darwin-64/1.8.8:
resolution: {integrity: sha512-18cSeIm7aeEvIxGyq7PVoFyEnPpWDM/0CpZvXKHpQ6qMTkfNt517qVqUTAwsIYqNS8xazcKAqkNbvU1V49n65Q==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-darwin-arm64/1.8.6:
resolution: {integrity: sha512-w4L2QLj90ex68UXxTPoqtZPl8mWzc6a1RtPjQhoxAWtZf9T2WXi813dCzYEbVUVC09/DOW/VxZRN7sb2r0KP9A==}
/turbo-darwin-arm64/1.8.8:
resolution: {integrity: sha512-ruGRI9nHxojIGLQv1TPgN7ud4HO4V8mFBwSgO6oDoZTNuk5ybWybItGR+yu6fni5vJoyMHXOYA2srnxvOc7hjQ==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-linux-64/1.8.6:
resolution: {integrity: sha512-eV245jefIhMAZskqQKalFwreC5UEdQcuHcBiWcgUk0py76fbwB7+1HfH5cmeJlb3a1sB6f3H0HHmGPmb34feCA==}
/turbo-linux-64/1.8.8:
resolution: {integrity: sha512-N/GkHTHeIQogXB1/6ZWfxHx+ubYeb8Jlq3b/3jnU4zLucpZzTQ8XkXIAfJG/TL3Q7ON7xQ8yGOyGLhHL7MpFRg==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-linux-arm64/1.8.6:
resolution: {integrity: sha512-Kiw3nyEvNU6Bpil4zE5FwhasPAOi59R4YdCmjJp0Sen6V9u+/Jij6SWwaoUdATORJLiYQBbhontWBH55B53VDw==}
/turbo-linux-arm64/1.8.8:
resolution: {integrity: sha512-hKqLbBHgUkYf2Ww8uBL9UYdBFQ5677a7QXdsFhONXoACbDUPvpK4BKlz3NN7G4NZ+g9dGju+OJJjQP0VXRHb5w==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-windows-64/1.8.6:
resolution: {integrity: sha512-34BkAG9r4nE00xeMeVahaF82h8R6SO+IIOcD60fNr2p+Ch+YcQa+DbEWA/KUj3coUTIiNP5XnRCLRUYADdlxjQ==}
/turbo-windows-64/1.8.8:
resolution: {integrity: sha512-2ndjDJyzkNslXxLt+PQuU21AHJWc8f6MnLypXy3KsN4EyX/uKKGZS0QJWz27PeHg0JS75PVvhfFV+L9t9i+Yyg==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo-windows-arm64/1.8.6:
resolution: {integrity: sha512-4jWUaI7Lmonp2I3x81GruiCYd0aQsG/xDOYhuv9+j2yIgB/UHJFz/P8PWp/nziwPtGpRd/AheDlPzzyd9lWoqw==}
/turbo-windows-arm64/1.8.8:
resolution: {integrity: sha512-xCA3oxgmW9OMqpI34AAmKfOVsfDljhD5YBwgs0ZDsn5h3kCHhC4x9W5dDk1oyQ4F5EXSH3xVym5/xl1J6WRpUg==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo/1.8.6:
resolution: {integrity: sha512-6IOOaa8ytgjnSCTnp3LKAd2uGBZ/Kmx8ZPlI/YMWuKMUqvkXKLbh+w76ApMgMm+faUqti+QujVWovCu2kY6KuQ==}
/turbo/1.8.8:
resolution: {integrity: sha512-qYJ5NjoTX+591/x09KgsDOPVDUJfU9GoS+6jszQQlLp1AHrf1wRFA3Yps8U+/HTG03q0M4qouOfOLtRQP4QypA==}
hasBin: true
requiresBuild: true
optionalDependencies:
turbo-darwin-64: 1.8.6
turbo-darwin-arm64: 1.8.6
turbo-linux-64: 1.8.6
turbo-linux-arm64: 1.8.6
turbo-windows-64: 1.8.6
turbo-windows-arm64: 1.8.6
turbo-darwin-64: 1.8.8
turbo-darwin-arm64: 1.8.8
turbo-linux-64: 1.8.8
turbo-linux-arm64: 1.8.8
turbo-windows-64: 1.8.8
turbo-windows-arm64: 1.8.8
dev: true
/tween-functions/1.2.0: