Add Formbricks Website (#130)
* add formbricks website with blog and docs Co-authored-by: knugget <johannes@knugget.de>
@@ -5,7 +5,7 @@
|
||||
<h3 align="center">snoopForms</h3>
|
||||
|
||||
<p align="center">
|
||||
Finally, good open-source forms!
|
||||
Finally, good open source forms!
|
||||
<br />
|
||||
<a href="https://snoopforms.com/">Website & Hosted version</a> | <a href="https://snoopforms.com/discord">Join Discord community</a>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["formbricks"],
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.vscode
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Matthias Nannt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,34 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
@@ -0,0 +1,153 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Hero } from "@/components/shared/Hero";
|
||||
import { Logo, Logomark } from "@/components/shared/Logo";
|
||||
import { MobileNavigation } from "@/components/shared/MobileNavigation";
|
||||
import { Navigation } from "@/components/shared/Navigation";
|
||||
import { Prose } from "@/components/shared/Prose";
|
||||
import { Search } from "@/components/shared/Search";
|
||||
import { ThemeSelector } from "@/components/shared/ThemeSelector";
|
||||
import navigation from "@/lib/docsNavigation";
|
||||
import Button from "../shared/Button";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
function Header({ navigation }: any) {
|
||||
const router = useRouter();
|
||||
let [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function onScroll() {
|
||||
setIsScrolled(window.scrollY > 0);
|
||||
}
|
||||
onScroll();
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={clsx(
|
||||
"sticky top-0 z-50 flex flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-blue-900/5 transition duration-500 dark:shadow-none sm:px-6 lg:px-8",
|
||||
isScrolled
|
||||
? "dark:bg-blue-900/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-blue-900/75"
|
||||
: "dark:bg-transparent"
|
||||
)}>
|
||||
<div className="mr-6 flex lg:hidden">
|
||||
<MobileNavigation navigation={navigation} />
|
||||
</div>
|
||||
<div className="relative flex flex-grow basis-0 items-center">
|
||||
<Link href="/" aria-label="Home page">
|
||||
<Logomark className="h-9 w-9 lg:hidden" />
|
||||
<Logo className="hidden h-9 w-auto fill-blue-700 dark:fill-sky-100 lg:block" />
|
||||
</Link>
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => router.push("/")}
|
||||
size="sm"
|
||||
className="ml-10 flex justify-center opacity-60">
|
||||
← Back to Mainpage
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-my-5 mr-6 sm:mr-8 md:mr-0">
|
||||
<Search />
|
||||
</div>
|
||||
<div className="relative flex basis-0 justify-end gap-6 sm:gap-8 md:flex-grow">
|
||||
<ThemeSelector className="relative z-10" />
|
||||
<Link href="https://github.com" className="group" aria-label="GitHub">
|
||||
<GitHubIcon className="h-6 w-6 fill-blue-400 group-hover:fill-blue-500 dark:group-hover:fill-blue-300" />
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
meta: {
|
||||
title: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function Layout({ children, meta }: LayoutProps) {
|
||||
let router = useRouter();
|
||||
let isHomePage = router.pathname === "/";
|
||||
let allLinks = navigation.flatMap((section) => section.links);
|
||||
let linkIndex = allLinks.findIndex((link) => link.href === router.pathname);
|
||||
let previousPage = allLinks[linkIndex - 1];
|
||||
let nextPage = allLinks[linkIndex + 1];
|
||||
let section = navigation.find((section) => section.links.find((link) => link.href === router.pathname));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header navigation={navigation} />
|
||||
|
||||
{isHomePage && <Hero />}
|
||||
|
||||
<div className="max-w-8xl relative mx-auto flex justify-center sm:px-2 lg:px-8 xl:px-12">
|
||||
<div className="hidden lg:relative lg:block lg:flex-none">
|
||||
<div className="absolute inset-y-0 right-0 w-[50vw] bg-blue-50 dark:hidden" />
|
||||
<div className="absolute top-16 bottom-0 right-0 hidden h-12 w-px bg-gradient-to-t from-blue-800 dark:block" />
|
||||
<div className="absolute top-28 bottom-0 right-0 hidden w-px bg-blue-800 dark:block" />
|
||||
<div className="sticky top-[4.5rem] -ml-0.5 h-[calc(100vh-4.5rem)] overflow-y-auto overflow-x-hidden py-16 pl-0.5">
|
||||
<Navigation navigation={navigation} className="w-64 pr-8 xl:w-72 xl:pr-16" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 max-w-2xl flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16">
|
||||
<article>
|
||||
{(meta.title || section) && (
|
||||
<header className="mb-9 space-y-1">
|
||||
{section && <p className="font-display text-sm font-medium text-sky-500">{section.title}</p>}
|
||||
{meta.title && (
|
||||
<h1 className="font-display text-blue text-3xl tracking-tight dark:text-white">
|
||||
{meta.title}
|
||||
</h1>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
<Prose className="">{children}</Prose>
|
||||
</article>
|
||||
<dl className="mt-12 flex border-t border-blue-200 pt-6 dark:border-blue-800">
|
||||
{previousPage && (
|
||||
<div>
|
||||
<dt className="font-display text-blue text-sm font-medium dark:text-white">Previous</dt>
|
||||
<dd className="mt-1">
|
||||
<Link
|
||||
href={previousPage.href}
|
||||
className="text-base font-semibold text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
<span aria-hidden="true">←</span> {previousPage.title}
|
||||
</Link>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{nextPage && (
|
||||
<div className="ml-auto text-right">
|
||||
<dt className="font-display text-blue text-sm font-medium dark:text-white">Next</dt>
|
||||
<dd className="mt-1">
|
||||
<Link
|
||||
href={nextPage.href}
|
||||
className="text-base font-semibold text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
{nextPage.title} <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
import { Logomark } from "@/components/shared/Logo";
|
||||
import { Navigation } from "@/components/shared/Navigation";
|
||||
|
||||
function MenuIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" strokeWidth="2" strokeLinecap="round" {...props}>
|
||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function CloseIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" strokeWidth="2" strokeLinecap="round" {...props}>
|
||||
<path d="M5 5l14 14M19 5l-14 14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MobileNavigation({ navigation }) {
|
||||
let router = useRouter();
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
function onRouteChange() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
router.events.on("routeChangeComplete", onRouteChange);
|
||||
router.events.on("routeChangeError", onRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", onRouteChange);
|
||||
router.events.off("routeChangeError", onRouteChange);
|
||||
};
|
||||
}, [router, isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={() => setIsOpen(true)} className="relative" aria-label="Open navigation">
|
||||
<MenuIcon className="h-6 w-6 stroke-blue-500" />
|
||||
</button>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={setIsOpen}
|
||||
className="fixed inset-0 z-50 flex items-start overflow-y-auto bg-blue-900/50 pr-10 backdrop-blur lg:hidden"
|
||||
aria-label="Navigation">
|
||||
<Dialog.Panel className="min-h-full w-full max-w-xs bg-white px-4 pt-5 pb-12 dark:bg-blue-900 sm:px-6">
|
||||
<div className="flex items-center">
|
||||
<button type="button" onClick={() => setIsOpen(false)} aria-label="Close navigation">
|
||||
<CloseIcon className="h-6 w-6 stroke-blue-500" />
|
||||
</button>
|
||||
<Link href="/" className="ml-6" aria-label="Home page">
|
||||
<Logomark className="h-9 w-9" />
|
||||
</Link>
|
||||
</div>
|
||||
<Navigation navigation={navigation} className="mt-5 px-1" />
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
PlusIcon,
|
||||
SquaresPlusIcon,
|
||||
ChartBarIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
DocumentPlusIcon,
|
||||
RectangleGroupIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import clsx from "clsx";
|
||||
import HeadingCentered from "../shared/HeadingCenetered";
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "formCreation",
|
||||
name: "Fast Form Creation",
|
||||
description: "Build complex forms with our React Lib. Our data pipes also work with any other form.",
|
||||
icon: PlusIcon,
|
||||
},
|
||||
{
|
||||
id: "dataPipelines",
|
||||
name: "Data Pipelines",
|
||||
description: "Save your data where you need it. Use webhooks or pre-built integrations.",
|
||||
icon: SquaresPlusIcon,
|
||||
},
|
||||
{
|
||||
id: "dataInsights",
|
||||
name: "Powerful Data Insights",
|
||||
description: "View and manage your results quicker. Handle submissions in our dahsboard.",
|
||||
icon: ChartBarIcon,
|
||||
},
|
||||
{
|
||||
id: "nocodeBuilder",
|
||||
name: "No-Code Builder",
|
||||
description: "Let your operators create and change forms. Stick with React to style and embed forms.",
|
||||
icon: RectangleGroupIcon,
|
||||
comingSoon: true,
|
||||
},
|
||||
{
|
||||
id: "analytics",
|
||||
name: "Built-in Analytics",
|
||||
description: "Opening rate, drop-offs, conversions. Use privacy-first analytics out of the box.",
|
||||
icon: ArrowTrendingUpIcon,
|
||||
comingSoon: true,
|
||||
},
|
||||
{
|
||||
id: "templates",
|
||||
name: "Survey Templates",
|
||||
description: "NPS, CSAT, Employee Surveys. Name your business objective, we have the questions.",
|
||||
icon: DocumentPlusIcon,
|
||||
comingSoon: true,
|
||||
},
|
||||
];
|
||||
export default function Features() {
|
||||
return (
|
||||
<div className="relative px-4 pt-16 pb-20 sm:px-6 lg:px-8 lg:pt-24 lg:pb-28">
|
||||
<div className="absolute inset-0">
|
||||
<div className="h-1/3 sm:h-2/3" />
|
||||
</div>
|
||||
<div className="relative mx-auto max-w-7xl">
|
||||
<HeadingCentered
|
||||
closer
|
||||
teaser="the Swiss army knife for forms & surveys"
|
||||
heading="Home-cooked taste, delivered in minutes"
|
||||
subheading="Build a 'home-cooked' solution at the fraction of the time. We do the heavy lifting, you customize
|
||||
to your needs."
|
||||
/>
|
||||
|
||||
<ul role="list" className="grid grid-cols-1 gap-6 pt-8 sm:grid-cols-2 md:grid-cols-3">
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature.id}
|
||||
className={clsx(
|
||||
feature.comingSoon ? "dark:to-blue dark:from-blue-900" : "dark:from-black dark:to-blue-900",
|
||||
"relative col-span-1 mt-16 flex flex-col rounded-xl bg-gradient-to-b from-blue-200 to-gray-100 text-center drop-shadow-sm"
|
||||
)}>
|
||||
<div className="absolute w-full -mt-12">
|
||||
<div
|
||||
className={clsx(
|
||||
feature.comingSoon
|
||||
? "dark:to-blue bg-gradient-to-br from-blue-200 to-gray-100 dark:from-blue-900 dark:via-blue-900"
|
||||
: "via-blue to-blue dark bg-gradient-to-br from-black ",
|
||||
"mx-auto flex h-20 w-20 items-center justify-center rounded-full shadow"
|
||||
)}>
|
||||
<feature.icon className="flex-shrink-0 w-10 h-10 mx-auto text-teal-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 p-10">
|
||||
<h3 className="my-4 text-lg font-medium text-blue dark:text-blue-100">{feature.name}</h3>
|
||||
<dl className="flex flex-col justify-between flex-grow mt-1">
|
||||
<dt className="sr-only">Description</dt>
|
||||
<dd className="text-sm text-gray-600 dark:text-blue-400">{feature.description}</dd>
|
||||
{feature.comingSoon && (
|
||||
<dd className="mt-4">
|
||||
<span className="px-3 py-1 text-xs font-medium bg-gray-400 rounded-full text-blue-50">
|
||||
coming soon
|
||||
</span>
|
||||
</dd>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Button from "../shared/Button";
|
||||
import HeroAnimation from "../shared/HeroAnimation";
|
||||
import HeroTitle from "../shared/HeroTitle";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default function Hero({}: Props) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div className="relative">
|
||||
<HeroTitle
|
||||
headingPt1="The"
|
||||
headingTeal="Open Source"
|
||||
headingPt2="Forms & Survey Toolbox"
|
||||
subheading="We're building all essential form functionality so you don't have to. Modular, customizable,
|
||||
extendable. And open source.">
|
||||
<Button variant="secondary" onClick={() => router.push("/docs")}>
|
||||
Read docs
|
||||
</Button>
|
||||
<Button variant="primary" className="ml-3" onClick={() => router.push("/get-started")}>
|
||||
Get started
|
||||
</Button>
|
||||
</HeroTitle>
|
||||
<HeroAnimation />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import Image from "next/image";
|
||||
import ImageReactLib from "@/images/react-lib.png";
|
||||
import ImageDataPipelines from "@/images/data-pipelines.png";
|
||||
import Link from "next/link";
|
||||
import Button from "../shared/Button";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function Highlights({}) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<>
|
||||
<div className="mt-32">
|
||||
<div className="max-w-md px-4 mx-auto sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
|
||||
<div className="lg:grid lg:grid-cols-2 lg:items-center lg:gap-24">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight text-blue dark:text-blue-100 sm:text-3xl">
|
||||
Build forms in minutes with our <span className="font-light text-teal-500">lightweight</span>{" "}
|
||||
React Form Builder.
|
||||
</h2>
|
||||
<p className="max-w-3xl mt-6 leading-7 text-blue-500 text-md dark:text-blue-300">
|
||||
Loads of question types, validation, multi-page forms, logic jumps, i18n, custom styles - all
|
||||
the good stuff you want, but don't want to build yourself.
|
||||
</p>
|
||||
<p className="max-w-3xl mt-6 leading-7 text-blue-500 text-md dark:text-blue-300">
|
||||
Build <span className="font-semibold">exactly</span> the form you want in a fraction of the
|
||||
time.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Button variant="minimal" size="sm" onClick={() => router.push("/react-form-builder")}>
|
||||
Read more
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Image src={ImageReactLib} alt="react library" className="rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-32">
|
||||
<div className="max-w-md px-4 mx-auto sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
|
||||
<div className="lg:grid lg:grid-cols-2 lg:items-center lg:gap-24">
|
||||
<Image src={ImageDataPipelines} alt="react library" className="rounded-lg" />
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight text-blue dark:text-blue-100 sm:text-3xl">
|
||||
<span className="text-teal-500 ">API</span> all the way
|
||||
</h2>
|
||||
<p className="max-w-3xl mt-6 leading-7 text-blue-500 text-md dark:text-blue-300">
|
||||
Your form looks perfect? Time to build integrations...
|
||||
</p>
|
||||
<p className="max-w-3xl mt-6 leading-7 text-blue-500 text-md dark:text-blue-300">
|
||||
<span className="font-semibold">Or use our prebuilt data pipelines.</span> Pipe submissions
|
||||
right into your database. Set up webhooks, email notifications and 3rd party integrations in
|
||||
our webUI.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Button variant="minimal" size="sm" onClick={() => router.push("/core-api")}>
|
||||
Read more
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
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"
|
||||
: "border border-transparent dark:text-blue-800 text-black bg-teal-500 dark:bg-teal-500 hover:bg-opacity-90 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-teal-900"),
|
||||
variant === "primary" &&
|
||||
(disabled
|
||||
? "border border-transparent bg-gray-400 text-white"
|
||||
: "border border-transparent dark:text-black text-blue-100 bg-blue-800 dark:bg-gradient-to-b dark:from-blue-200 dark:to-gray-100 hover:bg-opacity-90 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-teal-900"),
|
||||
|
||||
variant === "secondary" &&
|
||||
(disabled
|
||||
? "border border-gray-200 text-gray-400 bg-white"
|
||||
: "border-2 border-blue-800 text-blue-800 bg-blue-50 hover:bg-white hover:text-blue-800 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900 dark:bg-blue-900 dark:text-gray-100 dark:hover:bg-black"),
|
||||
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-gray-400 bg-transparent"
|
||||
: "text-gray-50 bg-blue dark:bg-blue-700 hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-1 dark:focus:bg-blue-900 focus:bg-blue-700 focus:ring-neutral-500"),
|
||||
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 transform -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2">
|
||||
<svg
|
||||
className={clsx(
|
||||
"mx-4 h-5 w-5 animate-spin",
|
||||
variant === "primary" ? "text-white dark:text-black" : "text-black"
|
||||
)}
|
||||
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;
|
||||
@@ -0,0 +1,31 @@
|
||||
import Button from "@/components/shared/Button";
|
||||
import { useRouter } from "next/router";
|
||||
import HeadingCentered from "./HeadingCenetered";
|
||||
|
||||
export default function CTA() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<>
|
||||
<div className="px-4 py-16 mx-auto sm:px-6 lg:px-8 lg:pt-24 lg:pb-40">
|
||||
<HeadingCentered closer teaser="Get started" heading="Ready for the last form tool you need?" />
|
||||
|
||||
<div className="grid grid-cols-1 mt-12 md:grid-cols-2">
|
||||
<div className="rounded-xl bg-gradient-to-br from-teal-500 via-teal-500 to-teal-600 p-24 text-center text-white md:ml-2.5 md:-mr-5 md:rounded-l-xl">
|
||||
<h3 className="text-3xl font-bold">Self-hosted</h3>
|
||||
<p className="mt-2 mb-4">Run locally e.g. with docker-compose.</p>
|
||||
<Button variant="primary" onClick={() => router.push("/docs")} className="mt-3">
|
||||
Read docs
|
||||
</Button>
|
||||
</div>
|
||||
<div className="py-24 text-center text-white rounded-xl bg-gradient-to-br from-blue-700 to-blue-800">
|
||||
<h3 className="text-3xl font-bold">Cloud</h3>
|
||||
<p className="mt-2 mb-4">Use our free managed service.</p>
|
||||
<Button variant="primary" onClick={() => router.push("/docs")} className="mt-3">
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Icon } from "@/components/shared/Icon";
|
||||
|
||||
const styles = {
|
||||
note: {
|
||||
container: "bg-sky-50 dark:bg-slate-800/60 dark:ring-1 dark:ring-slate-300/10",
|
||||
title: "text-sky-900 dark:text-sky-400",
|
||||
body: "text-sky-800 [--tw-prose-background:theme(colors.sky.50)] prose-a:text-sky-900 prose-code:text-sky-900 dark:text-slate-300 dark:prose-code:text-slate-300",
|
||||
},
|
||||
warning: {
|
||||
container: "bg-amber-50 dark:bg-slate-800/60 dark:ring-1 dark:ring-slate-300/10",
|
||||
title: "text-amber-900 dark:text-amber-500",
|
||||
body: "text-amber-800 [--tw-prose-underline:theme(colors.amber.400)] [--tw-prose-background:theme(colors.amber.50)] prose-a:text-amber-900 prose-code:text-amber-900 dark:text-slate-300 dark:[--tw-prose-underline:theme(colors.sky.700)] dark:prose-code:text-slate-300",
|
||||
},
|
||||
};
|
||||
|
||||
const icons = {
|
||||
note: (props: any) => <Icon icon="lightbulb" {...props} />,
|
||||
warning: (props: any) => <Icon icon="warning" color="amber" {...props} />,
|
||||
};
|
||||
|
||||
interface CalloutProps {
|
||||
type: "note" | "warning";
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Callout({ type = "note", title, children }: CalloutProps) {
|
||||
let IconComponent = icons[type];
|
||||
|
||||
return (
|
||||
<div className={clsx("my-8 flex rounded-3xl p-6", styles[type].container)}>
|
||||
<IconComponent className="h-8 w-8 flex-none" />
|
||||
<div className="ml-4 flex-auto">
|
||||
<p className={clsx("font-display m-0 text-xl", styles[type].title)}>{title}</p>
|
||||
<div className={clsx("prose mt-2.5", styles[type].body)}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
|
||||
function ChevronRightIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true" {...props}>
|
||||
<path d="M6.75 5.75 9.25 8l-2.5 2.25" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Card({ as: Component = "div", className, children }) {
|
||||
return (
|
||||
<Component className={clsx(className, "group relative flex flex-col items-start")}>{children}</Component>
|
||||
);
|
||||
}
|
||||
|
||||
Card.Link = function CardLink({ children, ...props }) {
|
||||
return (
|
||||
<>
|
||||
<div className="absolute -inset-y-6 -inset-x-4 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 dark:bg-zinc-800/50 sm:-inset-x-6 sm:rounded-2xl" />
|
||||
<Link {...props}>
|
||||
<span className="absolute -inset-y-6 -inset-x-4 sm:-inset-x-6 sm:rounded-2xl" />
|
||||
<span className="relative">{children}</span>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Card.Title = function CardTitle({ as: Component = "h2", href, children }) {
|
||||
return (
|
||||
<Component className="text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
|
||||
{href ? <Card.Link href={href}>{children}</Card.Link> : children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
Card.Description = function CardDescription({ children }) {
|
||||
return <p className="relative mt-2 text-sm text-zinc-600 dark:text-zinc-400">{children}</p>;
|
||||
};
|
||||
|
||||
Card.Cta = function CardCta({ children }) {
|
||||
return (
|
||||
<div aria-hidden="true" className="relative mt-4 flex items-center text-sm font-medium text-teal-500">
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-1 h-4 w-4 stroke-current" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Card.Eyebrow = function CardEyebrow({
|
||||
as: Component = "p",
|
||||
decorate = false,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<Component
|
||||
className={clsx(
|
||||
className,
|
||||
"relative order-first mb-3 flex items-center text-sm text-zinc-400 dark:text-zinc-500",
|
||||
decorate && "pl-3.5"
|
||||
)}
|
||||
{...props}>
|
||||
{decorate && (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center" aria-hidden="true">
|
||||
<span className="h-4 w-0.5 rounded-full bg-zinc-200 dark:bg-zinc-500" />
|
||||
</span>
|
||||
)}
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Props {
|
||||
features: any[];
|
||||
}
|
||||
|
||||
export default function FeatureHighlights({ features }: Props) {
|
||||
return (
|
||||
<ul role="list" className="grid grid-cols-1 gap-6 pt-8 sm:grid-cols-2 md:grid-cols-3">
|
||||
{features.map((feature: any) => (
|
||||
<li
|
||||
key={feature.id}
|
||||
className={clsx(
|
||||
feature.comingSoon ? "dark:to-blue dark:from-blue-900" : "dark:from-black dark:to-blue-900",
|
||||
"relative col-span-1 mt-16 flex flex-col rounded-xl bg-gradient-to-b from-blue-200 to-gray-100 text-center drop-shadow-sm dark:from-black dark:to-blue-900"
|
||||
)}>
|
||||
<div className="absolute -mt-12 w-full">
|
||||
<div
|
||||
className={clsx(
|
||||
feature.comingSoon
|
||||
? "dark:to-blue bg-gradient-to-br from-blue-200 to-gray-100 dark:from-blue-900 dark:via-blue-900"
|
||||
: "via-blue to-blue dark bg-gradient-to-br from-black ",
|
||||
"mx-auto flex h-20 w-20 items-center justify-center rounded-full shadow"
|
||||
)}>
|
||||
<feature.icon className="mx-auto h-10 w-10 flex-shrink-0 text-teal-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col p-10">
|
||||
<h3 className="text-blue my-4 text-lg font-medium dark:text-blue-100">{feature.name}</h3>
|
||||
<dl className="mt-1 flex flex-grow flex-col justify-between">
|
||||
<dt className="sr-only">Description</dt>
|
||||
<dd className="text-sm text-gray-600 dark:text-blue-400">{feature.description}</dd>
|
||||
{feature.comingSoon && (
|
||||
<dd className="mt-4">
|
||||
<span className="rounded-full bg-gray-400 px-3 py-1 text-xs font-medium text-blue-50">
|
||||
coming soon
|
||||
</span>
|
||||
</dd>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import Button from "./Button";
|
||||
import { useRouter } from "next/router";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Props {
|
||||
featureTitle: string;
|
||||
text: string;
|
||||
img: React.ReactNode;
|
||||
isImgLeft?: boolean;
|
||||
cta?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export default function FeatureHighlights({ featureTitle, text, img, isImgLeft, cta, href }: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="my-12">
|
||||
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
|
||||
<div className="lg:grid lg:grid-cols-2 lg:items-center lg:gap-24">
|
||||
<div className={clsx(isImgLeft ? "order-last" : "")}>
|
||||
<h2 className="text-blue text-2xl font-bold tracking-tight dark:text-blue-100 sm:text-3xl">
|
||||
{featureTitle}
|
||||
</h2>
|
||||
<div className="text-md mt-6 whitespace-pre-line leading-7 text-blue-500 dark:text-blue-300">
|
||||
{text}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
{cta && href && (
|
||||
<Button variant="minimal" size="sm" onClick={() => router.push(href)}>
|
||||
{cta}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{img}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Fragment } from 'react'
|
||||
import Highlight, { defaultProps } from 'prism-react-renderer'
|
||||
|
||||
export function Fence({ children, language }) {
|
||||
return (
|
||||
<Highlight
|
||||
{...defaultProps}
|
||||
code={children.trimEnd()}
|
||||
language={language}
|
||||
theme={undefined}
|
||||
>
|
||||
{({ className, style, tokens, getTokenProps }) => (
|
||||
<pre className={className} style={style}>
|
||||
<code>
|
||||
{tokens.map((line, lineIndex) => (
|
||||
<Fragment key={lineIndex}>
|
||||
{line
|
||||
.filter((token) => !token.empty)
|
||||
.map((token, tokenIndex) => (
|
||||
<span key={tokenIndex} {...getTokenProps({ token })} />
|
||||
))}
|
||||
{'\n'}
|
||||
</Fragment>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import Link from "next/link";
|
||||
import clsx from "clsx";
|
||||
import { Logo } from "./Logo";
|
||||
|
||||
const navigation = {
|
||||
creation: [
|
||||
{ name: "React Form Builder", href: "/react-form-builder", status: true },
|
||||
{ name: "Visual Builder", href: "#", status: false },
|
||||
{ name: "Templates", href: "#", status: false },
|
||||
],
|
||||
pipelines: [
|
||||
{ name: "Core API", href: "/core-api", status: true },
|
||||
{ name: "Webhooks", href: "/webhooks", status: true },
|
||||
{ name: "Email", href: "/email", status: true },
|
||||
{ name: "Integrations", href: "#", status: false },
|
||||
],
|
||||
insights: [
|
||||
{ name: "Form HQ", href: "/form-hq", status: true },
|
||||
{ name: "Reports", href: "#", status: false },
|
||||
],
|
||||
legal: [
|
||||
{ name: "Community", href: "/community" },
|
||||
{ name: "Docs", href: "/docs" },
|
||||
{ name: "Blog", href: "/blog" },
|
||||
],
|
||||
social: [
|
||||
{
|
||||
name: "Twitter",
|
||||
href: "https://twitter.com/formbricks",
|
||||
icon: (props: any) => (
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
href: "https://github.com/formbricks/formbricks",
|
||||
icon: (props: any) => (
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer
|
||||
className="dark:from-blue bg-gradient-to-b from-blue-100 to-blue-300 dark:to-black"
|
||||
aria-labelledby="footer-heading">
|
||||
<h2 id="footer-heading" className="sr-only">
|
||||
Footer
|
||||
</h2>
|
||||
<div className="px-4 py-12 mx-auto max-w-7xl sm:px-6 lg:py-16 lg:px-8">
|
||||
<div className="xl:grid xl:grid-cols-3 xl:gap-8">
|
||||
<div className="space-y-8 xl:col-span-1">
|
||||
<Logo className="w-auto h-8 sm:h-10" />
|
||||
<p className="text-base text-blue-600 dark:text-blue-400">
|
||||
The Open Source Forms & Survey Toolbox
|
||||
</p>
|
||||
<div className="flex space-x-6">
|
||||
{navigation.social.map((item) => (
|
||||
<Link key={item.name} href={item.href} className="text-blue-400 hover:text-gray-400">
|
||||
<span className="sr-only">{item.name}</span>
|
||||
<item.icon className="w-6 h-6" aria-hidden="true" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-8 mt-12 xl:col-span-2 xl:mt-0">
|
||||
<div className="md:grid md:grid-cols-2 md:gap-8">
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-blue-800 dark:text-blue-50">Form Creation</h3>
|
||||
<ul role="list" className="mt-4 space-y-4">
|
||||
{navigation.creation.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link
|
||||
href={item.href}
|
||||
scroll={item.status}
|
||||
className={clsx(
|
||||
item.status
|
||||
? "cursor-pointer text-blue-700 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
: "cursor-default text-blue-300 dark:text-blue-600",
|
||||
"text-base"
|
||||
)}>
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-12 md:mt-0">
|
||||
<h3 className="text-sm font-bold text-blue-800 dark:text-blue-50">Data Pipelines</h3>
|
||||
<ul role="list" className="mt-4 space-y-4">
|
||||
{navigation.pipelines.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link
|
||||
scroll={item.status}
|
||||
href={item.href}
|
||||
className={clsx(
|
||||
item.status
|
||||
? "cursor-pointer text-blue-700 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
: "cursor-default text-blue-300 dark:text-blue-600",
|
||||
"text-base"
|
||||
)}>
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:grid md:grid-cols-2 md:gap-8">
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-blue-800 dark:text-blue-50">Data Insights</h3>
|
||||
<ul role="list" className="mt-4 space-y-4">
|
||||
{navigation.insights.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link
|
||||
href={item.href}
|
||||
scroll={item.status}
|
||||
className={clsx(
|
||||
item.status
|
||||
? "cursor-pointer text-blue-700 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
: "cursor-default text-blue-300 dark:text-blue-600",
|
||||
"text-base"
|
||||
)}>
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-12 md:mt-0">
|
||||
<h3 className="text-sm font-bold text-blue-800 dark:text-blue-50">Other</h3>
|
||||
<ul role="list" className="mt-4 space-y-4">
|
||||
{navigation.legal.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className="text-base text-blue-700 hover:text-blue-400 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-8 mt-12 border-gray-500">
|
||||
<p className="text-sm text-blue-600 dark:text-gray-300 xl:text-center">
|
||||
© 2022 Form Bricks, Inc. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import {
|
||||
Bars3Icon,
|
||||
BoltIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
CodeBracketSquareIcon,
|
||||
CpuChipIcon,
|
||||
CursorArrowRaysIcon,
|
||||
CursorArrowRippleIcon,
|
||||
DocumentChartBarIcon,
|
||||
EnvelopeIcon,
|
||||
SquaresPlusIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment } from "react";
|
||||
import Button from "./Button";
|
||||
import { Logo } from "./Logo";
|
||||
import { ThemeSelector } from "./ThemeSelector";
|
||||
|
||||
const creation = [
|
||||
{
|
||||
name: "React Library",
|
||||
description: "Build forms with React.js",
|
||||
href: "/react-form-builder",
|
||||
icon: CodeBracketSquareIcon,
|
||||
status: true,
|
||||
},
|
||||
{
|
||||
name: "No Code Builder",
|
||||
description: "Notion-like visual builder",
|
||||
href: "#",
|
||||
icon: CursorArrowRaysIcon,
|
||||
status: false,
|
||||
},
|
||||
{
|
||||
name: "Templates",
|
||||
description: "CSAT, PMF survey, etc.",
|
||||
href: "#",
|
||||
icon: ClipboardDocumentListIcon,
|
||||
status: false,
|
||||
},
|
||||
];
|
||||
|
||||
const pipes = [
|
||||
{
|
||||
name: "Core API",
|
||||
description: "The OS form engine",
|
||||
href: "/core-api",
|
||||
icon: CpuChipIcon,
|
||||
status: true,
|
||||
},
|
||||
{
|
||||
name: "Webhooks",
|
||||
description: "Send JSON anywhere",
|
||||
href: "/webhooks",
|
||||
icon: BoltIcon,
|
||||
status: true,
|
||||
},
|
||||
{
|
||||
name: "Email",
|
||||
description: "Send data and notifications",
|
||||
href: "/email",
|
||||
icon: EnvelopeIcon,
|
||||
status: true,
|
||||
},
|
||||
{
|
||||
name: "Integrations",
|
||||
description: "Connect with 100+ apps",
|
||||
href: "/integrations",
|
||||
icon: SquaresPlusIcon,
|
||||
status: false,
|
||||
},
|
||||
];
|
||||
|
||||
const insights = [
|
||||
{
|
||||
name: "Form HQ",
|
||||
description: "Manage submissions easily",
|
||||
href: "/form-hq",
|
||||
icon: CursorArrowRippleIcon,
|
||||
cat: "insights",
|
||||
status: true,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
description: "Based on Templates",
|
||||
href: "#",
|
||||
icon: DocumentChartBarIcon,
|
||||
cat: "insights",
|
||||
status: false,
|
||||
},
|
||||
];
|
||||
|
||||
export default function Header() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<div className="flex items-center justify-between px-4 py-6 sm:px-6 md:justify-start md:space-x-10">
|
||||
<div className="flex justify-start lg:w-0 lg:flex-1">
|
||||
<Link href="/">
|
||||
<span className="sr-only">Formbricks</span>
|
||||
<Logo 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-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">
|
||||
<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">
|
||||
<Popover className="relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={clsx(
|
||||
open
|
||||
? "text-gray-300 "
|
||||
: "text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50",
|
||||
"group inline-flex items-center rounded-md text-base font-medium hover:text-gray-300 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 dark:hover:text-gray-50"
|
||||
)}>
|
||||
<span>Bricks</span>
|
||||
<ChevronDownIcon
|
||||
className={clsx(
|
||||
open ? "text-gray-600" : "text-gray-400",
|
||||
"ml-2 h-5 w-5 group-hover:text-gray-500"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Popover.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1">
|
||||
<Popover.Panel className="absolute z-10 mt-3 -ml-4 w-screen max-w-lg transform lg:left-1/2 lg:ml-0 lg:max-w-4xl lg:-translate-x-1/2">
|
||||
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-6 sm:p-8 lg:grid-cols-3">
|
||||
<div>
|
||||
<h4 className="mb-6 ml-16 text-sm text-blue-400">Form Creation</h4>
|
||||
{creation.map((brick) => (
|
||||
<Link
|
||||
key={brick.name}
|
||||
href={brick.href}
|
||||
className={clsx(
|
||||
brick.status ? "cursor-pointer hover:bg-gray-50" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-4"
|
||||
)}>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md text-teal-500 sm:h-12 sm:w-12">
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status ? "text-gray-900" : "text-gray-400",
|
||||
"text-lg font-semibold"
|
||||
)}>
|
||||
{brick.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">{brick.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-6 ml-16 text-sm text-blue-400">Data Pipelines</h4>
|
||||
{pipes.map((brick) => (
|
||||
<Link
|
||||
key={brick.name}
|
||||
href={brick.href}
|
||||
className={clsx(
|
||||
brick.status ? "cursor-pointer hover:bg-gray-50" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-4"
|
||||
)}>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center text-teal-500 sm:h-12 sm:w-12">
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status ? "text-gray-900" : "text-gray-400",
|
||||
"text-lg font-semibold"
|
||||
)}>
|
||||
{brick.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">{brick.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-6 ml-16 text-sm text-blue-400">Data Insights</h4>
|
||||
{insights.map((brick) => (
|
||||
<Link
|
||||
key={brick.name}
|
||||
href={brick.href}
|
||||
className={clsx(
|
||||
brick.status ? "cursor-pointer hover:bg-gray-50" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-4"
|
||||
)}>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md text-teal-500 sm:h-12 sm:w-12">
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status ? "text-gray-900" : "text-gray-400",
|
||||
"text-lg font-semibold"
|
||||
)}>
|
||||
{brick.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">{brick.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
|
||||
<Link
|
||||
href="/community"
|
||||
className="text-base font-medium text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50">
|
||||
Community
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
className="text-base font-medium text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50">
|
||||
Blog
|
||||
</Link>
|
||||
<Link
|
||||
href="/docs"
|
||||
className="text-base font-medium text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50">
|
||||
Docs
|
||||
</Link>
|
||||
</Popover.Group>
|
||||
<div className="hidden items-center justify-end md:flex md:flex-1 lg:w-0">
|
||||
<ThemeSelector className="relative z-10 mr-5" />
|
||||
<Button
|
||||
variant="secondary"
|
||||
EndIcon={GitHubIcon}
|
||||
endIconClassName="fill-blue-900 dark:fill-blue-50"
|
||||
onClick={() => router.push("https://github.com/formbricks/formbricks")}>
|
||||
View on Github
|
||||
</Button>
|
||||
<Button variant="highlight" className="ml-2" onClick={() => router.push("/get-started")}>
|
||||
Get started
|
||||
</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="divide-y-2 divide-gray-50 rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<div className="px-5 pt-5 pb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Logo 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">
|
||||
<span className="sr-only">Close menu</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="relative bg-white px-5 py-6">
|
||||
<div>
|
||||
<h4 className="mb-3 text-sm text-gray-500">Form Creation</h4>
|
||||
{creation.map((brick) => (
|
||||
<Link
|
||||
key={brick.name}
|
||||
href={brick.href}
|
||||
className={clsx(
|
||||
brick.status ? "cursor-pointer hover:bg-gray-50" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-3"
|
||||
)}>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md text-teal-500 sm:h-12 sm:w-12">
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status ? "text-gray-900" : "text-gray-400",
|
||||
"text-lg font-semibold"
|
||||
)}>
|
||||
{brick.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">{brick.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mt-8 mb-3 text-sm text-gray-500">Data Pipelines</h4>
|
||||
{pipes.map((brick) => (
|
||||
<Link
|
||||
key={brick.name}
|
||||
href={brick.href}
|
||||
className={clsx(
|
||||
brick.status ? "cursor-pointer hover:bg-gray-50" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-3"
|
||||
)}>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center text-teal-500 sm:h-12 sm:w-12">
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status ? "text-gray-900" : "text-gray-400",
|
||||
"text-lg font-semibold"
|
||||
)}>
|
||||
{brick.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">{brick.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mt-8 mb-3 text-sm text-gray-500">Data Insights</h4>
|
||||
{insights.map((brick) => (
|
||||
<Link
|
||||
key={brick.name}
|
||||
href={brick.href}
|
||||
className={clsx(
|
||||
brick.status ? "cursor-pointer hover:bg-gray-50" : "cursor-default",
|
||||
"-m-3 flex items-start rounded-lg p-3 py-3"
|
||||
)}>
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md text-teal-500 sm:h-12 sm:w-12">
|
||||
<brick.icon className="h-6 w-6" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={clsx(
|
||||
brick.status ? "text-gray-900" : "text-gray-400",
|
||||
"text-lg font-semibold"
|
||||
)}>
|
||||
{brick.name}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">{brick.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="px-5 py-6">
|
||||
<div className="grid grid-cols-3 text-center text-sm font-medium text-gray-900 hover:text-gray-700 sm:text-base">
|
||||
<Link href="/community">Community</Link>
|
||||
|
||||
<Link href="/blog">Blog</Link>
|
||||
|
||||
<Link href="/docs">Documentation</Link>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
variant="secondary"
|
||||
EndIcon={GitHubIcon}
|
||||
onClick={() => router.push("https://github.com/formbricks/formbricks")}
|
||||
className="flex w-full justify-center">
|
||||
View on Github
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => router.push("https://app.formbricks.com")}
|
||||
className="mt-3 flex w-full justify-center">
|
||||
Get started
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Props {
|
||||
teaser?: string;
|
||||
heading: string;
|
||||
subheading?: string;
|
||||
closer?: boolean;
|
||||
}
|
||||
|
||||
export default function HeadingCentered({ teaser, heading, subheading, closer }: Props) {
|
||||
return (
|
||||
<div className={clsx(closer ? "pt-24" : "pt-40", "pb-12 text-center")}>
|
||||
<p className="max-w-2xl mx-auto mb-3 font-semibold text-transparent uppercase text-md from-teal bg-gradient-to-b to-teal-600 bg-clip-text sm:mt-4">
|
||||
{teaser}
|
||||
</p>
|
||||
<h2 className="text-3xl font-bold tracking-tight text-blue dark:text-blue-100 sm:text-4xl">
|
||||
{heading}
|
||||
</h2>
|
||||
<p className="max-w-3xl mx-auto mt-3 text-xl text-blue-500 dark:text-blue-300 sm:mt-4">{subheading}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Fragment } from "react";
|
||||
import Image from "next/image";
|
||||
import clsx from "clsx";
|
||||
import Highlight, { defaultProps } from "prism-react-renderer";
|
||||
|
||||
import { Button } from "@/components/shared/Button";
|
||||
import { HeroBackground } from "@/components/shared/HeroBackground";
|
||||
import blurCyanImage from "@/images/blur-cyan.png";
|
||||
import blurIndigoImage from "@/images/blur-indigo.png";
|
||||
|
||||
const codeLanguage = "javascript";
|
||||
const code = `export default {
|
||||
strategy: 'predictive',
|
||||
engine: {
|
||||
cpus: 12,
|
||||
backups: ['./storage/cache.wtf'],
|
||||
},
|
||||
}`;
|
||||
|
||||
const tabs = [
|
||||
{ name: "cache-advance.config.js", isActive: true },
|
||||
{ name: "package.json", isActive: false },
|
||||
];
|
||||
|
||||
function TrafficLightsIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 42 10" fill="none" {...props}>
|
||||
<circle cx="5" cy="5" r="4.5" />
|
||||
<circle cx="21" cy="5" r="4.5" />
|
||||
<circle cx="37" cy="5" r="4.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<div className="overflow-hidden bg-blue-900 dark:-mb-32 dark:mt-[-4.5rem] dark:pb-32 dark:pt-[4.5rem] dark:lg:mt-[-4.75rem] dark:lg:pt-[4.75rem]">
|
||||
<div className="py-16 sm:px-2 lg:relative lg:py-20 lg:px-0">
|
||||
<div className="mx-auto grid max-w-2xl grid-cols-1 items-center gap-y-16 gap-x-8 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12">
|
||||
<div className="relative z-10 md:text-center lg:text-left">
|
||||
<Image
|
||||
className="absolute bottom-full right-full -mr-72 -mb-56 opacity-50"
|
||||
src={blurCyanImage}
|
||||
alt=""
|
||||
width={530}
|
||||
height={530}
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
<div className="relative">
|
||||
<p className="inline bg-gradient-to-r from-indigo-200 via-sky-400 to-indigo-200 bg-clip-text font-display text-5xl tracking-tight text-transparent">
|
||||
Never miss the cache again.
|
||||
</p>
|
||||
<p className="mt-3 text-2xl tracking-tight text-blue-400">
|
||||
Cache every single thing your app could ever do ahead of time, so your code never even has to
|
||||
run at all.
|
||||
</p>
|
||||
<div className="mt-8 flex gap-4 md:justify-center lg:justify-start">
|
||||
<Button href="/">Get started</Button>
|
||||
<Button href="/" variant="secondary">
|
||||
View on GitHub
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative lg:static xl:pl-10">
|
||||
<div className="absolute inset-x-[-50vw] -top-32 -bottom-48 [mask-image:linear-gradient(transparent,white,white)] dark:[mask-image:linear-gradient(transparent,white,transparent)] lg:left-[calc(50%+14rem)] lg:right-0 lg:-top-32 lg:-bottom-32 lg:[mask-image:none] lg:dark:[mask-image:linear-gradient(white,white,transparent)]">
|
||||
<HeroBackground className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 lg:left-0 lg:translate-x-0 lg:translate-y-[-60%]" />
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Image
|
||||
className="absolute -top-64 -right-64"
|
||||
src={blurCyanImage}
|
||||
alt=""
|
||||
width={530}
|
||||
height={530}
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
className="absolute -bottom-40 -right-44"
|
||||
src={blurIndigoImage}
|
||||
alt=""
|
||||
width={567}
|
||||
height={567}
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-tr from-sky-300 via-sky-300/70 to-blue-300 opacity-10 blur-lg" />
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-tr from-sky-300 via-sky-300/70 to-blue-300 opacity-10" />
|
||||
<div className="relative rounded-2xl bg-[#0A101F]/80 ring-1 ring-white/10 backdrop-blur">
|
||||
<div className="absolute -top-px left-20 right-11 h-px bg-gradient-to-r from-sky-300/0 via-sky-300/70 to-sky-300/0" />
|
||||
<div className="absolute -bottom-px left-11 right-20 h-px bg-gradient-to-r from-blue-400/0 via-blue-400 to-blue-400/0" />
|
||||
<div className="pl-4 pt-4">
|
||||
<TrafficLightsIcon className="h-2.5 w-auto stroke-blue-500/30" />
|
||||
<div className="mt-4 flex space-x-2 text-xs">
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
key={tab.name}
|
||||
className={clsx(
|
||||
"flex h-6 rounded-full",
|
||||
tab.isActive
|
||||
? "bg-gradient-to-r from-sky-400/30 via-sky-400 to-sky-400/30 p-px font-medium text-sky-300"
|
||||
: "text-blue-500"
|
||||
)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center rounded-full px-2.5",
|
||||
tab.isActive && "bg-blue-800"
|
||||
)}>
|
||||
{tab.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 flex items-start px-1 text-sm">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="select-none border-r border-blue-300/5 pr-4 font-mono text-blue-600">
|
||||
{Array.from({
|
||||
length: code.split("\n").length,
|
||||
}).map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
{(index + 1).toString().padStart(2, "0")}
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
<Highlight {...defaultProps} code={code} language={codeLanguage} theme={undefined}>
|
||||
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
||||
<pre className={clsx(className, "flex overflow-x-auto pb-6")} style={style}>
|
||||
<code className="px-4">
|
||||
{tokens.map((line, lineIndex) => (
|
||||
<div key={lineIndex} {...getLineProps({ line })}>
|
||||
{line.map((token, tokenIndex) => (
|
||||
<span key={tokenIndex} {...getTokenProps({ token })} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { LottiePlayer } from "lottie-web";
|
||||
|
||||
export default function HeroAnimation(props: any) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [lottie, setLottie] = useState<LottiePlayer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
import("lottie-web").then((Lottie) => setLottie(Lottie.default));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (lottie && ref.current) {
|
||||
const animation = lottie.loadAnimation({
|
||||
container: ref.current,
|
||||
renderer: "svg",
|
||||
loop: true,
|
||||
autoplay: true,
|
||||
// path to your animation file, place it inside public folder
|
||||
path: "/animations/hero.json",
|
||||
});
|
||||
|
||||
return () => animation.destroy();
|
||||
}
|
||||
}, [lottie]);
|
||||
|
||||
return <div ref={ref} {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import { useId } from 'react'
|
||||
|
||||
export function HeroBackground(props) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 668 1069"
|
||||
width={668}
|
||||
height={1069}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<clipPath id={`${id}-clip-path`}>
|
||||
<path
|
||||
fill="#fff"
|
||||
transform="rotate(-180 334 534.4)"
|
||||
d="M0 0h668v1068.8H0z"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g opacity=".4" clipPath={`url(#${id}-clip-path)`} strokeWidth={4}>
|
||||
<path
|
||||
opacity=".3"
|
||||
d="M584.5 770.4v-474M484.5 770.4v-474M384.5 770.4v-474M283.5 769.4v-474M183.5 768.4v-474M83.5 767.4v-474"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<path
|
||||
d="M83.5 221.275v6.587a50.1 50.1 0 0 0 22.309 41.686l55.581 37.054a50.102 50.102 0 0 1 22.309 41.686v6.587M83.5 716.012v6.588a50.099 50.099 0 0 0 22.309 41.685l55.581 37.054a50.102 50.102 0 0 1 22.309 41.686v6.587M183.7 584.5v6.587a50.1 50.1 0 0 0 22.31 41.686l55.581 37.054a50.097 50.097 0 0 1 22.309 41.685v6.588M384.101 277.637v6.588a50.1 50.1 0 0 0 22.309 41.685l55.581 37.054a50.1 50.1 0 0 1 22.31 41.686v6.587M384.1 770.288v6.587a50.1 50.1 0 0 1-22.309 41.686l-55.581 37.054A50.099 50.099 0 0 0 283.9 897.3v6.588"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<path
|
||||
d="M384.1 770.288v6.587a50.1 50.1 0 0 1-22.309 41.686l-55.581 37.054A50.099 50.099 0 0 0 283.9 897.3v6.588M484.3 594.937v6.587a50.1 50.1 0 0 1-22.31 41.686l-55.581 37.054A50.1 50.1 0 0 0 384.1 721.95v6.587M484.3 872.575v6.587a50.1 50.1 0 0 1-22.31 41.686l-55.581 37.054a50.098 50.098 0 0 0-22.309 41.686v6.582M584.501 663.824v39.988a50.099 50.099 0 0 1-22.31 41.685l-55.581 37.054a50.102 50.102 0 0 0-22.309 41.686v6.587M283.899 945.637v6.588a50.1 50.1 0 0 1-22.309 41.685l-55.581 37.05a50.12 50.12 0 0 0-22.31 41.69v6.59M384.1 277.637c0 19.946 12.763 37.655 31.686 43.962l137.028 45.676c18.923 6.308 31.686 24.016 31.686 43.962M183.7 463.425v30.69c0 21.564 13.799 40.709 34.257 47.529l134.457 44.819c18.922 6.307 31.686 24.016 31.686 43.962M83.5 102.288c0 19.515 13.554 36.412 32.604 40.645l235.391 52.309c19.05 4.234 32.605 21.13 32.605 40.646M83.5 463.425v-58.45M183.699 542.75V396.625M283.9 1068.8V945.637M83.5 363.225v-141.95M83.5 179.524v-77.237M83.5 60.537V0M384.1 630.425V277.637M484.301 830.824V594.937M584.5 1068.8V663.825M484.301 555.275V452.988M584.5 622.075V452.988M384.1 728.537v-56.362M384.1 1068.8v-20.88M384.1 1006.17V770.287M283.9 903.888V759.85M183.699 1066.71V891.362M83.5 1068.8V716.012M83.5 674.263V505.175"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="384.1"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 384.1)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="200.399"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 200.399)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="81.412"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 81.412)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="183.699"
|
||||
cy="375.75"
|
||||
r="10.438"
|
||||
transform="rotate(-180 183.699 375.75)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="183.699"
|
||||
cy="563.625"
|
||||
r="10.438"
|
||||
transform="rotate(-180 183.699 563.625)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="651.3"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 651.3)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="484.301"
|
||||
cy="574.062"
|
||||
r="10.438"
|
||||
transform="rotate(-180 484.301 574.062)"
|
||||
fill="#0EA5E9"
|
||||
fillOpacity=".42"
|
||||
stroke="#0EA5E9"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="749.412"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 749.412)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="1027.05"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 1027.05)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="283.9"
|
||||
cy="924.763"
|
||||
r="10.438"
|
||||
transform="rotate(-180 283.9 924.763)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="183.699"
|
||||
cy="870.487"
|
||||
r="10.438"
|
||||
transform="rotate(-180 183.699 870.487)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="283.9"
|
||||
cy="738.975"
|
||||
r="10.438"
|
||||
transform="rotate(-180 283.9 738.975)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="695.138"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 695.138)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="484.3"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 484.3)"
|
||||
fill="#0EA5E9"
|
||||
fillOpacity=".42"
|
||||
stroke="#0EA5E9"
|
||||
/>
|
||||
<circle
|
||||
cx="484.301"
|
||||
cy="432.112"
|
||||
r="10.438"
|
||||
transform="rotate(-180 484.301 432.112)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="584.5"
|
||||
cy="432.112"
|
||||
r="10.438"
|
||||
transform="rotate(-180 584.5 432.112)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="584.5"
|
||||
cy="642.95"
|
||||
r="10.438"
|
||||
transform="rotate(-180 584.5 642.95)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="484.301"
|
||||
cy="851.699"
|
||||
r="10.438"
|
||||
transform="rotate(-180 484.301 851.699)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="256.763"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 256.763)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
interface Props {
|
||||
headingPt1: string;
|
||||
headingTeal?: string;
|
||||
headingPt2?: string;
|
||||
subheading?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function HeroTitle({ headingPt1, headingTeal, headingPt2, subheading, children }: Props) {
|
||||
return (
|
||||
<div className="px-4 py-20 text-center sm:px-6 lg:px-8 lg:py-28">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-blue dark:text-blue-100 sm:text-4xl md:text-5xl">
|
||||
<span className="block xl:inline">{headingPt1}</span>{" "}
|
||||
<span className="block text-transparent from-teal bg-gradient-to-b to-teal-600 bg-clip-text xl:inline">
|
||||
{headingTeal}
|
||||
</span>{" "}
|
||||
<span className="block xl:inline">{headingPt2}</span>
|
||||
</h1>
|
||||
<p className="max-w-md mx-auto mt-3 text-base text-blue-500 dark:text-blue-300 sm:text-lg md:mt-5 md:max-w-2xl md:text-xl">
|
||||
{subheading}
|
||||
</p>
|
||||
<div className="max-w-md mx-auto mt-5 sm:flex sm:justify-center md:mt-8">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useId } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { InstallationIcon } from "@/components/shared/icons/InstallationIcon";
|
||||
import { LightbulbIcon } from "@/components/shared/icons/LightbulbIcon";
|
||||
import { PluginsIcon } from "@/components/shared/icons/PluginsIcon";
|
||||
import { PresetsIcon } from "@/components/shared/icons/PresetsIcon";
|
||||
import { ThemingIcon } from "@/components/shared/icons/ThemingIcon";
|
||||
import { WarningIcon } from "@/components/shared/icons/WarningIcon";
|
||||
|
||||
const icons = {
|
||||
installation: InstallationIcon,
|
||||
presets: PresetsIcon,
|
||||
plugins: PluginsIcon,
|
||||
theming: ThemingIcon,
|
||||
lightbulb: LightbulbIcon,
|
||||
warning: WarningIcon,
|
||||
};
|
||||
|
||||
const iconStyles = {
|
||||
blue: "[--icon-foreground:theme(colors.slate.900)] [--icon-background:theme(colors.white)]",
|
||||
amber: "[--icon-foreground:theme(colors.amber.900)] [--icon-background:theme(colors.amber.100)]",
|
||||
};
|
||||
|
||||
export function Icon({ color = "blue", icon, className, ...props }) {
|
||||
let id = useId();
|
||||
let IconComponent = icons[icon];
|
||||
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
className={clsx(className, iconStyles[color])}
|
||||
{...props}>
|
||||
<IconComponent id={id} color={color} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const gradients = {
|
||||
blue: [
|
||||
{ stopColor: "#0EA5E9" },
|
||||
{ stopColor: "#22D3EE", offset: ".527" },
|
||||
{ stopColor: "#818CF8", offset: 1 },
|
||||
],
|
||||
amber: [
|
||||
{ stopColor: "#FDE68A", offset: ".08" },
|
||||
{ stopColor: "#F59E0B", offset: ".837" },
|
||||
],
|
||||
};
|
||||
|
||||
export function Gradient({ color = "blue", ...props }) {
|
||||
return (
|
||||
<radialGradient cx={0} cy={0} r={1} gradientUnits="userSpaceOnUse" {...props}>
|
||||
{gradients[color].map((stop, stopIndex) => (
|
||||
<stop key={stopIndex} {...stop} />
|
||||
))}
|
||||
</radialGradient>
|
||||
);
|
||||
}
|
||||
|
||||
export function LightMode({ className, ...props }) {
|
||||
return <g className={clsx("dark:hidden", className)} {...props} />;
|
||||
}
|
||||
|
||||
export function DarkMode({ className, ...props }) {
|
||||
return <g className={clsx("hidden dark:inline", className)} {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import Footer from "./Footer";
|
||||
import Header from "./Header";
|
||||
import MetaInformation from "./MetaInformation";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function Layout({ title, description, children }: LayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<MetaInformation title={title} description={description} />
|
||||
<Header />
|
||||
<main className="max-w-8xl relative mx-auto flex flex-col justify-center sm:px-2 lg:px-8 xl:px-12">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Layout from "./Layout";
|
||||
import { Prose } from "./Prose";
|
||||
|
||||
interface Props {
|
||||
meta: {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export default function LayoutMdx({ meta, children }: Props) {
|
||||
return (
|
||||
<Layout title={meta.title} description={meta.description}>
|
||||
<article className="mx-auto my-16 max-w-3xl">
|
||||
{meta.title && (
|
||||
<header className="mb-9 space-y-1">
|
||||
{meta.title && (
|
||||
<h1 className="font-display text-blue text-3xl tracking-tight dark:text-white">{meta.title}</h1>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
<Prose className="">{children}</Prose>
|
||||
</article>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Image from "next/image";
|
||||
import logomark from "@/images/logomark.svg";
|
||||
import logo from "@/images/logo.svg";
|
||||
import logoDark from "@/images/logo_dark.svg";
|
||||
|
||||
export function Logomark(props: any) {
|
||||
return <Image src={logomark} {...props} alt="Formbricks Logomark" />;
|
||||
}
|
||||
|
||||
export function Logo(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<div className="block dark:hidden">
|
||||
<Image src={logo} {...props} alt="Formbricks Logo" />
|
||||
</div>
|
||||
<div className="hidden dark:block">
|
||||
<Image src={logoDark} {...props} alt="Formbricks Logo" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Head from "next/head";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function MetaInformation({ title, description }: Props) {
|
||||
return (
|
||||
<Head>
|
||||
<title>{title} - Formbricks - Open Source Form Infrastructure</title>
|
||||
<meta name="description" content={description} />
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
import { Logomark } from "@/components/shared/Logo";
|
||||
import { Navigation } from "@/components/shared/Navigation";
|
||||
|
||||
function MenuIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" strokeWidth="2" strokeLinecap="round" {...props}>
|
||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function CloseIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" strokeWidth="2" strokeLinecap="round" {...props}>
|
||||
<path d="M5 5l14 14M19 5l-14 14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MobileNavigation({ navigation }) {
|
||||
let router = useRouter();
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
function onRouteChange() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
router.events.on("routeChangeComplete", onRouteChange);
|
||||
router.events.on("routeChangeError", onRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", onRouteChange);
|
||||
router.events.off("routeChangeError", onRouteChange);
|
||||
};
|
||||
}, [router, isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={() => setIsOpen(true)} className="relative" aria-label="Open navigation">
|
||||
<MenuIcon className="h-6 w-6 stroke-blue-500" />
|
||||
</button>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={setIsOpen}
|
||||
className="fixed inset-0 z-50 flex items-start overflow-y-auto bg-blue-900/50 pr-10 backdrop-blur lg:hidden"
|
||||
aria-label="Navigation">
|
||||
<Dialog.Panel className="min-h-full w-full max-w-xs bg-white px-4 pt-5 pb-12 dark:bg-blue-900 sm:px-6">
|
||||
<div className="flex items-center">
|
||||
<button type="button" onClick={() => setIsOpen(false)} aria-label="Close navigation">
|
||||
<CloseIcon className="h-6 w-6 stroke-blue-500" />
|
||||
</button>
|
||||
<Link href="/" className="ml-6" aria-label="Home page">
|
||||
<Logomark className="h-9 w-9" />
|
||||
</Link>
|
||||
</div>
|
||||
<Navigation navigation={navigation} className="mt-5 px-1" />
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface NavigationProps {
|
||||
navigation: {
|
||||
title: string;
|
||||
links: {
|
||||
title: string;
|
||||
href: string;
|
||||
}[];
|
||||
}[];
|
||||
className: string;
|
||||
}
|
||||
|
||||
export function Navigation({ navigation, className }: NavigationProps) {
|
||||
let router = useRouter();
|
||||
|
||||
return (
|
||||
<nav className={clsx("text-base lg:text-sm", className)}>
|
||||
<ul role="list" className="space-y-9">
|
||||
{navigation.map((section) => (
|
||||
<li key={section.title}>
|
||||
<h2 className="font-display text-blue font-medium dark:text-white">{section.title}</h2>
|
||||
<ul
|
||||
role="list"
|
||||
className="mt-2 space-y-2 border-l-2 border-blue-100 dark:border-blue-800 lg:mt-4 lg:space-y-4 lg:border-blue-200">
|
||||
{section.links.map((link) => (
|
||||
<li key={link.href} className="relative">
|
||||
<Link
|
||||
href={link.href}
|
||||
className={clsx(
|
||||
"block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full",
|
||||
link.href === router.pathname
|
||||
? "font-semibold text-sky-500 before:bg-sky-500"
|
||||
: "text-blue-500 before:hidden before:bg-blue-300 hover:text-blue-600 hover:before:block dark:text-blue-400 dark:before:bg-blue-700 dark:hover:text-blue-300"
|
||||
)}>
|
||||
{link.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
export function Prose({ as: Component = "div", className, ...props }) {
|
||||
return (
|
||||
<Component
|
||||
className={clsx(
|
||||
className,
|
||||
"prose prose-blue max-w-none dark:prose-invert dark:text-blue-400",
|
||||
// headings
|
||||
"prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-normal lg:prose-headings:scroll-mt-[8.5rem]",
|
||||
// lead
|
||||
"prose-lead:text-blue-500 dark:prose-lead:text-blue-400",
|
||||
// links
|
||||
"prose-a:font-semibold dark:prose-a:text-sky-400",
|
||||
// link underline
|
||||
"prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] dark:[--tw-prose-background:theme(colors.blue.900)] dark:prose-a:shadow-[inset_0_calc(-1*var(--tw-prose-underline-size,2px))_0_0_var(--tw-prose-underline,theme(colors.sky.800))] dark:hover:prose-a:[--tw-prose-underline-size:6px]",
|
||||
// pre
|
||||
"prose-pre:rounded-xl prose-pre:bg-blue-900 prose-pre:shadow-lg dark:prose-pre:bg-blue-800/60 dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-blue-300/10",
|
||||
// hr
|
||||
"dark:prose-hr:border-blue-800"
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import Link from "next/link";
|
||||
import Router from "next/router";
|
||||
import { DocSearchModal, useDocSearchKeyboardEvents } from "@docsearch/react";
|
||||
|
||||
const docSearchConfig = {
|
||||
appId: process.env.NEXT_PUBLIC_DOCSEARCH_APP_ID || "",
|
||||
apiKey: process.env.NEXT_PUBLIC_DOCSEARCH_API_KEY || "",
|
||||
indexName: process.env.NEXT_PUBLIC_DOCSEARCH_INDEX_NAME || "",
|
||||
};
|
||||
|
||||
interface HitProps {
|
||||
hit: { url: string };
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Hit({ hit, children }: HitProps) {
|
||||
return <Link href={hit.url}>{children}</Link>;
|
||||
}
|
||||
|
||||
function SearchIcon(props: any) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 20 20" {...props}>
|
||||
<path d="M16.293 17.707a1 1 0 0 0 1.414-1.414l-1.414 1.414ZM9 14a5 5 0 0 1-5-5H2a7 7 0 0 0 7 7v-2ZM4 9a5 5 0 0 1 5-5V2a7 7 0 0 0-7 7h2Zm5-5a5 5 0 0 1 5 5h2a7 7 0 0 0-7-7v2Zm8.707 12.293-3.757-3.757-1.414 1.414 3.757 3.757 1.414-1.414ZM14 9a4.98 4.98 0 0 1-1.464 3.536l1.414 1.414A6.98 6.98 0 0 0 16 9h-2Zm-1.464 3.536A4.98 4.98 0 0 1 9 14v2a6.98 6.98 0 0 0 4.95-2.05l-1.414-1.414Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Search() {
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
let [modifierKey, setModifierKey] = useState<string>();
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
}, [setIsOpen]);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, [setIsOpen]);
|
||||
|
||||
useDocSearchKeyboardEvents({ isOpen, onOpen, onClose });
|
||||
|
||||
useEffect(() => {
|
||||
setModifierKey(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl ");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="group flex h-6 w-6 items-center justify-center sm:justify-start md:h-auto md:w-80 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-blue-200 md:hover:ring-blue-300 dark:md:bg-blue-800/75 dark:md:ring-inset dark:md:ring-white/5 dark:md:hover:bg-blue-700/40 dark:md:hover:ring-blue-500 lg:w-96"
|
||||
onClick={onOpen}>
|
||||
<SearchIcon className="h-5 w-5 flex-none fill-blue-400 group-hover:fill-blue-500 dark:fill-blue-500 md:group-hover:fill-blue-400" />
|
||||
<span className="sr-only md:not-sr-only md:ml-2 md:text-blue-500 md:dark:text-blue-400">
|
||||
Search docs
|
||||
</span>
|
||||
{modifierKey && (
|
||||
<kbd className="ml-auto hidden font-medium text-blue-400 dark:text-blue-500 md:block">
|
||||
<kbd className="font-sans">{modifierKey}</kbd>
|
||||
<kbd className="font-sans">K</kbd>
|
||||
</kbd>
|
||||
)}
|
||||
</button>
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<DocSearchModal
|
||||
{...docSearchConfig}
|
||||
initialScrollY={window.scrollY}
|
||||
onClose={onClose}
|
||||
hitComponent={Hit}
|
||||
navigator={{
|
||||
navigate({ itemUrl }) {
|
||||
Router.push(itemUrl);
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
|
||||
const themes = [
|
||||
{ name: "Light", value: "light", icon: LightIcon },
|
||||
{ name: "Dark", value: "dark", icon: DarkIcon },
|
||||
{ name: "System", value: "system", icon: SystemIcon },
|
||||
];
|
||||
|
||||
function LightIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7 1a1 1 0 0 1 2 0v1a1 1 0 1 1-2 0V1Zm4 7a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm2.657-5.657a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm-1.415 11.313-.707-.707a1 1 0 0 1 1.415-1.415l.707.708a1 1 0 0 1-1.415 1.414ZM16 7.999a1 1 0 0 0-1-1h-1a1 1 0 1 0 0 2h1a1 1 0 0 0 1-1ZM7 14a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm-2.536-2.464a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm0-8.486A1 1 0 0 1 3.05 4.464l-.707-.707a1 1 0 0 1 1.414-1.414l.707.707ZM3 8a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function DarkIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.23 3.333C7.757 2.905 7.68 2 7 2a6 6 0 1 0 0 12c.68 0 .758-.905.23-1.332A5.989 5.989 0 0 1 5 8c0-1.885.87-3.568 2.23-4.668ZM12 5a1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 0 2 1 1 0 0 0-1 1 1 1 0 1 1-2 0 1 1 0 0 0-1-1 1 1 0 1 1 0-2 1 1 0 0 0 1-1 1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function SystemIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3h-1.5l.31 1.242c.084.333.36.573.63.808.091.08.182.158.264.24A1 1 0 0 1 11 15H5a1 1 0 0 1-.704-1.71c.082-.082.173-.16.264-.24.27-.235.546-.475.63-.808L5.5 11H4a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ThemeSelector(props) {
|
||||
let [selectedTheme, setSelectedTheme] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTheme) {
|
||||
document.documentElement.setAttribute("data-theme", selectedTheme.value);
|
||||
} else {
|
||||
setSelectedTheme(
|
||||
themes.find((theme) => theme.value === document.documentElement.getAttribute("data-theme"))
|
||||
);
|
||||
}
|
||||
}, [selectedTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
let handler = () =>
|
||||
setSelectedTheme(themes.find((theme) => theme.value === (window.localStorage.theme ?? "system")));
|
||||
|
||||
window.addEventListener("storage", handler);
|
||||
|
||||
return () => window.removeEventListener("storage", handler);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Listbox as="div" value={selectedTheme} onChange={setSelectedTheme} {...props}>
|
||||
<Listbox.Label className="sr-only">Theme</Listbox.Label>
|
||||
<Listbox.Button
|
||||
className="flex h-6 w-6 items-center justify-center rounded-lg shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5"
|
||||
aria-label={selectedTheme?.name}>
|
||||
<LightIcon className="hidden h-4 w-4 fill-teal-500 [[data-theme=light]_&]:block" />
|
||||
<DarkIcon className="hidden h-4 w-4 fill-teal-500 [[data-theme=dark]_&]:block" />
|
||||
<LightIcon className="hidden h-4 w-4 fill-slate-400 [:not(.dark)[data-theme=system]_&]:block" />
|
||||
<DarkIcon className="hidden h-4 w-4 fill-slate-400 [.dark[data-theme=system]_&]:block" />
|
||||
</Listbox.Button>
|
||||
<Listbox.Options className="absolute top-full left-1/2 mt-3 w-36 -translate-x-1/2 space-y-1 rounded-xl bg-white p-3 text-sm font-medium shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
|
||||
{themes.map((theme) => (
|
||||
<Listbox.Option
|
||||
key={theme.value}
|
||||
value={theme}
|
||||
className={({ active, selected }) =>
|
||||
clsx("flex cursor-pointer select-none items-center rounded-[0.625rem] p-1", {
|
||||
"text-teal-500": selected,
|
||||
"text-blue dark:text-white": active && !selected,
|
||||
"text-slate-700 dark:text-slate-400": !active && !selected,
|
||||
"bg-slate-100 dark:bg-slate-900/40": active,
|
||||
})
|
||||
}>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<div className="rounded-md bg-white p-1 shadow ring-1 ring-slate-900/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5">
|
||||
<theme.icon
|
||||
className={clsx(
|
||||
"h-4 w-4",
|
||||
selected ? "fill-teal-500 dark:fill-teal-500" : "fill-slate-400"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">{theme.name}</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Button from "../shared/Button";
|
||||
import { DocumentDuplicateIcon } from "@heroicons/react/24/outline";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function HeadingCentered() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div className="grid content-center max-w-md grid-cols-2 gap-10 px-4 pt-24 mx-auto pb-36 sm:max-w-3xl sm:px-6 lg:max-w-6xl lg:px-8">
|
||||
<div className="">
|
||||
<p className="mb-3 font-semibold text-teal-500 uppercase text-md">What are you waiting for?</p>
|
||||
<h2 className="text-3xl font-bold tracking-tight text-blue dark:text-blue-100 sm:text-4xl">
|
||||
Try it right now!
|
||||
</h2>
|
||||
<p className="mt-3 text-xl text-blue-500 dark:text-blue-300 sm:mt-4">
|
||||
Dive right in or browse docs for examples.
|
||||
</p>
|
||||
<p className="mb-3 text-xl text-blue-500 dark:text-blue-300 sm:mb-4">
|
||||
Questions? Join our Discord, we’re happy to help!
|
||||
</p>
|
||||
<Button variant="secondary" onClick={() => router.push("/docs")}>
|
||||
Read docs
|
||||
</Button>
|
||||
<Button variant="primary" className="ml-3" onClick={() => router.push("/get-started")}>
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center justify-between w-full h-20 px-8 text-gray-100 bg-blue-900 rounded-lg ">
|
||||
<p>npm install @formbricks/react</p>
|
||||
<button onClick={() => navigator.clipboard.writeText("npm install @formbricks/react")}>
|
||||
<DocumentDuplicateIcon className="w-8 h-8" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
UsersIcon,
|
||||
CubeTransparentIcon,
|
||||
UserGroupIcon,
|
||||
CommandLineIcon,
|
||||
SwatchIcon,
|
||||
SquaresPlusIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: "Futureproof",
|
||||
description: "Form needs change. With Formbricks you’ll avoid island solutions right from the start.",
|
||||
icon: CubeTransparentIcon,
|
||||
},
|
||||
{
|
||||
name: "Privacy by design",
|
||||
description: "Self-host the entire product and fly through privacy compliance reviews.",
|
||||
icon: UsersIcon,
|
||||
},
|
||||
{
|
||||
name: "Community driven",
|
||||
description: "We're building for you. If you need something specific, we’re happy to build it!",
|
||||
icon: UserGroupIcon,
|
||||
},
|
||||
{
|
||||
name: "Great DX",
|
||||
description: "We love a solid developer experience. We felt your pain and do our best to avoid it.",
|
||||
icon: CommandLineIcon,
|
||||
},
|
||||
{
|
||||
name: "Customizable",
|
||||
description: "We have to build opinionated. If it doesn't suit your need, just change it up.",
|
||||
icon: SwatchIcon,
|
||||
},
|
||||
{
|
||||
name: "Extendable",
|
||||
description: "Even though we try, we cannot build every single integration. With Formbricks, you can.",
|
||||
icon: SquaresPlusIcon,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FeatureTable({}) {
|
||||
return (
|
||||
<div className="mt-56 rounded-xl bg-gradient-to-br from-blue-900 via-blue-900 to-black">
|
||||
<div className="mx-auto max-w-4xl px-4 py-8 sm:px-6 sm:pt-8 sm:pb-12 lg:max-w-7xl lg:px-8 lg:pt-12">
|
||||
<p className="text-md mb-3 max-w-2xl font-semibold uppercase text-teal-500 sm:mt-4">
|
||||
Why Formbricks?
|
||||
</p>
|
||||
<h2 className="mt-4 text-3xl font-bold tracking-tight text-white">
|
||||
The only complete open source option.
|
||||
</h2>
|
||||
<p className="mt-4 max-w-3xl text-lg text-blue-300">
|
||||
We needed this, so we are building it. We experienced first hand how form needs develop as companies
|
||||
grow. Make the right choice today, congratulate yourself tomorrow :)
|
||||
</p>
|
||||
<div className="mt-12 grid grid-cols-1 gap-x-6 gap-y-12 sm:grid-cols-2 lg:mt-16 lg:grid-cols-3 lg:gap-x-8 lg:gap-y-16">
|
||||
{features.map((feature) => (
|
||||
<div key={feature.name}>
|
||||
<div>
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-md bg-teal-50 bg-opacity-10">
|
||||
<feature.icon className="h-6 w-6 text-teal-500" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h3 className="text-lg font-semibold text-white">{feature.name}</h3>
|
||||
<p className="mt-2 text-base leading-6 text-blue-400">{feature.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { DarkMode, Gradient, LightMode } from "@/components/shared/Icon";
|
||||
|
||||
export function InstallationIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient id={`${id}-gradient`} color={color} gradientTransform="matrix(0 21 -21 0 12 3)" />
|
||||
<Gradient id={`${id}-gradient-dark`} color={color} gradientTransform="matrix(0 21 -21 0 16 7)" />
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={12} cy={12} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
d="m8 8 9 21 2-10 10-2L8 8Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
d="m4 4 10.286 24 2.285-11.429L28 14.286 4 4Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
stroke={`url(#${id}-gradient-dark)`}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { DarkMode, Gradient, LightMode } from "@/components/shared/Icon";
|
||||
|
||||
export function LightbulbIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient id={`${id}-gradient`} color={color} gradientTransform="matrix(0 21 -21 0 20 11)" />
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 24.5001 -19.2498 0 16 5.5)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20 24.995c0-1.855 1.094-3.501 2.427-4.792C24.61 18.087 26 15.07 26 12.231 26 7.133 21.523 3 16 3S6 7.133 6 12.23c0 2.84 1.389 5.857 3.573 7.973C10.906 21.494 12 23.14 12 24.995V27a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-2.005Z"
|
||||
className="fill-[var(--icon-background)]"
|
||||
fillOpacity={0.5}
|
||||
/>
|
||||
<path
|
||||
d="M25 12.23c0 2.536-1.254 5.303-3.269 7.255l1.391 1.436c2.354-2.28 3.878-5.547 3.878-8.69h-2ZM16 4c5.047 0 9 3.759 9 8.23h2C27 6.508 21.998 2 16 2v2Zm-9 8.23C7 7.76 10.953 4 16 4V2C10.002 2 5 6.507 5 12.23h2Zm3.269 7.255C8.254 17.533 7 14.766 7 12.23H5c0 3.143 1.523 6.41 3.877 8.69l1.392-1.436ZM13 27v-2.005h-2V27h2Zm1 1a1 1 0 0 1-1-1h-2a3 3 0 0 0 3 3v-2Zm4 0h-4v2h4v-2Zm1-1a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2Zm0-2.005V27h2v-2.005h-2ZM8.877 20.921C10.132 22.136 11 23.538 11 24.995h2c0-2.253-1.32-4.143-2.731-5.51L8.877 20.92Zm12.854-1.436C20.32 20.852 19 22.742 19 24.995h2c0-1.457.869-2.859 2.122-4.074l-1.391-1.436Z"
|
||||
className="fill-[var(--icon-foreground)]"
|
||||
/>
|
||||
<path
|
||||
d="M20 26a1 1 0 1 0 0-2v2Zm-8-2a1 1 0 1 0 0 2v-2Zm2 0h-2v2h2v-2Zm1 1V13.5h-2V25h2Zm-5-11.5v1h2v-1h-2Zm3.5 4.5h5v-2h-5v2Zm8.5-3.5v-1h-2v1h2ZM20 24h-2v2h2v-2Zm-2 0h-4v2h4v-2Zm-1-10.5V25h2V13.5h-2Zm2.5-2.5a2.5 2.5 0 0 0-2.5 2.5h2a.5.5 0 0 1 .5-.5v-2Zm2.5 2.5a2.5 2.5 0 0 0-2.5-2.5v2a.5.5 0 0 1 .5.5h2ZM18.5 18a3.5 3.5 0 0 0 3.5-3.5h-2a1.5 1.5 0 0 1-1.5 1.5v2ZM10 14.5a3.5 3.5 0 0 0 3.5 3.5v-2a1.5 1.5 0 0 1-1.5-1.5h-2Zm2.5-3.5a2.5 2.5 0 0 0-2.5 2.5h2a.5.5 0 0 1 .5-.5v-2Zm2.5 2.5a2.5 2.5 0 0 0-2.5-2.5v2a.5.5 0 0 1 .5.5h2Z"
|
||||
className="fill-[var(--icon-foreground)]"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 2C10.002 2 5 6.507 5 12.23c0 3.144 1.523 6.411 3.877 8.691.75.727 1.363 1.52 1.734 2.353.185.415.574.726 1.028.726H12a1 1 0 0 0 1-1v-4.5a.5.5 0 0 0-.5-.5A3.5 3.5 0 0 1 9 14.5V14a3 3 0 1 1 6 0v9a1 1 0 1 0 2 0v-9a3 3 0 1 1 6 0v.5a3.5 3.5 0 0 1-3.5 3.5.5.5 0 0 0-.5.5V23a1 1 0 0 0 1 1h.36c.455 0 .844-.311 1.03-.726.37-.833.982-1.626 1.732-2.353 2.354-2.28 3.878-5.547 3.878-8.69C27 6.507 21.998 2 16 2Zm5 25a1 1 0 0 0-1-1h-8a1 1 0 0 0-1 1 3 3 0 0 0 3 3h4a3 3 0 0 0 3-3Zm-8-13v1.5a.5.5 0 0 1-.5.5 1.5 1.5 0 0 1-1.5-1.5V14a1 1 0 1 1 2 0Zm6.5 2a.5.5 0 0 1-.5-.5V14a1 1 0 1 1 2 0v.5a1.5 1.5 0 0 1-1.5 1.5Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { DarkMode, Gradient, LightMode } from "@/components/shared/Icon";
|
||||
|
||||
export function PluginsIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient id={`${id}-gradient`} color={color} gradientTransform="matrix(0 21 -21 0 20 11)" />
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark-1`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 22.75 -22.75 0 16 6.25)"
|
||||
/>
|
||||
<Gradient id={`${id}-gradient-dark-2`} color={color} gradientTransform="matrix(0 14 -14 0 16 10)" />
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<g
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round">
|
||||
<path d="M3 9v14l12 6V15L3 9Z" />
|
||||
<path d="M27 9v14l-12 6V15l12-6Z" />
|
||||
</g>
|
||||
<path d="M11 4h8v2l6 3-10 6L5 9l6-3V4Z" fillOpacity={0.5} className="fill-[var(--icon-background)]" />
|
||||
<g
|
||||
className="stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round">
|
||||
<path d="M20 5.5 27 9l-12 6L3 9l7-3.5" />
|
||||
<path d="M20 5c0 1.105-2.239 2-5 2s-5-.895-5-2m10 0c0-1.105-2.239-2-5-2s-5 .895-5 2m10 0v3c0 1.105-2.239 2-5 2s-5-.895-5-2V5" />
|
||||
</g>
|
||||
</LightMode>
|
||||
<DarkMode strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
|
||||
<path
|
||||
d="M17.676 3.38a3.887 3.887 0 0 0-3.352 0l-9 4.288C3.907 8.342 3 9.806 3 11.416v9.168c0 1.61.907 3.073 2.324 3.748l9 4.288a3.887 3.887 0 0 0 3.352 0l9-4.288C28.093 23.657 29 22.194 29 20.584v-9.168c0-1.61-.907-3.074-2.324-3.748l-9-4.288Z"
|
||||
stroke={`url(#${id}-gradient-dark-1)`}
|
||||
/>
|
||||
<path
|
||||
d="M16.406 8.087a.989.989 0 0 0-.812 0l-7 3.598A1.012 1.012 0 0 0 8 12.61v6.78c0 .4.233.762.594.925l7 3.598a.989.989 0 0 0 .812 0l7-3.598c.361-.163.594-.525.594-.925v-6.78c0-.4-.233-.762-.594-.925l-7-3.598Z"
|
||||
fill={`url(#${id}-gradient-dark-2)`}
|
||||
stroke={`url(#${id}-gradient-dark-2)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { DarkMode, Gradient, LightMode } from "@/components/shared/Icon";
|
||||
|
||||
export function PresetsIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient id={`${id}-gradient`} color={color} gradientTransform="matrix(0 21 -21 0 20 3)" />
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 22.75 -22.75 0 16 6.25)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={12} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<g
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
fillOpacity={0.5}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round">
|
||||
<path d="M3 5v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2Z" />
|
||||
<path d="M18 17v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V17a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2Z" />
|
||||
<path d="M18 5v4a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2Z" />
|
||||
<path d="M3 25v2a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2Z" />
|
||||
</g>
|
||||
</LightMode>
|
||||
<DarkMode fill={`url(#${id}-gradient-dark)`}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3 17V4a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Zm16 10v-9a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2Zm0-23v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-8a1 1 0 0 0-1 1ZM3 28v-3a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z"
|
||||
/>
|
||||
<path d="M2 4v13h2V4H2Zm2-2a2 2 0 0 0-2 2h2V2Zm8 0H4v2h8V2Zm2 2a2 2 0 0 0-2-2v2h2Zm0 13V4h-2v13h2Zm-2 2a2 2 0 0 0 2-2h-2v2Zm-8 0h8v-2H4v2Zm-2-2a2 2 0 0 0 2 2v-2H2Zm16 1v9h2v-9h-2Zm3-3a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1v-2Zm6 0h-6v2h6v-2Zm3 3a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2Zm0 9v-9h-2v9h2Zm-3 3a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2Zm-6 0h6v-2h-6v2Zm-3-3a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1h-2Zm2-18V4h-2v5h2Zm0 0h-2a2 2 0 0 0 2 2V9Zm8 0h-8v2h8V9Zm0 0v2a2 2 0 0 0 2-2h-2Zm0-5v5h2V4h-2Zm0 0h2a2 2 0 0 0-2-2v2Zm-8 0h8V2h-8v2Zm0 0V2a2 2 0 0 0-2 2h2ZM2 25v3h2v-3H2Zm2-2a2 2 0 0 0-2 2h2v-2Zm9 0H4v2h9v-2Zm2 2a2 2 0 0 0-2-2v2h2Zm0 3v-3h-2v3h2Zm-2 2a2 2 0 0 0 2-2h-2v2Zm-9 0h9v-2H4v2Zm-2-2a2 2 0 0 0 2 2v-2H2Z" />
|
||||
</DarkMode>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { DarkMode, Gradient, LightMode } from "@/components/shared/Icon";
|
||||
|
||||
export function ThemingIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient id={`${id}-gradient`} color={color} gradientTransform="matrix(0 21 -21 0 12 11)" />
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 24.5 -24.5 0 16 5.5)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={12} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
d="M27 12.13 19.87 5 13 11.87v14.26l14-14Z"
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
fillOpacity={0.5}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 3h10v22a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V3Z"
|
||||
className="fill-[var(--icon-background)]"
|
||||
fillOpacity={0.5}
|
||||
/>
|
||||
<path
|
||||
d="M3 9v16a4 4 0 0 0 4 4h2a4 4 0 0 0 4-4V9M3 9V3h10v6M3 9h10M3 15h10M3 21h10"
|
||||
className="stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M29 29V19h-8.5L13 26c0 1.5-2.5 3-5 3h21Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3 2a1 1 0 0 0-1 1v21a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H3Zm16.752 3.293a1 1 0 0 0-1.593.244l-1.045 2A1 1 0 0 0 17 8v13a1 1 0 0 0 1.71.705l7.999-8.045a1 1 0 0 0-.002-1.412l-6.955-6.955ZM26 18a1 1 0 0 0-.707.293l-10 10A1 1 0 0 0 16 30h13a1 1 0 0 0 1-1V19a1 1 0 0 0-1-1h-3ZM5 18a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2H5Zm-1-5a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1Zm1-7a1 1 0 0 0 0 2h6a1 1 0 1 0 0-2H5Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { DarkMode, Gradient, LightMode } from "@/components/shared/Icon";
|
||||
|
||||
export function WarningIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="rotate(65.924 1.519 20.92) scale(25.7391)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 24.5 -24.5 0 16 5.5)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
d="M3 16c0 7.18 5.82 13 13 13s13-5.82 13-13S23.18 3 16 3 3 8.82 3 16Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="m15.408 16.509-1.04-5.543a1.66 1.66 0 1 1 3.263 0l-1.039 5.543a.602.602 0 0 1-1.184 0Z"
|
||||
className="fill-[var(--icon-foreground)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16 23a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
fillOpacity={0.5}
|
||||
stroke="currentColor"
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 16C2 8.268 8.268 2 16 2s14 6.268 14 14-6.268 14-14 14S2 23.732 2 16Zm11.386-4.85a2.66 2.66 0 1 1 5.228 0l-1.039 5.543a1.602 1.602 0 0 1-3.15 0l-1.04-5.543ZM16 20a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,23 @@
|
||||
<svg width="835" height="184" viewBox="0 0 835 184" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M123.303 67.78V73.1542H112.094V90.8124H123.303V153H144.032V90.8124H160.308V73.1542H144.032V68.0871C144.032 61.7916 146.95 58.567 153.092 58.567H158.466V40.9088H149.867C133.744 40.9088 123.303 51.6573 123.303 67.78ZM201.28 154.996C226.001 154.996 243.813 136.263 243.813 113.231C243.813 90.1982 226.001 71.3116 201.28 71.3116C176.558 71.3116 158.747 90.1982 158.747 113.231C158.747 136.263 176.558 154.996 201.28 154.996ZM179.476 113.231C179.476 100.793 188.075 90.1982 201.28 90.1982C214.485 90.1982 223.084 100.793 223.084 113.231C223.084 125.668 214.485 136.11 201.28 136.11C188.075 136.11 179.476 125.668 179.476 113.231ZM251.452 153H272.181V112.156C272.181 98.797 281.394 90.8124 293.371 90.8124H296.289V71.3116C282.469 70.5439 274.331 76.8394 271.567 88.3556V73.1542H251.452V153ZM301.65 153H322.379V109.238C322.379 95.4189 330.21 89.4305 338.962 89.4305C349.25 89.4305 356.16 95.4189 356.16 109.238V153H376.889V109.238C376.889 93.8834 383.492 89.4305 393.165 89.4305C402.532 89.4305 409.134 95.4189 409.134 109.238V153H429.863V104.171C429.863 84.0563 416.658 71.3116 399.461 71.3116C385.488 71.3116 377.81 76.6859 372.436 86.3595C367.215 76.6859 357.695 71.3116 346.486 71.3116C334.202 71.3116 326.218 75.9181 322.072 84.824V73.1542H301.65V153ZM485.779 154.996C507.737 154.996 525.242 136.417 525.242 113.384C525.242 90.3518 507.737 71.4652 485.779 71.4652C472.114 71.4652 464.897 76.993 460.597 85.4382V40.9088H439.868V153H459.983V139.948C464.283 149.008 471.499 154.996 485.779 154.996ZM459.983 113.384C459.983 100.947 468.735 90.1982 482.401 90.1982C495.607 90.1982 504.513 100.793 504.513 113.231C504.513 125.361 495.607 136.263 482.401 136.263C468.735 136.263 459.983 125.822 459.983 113.384ZM532.952 153H553.681V112.156C553.681 98.797 562.894 90.8124 574.871 90.8124H577.788V71.3116C563.969 70.5439 555.83 76.8394 553.067 88.3556V73.1542H532.952V153ZM583.149 153H603.878V73.1542H583.149V153ZM580.999 54.2677C580.999 61.0238 586.374 66.5516 593.437 66.5516C600.654 66.5516 605.874 61.1774 605.874 54.2677C605.874 47.665 600.654 42.1372 593.437 42.1372C586.374 42.1372 580.999 47.665 580.999 54.2677ZM652.683 154.996C668.345 154.996 679.401 149.315 686.925 139.641L671.877 126.589C667.731 132.117 662.51 136.11 654.526 136.11C641.32 136.11 632.261 125.515 632.261 113.077C632.261 100.64 640.86 90.0447 654.065 90.0447C661.128 90.0447 667.27 93.5763 670.955 99.1041L686.925 86.0524C680.168 76.6859 668.345 71.0045 653.758 71.1581C629.343 71.3116 611.532 89.8911 611.532 113.077C611.532 136.263 629.804 154.996 652.683 154.996ZM746.412 153H773.897L736.124 110.313L773.744 73.1542H744.262L714.32 105.4V40.9088H693.591V153H714.32V115.073L746.412 153ZM800.51 154.996C818.169 154.996 831.527 143.941 831.527 129.2C831.527 117.223 826.153 108.624 802.967 102.636C793.447 100.179 792.219 97.7222 792.219 94.9583C792.219 90.0447 796.518 87.895 802.199 87.895C807.574 87.895 812.487 90.3518 816.326 96.4938L831.374 84.9776C823.236 74.3826 814.637 71.1581 801.739 71.1581C783.006 71.1581 771.95 81.5995 771.95 95.5725C771.95 105.707 775.175 115.38 796.518 120.447C809.109 123.365 811.105 125.668 811.105 129.814C811.105 134.574 807.113 138.106 800.05 138.106C793.294 138.106 787.152 134.881 781.317 128.279L767.344 141.023C776.096 150.39 786.998 154.996 800.51 154.996Z" fill="#000A1C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5279 44.2825C17.5279 42.921 17.5279 42.2403 17.7971 41.7222C18.0239 41.2856 18.3799 40.9296 18.8165 40.7027C19.3346 40.4336 20.0153 40.4336 21.3768 40.4336H33.8367C35.1982 40.4336 35.8789 40.4336 36.397 40.7027C36.8336 40.9296 37.1896 41.2856 37.4164 41.7222C37.6856 42.2403 37.6856 42.921 37.6856 44.2825V46.9623C37.6856 47.4208 37.6856 47.8022 37.6753 48.1261H49.0895C49.0792 47.8022 49.0792 47.4208 49.0792 46.9623V44.2825C49.0792 42.921 49.0792 42.2403 49.3484 41.7222C49.5752 41.2856 49.9312 40.9296 50.3678 40.7027C50.886 40.4336 51.5667 40.4336 52.9281 40.4336H65.3881C66.7495 40.4336 67.4302 40.4336 67.9483 40.7027C68.385 40.9296 68.741 41.2856 68.9678 41.7222C69.2369 42.2403 69.2369 42.921 69.2369 44.2825V46.9623C69.2369 47.4208 69.2369 47.8022 69.2266 48.1261H76.1321C81.4133 48.1261 85.6945 52.4073 85.6945 57.6885V73.3823C85.6945 78.6634 81.4133 82.9446 76.1321 82.9446H9.56237C4.28121 82.9446 0 78.6634 0 73.3823V57.6885C0 52.4073 4.28122 48.1261 9.56238 48.1261H17.5382C17.5279 47.8022 17.5279 47.4208 17.5279 46.9623V44.2825Z" fill="#00E5CA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5279 44.2825C17.5279 42.921 17.5279 42.2403 17.7971 41.7222C18.0239 41.2856 18.3799 40.9296 18.8165 40.7027C19.3346 40.4336 20.0153 40.4336 21.3768 40.4336H33.8367C35.1982 40.4336 35.8789 40.4336 36.397 40.7027C36.8336 40.9296 37.1896 41.2856 37.4164 41.7222C37.6856 42.2403 37.6856 42.921 37.6856 44.2825V46.9623C37.6856 47.4208 37.6856 47.8022 37.6753 48.1261H49.0895C49.0792 47.8022 49.0792 47.4208 49.0792 46.9623V44.2825C49.0792 42.921 49.0792 42.2403 49.3484 41.7222C49.5752 41.2856 49.9312 40.9296 50.3678 40.7027C50.886 40.4336 51.5667 40.4336 52.9281 40.4336H65.3881C66.7495 40.4336 67.4302 40.4336 67.9483 40.7027C68.385 40.9296 68.741 41.2856 68.9678 41.7222C69.2369 42.2403 69.2369 42.921 69.2369 44.2825V46.9623C69.2369 47.4208 69.2369 47.8022 69.2266 48.1261H76.1321C81.4133 48.1261 85.6945 52.4073 85.6945 57.6885V73.3823C85.6945 78.6634 81.4133 82.9446 76.1321 82.9446H9.56237C4.28121 82.9446 0 78.6634 0 73.3823V57.6885C0 52.4073 4.28122 48.1261 9.56238 48.1261H17.5382C17.5279 47.8022 17.5279 47.4208 17.5279 46.9623V44.2825Z" fill="url(#paint0_linear_2303_6099)" fill-opacity="0.05"/>
|
||||
<path d="M0 92.5072C0 87.2261 4.28122 82.9448 9.56238 82.9448H76.1321C81.4133 82.9448 85.6945 87.226 85.6945 92.5072V109.168C85.6945 114.449 81.4133 118.731 76.1321 118.731H9.56237C4.28121 118.731 0 114.449 0 109.168V92.5072Z" fill="#002941"/>
|
||||
<path d="M0 92.5072C0 87.2261 4.28122 82.9448 9.56238 82.9448H76.1321C81.4133 82.9448 85.6945 87.226 85.6945 92.5072V109.168C85.6945 114.449 81.4133 118.731 76.1321 118.731H9.56237C4.28121 118.731 0 114.449 0 109.168V92.5072Z" fill="url(#paint1_linear_2303_6099)" fill-opacity="0.05"/>
|
||||
<path d="M0 128.293C0 123.012 4.28122 118.731 9.56238 118.731H76.1321C81.4133 118.731 85.6945 123.012 85.6945 128.293V143.987C85.6945 149.268 81.4133 153.549 76.1321 153.549H9.56237C4.28121 153.549 0 149.268 0 143.987V128.293Z" fill="#000A1C"/>
|
||||
<path d="M0 128.293C0 123.012 4.28122 118.731 9.56238 118.731H76.1321C81.4133 118.731 85.6945 123.012 85.6945 128.293V143.987C85.6945 149.268 81.4133 153.549 76.1321 153.549H9.56237C4.28121 153.549 0 149.268 0 143.987V128.293Z" fill="url(#paint2_linear_2303_6099)" fill-opacity="0.05"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2303_6099" x1="42.8473" y1="42.3228" x2="42.8473" y2="82.9447" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2303_6099" x1="42.8473" y1="82.9448" x2="42.8473" y2="118.731" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2303_6099" x1="42.8473" y1="118.731" x2="42.8473" y2="153.549" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,23 @@
|
||||
<svg width="829" height="180" viewBox="0 0 829 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M120.077 66.7213V71.9731H109.123V89.229H120.077V150H140.334V89.229H156.24V71.9731H140.334V67.0214C140.334 60.8693 143.185 57.7182 149.187 57.7182H154.439V40.4622H146.036C130.281 40.4622 120.077 50.9658 120.077 66.7213ZM197.778 151.951C221.936 151.951 239.342 133.644 239.342 111.137C239.342 88.6288 221.936 70.1725 197.778 70.1725C173.62 70.1725 156.214 88.6288 156.214 111.137C156.214 133.644 173.62 151.951 197.778 151.951ZM176.471 111.137C176.471 98.9824 184.874 88.6288 197.778 88.6288C210.683 88.6288 219.085 98.9824 219.085 111.137C219.085 123.291 210.683 133.494 197.778 133.494C184.874 133.494 176.471 123.291 176.471 111.137ZM248.308 150H268.565V110.086C268.565 97.0317 277.568 89.229 289.272 89.229H292.123V70.1725C278.618 69.4222 270.666 75.5743 267.965 86.8282V71.9731H248.308V150ZM298.863 150H319.12V107.235C319.12 93.7306 326.772 87.8786 335.325 87.8786C345.379 87.8786 352.131 93.7306 352.131 107.235V150H372.388V107.235C372.388 92.2301 378.84 87.8786 388.293 87.8786C397.447 87.8786 403.899 93.7306 403.899 107.235V150H424.156V102.284C424.156 82.6268 411.251 70.1725 394.446 70.1725C380.791 70.1725 373.288 75.4243 368.036 84.8775C362.935 75.4243 353.631 70.1725 342.678 70.1725C330.674 70.1725 322.871 74.674 318.819 83.377V71.9731H298.863V150ZM480.299 151.951C501.756 151.951 518.862 133.794 518.862 111.287C518.862 88.7789 501.756 70.3225 480.299 70.3225C466.944 70.3225 459.892 75.7244 455.69 83.9772V40.4622H435.433V150H455.09V137.246C459.291 146.099 466.344 151.951 480.299 151.951ZM455.09 111.287C455.09 99.1325 463.643 88.6288 476.997 88.6288C489.902 88.6288 498.605 98.9824 498.605 111.137C498.605 122.991 489.902 133.644 476.997 133.644C463.643 133.644 455.09 123.441 455.09 111.287ZM527.897 150H548.154V110.086C548.154 97.0317 557.157 89.229 568.861 89.229H571.712V70.1725C558.207 69.4222 550.254 75.5743 547.553 86.8282V71.9731H527.897V150ZM578.451 150H598.708V71.9731H578.451V150ZM576.35 53.5167C576.35 60.119 581.602 65.5209 588.505 65.5209C595.557 65.5209 600.659 60.2691 600.659 53.5167C600.659 47.0645 595.557 41.6626 588.505 41.6626C581.602 41.6626 576.35 47.0645 576.35 53.5167ZM647.902 151.951C663.207 151.951 674.011 146.399 681.363 136.945L666.658 124.191C662.607 129.593 657.505 133.494 649.702 133.494C636.798 133.494 627.945 123.141 627.945 110.987C627.945 98.8324 636.348 88.4788 649.252 88.4788C656.155 88.4788 662.157 91.93 665.758 97.3318L681.363 84.5774C674.761 75.4243 663.207 69.8724 648.952 70.0224C625.094 70.1725 607.688 88.3287 607.688 110.987C607.688 133.644 625.544 151.951 647.902 151.951ZM740.996 150H767.855L730.942 108.286L767.705 71.9731H738.895L709.635 103.484V40.4622H689.378V150H709.635V112.937L740.996 150ZM795.363 151.951C812.619 151.951 825.673 141.147 825.673 126.742C825.673 115.038 820.421 106.635 797.763 100.783C788.46 98.3822 787.26 95.9814 787.26 93.2804C787.26 88.4788 791.461 86.3781 797.013 86.3781C802.265 86.3781 807.067 88.7789 810.818 94.781L825.523 83.5271C817.57 73.1735 809.167 70.0224 796.563 70.0224C778.257 70.0224 767.453 80.2259 767.453 93.8806C767.453 103.784 770.604 113.237 791.461 118.189C803.766 121.04 805.716 123.291 805.716 127.342C805.716 131.994 801.815 135.445 794.912 135.445C788.31 135.445 782.308 132.294 776.606 125.842L762.951 138.296C771.504 147.449 782.158 151.951 795.363 151.951Z" fill="#EEF3F7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0536 43.7753C17.0536 42.4449 17.0536 41.7797 17.3166 41.2734C17.5382 40.8467 17.8861 40.4988 18.3128 40.2772C18.8191 40.0142 19.4843 40.0142 20.8148 40.0142H32.9017C34.2321 40.0142 34.8973 40.0142 35.4037 40.2772C35.8303 40.4988 36.1782 40.8467 36.3999 41.2734C36.6629 41.7797 36.6629 42.4449 36.6629 43.7753V46.3482C36.6629 46.8016 36.6629 47.1778 36.6525 47.4967H47.7532C47.7427 47.1778 47.7427 46.8016 47.7427 46.3482V43.7753C47.7427 42.4449 47.7427 41.7797 48.0058 41.2734C48.2274 40.8467 48.5753 40.4988 49.002 40.2772C49.5083 40.0142 50.1735 40.0142 51.5039 40.0142H63.5909C64.9213 40.0142 65.5865 40.0142 66.0928 40.2772C66.5195 40.4988 66.8674 40.8467 67.0891 41.2734C67.3521 41.7797 67.3521 42.4449 67.3521 43.7753V46.3482C67.3521 46.8016 67.3521 47.1778 67.3417 47.4967H74.0188C79.1796 47.4967 83.3633 51.6804 83.3633 56.8413V72.0235C83.3633 77.1844 79.1796 81.3681 74.0188 81.3681H9.34456C4.1837 81.3681 0 77.1844 0 72.0235V56.8413C0 51.6804 4.1837 47.4967 9.34455 47.4967H17.064C17.0536 47.1778 17.0536 46.8016 17.0536 46.3482V43.7753Z" fill="#00E5CA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0536 43.7753C17.0536 42.4449 17.0536 41.7797 17.3166 41.2734C17.5382 40.8467 17.8861 40.4988 18.3128 40.2772C18.8191 40.0142 19.4843 40.0142 20.8148 40.0142H32.9017C34.2321 40.0142 34.8973 40.0142 35.4037 40.2772C35.8303 40.4988 36.1782 40.8467 36.3999 41.2734C36.6629 41.7797 36.6629 42.4449 36.6629 43.7753V46.3482C36.6629 46.8016 36.6629 47.1778 36.6525 47.4967H47.7532C47.7427 47.1778 47.7427 46.8016 47.7427 46.3482V43.7753C47.7427 42.4449 47.7427 41.7797 48.0058 41.2734C48.2274 40.8467 48.5753 40.4988 49.002 40.2772C49.5083 40.0142 50.1735 40.0142 51.5039 40.0142H63.5909C64.9213 40.0142 65.5865 40.0142 66.0928 40.2772C66.5195 40.4988 66.8674 40.8467 67.0891 41.2734C67.3521 41.7797 67.3521 42.4449 67.3521 43.7753V46.3482C67.3521 46.8016 67.3521 47.1778 67.3417 47.4967H74.0188C79.1796 47.4967 83.3633 51.6804 83.3633 56.8413V72.0235C83.3633 77.1844 79.1796 81.3681 74.0188 81.3681H9.34456C4.1837 81.3681 0 77.1844 0 72.0235V56.8413C0 51.6804 4.1837 47.4967 9.34455 47.4967H17.064C17.0536 47.1778 17.0536 46.8016 17.0536 46.3482V43.7753Z" fill="url(#paint0_linear_2344_5715)" fill-opacity="0.05"/>
|
||||
<path d="M0 90.7132C0 85.5524 4.1837 81.3687 9.34455 81.3687H74.0188C79.1796 81.3687 83.3633 85.5524 83.3633 90.7132V106.836C83.3633 111.997 79.1796 116.181 74.0188 116.181H9.34456C4.1837 116.181 0 111.997 0 106.836V90.7132Z" fill="#D3E3ED"/>
|
||||
<path d="M0 90.7132C0 85.5524 4.1837 81.3687 9.34455 81.3687H74.0188C79.1796 81.3687 83.3633 85.5524 83.3633 90.7132V106.836C83.3633 111.997 79.1796 116.181 74.0188 116.181H9.34456C4.1837 116.181 0 111.997 0 106.836V90.7132Z" fill="url(#paint1_linear_2344_5715)" fill-opacity="0.05"/>
|
||||
<path d="M0 125.526C0 120.365 4.1837 116.181 9.34455 116.181H74.0188C79.1796 116.181 83.3633 120.365 83.3633 125.526V140.708C83.3633 145.869 79.1796 150.052 74.0188 150.052H9.34456C4.1837 150.052 0 145.869 0 140.708V125.526Z" fill="#8B94A4"/>
|
||||
<path d="M0 125.526C0 120.365 4.1837 116.181 9.34455 116.181H74.0188C79.1796 116.181 83.3633 120.365 83.3633 125.526V140.708C83.3633 145.869 79.1796 150.052 74.0188 150.052H9.34456C4.1837 150.052 0 145.869 0 140.708V125.526Z" fill="url(#paint2_linear_2344_5715)" fill-opacity="0.05"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2344_5715" x1="41.6817" y1="41.852" x2="41.6817" y2="81.3681" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2344_5715" x1="41.6817" y1="81.3687" x2="41.6817" y2="116.181" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2344_5715" x1="41.6817" y1="116.181" x2="41.6817" y2="150.052" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg width="301" height="302" viewBox="0 0 301 302" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M99.2803 43.0414C99.2803 40.2394 99.2803 38.8385 99.8342 37.7721C100.301 36.8735 101.034 36.1408 101.932 35.674C102.999 35.1201 104.4 35.1201 107.201 35.1201H132.845C135.647 35.1201 137.048 35.1201 138.114 35.674C139.013 36.1408 139.746 36.8735 140.212 37.7721C140.766 38.8385 140.766 40.2394 140.766 43.0414V48.5566C140.766 49.5003 140.766 50.2851 140.745 50.9518H164.232C164.211 50.2851 164.211 49.5003 164.211 48.5566V43.0414C164.211 40.2394 164.211 38.8385 164.765 37.7721C165.232 36.8735 165.965 36.1408 166.863 35.674C167.93 35.1201 169.33 35.1201 172.132 35.1201H197.776C200.578 35.1201 201.979 35.1201 203.045 35.674C203.944 36.1408 204.676 36.8735 205.143 37.7721C205.697 38.8385 205.697 40.2394 205.697 43.0414V48.5566C205.697 49.5003 205.697 50.2851 205.676 50.9518H219.89C230.759 50.9518 239.57 59.7628 239.57 70.6318V102.931C239.57 113.8 230.759 122.611 219.89 122.611H82.8842C72.0152 122.611 63.2041 113.8 63.2041 102.931V70.6319C63.2041 59.7628 72.0152 50.9518 82.8842 50.9518H99.3014C99.2803 50.2851 99.2803 49.5003 99.2803 48.5566V43.0414Z" fill="#00E5CA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M99.2803 43.0414C99.2803 40.2394 99.2803 38.8385 99.8342 37.7721C100.301 36.8735 101.034 36.1408 101.932 35.674C102.999 35.1201 104.4 35.1201 107.201 35.1201H132.845C135.647 35.1201 137.048 35.1201 138.114 35.674C139.013 36.1408 139.746 36.8735 140.212 37.7721C140.766 38.8385 140.766 40.2394 140.766 43.0414V48.5566C140.766 49.5003 140.766 50.2851 140.745 50.9518H164.232C164.211 50.2851 164.211 49.5003 164.211 48.5566V43.0414C164.211 40.2394 164.211 38.8385 164.765 37.7721C165.232 36.8735 165.965 36.1408 166.863 35.674C167.93 35.1201 169.33 35.1201 172.132 35.1201H197.776C200.578 35.1201 201.979 35.1201 203.045 35.674C203.944 36.1408 204.676 36.8735 205.143 37.7721C205.697 38.8385 205.697 40.2394 205.697 43.0414V48.5566C205.697 49.5003 205.697 50.2851 205.676 50.9518H219.89C230.759 50.9518 239.57 59.7628 239.57 70.6318V102.931C239.57 113.8 230.759 122.611 219.89 122.611H82.8842C72.0152 122.611 63.2041 113.8 63.2041 102.931V70.6319C63.2041 59.7628 72.0152 50.9518 82.8842 50.9518H99.3014C99.2803 50.2851 99.2803 49.5003 99.2803 48.5566V43.0414Z" fill="url(#paint0_linear_2240_5955)" fill-opacity="0.05"/>
|
||||
<path d="M63.2041 142.291C63.2041 131.422 72.0152 122.611 82.8842 122.611H219.89C230.759 122.611 239.57 131.422 239.57 142.291V176.581C239.57 187.45 230.759 196.261 219.89 196.261H82.8842C72.0152 196.261 63.2041 187.45 63.2041 176.581V142.291Z" fill="#002941"/>
|
||||
<path d="M63.2041 142.291C63.2041 131.422 72.0152 122.611 82.8842 122.611H219.89C230.759 122.611 239.57 131.422 239.57 142.291V176.581C239.57 187.45 230.759 196.261 219.89 196.261H82.8842C72.0152 196.261 63.2041 187.45 63.2041 176.581V142.291Z" fill="url(#paint1_linear_2240_5955)" fill-opacity="0.05"/>
|
||||
<path d="M63.2041 215.941C63.2041 205.072 72.0152 196.261 82.8842 196.261H219.89C230.759 196.261 239.57 205.072 239.57 215.941V248.24C239.57 259.109 230.759 267.92 219.89 267.92H82.8842C72.0152 267.92 63.2041 259.109 63.2041 248.24V215.941Z" fill="#000A1C"/>
|
||||
<path d="M63.2041 215.941C63.2041 205.072 72.0152 196.261 82.8842 196.261H219.89C230.759 196.261 239.57 205.072 239.57 215.941V248.24C239.57 259.109 230.759 267.92 219.89 267.92H82.8842C72.0152 267.92 63.2041 259.109 63.2041 248.24V215.941Z" fill="url(#paint2_linear_2240_5955)" fill-opacity="0.05"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2240_5955" x1="151.387" y1="39.0083" x2="151.387" y2="122.611" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2240_5955" x1="151.387" y1="122.611" x2="151.387" y2="196.261" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2240_5955" x1="151.387" y1="196.261" x2="151.387" y2="267.92" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,21 @@
|
||||
import glob from "fast-glob";
|
||||
import * as path from "path";
|
||||
|
||||
async function importArticle(articleFilename: string) {
|
||||
let { meta, default: component } = await import(`../pages/blog/${articleFilename}`);
|
||||
return {
|
||||
slug: articleFilename.replace(/(\/index)?\.mdx$/, ""),
|
||||
...meta,
|
||||
component,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllArticles() {
|
||||
let articleFilenames = await glob(["*.mdx", "*/index.mdx"], {
|
||||
cwd: path.join(process.cwd(), "pages/blog"),
|
||||
});
|
||||
|
||||
let articles = await Promise.all(articleFilenames.map(importArticle));
|
||||
|
||||
return articles.sort((a, z) => new Date(z.date).valueOf() - new Date(a.date).valueOf());
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const navigation = [
|
||||
{
|
||||
title: "Introduction",
|
||||
links: [
|
||||
{ title: "Why Formbricks?", href: "/docs/introduction/why-formbricks" },
|
||||
{ title: "How to achieve this?", href: "/docs/introduction/how-to-achieve-this" },
|
||||
{ title: "What is Formbricks?", href: "/docs/introduction/what-is-formbricks" },
|
||||
{ title: "Quick start", href: "/docs/introduction/quick-start" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Form Creation",
|
||||
links: [{ title: "React Form Builder", href: "/docs/form-creation/react-form-builder" }],
|
||||
},
|
||||
{
|
||||
title: "Data Pipelines",
|
||||
links: [
|
||||
{ title: "Core API", href: "/docs/data-pipelines/core-api" },
|
||||
{ title: "Email Notifications", href: "/docs/data-pipelines/email-notifications" },
|
||||
{ title: "Webhooks", href: "/docs/data-pipelines/webhooks" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Data Insights",
|
||||
links: [{ title: "Form HQ", href: "/docs/data-insights/form-hq" }],
|
||||
},
|
||||
];
|
||||
|
||||
export default navigation;
|
||||
@@ -0,0 +1,8 @@
|
||||
export function formatDate(dateString: string) {
|
||||
return new Date(`${dateString}T00:00:00Z`).toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const { withPlausibleProxy } = require("next-plausible");
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/discord",
|
||||
destination: "https://discord.gg/3YFcABF2Ts",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/roadmap",
|
||||
destination: "https://github.com/orgs/formbricks/projects/1",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/github",
|
||||
destination: "https://github.com/formbricks/formbricks",
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
const withMDX = require("@next/mdx")({
|
||||
extension: /\.mdx?$/,
|
||||
options: {
|
||||
// If you use remark-gfm, you'll need to use next.config.mjs
|
||||
// as the package is ESM only
|
||||
// https://github.com/remarkjs/remark-gfm#install
|
||||
remarkPlugins: [],
|
||||
rehypePlugins: [],
|
||||
// If you use `MDXProvider`, uncomment the following line.
|
||||
// providerImportSource: "@mdx-js/react",
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = withPlausibleProxy({ customDomain: "https://plausible.formbricks.com" })(
|
||||
withMDX(nextConfig)
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "formbricks-com",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/react": "^3.3.0",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@mdx-js/loader": "^2.1.5",
|
||||
"@mdx-js/react": "^2.1.5",
|
||||
"@next/mdx": "^13.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
"lottie-web": "^5.9.6",
|
||||
"next": "13.0.0",
|
||||
"next-plausible": "^3.6.4",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"@types/node": "18.11.6",
|
||||
"@types/react": "18.0.23",
|
||||
"@types/react-dom": "18.0.7",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.18",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"typescript": "4.8.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
|
||||
export const meta = {
|
||||
title: "Formbricks React Library",
|
||||
};
|
||||
|
||||
## Building React forms has never been quicker. But there is more...
|
||||
|
||||
Loads of question types, validation, multi-page forms, logic jumps, i18n, custom styles - all the good stuff you want, but don’t want to build.
|
||||
|
||||
Building forms fast is great, but where do you pipe your data? And what is it worth without a schema?
|
||||
|
||||
### Automatic schema generation for reliable insights
|
||||
|
||||
You can only reliably analyze your submissions when the form schema is sent along with the form.
|
||||
|
||||
Use our React Forms Library with the Formbricks Data Pipes and get a full image of the data sent. Analyze it in our dashboard or forward it to your database.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: "ipsum",
|
||||
dolor: ["sit", "amet", "consectetur"],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import PlausibleProvider from "next-plausible";
|
||||
import "../styles/globals.css";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<PlausibleProvider domain="formbricks.com" selfHosted={true}>
|
||||
<Component {...pageProps} />
|
||||
</PlausibleProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
const themeScript = `
|
||||
let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
function updateTheme(theme) {
|
||||
theme = theme ?? window.localStorage.theme ?? 'system'
|
||||
|
||||
if (theme === 'dark' || (theme === 'system' && isDarkMode.matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if (theme === 'light' || (theme === 'system' && !isDarkMode.matches)) {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
|
||||
return theme
|
||||
}
|
||||
|
||||
function updateThemeWithoutTransitions(theme) {
|
||||
updateTheme(theme)
|
||||
document.documentElement.classList.add('[&_*]:!transition-none')
|
||||
window.setTimeout(() => {
|
||||
document.documentElement.classList.remove('[&_*]:!transition-none')
|
||||
}, 0)
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-theme', updateTheme())
|
||||
|
||||
new MutationObserver(([{ oldValue }]) => {
|
||||
let newValue = document.documentElement.getAttribute('data-theme')
|
||||
if (newValue !== oldValue) {
|
||||
try {
|
||||
window.localStorage.setItem('theme', newValue)
|
||||
} catch {}
|
||||
updateThemeWithoutTransitions(newValue)
|
||||
}
|
||||
}).observe(document.documentElement, { attributeFilter: ['data-theme'], attributeOldValue: true })
|
||||
|
||||
isDarkMode.addEventListener('change', () => updateThemeWithoutTransitions())
|
||||
`;
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html className="antialiased [font-feature-settings:'ss01']" lang="en" dir="ltr">
|
||||
<Head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/faveicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/faveicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/faveicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/faveicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/faveicon/safari-pinned-tab.svg" color="#002941" />
|
||||
<link rel="shortcut icon" href="/faveicon/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#002941" />
|
||||
<meta name="msapplication-config" content="/faveicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</Head>
|
||||
<body className="bg-blue-100 dark:bg-blue">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import Head from "next/head";
|
||||
import { Card } from "@/components/shared/Card";
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import { getAllArticles } from "@/lib/articles";
|
||||
import { formatDate } from "@/lib/utils";
|
||||
|
||||
function Article({ article }: any) {
|
||||
return (
|
||||
<article className="md:grid md:grid-cols-4 md:items-baseline">
|
||||
<Card className="md:col-span-3">
|
||||
<Card.Title href={`/blog/${article.slug}`}>{article.title}</Card.Title>
|
||||
<Card.Eyebrow as="time" dateTime={article.date} className="md:hidden" decorate>
|
||||
{formatDate(article.date)}
|
||||
</Card.Eyebrow>
|
||||
<Card.Description>{article.description}</Card.Description>
|
||||
<Card.Cta>Read article</Card.Cta>
|
||||
</Card>
|
||||
<Card.Eyebrow as="time" dateTime={article.date} className="mt-1 hidden md:block">
|
||||
{formatDate(article.date)}
|
||||
</Card.Eyebrow>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ArticlesIndex({ articles }: any) {
|
||||
return (
|
||||
<>
|
||||
<Layout
|
||||
title="Blog"
|
||||
description="Blog articles around Formbricks, feature updates, the open source ecosystem and the future of forms.">
|
||||
<div className="mx-auto my-20 max-w-3xl md:border-l md:border-blue-100 md:pl-6 md:dark:border-blue-700/40">
|
||||
<div className="flex flex-col space-y-16">
|
||||
{articles.map((article: any) => (
|
||||
<Article key={article.slug} article={article} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return {
|
||||
props: {
|
||||
articles: (await getAllArticles()).map(({ component, ...meta }) => meta),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
After Width: | Height: | Size: 120 KiB |
@@ -0,0 +1,116 @@
|
||||
import Image from "next/image";
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import RobinHoodMeme from "./robin-hood-meme.png";
|
||||
import WhyWeDoIt from "./why-we-do-it.png";
|
||||
import EverythinEverywhereAllAtOnce from "./everything_everywhere_all_at_once.png";
|
||||
|
||||
export const meta = {
|
||||
title: "Open source forms will save the world.",
|
||||
description: "What motivates us to build open source tech in such a crowded space?",
|
||||
date: "2022-08-26",
|
||||
};
|
||||
|
||||
<Image src={RobinHoodMeme} alt="Robin Hood Meme" className="max-w-lg mx-auto rounded-lg" />
|
||||
|
||||
_What motivates us to build open source tech in such a crowded space? What do we see what others might not? And how do we understand the relationship between free open source tech and a commercial complement?_
|
||||
|
||||
As we build and understand open source as a mentality and opportunity better, the answers to the questions above change. We want to walk you through the current state of answers and invite you to share your thoughts.
|
||||
|
||||
Before we talk about why the open source mentality is growing on Matti and I, let's kick it off easy: Why are we building a form tool?
|
||||
|
||||
## Why are we building the 100th form tool?
|
||||
|
||||
Forms are an essential part of web infrastructure, every application has one or more. Whenever someone wants to get data from a brain into a computer, forms come into play:
|
||||
|
||||
<Image src={WhyWeDoIt} alt="Why we do it" className="max-w-lg mx-auto rounded-lg" />
|
||||
|
||||
Secondly, forms are the starting point of many deeper interactions. Lead generation forms, application forms, immigration forms collect lots of meaningful data. It gets processed and enriched, often in the process of building a lasting customer relationship. This implies more interaction, more data input and management, more opportunities to grow. Owning the starting point of this relationship is inherently valuable.
|
||||
|
||||
Lastly, forms are sticky and viral. Once people set up their forms and surveys, they are hesitant to change, unless there is a very good reason to do so (i.e. full data ownership or outrageous pricing). As users share around their forms and surveys, our brand naturally starts popping up all over the internet ✅
|
||||
|
||||
## Why do we build open source?
|
||||
|
||||
There are two answers to this question. A business one and a purpose one. Even though they go hand in hand, they are distinct.
|
||||
|
||||
Let's start with business, because without a working business, it's very difficult to build towards your purpose sustainably.
|
||||
|
||||
Why are large corporations like Google, Facebook, IBM, etc. investing $$$ in OSS? Did their executives stop believing in capitalism?
|
||||
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
className="mx-auto"
|
||||
src="https://www.youtube.com/embed/j4ovbmsp6p0?start=9"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
|
||||
_"I doubt it."_
|
||||
|
||||
I also doubt it. The reason is rooted in microeconomics: Demand for a product increases when the price of its complements decreases.
|
||||
|
||||
What does that mean? If you're selling cars, you want gas to be as cheap as possible, so more people buy your cars. If you sell software, you want hardware to be as cheap as possible, so more people buy your software. If you're selling a specific solution for a problem large organisations have with form infrastructure, you want as many companies as possible to work with your technology. So:
|
||||
|
||||
> Smart companies try to commoditize their products' complements.
|
||||
>
|
||||
> - Joel Spolsky
|
||||
|
||||
_There is a great article by Joel explaining this idea much better than I could, I highly recommend [reading it.](https://www.joelonsoftware.com/2002/06/12/strategy-letter-v/)_
|
||||
|
||||
So in order to create a growing market for our paid product, we have a strong incentive to freely share and widely distribute our open source technology.
|
||||
|
||||
To break this down, let's look at the challenges commercial OSS companies face. Many of the characteristic have both advantages and disadvantages, so we discuss them together:
|
||||
|
||||
- Weak lock-in: While the commercial component can still have a lock-in, a customer (or competitor) could build their own solution based on the open source core technology. Hence, the lock-in is of your product is weaker. This certainly is a controversial aspect of COSS offering as lock-ins have been celebrated as THE profit driver in B2B software for the past decade.
|
||||
|
||||
We want to challenge that notion: A disrupted business always starts with the misalignment of what the customer wants to buy and what the company wants to offer. A lock-in falls into this category of misalignments between buyer and seller: No one likes to be locked in. Especially large organizations are averse to hard lock-ins as they can get really, really expensive really, really fast. While offering a really expensive service is great to drive astronomical profits, it drives customers to competing offers sooner or later. In other words, *your lock-in is our opportunity.*
|
||||
|
||||
- Low customer acquisition cost: You run a profitable business when you make more from a customer than it costs you to acquire and service them. With a weaker lock-in, it's harder to drive astronomical profits, hence the cost of acquiring a customer has to be lower. The more people use, work with, and build on top of your OS technology, the bigger the market for your commercial complement. A COSS company without an active community which drives the adoption of your tech presents an unfavorable combination: Weak lock-in with high acquisition cost.
|
||||
- Self-hosting: To process sensitive data in compliance with data privacy regulations, many companies want the option to self-host their tooling. OSS uniquely enables them to do so. Every single of our Early Bird Customers referred to privacy as their main reason to buy the deal.
|
||||
- Security: Forms often manage sensitive data, having more eyeballs on the code generally leads to fewer weaknesses to be exploited.
|
||||
|
||||
## Open source as a mentality
|
||||
|
||||
Matti and I are not just motivated by the chance to gain economic security for our families over the course of the next decade. We also believe in and want to contribute to the positive sum thinking of the open source community. Building a commercial open source software company presents a model Robin Hood would be proud of:
|
||||
|
||||
<Image src={RobinHoodMeme} alt="Robin Hood Meme" className="max-w-lg mx-auto rounded-lg" />
|
||||
|
||||
A COSS company builds cool tech and shares it freely. It uses its own tech to offer a product to mostly enterprise customers and thereby finances the further development of the technology everyone can use - to a large extent - for free.
|
||||
|
||||
What this model often allows is enabling other people to leverage the technology and offer products built on top of it. It's positive sum thinking in practice.
|
||||
|
||||
For founders and employees, this is a great way to spend days, months, and years. You are creating significantly more value than you capture, and still capture enough to maintain and extend the technology. Everyone wins - except for competing proprietary tools sitting in walled gardens.
|
||||
|
||||
But there is more.
|
||||
|
||||
## The world goes down, open source forms will save us!
|
||||
|
||||
Growing up in the longest period without war in Europe as well as the longest "up only"-market in recent history, 2022 feels utterly unsettling. War, inflation, recession, heat records, wildfires, outrageous inequality, hate, weak democracies -- everything goes to sh\*t everywhere, all at once.
|
||||
|
||||
<Image
|
||||
src={EverythinEverywhereAllAtOnce}
|
||||
alt="Everything everywhere all at once"
|
||||
className="max-w-lg mx-auto rounded-lg"
|
||||
/>
|
||||
|
||||
If you haven't seen "Everything, everywhere, all at once" I highly recommend it.
|
||||
|
||||
I'm not here to tell you, that open source forms will save us. In fact, the challenges seem so big and complex that few believe we can prevent crisis. Things first must get unambiguously ugly before humanity will act -- a moment when it'll be too late, at least to save the climate we all depend on. Yay!
|
||||
|
||||
## So why am I ruining your mood?
|
||||
|
||||
I am, because OSS leverages the internet for a greater good than selling ads. A person with a laptop can write a piece of code which empowers hundreds of thousands of people to be significantly more productive in achieving their objectives. This is insane! Code distribution has 0 marginal cost, it should be used as widely as possible. Every open source app and infrastructure tech creates exponentially more value than it's proprietary counterpart.
|
||||
|
||||
## The *last* form tool humanity needs
|
||||
|
||||
Our infinite mission is to free as much time, focus and energy from rewriting existing code as possible. We aim to achieve that by building essential web infrastructure in a modular way: Take what you like, build on top what you need - and contribute it back, so everyone can use it. The last part is essential, and building inclusive OSS is the only way to that.
|
||||
|
||||
## You're invited!
|
||||
|
||||
All of this is very exciting! As of now, we are still super early. Matti recognized the need for versatile open source form infrastructure, Johannes joined him to build a first prototype of a commercial component. Early traction picked up much quicker than expected, which is a source of pure joy for both of us. As of now, we are wrapping up our freelance gigs to work on this full-time [(feel free to intro us to your favorite OS angels).](mailto:johannes@snoopforms.com)
|
||||
|
||||
We invite you to join us and build the last form tool humanity needs <br/>
|
||||
Say Hi in our [Discord!](https://snoopforms.com/discord)
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 155 KiB |
@@ -0,0 +1,185 @@
|
||||
import Image from "next/image";
|
||||
import LayoutMdx from "@/components/shared/LayoutMdx";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import { Logo } from "@/components/shared/Logo";
|
||||
import TitleImage from "./title-image.png";
|
||||
import ProprietaryDependence from "./propietary-dependence.jpeg";
|
||||
|
||||
export const meta = {
|
||||
title: "Why Open-Source + No-Code is the Future of Enterprise & Goverment Software",
|
||||
description:
|
||||
"Here is why a no-code interface is cheatcode for OSS and why particularly large corporations and governments are to benefit the most",
|
||||
date: "2022-06-03",
|
||||
};
|
||||
|
||||
<Image src={TitleImage} alt="Title Image" className="max-w-lg mx-auto rounded-lg" />
|
||||
|
||||
_Open source software (OSS) beats out proprietary software in every regard - except for value capturing. No-Code tools shorten the feedback loop between builders and consumers, kicking productivity through the roof. Here is why a no-code interface is cheatcode for OSS and why particularly large corporations and governments are to benefit the most._
|
||||
|
||||
## Open source is so popular I'm surprised it's not dating a Kardashian
|
||||
|
||||
Don't be fooled that money makes the world go 'round, it's open source software. Your Mac and iPhone run on Unix, 4.7 billion Android devices run on Linux. Every single supercomputer runs on Linux, Google, Facebook, and Twitter run on Linux - you get the point. Or maybe not? 90% of the public cloud workload runs on Linux and SpaceX powers the Falcon Rocket with Linux.
|
||||
|
||||
Here is a pretty cringy cartoon of what the world would be without Linux:
|
||||
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
className="mx-auto"
|
||||
src="https://www.youtube-nocookie.com/embed/LOPLogACIT0"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
|
||||
Enough of Linux.
|
||||
|
||||
### Proprietary software is like cable TV
|
||||
|
||||
You don't know exactly what you're getting and you're stuck with it when you don't want it anymore.
|
||||
|
||||
The idea of making and selling proprietary software is simple: If you cannot see the code of what you're buying, you're more dependent. With hundreds or thousands of employees involved and tools being deeply integrated into the tech infrastructure, the switching cost is too high. You're **locked in.**
|
||||
|
||||
> The larger the organization gets, the more important it is that your tool stack is **future-proof**.
|
||||
|
||||
### Enterprises & Governments are digging OSS
|
||||
|
||||
OSS doesn't have these restrictions. Unlike cable TV, you know exactly what you're getting, because the source code is openly accessible. If you are unhappy with the integrations built by the community or company developing the tool, you can build your own. OSS is the monthly subscription you can cancel once you binge watched the whole library. **It's better for the consumer and this is why it's the winning model.**
|
||||
|
||||
<Image
|
||||
src={ProprietaryDependence}
|
||||
alt="Depend on proprietary software meme"
|
||||
className="max-w-lg mx-auto rounded-lg"
|
||||
/>
|
||||
|
||||
But no lock-in is only one key advantage, **data ownership is equally important**. In times of GDPR, CCPA, YMCA, etc. owning your data is essential to be fully compliant. Currently, there a lot of legal uncertainty for European institutions to use any SaaS tool where data flows to servers located in the United States. You can neither use Google Analytics nor Zapier. The solution, in many cases, is to neither analyze nor automate. Governmental institutions or larger companies cannot afford the reputational risk and attached fines of being exposed as non-compliant with privacy legislation.
|
||||
|
||||
> In Germany, cities, municipalities and states have to choose the open source alternative, when there is one. In Europe, 49% of public institutions use OSS according to a survey from the University of Maastricht.
|
||||
|
||||
The Privacy Shield - which was the legal foundation for exchanging data between the US & EU - wasn't renewed because **Data Processing Agreements were never meant to be a final solution**. We need to rethink the approach to building technology to protect user data by design and reduce the power of data aggregators. The regulatory framework favours OSS already and will continue to do so in the long-term, which constitutes a significant competitive advantage for OSS.
|
||||
|
||||
_Enough about OSS, let's talk about No-Code:_
|
||||
|
||||
## No-Code so popular it's a surprise it wasn't canceled yet
|
||||
|
||||
Well, maybe because everyone wins with No-Code on the rise. No-Code is compelling: It commoditizes code and it commoditizes design. It cuts straight to the core: **providing value through functionality**. The commoditization shortens the feedback loop between the builder and the user significantly, leading to faster iterations.
|
||||
|
||||
<Callout title="Funfact" type="note">
|
||||
No-Code is a facet of a trend that has been on-going for a while: Moving away from the gut feeling and
|
||||
intuition of a few leaders to data-driven results of the many. <br />
|
||||
In the past a Don Draper kind of character decided which ad works best based on experience (and hubris). These
|
||||
days every other social media manager runs 20 variations of an ad to get data on what actually works best. Enabling
|
||||
people to no-code their own solutions shortens the feedback loop between decision-makers and consumers.
|
||||
</Callout>
|
||||
The beauty of No-Code is that every stakeholder gets something out of it, something different:
|
||||
|
||||
### Why less-technical people ❤️ No-Code
|
||||
|
||||
- Non-engineers love No-Code tools because they can suddenly **build everything they want**. In the realms of web applications, there is almost nothing you cannot not build with Bubble.io. No-Code tools are extremely empowering.
|
||||
- Furthermore, we can **build much faster**. Not having to find, vet and maybe pay a web developer, we can plug functionality from API-first tools together to test our ideas.
|
||||
- This not only leads to better results but is extremely fulfilling. During my studies I built WordPress websites for smaller companies, always focussing on teaching the employees to manage and extend the website themselves (because I wanted to do other things with my time). Many of the people I worked with really enjoyed picking up a new skill **boosting their self-efficacy**. Happy employees, happy company.
|
||||
|
||||
### Why devs ❤️ No-Code
|
||||
|
||||
- A huge advantage in larger teams is that devs can **focus on tasks they enjoy**. Much like I didn't like updating content on WordPress websites, most devs don't want to update content or fix typos in their web application: They want Jules from HR to fix the application form herself. No-Code allows them to do that. They implement a tool once and hand them over to the people working with them day in day out.
|
||||
- Generally, a lot of devs love No-Code tools. Many prefer spinning up a tool with a user interface rather than the CLI because it requires less doc reading. Using No-Code tools can **speed up the development** significantly as they are less prone to cause bugs.
|
||||
|
||||
### Why managers ❤️ No-Code
|
||||
|
||||
- Lastly, managers love No-Code tools. In short, because they **deliver better results quicker**. No-Code tools reduce the number of people involved in a process significantly. When you used to need a designer, a developer, a marketer and a set of customers to test a new idea, the marketer can get the job done alone. Design & engineering can be pulled in when the market demand is proven.
|
||||
- This leads to much **more efficient communication** as every node of communication increases the amount of communication exponentially, and
|
||||
- It leads to a **reduced demand of engineers and designers**, which are impossible to hire these days anyways. Overall, you can achieve better results quicker - for less money.
|
||||
|
||||
## Open source + No-Code = 🔥
|
||||
|
||||
Let's look at the compound benefit of combining OSS with No-Code interfaces in larger organizations. We'll do so by looking at why everyone involved would want to use OSS or a No-Code interface. Let's start with the CTO:
|
||||
|
||||
### CTO
|
||||
|
||||
---
|
||||
|
||||
**Why the CTO digs OSS:**
|
||||
|
||||
- No lock-in
|
||||
- 100% Data Ownership
|
||||
- No more tedious GDPR compliance checks & meetings
|
||||
- fewer bugs and security risks because more people see and fix the code
|
||||
|
||||
**Why the CTO digs No-Code:**
|
||||
|
||||
- has happy engineers (see below)
|
||||
- has to hire & manage less engineers
|
||||
|
||||
### The Software Engineer
|
||||
|
||||
---
|
||||
|
||||
**Why the Software Engineer digs OSS:**
|
||||
|
||||
- Loves working with a modern tech stack
|
||||
- Can check the code instead of opening support tickets
|
||||
- Can build integrations
|
||||
- Can hang with the community
|
||||
- Can build public reputation with contributing to OS repo
|
||||
|
||||
**Why the Software Engineer digs No-Code:**
|
||||
|
||||
- Doesn't have to fix typos or update content
|
||||
|
||||
### Everyone working with the tool
|
||||
|
||||
---
|
||||
|
||||
**Why 'everyone' digs OSS:**
|
||||
|
||||
- tbh they probably don't care too much
|
||||
|
||||
**Why 'everyone' digs No-Code:**
|
||||
|
||||
- Can build & update what they want
|
||||
- Is less dependent on others
|
||||
- Feels more self-efficacy
|
||||
|
||||
Overall, a pretty compelling offer, innit? Some closing thoughts 👇
|
||||
|
||||
### The productivity & ownership boost is yet to come
|
||||
|
||||
The API-first approach of building products has lowered the barrier to be entrepreneurial significantly over the past 10ish years. It's much cheaper and easier to test your business idea, and many people make use of that.
|
||||
|
||||
**This explosion of entrepreneurial spirit is yet to come within large organizations** in the form of ownership & initiative (intrapreneurship). To this day, they're restricted by data privacy compliance and missing integrations for the legacy stack the organization maintains. Offering them OSS with a No-Code interface is the **key to unlock their potential**.
|
||||
|
||||
### Your proprietary code is my opportunity.
|
||||
|
||||
The OSS Renaissance is in full swing. The argument laid out above is just one of many reasons why we are so excited to build in this space. Others are composability, communal effort or the ratio of value creation vs. value capture - but these are topics for other articles.
|
||||
|
||||
---
|
||||
|
||||
<Logo width="300" />
|
||||
|
||||
Finally good open source forms
|
||||
|
||||
We're developing an open source Typeform alternative to solve all form needs - from indie hackers to governments. Forms are an essential pillar of web infrastructure and yet there is no comprehensive open source form tool. We're changing that.
|
||||
|
||||
---
|
||||
|
||||
### Sources
|
||||
|
||||
[6.648MRD smartphones in 2022](https://www.statista.com/statistics/203734/global-smartphone-penetration-per-capita-since-2005/)
|
||||
|
||||
[70,56% Android 2022](https://gs.statcounter.com/os-market-share/mobile/worldwide)
|
||||
|
||||
[100% of super computers run Linux](https://itsfoss.com/linux-runs-top-supercomputers/)
|
||||
|
||||
[SpaceX Falcon](https://www.omgubuntu.co.uk/2018/08/interesting-facts-about-linux)
|
||||
|
||||
[90% of public cloud workload](https://www.cbtnuggets.com/blog/certifications/open source/why-linux-runs-90-percent-of-the-public-cloud-workload)
|
||||
|
||||
[49% of public institutions use OSS](https://www.pro-linux.de/news/1/8810/die-haelfte-der-eu-aemter-arbeitet-mit-freier-software.html)
|
||||
|
||||
[New Privacy Shield not before EOY 22](https://www.cnbc.com/2022/03/25/eu-and-us-agree-new-data-transfer-pact-to-replace-privacy-shield.html)
|
||||
|
||||
[TechCrunch: Google Analytics not complient with GDPR](https://techcrunch.com/2022/02/10/cnil-google-analytics-gdpr-breach/?guccounter=1)
|
||||
|
||||
Opinions from experts - [1](https://noyb.eu/en/privacy-shield-20-first-reaction-max-schrems) & [2](https://www.cliffordchance.com/insights/resources/hubs-and-toolkits/talking-tech/en/articles/2022/04/towards-privacy-shield-2-back-to-normal-for-us-data-trans.html)
|
||||
|
||||
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;
|
||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 477 KiB |
@@ -0,0 +1,76 @@
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Button from "../components/shared/Button";
|
||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { ChatBubbleOvalLeftEllipsisIcon, EnvelopeIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
const topContributors = [
|
||||
{
|
||||
name: "Midka (8 commits)",
|
||||
href: "https://github.com/kymppi",
|
||||
},
|
||||
{
|
||||
name: "Timothy (6 commits)",
|
||||
href: "https://github.com/timothyde",
|
||||
},
|
||||
{
|
||||
name: "Kiran (3 commits)",
|
||||
href: "https://github.com/devkiran",
|
||||
},
|
||||
{
|
||||
name: "Francois (1 commit)",
|
||||
href: "https://github.com/fdis111",
|
||||
},
|
||||
{
|
||||
name: "Chetan (1 commit)",
|
||||
href: "https://github.com/chetan",
|
||||
},
|
||||
];
|
||||
|
||||
const GetStartedPage = () => (
|
||||
<Layout title="Community" description="Get support for anything your building - or just say hi 👋">
|
||||
<HeroTitle headingPt1="Join the" headingTeal="Formbricks" headingPt2="Community" />
|
||||
<div className="mb-32 grid grid-cols-2 gap-8 px-16">
|
||||
<div className="rounded-lg bg-gradient-to-b from-blue-200 to-gray-50 px-10 py-6 dark:from-blue-300 dark:to-gray-100">
|
||||
<h2 className="text-blue mt-7 text-4xl font-bold">Top Contributors</h2>
|
||||
<p className="text-sm text-gray-500">The leader board of the Formbricks community contributors 🙌</p>
|
||||
<ol className="mt-10 ml-4 list-decimal">
|
||||
{topContributors.map((MVP) => (
|
||||
<li key={MVP.name} className="text-blue my-3 text-lg font-bold hover:text-blue-600">
|
||||
<a href={MVP.href} className="" target="_blank" rel="noreferrer">
|
||||
{MVP.name}
|
||||
|
||||
<ArrowTopRightOnSquareIcon className="text-teal mb-1 ml-1 inline h-5 w-5" />
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<div className="from-blue rounded-lg bg-gradient-to-b to-black px-10 pt-6 pb-12 dark:from-blue-300 dark:to-gray-100">
|
||||
<h3 className="mt-7 text-4xl font-bold text-gray-100 ">Community Discord</h3>
|
||||
<p className="text-gray-200">Get support for anything your building - or just say hi 👋</p>
|
||||
<Button className="mt-7 w-full text-center font-bold" variant="highlight">
|
||||
Join Discord <ChatBubbleOvalLeftEllipsisIcon className="ml-1 inline h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-7 flex">
|
||||
<a href="https://twitter.com/formbricks" target="_blank" rel="noreferrer" className="w-1/2">
|
||||
<div className="mr-3 flex justify-center rounded-lg bg-gradient-to-b from-blue-200 to-gray-50 py-6 dark:from-blue-300 dark:to-gray-100">
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" className="h-20 w-20 text-[#1DA1F2]">
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
<a href="mailto:hola@formbricks.com" className="w-1/2 ">
|
||||
<div className="ml-3 flex justify-center rounded-lg bg-gradient-to-b from-blue-200 to-gray-50 py-6 dark:from-blue-300 dark:to-gray-100">
|
||||
<EnvelopeIcon className="text-gray ml-1 h-20 w-20 hover:text-gray-400" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default GetStartedPage;
|
||||
@@ -0,0 +1,69 @@
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import ImageCoreApi from "@/images/core-api.svg";
|
||||
import Image from "next/image";
|
||||
import TryItCTA from "../components/shared/TryItCTA";
|
||||
import WhyFormbricks from "../components/shared/WhyFormbricks";
|
||||
import FeatureHighlight from "@/components/shared/FeatureHighlight";
|
||||
import { CodeBracketSquareIcon, TableCellsIcon, ServerStackIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "allForms",
|
||||
name: "Works with Every Form",
|
||||
description: "Javascript forms, HTML forms, form widgets - all works. Send it over, take it from here.",
|
||||
icon: CodeBracketSquareIcon,
|
||||
},
|
||||
{
|
||||
id: "schemaSupport",
|
||||
name: "Schema Support",
|
||||
description: "Add as many email addresses as you like. Send responses and notifications.",
|
||||
icon: TableCellsIcon,
|
||||
},
|
||||
{
|
||||
id: "selfHost",
|
||||
name: "Self-host or Cloud",
|
||||
description: "Manage submissions in our FormHQ or host the entire solution yourself.",
|
||||
icon: ServerStackIcon,
|
||||
},
|
||||
];
|
||||
|
||||
const CoreAPIPage = () => (
|
||||
<Layout
|
||||
title="Core API"
|
||||
description="Our core API handles all of the submission handling of your forms and surveys.">
|
||||
<HeroTitle headingPt1="Core" headingTeal="API" />
|
||||
<FeatureHighlight
|
||||
featureTitle="The OS form engine"
|
||||
text="Our core API handles all of the submission handling of your forms and surveys. Our main objective is versatility, so that you can use it with any currently existing form.
|
||||
Soon we will integrate it with our React Form Builder. This allows for handling schemas so that you get a full image of your submission data. "
|
||||
img={<Image src={ImageCoreApi} alt="react library" className="rounded-lg" />}
|
||||
isImgLeft
|
||||
/>
|
||||
<ul role="list" className="-mb-16 grid grid-cols-1 gap-6 px-12 pt-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature.id}
|
||||
className="relative col-span-1 mt-16 flex flex-col rounded-xl bg-gradient-to-b from-blue-200 to-gray-100 text-center drop-shadow-sm dark:from-black dark:to-blue-900">
|
||||
<div className="absolute -mt-12 w-full">
|
||||
<div className="via-blue to-blue mx-auto flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-black shadow">
|
||||
<feature.icon className="mx-auto h-10 w-10 flex-shrink-0 text-teal-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col p-10">
|
||||
<h3 className="text-blue my-4 text-lg font-medium dark:text-blue-100">{feature.name}</h3>
|
||||
<dl className="mt-1 flex flex-grow flex-col justify-between">
|
||||
<dt className="sr-only">Description</dt>
|
||||
<dd className="text-sm text-gray-600 dark:text-blue-400">{feature.description}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<WhyFormbricks />
|
||||
<div className="h-12"></div>
|
||||
<TryItCTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default CoreAPIPage;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "Form HQ",
|
||||
};
|
||||
|
||||
coming soon
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "Core API",
|
||||
};
|
||||
|
||||
coming soon
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "Email Notifications",
|
||||
};
|
||||
|
||||
coming soon
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "Webhooks",
|
||||
};
|
||||
|
||||
coming soon
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "React Form Builder",
|
||||
};
|
||||
|
||||
coming soon
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
|
After Width: | Height: | Size: 310 KiB |
@@ -0,0 +1,13 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import HeroAnimation from "./hero-animation-still.svg";
|
||||
import Image from "next/image";
|
||||
|
||||
export const meta = {
|
||||
title: "Formbricks – Open-source forms & survey toolbox",
|
||||
};
|
||||
|
||||
Modular, customizable, extendable. We're building all essential form functionality on an open platform.
|
||||
|
||||
<Image src={HeroAnimation} alt="HeroAnimation" />
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "How will we achieve this?",
|
||||
};
|
||||
|
||||
The goal is clear: Build the last form tool humanity needs. What does such a solution have to look like?
|
||||
|
||||
### Modular
|
||||
|
||||
Form needs differ widely but a one-size-fits all monolyth would fit no one. It would be slow and bloated. Only when it's possible to compose exactly the solution you need with the performance you expect we can compete with current solutions.
|
||||
|
||||
### Customizable
|
||||
|
||||
In many product decisions you cannot have it both ways. We have to build opionated to make progress. We always choose what we deem the best option based on over 10 years of experience building forms and form-based products. If you prefer it the other way, just change it up. Our code is freely accessible and licensed accordingly.
|
||||
|
||||
### Extendable
|
||||
|
||||
Similarily, we cannot offer a solution for every niche and edge case. And we don't have to. Running open source allows you to go 95% of the way with our tech and build a thin integration layer to make it fit your need perfectly.
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "Quick Start",
|
||||
};
|
||||
|
||||
We're sporting a mono repository to make it as easy as possible to get you up and running with the Bricks.
|
||||
|
||||
Please head over to [Github](https://github.com/formbricks/formbricks), clone the code and get started.
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "What is Formbricks?",
|
||||
};
|
||||
|
||||
On a high-level, form functionality can be divided into five groups: Form creation, usage analytics, data handling, data insights and data actions. Over time, we will offer solution bricks for each of these groups.
|
||||
|
||||
### Form Creation Bricks
|
||||
|
||||
There are two ways to create a form: Code and No Code. We will empower you to create forms both ways as well as hybrid (start with Code, update with No Code).
|
||||
|
||||
We're kicking it off with a [Library for React.js](/react-form-builder) and follow up with a fully compatible No Code builder. Your engineers will fly through form creation and can embed them natively into the product, while your operations teams can control the content of the form. Finally, no more Jira tickets to fix a typo.
|
||||
|
||||
### Usage Analytics
|
||||
|
||||
You want to know how respondents interact with your form or survey. How many complete the survey? Where do they drop-off? What combination of questions works best? We'll provide key insights natively and allow you to hook up your favorite product analytics tool (i.e. [PostHog](https://posthog.com)).
|
||||
|
||||
### Data Handling Bricks
|
||||
|
||||
Creating a form is only the first step. You need a destination for your submissions. We offer an [API endpoint](/core-api) where you can submit your pre-existing forms to. Within minutes you can start receiving submissions and view them in your [dashboard](/form-hq).
|
||||
|
||||
Additionally, we're offering [email notifications](/email), [webhooks](/webhooks) and integrations into all the great tools you're already using. Control the flow of your qualitative data in one place.
|
||||
|
||||
### Data Insight Bricks
|
||||
|
||||
To get the most out of your qualitative data, you will perform an in-depth analysis of your submissions. While we are busy building out the core tech, we enable you to view and export data as easily as possible. Down the line, assisting in the data analysis is where the magic happens 🧚
|
||||
|
||||
### Data Action Bricks
|
||||
|
||||
Similarily, you want to act on the qualitative insights about your customer or employee relationship. While you can partially do that with 3rd party tools, you gain real leverage when the flow from form creation to submission analysis to action is integrated intelligently.
|
||||
|
||||
Imagine your form tool knows a customers shopping history, the previous NPS rating and understands the sentiment of the micro-survey this customer just filled out on your webshop. It can automatically send out a contextualized voucher for a product the customer is likely to buy. But let's not spill the beans here :)
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Layout } from "@/components/docs/Layout";
|
||||
import { Fence } from "@/components/shared/Fence";
|
||||
|
||||
export const meta = {
|
||||
title: "Why Formbricks?",
|
||||
};
|
||||
|
||||
With all the form tools out there, why would we build another one?
|
||||
|
||||
## Why though?
|
||||
|
||||
It doesn't matter what products we've built over the past couple of years, forms always played an important role. While there are great solutions out there, you very quickly reach the barrier of what's possible. They all do one thing well, but when it comes to leveraging qualitative data, one thing isn't enough.
|
||||
|
||||
Over time, this leads to a collection of island solutions in different teams as well as creative approaches to hack form functionality together. Data is scattered everywhere, maintenance is costly and valuable insights are not used to their full potential.
|
||||
|
||||
### Forms bridge 🧠 and 💻
|
||||
|
||||
Until Elon ships the Neuralink brain-machine interface, forms are our best bet to get information out of brains into computers. In a business context, a 🧠 is either a customer or an employee. The interface to these very important stakeholders should run as smoothly as possible, almost as if it was built on one platform 😉
|
||||
|
||||
### Stop rewriting existing code
|
||||
|
||||
A common idea is to just "build it ourselves". Forms and surveys appear to be quick to build but once you get started you notice that it gets complicated quickly. Validation, spam protection, data pipelines into different databases and 3rd party services, schemas or supported data analysis are just a few of the things you want, but don't want to build yourself. And you shouldn't, someone already wrote that code, you just cannot use it. This is changing with Formbricks.
|
||||
|
||||
### The last form tool
|
||||
|
||||
The goal is clear: Build the last form tool humanity needs. But how?
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>;
|
||||
@@ -0,0 +1,67 @@
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import ImageEmail from "@/images/email.svg";
|
||||
import Image from "next/image";
|
||||
import TryItCTA from "../components/shared/TryItCTA";
|
||||
import FeatureHighlight from "@/components/shared/FeatureHighlight";
|
||||
import { CodeBracketSquareIcon, EnvelopeOpenIcon, ServerStackIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "allForms",
|
||||
name: "Works with Every Form",
|
||||
description: "Javascript forms, HTML forms, form widgets - all works. Send it over, take it from here.",
|
||||
icon: CodeBracketSquareIcon,
|
||||
},
|
||||
{
|
||||
id: "dataInsights",
|
||||
name: "Multiple Email Addresses",
|
||||
description: "Add as many email addresses as you like. Send responses and notifications.",
|
||||
icon: EnvelopeOpenIcon,
|
||||
},
|
||||
{
|
||||
id: "selfHost",
|
||||
name: "Self-host or Cloud",
|
||||
description: "Set your emails up in our Form HQ or self-host the entire solution yourself.",
|
||||
icon: ServerStackIcon,
|
||||
},
|
||||
];
|
||||
|
||||
const EmailPage = () => (
|
||||
<Layout
|
||||
title="Form to Email"
|
||||
description="In some cases, the good old email is the way to go. In the Form HQ you can setup forwarding submission data to one or more emails.">
|
||||
<HeroTitle headingPt1="Email" />
|
||||
<FeatureHighlight
|
||||
featureTitle="Get responses to your inbox"
|
||||
text="In some cases, the good old email is the way to go. In the Form HQ you can setup forwarding submission data to one or more emails."
|
||||
img={<Image src={ImageEmail} alt="react library" className="rounded-lg" />}
|
||||
isImgLeft
|
||||
cta="Get started"
|
||||
href="/get-started"
|
||||
/>
|
||||
<ul role="list" className="grid grid-cols-1 gap-6 px-12 pt-2 pb-16 sm:grid-cols-2 md:grid-cols-3">
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature.id}
|
||||
className="relative col-span-1 mt-16 flex flex-col rounded-xl bg-gradient-to-b from-blue-200 to-gray-100 text-center drop-shadow-sm dark:from-black dark:to-blue-900">
|
||||
<div className="absolute -mt-12 w-full">
|
||||
<div className="via-blue to-blue mx-auto flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-black shadow">
|
||||
<feature.icon className="mx-auto h-10 w-10 flex-shrink-0 text-teal-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col p-10">
|
||||
<h3 className="text-blue my-4 text-lg font-medium dark:text-blue-100">{feature.name}</h3>
|
||||
<dl className="mt-1 flex flex-grow flex-col justify-between">
|
||||
<dt className="sr-only">Description</dt>
|
||||
<dd className="text-sm text-gray-600 dark:text-blue-400">{feature.description}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<TryItCTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default EmailPage;
|
||||
@@ -0,0 +1,109 @@
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import TryItCTA from "../components/shared/TryItCTA";
|
||||
import HeroAnimation from "../components/shared/HeroAnimation";
|
||||
import HeadingCentered from "@/components/shared/HeadingCenetered";
|
||||
import {
|
||||
CodeBracketIcon,
|
||||
SquaresPlusIcon,
|
||||
ChartBarIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
DocumentPlusIcon,
|
||||
RectangleGroupIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import clsx from "clsx";
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "dataPipelines",
|
||||
name: "Data Pipelines",
|
||||
description: "Save your data where you need it. Use webhooks or pre-built integrations.",
|
||||
icon: SquaresPlusIcon,
|
||||
},
|
||||
{
|
||||
id: "dataInsights",
|
||||
name: "Powerful Data Insights",
|
||||
description: "View and manage your results quicker. Handle submissions in our dahsboard.",
|
||||
icon: ChartBarIcon,
|
||||
},
|
||||
{
|
||||
id: "fullOwnership",
|
||||
name: "Full Data Ownership",
|
||||
description: "We run open source. Self-host your instance and your data never leaves your servers.",
|
||||
icon: CodeBracketIcon,
|
||||
},
|
||||
{
|
||||
id: "nocodeBuilder",
|
||||
name: "No-Code Builder",
|
||||
description: "Let your operators create and change forms. Stick with React to style and embed forms.",
|
||||
icon: RectangleGroupIcon,
|
||||
comingSoon: true,
|
||||
},
|
||||
{
|
||||
id: "analytics",
|
||||
name: "Built-in Analytics",
|
||||
description: "Opening rate, drop-offs, conversions. Use privacy-first analytics out of the box.",
|
||||
icon: ArrowTrendingUpIcon,
|
||||
comingSoon: true,
|
||||
},
|
||||
{
|
||||
id: "templates",
|
||||
name: "Survey Templates",
|
||||
description: "NPS, CSAT, Employee Surveys. Name your business objective, we have the questions.",
|
||||
icon: DocumentPlusIcon,
|
||||
comingSoon: true,
|
||||
},
|
||||
];
|
||||
|
||||
const FormHQPage = () => (
|
||||
<Layout
|
||||
title="FormHQ"
|
||||
description="Manage all form data in one place. Analyze right here or pipe your data where you need it.">
|
||||
<HeroTitle headingPt1="Form" headingTeal="HQ" />
|
||||
<HeroAnimation />
|
||||
<HeadingCentered
|
||||
teaser="You have arrived"
|
||||
heading="Everything you always wanted (from a form tool)"
|
||||
subheading="The days of scattered response data are counted. Manage all form data in one place. Analyze right here or pipe your data where you need it."
|
||||
/>
|
||||
<ul role="list" className="grid grid-cols-1 gap-6 pt-4 pb-16 sm:grid-cols-2 md:grid-cols-3">
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature.id}
|
||||
className={clsx(
|
||||
feature.comingSoon ? "dark:to-blue dark:from-blue-900" : "dark:from-black dark:to-blue-900",
|
||||
"relative col-span-1 mt-16 flex flex-col rounded-xl bg-gradient-to-b from-blue-200 to-gray-100 text-center drop-shadow-sm"
|
||||
)}>
|
||||
<div className="absolute -mt-12 w-full">
|
||||
<div
|
||||
className={clsx(
|
||||
feature.comingSoon
|
||||
? "dark:to-blue bg-gradient-to-br from-blue-200 to-gray-100 dark:from-blue-900 dark:via-blue-900"
|
||||
: "via-blue to-blue bg-gradient-to-br from-black ",
|
||||
"mx-auto flex h-20 w-20 items-center justify-center rounded-full shadow"
|
||||
)}>
|
||||
<feature.icon className="mx-auto h-10 w-10 flex-shrink-0 text-teal-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col p-10">
|
||||
<h3 className="text-blue my-4 text-lg font-medium dark:text-blue-100">{feature.name}</h3>
|
||||
<dl className="mt-1 flex flex-grow flex-col justify-between">
|
||||
<dt className="sr-only">Description</dt>
|
||||
<dd className="text-sm text-gray-600 dark:text-blue-400">{feature.description}</dd>
|
||||
{feature.comingSoon && (
|
||||
<dd className="mt-4">
|
||||
<span className="rounded-full bg-gray-400 px-3 py-1 text-xs font-medium text-blue-50">
|
||||
coming soon
|
||||
</span>
|
||||
</dd>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<TryItCTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default FormHQPage;
|
||||
@@ -0,0 +1,73 @@
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Button from "../components/shared/Button";
|
||||
import {
|
||||
CloudIcon,
|
||||
ArrowPathIcon,
|
||||
InboxArrowDownIcon,
|
||||
PuzzlePieceIcon,
|
||||
ServerStackIcon,
|
||||
ShieldCheckIcon,
|
||||
CommandLineIcon,
|
||||
BuildingLibraryIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
const GetStartedPage = () => (
|
||||
<Layout
|
||||
title="Get started"
|
||||
description="Choose between managed form hosting in the cloud or self-hosting our open source software">
|
||||
<HeroTitle headingPt1="How do you want to" headingTeal="run" headingPt2="Formbricks?" />
|
||||
<div className="mb-32 grid grid-cols-2 gap-8 px-16">
|
||||
<div className="rounded-lg bg-gradient-to-b from-blue-200 to-gray-50 px-10 py-6 dark:from-blue-300 dark:to-gray-100">
|
||||
<CloudIcon className="h-20 w-20 flex-shrink-0 text-blue-500" />
|
||||
<h2 className="text-blue mt-7 text-4xl font-bold">Cloud</h2>
|
||||
<p className="text-sm text-gray-500">Managed hosting by Formbricks core team</p>
|
||||
<p className="mt-7 font-semibold">
|
||||
<span className="font-bold text-black">Free</span> for 500 submissions/mo
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">then $0.01/submission</p>
|
||||
<div className="mt-7 flex items-center py-1">
|
||||
<InboxArrowDownIcon className="h- mr-3 w-7 flex-shrink-0 text-blue-500" />
|
||||
<p className="text-blue font-medium">Start receiving submissions right away</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<ArrowPathIcon className="mr-3 h-7 w-7 flex-shrink-0 text-blue-500" />
|
||||
<p className="text-blue font-medium">Automatic upgrades</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<PuzzlePieceIcon className="mr-3 h-7 w-7 flex-shrink-0 text-blue-500" />
|
||||
<p className="text-blue font-medium">All enterprise features included</p>
|
||||
</div>
|
||||
<Button className="mt-7 w-full text-center font-bold" variant="highlight">
|
||||
Start FREE on Formbricks Cloud
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-lg bg-gradient-to-b from-blue-200 to-gray-50 px-10 py-6 dark:from-blue-300 dark:to-gray-100">
|
||||
<ServerStackIcon className="h-20 w-20 flex-shrink-0 text-blue-500" />
|
||||
<h2 className="text-blue mt-7 text-4xl font-bold">Self-host</h2>
|
||||
<p className="text-sm text-gray-500">Submission data never leaves your infrastructure</p>
|
||||
<p className="mt-7 font-semibold">
|
||||
<span className="font-bold text-black">Free</span> for 500 submissions/mo
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">then $0.01/submission</p>
|
||||
<div className="mt-7 flex items-center py-1">
|
||||
<ShieldCheckIcon className="h- mr-3 w-7 flex-shrink-0 text-blue-500" />
|
||||
<p className="text-blue font-medium">Easy deploy for most private cloud platforms</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<CommandLineIcon className="mr-3 h-7 w-7 flex-shrink-0 text-blue-500" />
|
||||
<p className="text-blue font-medium">Full access to production instance</p>
|
||||
</div>
|
||||
<div className="flex items-center py-1">
|
||||
<BuildingLibraryIcon className="mr-3 h-7 w-7 flex-shrink-0 text-blue-500" />
|
||||
<p className="text-blue font-medium">Full compliance with all data privacy regulation</p>
|
||||
</div>
|
||||
<Button className="mt-7 w-full text-center font-bold" variant="highlight">
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default GetStartedPage;
|
||||
@@ -0,0 +1,20 @@
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import Hero from "@/components/home/Hero";
|
||||
import Features from "@/components/home/Features";
|
||||
import Highlights from "@/components/home/Highlights";
|
||||
import WhyFormbricks from "../components/shared/WhyFormbricks";
|
||||
import CTA from "@/components/shared/CTA";
|
||||
|
||||
const IndexPage = () => (
|
||||
<Layout
|
||||
title="The Open Source Forms & Survey Toolbox"
|
||||
description="We're building all essential form functionality so you don't have to. Modular, customizable, extendable. And open source.">
|
||||
<Hero />
|
||||
<Features />
|
||||
<Highlights />
|
||||
<WhyFormbricks />
|
||||
<CTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default IndexPage;
|
||||
@@ -0,0 +1,141 @@
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Image from "next/image";
|
||||
import ImageReactLib from "@/images/react-lib.png";
|
||||
import ImageSchemaGeneration from "@/images/schema-generation-svg.svg";
|
||||
import HeadingCentered from "@/components/shared/HeadingCenetered";
|
||||
import { CheckIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
import TryItCTA from "../components/shared/TryItCTA";
|
||||
import FeatureHighlight from "@/components/shared/FeatureHighlight";
|
||||
|
||||
const hereFeatures = [
|
||||
{
|
||||
name: "Unlimited forms",
|
||||
},
|
||||
{
|
||||
name: "Unlimited submissions",
|
||||
},
|
||||
{
|
||||
name: "Multiple choice questions",
|
||||
},
|
||||
{
|
||||
name: "Free text questions",
|
||||
},
|
||||
{
|
||||
name: "Custom “ThankYou” Page",
|
||||
},
|
||||
{
|
||||
name: "Webhooks",
|
||||
},
|
||||
{
|
||||
name: "Email Notifications",
|
||||
},
|
||||
];
|
||||
|
||||
const nextFeatures = [
|
||||
{
|
||||
name: "20+ question types",
|
||||
},
|
||||
{
|
||||
name: "Integrations",
|
||||
},
|
||||
{
|
||||
name: "Granular data piping",
|
||||
},
|
||||
{
|
||||
name: "In-depth analytics",
|
||||
},
|
||||
];
|
||||
|
||||
const soonFeatures = [
|
||||
{
|
||||
name: "Email notifications",
|
||||
},
|
||||
{
|
||||
name: "Form logic",
|
||||
},
|
||||
{
|
||||
name: "Hidden fields ",
|
||||
},
|
||||
{
|
||||
name: "A/B Test of wording",
|
||||
},
|
||||
{
|
||||
name: "Vue.js Library",
|
||||
},
|
||||
];
|
||||
|
||||
const ReactFormBuilderPage = () => (
|
||||
<Layout
|
||||
title="React Form Builder Library by Formbricks – Open source Form Infrastructure"
|
||||
description="Loads of question types, validation, multi-page forms, logic jumps, i18n, custom styles - all the good stuff you want, but don't want to build yourself.">
|
||||
<HeroTitle headingPt1="React" headingTeal="Form Builder" headingPt2="Library" />
|
||||
<FeatureHighlight
|
||||
featureTitle="Building React forms has never been quicker. But there is more..."
|
||||
text={`Loads of question types, validation, multi-page forms, logic jumps, i18n, custom styles - all the good stuff you want, but don't want to build yourself.\nBuilding forms fast is great, but where do you pipe your data? And what is it worth without a schema?"`}
|
||||
img={<Image src={ImageReactLib} alt="react library" className="rounded-lg" />}
|
||||
isImgLeft
|
||||
/>
|
||||
<FeatureHighlight
|
||||
featureTitle="Automatic schema generation for reliable insights"
|
||||
text="You can only reliably analyze your submissions when the form schema is sent along with the form.
|
||||
|
||||
Use our React Forms Library with the Formbricks Data Pipes and get a full image of the data sent. Analyze it in our dashboard or forward it to your database."
|
||||
img={<Image src={ImageSchemaGeneration} alt="react library" className="rounded-lg" />}
|
||||
/>
|
||||
<HeadingCentered
|
||||
teaser="all you need in one package"
|
||||
heading="Tons of powerful features (in the pipeline)"
|
||||
subheading="20+ question types, easy multi-page forms and validation are on the roadmap. Check what’s already here:"
|
||||
/>
|
||||
<div className="mx-auto mt-8 mb-28 md:inline-flex md:gap-x-5 lg:gap-x-20">
|
||||
<dl>
|
||||
{hereFeatures.map((feature) => (
|
||||
<div key={feature.name}>
|
||||
<dt className="flex items-center">
|
||||
<CheckIcon className="text-teal absolute ml-4 h-6 w-6 md:ml-0" aria-hidden="true" />
|
||||
<p className="ml-14 text-lg leading-loose text-gray-500 dark:text-gray-200 md:ml-9">
|
||||
{feature.name}
|
||||
</p>
|
||||
</dt>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
<dl>
|
||||
{nextFeatures.map((feature) => (
|
||||
<div key={feature.name}>
|
||||
<dt className="mx-auto flex max-w-sm items-center">
|
||||
<div className="bg-teal ml-2 rounded-full px-1.5 text-xs font-semibold text-black">
|
||||
<p>next</p>
|
||||
</div>
|
||||
<p className="ml-3 text-lg leading-loose text-gray-500 dark:text-gray-200">{feature.name}</p>
|
||||
</dt>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
<dl>
|
||||
{soonFeatures.map((feature) => (
|
||||
<div key={feature.name}>
|
||||
<dt className="mx-auto flex max-w-sm items-center">
|
||||
<div className="text-teal ml-2 rounded-full bg-gray-100 px-1.5 text-xs font-bold dark:bg-black dark:font-normal">
|
||||
<p>soon</p>
|
||||
</div>
|
||||
<p className="ml-3 text-lg leading-loose text-gray-500 dark:text-gray-200">{feature.name}</p>
|
||||
</dt>
|
||||
</div>
|
||||
))}
|
||||
<a href="mailto:johannes@formbricks.com">
|
||||
<div className="mx-auto flex max-w-sm items-center transition delay-100 duration-200 ease-in-out hover:scale-105">
|
||||
<PlusIcon className="text-teal ml-4 h-6 w-6 md:ml-5" aria-hidden="true" />
|
||||
<p className="ml-5 text-lg leading-loose text-gray-500 underline underline-offset-4 dark:text-gray-200">
|
||||
Add feature to roadmap
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</dl>
|
||||
</div>
|
||||
<TryItCTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default ReactFormBuilderPage;
|
||||
@@ -0,0 +1,26 @@
|
||||
import HeroTitle from "@/components/shared/HeroTitle";
|
||||
import Layout from "@/components/shared/Layout";
|
||||
import ImageWebhooks from "@/images/webhook-png.png";
|
||||
import Image from "next/image";
|
||||
import CTA from "../components/shared/CTA";
|
||||
import FeatureHighlight from "@/components/shared/FeatureHighlight";
|
||||
|
||||
const WebhooksPage = () => (
|
||||
<Layout
|
||||
title="Webhooks"
|
||||
description="Don't be limited by our choice of integrations, pipe your data exactly where you need it">
|
||||
<HeroTitle headingPt1="Webhooks" />
|
||||
<FeatureHighlight
|
||||
featureTitle="Versatile data handling"
|
||||
text="Don't be limited by our choice of integrations, pipe your data exactly where you need it. Set up webhooks in our Form HQ with just a few clicks.
|
||||
Don't miss any piece of information by sending partial submissions alongside complete ones."
|
||||
img={<Image src={ImageWebhooks} alt="react library" className="rounded-lg" />}
|
||||
isImgLeft
|
||||
cta="Read docs"
|
||||
href="/docs"
|
||||
/>
|
||||
<CTA />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default WebhooksPage;
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="\apps\formbricks-com\images\faveicon/mstile-150x150.png"/>
|
||||
<TileColor>#002941</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,32 @@
|
||||
<svg width="301" height="301" viewBox="0 0 301 301" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2472_5827)">
|
||||
<rect width="300.216" height="300.216" rx="42.1355" fill="url(#paint0_linear_2472_5827)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.7994 16.5089C87.7994 13.707 87.7994 12.306 88.3533 11.2397C88.8201 10.3411 89.5528 9.60836 90.4514 9.14157C91.5178 8.58765 92.9187 8.58765 95.7206 8.58765H130.505C133.307 8.58765 134.708 8.58765 135.774 9.14157C136.673 9.60836 137.405 10.3411 137.872 11.2397C138.426 12.306 138.426 13.707 138.426 16.5089V26.73C138.426 27.1532 138.426 27.5445 138.424 27.9074H167.038C167.037 27.5445 167.037 27.1532 167.037 26.73V16.5089C167.037 13.707 167.037 12.306 167.59 11.2397C168.057 10.3411 168.79 9.60836 169.689 9.14157C170.755 8.58765 172.156 8.58765 174.958 8.58765H209.742C212.544 8.58765 213.945 8.58765 215.011 9.14157C215.91 9.60836 216.643 10.3411 217.109 11.2397C217.663 12.306 217.663 13.707 217.663 16.5089V26.73C217.663 27.1532 217.663 27.5445 217.661 27.9074H239.319C250.188 27.9074 258.999 36.7185 258.999 47.5875V95.6754C258.999 106.544 250.188 115.355 239.319 115.355H63.4545C52.5855 115.355 43.7744 106.544 43.7744 95.6754V47.5875C43.7744 36.7185 52.5855 27.9074 63.4545 27.9074H87.8013C87.7994 27.5445 87.7994 27.1532 87.7994 26.73V16.5089Z" fill="#00E5CA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.7994 16.5089C87.7994 13.707 87.7994 12.306 88.3533 11.2397C88.8201 10.3411 89.5528 9.60836 90.4514 9.14157C91.5178 8.58765 92.9187 8.58765 95.7206 8.58765H130.505C133.307 8.58765 134.708 8.58765 135.774 9.14157C136.673 9.60836 137.405 10.3411 137.872 11.2397C138.426 12.306 138.426 13.707 138.426 16.5089V26.73C138.426 27.1532 138.426 27.5445 138.424 27.9074H167.038C167.037 27.5445 167.037 27.1532 167.037 26.73V16.5089C167.037 13.707 167.037 12.306 167.59 11.2397C168.057 10.3411 168.79 9.60836 169.689 9.14157C170.755 8.58765 172.156 8.58765 174.958 8.58765H209.742C212.544 8.58765 213.945 8.58765 215.011 9.14157C215.91 9.60836 216.643 10.3411 217.109 11.2397C217.663 12.306 217.663 13.707 217.663 16.5089V26.73C217.663 27.1532 217.663 27.5445 217.661 27.9074H239.319C250.188 27.9074 258.999 36.7185 258.999 47.5875V95.6754C258.999 106.544 250.188 115.355 239.319 115.355H63.4545C52.5855 115.355 43.7744 106.544 43.7744 95.6754V47.5875C43.7744 36.7185 52.5855 27.9074 63.4545 27.9074H87.8013C87.7994 27.5445 87.7994 27.1532 87.7994 26.73V16.5089Z" fill="url(#paint1_linear_2472_5827)" fill-opacity="0.05"/>
|
||||
<path d="M43.7744 135.036C43.7744 124.167 52.5855 115.356 63.4545 115.356H239.319C250.188 115.356 258.999 124.167 258.999 135.036V185.553C258.999 196.422 250.188 205.233 239.319 205.233H63.4545C52.5855 205.233 43.7744 196.422 43.7744 185.553V135.036Z" fill="#002941"/>
|
||||
<path d="M43.7744 135.036C43.7744 124.167 52.5855 115.356 63.4545 115.356H239.319C250.188 115.356 258.999 124.167 258.999 135.036V185.553C258.999 196.422 250.188 205.233 239.319 205.233H63.4545C52.5855 205.233 43.7744 196.422 43.7744 185.553V135.036Z" fill="url(#paint2_linear_2472_5827)" fill-opacity="0.05"/>
|
||||
<path d="M43.7744 224.913C43.7744 214.044 52.5855 205.233 63.4545 205.233H239.319C250.188 205.233 258.999 214.044 258.999 224.913V273.001C258.999 283.87 250.188 292.681 239.319 292.681H63.4545C52.5855 292.681 43.7744 283.87 43.7744 273.001V224.913Z" fill="#000A1C"/>
|
||||
<path d="M43.7744 224.913C43.7744 214.044 52.5855 205.233 63.4545 205.233H239.319C250.188 205.233 258.999 214.044 258.999 224.913V273.001C258.999 283.87 250.188 292.681 239.319 292.681H63.4545C52.5855 292.681 43.7744 283.87 43.7744 273.001V224.913Z" fill="url(#paint3_linear_2472_5827)" fill-opacity="0.05"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2472_5827" x1="150.108" y1="300.216" x2="150.108" y2="-6.37745e-06" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.648958" stop-color="#E5EAEF"/>
|
||||
<stop offset="1" stop-color="#D3DDE7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2472_5827" x1="151.387" y1="13.3326" x2="151.387" y2="115.356" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2472_5827" x1="151.387" y1="115.356" x2="151.387" y2="205.233" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2472_5827" x1="151.387" y1="205.233" x2="151.387" y2="292.681" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2472_5827">
|
||||
<rect width="300.216" height="300.216" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |