Compare commits

...

30 Commits

Author SHA1 Message Date
review-agent-prime[bot]
bdcd1337b9 Edit packages/lib/i18n/utils.ts 2024-03-20 07:23:34 +00:00
Dhruwang
74a21f03f2 tweaks 2024-03-20 12:48:31 +05:30
Dhruwang
c148e3bd74 added data-migration2 2024-03-20 11:48:39 +05:30
Dhruwang
f27f55a655 fix: validation and tweaks 2024-03-19 18:59:12 +05:30
Dhruwang
d7237b81da fix: translate survey function 2024-03-19 18:00:51 +05:30
Shubham Palriwala
4abe080db2 fix: /api/js endpoint to work on vercel (#2242) 2024-03-19 09:46:09 +00:00
Matti Nannt
6c84850a7e fix: unable to save legacy surveys (#2280) 2024-03-19 09:06:18 +00:00
Matti Nannt
63708ec92b fix: Increase multilanguage data migration timeout (#2278) 2024-03-18 19:32:01 +00:00
Dhruwang Jariwala
8b5328aa74 feat: Multi language Surveys (#1630)
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2024-03-18 19:02:18 +00:00
Shubham Palriwala
5c9e59b136 feat: expiresAt should only update on sync in js package (#2253)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2024-03-18 12:17:00 +00:00
Shubham Palriwala
7123a620c2 fix: add option to disable rate limitng in kamal (#2274)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2024-03-18 10:43:28 +01:00
Shubham Palriwala
6609b57084 fix: add nextauth url to kamal (#2272) 2024-03-18 09:53:55 +01:00
Matti Nannt
9123e3c866 chore: prepare kamal for production (#2263) 2024-03-16 08:02:06 +00:00
Jonas Höbenreich
92d0c6bce6 chore: small improvements (#2254)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2024-03-15 16:40:12 +00:00
Shubham Palriwala
ae3f1885c2 feat: add onboarding responses to user profiling in posthog (#2261) 2024-03-15 16:16:47 +00:00
Shubham Palriwala
3ca6ec8b56 chore: remove passing of build time env vars (#2236) 2024-03-15 16:15:18 +00:00
Jonas Höbenreich
83a46d7313 fix: fix on-the-fly url trigger always triggering IMPORTANT (#2256)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2024-03-15 15:24:23 +00:00
Piyush Gupta
b55b37b874 fix: fixes e2e tests (#2259) 2024-03-15 14:44:52 +00:00
Johannes
c76bcecca0 fix: remove duplicate text (#2257) 2024-03-15 12:53:40 +00:00
Johannes
3776397468 fix: lp cases (#2252) 2024-03-15 10:08:51 +00:00
Piyush Gupta
4704c4a077 fix: command dropdown breaking (#2249) 2024-03-15 10:07:38 +00:00
Shubham Palriwala
034ca1d639 fix: animated backgrounds in ios to not have video controls (#2251) 2024-03-15 09:50:24 +00:00
Johannes
e5862a2064 chore: new sub LPs, updated LP (#2207)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2024-03-15 09:16:48 +00:00
Matti Nannt
0332a2efe3 chore: add cache headers to og endpoint (#2240) 2024-03-14 14:24:23 +00:00
Kanishk Rawat
be8e461f55 chore: update Requestly url in OSS Friends (#2239) 2024-03-14 14:06:56 +00:00
Piyush Gupta
722ee68b4c feat: Paginated Surveys Management API (#2198) 2024-03-14 13:59:58 +00:00
Shubham Palriwala
e4078a3307 feat: opentelemetry integration (#2235)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2024-03-14 13:59:49 +00:00
Matti Nannt
907a9dc563 chore: rename github actions for simplification (#2238) 2024-03-14 13:46:41 +00:00
Piyush Gupta
f6df94081d feat: Add Server-side pagination to Surveys List (#2197)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2024-03-14 13:45:31 +00:00
talboren
2436192995 chore: add Keep to OSS friends (#2229)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
2024-03-14 13:32:15 +00:00
330 changed files with 12500 additions and 3121 deletions

View File

@@ -165,3 +165,6 @@ ENTERPRISE_LICENSE_KEY=
# Ignore Rate Limiting across the Formbricks app
# RATE_LIMITING_DISABLED=1
# OpenTelemetry URL for tracing
# OPENTELEMETRY_LISTENER_URL=http://localhost:4318/v1/traces

View File

@@ -24,11 +24,6 @@ jobs:
id-token: write # Only necessary for sigstore/fulcio outside PRs
steps:
- name: Generate Secrets
run: |
echo "NEXTAUTH_SECRET=$(openssl rand -hex 32)" >> $GITHUB_ENV
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v3
@@ -78,9 +73,6 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }}
DATABASE_URL=${{ env.DATABASE_URL }}
ENCRYPTION_KEY=${{ env.ENCRYPTION_KEY }}
NEXT_PUBLIC_SENTRY_DSN=${{ env.NEXT_PUBLIC_SENTRY_DSN }}
- name: Sign the images with GitHub OIDC Token

View File

@@ -16,6 +16,7 @@ jobs:
DOCKER_BUILDKIT: 1
IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }}
WEBAPP_URL: ${{ vars.WEBAPP_URL }}
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
@@ -28,8 +29,8 @@ jobs:
PRIVACY_URL: ${{ vars.PRIVACY_URL }}
TERMS_URL: ${{ vars.TERMS_URL }}
IMPRINT_URL: ${{ vars.IMPRINT_URL }}
GITHUB_ID: ${{ secrets.GITHUB_ID }}
GITHUB_SECRET: ${{ secrets.GITHUB_SECRET }}
GITHUB_ID: ${{ secrets.FB_GITHUB_ID }}
GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }}
@@ -68,6 +69,8 @@ jobs:
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
S3_REGION: ${{ vars.S3_REGION }}
S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }}
OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }}
RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }}
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
steps:

View File

@@ -30,7 +30,7 @@ jobs:
- "!(**.md|.github/CODEOWNERS)"
test:
name: Run Tests
name: Run Unit Tests
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/test.yml
@@ -58,6 +58,7 @@ jobs:
secrets: inherit
required:
name: PR Check Summary
needs: [lint, test, build, e2e-test]
if: always()
runs-on: ubuntu-latest

View File

@@ -31,16 +31,6 @@ jobs:
id-token: write
steps:
- name: Generate Random NEXTAUTH_SECRET
run: |
SECRET=$(openssl rand -hex 32)
echo "NEXTAUTH_SECRET=$SECRET" >> $GITHUB_ENV
- name: Generate Random ENCRYPTION_KEY
run: |
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v3
@@ -89,10 +79,6 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }}
DATABASE_URL=${{ env.DATABASE_URL }}
ENCRYPTION_KEY=${{ env.ENCRYPTION_KEY }}
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker

View File

@@ -14,16 +14,6 @@ jobs:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
steps:
- name: Generate Random NEXTAUTH_SECRET
run: |
SECRET=$(openssl rand -hex 32)
echo "NEXTAUTH_SECRET=$SECRET" >> $GITHUB_ENV
- name: Generate Random ENCRYPTION_KEY
run: |
SECRET=$(openssl rand -hex 32)
echo "ENCRYPTION_KEY=$SECRET" >> $GITHUB_ENV
- name: Checkout Repo
uses: actions/checkout@v2
@@ -52,7 +42,3 @@ jobs:
tags: |
${{ secrets.DOCKER_USERNAME }}/formbricks:${{ env.RELEASE_TAG }}
${{ secrets.DOCKER_USERNAME }}/formbricks:latest
build-args: |
NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }}
DATABASE_URL=${{ env.DATABASE_URL }}
ENCRYPTION_KEY=${{ env.ENCRYPTION_KEY }}

View File

@@ -3,7 +3,7 @@ on:
workflow_call:
jobs:
build:
name: Tests
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 15

View File

@@ -21,9 +21,25 @@ export default function AppPage({}) {
}, [darkMode]);
useEffect(() => {
// enable Formbricks debug mode by adding formbricksDebug=true GET parameter
const addFormbricksDebugParam = () => {
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has("formbricksDebug")) {
urlParams.set("formbricksDebug", "true");
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
window.history.replaceState({}, "", newUrl);
}
};
addFormbricksDebugParam();
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
const isUserId = window.location.href.includes("userId=true");
const attributes = isUserId ? { "Init Attribute 1": "eight", "Init Attribute 2": "two" } : undefined;
const defaultAttributes = {
language: "gu",
};
const userInitAttributes = { "Init Attribute 1": "eight", "Init Attribute 2": "two" };
const attributes = isUserId ? { ...defaultAttributes, ...userInitAttributes } : defaultAttributes;
const userId = isUserId ? "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING" : undefined;
formbricks.init({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
@@ -69,7 +85,7 @@ export default function AppPage({}) {
<div className="rounded-lg border border-slate-300 bg-slate-100 p-6 dark:border-slate-600 dark:bg-slate-900">
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">1. Setup .env</h3>
<p className="text-slate-700 dark:text-slate-300">
Copy the environment ID of your Formbricks app to the env variable in demo/.env
Copy the environment ID of your Formbricks app to the env variable in /apps/demo/.env
</p>
<Image src={fbsetup} alt="fb setup" className="mt-4 rounded" priority />

View File

@@ -1,13 +1,14 @@
import { Fence } from "@/components/shared/Fence";
import {generateManagementApiMetadata} from "@/lib/utils"
import { generateManagementApiMetadata } from "@/lib/utils";
export const metadata = generateManagementApiMetadata("Surveys",["Fetch","Create","Update","Delete"])
export const metadata = generateManagementApiMetadata("Surveys", ["Fetch", "Create", "Update", "Delete"]);
#### Management API
# Surveys API
This set of API can be used to
- [List All Surveys](#list-all-surveys)
- [Get Survey](#get-survey-by-id)
- [Create Survey](#create-survey)
@@ -22,8 +23,7 @@ This set of API can be used to
<Row>
<Col>
Retrieve all the surveys you have for the environment.
Retrieve all the surveys you have for the environment with pagination.
### Mandatory Headers
@@ -33,14 +33,26 @@ This set of API can be used to
</Property>
</Properties>
### Query Parameters
<Properties>
<Property name="offset" type="number">
The number of surveys to skip before returning the results.
</Property>
<Property name="limit" type="number">
The number of surveys to return.
</Property>
</Properties>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/api/v1/management/surveys">
```bash {{ title: 'cURL' }}
curl --location \
'https://app.formbricks.com/api/v1/management/surveys' \
'https://app.formbricks.com/api/v1/management/surveys?offset=20&limit=10' \
--header \
'x-api-key: <your-api-key>'
```
@@ -403,7 +415,6 @@ This set of API can be used to
```
</CodeGroup>
</Col>
<Col sticky>
@@ -453,7 +464,7 @@ This set of API can be used to
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
@@ -497,7 +508,6 @@ This set of API can be used to
```
</CodeGroup>
</Col>
<Col sticky>
@@ -568,7 +578,7 @@ This set of API can be used to
}
}
```
```json {{ title: '401 Not Authenticated' }}
{
"code": "not_authenticated",
@@ -585,7 +595,6 @@ This set of API can be used to
---
## Delete Survey by ID {{ tag: 'DELETE', label: '/api/v1/management/surveys/<survey-id>' }}
<Row>

View File

@@ -130,7 +130,7 @@ The app initializes 'formbricks' when it's loaded in a browser environment (due
<Image
src={ReactApp}
alt="In app survey in React app for micro surveys"
alt="In-app survey in React app for micro surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -377,10 +377,9 @@ Enabling Formbricks debug mode in your browser is a useful troubleshooting step
To activate Formbricks debug mode:
1. **In Your Integration Code:**
1. **Via URL Parameter:**
- Locate the initialization code for Formbricks in your application (HTML, ReactJS, NextJS, VueJS).
- Set the `debug` option to `true` when initializing Formbricks.
- Enable debug mode mode by adding `?formbricksDebug=true` to your application's URL (e.g. `https://example.com?formbricksDebug=true` or `https://example.com?page=123&formbricksDebug=true`). This parameter will enable debugging for the current page.
2. **View Debug Logs:**
@@ -394,29 +393,21 @@ To activate Formbricks debug mode:
- **Safari:** Press `Option + Command + C` to open the developer tools and navigate to the "Console" tab.
- **Edge:** Press `F12` or right-click, select "Inspect Element," and go to the "Console" tab.
3. **Via URL Parameter:**
- For quick activation, add `?formbricksDebug=true` to your application's URL.
This parameter will enable debugging for the current session.
### Common Use Cases
Debug mode is beneficial for scenarios such as:
- Verifying Formbricks functionality.
- Identifying integration issues.
- Verifying Formbricks initialization.
- Identifying survey trigger issues.
- Troubleshooting unexpected behavior.
### Debug Log Messages
Specific debug log messages may provide insights into:
Debug log messages provide insights into:
- API calls and responses.
- Event tracking and form interactions.
- Integration errors.
**Note:** Disable debugging in production to prevent unnecessary logs and improve performance.
- Event tracking, survey triggers and form interactions.
- Initialization errors.
## Overwrite CSS Styles for In-App Surveys

View File

@@ -1,5 +1,6 @@
import Image from "next/image";
import ReactApp from "../framework-guides/react-in-app-survey-app-popup-form.webp";
import I1 from "./1-in-app-survey-or-popup-survey-setup.webp";
import I2 from "./2-settings-for-survey-popup-in-app-for-feedback.webp";
import I3 from "./3-web-app-survey-settings-for-in-app-survey-popup.webp";
@@ -8,7 +9,6 @@ import I5 from "./5-options-survey-popup-in-app-for-feedback.webp";
import I6 from "./6-setup-in-app-survey-popup-feedback-box.webp";
import I7 from "./7-in-app-survey-popup-for-feedback.webp";
import I8 from "./8-pop-up-form-in-web-app-survey.webp";
import ReactApp from "../framework-guides/react-in-app-survey-app-popup-form.webp";
export const metadata = {
title: "Formbricks Quickstart Guide: In-App Surveys Made Simple",
@@ -20,7 +20,7 @@ export const metadata = {
# Quickstart
In app surveys have 6-10x better conversion rates than emailed out surveys. This tutorial explains how to run an in app survey in your web app in 10 to 15 minutes. Lets go!
In-app surveys have 6-10x better conversion rates than emailed out surveys. This tutorial explains how to run an in-app survey in your web app in 10 to 15 minutes. Lets go!
## Create a free Formbricks Cloud account
@@ -28,7 +28,7 @@ While you can [self-host](/docs/self-hosting/deployment) Formbricks, the quickes
<Image
src={I1}
alt="Choose in app survey template"
alt="Choose in-app survey template"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl "
/>
@@ -59,7 +59,7 @@ Scroll down to Survey Trigger and choose “New Session”. This will cause this
<Image
src={I4}
alt="In app survey trigger for feedback popup micro survey"
alt="In-app survey trigger for feedback popup micro survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -68,7 +68,7 @@ In **Recontact Options** we choose the following settings, so that we can play a
<Image
src={I5}
alt="Options for survey popup in app micro survey"
alt="Options for survey popup in-app micro survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -88,7 +88,7 @@ On the Setup Checklist you have two elements. At the top you find the Widget Sta
<Image
src={I7}
alt="feedback popup in app survey"
alt="feedback popup in-app survey"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -100,7 +100,7 @@ In the manual below, this code snippet contains all the information you need:
<Image
src={I8}
alt="settings for in app survey popping up"
alt="settings for in-app survey popping up"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -122,7 +122,7 @@ Now, restart your app in your terminal to make sure the widget is loaded. Once i
<Image
src={ReactApp}
alt="In app survey in React app for micro surveys"
alt="In-app survey in React app for micro surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>

View File

@@ -22,7 +22,7 @@ Go back to [app.formbricks.com](http://app.formbricks.com) or your self-hosted i
<Image
src={I1}
alt="setup checklist ui of survey popup for in app surveys"
alt="setup checklist ui of survey popup for in-app surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -39,7 +39,7 @@ If your app is connected with Formbricks Cloud, the survey might have not been l
<Image
src={I3}
alt="survey logs for in app survey pop up micro"
alt="survey logs for in-app survey pop up micro"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
@@ -50,7 +50,7 @@ The widget only loads surveys which are **public** and **in progress**. Go to Fo
<Image
src={I2}
alt="ui of survey popup for in app micro surveys"
alt="ui of survey popup for in-app micro surveys"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>

View File

@@ -1,7 +1,6 @@
export const metadata = {
title: "Enterprise License to unlock advanced functionality",
description:
"Request a self-hosting licenses to unlock advanced enterprise functionality",
description: "Request a enterprise licenses to unlock advanced enterprise functionality",
};
#### Self-Hosting
@@ -14,13 +13,17 @@ Additional to the AGPL licensed Formbricks core, the Formbricks repository conta
**Please note:** Sooner than later we will introduce a enterprise license pricing. For a free beta key, fill out this form:
<div style={{ position: 'relative', height: '100vh', maxHeight: '100vh', overflow: 'auto', borderRadius:'12px' }}>
<div
style={{
position: "relative",
height: "100vh",
maxHeight: "100vh",
overflow: "auto",
borderRadius: "12px",
}}>
<iframe
src="https://app.formbricks.com/s/clrf4z8zg1u3912250j7shqb5"
style={{ position: 'absolute', left: 0, top: 0, width: '100%', height: '100%', border: 0 }}
>
</iframe>
style={{ position: "absolute", left: 0, top: 0, width: "100%", height: "100%", border: 0 }}></iframe>
</div>
**Cant figure it out?**: [Join our Discord!](https://formbricks.com/discord)

View File

@@ -183,6 +183,7 @@ These variables can be provided at the runtime i.e. in your docker-compose file.
| OIDC_CLIENT_SECRET | Secret for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | |
| OIDC_ISSUER | Issuer URL for Custom OpenID Connect Provider (should have `.well-known` configured at this) | optional (required if OIDC auth is enabled) | |
| OIDC_SIGNING_ALGORITHM | Signing Algorithm for Custom OpenID Connect Provider | optional | `RS256` |
| OPENTELEMETRY_LISTENER_URL | URL for OpenTelemetry listener inside Formbricks. | optional | | |
## Build-time Variables

View File

@@ -1,45 +0,0 @@
import { Popover } from "@headlessui/react";
import { usePlausible } from "next-plausible";
import Link from "next/link";
import { useRouter } from "next/router";
import { Button } from "@formbricks/ui/Button";
import { FooterLogo } from "../shared/Logo";
export default function HeaderLight() {
const plausible = usePlausible();
const router = useRouter();
return (
<Popover className="relative" as="header">
<div className="max-w-8xl mx-auto flex items-center justify-between py-6 sm:px-2 md:justify-start lg:px-8 xl:px-12">
<div className="flex w-0 flex-1 justify-start">
<Link href="/">
<span className="sr-only">Formbricks</span>
<FooterLogo className="ml-7 h-8 w-auto sm:h-10" />
</Link>
</div>
<div className="hidden flex-1 items-center justify-end md:flex">
<Button
variant="secondary"
onClick={() => {
router.push("https://cal.com/johannes/formbricks-demo");
plausible("Demo_CTA_TalkToUs");
}}>
Talk to us
</Button>
<Button
variant="highlight"
className="ml-2"
onClick={() => {
router.push("https://app.formbricks.com/auth/signup");
plausible("Demo_CTA_TryForFree");
}}>
Start for free
</Button>
</div>
</div>
</Popover>
);
}

View File

@@ -1,7 +1,6 @@
import { TSurveyCTAQuestion } from "@formbricks/types/surveys";
import Headline from "./Headline";
import HtmlBody from "./HtmlBody";
import { TSurveyCTAQuestion } from "./types";
interface CTAQuestionProps {
question: TSurveyCTAQuestion;

View File

@@ -2,10 +2,9 @@
import React, { useEffect, useState } from "react";
import { TTemplate } from "@formbricks/types/templates";
import PreviewSurvey from "./PreviewSurvey";
import { findTemplateByName } from "./templates";
import { TTemplate } from "./types";
interface DemoPreviewProps {
template: string;

View File

@@ -1,10 +1,9 @@
import { useEffect, useState } from "react";
import { TTemplate } from "@formbricks/types/templates";
import PreviewSurvey from "./PreviewSurvey";
import TemplateList from "./TemplateList";
import { templates } from "./templates";
import { TTemplate } from "./types";
export default function SurveyTemplatesPage({}) {
const [activeTemplate, setActiveTemplate] = useState<TTemplate | null>(null);

View File

@@ -1,10 +1,10 @@
import { useEffect, useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyMultipleChoiceMultiQuestion } from "@formbricks/types/surveys";
import Headline from "./Headline";
import Subheader from "./Subheader";
import { TSurveyMultipleChoiceMultiQuestion } from "./types";
interface MultipleChoiceMultiProps {
question: TSurveyMultipleChoiceMultiQuestion;

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyMultipleChoiceSingleQuestion } from "@formbricks/types/surveys";
import Headline from "./Headline";
import Subheader from "./Subheader";
import { TSurveyMultipleChoiceSingleQuestion } from "./types";
interface MultipleChoiceSingleProps {
question: TSurveyMultipleChoiceSingleQuestion;
@@ -20,6 +20,7 @@ export default function MultipleChoiceSingleQuestion({
brandColor,
}: MultipleChoiceSingleProps) {
const [selectedChoice, setSelectedChoice] = useState<string | null>(null);
return (
<form
onSubmit={(e) => {

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyNPSQuestion } from "@formbricks/types/surveys";
import Headline from "./Headline";
import Subheader from "./Subheader";
import { TSurveyNPSQuestion } from "./types";
interface NPSQuestionProps {
question: TSurveyNPSQuestion;

View File

@@ -1,9 +1,8 @@
import { useState } from "react";
import { TSurveyOpenTextQuestion } from "@formbricks/types/surveys";
import Headline from "./Headline";
import Subheader from "./Subheader";
import { TSurveyOpenTextQuestion } from "./types";
interface OpenTextQuestionProps {
question: TSurveyOpenTextQuestion;

View File

@@ -1,10 +1,9 @@
import { useState } from "react";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import Modal from "./Modal";
import QuestionConditional from "./QuestionConditional";
import ThankYouCard from "./ThankYouCard";
import { TSurvey, TSurveyQuestion } from "./types";
interface PreviewSurveyProps {
localSurvey?: TSurvey;
@@ -67,8 +66,8 @@ export default function PreviewSurvey({
{activeQuestionId == "thank-you-card" ? (
<ThankYouCard
brandColor={brandColor}
headline={localSurvey?.thankYouCard?.headline || ""}
subheader={localSurvey?.thankYouCard?.subheader || ""}
headline={localSurvey?.thankYouCard?.headline!}
subheader={localSurvey?.thankYouCard?.subheader!}
/>
) : (
questions.map(

View File

@@ -1,11 +1,10 @@
import { TSurveyQuestion, TSurveyQuestionType } from "@formbricks/types/surveys";
import CTAQuestion from "./CTAQuestion";
import MultipleChoiceMultiQuestion from "./MultipleChoiceMultiQuestion";
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
import NPSQuestion from "./NPSQuestion";
import OpenTextQuestion from "./OpenTextQuestion";
import RatingQuestion from "./RatingQuestion";
import { TSurveyQuestion, TSurveyQuestionType } from "./types";
interface QuestionConditionalProps {
question: TSurveyQuestion;

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyRatingQuestion } from "@formbricks/types/surveys";
import Headline from "./Headline";
import Subheader from "./Subheader";
import { TSurveyRatingQuestion } from "./types";
interface RatingQuestionProps {
question: TSurveyRatingQuestion;

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { TTemplate } from "@formbricks/types/templates";
import { templates } from "./templates";
import { TTemplate } from "./types";
type TemplateList = {
onTemplateClick: (template: TTemplate) => void;

View File

@@ -1,7 +1,6 @@
import { createId } from "@paralleldrive/cuid2";
import { TSurveyQuestionType } from "@formbricks/types/surveys";
import { TTemplate } from "@formbricks/types/templates";
import {
AppPieChartIcon,
ArrowRightCircleIcon,
@@ -27,14 +26,17 @@ import {
VideoTabletAdjustIcon,
} from "@formbricks/ui/icons";
import { TTemplate } from "./types";
const thankYouCardDefault = {
enabled: true,
headline: "Thank you!",
subheader: "We appreciate your feedback.",
subheader: "TWe appreciate your feedback.",
};
const welcomeCardDefault = {
enabled: true,
headline: "Welcome!",
timeToFinish: false,
showResponseCount: false,
};

View File

@@ -0,0 +1,501 @@
import z from "zod";
export enum TSurveyQuestionType {
FileUpload = "fileUpload",
OpenText = "openText",
MultipleChoiceSingle = "multipleChoiceSingle",
MultipleChoiceMulti = "multipleChoiceMulti",
NPS = "nps",
CTA = "cta",
Rating = "rating",
Consent = "consent",
PictureSelection = "pictureSelection",
Cal = "cal",
Date = "date",
}
export const ZAllowedFileExtension = z.enum([
"png",
"jpeg",
"jpg",
"pdf",
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"plain",
"csv",
"mp4",
"mov",
"avi",
"mkv",
"webm",
"zip",
"rar",
"7z",
"tar",
]);
export type TAllowedFileExtension = z.infer<typeof ZAllowedFileExtension>;
export const ZUserObjective = z.enum([
"increase_conversion",
"improve_user_retention",
"increase_user_adoption",
"sharpen_marketing_messaging",
"support_sales",
"other",
]);
export type TUserObjective = z.infer<typeof ZUserObjective>;
export const ZSurveyWelcomeCard = z.object({
enabled: z.boolean(),
headline: z.string().optional(),
html: z.string().optional(),
fileUrl: z.string().optional(),
buttonLabel: z.string().optional(),
timeToFinish: z.boolean().default(true),
showResponseCount: z.boolean().default(false),
});
export type TSurveyWelcomeCard = z.infer<typeof ZSurveyWelcomeCard>;
export const ZSurveyThankYouCard = z.object({
enabled: z.boolean(),
headline: z.optional(z.string()),
subheader: z.optional(z.string()),
buttonLabel: z.optional(z.string()),
buttonLink: z.optional(z.string()),
imageUrl: z.string().optional(),
});
export type TSurveyThankYouCard = z.infer<typeof ZSurveyThankYouCard>;
export const ZSurveyHiddenFields = z.object({
enabled: z.boolean(),
fieldIds: z.optional(z.array(z.string())),
});
export type TSurveyHiddenFields = z.infer<typeof ZSurveyHiddenFields>;
export const ZSurveyChoice = z.object({
id: z.string(),
label: z.string(),
});
export type TSurveyChoice = z.infer<typeof ZSurveyChoice>;
export const ZSurveyPictureChoice = z.object({
id: z.string(),
imageUrl: z.string(),
});
export type TSurveyPictureChoice = z.infer<typeof ZSurveyPictureChoice>;
export const ZSurveyLogicCondition = z.enum([
"accepted",
"clicked",
"submitted",
"skipped",
"equals",
"notEquals",
"lessThan",
"lessEqual",
"greaterThan",
"greaterEqual",
"includesAll",
"includesOne",
"uploaded",
"notUploaded",
"booked",
]);
export type TSurveyLogicCondition = z.infer<typeof ZSurveyLogicCondition>;
export const ZSurveyLogicBase = z.object({
condition: ZSurveyLogicCondition.optional(),
value: z.union([z.string(), z.array(z.string())]).optional(),
destination: z.union([z.string(), z.literal("end")]).optional(),
});
export const ZSurveyFileUploadLogic = ZSurveyLogicBase.extend({
condition: z.enum(["uploaded", "notUploaded"]).optional(),
value: z.undefined(),
});
export const ZSurveyOpenTextLogic = ZSurveyLogicBase.extend({
condition: z.enum(["submitted", "skipped"]).optional(),
value: z.undefined(),
});
export const ZSurveyConsentLogic = ZSurveyLogicBase.extend({
condition: z.enum(["skipped", "accepted"]).optional(),
value: z.undefined(),
});
export const ZSurveyMultipleChoiceSingleLogic = ZSurveyLogicBase.extend({
condition: z.enum(["submitted", "skipped", "equals", "notEquals"]).optional(),
value: z.string().optional(),
});
export const ZSurveyMultipleChoiceMultiLogic = ZSurveyLogicBase.extend({
condition: z.enum(["submitted", "skipped", "includesAll", "includesOne", "equals"]).optional(),
value: z.union([z.array(z.string()), z.string()]).optional(),
});
export const ZSurveyNPSLogic = ZSurveyLogicBase.extend({
condition: z
.enum([
"equals",
"notEquals",
"lessThan",
"lessEqual",
"greaterThan",
"greaterEqual",
"submitted",
"skipped",
])
.optional(),
value: z.union([z.string(), z.number()]).optional(),
});
const ZSurveyCTALogic = ZSurveyLogicBase.extend({
// "submitted" condition is legacy and should be removed later
condition: z.enum(["clicked", "submitted", "skipped"]).optional(),
value: z.undefined(),
});
const ZSurveyRatingLogic = ZSurveyLogicBase.extend({
condition: z
.enum([
"equals",
"notEquals",
"lessThan",
"lessEqual",
"greaterThan",
"greaterEqual",
"submitted",
"skipped",
])
.optional(),
value: z.union([z.string(), z.number()]).optional(),
});
const ZSurveyPictureSelectionLogic = ZSurveyLogicBase.extend({
condition: z.enum(["submitted", "skipped"]).optional(),
value: z.undefined(),
});
const ZSurveyCalLogic = ZSurveyLogicBase.extend({
condition: z.enum(["booked", "skipped"]).optional(),
value: z.undefined(),
});
export const ZSurveyLogic = z.union([
ZSurveyOpenTextLogic,
ZSurveyConsentLogic,
ZSurveyMultipleChoiceSingleLogic,
ZSurveyMultipleChoiceMultiLogic,
ZSurveyNPSLogic,
ZSurveyCTALogic,
ZSurveyRatingLogic,
ZSurveyPictureSelectionLogic,
ZSurveyFileUploadLogic,
ZSurveyCalLogic,
]);
export type TSurveyLogic = z.infer<typeof ZSurveyLogic>;
const ZSurveyQuestionBase = z.object({
id: z.string(),
type: z.string(),
headline: z.string(),
subheader: z.string().optional(),
imageUrl: z.string().optional(),
required: z.boolean(),
buttonLabel: z.string().optional(),
backButtonLabel: z.string().optional(),
scale: z.enum(["number", "smiley", "star"]).optional(),
range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]).optional(),
logic: z.array(ZSurveyLogic).optional(),
isDraft: z.boolean().optional(),
});
export const ZSurveyOpenTextQuestionInputType = z.enum(["text", "email", "url", "number", "phone"]);
export type TSurveyOpenTextQuestionInputType = z.infer<typeof ZSurveyOpenTextQuestionInputType>;
export const ZSurveyOpenTextQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.OpenText),
placeholder: z.string().optional(),
longAnswer: z.boolean().optional(),
logic: z.array(ZSurveyOpenTextLogic).optional(),
inputType: ZSurveyOpenTextQuestionInputType.optional().default("text"),
});
export type TSurveyOpenTextQuestion = z.infer<typeof ZSurveyOpenTextQuestion>;
export const ZSurveyConsentQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.Consent),
html: z.string().optional(),
label: z.string(),
dismissButtonLabel: z.string().optional(),
placeholder: z.string().optional(),
logic: z.array(ZSurveyConsentLogic).optional(),
});
export type TSurveyConsentQuestion = z.infer<typeof ZSurveyConsentQuestion>;
export const ZSurveyMultipleChoiceSingleQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.MultipleChoiceSingle),
choices: z.array(ZSurveyChoice),
logic: z.array(ZSurveyMultipleChoiceSingleLogic).optional(),
shuffleOption: z.enum(["none", "all", "exceptLast"]).optional(),
otherOptionPlaceholder: z.string().optional(),
});
export type TSurveyMultipleChoiceSingleQuestion = z.infer<typeof ZSurveyMultipleChoiceSingleQuestion>;
export const ZSurveyMultipleChoiceMultiQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.MultipleChoiceMulti),
choices: z.array(ZSurveyChoice),
logic: z.array(ZSurveyMultipleChoiceMultiLogic).optional(),
shuffleOption: z.enum(["none", "all", "exceptLast"]).optional(),
otherOptionPlaceholder: z.string().optional(),
});
export type TSurveyMultipleChoiceMultiQuestion = z.infer<typeof ZSurveyMultipleChoiceMultiQuestion>;
export const ZSurveyNPSQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.NPS),
lowerLabel: z.string(),
upperLabel: z.string(),
logic: z.array(ZSurveyNPSLogic).optional(),
});
export type TSurveyNPSQuestion = z.infer<typeof ZSurveyNPSQuestion>;
export const ZSurveyCTAQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.CTA),
html: z.string().optional(),
buttonUrl: z.string().optional(),
buttonExternal: z.boolean(),
dismissButtonLabel: z.string().optional(),
logic: z.array(ZSurveyCTALogic).optional(),
});
export type TSurveyCTAQuestion = z.infer<typeof ZSurveyCTAQuestion>;
// export const ZSurveyWelcomeQuestion = ZSurveyQuestionBase.extend({
// type: z.literal(TSurveyQuestionType.Welcome),
// html: z.string().optional(),
// fileUrl: z.string().optional(),
// buttonUrl: z.string().optional(),
// timeToFinish: z.boolean().default(false),
// logic: z.array(ZSurveyCTALogic).optional(),
// });
// export type TSurveyWelcomeQuestion = z.infer<typeof ZSurveyWelcomeQuestion>;
export const ZSurveyRatingQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.Rating),
scale: z.enum(["number", "smiley", "star"]),
range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]),
lowerLabel: z.string(),
upperLabel: z.string(),
logic: z.array(ZSurveyRatingLogic).optional(),
});
export const ZSurveyDateQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.Date),
html: z.string().optional(),
format: z.enum(["M-d-y", "d-M-y", "y-M-d"]),
});
export type TSurveyDateQuestion = z.infer<typeof ZSurveyDateQuestion>;
export type TSurveyRatingQuestion = z.infer<typeof ZSurveyRatingQuestion>;
export const ZSurveyPictureSelectionQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.PictureSelection),
allowMulti: z.boolean().optional().default(false),
choices: z.array(ZSurveyPictureChoice),
logic: z.array(ZSurveyPictureSelectionLogic).optional(),
});
export type TSurveyPictureSelectionQuestion = z.infer<typeof ZSurveyPictureSelectionQuestion>;
export const ZSurveyFileUploadQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.FileUpload),
allowMultipleFiles: z.boolean(),
maxSizeInMB: z.number().optional(),
allowedFileExtensions: z.array(ZAllowedFileExtension).optional(),
logic: z.array(ZSurveyFileUploadLogic).optional(),
});
export type TSurveyFileUploadQuestion = z.infer<typeof ZSurveyFileUploadQuestion>;
export const ZSurveyCalQuestion = ZSurveyQuestionBase.extend({
type: z.literal(TSurveyQuestionType.Cal),
calUserName: z.string(),
logic: z.array(ZSurveyCalLogic).optional(),
});
export type TSurveyCalQuestion = z.infer<typeof ZSurveyCalQuestion>;
export const ZSurveyQuestion = z.union([
ZSurveyOpenTextQuestion,
ZSurveyConsentQuestion,
ZSurveyMultipleChoiceSingleQuestion,
ZSurveyMultipleChoiceMultiQuestion,
ZSurveyNPSQuestion,
ZSurveyCTAQuestion,
ZSurveyRatingQuestion,
ZSurveyPictureSelectionQuestion,
ZSurveyDateQuestion,
ZSurveyFileUploadQuestion,
ZSurveyCalQuestion,
]);
export type TSurveyQuestion = z.infer<typeof ZSurveyQuestion>;
export const ZSurveyQuestions = z.array(ZSurveyQuestion);
export type TSurveyQuestions = z.infer<typeof ZSurveyQuestions>;
export const ZSurveyClosedMessage = z
.object({
enabled: z.boolean().optional(),
heading: z.string().optional(),
subheading: z.string().optional(),
})
.nullable()
.optional();
export type TSurveyClosedMessage = z.infer<typeof ZSurveyClosedMessage>;
export const ZSurveyAttributeFilter = z.object({
attributeClassId: z.string().cuid2(),
condition: z.enum(["equals", "notEquals"]),
value: z.string(),
});
export type TSurveyAttributeFilter = z.infer<typeof ZSurveyAttributeFilter>;
export const ZSurveyType = z.enum(["web", "email", "link", "mobile"]);
export type TSurveyType = z.infer<typeof ZSurveyType>;
const ZSurveyStatus = z.enum(["draft", "inProgress", "paused", "completed"]);
export type TSurveyStatus = z.infer<typeof ZSurveyStatus>;
const ZSurveyDisplayOption = z.enum(["displayOnce", "displayMultiple", "respondMultiple"]);
export type TSurveyDisplayOption = z.infer<typeof ZSurveyDisplayOption>;
export const ZColor = z.string().regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/);
export const ZPlacement = z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]);
export type TPlacement = z.infer<typeof ZPlacement>;
export const ZSurveyProductOverwrites = z.object({
brandColor: ZColor.nullish(),
highlightBorderColor: ZColor.nullish(),
placement: ZPlacement.nullish(),
clickOutsideClose: z.boolean().nullish(),
darkOverlay: z.boolean().nullish(),
});
export type TSurveyProductOverwrites = z.infer<typeof ZSurveyProductOverwrites>;
export const ZSurveyStylingBackground = z.object({
bg: z.string().nullish(),
bgType: z.enum(["animation", "color", "image"]).nullish(),
brightness: z.number().nullish(),
});
export type TSurveyStylingBackground = z.infer<typeof ZSurveyStylingBackground>;
export const ZSurveyStyling = z.object({
background: ZSurveyStylingBackground.nullish(),
hideProgressBar: z.boolean().nullish(),
});
export type TSurveyStyling = z.infer<typeof ZSurveyStyling>;
export const ZSurveySingleUse = z
.object({
enabled: z.boolean(),
heading: z.optional(z.string()),
subheading: z.optional(z.string()),
isEncrypted: z.boolean(),
})
.nullable();
export type TSurveySingleUse = z.infer<typeof ZSurveySingleUse>;
export const ZSurveyVerifyEmail = z
.object({
name: z.optional(z.string()),
subheading: z.optional(z.string()),
})
.optional();
export type TSurveyVerifyEmail = z.infer<typeof ZSurveyVerifyEmail>;
export const ZSurvey = z.object({
id: z.string().cuid2(),
createdAt: z.date(),
updatedAt: z.date(),
name: z.string(),
type: ZSurveyType,
environmentId: z.string(),
createdBy: z.string().nullable(),
status: ZSurveyStatus,
attributeFilters: z.array(ZSurveyAttributeFilter),
displayOption: ZSurveyDisplayOption,
autoClose: z.number().nullable(),
triggers: z.array(z.string()),
redirectUrl: z.string().url().nullable(),
recontactDays: z.number().nullable(),
welcomeCard: ZSurveyWelcomeCard,
questions: ZSurveyQuestions,
thankYouCard: ZSurveyThankYouCard,
hiddenFields: ZSurveyHiddenFields,
delay: z.number(),
autoComplete: z.number().nullable(),
closeOnDate: z.date().nullable(),
productOverwrites: ZSurveyProductOverwrites.nullable(),
styling: ZSurveyStyling.nullable(),
surveyClosedMessage: ZSurveyClosedMessage.nullable(),
singleUse: ZSurveySingleUse.nullable(),
verifyEmail: ZSurveyVerifyEmail.nullable(),
pin: z.string().nullable().optional(),
resultShareKey: z.string().nullable(),
displayPercentage: z.number().min(1).max(100).nullable(),
});
export type TSurvey = z.infer<typeof ZSurvey>;
export const ZTemplate = z.object({
name: z.string(),
description: z.string(),
icon: z.any().optional(),
category: z
.enum(["Product Experience", "Exploration", "Growth", "Increase Revenue", "Customer Success"])
.optional(),
objectives: z.array(ZUserObjective).optional(),
preset: z.object({
name: z.string(),
welcomeCard: ZSurveyWelcomeCard,
questions: ZSurveyQuestions,
thankYouCard: ZSurveyThankYouCard,
hiddenFields: ZSurveyHiddenFields,
}),
});
export type TTemplate = z.infer<typeof ZTemplate>;

