Merge branch 'pmf-page' into main

This commit is contained in:
knugget
2023-02-09 10:56:11 +01:00
37 changed files with 895 additions and 343 deletions

View File

@@ -1,12 +1,17 @@
import Button from "./Button";
import { Button } from "@formbricks/ui";
import { useRouter } from "next/router";
import clsx from "clsx";
interface Props {
teaser: string;
headline: string;
subheadline: string;
cta: string;
href: string;
inverted?: boolean;
}
export default function JoinWaitlist({ inverted = false }: Props) {
export default function BreakerCTA({ inverted = false, teaser, headline, subheadline, cta, href }: Props) {
const router = useRouter();
return (
<div
@@ -14,31 +19,36 @@ export default function JoinWaitlist({ inverted = false }: Props) {
inverted
? "from-slate-800 via-slate-800 to-slate-700 dark:from-slate-200 dark:to-slate-300"
: "from-slate-200 to-slate-300 dark:from-slate-800 dark:via-slate-800 dark:to-slate-700",
"mx-auto mb-12 w-full max-w-5xl bg-gradient-to-br sm:rounded-xl md:mb-0 "
"xs:mx-auto xs:w-full mx-4 mb-12 max-w-5xl rounded-xl bg-gradient-to-br md:mb-0 "
)}>
<div className="relative px-4 py-8 sm:px-6 sm:pt-8 sm:pb-12 lg:px-8 lg:pt-12">
<div className="absolute right-10 md:top-1/2 md:-translate-y-1/2">
<Button variant="highlight" onClick={() => router.push("/waitlist")}>
Get Access
<div className="xs:block xs:absolute xs:right-10 hidden md:top-1/2 md:-translate-y-1/2">
<Button variant="highlight" onClick={() => router.push(`${href}`)}>
{cta}
</Button>
</div>
<p className="lg:text-md dark:text-brand-dark text-brand-light text-sm font-semibold uppercase">
Curious?
{teaser}
</p>
<h2
className={clsx(
inverted ? "text-slate-200 dark:text-slate-800" : "text-slate-800 dark:text-slate-200",
"mt-4 text-2xl font-bold tracking-tight lg:text-3xl "
)}>
Get access now!
{headline}
</h2>
<p
className={clsx(
inverted ? "text-slate-300 dark:text-slate-500" : "text-slate-500 dark:text-slate-300",
"text-md mt-4 max-w-3xl lg:text-lg"
)}>
Were onboarding design partners regularly. Sign up to get early access.
{subheadline}
</p>
<div className="xs:hidden mt-4">
<Button variant="highlight" onClick={() => router.push(`${href}`)}>
{cta}
</Button>
</div>
</div>
</div>
);

View File

@@ -1,149 +0,0 @@
import Link, { LinkProps } from "next/link";
import React, { forwardRef } from "react";
import clsx from "clsx";
type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
export type ButtonBaseProps = {
variant?: "highlight" | "primary" | "secondary" | "minimal" | "warn" | "alert";
size?: "base" | "sm" | "lg" | "fab" | "icon";
loading?: boolean;
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
StartIcon?: SVGComponent;
startIconClassName?: string;
EndIcon?: SVGComponent;
endIconClassName?: string;
shallow?: boolean;
};
export type ButtonProps = ButtonBaseProps &
(
| (Omit<JSX.IntrinsicElements["a"], "href" | "onClick"> & LinkProps)
| (Omit<JSX.IntrinsicElements["button"], "onClick"> & { href?: never })
);
export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonProps>(function Button(
props: ButtonProps,
forwardedRef
) {
const {
loading = false,
variant = "primary",
size = "base",
StartIcon,
startIconClassName,
endIconClassName,
EndIcon,
shallow,
// attributes propagated from `HTMLAnchorProps` or `HTMLButtonProps`
...passThroughProps
} = props;
// Buttons are **always** disabled if we're in a `loading` state
const disabled = props.disabled || loading;
// If pass an `href`-attr is passed it's `<a>`, otherwise it's a `<button />`
const isLink = typeof props.href !== "undefined";
const elementType = isLink ? "a" : "button";
const element = React.createElement(
elementType,
{
...passThroughProps,
disabled,
ref: forwardedRef,
className: clsx(
// base styles independent what type of button it is
"inline-flex items-center appearance-none",
// different styles depending on size
size === "sm" && "px-3 py-2 text-sm leading-4 font-medium rounded-lg",
size === "base" && "px-6 py-2 text-sm font-medium rounded-xl",
size === "lg" && "px-4 py-2 text-base font-medium rounded-lg",
size === "icon" &&
"w-10 h-10 justify-center group p-2 border rounded-lg border-transparent text-neutral-400 hover:border-gray-200 transition",
// turn button into a floating action button (fab)
size === "fab" ? "fixed" : "relative",
size === "fab" && "justify-center bottom-20 right-8 rounded-full p-4 w-14 h-14",
// different styles depending on variant
variant === "highlight" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "text-slate-900 bg-gradient-to-b from-brand-light to-brand-dark hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-slate-900 transition ease-in-out delay-50 hover:scale-105"),
variant === "primary" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "text-slate-900 bg-gradient-to-b from-brand-light to-brand-dark hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-slate-900"),
variant === "secondary" &&
(disabled
? "border border-gray-200 text-gray-400 bg-white"
: "hover:text-slate-600 hover:bg-slate-300 bg-slate-200 text-slate-700 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900 dark:text-slate-400 dark:hover:text-slate-300 dark:bg-slate-800 dark:hover:bg-slate-700 transition ease-in-out delay-50 hover:scale-105"),
variant === "alert" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "border border-transparent dark:text-darkmodebrandcontrast text-brandcontrast bg-red-600 dark:bg-darkmodebrand hover:bg-opacity-90 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900"),
variant === "minimal" &&
(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 dark:bg-slate-800 dark:text-slate-300 dark:hover:text-slate-400 dark:hover:bg-slate-900 focus:outline-none focus:ring-2 focus:ring-offset-1 dark:focus:bg-slate-900 focus:bg-slate-700 focus:ring-neutral-500 transition ease-in-out delay-50 hover:scale-105"),
variant === "warn" &&
(disabled
? "text-gray-400 bg-transparent"
: "text-gray-700 bg-transparent hover:text-red-700 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-red-50 focus:ring-red-500"),
// set not-allowed cursor if disabled
loading ? "cursor-wait" : disabled ? "cursor-not-allowed" : "",
props.className
),
// if we click a disabled button, we prevent going through the click handler
onClick: disabled
? (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
}
: props.onClick,
},
<>
{StartIcon && (
<StartIcon
className={clsx(
"inline",
size === "icon" ? "h-4 w-4 " : "-ml-1 h-4 w-4 ltr:mr-2 rtl:ml-2 rtl:-mr-1",
startIconClassName || ""
)}
/>
)}
{props.children}
{loading && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
<svg
className={clsx(
"mx-4 h-5 w-5 animate-spin",
variant === "primary" ? "text-white dark:text-slate-900" : "text-slate-900"
)}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
)}
{EndIcon && (
<EndIcon className={clsx("-mr-1 inline h-5 w-5 ltr:ml-2 rtl:mr-2", endIconClassName || "")} />
)}
</>
);
return props.href ? (
<Link passHref href={props.href} shallow={shallow && shallow}>
{element}
</Link>
) : (
element
);
});
export default Button;

