Compare commits
30 Commits
ReviewBot/
...
ReviewBot/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdcd1337b9 | ||
|
|
74a21f03f2 | ||
|
|
c148e3bd74 | ||
|
|
f27f55a655 | ||
|
|
d7237b81da | ||
|
|
4abe080db2 | ||
|
|
6c84850a7e | ||
|
|
63708ec92b | ||
|
|
8b5328aa74 | ||
|
|
5c9e59b136 | ||
|
|
7123a620c2 | ||
|
|
6609b57084 | ||
|
|
9123e3c866 | ||
|
|
92d0c6bce6 | ||
|
|
ae3f1885c2 | ||
|
|
3ca6ec8b56 | ||
|
|
83a46d7313 | ||
|
|
b55b37b874 | ||
|
|
c76bcecca0 | ||
|
|
3776397468 | ||
|
|
4704c4a077 | ||
|
|
034ca1d639 | ||
|
|
e5862a2064 | ||
|
|
0332a2efe3 | ||
|
|
be8e461f55 | ||
|
|
722ee68b4c | ||
|
|
e4078a3307 | ||
|
|
907a9dc563 | ||
|
|
f6df94081d | ||
|
|
2436192995 |
@@ -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
|
||||
|
||||
8
.github/workflows/ecs-deployment.yml
vendored
@@ -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
|
||||
|
||||
7
.github/workflows/kamal.yml
vendored
@@ -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:
|
||||
|
||||
3
.github/workflows/pr.yml
vendored
@@ -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
|
||||
|
||||
14
.github/workflows/release-docker-github.yml
vendored
@@ -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
|
||||
|
||||
14
.github/workflows/release-docker.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/test.yml
vendored
@@ -3,7 +3,7 @@ on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
build:
|
||||
name: Tests
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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. Let’s 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. Let’s 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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
**Can’t figure it out?**: [Join our Discord!](https://formbricks.com/discord)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
501
apps/formbricks-com/components/dummyUI/types.ts
Normal 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>;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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'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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'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'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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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. Here’s 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 <script> tag to your HTML head - that’s 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 <script> tag to your HTML head - that’s 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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
19
apps/formbricks-com/components/salespage/FeatureCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
101
apps/formbricks-com/components/salespage/HeaderLight.tsx
Normal 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'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's free!
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
37
apps/formbricks-com/components/salespage/LogoBar.tsx
Normal 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 world’s 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>
|
||||
);
|
||||
}
|
||||
25
apps/formbricks-com/components/salespage/SalesBreaker.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
20
apps/formbricks-com/components/salespage/SalesCTA.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
28
apps/formbricks-com/components/salespage/SalesPageHero.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
32
apps/formbricks-com/components/salespage/SalesSteps.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 © {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 © {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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
79
apps/formbricks-com/components/shared/seo/SeoFaq.tsx
Normal 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;
|
||||
|
After Width: | Height: | Size: 399 KiB |
|
After Width: | Height: | Size: 60 KiB |
BIN
apps/formbricks-com/images/clients/headshots/bailey.jpeg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
apps/formbricks-com/images/clients/headshots/jonathan.png
Normal file
|
After Width: | Height: | Size: 443 KiB |
BIN
apps/formbricks-com/images/clients/headshots/marius.jpeg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
apps/formbricks-com/images/clients/headshots/peer.jpeg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
apps/formbricks-com/images/clients/headshots/ram.jpeg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
apps/formbricks-com/images/clients/headshots/sachin.jpeg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
apps/formbricks-com/images/clients/headshots/vishnu.jpeg
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
apps/formbricks-com/images/placeholder.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
@@ -34,6 +34,11 @@ const nextConfig = {
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/demo",
|
||||
destination: "/",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/discord",
|
||||
destination: "https://discord.gg/3YFcABF2Ts",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 today’s 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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 we’ll 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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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 that’s 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.",
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 512 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 571 KiB |
|
After Width: | Height: | Size: 974 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 650 KiB |
|
After Width: | Height: | Size: 930 KiB |
312
apps/formbricks-com/pages/in-app-survey/index.tsx
Normal 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, don’t 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>
|
||||
);
|
||||
}
|
||||
@@ -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"> </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>
|
||||
);
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 492 KiB |
|
After Width: | Height: | Size: 953 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 974 KiB |
|
After Width: | Height: | Size: 904 KiB |
|
After Width: | Height: | Size: 723 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 991 KiB |
474
apps/formbricks-com/pages/open-source-form-builder/index.tsx
Normal 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 isn’t an afterthought here; it’s 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, it’s 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. That’s 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>
|
||||
);
|
||||
}
|
||||
@@ -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. It’s 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>;
|
||||
|
||||