View File

@@ -1,87 +1,66 @@
import HeadingCentered from "@/components/shared/HeadingCentered";
import { FAQPageJsonLd } from "next-seo";
import SeoFaq from "@/components/shared/seo/SeoFaq";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui/Accordion";
const FAQ_DATA = [
const FAQs = [
{
question: "What is Formbricks?",
answer: () => (
<>
Formbricks is an open-source Experience Management tool that helps businesses understand what
customers think and feel about their products. It integrates natively into your platform to conduct
user research with a focus on data privacy and minimal development intervention.
</>
),
answer:
"Formbricks is an experience management platform built on top of the fastest growing open source survey infrastructure out there. It aims to assist businesses in capturing and understanding customer insights and emotions towards their products and services. Designed to integrate seamlessly with various platforms, Formbricks focuses on user research, emphasizing data privacy and requiring minimal development effort for integration.",
},
{
question: "How do I integrate Formbricks into my application?",
answer: () => (
<>
Integrating Formbricks is a breeze. Simply copy a script tag to your HTML head, or use NPM to install
Formbricks for platforms like React, Vue, Svelte, etc. Once installed, initialize Formbricks with your
environment details. Learn more with our framework guides{" "}
<a href="/docs/getting-started/framework-guides" className="text-brand-dark dark:text-brand-light">
here
</a>
.
</>
),
answer:
"Integrating Formbricks into an application is effortless. For web applications, it involves adding a simple script tag to the HTML head. For applications built with modern frameworks such as React, Vue, or Svelte, Formbricks can be installed via NPM. Initialization with specific environment details completes the setup. Detailed instructions and framework guides are readily available in the detailed Formbricks documentation.",
},
{
question: "Is Formbricks GDPR compliant?",
answer: () => (
<>
Yes, Formbricks is fully GDPR compliant. Whether you use our cloud solution or decide to self-host, we
ensure compliance with all data privacy regulations.
</>
),
answer:
"Indeed, Formbricks ensures full GDPR compliance, emphasizing the protection of user data privacy. It offers both cloud-based solutions and self-hosting options, adhering to data privacy regulations and making it a trusted choice for secure open source survey tool deployment.",
},
{
question: "Can I self-host Formbricks?",
answer: () => (
<>
Absolutely! We provide an option for users to host Formbricks on their own server, ensuring even more
control over data and compliance. And the best part? Self-hosting is available for free, always. For
documentation on self hosting, click{" "}
<a href="/docs/self-hosting/deployment" className="text-brand-dark dark:text-brand-light">
here
</a>
.
</>
),
answer:
"Certainly! Formbricks encourages self-hosting, providing users with greater control over their data and compliance. This option underscores Formbricks' commitment to offering versatile and free open source experience management software, ensuring users can adapt the platform to their unique requirements. Detailed self-hosting documentation is available for users seeking to leverage this capability.",
},
{
question: "How does Formbricks pricing work?",
answer: () => (
<>
Formbricks offers a Free forever plan on the cloud that includes unlimited surveys, in-product
surveys, and more. We also provide a self-hosting option which includes all free features and more,
available at no cost. If you require additional features or responses, check out our pricing section
above for more details.
</>
),
answer:
"Formbricks introduces a 'Free forever' plan, showcasing its commitment to making open source survey platforms universally accessible. This plan features unlimited surveys and in-product surveys, among other functionalities. Self-hosting users can enjoy all the benefits of the free plan with additional features at no extra cost. For those seeking advanced features Formbricks invites you to explore the pricing section for more information.",
},
{
question: "How does Formbricks make money?",
answer:
"Formbricks employs the 'Open Core' business model. The core of the Formbricks application is offered for free. Formbricks monetizes by providing advanced features and services, typically catering to the needs of larger clients, thereby generating revenue.",
},
{
question: "What is the best open source survey software available?",
answer:
"Identifying the best open source survey software requires evaluating features, flexibility, and support. Formbricks is a noteworthy contender, offering comprehensive experience management solutions. This platform excels in enabling businesses to delve into customer insights and feedback, offering versatility and ease of system integration.",
},
{
question: "Can open source survey platforms be customized for my business needs?",
answer:
"Definitely. Platforms like Formbricks exemplify the customizability of open source survey tools, allowing for extensive tailoring to meet specific business requirements. Access to the source code enables deep customization, from branding adjustments to complex integrations with existing systems, underscoring the flexibility of open source experience management solutions.",
},
{
question:
"What advantages does using an experience management platform offer over traditional survey tools?",
answer:
"Experience management platforms, especially those built on open source foundations, offer a more holistic view of customer interactions compared to traditional survey tools. They enable real-time collection, analysis, and application of customer feedback, ensuring a thorough understanding of the customer journey. This comprehensive insight facilitates informed decision-making and boosts customer satisfaction.",
},
];
const faqJsonLdData = FAQ_DATA.map((faq) => ({
questionName: faq.question,
acceptedAnswerText: faq.answer(),
}));
export default function FAQ() {
return (
<div className="max-w-7xl py-4 sm:px-6 sm:pb-6 lg:px-8" id="faq">
<FAQPageJsonLd mainEntity={faqJsonLdData} />
<HeadingCentered heading="Frequently Asked Questions" teaser="FAQ" closer />
<Accordion type="single" collapsible className="px-4 sm:px-0">
{FAQ_DATA.map((faq, index) => (
<AccordionItem key={`item-${index}`} value={`item-${index + 1}`}>
<AccordionTrigger>{faq.question}</AccordionTrigger>
<AccordionContent>{faq.answer()}</AccordionContent>
</AccordionItem>
))}
</Accordion>
<div>
<HeadingCentered heading="Frequently asked questions" teaser="FAQ" />
<SeoFaq
faqs={FAQs}
headline="Open Source Experience Management Platform"
description="Formbricks is an Experience Management Platform built of top of the largest open source survey infrastructure worldwide."
datePublished="2023-10-11"
dateModified="2024-03-12"
/>
</div>
);
}

View File

@@ -24,42 +24,39 @@ const features = [
];
export const Features: React.FC = () => {
return (
<div className="relative px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14 lg:pt-24">
<div className="relative mx-auto max-w-7xl">
<HeadingCentered
closer
teaser="Data Privacy at heart"
heading="The only open-source solution"
subheading="Comply with all data privacy regulation with ease. Self-host if you want."
/>
<div className="relative">
<HeadingCentered
teaser="Data Privacy at heart"
heading="The only open-source solution"
subheading="Comply with all data privacy regulation with ease. Self-host if you want."
/>
<ul role="list" className="grid grid-cols-1 gap-4 pt-8 sm:grid-cols-2 md:grid-cols-3 lg:gap-10">
{features.map((feature) => {
const IconComponent: React.ElementType = feature.icon;
<ul role="list" className="grid grid-cols-1 gap-4 pt-8 sm:grid-cols-2 md:grid-cols-3 lg:gap-10">
{features.map((feature) => {
const IconComponent: React.ElementType = feature.icon;
return (
<li
key={feature.id}
className="relative col-span-1 mt-16 flex flex-col rounded-xl bg-slate-100 text-center dark:bg-slate-700">
<div className="absolute -mt-12 w-full">
<div className="mx-auto flex h-20 w-20 items-center justify-center rounded-3xl bg-slate-200 shadow dark:bg-slate-800">
<IconComponent className="text-brand-dark dark:text-brand-light mx-auto h-10 w-10 flex-shrink-0" />
</div>
return (
<li
key={feature.id}
className="relative col-span-1 mt-16 flex flex-col rounded-xl bg-slate-100 text-center dark:bg-slate-700">
<div className="absolute -mt-12 w-full">
<div className="mx-auto flex h-20 w-20 items-center justify-center rounded-3xl bg-slate-200 shadow dark:bg-slate-800">
<IconComponent className="text-brand-dark dark:text-brand-light mx-auto h-10 w-10 flex-shrink-0" />
</div>
<div className="flex flex-1 flex-col p-10">
<h3 className="my-4 text-lg font-medium text-slate-800 dark:text-slate-200">
{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-slate-600 dark:text-slate-400">{feature.description}</dd>
</dl>
</div>
</li>
);
})}
</ul>
</div>
</div>
<div className="flex flex-1 flex-col p-10">
<h3 className="my-4 text-lg font-medium text-slate-800 dark:text-slate-200">
{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-slate-600 dark:text-slate-400">{feature.description}</dd>
</dl>
</div>
</li>
);
})}
</ul>
</div>
);
};

View File

@@ -1,13 +1,9 @@
import CalLogoDark from "@/images/clients/cal-logo-dark.svg";
import CalLogoLight from "@/images/clients/cal-logo-light.svg";
import CrowdLogoDark from "@/images/clients/crowd-logo-dark.svg";
import CrowdLogoLight from "@/images/clients/crowd-logo-light.svg";
import FlixbusLogo from "@/images/clients/flixbus-white.svg";
import NILogoDark from "@/images/clients/niLogoDark.svg";
import NILogoLight from "@/images/clients/niLogoWhite.svg";
import OptimoleLogo from "@/images/clients/optimole-logo.svg";
import ThemeisleLogo from "@/images/clients/themeisle-logo.webp";
import AnimationFallback from "@/public/animations/opensource-xm-platform-formbricks-fallback.png";
import { ShieldCheckIcon, StarIcon } from "lucide-react";
import { usePlausible } from "next-plausible";
import Image from "next/image";
@@ -15,14 +11,12 @@ import { useRouter } from "next/router";
import { Button } from "@formbricks/ui/Button";
import HeroAnimation from "./HeroAnimation";
export const Hero: React.FC = ({}) => {
const plausible = usePlausible();
const router = useRouter();
return (
<div className="relative">
<div className="px-4 pb-20 pt-16 text-center sm:px-6 lg:px-8 lg:pb-32 lg:pt-20">
<div className="text-center">
<div className="xs:text-sm flex items-center justify-center space-x-4 divide-x-2 text-xs text-slate-600">
<p>
<ShieldCheckIcon className="mb-1 inline h-4 w-4" /> Privacy-first
@@ -46,9 +40,8 @@ export const Hero: React.FC = ({}) => {
know what your customers need.
</span>
</p>
<div className="mx-auto mt-5 max-w-3xl items-center px-4 sm:flex sm:justify-center md:mt-6 md:space-x-8 md:px-0">
<div className="grid grid-cols-6 items-center gap-6 pt-2 md:gap-8">
<div className="mx-auto mt-5 max-w-xl items-center px-4 sm:flex sm:justify-center md:mt-6 md:space-x-8 md:px-0 lg:max-w-3xl">
<div className="grid grid-cols-2 items-center gap-8 pt-2 md:grid-cols-3 md:gap-10 lg:grid-cols-6">
<Image
src={FlixbusLogo}
alt="Flixbus Flix Flixtrain Logo"
@@ -56,37 +49,18 @@ export const Hero: React.FC = ({}) => {
width={200}
/>
<Image src={CalLogoLight} alt="Cal Logo" className="block rounded-lg dark:hidden" width={170} />
<Image src={CalLogoDark} alt="Cal Logo" className="hidden rounded-lg dark:block" width={170} />
<Image src={ThemeisleLogo} alt="Neverinstall Logo" className="pb-1" width={200} />
<Image src={ThemeisleLogo} alt="ThemeIsle Logo" className="pb-1" width={200} />
<Image
src={CrowdLogoLight}
alt="Crowd.dev Logo"
className="block rounded-lg pb-1 dark:hidden"
width={200}
/>
<Image
src={CrowdLogoDark}
alt="Crowd.dev Logo"
className="hidden rounded-lg pb-1 dark:block"
width={200}
/>
<Image src={OptimoleLogo} alt="Neverinstall Logo" className="pb-1" width={200} />
<Image src={OptimoleLogo} alt="Optimole Logo" className="pb-1" width={200} />
<Image src={NILogoDark} alt="Neverinstall Logo" className="block pb-1 dark:hidden" width={200} />
<Image
src={NILogoLight}
alt="Neverinstall Logo"
className="hidden pb-1 dark:block"
width={200}
/>
<Image
src={NILogoLight}
alt="Neverinstall Logo"
className="hidden pb-1 dark:block"
width={200}
/>
</div>
</div>
<div className="hidden pt-14 md:block">
<Button
variant="highlight"
@@ -95,7 +69,7 @@ export const Hero: React.FC = ({}) => {
router.push("https://app.formbricks.com/auth/signup");
plausible("Hero_CTA_GetStartedItsFree");
}}>
Get Started, it&apos;s Free
Get Started
</Button>
<Button
variant="secondary"
@@ -108,9 +82,6 @@ export const Hero: React.FC = ({}) => {
</Button>
</div>
</div>
<div className="relative px-2 md:px-0">
<HeroAnimation fallbackImage={AnimationFallback} />
</div>
</div>
);
};

View File

@@ -30,7 +30,7 @@ export const HeroAnimation: React.FC<any> = ({ fallbackImage, ...props }) => {
}, [lottie]);
return (
<div className="relative" {...props}>
<div className="relative hidden md:block" {...props}>
<div ref={ref} />
{!loaded && (
<div className="absolute inset-0">

View File

@@ -6,62 +6,43 @@ import Image from "next/image";
export const Highlights: React.FC = ({}) => {
return (
<>
<div className="mx-auto mb-12 mt-8 max-w-lg md:mb-0 md:mt-32 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 md:pb-0">
<h2 className="xs:text-3xl text-2xl font-bold leading-7 tracking-tight text-slate-800 dark:text-slate-200">
Ask at the right moment,
<br />
<span className="font-light">get the data you need.</span>
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Follow up emails are so 2010. Ask users as they experience your product - and leverage a
significantly higher conversion rate.
</p>
</div>
<div className="rounded-lg bg-slate-100 py-6 pr-4 sm:py-16 sm:pr-8 dark:bg-slate-800">
<Image
src={ImageEventTriggerLight}
alt="react library"
className="block rounded-lg dark:hidden"
/>
<Image
src={ImageEventTriggerDark}
alt="react library"
className="hidden rounded-lg dark:block"
/>
</div>
</div>
<div className="space-y-16">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 md:pb-0">
<h2 className="xs:text-3xl text-2xl font-bold leading-7 tracking-tight text-slate-800 dark:text-slate-200">
Ask at the right moment,
<br />
<span className="font-light">get the data you need.</span>
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Follow up emails are so 2010. Ask users as they experience your product - and leverage a
significantly higher conversion rate.
</p>
</div>
<div className="rounded-lg bg-slate-100 py-6 pr-4 sm:py-16 sm:pr-8 dark:bg-slate-800">
<Image src={ImageEventTriggerLight} alt="react library" className="block rounded-lg dark:hidden" />
<Image src={ImageEventTriggerDark} alt="react library" className="hidden rounded-lg dark:block" />
</div>
</div>
<div className="mx-auto mb-12 mt-8 max-w-lg md:mb-0 md:mt-32 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="order-last rounded-lg bg-slate-100 p-4 sm:p-8 md:order-first dark:bg-slate-800">
<Image
src={ImageAttributesLight}
alt="react library"
className="block rounded-lg dark:hidden"
/>
<Image src={ImageAttributesDark} alt="react library" className="hidden rounded-lg dark:block" />
</div>
<div className="pb-8 md:pb-0">
<h2 className="xs:text-3xl text-2xl font-bold leading-7 tracking-tight text-slate-800 sm:text-3xl dark:text-slate-100">
Don&apos;t Spray and pray.
<br />
<span className="font-light">Pre-segment granularly.</span>
</h2>
<p className="text-md mt-6 max-w-md leading-7 text-slate-500 dark:text-slate-400">
Pre-segment who sees your survey based on custom attributes. Keep the signal, cancel out the
noise.
</p>
</div>
</div>
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="order-last rounded-lg bg-slate-100 p-4 sm:p-8 md:order-first dark:bg-slate-800">
<Image src={ImageAttributesLight} alt="react library" className="block rounded-lg dark:hidden" />
<Image src={ImageAttributesDark} alt="react library" className="hidden rounded-lg dark:block" />
</div>
<div className="pb-8 md:pb-0">
<h2 className="xs:text-3xl text-2xl font-bold leading-7 tracking-tight text-slate-800 sm:text-3xl dark:text-slate-100">
Don&apos;t Spray and pray.
<br />
<span className="font-light">Pre-segment granularly.</span>
</h2>
<p className="text-md mt-6 max-w-md leading-7 text-slate-500 dark:text-slate-400">
Pre-segment who sees your survey based on custom attributes. Keep the signal, cancel out the
noise.
</p>
</div>
</div>
</>
</div>
);
};

View File

@@ -3,142 +3,121 @@ import DashboardMockupDark from "@/images/dashboard-mockup-dark.png";
import DashboardMockup from "@/images/dashboard-mockup.png";
import { MousePointerClickIcon } from "lucide-react";
import Image from "next/image";
import { useState } from "react";
import { Button } from "@formbricks/ui/Button";
import AddEventDummy from "../dummyUI/AddEventDummy";
import AddNoCodeEventModalDummy from "../dummyUI/AddNoCodeEventModalDummy";
import HeadingCentered from "../shared/HeadingCentered";
import SetupTabs from "./SetupTabs";
export const Steps: React.FC = () => {
const [isAddEventModalOpen, setAddEventModalOpen] = useState(false);
return (
<>
<div>
<HeadingCentered
closer
teaser="Leave your engineers in peace"
heading="Set Formbricks up in minutes"
subheading="Formbricks is designed for as little dev attention as possible. Heres how:"
/>
<div id="howitworks" className="xs:m-auto mb-12 mt-16 max-w-lg md:mb-0 md:mt-8 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="xs:grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 sm:pl-10 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 1</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 dark:text-slate-200">
Copy + Paste
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Simply copy a &lt;script&gt; tag to your HTML head - thats about it. Or use NPM to install
Formbricks for React, Vue, Svelte, etc.
</p>
</div>
<div className="rounded-lg bg-slate-100 dark:bg-slate-800">
<SetupTabs />
</div>
<div className="space-y-16">
<div className="xs:grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 sm:pl-10 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 1</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 dark:text-slate-200">
Copy + Paste
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Simply copy a &lt;script&gt; tag to your HTML head - thats about it. Or use NPM to install
Formbricks for React, Vue, Svelte, etc.
</p>
</div>
<div className="rounded-lg bg-slate-100 dark:bg-slate-800">
<SetupTabs />
</div>
</div>
</div>
<div className="mx-auto mb-12 mt-8 max-w-lg md:mb-0 md:mt-32 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="order-last w-full rounded-lg bg-slate-100 p-4 sm:py-8 md:order-first dark:bg-slate-800">
<div className="flex h-40 items-center justify-center">
<Button variant="primary">
<MousePointerClickIcon className="mr-2 h-5 w-5 text-white" />
Add Action
</Button>
</div>
</div>
<div className="pb-8 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 2</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-100">
No-Code: Track User Actions
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Set up user actions which can trigger your survey without writing a single line of code.
Surveys can be triggered on specific pages or after an element is clicked.
</p>
</div>
</div>
</div>
</div>
<div className="mx-auto mb-12 mt-8 max-w-lg md:mb-0 md:mt-32 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 sm:pl-10 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 3</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-200">
Create your survey
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Start from a template - or from scratch. Ask what you want, in any language. You can also
adjust the look and feel of your survey.
</p>
</div>
<div className="relative w-full rounded-lg p-1 sm:p-8 dark:bg-slate-800">
<DemoPreview template="Product Market Fit Survey (short)" />
</div>
</div>
</div>
</div>
<div className="mx-auto mb-12 mt-8 max-w-lg md:mb-0 md:mt-32 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="order-last w-full rounded-lg bg-slate-100 p-4 sm:py-8 md:order-first dark:bg-slate-800">
<div className="mx-auto flex flex-col items-center justify-center md:w-3/4">
<AddEventDummy />
</div>
</div>
<div className="pb-8 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 4</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-100">
Set segment and trigger
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Create a custom segment for each survey. Use attributes and past user actions to only survey
the people who have answers. Trigger your survey on any user action in your app.
</p>
</div>
</div>
</div>
</div>
<div className="mx-auto mb-12 mt-8 max-w-lg md:mb-0 md:mt-32 md:max-w-none">
<div className="px-4 sm:max-w-4xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 sm:pl-10 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 5</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-200">
Make better decisions
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Gather all insights you can - including partial submissions. Build conviction for the next
product decision. Better data, better business.
</p>
</div>
<div className="sm:scale-125 sm:p-8">
<Image
src={DashboardMockup}
quality="100"
alt="Data Pipelines"
className="block rounded-lg dark:hidden"
/>
<Image
src={DashboardMockupDark}
quality="100"
alt="Data Pipelines"
className="hidden dark:block"
/>
</div>
</div>
</div>
</div>
<AddNoCodeEventModalDummy open={isAddEventModalOpen} setOpen={setAddEventModalOpen} />
</>
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="order-last w-full rounded-lg bg-slate-100 p-4 sm:py-8 md:order-first dark:bg-slate-800">
<div className="flex h-40 items-center justify-center">
<Button variant="primary">
<MousePointerClickIcon className="mr-2 h-5 w-5 text-white" />
Add Action
</Button>
</div>
</div>
<div className="pb-8 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 2</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-100">
No-Code: Track User Actions
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Set up user actions which can trigger your survey without writing a single line of code. Surveys
can be triggered on specific pages or after an element is clicked.
</p>
</div>
</div>
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 sm:pl-10 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 3</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-200">
Create your survey
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Start from a template - or from scratch. Ask what you want, in any language. You can also adjust
the look and feel of your survey.
</p>
</div>
<div className="relative w-full rounded-lg p-1 sm:p-8 dark:bg-slate-800">
<DemoPreview template="Product Market Fit Survey (short)" />
</div>
</div>
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="order-last w-full rounded-lg bg-slate-100 p-4 sm:py-8 md:order-first dark:bg-slate-800">
<div className="mx-auto flex flex-col items-center justify-center md:w-3/4">
<AddEventDummy />
</div>
</div>
<div className="pb-8 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 4</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-100">
Set segment and trigger
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Create a custom segment for each survey. Use attributes and past user actions to only survey the
people who have answers. Trigger your survey on any user action in your app.
</p>
</div>
</div>
<div className="grid md:grid-cols-2 md:items-center md:gap-16">
<div className="pb-8 sm:pl-10 md:pb-0">
<h4 className="text-brand-dark font-bold">Step 5</h4>
<h2 className="xs:text-3xl text-2xl font-bold tracking-tight text-slate-800 sm:text-3xl dark:text-slate-200">
Make better decisions
</h2>
<p className="text-md mt-6 max-w-lg leading-7 text-slate-500 dark:text-slate-400">
Gather all insights you can - including partial submissions. Build conviction for the next
product decision. Better data, better business.
</p>
</div>
<div className="sm:scale-125 sm:p-8">
<Image
src={DashboardMockup}
quality="100"
alt="Data Pipelines"
className="block rounded-lg dark:hidden"
/>
<Image
src={DashboardMockupDark}
quality="100"
alt="Data Pipelines"
className="hidden dark:block"
/>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,19 @@
interface TestimonialProps {
title: string;
text: string;
Icon: React.ElementType;
}
export default function SalesTestimonial({ title, text, Icon }: TestimonialProps) {
return (
<div className="flex items-center gap-4 rounded-xl border border-slate-200 bg-gradient-to-tr from-slate-100 to-slate-100 p-4 transition-colors delay-1000 duration-1000 ease-in-out hover:to-slate-50">
<div className="rounded-xl border border-slate-200 bg-white p-8">
<Icon className="h-12 w-12 text-slate-500" strokeWidth={1} />
</div>
<div>
<h3 className="text-pretty text-lg font-medium text-slate-800">{title}</h3>
<p className="text-slate-500">{text}</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,101 @@
import { Popover, Transition } from "@headlessui/react";
import { Menu, X } from "lucide-react";
import { usePlausible } from "next-plausible";
import Link from "next/link";
import { useRouter } from "next/router";
import { Fragment } from "react";
import { Button } from "@formbricks/ui/Button";
import { FooterLogo } from "../shared/Logo";
const mainNav = [
{ name: "Link Surveys", href: "/open-source-form-builder", status: true },
{ name: "Website Surveys", href: "/website-survey", status: true },
{ name: "In-app Surveys", href: "/in-app-survey", status: true },
];
export default function HeaderLight() {
const plausible = usePlausible();
const router = useRouter();
return (
<header className="max-w-8xl mx-auto flex items-center justify-between px-6 py-6 lg:px-10 xl:px-12">
<Link href="/">
<span className="sr-only">Formbricks</span>
<FooterLogo className="h-8 w-auto sm:h-10" />
</Link>
<div className="hidden lg:block">
{mainNav.map((item) => (
<Link
key={item.name}
href={item.href}
className="px-8 text-sm font-medium text-slate-400 hover:text-slate-700 lg:text-base dark:hover:text-slate-300">
{item.name}
</Link>
))}
</div>
<Button
variant="highlight"
className="hidden md:px-6 lg:block"
onClick={() => {
router.push("https://app.formbricks.com/auth/signup");
plausible("Demo_CTA_TryForFree");
}}>
Get started - it&apos;s free!
</Button>
<Popover className="block lg:hidden">
<Popover.Button className="inline-flex items-center justify-center rounded-md bg-slate-100 p-2 text-slate-400 hover:bg-slate-100 hover:text-slate-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden dark:bg-slate-700 dark:text-slate-200">
<span className="sr-only">Open menu</span>
<Menu className="h-6 w-6" aria-hidden="true" />
</Popover.Button>
<Transition
as={Fragment}
enter="duration-200 ease-out"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="duration-100 ease-in"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95">
<Popover.Panel
focus
className="absolute inset-x-0 top-0 z-20 origin-top-right transform p-2 transition md:hidden">
<div className="dark:divide-slate divide-y-2 divide-slate-100 rounded-lg bg-slate-200 shadow-lg ring-1 ring-black ring-opacity-5 dark:divide-slate-700 dark:bg-slate-800">
<div className="px-5 pb-6 pt-5">
<div className="flex items-center justify-between">
<div>
<FooterLogo className="h-8 w-auto" />
</div>
<div className="-mr-2">
<Popover.Button className="inline-flex items-center justify-center rounded-md bg-white p-2 text-slate-400 hover:bg-slate-100 hover:text-slate-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 dark:bg-slate-700 dark:text-slate-200">
<span className="sr-only">Close menu</span>
<X className="h-6 w-6" aria-hidden="true" />
</Popover.Button>
</div>
</div>
</div>
<div className="px-5 py-6">
<div className="flex flex-col space-y-5 text-center text-sm dark:text-slate-300">
<div className="space-y-4">
{mainNav.map((item) => (
<Link key={item.name} href={item.href} className="block text-lg text-slate-700">
{item.name}
</Link>
))}
<Button
variant="primary"
onClick={() => router.push("https://app.formbricks.com/auth/signup")}
className="flex w-full justify-center text-lg">
Get started, it&apos;s free!
</Button>
</div>
</div>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
</header>
);
}

View File

@@ -8,16 +8,14 @@ interface LayoutProps {
description: string;
}
export default function Layout({ title, description, children }: LayoutProps) {
export default function LayoutLight({ title, description, children }: LayoutProps) {
return (
<div className="mx-auto w-full">
<MetaInformation title={title} description={description} />
<HeaderLight />
{
<main className="max-w-8xl relative mx-auto flex w-full flex-col justify-center px-2 lg:px-8 xl:px-12">
{children}
</main>
}
<main className="max-w-8xl relative mx-auto flex w-full flex-col justify-center space-y-24 px-6 lg:space-y-40 lg:px-24 xl:px-36 ">
{children}
</main>
<Footer />
</div>
);

View File

@@ -0,0 +1,37 @@
import CalLogoLight from "@/images/clients/cal-logo-light.svg";
import CrowdLogoLight from "@/images/clients/crowd-logo-light.svg";
import FlixbusLogo from "@/images/clients/flixbus-white.svg";
import NILogoDark from "@/images/clients/niLogoDark.svg";
import OptimoleLogo from "@/images/clients/optimole-logo.svg";
import ThemeisleLogo from "@/images/clients/themeisle-logo.webp";
import Image from "next/image";
export default function LogoBar() {
return (
<div className="mx-auto max-w-4xl">
<p className="text-center text-lg text-slate-700">
10,000+ teams at the worlds best companies trust Formbricks
</p>
<div className="mt-5 items-center px-4 sm:flex sm:justify-center md:mt-6 md:space-x-8 md:px-0">
<div className="grid grid-cols-2 items-center gap-8 pt-2 md:grid-cols-2 md:gap-10 lg:grid-cols-6">
<Image
src={FlixbusLogo}
alt="Flixbus Flix Flixtrain Logo"
className="rounded-lg pb-1 "
width={200}
/>
<Image src={CalLogoLight} alt="Cal Logo" className="block rounded-lg dark:hidden" width={170} />
<Image src={ThemeisleLogo} alt="ThemeIsle Logo" className="pb-1" width={200} />
<Image
src={CrowdLogoLight}
alt="Crowd.dev Logo"
className="block rounded-lg pb-1 dark:hidden"
width={200}
/>
<Image src={OptimoleLogo} alt="Optimole Logo" className="pb-1" width={200} />
<Image src={NILogoDark} alt="Neverinstall Logo" className="block pb-1 dark:hidden" width={200} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,25 @@
import SalesCTA from "@/components/salespage/SalesCTA";
interface Props {
headline: string;
subheadline: string;
}
export default function SalesBreaker({ headline, subheadline }: Props) {
return (
<div className="xs:mx-auto xs:w-full mx-4 my-4 mt-28 max-w-6xl rounded-xl bg-gradient-to-br from-slate-200 to-slate-300 md:mb-0 dark:from-slate-800 dark:via-slate-800 dark:to-slate-700">
<div className="relative px-4 py-8 sm:px-6 sm:pb-12 sm:pt-8 lg:px-8 lg:pt-12">
<div className="xs:block xs:absolute xs:right-10 hidden md:top-1/2 md:-translate-y-1/2">
<SalesCTA />
</div>
<h2 className="mt-4 text-2xl font-bold tracking-tight text-slate-800 lg:text-3xl">{headline}</h2>
<h4 className="text-md mt-4 max-w-3xl text-slate-500 lg:text-lg dark:text-slate-300">
{subheadline}
</h4>
<div className="xs:hidden mt-4">
<SalesCTA />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,20 @@
import { usePlausible } from "next-plausible";
import { useRouter } from "next/router";
import { Button } from "@formbricks/ui/Button";
export default function SalesCTA() {
const plausible = usePlausible();
const router = useRouter();
return (
<Button
variant="darkCTA"
className="w-fit"
onClick={() => {
router.push("https://app.formbricks.com/auth/signup");
plausible("SalesPage_CTA_GetStartedNow");
}}>
Get started now
</Button>
);
}

View File

@@ -0,0 +1,36 @@
import SalesCTA from "@/components/salespage/SalesCTA";
import Image, { StaticImageData } from "next/image";
interface SalesPageFeatureProps {
imgSrc: StaticImageData;
imgAlt: string;
headline: string;
subheadline: string;
imgLeft?: boolean;
}
export default function SalesPageFeature({
imgSrc,
imgAlt,
headline,
subheadline,
imgLeft,
}: SalesPageFeatureProps) {
return (
<div className="group grid content-center gap-12 lg:grid-cols-2">
<div
className={`order-last flex flex-col justify-center space-y-6 lg:order-none ${imgLeft && `!order-last`}`}>
<h2 className="text-balance text-3xl font-bold text-slate-800">{headline}</h2>
<p className="text-pretty text-lg text-slate-700">{subheadline}</p>
<SalesCTA />
</div>
<div className="relative">
<Image
src={imgSrc}
alt={imgAlt}
className="rounded-3xl border border-slate-200 bg-white transition delay-75 duration-[1500ms] group-hover:scale-[105%] group-hover:border-slate-300"
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,28 @@
import SalesCTA from "@/components/salespage/SalesCTA";
import Image, { StaticImageData } from "next/image";
interface SalesPageHeroProps {
imgSrc: StaticImageData;
imgAlt: string;
headline: React.ReactNode;
subheadline: string;
}
export default function SalesPageHero({ imgSrc, imgAlt, headline, subheadline }: SalesPageHeroProps) {
return (
<div className="group grid content-center gap-12 pt-20 lg:grid-cols-2">
<div className="my-auto space-y-6">
<h1 className="text-5xl font-bold text-slate-800">{headline}</h1>
<p className="text-balance text-lg text-slate-700">{subheadline}</p>
<SalesCTA />
</div>
<div className="relative hidden lg:block">
<Image
src={imgSrc}
alt={imgAlt}
className="scale-110 rounded-3xl border border-slate-200 bg-white transition-all delay-75 duration-[1500ms] group-hover:scale-[115%] group-hover:border-slate-300"
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,32 @@
interface SalesStepsProps {
steps: Array<{ id: string; name: string; description: string }>;
}
export default function SalesSteps({ steps }: SalesStepsProps) {
return (
<div className="relative">
<ul role="list" className="grid grid-cols-1 gap-4 pt-8 sm:grid-cols-2 md:grid-cols-3 lg:gap-10">
{steps.map((step) => {
return (
<li
key={step.id}
className="relative col-span-1 flex flex-col rounded-xl border border-slate-200 bg-slate-100 text-center ">
<div className="absolute -mt-12 w-full">
<div className="mx-auto flex h-20 w-20 items-center justify-center rounded-3xl bg-slate-200 text-5xl font-bold text-slate-700 shadow ">
{step.id}
</div>
</div>
<div className="flex flex-1 flex-col p-10">
<h3 className="my-4 text-lg font-medium text-slate-800 ">{step.name}</h3>
<dl className="mt-1 flex flex-grow flex-col justify-between">
<dt className="sr-only">Description</dt>
<dd className="text-slate-600 ">{step.description}</dd>
</dl>
</div>
</li>
);
})}
</ul>
</div>
);
}

View File

@@ -0,0 +1,28 @@
import Image, { StaticImageData } from "next/image";
interface TestimonialProps {
quote: string;
author: string;
imgSrc: StaticImageData;
imgAlt: string;
textSize: "base" | "large";
}
export default function SalesTestimonial({
quote,
author,
imgAlt,
imgSrc,
textSize = "base",
}: TestimonialProps) {
return (
<div className="flex flex-col items-center space-y-4 rounded-xl border border-slate-200 bg-slate-100 p-8 text-center">
<h3
className={`text-balance font-medium text-slate-700 ${textSize === "base" ? "text-xl" : "text-xl lg:text-2xl"} `}>
{quote}
</h3>
<p className="text-lg text-slate-500">{author}</p>
<Image src={imgSrc} alt={imgAlt} width={100} height={100} className="rounded-full" />
</div>
);
}

View File

@@ -79,15 +79,6 @@ export default function BestPracticeNavigation() {
description: "Give users the chance to share feedback in a single click.",
category: "Boost Retention",
},
{
name: "Improve Newsletter Content",
href: "/improve-newsletter-content",
status: true,
icon: FeedbackIcon,
description: "Improve your newsletter content by showing this survey to your readers.",
category: "Boost Retention",
},
];
return (

View File

@@ -1,20 +1,14 @@
import HeadingCentered from "@/components/shared/HeadingCentered";
import BestPracticeNavigation from "./BestPracticeNavigation";
export default function InsightOppos() {
return (
<div className="pb-10 pt-12 md:pt-20">
<div className="px-4 py-20 text-center sm:px-6 lg:px-8" id="best-practices">
<h1 className="text-3xl font-bold tracking-tight text-slate-800 sm:text-4xl md:text-5xl dark:text-slate-200">
Get started with{" "}
<span className="from-brand-light to-brand-dark bg-gradient-to-b bg-clip-text text-transparent xl:inline">
Best Practices
</span>
</h1>
<p className="mx-auto mt-3 max-w-md text-base text-slate-500 sm:text-lg md:mt-5 md:max-w-3xl md:text-xl dark:text-slate-300">
Run battle-tested approaches for qualitative user research in minutes.
</p>
</div>
<div id="best-practices">
<HeadingCentered
heading="Get started with Best Practices"
subheading="Run battle-tested approaches for qualitative user research in minutes."
/>
<BestPracticeNavigation />
</div>
);

View File

@@ -22,7 +22,7 @@ export default function BreakerCTA({ inverted = false, teaser, headline, subhead
inverted
? "from-slate-800 via-slate-800 to-slate-700 dark:from-slate-200 dark:to-slate-300"
: "from-slate-200 to-slate-300 dark:from-slate-800 dark:via-slate-800 dark:to-slate-700",
"xs:mx-auto xs:w-full mx-4 my-4 mt-28 max-w-6xl rounded-xl bg-gradient-to-br md:mb-0 "
"mx-auto w-full max-w-6xl rounded-xl bg-gradient-to-br "
)}>
<div className="relative px-4 py-8 sm:px-6 sm:pb-12 sm:pt-8 lg:px-8 lg:pt-12">
<div className="xs:block xs:absolute xs:right-10 hidden md:top-1/2 md:-translate-y-1/2">

View File

@@ -9,7 +9,7 @@ export default function CTA() {
return (
<>
<div className="mx-auto px-4 py-16 sm:px-6 lg:px-8 lg:pb-40 lg:pt-24">
<HeadingCentered closer teaser="Get started" heading="Ready for the last form tool you need?" />
<HeadingCentered teaser="Get started" heading="Ready for the last form tool you need?" />
<div className="mt-12 grid grid-cols-1 content-center md:grid-cols-2">
<div className="-mb-4 rounded-t-xl bg-gradient-to-br from-slate-300 to-slate-200 px-8 py-24 text-center text-slate-900 md:-mr-5 md:mb-0 md:ml-2.5 md:rounded-l-xl lg:p-24 dark:from-slate-800 dark:to-slate-900 dark:text-slate-100">

View File

@@ -4,13 +4,39 @@ import { FaDiscord, FaGithub, FaXTwitter } from "react-icons/fa6";
import { FooterLogo } from "./Logo";
const navigation = {
other: [
products: [
{ name: "Link Surveys", href: "/open-source-form-builder", status: true },
{ name: "Website Surveys", href: "/website-survey", status: true },
{ name: "In-app Surveys", href: "/in-app-survey", status: true },
],
comparisons: [
{ name: "vs. Google Forms", href: "/vs-google-forms", status: true },
{ name: "vs. Formspree", href: "/vs-formspree", status: true },
{ name: "vs. OhMyForm", href: "/vs-ohmyform", status: true },
],
footernav: [
{ name: "Community", href: "/community", status: true },
{ name: "Pricing", href: "/pricing", status: true },
{ name: "Blog", href: "/blog", status: true },
{ name: "OSS Friends", href: "/oss-friends", status: true },
{ name: "Docs", href: "/blog", status: true },
],
legal: [
{ name: "Imprint", href: "/imprint", status: true },
{ name: "Privacy Policy", href: "/privacy", status: true },
{ name: "Terms", href: "/terms", status: true },
{ name: "GDPR FAQ", href: "/gdpr", status: true },
{ name: "GDPR Guide", href: "/gdpr-guide", status: true },
],
bestPractices: [
{ name: "Interview Prompt", href: "/interview-prompt", status: true },
{ name: "PMF Survey", href: "/measure-product-market-fit", status: true },
{ name: "Onboarding Segments", href: "/onboarding-segmentation", status: true },
{ name: "Learn from Churn", href: "/learn-from-churn", status: true },
{ name: "Improve Trial CR", href: "/improve-trial-conversion", status: true },
{ name: "Docs Feedback", href: "/docs-feedback", status: true },
{ name: "Feature Chaser", href: "/feature-chaser", status: true },
{ name: "Feedback Box", href: "/feedback-box", status: true },
],
social: [
{
name: "Twitter",
@@ -39,27 +65,84 @@ export default function Footer() {
<h2 id="footer-heading" className="sr-only">
Footer
</h2>
<div className="mx-auto flex max-w-7xl flex-col space-y-6 px-4 py-12 text-center sm:px-6 lg:px-8 lg:py-16">
<Link href="/">
<span className="sr-only">Formbricks</span>
<FooterLogo className="mx-auto h-8 w-auto sm:h-10" />
</Link>
<p className="text-base text-slate-500 dark:text-slate-400">Privacy-first Experience Management</p>
<div className="border-slate-500">
<p className="text-sm text-slate-400 dark:text-slate-500">
Formbricks GmbH &copy; {currentYear}. All rights reserved.
<br />
<Link href="/imprint">Imprint</Link> | <Link href="/privacy">Privacy Policy</Link> |{" "}
<Link href="/terms">Terms</Link> | <Link href="/oss-friends">OSS Friends</Link>
</p>
<div className="mx-auto grid max-w-7xl content-center gap-12 px-4 py-12 md:grid-cols-2 lg:grid-cols-3 lg:py-16">
<div className="space-y-6">
<Link href="/">
<span className="sr-only">Formbricks</span>
<FooterLogo className="h-8 w-auto sm:h-10" />
</Link>
<p className="text-base text-slate-500 dark:text-slate-400">Privacy-first Experience Management</p>
<div className="border-slate-500">
<p className="text-sm text-slate-400 dark:text-slate-500">
Formbricks GmbH &copy; {currentYear}. All rights reserved.
<br />
<Link href="/imprint">Imprint</Link> | <Link href="/privacy">Privacy Policy</Link> |{" "}
<Link href="/terms">Terms</Link> | <Link href="/oss-friends">OSS Friends</Link>
</p>
</div>
<div className="flex space-x-6">
{navigation.social.map((item) => (
<Link key={item.name} href={item.href} className="text-slate-400 hover:text-slate-500">
<span className="sr-only">{item.name}</span>
<item.icon className="h-6 w-6" aria-hidden="true" />
</Link>
))}
</div>
</div>
<div className="flex justify-center space-x-6">
{navigation.social.map((item) => (
<Link key={item.name} href={item.href} className="text-slate-400 hover:text-slate-500">
<span className="sr-only">{item.name}</span>
<item.icon className="h-6 w-6" aria-hidden="true" />
</Link>
))}
<div className="grid grid-cols-2 gap-8 lg:col-span-2 lg:grid-cols-4">
<div>
<h4 className="mb-2 font-medium text-slate-700">Formbricks</h4>
{navigation.footernav.map((item) => (
<Link
key={item.name}
href={item.href}
className="my-1 block text-slate-500 hover:text-slate-600">
{item.name}
</Link>
))}
</div>
<div>
<h4 className="mb-2 font-medium text-slate-700">Product</h4>
{navigation.products.map((item) => (
<Link
key={item.name}
href={item.href}
className="my-1 block text-slate-500 hover:text-slate-600">
{item.name}
</Link>
))}
<h4 className="mb-2 mt-5 font-medium text-slate-700">Comparison</h4>
{navigation.comparisons.map((item) => (
<Link
key={item.name}
href={item.href}
className="my-1 block text-slate-500 hover:text-slate-600">
{item.name}
</Link>
))}
</div>
<div>
<h4 className="mb-2 font-medium text-slate-700">Best Practices</h4>
{navigation.bestPractices.map((item) => (
<Link
key={item.name}
href={item.href}
className="my-1 block text-slate-500 hover:text-slate-600">
{item.name}
</Link>
))}
</div>
<div>
<h4 className="mb-2 font-medium text-slate-700">Legal</h4>
{navigation.legal.map((item) => (
<Link
key={item.name}
href={item.href}
className="my-1 block text-slate-500 hover:text-slate-600">
{item.name}
</Link>
))}
</div>
</div>
</div>
</footer>

View File

@@ -268,12 +268,6 @@ export default function Header() {
</>
)}
</Popover>
{/* <Link
href="/community"
className="text-base font-medium text-slate-400 hover:text-slate-700 dark:hover:text-slate-300">
Community
</Link>
*/}
<Link
href="/pricing"
className="text-sm font-medium text-slate-400 hover:text-slate-700 lg:text-base dark:hover:text-slate-300">
@@ -294,11 +288,6 @@ export default function Header() {
className="text-sm font-medium text-slate-400 hover:text-slate-700 lg:text-base dark:hover:text-slate-300">
Blog {/* <p className="bg-brand inline rounded-full px-2 text-xs text-white">1</p> */}
</Link>
{/* <Link
href="/careers"
className="text-base font-medium text-slate-400 hover:text-slate-700 dark:hover:text-slate-300">
Careers <p className="bg-brand inline rounded-full px-2 text-xs text-white">1</p>
</Link> */}
</Popover.Group>
<div className="hidden flex-1 items-center justify-end md:flex">
<Button
@@ -319,11 +308,6 @@ export default function Header() {
className="hidden dark:block"
/>
</Button>
{/* <Button variant="secondary" className="ml-2 px-2" onClick={() => setVideoModal(true)}>
<VideoWalkThrough open={videoModal} setOpen={() => setVideoModal(false)} />
<PlayCircleIcon className="h-6 w-6" />
</Button> */}
<Button
variant="highlight"
className="ml-2 text-xs lg:text-sm"

View File

@@ -1,15 +1,12 @@
import clsx from "clsx";
interface Props {
teaser?: string;
heading: string;
heading: React.ReactNode;
subheading?: string;
closer?: boolean;
}
export default function HeadingCentered({ teaser, heading, subheading, closer }: Props) {
export default function HeadingCentered({ teaser, heading, subheading }: Props) {
return (
<div className={clsx(closer ? "pt-16 lg:pt-24" : "pt-24 lg:pt-40", "px-2 pb-4 text-center md:pb-12")}>
<div className="mb-12 text-center">
<p className="text-md text-brand-dark dark:text-brand-light mx-auto mb-3 max-w-2xl font-semibold uppercase sm:mt-4">
{teaser}
</p>

View File

@@ -1,5 +1,5 @@
import HeaderLight from "../salespage/HeaderLight";
import Footer from "./Footer";
import Header from "./Header";
import MetaInformation from "./MetaInformation";
interface LayoutProps {
@@ -10,11 +10,11 @@ interface LayoutProps {
export default function Layout({ title, description, children }: LayoutProps) {
return (
<div className="flex h-screen flex-col justify-between">
<div className="mx-auto w-full">
<MetaInformation title={title} description={description} />
<Header />
<HeaderLight />
{
<main className="max-w-8xl relative mx-auto mb-auto flex w-full flex-col justify-center sm:px-2 lg:px-8 xl:px-12">
<main className="max-w-8xl relative mx-auto flex w-full flex-col justify-center space-y-32 px-6 py-24 lg:px-24 xl:px-36 ">
{children}
</main>
}

View File

@@ -1,8 +1,8 @@
import HeaderLight from "@/components/salespage/HeaderLight";
import SlideInBanner from "@/components/shared/SlideInBanner";
import { useEffect } from "react";
import Footer from "./Footer";
import Header from "./Header";
import MetaInformation from "./MetaInformation";
import { Prose } from "./Prose";
@@ -39,7 +39,7 @@ interface Props {
export default function LayoutMdx({ meta, children }: Props) {
useExternalLinks(".prose a");
return (
<div className="flex h-screen flex-col justify-between">
<div className="mx-auto w-full">
<MetaInformation
title={meta.title}
description={meta.description}
@@ -48,7 +48,7 @@ export default function LayoutMdx({ meta, children }: Props) {
section={meta.section}
tags={meta.tags}
/>
<Header />
<HeaderLight />
<main className="min-w-0 max-w-2xl flex-auto px-4 lg:max-w-none lg:pl-8 lg:pr-0 xl:px-16">
<article className="mx-auto my-16 max-w-3xl px-2">
{meta.title && (

View File

@@ -22,8 +22,8 @@ export default function MetaInformation({
}: Props) {
const router = useRouter();
const pageTitle = `${title}`;
const BASE_URL = `https://${process.env.VERCEL_URL}`;
const canonicalLink = `${BASE_URL}${router.asPath}`;
const BASE_URL = `formbricks.com`;
const canonicalLink = `https://${BASE_URL}${router.asPath}`;
return (
<Head>
<title>{pageTitle}</title>

View File

@@ -0,0 +1,79 @@
import Script from "next/script";
import { FAQPage, WithContext } from "schema-dts";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui/Accordion";
interface Answer {
"@type": "Answer";
text: string;
}
interface Question {
"@type": "Question";
name: string;
acceptedAnswer: Answer;
}
interface FAQ {
question: string;
answer: string;
}
interface FAQSchemaProps {
faqs: FAQ[];
headline: string;
description: string;
datePublished: string;
dateModified: string;
}
const SeoFaq: React.FC<FAQSchemaProps> = ({ faqs, headline, description, datePublished, dateModified }) => {
const FAQMainEntity: Question[] = faqs.map((faq) => ({
"@type": "Question",
name: faq.question,
acceptedAnswer: {
"@type": "Answer",
text: faq.answer,
},
}));
const FAQjsonld: WithContext<FAQPage> = {
"@context": "https://schema.org",
"@type": "FAQPage",
name: `Frequently Asked Questions around ${headline}`,
mainEntity: FAQMainEntity,
headline,
description,
author: {
"@type": "Person",
name: "Johannes Dancker",
url: "https://formbricks.com",
},
image: "",
datePublished,
dateModified,
};
return (
<>
<Script
id="faq-schema"
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(FAQjsonld),
}}
/>
<Accordion type="single" collapsible className="px-4 sm:px-0">
{faqs.map((faq, index) => (
<AccordionItem key={`item-${index}`} value={`item-${index + 1}`}>
<AccordionTrigger>{faq.question}</AccordionTrigger>
<AccordionContent>{faq.answer}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</>
);
};
export default SeoFaq;

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -34,6 +34,11 @@ const nextConfig = {
},
async redirects() {
return [
{
source: "/demo",
destination: "/",
permanent: false,
},
{
source: "/discord",
destination: "https://discord.gg/3YFcABF2Ts",

View File

@@ -35,6 +35,7 @@
"flexsearch": "^0.7.43",
"framer-motion": "11.0.13",
"lottie-web": "^5.12.2",
"lucide": "^0.350.0",
"mdast-util-to-string": "^4.0.0",
"mdx-annotations": "^0.1.4",
"next": "14.1.3",
@@ -53,8 +54,9 @@
"react-responsive-embed": "^2.1.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"remark-mdx": "^3.0.1",
"sharp": "^0.33.2",
"remark-mdx": "^3.0.0",
"schema-dts": "^1.1.2",
"sharp": "^0.33.1",
"shiki": "^0.14.7",
"simple-functional-loader": "^1.2.1",
"tailwindcss": "^3.4.1",

View File

@@ -9,7 +9,6 @@ export default function Document() {
<Html className="scroll-smooth antialiased [font-feature-settings:'ss01']" lang="en" dir="ltr">
<Head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />

View File

@@ -121,6 +121,11 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
"Open source, end-to-end encrypted platform that lets you securely manage secrets and configs across your team, devices, and infrastructure.",
href: "https://infisical.com",
},
{
name: "Keep",
description: "Open source alert management and AIOps platform.",
href: "https://keephq.dev",
},
{
name: "Langfuse",
description: "Open source LLM engineering platform. Debug, analyze and iterate together.",
@@ -169,7 +174,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
name: "Requestly",
description:
"Makes frontend development cycle 10x faster with API Client, Mock Server, Intercept & Modify HTTP Requests and Session Replays.",
href: "https://requestly.io",
href: "https://requestly.com",
},
{
name: "Revert",

View File

@@ -14,7 +14,7 @@ import Userpilot from "./userpilot-best-feedback-in-app-tool.png";
export const meta = {
title: "Feedback App Contest: 6 Candidates, 1 Winner (and how to use it)",
description:
"We looked at the best in app feedback tools 2024 and found a clear winner. Gather feedback in your app for free with Formbricks.",
"We looked at the best in-app feedback tools 2024 and found a clear winner. Gather feedback in your app for free with Formbricks.",
date: "2023-12-21",
publishedTime: "2023-12-21T12:00:00",
authors: ["Olasunkanmi Balogun"],
@@ -22,7 +22,7 @@ export const meta = {
tags: ["Feedback Apps", "Formbricks", "Userpilot", "Pendo", "Appcues", "Survicate", "Qualaroo"],
};
<Image src={Header} alt="Gather in app feedback for free with these 6 tools." className="w-full rounded-lg" />
<Image src={Header} alt="Gather in-app feedback for free with these 6 tools." className="w-full rounded-lg" />
<AuthorBox
name="Olasunkanmi Balogun"
@@ -87,7 +87,7 @@ Among the plethora of tools available in todays market, this section will gui
<Image
src={Formbricks}
alt="Formbricks is a free and open source survey software for in app micro surveys. Ask any user segment at any point in the user journey."
alt="Formbricks is a free and open source survey software for in-app micro surveys. Ask any user segment at any point in the user journey."
className="w-full rounded-lg"
/>

View File

@@ -64,7 +64,7 @@ Let's have a look at the best HotJar alternatives in 2024, including open source
<Image
src={Formbricks}
alt="Formbricks is a free and open source survey software for in app micro surveys. Ask any user segment at any point in the user journey."
alt="Formbricks is a free and open source survey software for in-app micro surveys. Ask any user segment at any point in the user journey."
className="w-full rounded-lg"
/>

View File

@@ -30,13 +30,13 @@ _Most open source projects get abandoned after a while. But these 5 open source
Looking for the perfect open source survey tool to help you gather valuable insights and improve your business? Look no further!
We've compiled a list of the top 5 open source form and survey tools that are still maintained in 2024. In app surveys, conversational bots, AI-generated surveys: These open source tools offer various features that cater to different needs.
We've compiled a list of the top 5 open source form and survey tools that are still maintained in 2024. In-app surveys, conversational bots, AI-generated surveys: These open source tools offer various features that cater to different needs.
## 1. Formbricks - In app micro surveys
## 1. Formbricks - In-app micro surveys
<Image
src={Formbricks}
alt="Formbricks is a free and open source survey software for in app micro surveys. Ask any user segment at any point in the user journey."
alt="Formbricks is a free and open source survey software for in-app micro surveys. Ask any user segment at any point in the user journey."
className="rounded-lg w-full"
/>
@@ -125,7 +125,7 @@ LimeSurvey has been around for at least a decade. It's a powerful survey tool ma
In this article, we've rounded up the top 5 open source form and survey tools that are still rocking it in 2024. Perfect for devs who are always on the lookout for the latest and greatest!
1. Formbricks: A game-changer for in app micro surveys, letting you target specific customer segments at any point in their journey. It's still early days, but this bad boy is worth keeping an eye on.
1. Formbricks: A game-changer for in-app micro surveys, letting you target specific customer segments at any point in their journey. It's still early days, but this bad boy is worth keeping an eye on.
2. SurveyJS: A must-have for DIY enthusiasts, this collection of JavaScript libraries makes building your own form management system a breeze. Just remember, the starting price is $499/year.

View File

@@ -2,17 +2,17 @@ import AuthorBox from "@/components/shared/AuthorBox";
import LayoutMdx from "@/components/shared/LayoutMdx";
import Image from "next/image";
import CrazyEgg from './crazy-egg-website-optimization-heatmaps-recordings-surveys.png'
import CoverImage from './cover-best-feedbackt-tools-2024-open-source-website-surveys-targeted.webp'
import Formbricks from './formbricks-privacy-first-experience-management.png'
import Hotjar from './hotjar-website-heatmaps-behavior-analytics-tools.png'
import IdeaScale from './idea-and-innovation-management-software-ideaScale.png'
import Mopinion from './mopinion-feedback-for-websites-apps-and-email.png'
import Sprinklr from './sprinklr-unified-customer-experience-management-platform-sprinklr.png'
import SurveyMonkey from './surveyMonkey-the-world-most-popular-free-online-survey-tool.png'
import Qualaroo from './user-research-customer-feedback-software-qualaroo.png'
import UserReport from './userReport-simple-user-engagement-tools-that-help-you-improve.png'
import UserSnap from './usersnap-your-number-one-user-feedback-platform.png'
import CoverImage from './cover-best-feedbackt-tools-2024-open-source-website-surveys-targeted.webp';
import CrazyEgg from './crazy-egg-website-optimization-heatmaps-recordings-surveys.png';
import Formbricks from './formbricks-privacy-first-experience-management.png';
import Hotjar from './hotjar-website-heatmaps-behavior-analytics-tools.png';
import IdeaScale from './idea-and-innovation-management-software-ideaScale.png';
import Mopinion from './mopinion-feedback-for-websites-apps-and-email.png';
import Sprinklr from './sprinklr-unified-customer-experience-management-platform-sprinklr.png';
import SurveyMonkey from './surveyMonkey-the-world-most-popular-free-online-survey-tool.png';
import Qualaroo from './user-research-customer-feedback-software-qualaroo.png';
import UserReport from './userReport-simple-user-engagement-tools-that-help-you-improve.png';
import UserSnap from './usersnap-your-number-one-user-feedback-platform.png';
export const meta = {
title: "Best Website Feedback Tools in 2024",
@@ -68,7 +68,7 @@ These tools, as well see, are tools that help you collect and analyze the opi
<Image
src={Formbricks}
alt="Formbricks is a free and open source survey software for in app micro surveys. Ask any user segment at any point in the user journey."
alt="Formbricks is a free and open source survey software for in-app micro surveys. Ask any user segment at any point in the user journey."
className="rounded-lg w-full"
/>

View File

@@ -19,7 +19,7 @@ export const meta = {
tags: ["Improve Newsletter Content"],
};
<Image src={Header} alt="Gather in app feedback for free with these 6 tools." className="w-full rounded-lg" />
<Image src={Header} alt="Gather in-app feedback for free with these 6 tools." className="w-full rounded-lg" />
<AuthorBox
name="Olasunkanmi Balogun"

View File

@@ -118,7 +118,7 @@ Enter Formbricks, an open-source survey solution designed to capture targeted us
<Image
src={Formbricks}
alt="Formbricks is a free and open source survey software for in app micro surveys. Ask any user segment at any point in the user journey."
alt="Formbricks is a free and open source survey software for in-app micro surveys. Ask any user segment at any point in the user journey."
className="w-full rounded-lg"
/>

View File

@@ -202,9 +202,9 @@ const FAQ = [
"The commercial plan is for features who break the OSS WIN-WIN Loop or incur additional cost. We charge 30$ if you want a custom domain, remove Formbricks branding, collect large files in surveys or collect payments. We think thats fair :)",
},
{
question: "Are your in app surveys also free forever?",
question: "Are your in-app surveys also free forever?",
answer:
"The in app surveys you can run with Formbricks are not part of this Deal. We offer a generous free plan but keep full control over the pricing in the long run. In app surveys are really powerful for products with thousands of users and something has to bring in the dollars.",
"The in-app surveys you can run with Formbricks are not part of this Deal. We offer a generous free plan but keep full control over the pricing in the long run. In-app surveys are really powerful for products with thousands of users and something has to bring in the dollars.",
},
{

View File

@@ -1,12 +0,0 @@
import Layout from "@/components/demo/LayoutLight";
import DemoView from "@/components/dummyUI/DemoView";
export default function DemoPage() {
return (
<Layout
title="Formbricks Demo"
description="Play around with our pre-defined 30+ templates and them to kick-start your survey & experience management.">
<DemoView />
</Layout>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

View File

@@ -0,0 +1,312 @@
import FeatureCard from "@/components/salespage/FeatureCard";
import LayoutLight from "@/components/salespage/LayoutLight";
import LogoBar from "@/components/salespage/LogoBar";
import SalesBreaker from "@/components/salespage/SalesBreaker";
import SalesPageFeature from "@/components/salespage/SalesPageFeature";
import SalesPageHero from "@/components/salespage/SalesPageHero";
import SalesSteps from "@/components/salespage/SalesSteps";
import SalesTestimonial from "@/components/salespage/SalesTestimonial";
import HeadingCentered from "@/components/shared/HeadingCentered";
import SeoFaq from "@/components/shared/seo/SeoFaq";
import Bailey from "@/images/clients/headshots/bailey.jpeg";
import Ram from "@/images/clients/headshots/ram.jpeg";
import Sachin from "@/images/clients/headshots/sachin.jpeg";
import {
IoCalendarNumber,
IoCaretDownCircle,
IoFileTrayFull,
IoFilter,
IoPlayForward,
IoStopwatch,
} from "react-icons/io5";
import Img1 from "./1-in-app-survey-open-source-free-gdpr-compliant-for-in-product-research.png";
import Img2 from "./2-in-app-survey-open-source-sprig-alternative.png";
import Img3 from "./3-granular-targeting-for-in-app-surveys-open-source.png";
import Img4 from "./4-multi-language-in-app-survey-translation-rtl-ltr.png";
import Img5 from "./5-fast-loading-in-product-surveys-for-apps-and-web-apps.png";
import Img6 from "./6-in-app-survey-native-look-and-feel-design-open-source.png";
import Img7 from "./7-unlimited-in-product-surveys-seats-team-members-open-source-and-free.png";
import Img8 from "./8-team-roles-micro-surveys-in-app-open-source-and-for-free.png";
import Img9 from "./9-reusable-segments-open-source-in-product-survey-software.png";
const inAppSurveySteps = [
{
id: "1",
name: "Connect your app",
description:
"Install the Formbricks SDK with your favorite package manager in seconds. Run native inapp surveys within minutes.",
},
{
id: "2",
name: "Pre-segment cohorts",
description:
"Send attributes and events to Formbricks to create usage-based cohorts. Send out highly targeted app surveys for better insights.",
},
{
id: "3",
name: "AI analysis",
description:
"Analyze insights in Formbricks in a breeze with our AI. Enable everyone in your team to get the most out of your in-product research.",
},
];
const inAppSurveyFeatures = [
{
headline: "Granular in-app targeting",
subheadline:
"Combine usage data with custom attributes and device information for fine-grained targeting. Targeted embedded surveys mean better insights for your research team and a better UX for your users.",
imgSrc: Img3,
imgAlt: "Screenshot of granular targeting feature in an in-app survey tool",
imgLeft: false,
},
{
headline: "Multi-language app surveys",
subheadline:
"For app surveys to fit in smoothly, they should feel like a part of your UI. Matching languages plays a big role for seamless product research. Formbricks lets you handle survey translations easily.",
imgSrc: Img4,
imgAlt: "Example of a multi-language survey embedded in a mobile app",
imgLeft: true,
},
{
headline: "Super fast loading",
subheadline:
"The Formbricks SDK is tiny (7KB). Keep your app lightning fast and your users engaged. The in-app survey SDK always loads deferred and never slows down your app.",
imgSrc: Img5,
imgAlt: "Demonstration of super fast loading times for an embedded survey in an app",
imgLeft: false,
},
{
headline: "On brand design",
subheadline:
"Customize your embedded surveys to fit in. Match the look & feel of your embedded surveys with your app. Leverage our no-code design editor or load a custom style sheet - all on the free plan!",
imgSrc: Img6,
imgAlt: "Preview of an on-brand design survey custom designed to fit within an app",
imgLeft: true,
},
{
headline: "Unlimited seats & products included",
subheadline:
"Embed Formbricks to run surveys in as many apps as you wish, without additional cost. Invite everyone who should work with user insights (hence, everyone). Product survey tools should never limit how far customer insights spread within a company.",
imgSrc: Img7,
imgAlt: "Illustration of embedding Formbricks surveys across multiple mobile apps",
imgLeft: false,
},
];
const linkSurveyFeaturesPt2 = [
{
headline: "Team roles",
subheadline:
"Control who can set up app surveys, and who gets to work with the insights gathered by your reserach. Granular access control allows everyone to work with the insights gathered with in-product research.",
imgSrc: Img8,
imgAlt: "Interface showcasing team roles and access rights for in-app survey setup and insights",
imgLeft: true,
},
{
headline: "Reuse segments to target consistently",
subheadline:
"Compose segments of app users with advanced filters. Reuse these segments to survey the same cohorts consistently. Keeping your targeting consistent allows to measure how much your app experience improves over time.",
imgSrc: Img9,
imgAlt: "Visualization of creating and reusing segments for targeted surveys inapp",
imgLeft: false,
},
];
const allFeaturesList = [
{
title: "Show survey to % of user",
text: "Only show surveys to e.g. 50% of visitors.",
icon: IoFilter,
},
{
title: "Add delay before showing",
text: "Wait a few seconds before showing the survey",
icon: IoStopwatch,
},
{
title: "Auto close in inactivity",
text: "Auto close a survey if the visitors does not interact.",
icon: IoCaretDownCircle,
},
{
title: "Close survey on response limit",
text: "Auto-close a survey after hitting e.g. 50 responses",
icon: IoFileTrayFull,
},
{
title: "Close survey on date",
text: "Auto-close a survey on a specific date.",
icon: IoCalendarNumber,
},
{
title: "Redirect on completion",
text: "Redirect visitors after they completed your survey.",
icon: IoPlayForward,
},
];
const FAQs = [
{
question: "Is Formbricks really free for creating embedded or in-app surveys?",
answer:
"Yes, Formbricks offers both a free Cloud plan and an open source community edition. This makes it an accessible choice for deploying embedded surveys and in-app survey. Advanced features are available for those needing more specialized capabilities.",
},
{
question: "Can I self-host Formbricks for more control over my product research tools?",
answer:
"Absolutely. Formbricks can be self-hosted with one click via our Docker image. This gives you full control over your product survey tools, while ensuring data privacy and compliance.",
},
{
question:
"How does Formbricks compare to other micro survey software in terms of features and flexibility?",
answer:
"Formbricks offers a comprehensive suite of features for embedded surveys, in-app feedback, and micro surveys. For see the speed development, have a look at the Formbricks repository on GitHub linked in the Footer. In case you're missing something, just let us know and we'll build it.",
},
{
question: "Is Formbricks GDPR-compliant for use as an in-app survey tool and embedded survey platform?",
answer:
"Yes, Formbricks is fully GDPR and CCPA compliant. It's a reliable choice for businesses seeking an in-app survey tool which handles potentially personalized data. Hosted in Frankfurt, Germany, and developed by a German company, it ensures the highest standards of data protection.",
},
{
question: "Can Formbricks help in conducting micro surveys within a mobile app?",
answer: "Currently, we do not offer SDKs for mobile apps yet. However, this is on the roadmap for 2024.",
},
{
question: "What are the best tools for creating an app survey?",
answer:
"For creating app surveys, Formbricks is among the top tools. As an open source product, we keep developer requirements at heart. However, the UX of Formbricks is designed also support marketers, researchers and sales reps in their work.",
},
{
question: "How can I implement an in-app survey effectively?",
answer:
"To implement an in-app survey effectively, use Formbricks to embed surveys directly into your application. In-product research has higher conversion and completion rates than any other form of surveying.",
},
{
question: "What is a micro survey and how can I use it with Formbricks?",
answer:
"A micro survey is a short, focused survey designed to capture quick insights. With Formbricks, you can easily create and embed these micro surveys into your website or app, enhancing the user experience and obtaining precise feedback.",
},
{
question: "Are embedded surveys more effective for user engagement?",
answer:
"Yes, embedded surveys, like those created with Formbricks, are highly effective for user engagement. They blend naturally with the app or website, encouraging more users to participate and share their insights without leaving the platform.",
},
{
question: "What advantages does Formbricks offer for in-app survey tools?",
answer:
"Formbricks offers numerous advantages for in-app surveys, including easy integration, real-time analytics, customizable survey templates, and micro survey capabilities. It's a powerful tool for enhancing user engagement and feedback collection.",
},
];
export default function LinkSurveyPage() {
return (
<LayoutLight
title="In-app Surveys, Open Source"
description="Run targeted in-app surveys with full control over your data. Natively embed open source in-product reserach to understand what your users think. Get started in minutes.">
<SalesPageHero
headline={
<span>
In-app Surveys People <i>Want</i> to Fill Out
</span>
}
subheadline="In-product user research with a native look and feel. Ask only the right cohort, ask gracefully."
imgSrc={Img1}
imgAlt="Targeted in-app surveys built on open source technology."
/>
<div className="grid gap-4 md:grid-cols-2">
<SalesTestimonial
quote="We self-host Formbricks for our app with 100.000+ users. It's remarkable how the surveys feel like a native part of our own app. Great product, built for everyone who cares about UX!"
author="Ram Pasala, CEO @ NeverInstall"
imgSrc={Ram}
imgAlt="Ram Pasala, CEO @ NeverInstall"
textSize="base"
/>
<SalesTestimonial
quote="As a product-led growth company, we run surveys at key moments in our user journey. We spent a lot of time crafting our UX and I love how seamless Formbricks fits in! Should be a no-brainer for every product team."
author="Bailey Pumfleet, Co-CEO @ Cal.com"
imgSrc={Bailey}
imgAlt="Cal.com co-founder Bailey Pumfleet speaks about how Formbricks in-app surveys feel like a native part of the UI of their product."
textSize="large"
/>
</div>
<SalesPageFeature
headline="Native look and feel, powered by open source"
subheadline="Formbricks is fully open source. Integrate it natively and keep engineers, designers and researchers happy. Formbricks is the most versatile open source in-product survey tool available."
imgSrc={Img2}
imgAlt="Indicator of GitHub stars for open source in-app survey product Formbricks which rund embedded surveys with a native look and feel."
imgLeft
/>
<SalesSteps steps={inAppSurveySteps} />
{inAppSurveyFeatures.map((feature) => {
return (
<SalesPageFeature
key={feature.headline}
headline={feature.headline}
subheadline={feature.subheadline}
imgSrc={feature.imgSrc}
imgAlt={feature.imgAlt}
imgLeft={feature.imgLeft}
/>
);
})}
<LogoBar />
{linkSurveyFeaturesPt2.map((feature) => {
return (
<SalesPageFeature
key={feature.headline}
headline={feature.headline}
subheadline={feature.subheadline}
imgSrc={feature.imgSrc}
imgAlt={feature.imgAlt}
imgLeft={feature.imgLeft}
/>
);
})}
<SalesTestimonial
quote="We're using Formbricks with Amplitude. The surveys fit in perfectly with our UI and the event-based targeting is super useful. The team loves it!"
author="Sachin Jain, CEO @ Requestly (YC W22)"
imgSrc={Sachin}
imgAlt="Sachin Jain, CEO @ Requestly"
textSize="base"
/>
<div className="space-y-12">
<HeadingCentered
heading={
<span>
In-app surveys <i>exactly</i> how you want them
</span>
}
teaser="All features backed in"
/>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{allFeaturesList.map((feature) => {
return (
<FeatureCard
key={feature.title}
title={feature.title}
text={feature.text}
Icon={feature.icon}
/>
);
})}
</div>
</div>
<SalesBreaker
headline="Embed surveys the right way - natively."
subheadline="You spent months crafting your product, dont ruin it with pop-ups."
/>
<div>
<HeadingCentered heading="Frequently asked questions" teaser="FAQ" />
<SeoFaq
faqs={FAQs}
headline="Targeted website surveys, open source. Like HotJar Ask but GDPR compliant."
description="Make the most out of your website traffic by asking pointed quesitons in online surveys."
datePublished="2024-03-12"
dateModified="2024-03-12"
/>
</div>
</LayoutLight>
);
}

View File

@@ -7,19 +7,19 @@ import Steps from "@/components/home/Steps";
import BestPractices from "@/components/shared/BestPractices";
import BreakerCTA from "@/components/shared/BreakerCTA";
import Layout from "@/components/shared/Layout";
import AnimationFallback from "@/public/animations/opensource-xm-platform-formbricks-fallback.png";
import HeroAnimation from "../components/home/HeroAnimation";
const IndexPage = () => (
<Layout
title="Formbricks | Privacy-first Experience Management"
description="Build qualitative user research into your product. Leverage Best practices to increase Product-Market Fit.">
<Hero />
<BestPractices />
<HeroAnimation fallbackImage={AnimationFallback} />
<Features />
<Highlights />
<ScrollToTopButton />
{/* <div className="block lg:hidden">
<GitHubSponsorship />
</div> */}
<div className="hidden lg:block">
<BreakerCTA
teaser="READY?"
@@ -29,9 +29,7 @@ const IndexPage = () => (
href="https://app.formbricks.com/auth/signup"
/>
</div>
<div className="pb-16">&nbsp;</div>
<Steps />
<BreakerCTA
teaser="Curious?"
headline="Give it a squeeze 🍋"
@@ -40,8 +38,8 @@ const IndexPage = () => (
href="https://app.formbricks.com/auth/signup"
inverted
/>
<Faq />
<BestPractices />
</Layout>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

View File

@@ -0,0 +1,474 @@
import FeatureCard from "@/components/salespage/FeatureCard";
import LayoutLight from "@/components/salespage/LayoutLight";
import LogoBar from "@/components/salespage/LogoBar";
import SalesBreaker from "@/components/salespage/SalesBreaker";
import SalesPageFeature from "@/components/salespage/SalesPageFeature";
import SalesPageHero from "@/components/salespage/SalesPageHero";
import SalesSteps from "@/components/salespage/SalesSteps";
import SalesTestimonial from "@/components/salespage/SalesTestimonial";
import HeadingCentered from "@/components/shared/HeadingCentered";
import SeoFaq from "@/components/shared/seo/SeoFaq";
import Jonathan from "@/images/clients/headshots/jonathan.png";
import Peer from "@/images/clients/headshots/peer.jpeg";
import {
BadgeCheck,
CalendarRange,
Check,
CreditCard,
Gauge,
Grid,
Hammer,
Headset,
Home,
Image,
List,
ListTodoIcon,
MessageSquareText,
MousePointerClick,
PencilIcon,
Star,
Upload,
} from "lucide-react";
import Img1 from "./1-open-source-typeform-alternative-free.png";
import Img2 from "./2-finally-good-open-source-forms-for-free.png";
import Img3 from "./3-free-online-form-builder-unlimited-forms-and-responses.png";
import Img4 from "./4-all-question-types-an-open-source-form-builder-needs.png";
import Img5 from "./5-all-question-types-an-open-source-form-builder-needs.png";
import Img6 from "./6-slack-zapier-hubspot-integration-for-open-source-form-builder-online.png";
import Img7 from "./7-embed-open-source-form-any-where-you-need-it.png";
import Img8 from "./8-pre-populate-typeform-open-source-alternative.png";
import Img9 from "./9-conditional-logic-jumps-for-free-open-source-online-form-builder.png";
import Img10 from "./10-multi-language-surveys-free-and-open-source.png";
const linkSurveySteps = [
{
id: "1",
name: "Register for free",
description: "Sign up for lifelong access to our open source at no cost.",
},
{
id: "2",
name: "Design your forms",
description: "Build your open source forms in minutes. Style it to match your brand.",
},
{
id: "3",
name: "AI insights",
description:
"Analyze responses right in Formbricks. Or pipe them to where you need them via one of our native integrations.",
},
];
const linkSurveyFeaturesPt1 = [
{
headline: "Free forms forever, unlimited",
subheadline:
"Get unlimited forms, responses, team members and workspaces in our open source web forms builder. In the Cloud we only charge for branding removal. Self-host with one click and remove the branding from all forms for free.",
imgSrc: Img3,
imgAlt: "Unlimited open source web forms displayed on various devices",
imgLeft: false,
},
{
headline: "The 'Do everything' forms",
subheadline:
"Our open source forms builder packs all question types you can think of. As a community-driven project, we build what our community needs: Your feedback makes Formbricks the best free web form builder out there.",
imgSrc: Img4,
imgAlt: "Versatile form types available in Formbricks open source form builder",
imgLeft: true,
},
{
headline: "100% on brand design",
subheadline:
"Create your open source web surveys in exactly the look & feel of your brand. Change colors or roundness, and set background images or animations to get exactly the look you want. Formbricks open source approach let's you customize your forms as much as you want.",
imgSrc: Img5,
imgAlt: "Customizable open source survey design matching brand identity",
imgLeft: false,
},
{
headline: "Slack, Zapier, Hubspot",
subheadline:
"Use native integrations into all of your tools. Keep your respondents data safe and your Privacy Policy short. The Formbricks community integrations make our web form builder the most versatile and extendable solution on the internet.",
imgSrc: Img6,
imgAlt: "Open source form builder integrating with Slack, Zapier, and Hubspot",
imgLeft: true,
},
{
headline: "Embed anywhere",
subheadline:
"Embed forms websites and in emails. Get your open source surveys in front of the right people effortlessly. You want to embed forms on a WordPress page? Our community is working on a free WordPress form plugin as we speak.",
imgSrc: Img7,
imgAlt: "Embeddable open source form on a website and email",
imgLeft: false,
},
];
const linkSurveyFeaturesPt2 = [
{
headline: "Pre-populate fields",
subheadline:
"Prefill fields with data you have already. Enrich your analysis by gathering all data points in one place. Versatile link prefilling lets you collect all data on our open source platform.",
imgSrc: Img8,
imgAlt: "Pre-populating fields in an open source form builder for enhanced data collection",
imgLeft: true,
},
{
headline: "Conditional logic",
subheadline:
"Jump questions based on previous answers for higher completion rate. Conditional logic lets you personalize the survey experience. Formbricks is the only open source form builder with comprehensive logic capabilities included.",
imgSrc: Img9,
imgAlt: "Custom survey paths using conditional logic in open source forms builder",
imgLeft: false,
},
{
headline: "Multi-language surveys",
subheadline:
"Run the same survey in several languages. Analyse all languages together or filter out feedback provided in specific languages. Not even Typeform has multi-language surveys, but our open source forms builder can handle it.",
imgSrc: Img10,
imgAlt: "Multi-language support in open source form builder, showcasing global survey capabilities",
imgLeft: true,
},
];
const allFeaturesList = [
{
title: "Close on response limit",
text: "Auto-close a survey after hitting e.g. 50 responses.",
icon: Check,
},
{
title: "Close on date",
text: "Auto-close a survey on a specific date.",
icon: Check,
},
{
title: "Redirect on completion",
text: "Redirect visitors after they completed your survey.",
icon: Check,
},
{
title: "Custom Survey closed message",
text: "Adjust how you tell respondents that your survey is closed.",
icon: Check,
},
{
title: "Single-use survey links",
text: "Generate links which can be used only once.",
icon: Check,
},
{
title: "Verify email before response",
text: "Ask for a valid email before allowing people to respond.",
icon: Check,
},
{
title: "PIN protected forms",
text: "Require a PIN before letting people respond.",
icon: Check,
},
{
title: "Hidden fields",
text: "Add hidden fields you can prepulate via URL.",
icon: Check,
},
{
title: "Multi-language surveys",
text: "Translate your surveys, analyze responses together.",
icon: Check,
},
{
title: "Email notifications",
text: "Receive emails when people respond to your surveys",
icon: Check,
},
{
title: "Partial submissions",
text: "Get all the insights down to the very first answer.",
icon: Check,
},
{
title: "Recall information",
text: "Pipe answers from previous quesitons for better questions.",
icon: Check,
},
{
title: "Add images to questions",
text: "Add an image or video to any question.",
icon: Check,
},
{
title: "Add videos to questions",
text: "Add an image or video to any question.",
icon: Hammer,
},
{
title: "Branded surveys",
text: "Add your logo at the header of you survey.",
icon: Hammer,
},
{
title: "Custom domains",
text: "Add your domain for higher brand recognition and trust.",
icon: Hammer,
},
{
title: "Calculations & quizzing",
text: "Redirect visitors after they completed your survey.",
icon: Hammer,
},
{
title: "Auto-email respondents",
text: "Auto-close a survey after hitting e.g. 50 responses.",
icon: Hammer,
},
{
title: "Dropdowns & Rankings",
text: "Auto-close a survey after hitting e.g. 50 responses.",
icon: Hammer,
},
{
title: "Question groups",
text: "Auto-close a survey on a specific date.",
icon: Hammer,
},
];
const allQuestionTypes = [
{
title: "Address field",
text: "Gather addresses",
icon: Home,
},
{
title: "File upload",
text: "Handle file uploads",
icon: Upload,
},
{
title: "Net Promoter Score",
text: "NPS",
icon: Gauge,
},
{
title: "Picture selection",
text: "Find out what resonates",
icon: Image,
},
{
title: "Date picker",
text: "Ask for specific dates",
icon: CalendarRange,
},
{
title: "Schedule a meeting",
text: "Powered by Cal.com",
icon: Headset,
},
{
title: "Open text",
text: "Free text field",
icon: MessageSquareText,
},
{
title: "Single select",
text: "Radio select field",
icon: List,
},
{
title: "Multi select",
text: "List of checkboxes",
icon: ListTodoIcon,
},
{
title: "Rating",
text: "Stars, smiles or numbers",
icon: Star,
},
{
title: "Call to action",
text: "Prompt users with a CTA",
icon: MousePointerClick,
},
{
title: "Consent",
text: "Ask for consent.",
icon: BadgeCheck,
},
{
title: "Signature (soon)",
text: "Powered by Documenso",
icon: PencilIcon,
},
{
title: "Collect payments (soon)",
text: "Powered by Stripe",
icon: CreditCard,
},
{
title: "Matrix question (soon)",
text: "Run scientific surveys",
icon: Grid,
},
];
const FAQs = [
{
question: "Is Formbricks truly a free and open source forms builder?",
answer:
"Absolutely, Formbricks is crafted for the community by the community. It's an open source project to build a free form builder for the web. Our open source license ensures its longevity for as long as the internet exists.",
},
{
question: "Can I self-host Formbricks, the open source web forms builder?",
answer:
"Certainly. You can easily self-host Formbricks with a single click using our Docker image. We aim to build the most developer-friendly open source forms builder ever.",
},
{
question: "How does Formbricks stack up against other JavaScript form builders?",
answer:
"We've conducted a detailed side-by-side comparison with other tools, including JavaScript form builders. You find the findings on our blog.",
},
{
question: "Is Formbricks GDPR- and CCPA-compliant?",
answer:
"Indeed. As a German-developed open source form builder, Formbricks operates under strict GDPR and CCPA compliance. Our Cloud is securely hosted in Frankfurt, Germany.",
},
{
question:
"I want to help ship this JavaScript form build, how can I contribute to this open source form project?",
answer:
"We're thrilled to have more hands on deck! Join our community on Discord and start contributing to Formbricks. This opensource forms project is carried by its community.",
},
{
question: "What sets Formbricks apart from other open source web form builders?",
answer:
"Unlike other open source forms builders, Formbricks is not a hobby project. We build this free web forms builder as a form of marketing for the wider Formbricks experience management platform. While this open source form builder will always be free, more advanced features for enterprise clients are licensed differently.",
},
{
question: "Can Formbricks handle the nitty-gritty of complex form logic and third-party integrations?",
answer:
"Absolutely, Formbricks is a beast when it comes to complex scenarios. Whether it's weaving through intricate form logic or knitting together various APIs and services, this platform is all set to empower developers with its robust capabilities as an open source forms builder.",
},
{
question: "How tight is the data security with Formbricks?",
answer:
"Security isnt an afterthought here; its front and center. As an open source form project, Formbricks is committed to keeping your data locked down with best-in-class security practices, regular updates, and compliance with global data protection laws. Rest easy knowing your form data is in safe hands.",
},
{
question: "What kind of customization can you actually do with Formbricks?",
answer:
"The sky's the limit when it comes to tweaking your forms. Formbricks hands you the keys to the kingdom with full code access, allowing for total customization. Whether you want to adjust the color scheme or overhaul the entire form layout, its all doable if you know how to code!",
},
];
export default function LinkSurveyPage() {
return (
<LayoutLight
title="Finally, a Good Open Source Form Builder"
description="Build free online forms with our open source form builder. Build online questionnaiers like with Google Forms, Microsoft Forms or Typeform for free! Get started now.">
<SalesPageHero
headline="The Open Source Form Builder that does it all"
subheadline="Create beautiful online forms for free all open source. Unlimited surveys, unlimited responses. Easily self-hostable."
imgSrc={Img1}
imgAlt="Free and open source Typeform alternative. Build forms online for free while keeping all data secure. Self-hosting for online form builder available for free."
/>
<LogoBar />
<SalesPageFeature
headline="A worthy open source Typeform alternative"
subheadline="Everyone needs online forms and yet, there was no good open source builder for them. Thats why we - together with our community - have set out to build the best open source forms builder mankind has seen."
imgSrc={Img2}
imgAlt="GitHub indicator for open source forms builder project to build free and open source online forms."
imgLeft
/>
<SalesSteps steps={linkSurveySteps} />
{linkSurveyFeaturesPt1.map((feature) => {
return (
<SalesPageFeature
key={feature.headline}
headline={feature.headline}
subheadline={feature.subheadline}
imgSrc={feature.imgSrc}
imgAlt={feature.imgAlt}
imgLeft={feature.imgLeft}
/>
);
})}
<SalesTestimonial
quote="Finally a great open source survey builder! Formbricks proves that open source surveys can be both powerful and user-friendly."
author="Jonathan Reimer, CEO @ crowd.dev"
imgSrc={Jonathan}
imgAlt="Jonathan Reimer, CEO @ crowd.dev"
textSize="large"
/>
{linkSurveyFeaturesPt2.map((feature) => {
return (
<SalesPageFeature
key={feature.headline}
headline={feature.headline}
subheadline={feature.subheadline}
imgSrc={feature.imgSrc}
imgAlt={feature.imgAlt}
imgLeft={feature.imgLeft}
/>
);
})}
<SalesBreaker
headline="Try THE open source form builder 💪"
subheadline="Convinced that Formbricks is a good open source Typeform alternative? Try it now!"
/>
<div className="">
<HeadingCentered
heading="All form builder features"
teaser="Build open source forms like never before"
/>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{allFeaturesList.map((feature) => {
return (
<FeatureCard
key={feature.title}
title={feature.title}
text={feature.text}
Icon={feature.icon}
/>
);
})}
</div>
</div>
<SalesTestimonial
quote="I've been looking for a solid open source form builder for a while. Super happy to see Formbricks building it!"
author="Peer Richelsen, CEO @ cal.com"
imgSrc={Peer}
imgAlt="Peer Richelsen, Co-Founder and CEO of Cal.com"
textSize="large"
/>
<div className="">
<HeadingCentered heading="All question types included" teaser="A complete open source form builder" />
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{allQuestionTypes.map((feature) => {
return (
<FeatureCard
key={feature.title}
title={feature.title}
text={feature.text}
Icon={feature.icon}
/>
);
})}
</div>
</div>
<div>
<HeadingCentered heading="Frequently asked questions" teaser="FAQ" />
<SeoFaq
faqs={FAQs}
headline="Open Source Typeform Alternative"
description="Build Online Forms for Free with this Open Source Typeform Alternative"
datePublished="2024-03-12"
dateModified="2024-03-12"
/>
</div>
<SalesBreaker
headline="What are you waiting for? 🤓"
subheadline="Create your first open source form, it's free!"
/>
</LayoutLight>
);
}

View File

@@ -1,8 +1,9 @@
import LayoutMdx from "@/components/shared/LayoutMdx";
import Image from "next/image";
import { Callout } from "@/components/shared/Callout";
import HeroAnimation from "@/components/shared/HeroAnimation.tsx";
import LayoutMdx from "@/components/shared/LayoutMdx";
import MdxCTA from "@/components/shared/MdxCTA.tsx";
import Image from "next/image";
import Link from "next/link";
import HeaderImage from "/images/SEO/Formspree open source alternative vs Formbricks FormHQ comparison post for form backend as a service.png";
export const meta = {
@@ -61,22 +62,20 @@ Here a detailed overview of both free plans. Its pretty obvious:
| Forms | unlimited | unlimited |
| Submission Archive | unlimited | 30 days |
| Linked Emails | unlimited | 2 |
| Auto Responder | ⚙️ | ❌ |
| Auto Responder | | ❌ |
| Multiple Email Recipients | ✅ | ❌ |
| Custom Email Templates | ⚙️ | ❌ |
| Custom Email Templates | | ❌ |
| File Uploads | 10MB | ❌ |
| Submission Export | CSV | ❌ |
| Airtable Integration | ⚙️ | ❌ |
| Google Sheets Integration | ⚙️ | ❌ |
| Zapier Integration | ⚙️ | ❌ |
| Submission Export | CSV, Excel | ❌ |
| Airtable Integration | | ❌ |
| Google Sheets Integration | | ❌ |
| Zapier Integration | | ❌ |
| Airtable Integration | ✅ | ❌ |
| Notion Integration | ✅ | ❌ |
| Slack Integration | ✅ | ❌ |
| API Access | ✅ | ❌ |
| Workflows | ✅ | ❌ |
| Self-hosting | ✅ | ❌ |
<Callout title="In development" type="warning">
The Formbricks HQ is still in development and not yet available. Follow us on
[Twitter](https://twitter.com/formbricks) to stay uptodate on the release!
</Callout>
### Functionality
@@ -87,25 +86,24 @@ Formspree offers a wide range of functionality, a lot of which is only accessibl
| Forward Form Data via Email | ✅ | ✅ |
| Multiple To Emails | ✅ | from 18$ / mo |
| Webhooks | ✅ | ❌ |
| 3rd Party Integrations | ⚙️ | from 8$ / mo |
| File Uploads | ⚙️ | from 8$ / mo |
| 3rd Party Integrations | | from 8$ / mo |
| File Uploads | | from 8$ / mo |
| Export submissions | ✅ | from 8$ / mo |
| Search submissions | ⚙️ | from 18$ / mo |
| reCaptcha | ⚙️ | ✅ |
| Restrict to domain | ⚙️ | ✅ |
| Custom Redirect | ⚙️ | from 18$ / mo |
| Search submissions | | from 18$ / mo |
| reCaptcha | | ✅ |
| Restrict to domain | | ✅ |
| Custom Redirect | | from 18$ / mo |
| Email Auto Response | ⚙️ | from 18$ / mo |
| Custom Email Domain | ⚙️ | from 85$ / mo |
| Automated Workflows | ⚙️ | from 85$ / mo |
| Team Functionality | ⚙️ | from 8$/ mo |
| Team Functionality | | from 8$/ mo |
As of today, Formspree is the much more comprehensive solution.
As of today, Formspree is the more comprehensive solution.
### Data Privacy: Can I legally use Formspree in the EU?
Data ownership is not only important since GDPR, CCPA, HIPAA and other data privacy regulation. But since the Privacy Shield has been removed, many European companies cannot use US-american Software-as-a-Service offerings legally anymore. How do Formbricks and Formspree compare on data privacy?
### Formbricks
### Formbricks, the open source Formspree alternative
✅ Offers self-hosting, very easy compliance with all privacy regulation
@@ -121,6 +119,8 @@ Data ownership is not only important since GDPR, CCPA, HIPAA and other data priv
As of now, Formspree offers a more comprehensive set of features. However, for the really useful functionality you have to pay $18 / month. Formbricks on the other hand is the new kid on the blog with many cool features in development. The option to easily self-host makes the difference for many engineers.
<MdxCTA />
<Callout title="Wanna find out more?" type="note">
Did you know that Formbricks can also run <Link href="/website-survey">targeted surveys on public websites?</Link> And <Link href="/in-app-survey">hyper-targeted surveys in apps?</Link> Find out more!
</Callout>
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;

Some files were not shown because too many files have changed in this diff Show More