View File

@@ -1,4 +1,4 @@
import Button from "@/components/shared/Button";
import { Button } from "@formbricks/ui";
import { useRouter } from "next/router";
import HeadingCentered from "./HeadingCentered";

View File

@@ -0,0 +1,35 @@
import { useRouter } from "next/router";
import { Button } from "@formbricks/ui";
import Image from "next/image";
import EarlyBird from "@/images/early bird deal for open source jotform alternative typeform and surveymonkey_v2.svg";
export default function EarlyBirdDeal() {
const router = useRouter();
return (
<div className="bg-brand-dark relative mx-4 max-w-7xl overflow-hidden rounded-xl p-6 pb-16 sm:p-8 sm:pb-16 md:py-8 md:px-12 lg:flex lg:items-center">
<div className="lg:w-0 lg:flex-1 ">
<h2
className="mb-1 text-2xl font-bold tracking-tight text-white sm:text-2xl"
id="newsletter-headline">
50% off for early birds.
</h2>
<h2 className="text-xl font-semibold tracking-tight text-slate-200 sm:text-lg">
Limited Early Bird deal. Only{" "}
<span className="bg- rounded-sm bg-slate-200/40 px-2 py-0.5 text-slate-100">17</span> left.
</h2>
<div className="mt-6">
<Button variant="secondary" onClick={() => router.push("https://app.formbricks.com/auth/signup")}>
Get Early Bird Deal
</Button>
</div>
<p className="mt-2 mb-24 max-w-3xl text-xs tracking-tight text-slate-200 md:mb-0 md:max-w-sm lg:max-w-none">
This saves you $588 every year.
</p>
<div className="absolute -right-20 -bottom-36 mx-auto h-96 w-96 scale-75 sm:-right-10">
<Image src={EarlyBird} fill alt="formbricks favicon open source forms typeform alternative" />
</div>
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import Button from "./Button";
import { Button } from "@formbricks/ui";
import { useRouter } from "next/router";
import clsx from "clsx";

View File

@@ -3,7 +3,7 @@ import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import { useRouter } from "next/router";
import { Fragment } from "react";
import Button from "./Button";
import { Button } from "@formbricks/ui";
import { FooterLogo } from "./Logo";
import { ThemeSelector } from "./ThemeSelector";

View File

@@ -0,0 +1,112 @@
import { Popover, Transition } from "@headlessui/react";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import { useRouter } from "next/router";
import { Fragment } from "react";
import { Button } from "@formbricks/ui";
import { FooterLogo } from "./Logo";
import { ThemeSelector } from "./ThemeSelector";
export default function Header() {
const router = useRouter();
return (
<Popover className="relative" as="header">
<div className="flex items-center justify-between px-4 py-6 sm:px-6 md:justify-start ">
<div className="flex w-0 flex-1 justify-start">
<Link href="/">
<span className="sr-only">Formbricks</span>
<FooterLogo className="h-8 w-auto sm:h-10" />
</Link>
</div>
<div className="-my-2 -mr-2 md:hidden">
<Popover.Button className="inline-flex items-center justify-center rounded-md bg-gray-100 p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 dark:bg-slate-700 dark:text-slate-200">
<span className="sr-only">Open menu</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</Popover.Button>
</div>
<Popover.Group as="nav" className="hidden space-x-10 md:flex">
<Link
href="#howitworks"
className="text-base font-medium text-slate-400 hover:text-slate-700 dark:hover:text-slate-300">
How it works
</Link>
<Link
href="#pricing"
className="text-base font-medium text-slate-400 hover:text-slate-700 dark:hover:text-slate-300">
Pricing <p className="bg-brand inline rounded-full px-2 text-xs text-white">50%</p>
</Link>
</Popover.Group>
<div className="hidden flex-1 items-center justify-end md:flex">
<ThemeSelector className="relative z-10 mr-5" />
<Button variant="secondary" className="ml-2" href="https://app.formbricks.com/demo" target="_blank">
Try Demo
</Button>
<Button
variant="highlight"
className="ml-2"
href="https://app.formbricks.com/auth/signup"
target="_blank">
Sign Up
</Button>
</div>
</div>
<Transition
as={Fragment}
enter="duration-200 ease-out"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="duration-100 ease-in"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95">
<Popover.Panel
focus
className="absolute inset-x-0 top-0 z-20 origin-top-right transform p-2 transition md:hidden">
<div className="dark:divide-slate divide-y-2 divide-gray-100 rounded-lg bg-gray-200 shadow-lg ring-1 ring-black ring-opacity-5 dark:divide-gray-700 dark:bg-slate-800">
<div className="px-5 pt-5 pb-6">
<div className="flex items-center justify-between">
<div>
<FooterLogo className="h-8 w-auto" />
</div>
<div className="-mr-2">
<Popover.Button className="inline-flex items-center justify-center rounded-md bg-white p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 dark:bg-slate-700 dark:text-slate-200">
<span className="sr-only">Close menu</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</Popover.Button>
</div>
</div>
</div>
<div className="px-5 py-6">
<div className="flex flex-col space-y-5 text-center text-sm dark:text-slate-300">
<Link href="#howitworks">How it works</Link>
<Link href="#pricing">Pricing</Link>
<Button
variant="secondary"
target="_blank"
onClick={() => router.push("https://app.formbricks.com/demo")}
className="flex w-full justify-center fill-slate-800 dark:fill-slate-200">
Try Demo
</Button>
<Button
variant="primary"
target="_blank"
onClick={() => router.push("https://app.formbricks.com/auth/signup")}
className="flex w-full justify-center">
Sign Up
</Button>
</div>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
}
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

@@ -17,7 +17,7 @@ import clsx from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { Fragment } from "react";
import Button from "./Button";
import { Button } from "@formbricks/ui";
import { FooterLogo } from "./Logo";
import { ThemeSelector } from "./ThemeSelector";

View File

@@ -3,7 +3,7 @@ import Image from "next/image";
import clsx from "clsx";
import Highlight, { defaultProps } from "prism-react-renderer";
import { Button } from "@/components/shared/Button";
import { Button } from "@formbricks/ui";
import { HeroBackground } from "@/components/shared/HeroBackground";
import blurCyanImage from "@/images/blur-cyan.png";
import blurIndigoImage from "@/images/blur-indigo.png";

View File

@@ -25,6 +25,7 @@ const BestPractices = [
description: "Find out how disappointed people would be if they could not use your service any more.",
category: "In-Moment",
icon: PMFIcon,
href: "/pmf",
},
{
title: "Feature Chaser",

View File

@@ -0,0 +1,24 @@
import Footer from "./Footer";
import HeaderPMF from "./HeaderPMF";
import MetaInformation from "./MetaInformation";
interface LayoutProps {
children: React.ReactNode;
title: string;
description: string;
}
export default function Layout({ title, description, children }: LayoutProps) {
return (
<div className="flex h-screen flex-col justify-between">
<MetaInformation title={title} description={description} />
<HeaderPMF />
{
<main className="max-w-8xl relative mx-auto mb-auto flex w-full flex-col justify-center sm:px-2 lg:px-8 xl:px-12">
{children}
</main>
}
<Footer />
</div>
);
}

View File

@@ -1,4 +1,4 @@
import Button from "@/components/shared/Button";
import { Button } from "@formbricks/ui";
import { useRouter } from "next/router";
import HeadingCentered from "./HeadingCentered";

View File

@@ -1,4 +1,4 @@
import Button from "./Button";
import { Button } from "@formbricks/ui";
import { DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";

View File

@@ -1,5 +1,5 @@
import Image from "next/image";
import Button from "@/components/shared/Button";
import { Button } from "@formbricks/ui";
import Friends from "@/images/newsletter-signup-gif.gif";
export default function WaitlistForm() {

View File

@@ -0,0 +1,110 @@
import { useRouter } from "next/router";
import HeadingCentered from "./HeadingCentered";
import clsx from "clsx";
import { Button } from "@formbricks/ui";
import EarlyBirdDeal from "./EarlyBirdDeal";
const tiers = [
{
name: "Self-hosting",
href: "#",
priceMonthly: "tba",
button: "secondary",
discounted: false,
highlight: false,
paymentRythm: "/month",
description: "Host Formbricks on your own server.",
ctaName: "Contact us",
ctaAction: () => window.open("mailto:hola@formbricks.com"),
},
{
name: "Cloud",
href: "#",
priceMonthly: "$99",
button: "highlight",
discounted: true,
highlight: true,
paymentRythm: "/month",
description: "Use the managed cloud, gather insights immediately.",
ctaName: "Sign up now",
ctaAction: () => window.open("https://app.formbricks.com/auth/signup"),
},
];
export default function PricingPmf() {
const router = useRouter();
return (
<div className="-mt-10 pb-20">
<div className="mx-auto max-w-7xl py-4 sm:px-6 sm:pb-6 lg:px-8 ">
<HeadingCentered heading="One price, unlimited usage." teaser="Pricing" />
<div className="mx-auto space-y-4 px-4 sm:grid sm:grid-cols-2 sm:gap-6 sm:space-y-0 md:px-0 lg:max-w-5xl">
{tiers.map((tier) => (
<div
key={tier.name}
className={clsx(
`rounded-lg shadow-sm`,
tier.highlight
? "border border-slate-300 bg-slate-200 dark:border-slate-500 dark:bg-slate-600"
: "bg-slate-100 dark:bg-slate-700"
)}>
<div className="p-8">
<h2
className={clsx(
"inline-flex text-3xl font-bold",
tier.highlight
? "text-slate-700 dark:text-slate-200"
: "text-slate-500 dark:text-slate-300"
)}>
{tier.name}
</h2>
<p
className={clsx(
"mt-4 whitespace-pre-wrap text-sm",
tier.highlight ? "text-gray-600 dark:text-slate-300" : "text-gray-500 dark:text-slate-300"
)}>
{tier.description}
</p>
<p className="mt-8">
<span
className={clsx(
`text-4xl font-light`,
tier.highlight
? "text-slate-800 dark:text-slate-100"
: "text-slate-500 dark:text-slate-200",
tier.discounted ? "decoration-brand line-through" : ""
)}>
{tier.priceMonthly}
</span>{" "}
<span className="text-4xl font-bold text-slate-900 dark:text-slate-50">
{tier.discounted && "$49"}
</span>
<span
className={clsx(
"text-base font-medium",
tier.highlight
? "text-gray-500 dark:text-slate-400"
: "text-gray-400 dark:text-slate-500"
)}>
{tier.paymentRythm}
</span>
</p>
{tier.ctaName && tier.ctaAction && (
<Button
onClick={tier.ctaAction}
className="mt-6 w-full justify-center py-4 text-lg shadow-sm"
variant={tier.highlight ? "highlight" : "secondary"}>
{tier.ctaName}
</Button>
)}
</div>
</div>
))}
</div>
</div>
<div className="mx-auto max-w-5xl">
<EarlyBirdDeal />
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import Button from "../shared/Button";
import { Button } from "@formbricks/ui";
import { DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";