Compare commits
23 Commits
release-v3
...
fix/enviro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f803022d7c | ||
|
|
22e8a137ef | ||
|
|
a9fe05d64a | ||
|
|
f476693f0d | ||
|
|
5219065b8e | ||
|
|
cb8497229d | ||
|
|
25b8920d20 | ||
|
|
9203db88ab | ||
|
|
36378e9c23 | ||
|
|
9c33e77755 | ||
|
|
88cb4c742f | ||
|
|
475cce8253 | ||
|
|
a86c1738d1 | ||
|
|
96a4d02c80 | ||
|
|
bb6df783ab | ||
|
|
26cca5c2f8 | ||
|
|
7e3dd7d624 | ||
|
|
db9a53f923 | ||
|
|
92ae4786f0 | ||
|
|
b35cf14d32 | ||
|
|
14374b55d2 | ||
|
|
5a919018c5 | ||
|
|
6ac73d3f25 |
39
.github/workflows/sonarqube.yml
vendored
@@ -1,11 +1,11 @@
|
|||||||
name: SonarQube
|
name: SonarQube
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
# push:
|
push:
|
||||||
# branches:
|
branches:
|
||||||
# - main
|
- main
|
||||||
# pull_request:
|
pull_request:
|
||||||
# types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
jobs:
|
jobs:
|
||||||
@@ -16,6 +16,35 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||||
|
|
||||||
|
- name: Setup Node.js 20.x
|
||||||
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
|
||||||
|
with:
|
||||||
|
node-version: 20.x
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --config.platform=linux --config.architecture=x64
|
||||||
|
|
||||||
|
- name: create .env
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Generate Random ENCRYPTION_KEY, CRON_SECRET & NEXTAUTH_SECRET and fill in .env
|
||||||
|
run: |
|
||||||
|
RANDOM_KEY=$(openssl rand -hex 32)
|
||||||
|
sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${RANDOM_KEY}/" .env
|
||||||
|
sed -i "s/CRON_SECRET=.*/CRON_SECRET=${RANDOM_KEY}/" .env
|
||||||
|
sed -i "s/NEXTAUTH_SECRET=.*/NEXTAUTH_SECRET=${RANDOM_KEY}/" .env
|
||||||
|
|
||||||
|
- name: Run tests with coverage
|
||||||
|
run: |
|
||||||
|
cd apps/web
|
||||||
|
pnpm test:coverage
|
||||||
|
cd ../../
|
||||||
|
# The Vitest coverage config is in your vite.config.mts
|
||||||
|
|
||||||
- name: SonarQube Scan
|
- name: SonarQube Scan
|
||||||
uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203
|
uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203
|
||||||
env:
|
env:
|
||||||
|
|||||||
39
.github/workflows/tolgee-missing-key-check.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Check Missing Translations
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-missing-translations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
- name: Install Tolgee CLI
|
||||||
|
run: npm install -g @tolgee/cli
|
||||||
|
|
||||||
|
- name: Compare Tolgee Keys
|
||||||
|
id: compare
|
||||||
|
run: |
|
||||||
|
tolgee compare --api-key ${{ secrets.TOLGEE_API_KEY }} > compare_output.txt
|
||||||
|
cat compare_output.txt
|
||||||
|
|
||||||
|
- name: Check for Missing Translations
|
||||||
|
run: |
|
||||||
|
if grep -q "new key found" compare_output.txt; then
|
||||||
|
echo "New keys found that may require translations:"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "No new keys found."
|
||||||
|
fi
|
||||||
42
.github/workflows/tolgee.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Tolgee Tagging on PR Merge
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tag-production-keys:
|
||||||
|
name: Tag Production Keys
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18 # Ensure compatibility with your project
|
||||||
|
|
||||||
|
- name: Install Tolgee CLI
|
||||||
|
run: npm install -g @tolgee/cli
|
||||||
|
|
||||||
|
- name: Tag Production Keys
|
||||||
|
run: |
|
||||||
|
BRANCH_NAME=${GITHUB_REF##*/}
|
||||||
|
npx tolgee tag \
|
||||||
|
--api-key ${{ secrets.TOLGEE_API_KEY }} \
|
||||||
|
--filter-extracted \
|
||||||
|
--filter-tag "draft: ${BRANCH_NAME}" \
|
||||||
|
--tag production \
|
||||||
|
--untag "draft: ${BRANCH_NAME}"
|
||||||
|
|
||||||
|
- name: Tag Deprecated Keys
|
||||||
|
run: |
|
||||||
|
npx tolgee tag \
|
||||||
|
--api-key ${{ secrets.TOLGEE_API_KEY }} \
|
||||||
|
--filter-not-extracted --filter-tag production \
|
||||||
|
--tag deprecated --untag production
|
||||||
1
.gitignore
vendored
@@ -58,4 +58,5 @@ packages/lib/uploads
|
|||||||
# js compiled assets
|
# js compiled assets
|
||||||
apps/web/public/js
|
apps/web/public/js
|
||||||
|
|
||||||
|
|
||||||
packages/database/migrations
|
packages/database/migrations
|
||||||
1
.husky/post-checkout
Normal file
@@ -0,0 +1 @@
|
|||||||
|
echo "{\"branchName\": \"$(git rev-parse --abbrev-ref HEAD)\"}" > ../branch.json
|
||||||
1
.husky/post-commit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
echo "{\"branchName\": \"$(git rev-parse --abbrev-ref HEAD)\"}" > ../branch.json
|
||||||
@@ -1 +1,5 @@
|
|||||||
pnpm lint-staged
|
pnpm lint-staged
|
||||||
|
pnpm tolgee-pull || true
|
||||||
|
echo "{\"branchName\": \"main\"}" > ../branch.json
|
||||||
|
git add branch.json packages/lib/messages/*.json
|
||||||
|
|
||||||
|
|||||||
31
.tolgeerc.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.tolgee.io/cli-schema.json",
|
||||||
|
"format": "JSON_TOLGEE",
|
||||||
|
"patterns": ["./apps/web/**/*.ts?(x)"],
|
||||||
|
"projectId": 10304,
|
||||||
|
"pull": {
|
||||||
|
"path": "./packages/lib/messages"
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"language": "en-US",
|
||||||
|
"path": "./packages/lib/messages/en-US.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "de-DE",
|
||||||
|
"path": "./packages/lib/messages/de-DE.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "fr-FR",
|
||||||
|
"path": "./packages/lib/messages/fr-FR.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "pt-BR",
|
||||||
|
"path": "./packages/lib/messages/pt-BR.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"forceMode": "OVERRIDE"
|
||||||
|
},
|
||||||
|
"strictNamespace": false
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
EXPO_PUBLIC_API_HOST=http://192.168.178.20:3000
|
EXPO_PUBLIC_APP_URL=http://192.168.0.197:3000
|
||||||
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clzr04nkd000bcdl110j0ijyq
|
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=cm5p0cs7r000819182b32j0a1
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
},
|
},
|
||||||
"jsEngine": "hermes",
|
"jsEngine": "hermes",
|
||||||
"name": "react-native-demo",
|
"name": "react-native-demo",
|
||||||
|
"newArchEnabled": true,
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"slug": "react-native-demo",
|
"slug": "react-native-demo",
|
||||||
"splash": {
|
"splash": {
|
||||||
|
|||||||
@@ -13,16 +13,17 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formbricks/js": "workspace:*",
|
"@formbricks/js": "workspace:*",
|
||||||
"@formbricks/react-native": "workspace:*",
|
"@formbricks/react-native": "workspace:*",
|
||||||
"expo": "52.0.18",
|
"@react-native-async-storage/async-storage": "2.1.0",
|
||||||
"expo-status-bar": "2.0.0",
|
"expo": "52.0.28",
|
||||||
|
"expo-status-bar": "2.0.1",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-native": "0.76.5",
|
"react-native": "0.76.6",
|
||||||
"react-native-webview": "13.12.5"
|
"react-native-webview": "13.12.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.26.0",
|
"@babel/core": "7.26.0",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "18.3.18",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { StatusBar } from "expo-status-bar";
|
import { StatusBar } from "expo-status-bar";
|
||||||
import React, { type JSX } from "react";
|
import React, { type JSX } from "react";
|
||||||
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
|
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
|
||||||
import Formbricks, { track } from "@formbricks/react-native";
|
import Formbricks, {
|
||||||
|
logout,
|
||||||
|
setAttribute,
|
||||||
|
setAttributes,
|
||||||
|
setLanguage,
|
||||||
|
setUserId,
|
||||||
|
track,
|
||||||
|
} from "@formbricks/react-native";
|
||||||
|
|
||||||
LogBox.ignoreAllLogs();
|
LogBox.ignoreAllLogs();
|
||||||
|
|
||||||
@@ -10,35 +17,92 @@ export default function App(): JSX.Element {
|
|||||||
throw new Error("EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID is required");
|
throw new Error("EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.EXPO_PUBLIC_API_HOST) {
|
if (!process.env.EXPO_PUBLIC_APP_URL) {
|
||||||
throw new Error("EXPO_PUBLIC_API_HOST is required");
|
throw new Error("EXPO_PUBLIC_APP_URL is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
|
||||||
environmentId: process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID as string,
|
|
||||||
apiHost: process.env.EXPO_PUBLIC_API_HOST as string,
|
|
||||||
userId: "random-user-id",
|
|
||||||
attributes: {
|
|
||||||
language: "en",
|
|
||||||
testAttr: "attr-test",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text>Formbricks React Native SDK Demo</Text>
|
<Text>Formbricks React Native SDK Demo</Text>
|
||||||
|
|
||||||
<Button
|
<View
|
||||||
title="Trigger Code Action"
|
style={{
|
||||||
onPress={() => {
|
display: "flex",
|
||||||
track("code").catch((error: unknown) => {
|
flexDirection: "column",
|
||||||
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
gap: 10,
|
||||||
console.error("Error tracking event:", error);
|
}}>
|
||||||
});
|
<Button
|
||||||
}}
|
title="Trigger Code Action"
|
||||||
/>
|
onPress={() => {
|
||||||
|
track("code").catch((error: unknown) => {
|
||||||
|
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||||
|
console.error("Error tracking event:", error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Set User Id"
|
||||||
|
onPress={() => {
|
||||||
|
setUserId("random-user-id").catch((error: unknown) => {
|
||||||
|
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||||
|
console.error("Error setting user id:", error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Set User Attributess (multiple)"
|
||||||
|
onPress={() => {
|
||||||
|
setAttributes({
|
||||||
|
testAttr: "attr-test",
|
||||||
|
testAttr2: "attr-test-2",
|
||||||
|
testAttr3: "attr-test-3",
|
||||||
|
testAttr4: "attr-test-4",
|
||||||
|
}).catch((error: unknown) => {
|
||||||
|
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||||
|
console.error("Error setting user attributes:", error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Set User Attributes (single)"
|
||||||
|
onPress={() => {
|
||||||
|
setAttribute("testSingleAttr", "testSingleAttr").catch((error: unknown) => {
|
||||||
|
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||||
|
console.error("Error setting user attributes:", error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Logout"
|
||||||
|
onPress={() => {
|
||||||
|
logout().catch((error: unknown) => {
|
||||||
|
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||||
|
console.error("Error logging out:", error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title="Set Language (de)"
|
||||||
|
onPress={() => {
|
||||||
|
setLanguage("de").catch((error: unknown) => {
|
||||||
|
// eslint-disable-next-line no-console -- logging is allowed in demo apps
|
||||||
|
console.error("Error setting language:", error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
<Formbricks initConfig={config} />
|
|
||||||
|
<Formbricks
|
||||||
|
appUrl={process.env.EXPO_PUBLIC_APP_URL as string}
|
||||||
|
environmentId={process.env.EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID as string}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/button";
|
||||||
|
import { LoadingSpinner } from "@/components/icons/loading-spinner";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { RedocStandalone } from "redoc";
|
import { RedocStandalone } from "redoc";
|
||||||
import { LoadingSpinner } from "@/components/icons/loading-spinner";
|
|
||||||
import { Button } from "@/components/button";
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
export function ApiDocs() {
|
export function ApiDocs() {
|
||||||
@@ -61,7 +61,13 @@ export function ApiDocs() {
|
|||||||
<Button href="/developer-docs/rest-api" arrow="left" className="mb-4 mt-8">
|
<Button href="/developer-docs/rest-api" arrow="left" className="mb-4 mt-8">
|
||||||
Back to docs
|
Back to docs
|
||||||
</Button>
|
</Button>
|
||||||
<RedocStandalone specUrl="/docs/openapi.yaml" onLoaded={() => { setLoading(false); }} options={redocTheme} />
|
<RedocStandalone
|
||||||
|
specUrl="/docs/openapi.yaml"
|
||||||
|
onLoaded={() => {
|
||||||
|
setLoading(false);
|
||||||
|
}}
|
||||||
|
options={redocTheme}
|
||||||
|
/>
|
||||||
{loading ? <LoadingSpinner /> : null}
|
{loading ? <LoadingSpinner /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Image from "next/image";
|
|
||||||
import { Button } from "@/components/button";
|
import { Button } from "@/components/button";
|
||||||
import logoHtml from "@/images/frameworks/html5.svg";
|
import logoHtml from "@/images/frameworks/html5.svg";
|
||||||
import logoNextjs from "@/images/frameworks/nextjs.svg";
|
import logoNextjs from "@/images/frameworks/nextjs.svg";
|
||||||
import logoReactJs from "@/images/frameworks/reactjs.svg";
|
import logoReactJs from "@/images/frameworks/reactjs.svg";
|
||||||
import logoVueJs from "@/images/frameworks/vuejs.svg";
|
import logoVueJs from "@/images/frameworks/vuejs.svg";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
const libraries = [
|
const libraries = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -357,17 +357,11 @@ Now, update your App.js/App.tsx file to initialize Formbricks:
|
|||||||
// other imports
|
// other imports
|
||||||
import Formbricks from "@formbricks/react-native";
|
import Formbricks from "@formbricks/react-native";
|
||||||
|
|
||||||
const config = {
|
|
||||||
environmentId: "<environment-id>",
|
|
||||||
apiHost: "<api-host>",
|
|
||||||
userId: "<user-id>", // optional
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Your app content */}
|
{/* Your app content */}
|
||||||
<Formbricks initConfig={config} />
|
<Formbricks appUrl="https://app.formbricks.com" environmentId="your-environment-id" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -381,7 +375,7 @@ export default function App() {
|
|||||||
<Property name="environment-id" type="string">
|
<Property name="environment-id" type="string">
|
||||||
Formbricks Environment ID.
|
Formbricks Environment ID.
|
||||||
</Property>
|
</Property>
|
||||||
<Property name="api-host" type="string">
|
<Property name="app-url" type="string">
|
||||||
URL of the hosted Formbricks instance.
|
URL of the hosted Formbricks instance.
|
||||||
</Property>
|
</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 154 KiB |
155
apps/docs/app/developer-docs/integrations/activepieces/page.mdx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import { MdxImage } from "@/components/mdx-image";
|
||||||
|
|
||||||
|
import CreateConnection from "./create-connection.webp";
|
||||||
|
import CreateNewFlow from "./create-new-flow.webp";
|
||||||
|
import DuplicateSurvey from "./duplicate-survey.webp";
|
||||||
|
import ConfigureConnection from "./configure-connection.webp";
|
||||||
|
import SearchFormbricks from "./search-formbricks.webp";
|
||||||
|
import SelectSurvey from "./select-survey.webp";
|
||||||
|
import TestTrigger from "./test-trigger.webp";
|
||||||
|
import UpdateQuestionId from "./update-question-id.webp";
|
||||||
|
import SuccessResponse from "./success-response.webp";
|
||||||
|
import SelectGSSheet from "./select-gs-sheet.webp";
|
||||||
|
import SelectGoogleSheet from "./select-google-sheet.webp";
|
||||||
|
import MatchData from "./match-data.webp";
|
||||||
|
import Result from "./result.webp";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "Formbricks Integration with Activepieces: A Step-by-Step Guide",
|
||||||
|
description:
|
||||||
|
"Learn how to integrate Formbricks with Activepieces. Follow our detailed guide to set up workflows, connect with various apps, and send your survey data to multiple platforms.",
|
||||||
|
};
|
||||||
|
|
||||||
|
#### Integrations
|
||||||
|
|
||||||
|
# Activepieces Setup
|
||||||
|
|
||||||
|
Activepieces is a versatile tool to automate workflows between Formbricks and numerous applications. Here's how to set it up.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Ensure your survey is finalized before setting up Activepieces. Any changes in the survey will require additional adjustments in the workflow.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## Step 1: Setup your survey incl. `questionId` for every question
|
||||||
|
|
||||||
|
Set up the `questionId`s of your survey questions before publishing.
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={UpdateQuestionId}
|
||||||
|
alt="Update Question ID"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
_Update the Question ID field in every question card under Advanced Settings._
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Already published? Duplicate survey You can only update the questionId before publishing the survey. If already published, simply duplicate it.
|
||||||
|
<MdxImage
|
||||||
|
src={DuplicateSurvey}
|
||||||
|
alt="Duplicate Survey"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## Step 2: Setup Activepieces
|
||||||
|
|
||||||
|
Visit [Activepieces](https://activepieces.com) to start a new Flow.
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={CreateNewFlow}
|
||||||
|
alt="Create New Flow"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
Search for `Formbricks` and choose the event to trigger the flow, we will choose `Response Finished` here
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={SearchFormbricks}
|
||||||
|
alt="Search Formbricks"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Step 3: Connect Formbricks with Activepieces
|
||||||
|
|
||||||
|
Click on `Create connection`:
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={CreateConnection}
|
||||||
|
alt="Create Connection"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
Enter the Formbricks API Host and API Key. API Host is by default set to https://app.formbricks.com but can be modified for self-hosting instances. Learn how to get an API Key from the [API Key tutorial](/additional-features/api#how-to-generate-an-api-key).
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={ConfigureConnection}
|
||||||
|
alt="Configure Connection"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Step 4: Select Survey
|
||||||
|
|
||||||
|
Choose from your created surveys:
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={SelectSurvey}
|
||||||
|
alt="Select Survey"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Step 5: Send a test response
|
||||||
|
|
||||||
|
You need a test response for Activepieces setup. Click on Test trigger and submit a test response in the connected Formbricks survey to see the data in Activepieces.
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={TestTrigger}
|
||||||
|
alt="Test Trigger"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
If the test response is successful, you will see the data in Activepieces.
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={SuccessResponse}
|
||||||
|
alt="Success Response"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Step 6: Set up Google Sheet
|
||||||
|
|
||||||
|
Decide on the desired action for the data. Here, we'll send submissions to a Google Sheet. Add Google sheet step to your flow and configure it as follows:
|
||||||
|
Choose "Add a Row" for the action. Authenticate with Google and select the spreadsheet you want to add the data to.
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={SelectGSSheet}
|
||||||
|
alt="Select Google sheet"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
Specify the fields you want to add to the spreadsheet.
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={MatchData}
|
||||||
|
alt="Match Data"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
A new row gets added to the spreadsheet for every response:
|
||||||
|
|
||||||
|
<MdxImage
|
||||||
|
src={Result}
|
||||||
|
alt="Result"
|
||||||
|
quality="100"
|
||||||
|
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||||
|
/>
|
||||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -18,25 +18,27 @@ const jost = Jost({ subsets: ["latin"] });
|
|||||||
|
|
||||||
async function RootLayout({ children }: { children: React.ReactNode }) {
|
async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
const pages = await glob("**/*.mdx", { cwd: "src/app" });
|
const pages = await glob("**/*.mdx", { cwd: "src/app" });
|
||||||
const allSectionsEntries: [string, Section[]][] = (await Promise.all(
|
const allSectionsEntries: [string, Section[]][] = await Promise.all(
|
||||||
pages.map(async (filename) => [
|
pages.map(async (filename) => [
|
||||||
`/${filename.replace(/(?:^|\/)page\.mdx$/, "")}`,
|
`/${filename.replace(/(?:^|\/)page\.mdx$/, "")}`,
|
||||||
(await import(`./${filename}`) as { sections: Section[] }).sections,
|
((await import(`./${filename}`)) as { sections: Section[] }).sections,
|
||||||
])
|
])
|
||||||
));
|
);
|
||||||
const allSections = Object.fromEntries(allSectionsEntries);
|
const allSections = Object.fromEntries(allSectionsEntries);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className="h-full" suppressHydrationWarning>
|
<html lang="en" className="h-full" suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
{process.env.NEXT_PUBLIC_LAYER_API_KEY ? <Script
|
{process.env.NEXT_PUBLIC_LAYER_API_KEY ? (
|
||||||
strategy="afterInteractive"
|
<Script
|
||||||
src="https://storage.googleapis.com/generic-assets/buildwithlayer-widget-4.js"
|
strategy="afterInteractive"
|
||||||
primary-color="#00C4B8"
|
src="https://storage.googleapis.com/generic-assets/buildwithlayer-widget-4.js"
|
||||||
api-key={process.env.NEXT_PUBLIC_LAYER_API_KEY}
|
primary-color="#00C4B8"
|
||||||
walkthrough-enabled="false"
|
api-key={process.env.NEXT_PUBLIC_LAYER_API_KEY}
|
||||||
design-style="copilot"
|
walkthrough-enabled="false"
|
||||||
/> : null}
|
design-style="copilot"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</head>
|
</head>
|
||||||
<body className={`flex min-h-full bg-white antialiased dark:bg-zinc-900 ${jost.className}`}>
|
<body className={`flex min-h-full bg-white antialiased dark:bg-zinc-900 ${jost.className}`}>
|
||||||
<Providers>
|
<Providers>
|
||||||
|
|||||||
158
apps/docs/app/self-hosting/kubernetes/page.mdx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
export const metadata = {
|
||||||
|
title: "Kubernetes Deployment",
|
||||||
|
description: "Deploy Formbricks on a Kubernetes cluster using Helm.",
|
||||||
|
};
|
||||||
|
|
||||||
|
# Deploying Formbricks on Kubernetes
|
||||||
|
|
||||||
|
This guide explains how to deploy Formbricks on a **Kubernetes cluster** using **Helm**. It assumes that:
|
||||||
|
- You **already have a Kubernetes cluster** running (e.g., DigitalOcean, GKE, AWS, Minikube).
|
||||||
|
- An **Ingress controller** (e.g., Traefik, Nginx) is configured.
|
||||||
|
- You have **Helm installed** on your local machine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **Step 1: Install Formbricks with Helm**
|
||||||
|
|
||||||
|
### **1️⃣ Clone the Formbricks Helm Chart**
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/formbricks/formbricks.git
|
||||||
|
cd formbricks/helm-chart
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2️⃣ Deploy Formbricks**
|
||||||
|
```sh
|
||||||
|
helm install my-formbricks ./ \
|
||||||
|
--namespace formbricks \
|
||||||
|
--create-namespace \
|
||||||
|
--set replicaCount=2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 🎯 **Step 2: Verify and Access Formbricks**
|
||||||
|
### **Check the Running Services**
|
||||||
|
```sh
|
||||||
|
kubectl get pods -n formbricks
|
||||||
|
kubectl get svc -n formbricks
|
||||||
|
kubectl get ingress -n formbricks
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Access Formbricks**
|
||||||
|
- If running locally with **Minikube**:
|
||||||
|
```sh
|
||||||
|
minikube service my-formbricks -n formbricks
|
||||||
|
```
|
||||||
|
- If deployed on a **cloud cluster**, visit:
|
||||||
|
```
|
||||||
|
https://formbricks.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Upgrading Formbricks
|
||||||
|
|
||||||
|
This section provides guidance on how to upgrade your Formbricks deployment using Helm, including examples of common upgrade scenarios.
|
||||||
|
|
||||||
|
### Upgrade Process
|
||||||
|
|
||||||
|
To upgrade your Formbricks deployment when using a local chart (e.g., with Minikube), use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the helm-chart directory
|
||||||
|
helm upgrade my-formbricks ./ --namespace formbricks
|
||||||
|
```
|
||||||
|
|
||||||
|
For installations from the Helm repository (typically for production deployments):
|
||||||
|
```bash
|
||||||
|
helm repo update
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks --namespace formbricks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Upgrade Scenarios
|
||||||
|
|
||||||
|
#### 1. Updating Environment Variables
|
||||||
|
|
||||||
|
To update or add new environment variables, use the `--set` flag with the `env` prefix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks \
|
||||||
|
--set env.SMTP_HOST=new-smtp.example.com \
|
||||||
|
--set env.SMTP_PORT=587 \
|
||||||
|
--set env.NEW_CUSTOM_VAR=newvalue
|
||||||
|
```
|
||||||
|
|
||||||
|
This command updates the SMTP host and port, and adds a new custom environment variable.
|
||||||
|
|
||||||
|
#### 2. Enabling or Disabling Features
|
||||||
|
|
||||||
|
You can enable or disable features by updating their respective values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable Redis
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks --set redis.enabled=false
|
||||||
|
|
||||||
|
# Enable Redis
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks --set redis.enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Scaling Resources
|
||||||
|
|
||||||
|
To adjust resource allocation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks \
|
||||||
|
--set resources.limits.cpu=1 \
|
||||||
|
--set resources.limits.memory=2Gi \
|
||||||
|
--set resources.requests.cpu=500m \
|
||||||
|
--set resources.requests.memory=1Gi
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Updating Autoscaling Configuration
|
||||||
|
|
||||||
|
To modify autoscaling settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks \
|
||||||
|
--set autoscaling.minReplicas=3 \
|
||||||
|
--set autoscaling.maxReplicas=10 \
|
||||||
|
--set autoscaling.metrics[0].resource.target.averageUtilization=75
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Changing Database Credentials
|
||||||
|
|
||||||
|
To update PostgreSQL database credentials:
|
||||||
|
To switch from the built-in PostgreSQL to an external database or update the external database credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm upgrade my-formbricks formbricks/formbricks \
|
||||||
|
--set postgresql.enabled=false \
|
||||||
|
--set postgresql.externalUrl="postgresql://newuser:newpassword@external-postgres-host:5432/newdatabase"
|
||||||
|
```
|
||||||
|
|
||||||
|
This command disables the built-in PostgreSQL and configures Formbricks to use an external PostgreSQL database. Make sure your external database is set up and accessible before making this change.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Full Values Documentation
|
||||||
|
|
||||||
|
Below is a comprehensive list of all configurable values in the Formbricks Helm chart:
|
||||||
|
|
||||||
|
| Field | Description | Default |
|
||||||
|
| ----------------------------------------------------------- | ------------------------------------------ | ------------------------------- |
|
||||||
|
| `image.repository` | Docker image repository for Formbricks | `ghcr.io/formbricks/formbricks` |
|
||||||
|
| `image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||||
|
| `image.tag` | Docker image tag | `"2.6.0"` |
|
||||||
|
| `service.type` | Kubernetes service type | `ClusterIP` |
|
||||||
|
| `service.port` | Kubernetes service port | `80` |
|
||||||
|
| `service.targetPort` | Container port to expose | `3000` |
|
||||||
|
| `resources.limits.cpu` | CPU resource limit | `500m` |
|
||||||
|
| `resources.limits.memory` | Memory resource limit | `1Gi` |
|
||||||
|
| `resources.requests.cpu` | Memory resource request | `null` |
|
||||||
|
| `resources.requests.memory` | Memory resource request | `null` |
|
||||||
|
| `autoscaling.enabled` | Enable autoscaling | `false` |
|
||||||
|
| `autoscaling.minReplicas` | Minimum number of replicas | `1` |
|
||||||
|
| `autoscaling.maxReplicas` | Maximum number of replicas | `5` |
|
||||||
|
| `autoscaling.metrics[0].type` | Type of metric for autoscaling | `Resource` |
|
||||||
|
| `autoscaling.metrics[0].resource.name` | Resource name for autoscaling metric | `cpu` |
|
||||||
|
| `autoscaling.metrics[0].resource.target.type` | Target type for autoscaling | `Utilization` |
|
||||||
|
| `autoscaling.metrics[0].resource.target.averageUtilization` | Average utilization target for autoscaling | `80`
|
||||||
@@ -30,11 +30,17 @@ type ButtonProps = {
|
|||||||
variant?: keyof typeof variantStyles;
|
variant?: keyof typeof variantStyles;
|
||||||
arrow?: "left" | "right";
|
arrow?: "left" | "right";
|
||||||
} & (
|
} & (
|
||||||
| React.ComponentPropsWithoutRef<typeof Link>
|
| React.ComponentPropsWithoutRef<typeof Link>
|
||||||
| (React.ComponentPropsWithoutRef<"button"> & { href?: undefined })
|
| (React.ComponentPropsWithoutRef<"button"> & { href?: undefined })
|
||||||
);
|
);
|
||||||
|
|
||||||
export function Button({ variant = "primary", className, children, arrow, ...props }: ButtonProps): React.JSX.Element {
|
export function Button({
|
||||||
|
variant = "primary",
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
arrow,
|
||||||
|
...props
|
||||||
|
}: ButtonProps): React.JSX.Element {
|
||||||
const buttonClassName = clsx(
|
const buttonClassName = clsx(
|
||||||
"inline-flex gap-0.5 justify-center items-center overflow-hidden font-medium transition text-center",
|
"inline-flex gap-0.5 justify-center items-center overflow-hidden font-medium transition text-center",
|
||||||
variantStyles[variant],
|
variantStyles[variant],
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Tag } from "@/components/tag";
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Children, createContext, isValidElement, useContext, useEffect, useRef, useState } from "react";
|
import { Children, createContext, isValidElement, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { Tag } from "@/components/tag";
|
|
||||||
|
|
||||||
const languageNames: Record<string, string> = {
|
const languageNames: Record<string, string> = {
|
||||||
js: "JavaScript",
|
js: "JavaScript",
|
||||||
@@ -49,7 +49,9 @@ function CopyButton({ code }: { code: string }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (copyCount > 0) {
|
if (copyCount > 0) {
|
||||||
const timeout = setTimeout(() => { setCopyCount(0); }, 1000);
|
const timeout = setTimeout(() => {
|
||||||
|
setCopyCount(0);
|
||||||
|
}, 1000);
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
};
|
};
|
||||||
@@ -98,9 +100,11 @@ function CodePanelHeader({ tag, label }: { tag?: string; label?: string }): Reac
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b-white/7.5 bg-white/2.5 dark:bg-white/1 flex h-9 items-center gap-2 border-y border-t-transparent bg-slate-900 px-4 dark:border-b-white/5">
|
<div className="border-b-white/7.5 bg-white/2.5 dark:bg-white/1 flex h-9 items-center gap-2 border-y border-t-transparent bg-slate-900 px-4 dark:border-b-white/5">
|
||||||
{tag ? <div className="dark flex">
|
{tag ? (
|
||||||
<Tag variant="small">{tag}</Tag>
|
<div className="dark flex">
|
||||||
</div> : null}
|
<Tag variant="small">{tag}</Tag>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{tag && label ? <span className="h-0.5 w-0.5 rounded-full bg-slate-500" /> : null}
|
{tag && label ? <span className="h-0.5 w-0.5 rounded-full bg-slate-500" /> : null}
|
||||||
{label ? <span className="font-mono text-xs text-slate-400">{label}</span> : null}
|
{label ? <span className="font-mono text-xs text-slate-400">{label}</span> : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -162,30 +166,34 @@ function CodeGroupHeader({
|
|||||||
return (
|
return (
|
||||||
<div className="flex min-h-[calc(theme(spacing.12)+1px)] flex-wrap items-start gap-x-4 border-b border-slate-700 bg-slate-800 px-4 dark:border-slate-800 dark:bg-transparent">
|
<div className="flex min-h-[calc(theme(spacing.12)+1px)] flex-wrap items-start gap-x-4 border-b border-slate-700 bg-slate-800 px-4 dark:border-slate-800 dark:bg-transparent">
|
||||||
{title ? <h3 className="mr-auto pt-3 text-xs font-semibold text-white">{title}</h3> : null}
|
{title ? <h3 className="mr-auto pt-3 text-xs font-semibold text-white">{title}</h3> : null}
|
||||||
{hasTabs ? <Tab.List className="-mb-px flex gap-4 text-xs font-medium">
|
{hasTabs ? (
|
||||||
{Children.map(children, (child, childIndex) => {
|
<Tab.List className="-mb-px flex gap-4 text-xs font-medium">
|
||||||
if (isValidElement(child)) {
|
{Children.map(children, (child, childIndex) => {
|
||||||
return (
|
if (isValidElement(child)) {
|
||||||
<Tab
|
return (
|
||||||
className={clsx(
|
<Tab
|
||||||
"ui-not-focus-visible:outline-none border-b py-3 transition",
|
className={clsx(
|
||||||
childIndex === selectedIndex
|
"ui-not-focus-visible:outline-none border-b py-3 transition",
|
||||||
? "border-teal-500 text-teal-400"
|
childIndex === selectedIndex
|
||||||
: "border-transparent text-slate-400 hover:text-slate-300"
|
? "border-teal-500 text-teal-400"
|
||||||
)}
|
: "border-transparent text-slate-400 hover:text-slate-300"
|
||||||
>
|
)}>
|
||||||
{getPanelTitle(child.props as { title?: string; language?: string })}
|
{getPanelTitle(child.props as { title?: string; language?: string })}
|
||||||
</Tab>
|
</Tab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})}
|
})}
|
||||||
</Tab.List> : null}
|
</Tab.List>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CodeGroupPanels({ children, ...props }: React.ComponentPropsWithoutRef<typeof CodePanel>): React.JSX.Element {
|
function CodeGroupPanels({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentPropsWithoutRef<typeof CodePanel>): React.JSX.Element {
|
||||||
const hasTabs = Children.count(children) >= 1;
|
const hasTabs = Children.count(children) >= 1;
|
||||||
|
|
||||||
if (hasTabs) {
|
if (hasTabs) {
|
||||||
@@ -264,7 +272,9 @@ const useTabGroupProps = (availableLanguages: string[]) => {
|
|||||||
const { positionRef, preventLayoutShift } = usePreventLayoutShift();
|
const { positionRef, preventLayoutShift } = usePreventLayoutShift();
|
||||||
|
|
||||||
const onChange = (index: number) => {
|
const onChange = (index: number) => {
|
||||||
preventLayoutShift(() => { addPreferredLanguage(availableLanguages[index] ?? ""); });
|
preventLayoutShift(() => {
|
||||||
|
addPreferredLanguage(availableLanguages[index] ?? "");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -331,7 +341,10 @@ export function Code({ children, ...props }: React.ComponentPropsWithoutRef<"cod
|
|||||||
return <code {...props}>{children}</code>;
|
return <code {...props}>{children}</code>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Pre({ children, ...props }: React.ComponentPropsWithoutRef<typeof CodeGroup>): React.ReactNode {
|
export function Pre({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentPropsWithoutRef<typeof CodeGroup>): React.ReactNode {
|
||||||
const isGrouped = useContext(CodeGroupContext);
|
const isGrouped = useContext(CodeGroupContext);
|
||||||
|
|
||||||
if (isGrouped) {
|
if (isGrouped) {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ function CheckIcon(props: React.ComponentPropsWithoutRef<"svg">): React.JSX.Elem
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FeedbackButton(props: Omit<React.ComponentPropsWithoutRef<"button">, "type" | "className">): React.JSX.Element {
|
function FeedbackButton(
|
||||||
|
props: Omit<React.ComponentPropsWithoutRef<"button">, "type" | "className">
|
||||||
|
): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -49,16 +51,18 @@ const FeedbackForm = forwardRef<
|
|||||||
|
|
||||||
FeedbackForm.displayName = "FeedbackForm";
|
FeedbackForm.displayName = "FeedbackForm";
|
||||||
|
|
||||||
const FeedbackThanks = forwardRef<React.ElementRef<"div">, React.ComponentPropsWithoutRef<"div">>((_props, ref): React.JSX.Element => {
|
const FeedbackThanks = forwardRef<React.ElementRef<"div">, React.ComponentPropsWithoutRef<"div">>(
|
||||||
return (
|
(_props, ref): React.JSX.Element => {
|
||||||
<div ref={ref} className="absolute inset-0 flex justify-center md:justify-start">
|
return (
|
||||||
<div className="flex items-center gap-3 rounded-full bg-teal-50/50 py-1 pl-1.5 pr-3 text-sm text-teal-900 ring-1 ring-inset ring-teal-500/20 dark:bg-teal-500/5 dark:text-teal-200 dark:ring-teal-500/30">
|
<div ref={ref} className="absolute inset-0 flex justify-center md:justify-start">
|
||||||
<CheckIcon className="h-5 w-5 flex-none fill-teal-500 stroke-white dark:fill-teal-200/20 dark:stroke-teal-200" />
|
<div className="flex items-center gap-3 rounded-full bg-teal-50/50 py-1 pl-1.5 pr-3 text-sm text-teal-900 ring-1 ring-inset ring-teal-500/20 dark:bg-teal-500/5 dark:text-teal-200 dark:ring-teal-500/30">
|
||||||
Thanks for your feedback!
|
<CheckIcon className="h-5 w-5 flex-none fill-teal-500 stroke-white dark:fill-teal-200/20 dark:stroke-teal-200" />
|
||||||
|
Thanks for your feedback!
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
FeedbackThanks.displayName = "FeedbackThanks";
|
FeedbackThanks.displayName = "FeedbackThanks";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { navigation } from "@/lib/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { navigation } from "@/lib/navigation";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { DiscordIcon } from "./icons/discord-icon";
|
import { DiscordIcon } from "./icons/discord-icon";
|
||||||
import { GithubIcon } from "./icons/github-icon";
|
import { GithubIcon } from "./icons/github-icon";
|
||||||
|
|||||||
@@ -24,18 +24,20 @@ export function GridPattern({
|
|||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${patternId})`} />
|
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${patternId})`} />
|
||||||
{squares.length > 0 ? <svg x={x} y={y} className="overflow-visible">
|
{squares.length > 0 ? (
|
||||||
{squares.map(([sqX, sqY]) => (
|
<svg x={x} y={y} className="overflow-visible">
|
||||||
<rect
|
{squares.map(([sqX, sqY]) => (
|
||||||
strokeWidth="0"
|
<rect
|
||||||
key={`${sqX.toString()}-${sqY.toString()}`}
|
strokeWidth="0"
|
||||||
width={width + 1}
|
key={`${sqX.toString()}-${sqY.toString()}`}
|
||||||
height={height + 1}
|
width={width + 1}
|
||||||
x={sqX * width}
|
height={height + 1}
|
||||||
y={sqY * height}
|
x={sqX * width}
|
||||||
/>
|
y={sqY * height}
|
||||||
))}
|
/>
|
||||||
</svg> : null}
|
))}
|
||||||
|
</svg>
|
||||||
|
) : null}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Logo } from "@/components/logo";
|
||||||
|
import { Navigation } from "@/components/navigation";
|
||||||
|
import { Search } from "@/components/search";
|
||||||
|
import { useIsInsideMobileNavigation, useMobileNavigationStore } from "@/hooks/use-mobile-navigation";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { type MotionStyle, motion, useScroll, useTransform } from "framer-motion";
|
import { type MotionStyle, motion, useScroll, useTransform } from "framer-motion";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
import { Search } from "@/components/search";
|
|
||||||
import { Logo } from "@/components/logo";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { MobileNavigation } from "./mobile-navigation";
|
import { MobileNavigation } from "./mobile-navigation";
|
||||||
import { ThemeToggle } from "./theme-toggle";
|
import { ThemeToggle } from "./theme-toggle";
|
||||||
import { Navigation } from "@/components/navigation";
|
|
||||||
import { useIsInsideMobileNavigation, useMobileNavigationStore } from "@/hooks/use-mobile-navigation";
|
|
||||||
|
|
||||||
function TopLevelNavItem({ href, children }: { href: string; children: React.ReactNode }): React.JSX.Element {
|
function TopLevelNavItem({ href, children }: { href: string; children: React.ReactNode }): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useInView } from "framer-motion";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { useSectionStore } from "@/components/section-provider";
|
import { useSectionStore } from "@/components/section-provider";
|
||||||
import { Tag } from "@/components/tag";
|
import { Tag } from "@/components/tag";
|
||||||
import { remToPx } from "@/lib/rem-to-px";
|
import { remToPx } from "@/lib/rem-to-px";
|
||||||
|
import { useInView } from "framer-motion";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
function AnchorIcon(props: React.ComponentPropsWithoutRef<"svg">): React.JSX.Element {
|
function AnchorIcon(props: React.ComponentPropsWithoutRef<"svg">): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
@@ -29,14 +29,24 @@ function Eyebrow({ tag, label }: { tag?: string; label?: string }): React.JSX.El
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Anchor({ id, inView, children }: { id: string; inView: boolean; children: React.ReactNode }): React.JSX.Element {
|
function Anchor({
|
||||||
|
id,
|
||||||
|
inView,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
inView: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Link href={`#${id}`} className="group text-inherit no-underline hover:text-inherit">
|
<Link href={`#${id}`} className="group text-inherit no-underline hover:text-inherit">
|
||||||
{inView ? <div className="absolute ml-[calc(-1*var(--width))] mt-1 hidden w-[var(--width)] opacity-0 transition [--width:calc(1.35rem+0.85px+38%-min(38%,calc(theme(maxWidth.lg)+theme(spacing.2))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]">
|
{inView ? (
|
||||||
<div className="group/anchor block h-5 w-5 rounded-lg bg-zinc-50 ring-1 ring-inset ring-zinc-300 transition hover:ring-zinc-500 dark:bg-zinc-800 dark:ring-zinc-700 dark:hover:bg-zinc-700 dark:hover:ring-zinc-600">
|
<div className="absolute ml-[calc(-1*var(--width))] mt-1 hidden w-[var(--width)] opacity-0 transition [--width:calc(1.35rem+0.85px+38%-min(38%,calc(theme(maxWidth.lg)+theme(spacing.2))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]">
|
||||||
<AnchorIcon className="h-5 w-5 stroke-zinc-500 transition dark:stroke-zinc-400 dark:group-hover/anchor:stroke-white" />
|
<div className="group/anchor block h-5 w-5 rounded-lg bg-zinc-50 ring-1 ring-inset ring-zinc-300 transition hover:ring-zinc-500 dark:bg-zinc-800 dark:ring-zinc-700 dark:hover:bg-zinc-700 dark:hover:ring-zinc-600">
|
||||||
|
<AnchorIcon className="h-5 w-5 stroke-zinc-500 transition dark:stroke-zinc-400 dark:group-hover/anchor:stroke-white" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> : null}
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -67,7 +77,7 @@ export function Heading<Level extends 2 | 3 | 4>({
|
|||||||
const ref = useRef<HTMLHeadingElement>(null);
|
const ref = useRef<HTMLHeadingElement>(null);
|
||||||
const registerHeading = useSectionStore((s) => s.registerHeading);
|
const registerHeading = useSectionStore((s) => s.registerHeading);
|
||||||
|
|
||||||
const topMargin = remToPx(-3.5)
|
const topMargin = remToPx(-3.5);
|
||||||
const inView = useInView(ref, {
|
const inView = useInView(ref, {
|
||||||
margin: `${topMargin}px 0px 0px 0px`,
|
margin: `${topMargin}px 0px 0px 0px`,
|
||||||
amount: "all",
|
amount: "all",
|
||||||
@@ -75,18 +85,18 @@ export function Heading<Level extends 2 | 3 | 4>({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (headingLevel === 2) {
|
if (headingLevel === 2) {
|
||||||
registerHeading({ id: props.id, ref, offsetRem: tag ?? label ? 8 : 6 });
|
registerHeading({ id: props.id, ref, offsetRem: (tag ?? label) ? 8 : 6 });
|
||||||
} else if (headingLevel === 3) {
|
} else if (headingLevel === 3) {
|
||||||
registerHeading({ id: props.id, ref, offsetRem: tag ?? label ? 7 : 5 });
|
registerHeading({ id: props.id, ref, offsetRem: (tag ?? label) ? 7 : 5 });
|
||||||
} else if (headingLevel === 4) {
|
} else if (headingLevel === 4) {
|
||||||
registerHeading({ id: props.id, ref, offsetRem: tag ?? label ? 6 : 4 });
|
registerHeading({ id: props.id, ref, offsetRem: (tag ?? label) ? 6 : 4 });
|
||||||
}
|
}
|
||||||
}, [label, headingLevel, props.id, registerHeading, tag]);
|
}, [label, headingLevel, props.id, registerHeading, tag]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Eyebrow tag={tag} label={label} />
|
<Eyebrow tag={tag} label={label} />
|
||||||
<Component ref={ref} className={tag ?? label ? "mt-2 scroll-mt-32" : "scroll-mt-24"} {...props}>
|
<Component ref={ref} className={(tag ?? label) ? "mt-2 scroll-mt-32" : "scroll-mt-24"} {...props}>
|
||||||
{anchor ? (
|
{anchor ? (
|
||||||
<Anchor id={props.id} inView={inView}>
|
<Anchor id={props.id} inView={inView}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ export function GithubIcon(props: React.ComponentPropsWithoutRef<"svg">): React.
|
|||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ export function LoadingSpinner(props: React.ComponentPropsWithoutRef<"div">): Re
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ export function TwitterIcon(props: React.ComponentPropsWithoutRef<"svg">): React
|
|||||||
<path d="M403.229 0h78.506L310.219 196.04 512 462.799H354.002L230.261 301.007 88.669 462.799h-78.56l183.455-209.683L0 0h161.999l111.856 147.88L403.229 0zm-27.556 415.805h43.505L138.363 44.527h-46.68l283.99 371.278z" />
|
<path d="M403.229 0h78.506L310.219 196.04 512 462.799H354.002L230.261 301.007 88.669 462.799h-78.56l183.455-209.683L0 0h161.999l111.856 147.88L403.229 0zm-27.556 415.805h43.505L138.363 44.527h-46.68l283.99 371.278z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
import { Logo } from "@/components/logo";
|
import { Logo } from "@/components/logo";
|
||||||
import { Navigation } from "@/components/navigation";
|
import { Navigation } from "@/components/navigation";
|
||||||
import { SideNavigation } from "@/components/side-navigation";
|
import { SideNavigation } from "@/components/side-navigation";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import { Footer } from "./footer";
|
import { Footer } from "./footer";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { type Section, SectionProvider } from "./section-provider";
|
import { type Section, SectionProvider } from "./section-provider";
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import Image from "next/image";
|
|
||||||
import logoDark from "@/images/logo/logo-dark.svg";
|
import logoDark from "@/images/logo/logo-dark.svg";
|
||||||
import logoLight from "@/images/logo/logo-light.svg";
|
import logoLight from "@/images/logo/logo-light.svg";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
export function Logo({ className }: { className?: string }) {
|
export function Logo({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="block dark:hidden">
|
<div className="block dark:hidden">
|
||||||
<Image className={className} src={logoLight as string} alt="Formbricks Open source Forms & Surveys Logo" />
|
<Image
|
||||||
|
className={className}
|
||||||
|
src={logoLight as string}
|
||||||
|
alt="Formbricks Open source Forms & Surveys Logo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden dark:block">
|
<div className="hidden dark:block">
|
||||||
<Image className={className} src={logoDark as string} alt="Formbricks Open source Forms & Surveys Logo" />
|
<Image
|
||||||
|
className={className}
|
||||||
|
src={logoDark as string}
|
||||||
|
alt="Formbricks Open source Forms & Surveys Logo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function MobileNavigationDialog({
|
|||||||
if (
|
if (
|
||||||
link &&
|
link &&
|
||||||
link.pathname + link.search + link.hash ===
|
link.pathname + link.search + link.hash ===
|
||||||
window.location.pathname + window.location.search + window.location.hash
|
window.location.pathname + window.location.search + window.location.hash
|
||||||
) {
|
) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useIsInsideMobileNavigation } from "@/hooks/use-mobile-navigation";
|
||||||
|
import { navigation } from "@/lib/navigation";
|
||||||
|
import { remToPx } from "@/lib/rem-to-px";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { AnimatePresence, motion, useIsPresent } from "framer-motion";
|
import { AnimatePresence, motion, useIsPresent } from "framer-motion";
|
||||||
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { remToPx } from "@/lib/rem-to-px";
|
|
||||||
import { navigation } from "@/lib/navigation";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { useIsInsideMobileNavigation } from "@/hooks/use-mobile-navigation";
|
|
||||||
import { useSectionStore } from "./section-provider";
|
import { useSectionStore } from "./section-provider";
|
||||||
|
|
||||||
export interface BaseLink {
|
export interface BaseLink {
|
||||||
@@ -79,7 +79,6 @@ function NavLink({
|
|||||||
<span className="flex w-full truncate">{children}</span>
|
<span className="flex w-full truncate">{children}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function VisibleSectionHighlight({ group, pathname }: { group: NavGroup; pathname: string }) {
|
function VisibleSectionHighlight({ group, pathname }: { group: NavGroup; pathname: string }) {
|
||||||
@@ -97,7 +96,7 @@ function VisibleSectionHighlight({ group, pathname }: { group: NavGroup; pathnam
|
|||||||
const activePageIndex = group.links.findIndex(
|
const activePageIndex = group.links.findIndex(
|
||||||
(link) =>
|
(link) =>
|
||||||
(link.href && pathname.startsWith(link.href)) ??
|
(link.href && pathname.startsWith(link.href)) ??
|
||||||
(link.children?.some((child) => pathname.startsWith(child.href)))
|
link.children?.some((child) => pathname.startsWith(child.href))
|
||||||
);
|
);
|
||||||
|
|
||||||
const height = isPresent ? Math.max(1, visibleSections.length) * itemHeight : itemHeight;
|
const height = isPresent ? Math.max(1, visibleSections.length) * itemHeight : itemHeight;
|
||||||
@@ -116,13 +115,19 @@ function VisibleSectionHighlight({ group, pathname }: { group: NavGroup; pathnam
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActivePageMarker({ group, pathname }: { group: NavGroup; pathname: string }): React.JSX.Element | null {
|
function ActivePageMarker({
|
||||||
|
group,
|
||||||
|
pathname,
|
||||||
|
}: {
|
||||||
|
group: NavGroup;
|
||||||
|
pathname: string;
|
||||||
|
}): React.JSX.Element | null {
|
||||||
const itemHeight = remToPx(2);
|
const itemHeight = remToPx(2);
|
||||||
const offset = remToPx(0.25);
|
const offset = remToPx(0.25);
|
||||||
const activePageIndex = group.links.findIndex(
|
const activePageIndex = group.links.findIndex(
|
||||||
(link) =>
|
(link) =>
|
||||||
(link.href && pathname.startsWith(link.href)) ??
|
(link.href && pathname.startsWith(link.href)) ??
|
||||||
(link.children?.some((child) => pathname.startsWith(child.href)))
|
link.children?.some((child) => pathname.startsWith(child.href))
|
||||||
);
|
);
|
||||||
if (activePageIndex === -1) return null;
|
if (activePageIndex === -1) return null;
|
||||||
const top = offset + activePageIndex * itemHeight;
|
const top = offset + activePageIndex * itemHeight;
|
||||||
@@ -228,21 +233,25 @@ function NavigationGroup({
|
|||||||
key={link.title}
|
key={link.title}
|
||||||
layout="position"
|
layout="position"
|
||||||
className="relative"
|
className="relative"
|
||||||
onClick={() => { setIsActiveGroup(true); }}>
|
onClick={() => {
|
||||||
|
setIsActiveGroup(true);
|
||||||
|
}}>
|
||||||
{link.href ? (
|
{link.href ? (
|
||||||
<NavLink
|
<NavLink href={link.href} active={Boolean(pathname.startsWith(link.href))}>
|
||||||
href={link.href}
|
|
||||||
active={Boolean(pathname.startsWith(link.href))}>
|
|
||||||
{link.title}
|
{link.title}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={() => { toggleParentTitle(`${group.title}-${link.title}`); }} className="w-full">
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
toggleParentTitle(`${group.title}-${link.title}`);
|
||||||
|
}}
|
||||||
|
className="w-full">
|
||||||
<NavLink
|
<NavLink
|
||||||
href={!isMobile ? link.children?.[0]?.href ?? "" : undefined}
|
href={!isMobile ? (link.children?.[0]?.href ?? "") : undefined}
|
||||||
active={
|
active={Boolean(
|
||||||
Boolean(isParentOpen(`${group.title}-${link.title}`) &&
|
isParentOpen(`${group.title}-${link.title}`) &&
|
||||||
link.children?.some((child) => pathname.startsWith(child.href)))
|
link.children?.some((child) => pathname.startsWith(child.href))
|
||||||
}>
|
)}>
|
||||||
<span className="flex w-full justify-between">
|
<span className="flex w-full justify-between">
|
||||||
{link.title}
|
{link.title}
|
||||||
{isParentOpen(`${group.title}-${link.title}`) ? (
|
{isParentOpen(`${group.title}-${link.title}`) ? (
|
||||||
@@ -255,19 +264,24 @@ function NavigationGroup({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<AnimatePresence mode="popLayout" initial={false}>
|
<AnimatePresence mode="popLayout" initial={false}>
|
||||||
{isActiveGroup && link.children && isParentOpen(`${group.title}-${link.title}`) ? <motion.ul
|
{isActiveGroup && link.children && isParentOpen(`${group.title}-${link.title}`) ? (
|
||||||
role="list"
|
<motion.ul
|
||||||
initial={{ opacity: 0 }}
|
role="list"
|
||||||
animate={{ opacity: 1, transition: { delay: 0.1 } }}
|
initial={{ opacity: 0 }}
|
||||||
exit={{ opacity: 0, transition: { duration: 0.15 } }}>
|
animate={{ opacity: 1, transition: { delay: 0.1 } }}
|
||||||
{link.children.map((child) => (
|
exit={{ opacity: 0, transition: { duration: 0.15 } }}>
|
||||||
<li key={child.href}>
|
{link.children.map((child) => (
|
||||||
<NavLink href={child.href} isAnchorLink active={Boolean(pathname.startsWith(child.href))}>
|
<li key={child.href}>
|
||||||
{child.title}
|
<NavLink
|
||||||
</NavLink>
|
href={child.href}
|
||||||
</li>
|
isAnchorLink
|
||||||
))}
|
active={Boolean(pathname.startsWith(child.href))}>
|
||||||
</motion.ul> : null}
|
{child.title}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</motion.ul>
|
||||||
|
) : null}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</motion.li>
|
</motion.li>
|
||||||
))}
|
))}
|
||||||
@@ -306,7 +320,7 @@ export function Navigation({ isMobile, ...props }: NavigationProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav {...props}>
|
<nav {...props}>
|
||||||
<ul >
|
<ul>
|
||||||
{navigation.map((group, groupIndex) => (
|
{navigation.map((group, groupIndex) => (
|
||||||
<NavigationGroup
|
<NavigationGroup
|
||||||
key={group.title}
|
key={group.title}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type MotionValue, motion, useMotionTemplate, useMotionValue } from "framer-motion";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { GridPattern } from "@/components/grid-pattern";
|
import { GridPattern } from "@/components/grid-pattern";
|
||||||
import { Heading } from "@/components/heading";
|
import { Heading } from "@/components/heading";
|
||||||
import { ChatBubbleIcon } from "@/components/icons/chat-bubble-icon";
|
import { ChatBubbleIcon } from "@/components/icons/chat-bubble-icon";
|
||||||
import { EnvelopeIcon } from "@/components/icons/envelope-icon";
|
import { EnvelopeIcon } from "@/components/icons/envelope-icon";
|
||||||
import { UserIcon } from "@/components/icons/user-icon";
|
import { UserIcon } from "@/components/icons/user-icon";
|
||||||
import { UsersIcon } from "@/components/icons/users-icon";
|
import { UsersIcon } from "@/components/icons/users-icon";
|
||||||
|
import { type MotionValue, motion, useMotionTemplate, useMotionValue } from "framer-motion";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
interface TResource {
|
interface TResource {
|
||||||
href: string;
|
href: string;
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export function ResponsiveVideo({ src, title }: { src: string; title: string }):
|
|||||||
className="absolute left-0 top-0 h-full w-full"
|
className="absolute left-0 top-0 h-full w-full"
|
||||||
referrerPolicy="strict-origin-when-cross-origin"
|
referrerPolicy="strict-origin-when-cross-origin"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
allowFullScreen />
|
allowFullScreen
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -54,7 +54,11 @@ export function Search(): React.JSX.Element {
|
|||||||
|
|
||||||
useDocSearchKeyboardEvents({
|
useDocSearchKeyboardEvents({
|
||||||
isOpen,
|
isOpen,
|
||||||
onOpen: isSearchDisabled ? () => { return void 0 } : onOpen,
|
onOpen: isSearchDisabled
|
||||||
|
? () => {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
: onOpen,
|
||||||
onClose,
|
onClose,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,7 +115,6 @@ export function Search(): React.JSX.Element {
|
|||||||
};
|
};
|
||||||
}, [isLightMode]);
|
}, [isLightMode]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { remToPx } from "@/lib/rem-to-px";
|
||||||
import { createContext, useContext, useEffect, useLayoutEffect, useState } from "react";
|
import { createContext, useContext, useEffect, useLayoutEffect, useState } from "react";
|
||||||
import { type StoreApi, createStore, useStore } from "zustand";
|
import { type StoreApi, createStore, useStore } from "zustand";
|
||||||
import { remToPx } from "@/lib/rem-to-px";
|
|
||||||
|
|
||||||
export interface Section {
|
export interface Section {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -31,7 +31,9 @@ const createSectionStore = (sections: Section[]) => {
|
|||||||
return createStore<SectionState>()((set) => ({
|
return createStore<SectionState>()((set) => ({
|
||||||
sections,
|
sections,
|
||||||
visibleSections: [],
|
visibleSections: [],
|
||||||
setVisibleSections: (visibleSections) => { set((state) => (state.visibleSections.join() === visibleSections.join() ? {} : { visibleSections })); },
|
setVisibleSections: (visibleSections) => {
|
||||||
|
set((state) => (state.visibleSections.join() === visibleSections.join() ? {} : { visibleSections }));
|
||||||
|
},
|
||||||
registerHeading: ({ id, ref, offsetRem }) => {
|
registerHeading: ({ id, ref, offsetRem }) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
return {
|
return {
|
||||||
@@ -92,7 +94,9 @@ const useVisibleSections = (sectionStore: StoreApi<SectionState>) => {
|
|||||||
setVisibleSections(newVisibleSections);
|
setVisibleSections(newVisibleSections);
|
||||||
};
|
};
|
||||||
|
|
||||||
const raf = window.requestAnimationFrame(() => { checkVisibleSections(); });
|
const raf = window.requestAnimationFrame(() => {
|
||||||
|
checkVisibleSections();
|
||||||
|
});
|
||||||
window.addEventListener("scroll", checkVisibleSections, { passive: true });
|
window.addEventListener("scroll", checkVisibleSections, { passive: true });
|
||||||
window.addEventListener("resize", checkVisibleSections);
|
window.addEventListener("resize", checkVisibleSections);
|
||||||
|
|
||||||
@@ -108,13 +112,7 @@ const SectionStoreContext = createContext<StoreApi<SectionState> | null>(null);
|
|||||||
|
|
||||||
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
||||||
|
|
||||||
export function SectionProvider({
|
export function SectionProvider({ sections, children }: { sections: Section[]; children: React.ReactNode }) {
|
||||||
sections,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
sections: Section[];
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const [sectionStore] = useState(() => createSectionStore(sections));
|
const [sectionStore] = useState(() => createSectionStore(sections));
|
||||||
|
|
||||||
useVisibleSections(sectionStore);
|
useVisibleSections(sectionStore);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function SideNavigation({ pathname }: { pathname: string }): React.JSX.El
|
|||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={heading.text}
|
key={heading.text}
|
||||||
className={clsx(`mb-4 text-slate-900 dark:text-white ml-4`, {
|
className={clsx(`mb-4 ml-4 text-slate-900 dark:text-white`, {
|
||||||
"ml-0": heading.level === 2,
|
"ml-0": heading.level === 2,
|
||||||
"ml-4": heading.level === 3,
|
"ml-4": heading.level === 3,
|
||||||
"ml-6": heading.level === 4,
|
"ml-6": heading.level === 4,
|
||||||
|
|||||||
@@ -22,4 +22,3 @@ export default function SurveyEmbed({ surveyUrl }: SurveyEmbedProps): React.JSX.
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export function TellaVideo({ tellaVideoIdentifier }: { tellaVideoIdentifier: str
|
|||||||
}}
|
}}
|
||||||
src={`https://www.tella.tv/video/${tellaVideoIdentifier}/embed?b=0&title=0&a=1&loop=0&autoPlay=true&t=0&muted=1&wt=0`}
|
src={`https://www.tella.tv/video/${tellaVideoIdentifier}/embed?b=0&title=0&a=1&loop=0&autoPlay=true&t=0&muted=1&wt=0`}
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
title="Tella Video Help" />
|
title="Tella Video Help"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export function ThemeToggle(): React.JSX.Element {
|
|||||||
type="button"
|
type="button"
|
||||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
|
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
|
||||||
aria-label={mounted ? `Switch to ${otherTheme} theme` : "Toggle theme"}
|
aria-label={mounted ? `Switch to ${otherTheme} theme` : "Toggle theme"}
|
||||||
onClick={() => { setTheme(otherTheme); }}>
|
onClick={() => {
|
||||||
|
setTheme(otherTheme);
|
||||||
|
}}>
|
||||||
<SunIcon className="h-5 w-5 stroke-zinc-900 dark:hidden" />
|
<SunIcon className="h-5 w-5 stroke-zinc-900 dark:hidden" />
|
||||||
<MoonIcon className="hidden h-5 w-5 stroke-white dark:block" />
|
<MoonIcon className="hidden h-5 w-5 stroke-white dark:block" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ export const useMobileNavigationStore = create<{
|
|||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
}>()((set) => ({
|
}>()((set) => ({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
open: () => { set({ isOpen: true }); },
|
open: () => {
|
||||||
close: () => { set({ isOpen: false }); },
|
set({ isOpen: true });
|
||||||
toggle: () => { set((state) => ({ isOpen: !state.isOpen })); },
|
},
|
||||||
|
close: () => {
|
||||||
|
set({ isOpen: false });
|
||||||
|
},
|
||||||
|
toggle: () => {
|
||||||
|
set((state) => ({ isOpen: !state.isOpen }));
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -24,12 +24,9 @@ export const useTableContentObserver = (setActiveId: (id: string) => void, pathn
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const callback = (headings: HeadingElement[]) => {
|
const callback = (headings: HeadingElement[]) => {
|
||||||
// Create a map of heading elements, where the key is the heading's ID and the value is the heading element
|
// Create a map of heading elements, where the key is the heading's ID and the value is the heading element
|
||||||
headingElementsRef.current = headings.reduce(
|
headingElementsRef.current = headings.reduce((map, headingElement) => {
|
||||||
(map, headingElement) => {
|
return { ...map, [headingElement.target.id]: headingElement };
|
||||||
return { ...map, [headingElement.target.id]: headingElement };
|
}, {});
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find the visible headings (i.e., headings that are currently intersecting with the viewport)
|
// Find the visible headings (i.e., headings that are currently intersecting with the viewport)
|
||||||
const visibleHeadings: HeadingElement[] = [];
|
const visibleHeadings: HeadingElement[] = [];
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const navigation: NavGroup[] = [
|
|||||||
{ title: "Airtable", href: "/developer-docs/integrations/airtable" },
|
{ title: "Airtable", href: "/developer-docs/integrations/airtable" },
|
||||||
{ title: "Google Sheets", href: "/developer-docs/integrations/google-sheets" },
|
{ title: "Google Sheets", href: "/developer-docs/integrations/google-sheets" },
|
||||||
{ title: "Make", href: "/developer-docs/integrations/make" },
|
{ title: "Make", href: "/developer-docs/integrations/make" },
|
||||||
|
{ title: "Activepieces", href: "/developer-docs/integrations/activepieces" },
|
||||||
{ title: "n8n", href: "/developer-docs/integrations/n8n" },
|
{ title: "n8n", href: "/developer-docs/integrations/n8n" },
|
||||||
{ title: "Notion", href: "/developer-docs/integrations/notion" },
|
{ title: "Notion", href: "/developer-docs/integrations/notion" },
|
||||||
{ title: "Slack", href: "/developer-docs/integrations/slack" },
|
{ title: "Slack", href: "/developer-docs/integrations/slack" },
|
||||||
@@ -145,6 +146,7 @@ export const navigation: NavGroup[] = [
|
|||||||
{ title: "License", href: "/self-hosting/license" },
|
{ title: "License", href: "/self-hosting/license" },
|
||||||
{ title: "Cluster Setup", href: "/self-hosting/cluster-setup" },
|
{ title: "Cluster Setup", href: "/self-hosting/cluster-setup" },
|
||||||
{ title: "Rate Limiting", href: "/self-hosting/rate-limiting" },
|
{ title: "Rate Limiting", href: "/self-hosting/rate-limiting" },
|
||||||
|
{ title: "Kubernetes", href: "/self-hosting/kubernetes" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,33 +35,41 @@ tags:
|
|||||||
sessions, and responses within the Formbricks platform. This API is ideal
|
sessions, and responses within the Formbricks platform. This API is ideal
|
||||||
for client-side interactions, as it doesn't expose sensitive information.
|
for client-side interactions, as it doesn't expose sensitive information.
|
||||||
|
|
||||||
|
- Displays API - Mark
|
||||||
- [Actions
|
|
||||||
API](https://go.postman.co/workspace/90ba379a-0de2-47d2-a94c-8eb541e47082/documentation/11026000-927c954f-85a9-4f8f-b0ec-14191b903737?entity=folder-b8f3a10e-1642-4d82-a629-fef0a8c6c86c)
|
|
||||||
- Create actions for a person
|
|
||||||
|
|
||||||
- [Displays API](https://formbricks.com/docs/api/client/displays) - Mark
|
|
||||||
Survey as Displayed or Responded for a Person
|
Survey as Displayed or Responded for a Person
|
||||||
|
|
||||||
- [People API](https://formbricks.com/docs/api/client/people) - Create &
|
- Responses API - Create & update responses for a survey
|
||||||
update people (e.g. attributes)
|
|
||||||
|
- Environment API - Get the environment state to be used in Formbricks SDKs
|
||||||
|
|
||||||
|
- Contacts API - Identify & update contacts (e.g. attributes)
|
||||||
|
|
||||||
|
- User API - Identify & track users based on their attributes, device type, etc.
|
||||||
|
|
||||||
- [Responses API](https://formbricks.com/docs/api/client/responses) -
|
|
||||||
Create & update responses for a survey
|
|
||||||
- name: Client API > Display
|
- name: Client API > Display
|
||||||
description: >-
|
description: >-
|
||||||
Displays are metrics used to measure the number of times a survey was
|
Displays are metrics used to measure the number of times a survey was
|
||||||
viewed both by unidentified or identified users.
|
viewed both by unidentified or identified users.
|
||||||
- name: Client API > People
|
|
||||||
description: >-
|
|
||||||
Persons are the identified users on Formbricks app that get initated when
|
|
||||||
you pass a userId and have user activation enabled. This now allows you to
|
|
||||||
track & show them targeted surveys based on their actions, attributes,
|
|
||||||
etc.
|
|
||||||
- name: Client API > Response
|
- name: Client API > Response
|
||||||
description: >-
|
description: >-
|
||||||
Responses are captured whenever a user fills in your survey either
|
Responses are captured whenever a user fills in your survey either
|
||||||
partially or completely.
|
partially or completely.
|
||||||
|
- name: Client API > Environment
|
||||||
|
description: >-
|
||||||
|
Get the environment state to be used in Formbricks SDKs
|
||||||
|
- name: Client API > Contacts
|
||||||
|
description: >-
|
||||||
|
Contacts are the identified users on Formbricks app that get initated when
|
||||||
|
you pass a userId and have user activation enabled. This now allows you to
|
||||||
|
track & show them targeted surveys based on their attributes, device type,
|
||||||
|
etc.
|
||||||
|
- name: Client API > User
|
||||||
|
description: >-
|
||||||
|
Users are the identified users on Formbricks app that get initated when
|
||||||
|
you pass a userId and have user activation enabled. This now allows you to
|
||||||
|
track & show them targeted surveys based on their attributes, device type,
|
||||||
|
etc. Currently, this api is only being used in the react-native sdk.
|
||||||
|
|
||||||
- name: Management API
|
- name: Management API
|
||||||
description: "The Management API provides access to all data and settings that are visible in the Formbricks App. This API requires a personal API Key for authentication, which can be generated in the Settings section of the Formbricks App. With the Management API, you can manage your Formbricks account programmatically, accessing and modifying data and settings as needed.\n\n> **For Auth:**\_we use the `x-api-key` header \n \n\nAPI requests made to the Management API are authorized using a personal API key. This key grants the same rights and access as if you were logged in at formbricks.com. It's essential to keep your API key secure and not share it with others.\n\nTo generate, store, or delete an API key, follow the instructions provided on the following page\_[API Key](https://formbricks.com/docs/api/management/api-key-setup)."
|
description: "The Management API provides access to all data and settings that are visible in the Formbricks App. This API requires a personal API Key for authentication, which can be generated in the Settings section of the Formbricks App. With the Management API, you can manage your Formbricks account programmatically, accessing and modifying data and settings as needed.\n\n> **For Auth:**\_we use the `x-api-key` header \n \n\nAPI requests made to the Management API are authorized using a personal API key. This key grants the same rights and access as if you were logged in at formbricks.com. It's essential to keep your API key secure and not share it with others.\n\nTo generate, store, or delete an API key, follow the instructions provided on the following page\_[API Key](https://formbricks.com/docs/api/management/api-key-setup)."
|
||||||
- name: Management API > Action Class
|
- name: Management API > Action Class
|
||||||
@@ -436,139 +444,424 @@ paths:
|
|||||||
code: internal_server_error
|
code: internal_server_error
|
||||||
message: Person with ID 2 not found
|
message: Person with ID 2 not found
|
||||||
details: {}
|
details: {}
|
||||||
/api/v1/client/{environmentId}/people:
|
/api/v1/client/{environmentId}/environment:
|
||||||
post:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Client API > People
|
- Client API > Environment
|
||||||
summary: Create Person
|
summary: Get Environment State
|
||||||
description: Creates a person in Formbricks with the given userId, it must be unique.
|
description: >-
|
||||||
requestBody:
|
Retrieves the environment state to be used in Formbricks SDKs
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
example:
|
|
||||||
userId: "{{userId}}"
|
|
||||||
parameters:
|
parameters:
|
||||||
- name: environmentId
|
- name: environmentId
|
||||||
in: path
|
in: path
|
||||||
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
description: The ID of the environment
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "HTTP Status 200"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
surveys:
|
||||||
|
type: array
|
||||||
|
description: List of surveys in the environment
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: "cm6orr901000g19wjwwa690eo"
|
||||||
|
welcomeCard:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
html:
|
||||||
|
type: string
|
||||||
|
example: "Thanks for providing your feedback - let's go!"
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
headline:
|
||||||
|
type: string
|
||||||
|
example: "Welcome!"
|
||||||
|
buttonLabel:
|
||||||
|
type: string
|
||||||
|
example: "Next"
|
||||||
|
timeToFinish:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
showResponseCount:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: "Start from scratch"
|
||||||
|
questions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: "dd5c8w2a4ttkbnjb9nwhtb17"
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
example: "openText"
|
||||||
|
headline:
|
||||||
|
type: string
|
||||||
|
example: "What would you like to know?"
|
||||||
|
required:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
charLimit:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
inputType:
|
||||||
|
type: string
|
||||||
|
example: "text"
|
||||||
|
buttonLabel:
|
||||||
|
type: string
|
||||||
|
example: "Next"
|
||||||
|
placeholder:
|
||||||
|
type: string
|
||||||
|
example: "Type your answer here..."
|
||||||
|
variables:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
example: "app"
|
||||||
|
showLanguageSwitch:
|
||||||
|
type: boolean
|
||||||
|
example: null
|
||||||
|
languages:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
endings:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: "o729tod5klhix62njmk262dk"
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
example: "endScreen"
|
||||||
|
headline:
|
||||||
|
type: string
|
||||||
|
example: "Thank you!"
|
||||||
|
subheader:
|
||||||
|
type: string
|
||||||
|
example: "We appreciate your feedback."
|
||||||
|
buttonLink:
|
||||||
|
type: string
|
||||||
|
example: "https://formbricks.com"
|
||||||
|
buttonLabel:
|
||||||
|
type: string
|
||||||
|
example: "Create your own Survey"
|
||||||
|
autoClose:
|
||||||
|
type: boolean
|
||||||
|
example: null
|
||||||
|
styling:
|
||||||
|
type: object
|
||||||
|
example: null
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: "inProgress"
|
||||||
|
segment:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: "cm6orr90h000h19wj1lnwoxwg"
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
example: "2025-02-03T08:08:33.377Z"
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
example: "2025-02-03T08:08:33.377Z"
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
example: "cm6orr901000g19wjwwa690eo"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: null
|
||||||
|
isPrivate:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
filters:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
recontactDays:
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
displayLimit:
|
||||||
|
type: integer
|
||||||
|
example: 5
|
||||||
|
displayOption:
|
||||||
|
type: string
|
||||||
|
example: "respondMultiple"
|
||||||
|
hiddenFields:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
fieldIds:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
triggers:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
actionClass:
|
||||||
|
type: string
|
||||||
|
example: "code action"
|
||||||
|
displayPercentage:
|
||||||
|
type: integer
|
||||||
|
example: null
|
||||||
|
delay:
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
actionClasses:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: "cm6orqtdd000b19wjec82bpp2"
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
example: "automatic"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: "New Session"
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
example: null
|
||||||
|
noCodeConfig:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
example: null
|
||||||
|
example:
|
||||||
|
- id: "cm6orqtdd000b19wjec82bpp2"
|
||||||
|
type: "automatic"
|
||||||
|
name: "New Session"
|
||||||
|
key: null
|
||||||
|
noCodeConfig: null
|
||||||
|
- id: "cm6oryki3000i19wj860utcnn"
|
||||||
|
type: "code"
|
||||||
|
name: "code action"
|
||||||
|
key: "code"
|
||||||
|
noCodeConfig: null
|
||||||
|
project:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
example: "cm6orqtcl000319wj9wb7dltl"
|
||||||
|
recontactDays:
|
||||||
|
type: integer
|
||||||
|
example: 7
|
||||||
|
clickOutsideClose:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
darkOverlay:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
placement:
|
||||||
|
type: string
|
||||||
|
example: "bottomRight"
|
||||||
|
inAppSurveyBranding:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
styling:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
brandColor:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
light:
|
||||||
|
type: string
|
||||||
|
example: "#64748b"
|
||||||
|
allowStyleOverwrite:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
example: "not_found"
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "Environment not found"
|
||||||
|
details:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
example: "tpywklouw2p7tebdu4zv01an"
|
||||||
|
resource_type:
|
||||||
|
type: string
|
||||||
|
example: "environment"
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
example: "internal_server_error"
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "Unable to complete request: Expected property name or '}' in JSON at position 29"
|
||||||
|
details:
|
||||||
|
type: object
|
||||||
|
/api/v1/client/{environmentId}/identify/contacts/{userId}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Client API > Contacts
|
||||||
|
summary: Get Contact State
|
||||||
|
description: >-
|
||||||
|
Retrieves a contact's state including their segments, displays, responses
|
||||||
|
and other tracking information. If the contact doesn't exist, it will be created.
|
||||||
|
parameters:
|
||||||
|
- name: environmentId
|
||||||
|
in: path
|
||||||
required: true
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The ID of the environment
|
||||||
|
- name: userId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The user ID to identify the contact
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
headers:
|
|
||||||
Access-Control-Allow-Credentials:
|
|
||||||
schema:
|
|
||||||
type: boolean
|
|
||||||
example: "true"
|
|
||||||
Access-Control-Allow-Origin:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: "*"
|
|
||||||
access-control-allow-methods:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: GET, POST, PUT, DELETE, OPTIONS
|
|
||||||
access-control-allow-headers:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: Content-Type, Authorization
|
|
||||||
vary:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: RSC, Next-Router-State-Tree, Next-Router-Prefetch
|
|
||||||
cache-control:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: private, no-store
|
|
||||||
content-type:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: application/json
|
|
||||||
Date:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: Tue, 23 Apr 2024 07:35:47 GMT
|
|
||||||
Connection:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: keep-alive
|
|
||||||
Keep-Alive:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: timeout=5
|
|
||||||
Transfer-Encoding:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: chunked
|
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
example:
|
properties:
|
||||||
data:
|
data:
|
||||||
userId: Shubham
|
type: object
|
||||||
"400":
|
properties:
|
||||||
description: Bad Request
|
userId:
|
||||||
headers:
|
type: string
|
||||||
Access-Control-Allow-Credentials:
|
description: The user ID of the contact
|
||||||
schema:
|
segments:
|
||||||
type: boolean
|
type: array
|
||||||
example: "true"
|
description: List of segment IDs the contact belongs to
|
||||||
Access-Control-Allow-Origin:
|
items:
|
||||||
schema:
|
type: string
|
||||||
type: string
|
displays:
|
||||||
example: "*"
|
type: array
|
||||||
access-control-allow-methods:
|
description: List of survey displays for this contact
|
||||||
schema:
|
items:
|
||||||
type: string
|
type: object
|
||||||
example: GET, POST, PUT, DELETE, OPTIONS
|
properties:
|
||||||
access-control-allow-headers:
|
surveyId:
|
||||||
schema:
|
type: string
|
||||||
type: string
|
createdAt:
|
||||||
example: Content-Type, Authorization
|
type: string
|
||||||
vary:
|
format: date-time
|
||||||
schema:
|
responses:
|
||||||
type: string
|
type: array
|
||||||
example: RSC, Next-Router-State-Tree, Next-Router-Prefetch
|
description: List of survey IDs the contact has responded to
|
||||||
content-type:
|
items:
|
||||||
schema:
|
type: string
|
||||||
type: string
|
lastDisplayAt:
|
||||||
example: application/json
|
type: string
|
||||||
Date:
|
format: date-time
|
||||||
schema:
|
nullable: true
|
||||||
type: string
|
description: Timestamp of the last survey display
|
||||||
example: Tue, 23 Apr 2024 07:36:43 GMT
|
example:
|
||||||
Connection:
|
userId: "user-123"
|
||||||
schema:
|
segments: ["fi8f9oekza95wwszrptidivq", "zgwrv8eg7vfavdhzv1s0po1w"]
|
||||||
type: string
|
displays: [{ surveyId: "pjogp5a1wyxon6umplmf49b8", createdAt: "2024-04-23T08:59:37.550Z" }]
|
||||||
example: keep-alive
|
responses: ["pjogp5a1wyxon6umplmf49b8"]
|
||||||
Keep-Alive:
|
lastDisplayAt: "2024-04-23T08:59:37.550Z"
|
||||||
schema:
|
"403":
|
||||||
type: string
|
description: Forbidden
|
||||||
example: timeout=5
|
|
||||||
Transfer-Encoding:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: chunked
|
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
example:
|
properties:
|
||||||
code: bad_request
|
code:
|
||||||
message: userId is required
|
type: string
|
||||||
details:
|
example: "forbidden"
|
||||||
environmentId: clurwouax000azffxt7n5unn3
|
message:
|
||||||
/api/v1/client/{environmentId}/people/{userId}/attributes:
|
type: string
|
||||||
|
example: "User identification is only available for enterprise users."
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
example: "not_found"
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "Environment not found"
|
||||||
|
details:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
example: "tpywklouw2p7tebdu4zv01an"
|
||||||
|
resource_type:
|
||||||
|
type: string
|
||||||
|
example: "environment"
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
example: "internal_server_error"
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "Unable to complete request: Expected property name or '}' in JSON at position 29"
|
||||||
|
details:
|
||||||
|
type: object
|
||||||
|
/api/v1/client/{environmentId}/contacts/{userId}/attributes:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Client API > People
|
- Client API > Contacts
|
||||||
summary: Update Person
|
summary: Update Contact (Attributes)
|
||||||
description: >-
|
description: >-
|
||||||
Update a person's attributes in Formbricks to keep them in sync with
|
Update a contact's attributes in Formbricks to keep them in sync with
|
||||||
your app or when you want to set a custom attribute in Formbricks.
|
your app or when you want to set a custom attribute in Formbricks.
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
@@ -713,6 +1006,280 @@ paths:
|
|||||||
Unable to complete request: Expected property name or '}' in
|
Unable to complete request: Expected property name or '}' in
|
||||||
JSON at position 29
|
JSON at position 29
|
||||||
details: {}
|
details: {}
|
||||||
|
/api/v1/client/{environmentId}/user:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Client API > User
|
||||||
|
summary: Create or Identify User
|
||||||
|
description: >
|
||||||
|
Endpoint for creating or identifying a user within the specified environment.
|
||||||
|
If the user already exists, this will identify them and potentially
|
||||||
|
update user attributes. If they don't exist, it will create a new user.
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
userId: "hello-user"
|
||||||
|
attributes:
|
||||||
|
plan: "free"
|
||||||
|
parameters:
|
||||||
|
- name: environmentId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
headers:
|
||||||
|
Access-Control-Allow-Credentials:
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: "true"
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "*"
|
||||||
|
access-control-allow-methods:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: GET, POST, PUT, DELETE, OPTIONS
|
||||||
|
access-control-allow-headers:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Content-Type, Authorization
|
||||||
|
vary:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: RSC, Next-Router-State-Tree, Next-Router-Prefetch
|
||||||
|
content-type:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: application/json
|
||||||
|
Date:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Tue, 23 Apr 2024 07:36:43 GMT
|
||||||
|
Connection:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: keep-alive
|
||||||
|
Keep-Alive:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: timeout=5
|
||||||
|
Transfer-Encoding:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: chunked
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
state:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
userId:
|
||||||
|
type: string
|
||||||
|
example: "hello-user"
|
||||||
|
segments:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: "cm6onrezn000hw2ahcokiz41v"
|
||||||
|
displays:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
surveyId:
|
||||||
|
type: string
|
||||||
|
example: "cm6orqtdd000a19wjhnbces5s"
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
example: "2025-02-03T11:23:13.050Z"
|
||||||
|
responses:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: "cm6orqtdd000a19wjhnbces5s"
|
||||||
|
lastDisplayAt:
|
||||||
|
type: string
|
||||||
|
example: "2025-02-03T11:23:13.050Z"
|
||||||
|
expiresAt:
|
||||||
|
type: string
|
||||||
|
example: "2025-02-03T11:23:13.050Z"
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
headers:
|
||||||
|
Access-Control-Allow-Credentials:
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: "true"
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "*"
|
||||||
|
access-control-allow-methods:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: GET, POST, PUT, DELETE, OPTIONS
|
||||||
|
access-control-allow-headers:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Content-Type, Authorization
|
||||||
|
vary:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: RSC, Next-Router-State-Tree, Next-Router-Prefetch
|
||||||
|
content-type:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: application/json
|
||||||
|
Date:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Tue, 23 Apr 2024 07:36:43 GMT
|
||||||
|
Connection:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: keep-alive
|
||||||
|
Keep-Alive:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: timeout=5
|
||||||
|
Transfer-Encoding:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: chunked
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
code: bad_request
|
||||||
|
message: userId is required
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
headers:
|
||||||
|
Access-Control-Allow-Credentials:
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: "true"
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "*"
|
||||||
|
access-control-allow-methods:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: GET, POST, PUT, DELETE, OPTIONS
|
||||||
|
access-control-allow-headers:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Content-Type, Authorization
|
||||||
|
vary:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: RSC, Next-Router-State-Tree, Next-Router-Prefetch
|
||||||
|
cache-control:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: private, no-store
|
||||||
|
content-type:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: application/json
|
||||||
|
Date:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Tue, 23 Apr 2024 07:56:29 GMT
|
||||||
|
Connection:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: keep-alive
|
||||||
|
Keep-Alive:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: timeout=5
|
||||||
|
Transfer-Encoding:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: chunked
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
code: not_found
|
||||||
|
message: Environment not found
|
||||||
|
details:
|
||||||
|
resource_id: f16ttdvtkx85k5m4s561ruqj
|
||||||
|
resource_type: Environment
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
headers:
|
||||||
|
Access-Control-Allow-Credentials:
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: "true"
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "*"
|
||||||
|
access-control-allow-methods:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: GET, POST, PUT, DELETE, OPTIONS
|
||||||
|
access-control-allow-headers:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Content-Type, Authorization
|
||||||
|
vary:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: RSC, Next-Router-State-Tree, Next-Router-Prefetch
|
||||||
|
cache-control:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: private, no-store
|
||||||
|
content-type:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: application/json
|
||||||
|
Date:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: Tue, 23 Apr 2024 07:56:29 GMT
|
||||||
|
Connection:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: keep-alive
|
||||||
|
Keep-Alive:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: timeout=5
|
||||||
|
Transfer-Encoding:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: chunked
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
code: internal_server_error
|
||||||
|
message: An unexpected error occurred
|
||||||
|
details: {}
|
||||||
/api/v1/client/{environmentId}/responses:
|
/api/v1/client/{environmentId}/responses:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ FROM node:22-alpine3.20 AS base
|
|||||||
FROM base AS installer
|
FROM base AS installer
|
||||||
|
|
||||||
# Enable corepack and prepare pnpm
|
# Enable corepack and prepare pnpm
|
||||||
|
RUN npm install -g corepack@latest
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install necessary build tools and compilers
|
# Install necessary build tools and compilers
|
||||||
@@ -61,6 +62,8 @@ RUN jq -r '.devDependencies.prisma' packages/database/package.json > /prisma_ver
|
|||||||
## step 3: setup production runner
|
## step 3: setup production runner
|
||||||
#
|
#
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
|
|
||||||
|
RUN npm install -g corepack@latest
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
RUN apk add --no-cache curl \
|
RUN apk add --no-cache curl \
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
@@ -23,7 +23,7 @@ export const ConnectWithFormbricks = ({
|
|||||||
widgetSetupCompleted,
|
widgetSetupCompleted,
|
||||||
channel,
|
channel,
|
||||||
}: ConnectWithFormbricksProps) => {
|
}: ConnectWithFormbricksProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleFinishOnboarding = async () => {
|
const handleFinishOnboarding = async () => {
|
||||||
router.push(`/environments/${environment.id}/surveys`);
|
router.push(`/environments/${environment.id}/surveys`);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Button } from "@/modules/ui/components/button";
|
|||||||
import { CodeBlock } from "@/modules/ui/components/code-block";
|
import { CodeBlock } from "@/modules/ui/components/code-block";
|
||||||
import { Html5Icon, NpmIcon } from "@/modules/ui/components/icons";
|
import { Html5Icon, NpmIcon } from "@/modules/ui/components/icons";
|
||||||
import { TabBar } from "@/modules/ui/components/tab-bar";
|
import { TabBar } from "@/modules/ui/components/tab-bar";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import "prismjs/themes/prism.css";
|
import "prismjs/themes/prism.css";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -29,7 +29,7 @@ export const OnboardingSetupInstructions = ({
|
|||||||
channel,
|
channel,
|
||||||
widgetSetupCompleted,
|
widgetSetupCompleted,
|
||||||
}: OnboardingSetupInstructionsProps) => {
|
}: OnboardingSetupInstructionsProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
||||||
const htmlSnippetForAppSurveys = `<!-- START Formbricks Surveys -->
|
const htmlSnippetForAppSurveys = `<!-- START Formbricks Surveys -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ConnectWithFormbricks } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks";
|
import { ConnectWithFormbricks } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Header } from "@/modules/ui/components/header";
|
import { Header } from "@/modules/ui/components/header";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||||
@@ -16,7 +16,7 @@ interface ConnectPageProps {
|
|||||||
|
|
||||||
const Page = async (props: ConnectPageProps) => {
|
const Page = async (props: ConnectPageProps) => {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const environment = await getEnvironment(params.environmentId);
|
const environment = await getEnvironment(params.environmentId);
|
||||||
|
|
||||||
if (!environment) {
|
if (!environment) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { getXMTemplates } from "@/app/(app)/(onboarding)/environments/[environme
|
|||||||
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
|
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
|
||||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||||
import { createSurveyAction } from "@/modules/surveys/components/TemplateList/actions";
|
import { createSurveyAction } from "@/modules/surveys/components/TemplateList/actions";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { ActivityIcon, ShoppingCartIcon, SmileIcon, StarIcon, ThumbsUpIcon, UsersIcon } from "lucide-react";
|
import { ActivityIcon, ShoppingCartIcon, SmileIcon, StarIcon, ThumbsUpIcon, UsersIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
@@ -23,7 +23,7 @@ interface XMTemplateListProps {
|
|||||||
|
|
||||||
export const XMTemplateList = ({ project, user, environmentId }: XMTemplateListProps) => {
|
export const XMTemplateList = ({ project, user, environmentId }: XMTemplateListProps) => {
|
||||||
const [activeTemplateId, setActiveTemplateId] = useState<number | null>(null);
|
const [activeTemplateId, setActiveTemplateId] = useState<number | null>(null);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const createSurvey = async (activeTemplate: TXMTemplate) => {
|
const createSurvey = async (activeTemplate: TXMTemplate) => {
|
||||||
@@ -47,7 +47,7 @@ export const XMTemplateList = ({ project, user, environmentId }: XMTemplateListP
|
|||||||
|
|
||||||
const handleTemplateClick = (templateIdx: number) => {
|
const handleTemplateClick = (templateIdx: number) => {
|
||||||
setActiveTemplateId(templateIdx);
|
setActiveTemplateId(templateIdx);
|
||||||
const template = getXMTemplates(user.locale)[templateIdx];
|
const template = getXMTemplates(t)[templateIdx];
|
||||||
const newTemplate = replacePresetPlaceholders(template, project);
|
const newTemplate = replacePresetPlaceholders(template, project);
|
||||||
createSurvey(newTemplate);
|
createSurvey(newTemplate);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { TXMTemplate } from "@formbricks/types/templates";
|
|||||||
// replace all occurences of projectName with the actual project name in the current template
|
// replace all occurences of projectName with the actual project name in the current template
|
||||||
export const replacePresetPlaceholders = (template: TXMTemplate, project: TProject) => {
|
export const replacePresetPlaceholders = (template: TXMTemplate, project: TProject) => {
|
||||||
const survey = structuredClone(template);
|
const survey = structuredClone(template);
|
||||||
survey.name = survey.name.replace("{{projectName}}", project.name);
|
survey.name = survey.name.replace("$[projectName]", project.name);
|
||||||
survey.questions = survey.questions.map((question) => {
|
survey.questions = survey.questions.map((question) => {
|
||||||
return replaceQuestionPresetPlaceholders(question, project);
|
return replaceQuestionPresetPlaceholders(question, project);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
|
import { getDefaultEndingCard } from "@/app/lib/templates";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
import { getDefaultEndingCard, translate } from "@formbricks/lib/templates";
|
import { TFnType } from "@tolgee/react";
|
||||||
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||||
import { TXMTemplate } from "@formbricks/types/templates";
|
import { TXMTemplate } from "@formbricks/types/templates";
|
||||||
|
|
||||||
function validateLocale(locale: string): boolean {
|
|
||||||
// Add logic to validate the locale, e.g., check against a list of supported locales
|
|
||||||
return typeof locale === "string" && locale.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function logError(error: Error, context: string) {
|
function logError(error: Error, context: string) {
|
||||||
console.error(`Error in ${context}:`, error);
|
console.error(`Error in ${context}:`, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getXMSurveyDefault = (locale: string): TXMTemplate => {
|
export const getXMSurveyDefault = (t: TFnType): TXMTemplate => {
|
||||||
try {
|
try {
|
||||||
if (!validateLocale(locale)) {
|
|
||||||
throw new Error("Invalid locale");
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
endings: [getDefaultEndingCard([], locale)],
|
endings: [getDefaultEndingCard([], t)],
|
||||||
questions: [],
|
questions: [],
|
||||||
styling: {
|
styling: {
|
||||||
overwriteThemeStyling: true,
|
overwriteThemeStyling: true,
|
||||||
@@ -31,24 +24,24 @@ export const getXMSurveyDefault = (locale: string): TXMTemplate => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const NPSSurvey = (locale: string): TXMTemplate => {
|
const npsSurvey = (t: TFnType): TXMTemplate => {
|
||||||
return {
|
return {
|
||||||
...getXMSurveyDefault(locale),
|
...getXMSurveyDefault(t),
|
||||||
name: translate("nps_survey_name", locale),
|
name: t("templates.nps_survey_name"),
|
||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.NPS,
|
type: TSurveyQuestionTypeEnum.NPS,
|
||||||
headline: { default: translate("nps_survey_question_1_headline", locale) },
|
headline: { default: t("templates.nps_survey_question_1_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
lowerLabel: { default: translate("nps_survey_question_1_lower_label", locale) },
|
lowerLabel: { default: t("templates.nps_survey_question_1_lower_label") },
|
||||||
upperLabel: { default: translate("nps_survey_question_1_upper_label", locale) },
|
upperLabel: { default: t("templates.nps_survey_question_1_upper_label") },
|
||||||
isColorCodingEnabled: true,
|
isColorCodingEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("nps_survey_question_2_headline", locale) },
|
headline: { default: t("templates.nps_survey_question_2_headline") },
|
||||||
required: false,
|
required: false,
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
@@ -58,7 +51,7 @@ const NPSSurvey = (locale: string): TXMTemplate => {
|
|||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("nps_survey_question_3_headline", locale) },
|
headline: { default: t("templates.nps_survey_question_3_headline") },
|
||||||
required: false,
|
required: false,
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
@@ -69,13 +62,13 @@ const NPSSurvey = (locale: string): TXMTemplate => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarRatingSurvey = (locale: string): TXMTemplate => {
|
const starRatingSurvey = (t: TFnType): TXMTemplate => {
|
||||||
const reusableQuestionIds = [createId(), createId(), createId()];
|
const reusableQuestionIds = [createId(), createId(), createId()];
|
||||||
const defaultSurvey = getXMSurveyDefault(locale);
|
const defaultSurvey = getXMSurveyDefault(t);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...defaultSurvey,
|
...defaultSurvey,
|
||||||
name: translate("star_rating_survey_name", locale),
|
name: t("templates.star_rating_survey_name"),
|
||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[0],
|
id: reusableQuestionIds[0],
|
||||||
@@ -112,15 +105,15 @@ const StarRatingSurvey = (locale: string): TXMTemplate => {
|
|||||||
],
|
],
|
||||||
range: 5,
|
range: 5,
|
||||||
scale: "number",
|
scale: "number",
|
||||||
headline: { default: translate("star_rating_survey_question_1_headline", locale) },
|
headline: { default: t("templates.star_rating_survey_question_1_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
lowerLabel: { default: translate("star_rating_survey_question_1_lower_label", locale) },
|
lowerLabel: { default: t("templates.star_rating_survey_question_1_lower_label") },
|
||||||
upperLabel: { default: translate("star_rating_survey_question_1_upper_label", locale) },
|
upperLabel: { default: t("templates.star_rating_survey_question_1_upper_label") },
|
||||||
isColorCodingEnabled: false,
|
isColorCodingEnabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[1],
|
id: reusableQuestionIds[1],
|
||||||
html: { default: translate("star_rating_survey_question_2_html", locale) },
|
html: { default: t("templates.star_rating_survey_question_2_html") },
|
||||||
type: TSurveyQuestionTypeEnum.CTA,
|
type: TSurveyQuestionTypeEnum.CTA,
|
||||||
logic: [
|
logic: [
|
||||||
{
|
{
|
||||||
@@ -148,20 +141,20 @@ const StarRatingSurvey = (locale: string): TXMTemplate => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
headline: { default: translate("star_rating_survey_question_2_headline", locale) },
|
headline: { default: t("templates.star_rating_survey_question_2_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
buttonUrl: "https://formbricks.com/github",
|
buttonUrl: "https://formbricks.com/github",
|
||||||
buttonLabel: { default: translate("star_rating_survey_question_2_button_label", locale) },
|
buttonLabel: { default: t("templates.star_rating_survey_question_2_button_label") },
|
||||||
buttonExternal: true,
|
buttonExternal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[2],
|
id: reusableQuestionIds[2],
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: "Sorry to hear! What is ONE thing we can do better?" },
|
headline: { default: t("templates.star_rating_survey_question_3_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
subheader: { default: "Help us improve your experience." },
|
subheader: { default: t("templates.star_rating_survey_question_3_subheader") },
|
||||||
buttonLabel: { default: "Send" },
|
buttonLabel: { default: t("templates.star_rating_survey_question_3_button_label") },
|
||||||
placeholder: { default: "Type your answer here..." },
|
placeholder: { default: t("templates.star_rating_survey_question_3_placeholder") },
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -171,13 +164,13 @@ const StarRatingSurvey = (locale: string): TXMTemplate => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const CSATSurvey = (locale: string): TXMTemplate => {
|
const csatSurvey = (t: TFnType): TXMTemplate => {
|
||||||
const reusableQuestionIds = [createId(), createId(), createId()];
|
const reusableQuestionIds = [createId(), createId(), createId()];
|
||||||
const defaultSurvey = getXMSurveyDefault(locale);
|
const defaultSurvey = getXMSurveyDefault(t);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...defaultSurvey,
|
...defaultSurvey,
|
||||||
name: translate("csat_survey_name", locale),
|
name: t("templates.csat_survey_name"),
|
||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[0],
|
id: reusableQuestionIds[0],
|
||||||
@@ -214,10 +207,10 @@ const CSATSurvey = (locale: string): TXMTemplate => {
|
|||||||
],
|
],
|
||||||
range: 5,
|
range: 5,
|
||||||
scale: "smiley",
|
scale: "smiley",
|
||||||
headline: { default: translate("csat_survey_question_1_headline", locale) },
|
headline: { default: t("templates.csat_survey_question_1_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
lowerLabel: { default: translate("csat_survey_question_1_lower_label", locale) },
|
lowerLabel: { default: t("templates.csat_survey_question_1_lower_label") },
|
||||||
upperLabel: { default: translate("csat_survey_question_1_upper_label", locale) },
|
upperLabel: { default: t("templates.csat_survey_question_1_upper_label") },
|
||||||
isColorCodingEnabled: false,
|
isColorCodingEnabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -249,9 +242,9 @@ const CSATSurvey = (locale: string): TXMTemplate => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
headline: { default: translate("csat_survey_question_2_headline", locale) },
|
headline: { default: t("templates.csat_survey_question_2_headline") },
|
||||||
required: false,
|
required: false,
|
||||||
placeholder: { default: translate("csat_survey_question_2_placeholder", locale) },
|
placeholder: { default: t("templates.csat_survey_question_2_placeholder") },
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -260,9 +253,9 @@ const CSATSurvey = (locale: string): TXMTemplate => {
|
|||||||
{
|
{
|
||||||
id: reusableQuestionIds[2],
|
id: reusableQuestionIds[2],
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("csat_survey_question_3_headline", locale) },
|
headline: { default: t("templates.csat_survey_question_3_headline") },
|
||||||
required: false,
|
required: false,
|
||||||
placeholder: { default: translate("csat_survey_question_3_placeholder", locale) },
|
placeholder: { default: t("templates.csat_survey_question_3_placeholder") },
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -272,28 +265,28 @@ const CSATSurvey = (locale: string): TXMTemplate => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const CESSurvey = (locale: string): TXMTemplate => {
|
const cessSurvey = (t: TFnType): TXMTemplate => {
|
||||||
return {
|
return {
|
||||||
...getXMSurveyDefault(locale),
|
...getXMSurveyDefault(t),
|
||||||
name: translate("cess_survey_name", locale),
|
name: t("templates.cess_survey_name"),
|
||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.Rating,
|
type: TSurveyQuestionTypeEnum.Rating,
|
||||||
range: 5,
|
range: 5,
|
||||||
scale: "number",
|
scale: "number",
|
||||||
headline: { default: translate("cess_survey_question_1_headline", locale) },
|
headline: { default: t("templates.cess_survey_question_1_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
lowerLabel: { default: translate("cess_survey_question_1_lower_label", locale) },
|
lowerLabel: { default: t("templates.cess_survey_question_1_lower_label") },
|
||||||
upperLabel: { default: translate("cess_survey_question_1_upper_label", locale) },
|
upperLabel: { default: t("templates.cess_survey_question_1_upper_label") },
|
||||||
isColorCodingEnabled: false,
|
isColorCodingEnabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("cess_survey_question_2_headline", locale) },
|
headline: { default: t("templates.cess_survey_question_2_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: { default: translate("cess_survey_question_2_placeholder", locale) },
|
placeholder: { default: t("templates.cess_survey_question_2_placeholder") },
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -303,13 +296,13 @@ const CESSurvey = (locale: string): TXMTemplate => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const SmileysRatingSurvey = (locale: string): TXMTemplate => {
|
const smileysRatingSurvey = (t: TFnType): TXMTemplate => {
|
||||||
const reusableQuestionIds = [createId(), createId(), createId()];
|
const reusableQuestionIds = [createId(), createId(), createId()];
|
||||||
const defaultSurvey = getXMSurveyDefault(locale);
|
const defaultSurvey = getXMSurveyDefault(t);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...defaultSurvey,
|
...defaultSurvey,
|
||||||
name: translate("smileys_survey_name", locale),
|
name: t("templates.smileys_survey_name"),
|
||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[0],
|
id: reusableQuestionIds[0],
|
||||||
@@ -346,15 +339,15 @@ const SmileysRatingSurvey = (locale: string): TXMTemplate => {
|
|||||||
],
|
],
|
||||||
range: 5,
|
range: 5,
|
||||||
scale: "smiley",
|
scale: "smiley",
|
||||||
headline: { default: translate("smileys_survey_question_1_headline", locale) },
|
headline: { default: t("templates.smileys_survey_question_1_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
lowerLabel: { default: translate("smileys_survey_question_1_lower_label", locale) },
|
lowerLabel: { default: t("templates.smileys_survey_question_1_lower_label") },
|
||||||
upperLabel: { default: translate("smileys_survey_question_1_upper_label", locale) },
|
upperLabel: { default: t("templates.smileys_survey_question_1_upper_label") },
|
||||||
isColorCodingEnabled: false,
|
isColorCodingEnabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[1],
|
id: reusableQuestionIds[1],
|
||||||
html: { default: translate("smileys_survey_question_2_html", locale) },
|
html: { default: t("templates.smileys_survey_question_2_html") },
|
||||||
type: TSurveyQuestionTypeEnum.CTA,
|
type: TSurveyQuestionTypeEnum.CTA,
|
||||||
logic: [
|
logic: [
|
||||||
{
|
{
|
||||||
@@ -382,20 +375,20 @@ const SmileysRatingSurvey = (locale: string): TXMTemplate => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
headline: { default: translate("smileys_survey_question_2_headline", locale) },
|
headline: { default: t("templates.smileys_survey_question_2_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
buttonUrl: "https://formbricks.com/github",
|
buttonUrl: "https://formbricks.com/github",
|
||||||
buttonLabel: { default: translate("smileys_survey_question_2_button_label", locale) },
|
buttonLabel: { default: t("templates.smileys_survey_question_2_button_label") },
|
||||||
buttonExternal: true,
|
buttonExternal: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: reusableQuestionIds[2],
|
id: reusableQuestionIds[2],
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("smileys_survey_question_3_headline", locale) },
|
headline: { default: t("templates.smileys_survey_question_3_headline") },
|
||||||
required: true,
|
required: true,
|
||||||
subheader: { default: translate("smileys_survey_question_3_subheader", locale) },
|
subheader: { default: t("templates.smileys_survey_question_3_subheader") },
|
||||||
buttonLabel: { default: translate("smileys_survey_question_3_button_label", locale) },
|
buttonLabel: { default: t("templates.smileys_survey_question_3_button_label") },
|
||||||
placeholder: { default: translate("smileys_survey_question_3_placeholder", locale) },
|
placeholder: { default: t("templates.smileys_survey_question_3_placeholder") },
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -405,26 +398,26 @@ const SmileysRatingSurvey = (locale: string): TXMTemplate => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const eNPSSurvey = (locale: string): TXMTemplate => {
|
const enpsSurvey = (t: TFnType): TXMTemplate => {
|
||||||
return {
|
return {
|
||||||
...getXMSurveyDefault(locale),
|
...getXMSurveyDefault(t),
|
||||||
name: translate("enps_survey_name", locale),
|
name: t("templates.enps_survey_name"),
|
||||||
questions: [
|
questions: [
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.NPS,
|
type: TSurveyQuestionTypeEnum.NPS,
|
||||||
headline: {
|
headline: {
|
||||||
default: translate("enps_survey_question_1_headline", locale),
|
default: t("templates.enps_survey_question_1_headline"),
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
lowerLabel: { default: translate("enps_survey_question_1_lower_label", locale) },
|
lowerLabel: { default: t("templates.enps_survey_question_1_lower_label") },
|
||||||
upperLabel: { default: translate("enps_survey_question_1_upper_label", locale) },
|
upperLabel: { default: t("templates.enps_survey_question_1_upper_label") },
|
||||||
isColorCodingEnabled: true,
|
isColorCodingEnabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("enps_survey_question_2_headline", locale) },
|
headline: { default: t("templates.enps_survey_question_2_headline") },
|
||||||
required: false,
|
required: false,
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
@@ -434,7 +427,7 @@ const eNPSSurvey = (locale: string): TXMTemplate => {
|
|||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: TSurveyQuestionTypeEnum.OpenText,
|
type: TSurveyQuestionTypeEnum.OpenText,
|
||||||
headline: { default: translate("enps_survey_question_3_headline", locale) },
|
headline: { default: t("templates.enps_survey_question_3_headline") },
|
||||||
required: false,
|
required: false,
|
||||||
inputType: "text",
|
inputType: "text",
|
||||||
charLimit: {
|
charLimit: {
|
||||||
@@ -445,18 +438,15 @@ const eNPSSurvey = (locale: string): TXMTemplate => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getXMTemplates = (locale: string): TXMTemplate[] => {
|
export const getXMTemplates = (t: TFnType): TXMTemplate[] => {
|
||||||
try {
|
try {
|
||||||
if (!validateLocale(locale)) {
|
|
||||||
throw new Error("Invalid locale");
|
|
||||||
}
|
|
||||||
return [
|
return [
|
||||||
NPSSurvey(locale),
|
npsSurvey(t),
|
||||||
StarRatingSurvey(locale),
|
starRatingSurvey(t),
|
||||||
CSATSurvey(locale),
|
csatSurvey(t),
|
||||||
CESSurvey(locale),
|
cessSurvey(t),
|
||||||
SmileysRatingSurvey(locale),
|
smileysRatingSurvey(t),
|
||||||
eNPSSurvey(locale),
|
enpsSurvey(t),
|
||||||
];
|
];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, "getXMTemplates");
|
logError(error, "getXMTemplates");
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
|
|||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Header } from "@/modules/ui/components/header";
|
import { Header } from "@/modules/ui/components/header";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||||
import { getProjectByEnvironmentId, getUserProjects } from "@formbricks/lib/project/service";
|
import { getProjectByEnvironmentId, getUserProjects } from "@formbricks/lib/project/service";
|
||||||
@@ -21,7 +21,7 @@ const Page = async (props: XMTemplatePageProps) => {
|
|||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
const environment = await getEnvironment(params.environmentId);
|
const environment = await getEnvironment(params.environmentId);
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
throw new Error(t("common.session_not_found"));
|
throw new Error(t("common.session_not_found"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import {
|
|||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/modules/ui/components/dropdown-menu";
|
} from "@/modules/ui/components/dropdown-menu";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
|
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
|
||||||
import { signOut } from "next-auth/react";
|
import { signOut } from "next-auth/react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -44,7 +44,7 @@ export const LandingSidebar = ({
|
|||||||
}: LandingSidebarProps) => {
|
}: LandingSidebarProps) => {
|
||||||
const [openCreateOrganizationModal, setOpenCreateOrganizationModal] = useState<boolean>(false);
|
const [openCreateOrganizationModal, setOpenCreateOrganizationModal] = useState<boolean>(false);
|
||||||
|
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organiza
|
|||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
|
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
|
||||||
import { Header } from "@/modules/ui/components/header";
|
import { Header } from "@/modules/ui/components/header";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service";
|
import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service";
|
||||||
import { getUser } from "@formbricks/lib/user/service";
|
import { getUser } from "@formbricks/lib/user/service";
|
||||||
|
|
||||||
const Page = async (props) => {
|
const Page = async (props) => {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
return redirect(`/auth/login`);
|
return redirect(`/auth/login`);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
|
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
|
||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { canUserAccessOrganization } from "@formbricks/lib/organization/auth";
|
import { canUserAccessOrganization } from "@formbricks/lib/organization/auth";
|
||||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||||
@@ -14,7 +14,7 @@ const ProjectOnboardingLayout = async (props) => {
|
|||||||
|
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
return redirect(`/auth/login`);
|
return redirect(`/auth/login`);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizatio
|
|||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Header } from "@/modules/ui/components/header";
|
import { Header } from "@/modules/ui/components/header";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { PictureInPicture2Icon, SendIcon, XIcon } from "lucide-react";
|
import { PictureInPicture2Icon, SendIcon, XIcon } from "lucide-react";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getUserProjects } from "@formbricks/lib/project/service";
|
import { getUserProjects } from "@formbricks/lib/project/service";
|
||||||
@@ -22,7 +22,7 @@ const Page = async (props: ChannelPageProps) => {
|
|||||||
return redirect(`/auth/login`);
|
return redirect(`/auth/login`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const channelOptions = [
|
const channelOptions = [
|
||||||
{
|
{
|
||||||
title: t("organizations.projects.new.channel.link_and_email_surveys"),
|
title: t("organizations.projects.new.channel.link_and_email_surveys"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { getOrganizationProjectsLimit } from "@/modules/ee/license-check/lib/utils";
|
import { getOrganizationProjectsLimit } from "@/modules/ee/license-check/lib/utils";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||||
@@ -12,7 +12,7 @@ const OnboardingLayout = async (props) => {
|
|||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
|
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizatio
|
|||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Header } from "@/modules/ui/components/header";
|
import { Header } from "@/modules/ui/components/header";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react";
|
import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getUserProjects } from "@formbricks/lib/project/service";
|
import { getUserProjects } from "@formbricks/lib/project/service";
|
||||||
@@ -22,7 +22,7 @@ const Page = async (props: ModePageProps) => {
|
|||||||
return redirect(`/auth/login`);
|
return redirect(`/auth/login`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const channelOptions = [
|
const channelOptions = [
|
||||||
{
|
{
|
||||||
title: t("organizations.projects.new.mode.formbricks_surveys"),
|
title: t("organizations.projects.new.mode.formbricks_surveys"),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { createProjectAction } from "@/app/(app)/environments/[environmentId]/actions";
|
import { createProjectAction } from "@/app/(app)/environments/[environmentId]/actions";
|
||||||
|
import { previewSurvey } from "@/app/lib/templates";
|
||||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||||
import { TOrganizationTeam } from "@/modules/ee/teams/project-teams/types/team";
|
import { TOrganizationTeam } from "@/modules/ee/teams/project-teams/types/team";
|
||||||
import { CreateTeamModal } from "@/modules/ee/teams/team-list/components/create-team-modal";
|
import { CreateTeamModal } from "@/modules/ee/teams/team-list/components/create-team-modal";
|
||||||
@@ -19,14 +20,13 @@ import { Input } from "@/modules/ui/components/input";
|
|||||||
import { MultiSelect } from "@/modules/ui/components/multi-select";
|
import { MultiSelect } from "@/modules/ui/components/multi-select";
|
||||||
import { SurveyInline } from "@/modules/ui/components/survey";
|
import { SurveyInline } from "@/modules/ui/components/survey";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { FORMBRICKS_SURVEYS_FILTERS_KEY_LS } from "@formbricks/lib/localStorage";
|
import { FORMBRICKS_SURVEYS_FILTERS_KEY_LS } from "@formbricks/lib/localStorage";
|
||||||
import { getPreviewSurvey } from "@formbricks/lib/styling/constants";
|
|
||||||
import {
|
import {
|
||||||
TProjectConfigChannel,
|
TProjectConfigChannel,
|
||||||
TProjectConfigIndustry,
|
TProjectConfigIndustry,
|
||||||
@@ -43,7 +43,6 @@ interface ProjectSettingsProps {
|
|||||||
defaultBrandColor: string;
|
defaultBrandColor: string;
|
||||||
organizationTeams: TOrganizationTeam[];
|
organizationTeams: TOrganizationTeam[];
|
||||||
canDoRoleManagement: boolean;
|
canDoRoleManagement: boolean;
|
||||||
locale: string;
|
|
||||||
userProjectsCount: number;
|
userProjectsCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,13 +54,12 @@ export const ProjectSettings = ({
|
|||||||
defaultBrandColor,
|
defaultBrandColor,
|
||||||
organizationTeams,
|
organizationTeams,
|
||||||
canDoRoleManagement = false,
|
canDoRoleManagement = false,
|
||||||
locale,
|
|
||||||
userProjectsCount,
|
userProjectsCount,
|
||||||
}: ProjectSettingsProps) => {
|
}: ProjectSettingsProps) => {
|
||||||
const [createTeamModalOpen, setCreateTeamModalOpen] = useState(false);
|
const [createTeamModalOpen, setCreateTeamModalOpen] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const addProject = async (data: TProjectUpdateInput) => {
|
const addProject = async (data: TProjectUpdateInput) => {
|
||||||
try {
|
try {
|
||||||
const createProjectResponse = await createProjectAction({
|
const createProjectResponse = await createProjectAction({
|
||||||
@@ -233,7 +231,7 @@ export const ProjectSettings = ({
|
|||||||
<p className="text-sm text-slate-400">{t("common.preview")}</p>
|
<p className="text-sm text-slate-400">{t("common.preview")}</p>
|
||||||
<div className="z-0 h-3/4 w-3/4">
|
<div className="z-0 h-3/4 w-3/4">
|
||||||
<SurveyInline
|
<SurveyInline
|
||||||
survey={getPreviewSurvey(locale, projectName || "my Product")}
|
survey={previewSurvey(projectName || "my Product", t)}
|
||||||
styling={{ brandColor: { light: brandColor } }}
|
styling={{ brandColor: { light: brandColor } }}
|
||||||
isBrandingEnabled={false}
|
isBrandingEnabled={false}
|
||||||
languageCode="default"
|
languageCode="default"
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { authOptions } from "@/modules/auth/lib/authOptions";
|
|||||||
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
|
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Header } from "@/modules/ui/components/header";
|
import { Header } from "@/modules/ui/components/header";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { DEFAULT_BRAND_COLOR, DEFAULT_LOCALE } from "@formbricks/lib/constants";
|
import { DEFAULT_BRAND_COLOR } from "@formbricks/lib/constants";
|
||||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||||
import { getUserProjects } from "@formbricks/lib/project/service";
|
import { getUserProjects } from "@formbricks/lib/project/service";
|
||||||
import { getUserLocale } from "@formbricks/lib/user/service";
|
|
||||||
import { TProjectConfigChannel, TProjectConfigIndustry, TProjectMode } from "@formbricks/types/project";
|
import { TProjectConfigChannel, TProjectConfigIndustry, TProjectMode } from "@formbricks/types/project";
|
||||||
|
|
||||||
interface ProjectSettingsPageProps {
|
interface ProjectSettingsPageProps {
|
||||||
@@ -29,7 +28,7 @@ interface ProjectSettingsPageProps {
|
|||||||
const Page = async (props: ProjectSettingsPageProps) => {
|
const Page = async (props: ProjectSettingsPageProps) => {
|
||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
@@ -39,7 +38,6 @@ const Page = async (props: ProjectSettingsPageProps) => {
|
|||||||
const channel = searchParams.channel || null;
|
const channel = searchParams.channel || null;
|
||||||
const industry = searchParams.industry || null;
|
const industry = searchParams.industry || null;
|
||||||
const mode = searchParams.mode || "surveys";
|
const mode = searchParams.mode || "surveys";
|
||||||
const locale = session?.user.id ? await getUserLocale(session.user.id) : undefined;
|
|
||||||
const projects = await getUserProjects(session.user.id, params.organizationId);
|
const projects = await getUserProjects(session.user.id, params.organizationId);
|
||||||
|
|
||||||
const organizationTeams = await getTeamsByOrganizationId(params.organizationId);
|
const organizationTeams = await getTeamsByOrganizationId(params.organizationId);
|
||||||
@@ -70,7 +68,6 @@ const Page = async (props: ProjectSettingsPageProps) => {
|
|||||||
defaultBrandColor={DEFAULT_BRAND_COLOR}
|
defaultBrandColor={DEFAULT_BRAND_COLOR}
|
||||||
organizationTeams={organizationTeams}
|
organizationTeams={organizationTeams}
|
||||||
canDoRoleManagement={canDoRoleManagement}
|
canDoRoleManagement={canDoRoleManagement}
|
||||||
locale={locale ?? DEFAULT_LOCALE}
|
|
||||||
userProjectsCount={projects.length}
|
userProjectsCount={projects.length}
|
||||||
/>
|
/>
|
||||||
{projects.length >= 1 && (
|
{projects.length >= 1 && (
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]
|
|||||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||||
import { DevEnvironmentBanner } from "@/modules/ui/components/dev-environment-banner";
|
import { DevEnvironmentBanner } from "@/modules/ui/components/dev-environment-banner";
|
||||||
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
||||||
|
import { getTranslate } from "@/tolgee/server";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||||
@@ -18,7 +18,7 @@ const SurveyEditorEnvironmentLayout = async (props) => {
|
|||||||
|
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const t = await getTranslations();
|
const t = await getTranslate();
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
return redirect(`/auth/login`);
|
return redirect(`/auth/login`);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ModalWithTabs } from "@/modules/ui/components/modal-with-tabs";
|
import { ModalWithTabs } from "@/modules/ui/components/modal-with-tabs";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { TActionClass } from "@formbricks/types/action-classes";
|
import { TActionClass } from "@formbricks/types/action-classes";
|
||||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||||
import { CreateNewActionTab } from "./CreateNewActionTab";
|
import { CreateNewActionTab } from "./CreateNewActionTab";
|
||||||
@@ -28,7 +28,7 @@ export const AddActionModal = ({
|
|||||||
isReadOnly,
|
isReadOnly,
|
||||||
environmentId,
|
environmentId,
|
||||||
}: AddActionModalProps) => {
|
}: AddActionModalProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
title: t("environments.surveys.edit.select_saved_action"),
|
title: t("environments.surveys.edit.select_saved_action"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||||
|
|
||||||
interface AddEndingCardButtonProps {
|
interface AddEndingCardButtonProps {
|
||||||
@@ -11,7 +11,7 @@ interface AddEndingCardButtonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AddEndingCardButton = ({ localSurvey, addEndingCard }: AddEndingCardButtonProps) => {
|
export const AddEndingCardButton = ({ localSurvey, addEndingCard }: AddEndingCardButtonProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="group inline-flex rounded-lg border border-slate-300 bg-slate-50 hover:cursor-pointer hover:bg-white"
|
className="group inline-flex rounded-lg border border-slate-300 bg-slate-50 hover:cursor-pointer hover:bg-white"
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
import {
|
import {
|
||||||
@@ -19,14 +19,13 @@ interface AddQuestionButtonProps {
|
|||||||
addQuestion: (question: any) => void;
|
addQuestion: (question: any) => void;
|
||||||
project: TProject;
|
project: TProject;
|
||||||
isCxMode: boolean;
|
isCxMode: boolean;
|
||||||
locale: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddQuestionButton = ({ addQuestion, project, isCxMode, locale }: AddQuestionButtonProps) => {
|
export const AddQuestionButton = ({ addQuestion, project, isCxMode }: AddQuestionButtonProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [hoveredQuestionId, setHoveredQuestionId] = useState<string | null>(null);
|
const [hoveredQuestionId, setHoveredQuestionId] = useState<string | null>(null);
|
||||||
const availableQuestionTypes = isCxMode ? getCXQuestionTypes(locale) : getQuestionTypes(locale);
|
const availableQuestionTypes = isCxMode ? getCXQuestionTypes(t) : getQuestionTypes(t);
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -60,7 +59,7 @@ export const AddQuestionButton = ({ addQuestion, project, isCxMode, locale }: Ad
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
addQuestion({
|
addQuestion({
|
||||||
...universalQuestionPresets,
|
...universalQuestionPresets,
|
||||||
...getQuestionDefaults(questionType.id, project, locale),
|
...getQuestionDefaults(questionType.id, project, t),
|
||||||
id: createId(),
|
id: createId(),
|
||||||
type: questionType.id,
|
type: questionType.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
|||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
|
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { type JSX, useEffect } from "react";
|
import { type JSX, useEffect } from "react";
|
||||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||||
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
|
||||||
@@ -34,7 +34,7 @@ export const AddressQuestionForm = ({
|
|||||||
locale,
|
locale,
|
||||||
}: AddressQuestionFormProps): JSX.Element => {
|
}: AddressQuestionFormProps): JSX.Element => {
|
||||||
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages ?? []);
|
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages ?? []);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
id: "addressLine1",
|
id: "addressLine1",
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/
|
|||||||
import { Slider } from "@/modules/ui/components/slider";
|
import { Slider } from "@/modules/ui/components/slider";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { CheckIcon } from "lucide-react";
|
import { CheckIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { UseFormReturn } from "react-hook-form";
|
import { UseFormReturn } from "react-hook-form";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
import { TProjectStyling } from "@formbricks/types/project";
|
import { TProjectStyling } from "@formbricks/types/project";
|
||||||
@@ -34,7 +34,7 @@ export const BackgroundStylingCard = ({
|
|||||||
isUnsplashConfigured,
|
isUnsplashConfigured,
|
||||||
form,
|
form,
|
||||||
}: BackgroundStylingCardProps) => {
|
}: BackgroundStylingCardProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Input } from "@/modules/ui/components/input";
|
|||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { type JSX, useState } from "react";
|
import { type JSX, useState } from "react";
|
||||||
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
|
||||||
import { TUserLocale } from "@formbricks/types/user";
|
import { TUserLocale } from "@formbricks/types/user";
|
||||||
@@ -34,7 +34,7 @@ export const CTAQuestionForm = ({
|
|||||||
setSelectedLanguageCode,
|
setSelectedLanguageCode,
|
||||||
locale,
|
locale,
|
||||||
}: CTAQuestionFormProps): JSX.Element => {
|
}: CTAQuestionFormProps): JSX.Element => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
value: "internal",
|
value: "internal",
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||||
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
|
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Input } from "@/modules/ui/components/input";
|
import { Input } from "@/modules/ui/components/input";
|
||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { type JSX, useEffect, useState } from "react";
|
import { type JSX, useEffect, useState } from "react";
|
||||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||||
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
|
||||||
@@ -34,7 +36,7 @@ export const CalQuestionForm = ({
|
|||||||
}: CalQuestionFormProps): JSX.Element => {
|
}: CalQuestionFormProps): JSX.Element => {
|
||||||
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
|
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
|
||||||
const [isCalHostEnabled, setIsCalHostEnabled] = useState(!!question.calHost);
|
const [isCalHostEnabled, setIsCalHostEnabled] = useState(!!question.calHost);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isCalHostEnabled) {
|
if (!isCalHostEnabled) {
|
||||||
updateQuestion(questionIdx, { calHost: undefined });
|
updateQuestion(questionIdx, { calHost: undefined });
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { Slider } from "@/modules/ui/components/slider";
|
|||||||
import { Switch } from "@/modules/ui/components/switch";
|
import { Switch } from "@/modules/ui/components/switch";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { CheckIcon } from "lucide-react";
|
import { CheckIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { UseFormReturn } from "react-hook-form";
|
import { UseFormReturn } from "react-hook-form";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
@@ -36,7 +36,7 @@ export const CardStylingSettings = ({
|
|||||||
setOpen,
|
setOpen,
|
||||||
form,
|
form,
|
||||||
}: CardStylingSettingsProps) => {
|
}: CardStylingSettingsProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const isAppSurvey = surveyType === "app";
|
const isAppSurvey = surveyType === "app";
|
||||||
const surveyTypeDerived = isAppSurvey ? "App" : "Link";
|
const surveyTypeDerived = isAppSurvey ? "App" : "Link";
|
||||||
const isLogoVisible = !!project.logo?.url;
|
const isLogoVisible = !!project.logo?.url;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { LogicEditor } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor";
|
import { LogicEditor } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor";
|
||||||
import {
|
import {
|
||||||
getDefaultOperatorForQuestion,
|
getDefaultOperatorForQuestion,
|
||||||
@@ -13,6 +15,7 @@ import {
|
|||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
@@ -22,7 +25,6 @@ import {
|
|||||||
SplitIcon,
|
SplitIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
|
import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
|
||||||
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
|
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
|
||||||
@@ -41,7 +43,7 @@ export function ConditionalLogic({
|
|||||||
questionIdx,
|
questionIdx,
|
||||||
updateQuestion,
|
updateQuestion,
|
||||||
}: ConditionalLogicProps) {
|
}: ConditionalLogicProps) {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const transformedSurvey = useMemo(() => {
|
const transformedSurvey = useMemo(() => {
|
||||||
let modifiedSurvey = replaceHeadlineRecall(localSurvey, "default");
|
let modifiedSurvey = replaceHeadlineRecall(localSurvey, "default");
|
||||||
modifiedSurvey = replaceEndingCardHeadlineRecall(modifiedSurvey, "default");
|
modifiedSurvey = replaceEndingCardHeadlineRecall(modifiedSurvey, "default");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
|
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
|
||||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { type JSX, useState } from "react";
|
import { type JSX, useState } from "react";
|
||||||
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
|
||||||
import { TUserLocale } from "@formbricks/types/user";
|
import { TUserLocale } from "@formbricks/types/user";
|
||||||
@@ -30,7 +30,7 @@ export const ConsentQuestionForm = ({
|
|||||||
locale,
|
locale,
|
||||||
}: ConsentQuestionFormProps): JSX.Element => {
|
}: ConsentQuestionFormProps): JSX.Element => {
|
||||||
const [firstRender, setFirstRender] = useState(true);
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
<QuestionFormInput
|
<QuestionFormInput
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
|||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
|
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { type JSX, useEffect } from "react";
|
import { type JSX, useEffect } from "react";
|
||||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||||
import { TSurvey, TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
|
||||||
@@ -33,7 +33,7 @@ export const ContactInfoQuestionForm = ({
|
|||||||
setSelectedLanguageCode,
|
setSelectedLanguageCode,
|
||||||
locale,
|
locale,
|
||||||
}: ContactInfoQuestionFormProps): JSX.Element => {
|
}: ContactInfoQuestionFormProps): JSX.Element => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages ?? []);
|
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages ?? []);
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { isValidCssSelector } from "@/app/lib/actionClass/actionClass";
|
import { isValidCssSelector } from "@/app/lib/actionClass/actionClass";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { CodeActionForm } from "@/modules/ui/components/code-action-form";
|
import { CodeActionForm } from "@/modules/ui/components/code-action-form";
|
||||||
@@ -7,7 +9,7 @@ import { Label } from "@/modules/ui/components/label";
|
|||||||
import { NoCodeActionForm } from "@/modules/ui/components/no-code-action-form";
|
import { NoCodeActionForm } from "@/modules/ui/components/no-code-action-form";
|
||||||
import { TabToggle } from "@/modules/ui/components/tab-toggle";
|
import { TabToggle } from "@/modules/ui/components/tab-toggle";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
@@ -38,7 +40,7 @@ export const CreateNewActionTab = ({
|
|||||||
setLocalSurvey,
|
setLocalSurvey,
|
||||||
environmentId,
|
environmentId,
|
||||||
}: CreateNewActionTabProps) => {
|
}: CreateNewActionTabProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const actionClassNames = useMemo(
|
const actionClassNames = useMemo(
|
||||||
() => actionClasses.map((actionClass) => actionClass.name),
|
() => actionClasses.map((actionClass) => actionClass.name),
|
||||||
[actionClasses]
|
[actionClasses]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||||
import { Button } from "@/modules/ui/components/button";
|
import { Button } from "@/modules/ui/components/button";
|
||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import type { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||||
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
|
||||||
@@ -48,7 +50,7 @@ export const DateQuestionForm = ({
|
|||||||
locale,
|
locale,
|
||||||
}: IDateQuestionFormProps): JSX.Element => {
|
}: IDateQuestionFormProps): JSX.Element => {
|
||||||
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
|
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import { useSortable } from "@dnd-kit/sortable";
|
|||||||
import { CSS } from "@dnd-kit/utilities";
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { GripIcon, Handshake, Undo2 } from "lucide-react";
|
import { GripIcon, Handshake, Undo2 } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
@@ -59,7 +59,7 @@ export const EditEndingCard = ({
|
|||||||
locale,
|
locale,
|
||||||
}: EditEndingCardProps) => {
|
}: EditEndingCardProps) => {
|
||||||
const endingCard = localSurvey.endings[endingCardIndex];
|
const endingCard = localSurvey.endings[endingCardIndex];
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const isRedirectToUrlDisabled = isFormbricksCloud
|
const isRedirectToUrlDisabled = isFormbricksCloud
|
||||||
? plan === "free" && endingCard.type !== "redirectToUrl"
|
? plan === "free" && endingCard.type !== "redirectToUrl"
|
||||||
: false;
|
: false;
|
||||||
@@ -231,7 +231,6 @@ export const EditEndingCard = ({
|
|||||||
updateCard={() => {}}
|
updateCard={() => {}}
|
||||||
addCard={addEndingCard}
|
addCard={addEndingCard}
|
||||||
cardType="ending"
|
cardType="ending"
|
||||||
locale={locale}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { FileInput } from "@/modules/ui/components/file-input";
|
|||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { Switch } from "@/modules/ui/components/switch";
|
import { Switch } from "@/modules/ui/components/switch";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { Hand } from "lucide-react";
|
import { Hand } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
@@ -35,7 +35,7 @@ export const EditWelcomeCard = ({
|
|||||||
setSelectedLanguageCode,
|
setSelectedLanguageCode,
|
||||||
locale,
|
locale,
|
||||||
}: EditWelcomeCardProps) => {
|
}: EditWelcomeCardProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [firstRender, setFirstRender] = useState(true);
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
const path = usePathname();
|
const path = usePathname();
|
||||||
const environmentId = path?.split("/environments/")[1]?.split("/")[0];
|
const environmentId = path?.split("/environments/")[1]?.split("/")[0];
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import {
|
|||||||
} from "@/modules/ui/components/dropdown-menu";
|
} from "@/modules/ui/components/dropdown-menu";
|
||||||
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, EllipsisIcon, TrashIcon } from "lucide-react";
|
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, EllipsisIcon, TrashIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
QUESTIONS_ICON_MAP,
|
|
||||||
getCXQuestionNameMap,
|
getCXQuestionNameMap,
|
||||||
getQuestionDefaults,
|
getQuestionDefaults,
|
||||||
|
getQuestionIconMap,
|
||||||
getQuestionNameMap,
|
getQuestionNameMap,
|
||||||
} from "@formbricks/lib/utils/questions";
|
} from "@formbricks/lib/utils/questions";
|
||||||
import { TProject } from "@formbricks/types/project";
|
import { TProject } from "@formbricks/types/project";
|
||||||
@@ -44,7 +44,6 @@ interface EditorCardMenuProps {
|
|||||||
cardType: "question" | "ending";
|
cardType: "question" | "ending";
|
||||||
project?: TProject;
|
project?: TProject;
|
||||||
isCxMode?: boolean;
|
isCxMode?: boolean;
|
||||||
locale: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorCardMenu = ({
|
export const EditorCardMenu = ({
|
||||||
@@ -60,9 +59,9 @@ export const EditorCardMenu = ({
|
|||||||
addCard,
|
addCard,
|
||||||
cardType,
|
cardType,
|
||||||
isCxMode = false,
|
isCxMode = false,
|
||||||
locale,
|
|
||||||
}: EditorCardMenuProps) => {
|
}: EditorCardMenuProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
|
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
|
||||||
const [logicWarningModal, setLogicWarningModal] = useState(false);
|
const [logicWarningModal, setLogicWarningModal] = useState(false);
|
||||||
const [changeToType, setChangeToType] = useState(() => {
|
const [changeToType, setChangeToType] = useState(() => {
|
||||||
if (card.type !== "endScreen" && card.type !== "redirectToUrl") {
|
if (card.type !== "endScreen" && card.type !== "redirectToUrl") {
|
||||||
@@ -76,7 +75,7 @@ export const EditorCardMenu = ({
|
|||||||
? survey.questions.length === 1
|
? survey.questions.length === 1
|
||||||
: survey.type === "link" && survey.endings.length === 1;
|
: survey.type === "link" && survey.endings.length === 1;
|
||||||
|
|
||||||
const availableQuestionTypes = isCxMode ? getCXQuestionNameMap(locale) : getQuestionNameMap(locale);
|
const availableQuestionTypes = isCxMode ? getCXQuestionNameMap(t) : getQuestionNameMap(t);
|
||||||
|
|
||||||
const changeQuestionType = (type?: TSurveyQuestionTypeEnum) => {
|
const changeQuestionType = (type?: TSurveyQuestionTypeEnum) => {
|
||||||
if (!type) return;
|
if (!type) return;
|
||||||
@@ -84,7 +83,7 @@ export const EditorCardMenu = ({
|
|||||||
const { headline, required, subheader, imageUrl, videoUrl, buttonLabel, backButtonLabel } =
|
const { headline, required, subheader, imageUrl, videoUrl, buttonLabel, backButtonLabel } =
|
||||||
card as TSurveyQuestion;
|
card as TSurveyQuestion;
|
||||||
|
|
||||||
const questionDefaults = getQuestionDefaults(type, project, locale);
|
const questionDefaults = getQuestionDefaults(type, project, t);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(type === TSurveyQuestionTypeEnum.MultipleChoiceSingle &&
|
(type === TSurveyQuestionTypeEnum.MultipleChoiceSingle &&
|
||||||
@@ -123,7 +122,7 @@ export const EditorCardMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addQuestionCardBelow = (type: TSurveyQuestionTypeEnum) => {
|
const addQuestionCardBelow = (type: TSurveyQuestionTypeEnum) => {
|
||||||
const questionDefaults = getQuestionDefaults(type, project, locale);
|
const questionDefaults = getQuestionDefaults(type, project, t);
|
||||||
|
|
||||||
addCard(
|
addCard(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RecallWrapper } from "@/modules/surveys/components/QuestionFormInput/co
|
|||||||
import { Input } from "@/modules/ui/components/input";
|
import { Input } from "@/modules/ui/components/input";
|
||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { Switch } from "@/modules/ui/components/switch";
|
import { Switch } from "@/modules/ui/components/switch";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||||
@@ -34,7 +34,7 @@ export const EndScreenForm = ({
|
|||||||
endingCard,
|
endingCard,
|
||||||
locale,
|
locale,
|
||||||
}: EndScreenFormProps) => {
|
}: EndScreenFormProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [showEndingCardCTA, setshowEndingCardCTA] = useState<boolean>(
|
const [showEndingCardCTA, setshowEndingCardCTA] = useState<boolean>(
|
||||||
endingCard.type === "endScreen" &&
|
endingCard.type === "endScreen" &&
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { Button } from "@/modules/ui/components/button";
|
|||||||
import { Input } from "@/modules/ui/components/input";
|
import { Input } from "@/modules/ui/components/input";
|
||||||
import { useGetBillingInfo } from "@/modules/utils/hooks/useGetBillingInfo";
|
import { useGetBillingInfo } from "@/modules/utils/hooks/useGetBillingInfo";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon, XCircleIcon } from "lucide-react";
|
import { PlusIcon, XCircleIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { type JSX, useMemo, useState } from "react";
|
import { type JSX, useMemo, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
@@ -45,7 +45,7 @@ export const FileUploadQuestionForm = ({
|
|||||||
locale,
|
locale,
|
||||||
}: FileUploadFormProps): JSX.Element => {
|
}: FileUploadFormProps): JSX.Element => {
|
||||||
const [extension, setExtension] = useState("");
|
const [extension, setExtension] = useState("");
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [isMaxSizeError, setMaxSizeError] = useState(false);
|
const [isMaxSizeError, setMaxSizeError] = useState(false);
|
||||||
const {
|
const {
|
||||||
billingInfo,
|
billingInfo,
|
||||||
@@ -227,7 +227,7 @@ export const FileUploadQuestionForm = ({
|
|||||||
className="underline"
|
className="underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={`/environments/${localSurvey.environmentId}/settings/billing`}>
|
href={`/environments/${localSurvey.environmentId}/settings/billing`}>
|
||||||
{t("environments.surveys.edit.upgrade_your_plan")}
|
{t("common.please_upgrade_your_plan")}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { ColorPicker } from "@/modules/ui/components/color-picker";
|
|||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { CheckIcon, SparklesIcon } from "lucide-react";
|
import { CheckIcon, SparklesIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { UseFormReturn } from "react-hook-form";
|
import { UseFormReturn } from "react-hook-form";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
@@ -30,7 +30,7 @@ export const FormStylingSettings = ({
|
|||||||
setOpen,
|
setOpen,
|
||||||
form,
|
form,
|
||||||
}: FormStylingSettingsProps) => {
|
}: FormStylingSettingsProps) => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const brandColor = form.watch("brandColor.light") || COLOR_DEFAULTS.brandColor;
|
const brandColor = form.watch("brandColor.light") || COLOR_DEFAULTS.brandColor;
|
||||||
const background = form.watch("background");
|
const background = form.watch("background");
|
||||||
const highlightBorderColor = form.watch("highlightBorderColor");
|
const highlightBorderColor = form.watch("highlightBorderColor");
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { Switch } from "@/modules/ui/components/switch";
|
|||||||
import { Tag } from "@/modules/ui/components/tag";
|
import { Tag } from "@/modules/ui/components/tag";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { EyeOff } from "lucide-react";
|
import { EyeOff } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
@@ -32,7 +32,7 @@ export const HiddenFieldsCard = ({
|
|||||||
}: HiddenFieldsCardProps) => {
|
}: HiddenFieldsCardProps) => {
|
||||||
const open = activeQuestionId == "hidden";
|
const open = activeQuestionId == "hidden";
|
||||||
const [hiddenField, setHiddenField] = useState<string>("");
|
const [hiddenField, setHiddenField] = useState<string>("");
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const setOpen = (open: boolean) => {
|
const setOpen = (open: boolean) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
setActiveQuestionId("hidden");
|
setActiveQuestionId("hidden");
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { getDefaultEndingCard } from "@/app/lib/templates";
|
||||||
import { Badge } from "@/modules/ui/components/badge";
|
import { Badge } from "@/modules/ui/components/badge";
|
||||||
import { Label } from "@/modules/ui/components/label";
|
import { Label } from "@/modules/ui/components/label";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { AlertCircleIcon, CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
|
import { AlertCircleIcon, CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
import { getDefaultEndingCard } from "@formbricks/lib/templates";
|
|
||||||
import { TEnvironment } from "@formbricks/types/environment";
|
import { TEnvironment } from "@formbricks/types/environment";
|
||||||
import { TSegment } from "@formbricks/types/segment";
|
import { TSegment } from "@formbricks/types/segment";
|
||||||
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
|
||||||
@@ -19,13 +19,12 @@ interface HowToSendCardProps {
|
|||||||
localSurvey: TSurvey;
|
localSurvey: TSurvey;
|
||||||
setLocalSurvey: (survey: TSurvey | ((TSurvey: TSurvey) => TSurvey)) => void;
|
setLocalSurvey: (survey: TSurvey | ((TSurvey: TSurvey) => TSurvey)) => void;
|
||||||
environment: TEnvironment;
|
environment: TEnvironment;
|
||||||
locale: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment, locale }: HowToSendCardProps) => {
|
export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowToSendCardProps) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [appSetupCompleted, setAppSetupCompleted] = useState(false);
|
const [appSetupCompleted, setAppSetupCompleted] = useState(false);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (environment) {
|
if (environment) {
|
||||||
setAppSetupCompleted(environment.appSetupCompleted);
|
setAppSetupCompleted(environment.appSetupCompleted);
|
||||||
@@ -35,7 +34,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment, locale
|
|||||||
const setSurveyType = (type: TSurveyType) => {
|
const setSurveyType = (type: TSurveyType) => {
|
||||||
const endingsTemp = localSurvey.endings;
|
const endingsTemp = localSurvey.endings;
|
||||||
if (type === "link" && localSurvey.endings.length === 0) {
|
if (type === "link" && localSurvey.endings.length === 0) {
|
||||||
endingsTemp.push(getDefaultEndingCard(localSurvey.languages, locale));
|
endingsTemp.push(getDefaultEndingCard(localSurvey.languages, t));
|
||||||
}
|
}
|
||||||
setLocalSurvey((prevSurvey) => ({
|
setLocalSurvey((prevSurvey) => ({
|
||||||
...prevSurvey,
|
...prevSurvey,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { LogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions";
|
import { LogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions";
|
||||||
import { LogicEditorConditions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions";
|
import { LogicEditorConditions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions";
|
||||||
import {
|
import {
|
||||||
@@ -7,11 +9,11 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/modules/ui/components/select";
|
} from "@/modules/ui/components/select";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { ArrowRightIcon } from "lucide-react";
|
import { ArrowRightIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { ReactElement, useMemo } from "react";
|
import { ReactElement, useMemo } from "react";
|
||||||
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||||
import { QUESTIONS_ICON_MAP } from "@formbricks/lib/utils/questions";
|
import { getQuestionIconMap } from "@formbricks/lib/utils/questions";
|
||||||
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
|
||||||
|
|
||||||
interface LogicEditorProps {
|
interface LogicEditorProps {
|
||||||
@@ -33,8 +35,8 @@ export function LogicEditor({
|
|||||||
logicIdx,
|
logicIdx,
|
||||||
isLast,
|
isLast,
|
||||||
}: LogicEditorProps) {
|
}: LogicEditorProps) {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
|
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
|
||||||
const fallbackOptions = useMemo(() => {
|
const fallbackOptions = useMemo(() => {
|
||||||
let options: {
|
let options: {
|
||||||
icon?: ReactElement;
|
icon?: ReactElement;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getActionObjectiveOptions,
|
getActionObjectiveOptions,
|
||||||
getActionOperatorOptions,
|
getActionOperatorOptions,
|
||||||
@@ -14,8 +16,8 @@ import {
|
|||||||
} from "@/modules/ui/components/dropdown-menu";
|
} from "@/modules/ui/components/dropdown-menu";
|
||||||
import { InputCombobox } from "@/modules/ui/components/input-combo-box";
|
import { InputCombobox } from "@/modules/ui/components/input-combo-box";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { CopyIcon, CornerDownRightIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
|
import { CopyIcon, CornerDownRightIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { getUpdatedActionBody } from "@formbricks/lib/surveyLogic/utils";
|
import { getUpdatedActionBody } from "@formbricks/lib/surveyLogic/utils";
|
||||||
import {
|
import {
|
||||||
TActionNumberVariableCalculateOperator,
|
TActionNumberVariableCalculateOperator,
|
||||||
@@ -46,7 +48,7 @@ export function LogicEditorActions({
|
|||||||
questionIdx,
|
questionIdx,
|
||||||
}: LogicEditorActions) {
|
}: LogicEditorActions) {
|
||||||
const actions = logicItem.actions;
|
const actions = logicItem.actions;
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const handleActionsChange = (
|
const handleActionsChange = (
|
||||||
operation: "remove" | "addBelow" | "duplicate" | "update",
|
operation: "remove" | "addBelow" | "duplicate" | "update",
|
||||||
actionIdx: number,
|
actionIdx: number,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getConditionOperatorOptions,
|
getConditionOperatorOptions,
|
||||||
getConditionValueOptions,
|
getConditionValueOptions,
|
||||||
@@ -13,8 +15,8 @@ import {
|
|||||||
import { InputCombobox, TComboboxOption } from "@/modules/ui/components/input-combo-box";
|
import { InputCombobox, TComboboxOption } from "@/modules/ui/components/input-combo-box";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { CopyIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon, WorkflowIcon } from "lucide-react";
|
import { CopyIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon, WorkflowIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { cn } from "@formbricks/lib/cn";
|
import { cn } from "@formbricks/lib/cn";
|
||||||
import {
|
import {
|
||||||
addConditionBelow,
|
addConditionBelow,
|
||||||
@@ -54,7 +56,7 @@ export function LogicEditorConditions({
|
|||||||
updateQuestion,
|
updateQuestion,
|
||||||
depth = 0,
|
depth = 0,
|
||||||
}: LogicEditorConditionsProps) {
|
}: LogicEditorConditionsProps) {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const [parent] = useAutoAnimate();
|
const [parent] = useAutoAnimate();
|
||||||
|
|
||||||
const handleAddConditionBelow = (resourceId: string) => {
|
const handleAddConditionBelow = (resourceId: string) => {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { Label } from "@/modules/ui/components/label";
|
|||||||
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
|
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
|
||||||
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon, TrashIcon } from "lucide-react";
|
import { PlusIcon, TrashIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import type { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||||
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
|
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
|
||||||
@@ -37,7 +37,7 @@ export const MatrixQuestionForm = ({
|
|||||||
locale,
|
locale,
|
||||||
}: MatrixQuestionFormProps): JSX.Element => {
|
}: MatrixQuestionFormProps): JSX.Element => {
|
||||||
const languageCodes = extractLanguageCodes(localSurvey.languages);
|
const languageCodes = extractLanguageCodes(localSurvey.languages);
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
// Function to add a new Label input field
|
// Function to add a new Label input field
|
||||||
const handleAddLabel = (type: "row" | "column") => {
|
const handleAddLabel = (type: "row" | "column") => {
|
||||||
if (type === "row") {
|
if (type === "row") {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { DndContext } from "@dnd-kit/core";
|
|||||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
|
import { useTranslate } from "@tolgee/react";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { type JSX, useEffect, useRef, useState } from "react";
|
import { type JSX, useEffect, useRef, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||||
@@ -46,7 +46,7 @@ export const MultipleChoiceQuestionForm = ({
|
|||||||
setSelectedLanguageCode,
|
setSelectedLanguageCode,
|
||||||
locale,
|
locale,
|
||||||
}: MultipleChoiceQuestionFormProps): JSX.Element => {
|
}: MultipleChoiceQuestionFormProps): JSX.Element => {
|
||||||
const t = useTranslations();
|
const { t } = useTranslate();
|
||||||
const lastChoiceRef = useRef<HTMLInputElement>(null);
|
const lastChoiceRef = useRef<HTMLInputElement>(null);
|
||||||
const [isNew, setIsNew] = useState(true);
|
const [isNew, setIsNew] = useState(true);
|
||||||
const [isInvalidValue, setisInvalidValue] = useState<string | null>(null);
|
const [isInvalidValue, setisInvalidValue] = useState<string | null>(null);
|
||||||
|
|||||||