Compare commits

..

22 Commits

Author SHA1 Message Date
Dhruwang Jariwala
36378e9c23 feat: tolgee (#4692)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2025-02-07 05:49:35 +00:00
Piyush Gupta
9c33e77755 fix: phone number validations (#4708)
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
2025-02-07 04:29:30 +00:00
Dhruwang Jariwala
88cb4c742f feat: activepieces integration (#4711) 2025-02-06 15:34:02 +00:00
Anshuman Pandey
475cce8253 fix: prisma docker libsso issue (#4717) 2025-02-06 14:37:59 +00:00
Anshuman Pandey
a86c1738d1 fix: renames init to setup (#4714) 2025-02-05 10:12:14 +00:00
Piyush Gupta
96a4d02c80 fix: Branch test coverage (#4710) 2025-02-05 09:46:38 +00:00
Anshuman Pandey
bb6df783ab feat: react native sdk v2 (#4616)
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
2025-02-04 15:32:04 +00:00
Matti Nannt
26cca5c2f8 fix: docker corepack issues (#4709) 2025-02-04 12:25:50 +00:00
Kartik Saini
7e3dd7d624 fix: what_is_a_pannel_answer text key missing (#4679) 2025-02-04 09:45:16 +00:00
Paribesh Nepal
db9a53f923 fix: userError (#4703)
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2025-02-04 09:32:57 +00:00
Piyush Gupta
92ae4786f0 feat: test-coverage (#4701) 2025-02-04 07:59:16 +00:00
Dhruwang Jariwala
b35cf14d32 fix: line height in description (#4687)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2025-02-03 21:17:15 +00:00
Anshuman Pandey
14374b55d2 fix(client-api): apis openapi spec (#4706) 2025-02-03 20:59:32 +00:00
Matthias Nannt
5a919018c5 chore: prepare 3.1.4 release 2025-02-03 20:28:53 +01:00
Matthias Nannt
6ac73d3f25 chore: use latest node:22 version in Dockerfile 2025-02-03 20:22:36 +01:00
dependabot[bot]
510fe3902e chore(deps): bump the npm_and_yarn group across 2 directories with 2 updates (#4686)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 10:05:27 +00:00
Dhruwang Jariwala
2bc23594ad fix: open file in new tab (#4697) 2025-01-31 07:08:02 +00:00
Piyush Gupta
06e00f3066 chore: invite types (#4613)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2025-01-31 06:56:55 +00:00
Dhruwang Jariwala
9b3d409695 fix: org leaving issue and minor tweaks (#4691) 2025-01-31 03:59:26 +00:00
Dhruwang Jariwala
f7f5737abf fix: email inconsistencies (#4678) 2025-01-30 09:54:48 +00:00
Matti Nannt
458f135ee1 chore(cloud): move from customer-io to brevo (#4681)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
2025-01-29 09:18:16 +00:00
Anshuman Pandey
8e116bf62d fix: recall in follow up (#4680) 2025-01-29 06:04:22 +00:00
632 changed files with 14899 additions and 8944 deletions

View File

@@ -167,9 +167,9 @@ ENTERPRISE_LICENSE_KEY=
# DEFAULT_ORGANIZATION_ID=
# DEFAULT_ORGANIZATION_ROLE=owner
# Send new users to customer.io
# CUSTOMER_IO_API_KEY=
# CUSTOMER_IO_SITE_ID=
# Send new users to Brevo
# BREVO_API_KEY=
# BREVO_LIST_ID=
# Ignore Rate Limiting across the Formbricks app
# RATE_LIMITING_DISABLED=1

View File

@@ -1,11 +1,11 @@
name: SonarQube
on:
workflow_dispatch:
# push:
# branches:
# - main
# pull_request:
# types: [opened, synchronize, reopened]
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
@@ -16,6 +16,35 @@ jobs:
- uses: actions/checkout@v4
with:
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
uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203
env:

View 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
View 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
View File

@@ -58,4 +58,5 @@ packages/lib/uploads
# js compiled assets
apps/web/public/js
packages/database/migrations

1
.husky/post-checkout Normal file
View File

@@ -0,0 +1 @@
echo "{\"branchName\": \"$(git rev-parse --abbrev-ref HEAD)\"}" > ../branch.json

1
.husky/post-commit Normal file
View File

@@ -0,0 +1 @@
echo "{\"branchName\": \"$(git rev-parse --abbrev-ref HEAD)\"}" > ../branch.json

View File

@@ -1 +1,3 @@
pnpm lint-staged
pnpm lint-staged
pnpm tolgee-pull || true
git add packages/lib/messages/*.json

31
.tolgeerc.json Normal file
View 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
}

View File

@@ -1,2 +1,2 @@
EXPO_PUBLIC_API_HOST=http://192.168.178.20:3000
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clzr04nkd000bcdl110j0ijyq
EXPO_PUBLIC_APP_URL=http://192.168.0.197:3000
EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=cm5p0cs7r000819182b32j0a1

View File

@@ -18,6 +18,7 @@
},
"jsEngine": "hermes",
"name": "react-native-demo",
"newArchEnabled": true,
"orientation": "portrait",
"slug": "react-native-demo",
"splash": {

View File

@@ -13,16 +13,17 @@
"dependencies": {
"@formbricks/js": "workspace:*",
"@formbricks/react-native": "workspace:*",
"expo": "52.0.18",
"expo-status-bar": "2.0.0",
"@react-native-async-storage/async-storage": "2.1.0",
"expo": "52.0.28",
"expo-status-bar": "2.0.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-native": "0.76.5",
"react-native": "0.76.6",
"react-native-webview": "13.12.5"
},
"devDependencies": {
"@babel/core": "7.26.0",
"@types/react": "19.0.1",
"@types/react": "18.3.18",
"typescript": "5.7.2"
},
"private": true

View File

@@ -1,7 +1,14 @@
import { StatusBar } from "expo-status-bar";
import React, { type JSX } from "react";
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();
@@ -10,35 +17,92 @@ export default function App(): JSX.Element {
throw new Error("EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID is required");
}
if (!process.env.EXPO_PUBLIC_API_HOST) {
throw new Error("EXPO_PUBLIC_API_HOST is required");
if (!process.env.EXPO_PUBLIC_APP_URL) {
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 (
<View style={styles.container}>
<Text>Formbricks React Native SDK Demo</Text>
<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);
});
}}
/>
<View
style={{
display: "flex",
flexDirection: "column",
gap: 10,
}}>
<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" />
<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 File

@@ -357,17 +357,11 @@ Now, update your App.js/App.tsx file to initialize Formbricks:
// other imports
import Formbricks from "@formbricks/react-native";
const config = {
environmentId: "<environment-id>",
apiHost: "<api-host>",
userId: "<user-id>", // optional
};
export default function App() {
return (
<>
{/* 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">
Formbricks Environment ID.
</Property>
<Property name="api-host" type="string">
<Property name="app-url" type="string">
URL of the hosted Formbricks instance.
</Property>
</Properties>

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View 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"
/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -120,6 +120,7 @@ export const navigation: NavGroup[] = [
{ title: "Airtable", href: "/developer-docs/integrations/airtable" },
{ title: "Google Sheets", href: "/developer-docs/integrations/google-sheets" },
{ title: "Make", href: "/developer-docs/integrations/make" },
{ title: "Activepieces", href: "/developer-docs/integrations/activepieces" },
{ title: "n8n", href: "/developer-docs/integrations/n8n" },
{ title: "Notion", href: "/developer-docs/integrations/notion" },
{ title: "Slack", href: "/developer-docs/integrations/slack" },

View File

@@ -35,33 +35,41 @@ tags:
sessions, and responses within the Formbricks platform. This API is ideal
for client-side interactions, as it doesn't expose sensitive information.
- [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
- Displays API - Mark
Survey as Displayed or Responded for a Person
- [People API](https://formbricks.com/docs/api/client/people) - Create &
update people (e.g. attributes)
- Responses API - Create & update responses for a survey
- 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
description: >-
Displays are metrics used to measure the number of times a survey was
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
description: >-
Responses are captured whenever a user fills in your survey either
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
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
@@ -436,139 +444,424 @@ paths:
code: internal_server_error
message: Person with ID 2 not found
details: {}
/api/v1/client/{environmentId}/people:
post:
/api/v1/client/{environmentId}/environment:
get:
tags:
- Client API > People
summary: Create Person
description: Creates a person in Formbricks with the given userId, it must be unique.
requestBody:
content:
application/json:
schema:
type: object
example:
userId: "{{userId}}"
- Client API > Environment
summary: Get Environment State
description: >-
Retrieves the environment state to be used in Formbricks SDKs
parameters:
- name: environmentId
in: path
required: true
schema:
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
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:
"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
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:
application/json:
schema:
type: object
example:
data:
userId: Shubham
"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
properties:
data:
type: object
properties:
userId:
type: string
description: The user ID of the contact
segments:
type: array
description: List of segment IDs the contact belongs to
items:
type: string
displays:
type: array
description: List of survey displays for this contact
items:
type: object
properties:
surveyId:
type: string
createdAt:
type: string
format: date-time
responses:
type: array
description: List of survey IDs the contact has responded to
items:
type: string
lastDisplayAt:
type: string
format: date-time
nullable: true
description: Timestamp of the last survey display
example:
userId: "user-123"
segments: ["fi8f9oekza95wwszrptidivq", "zgwrv8eg7vfavdhzv1s0po1w"]
displays: [{ surveyId: "pjogp5a1wyxon6umplmf49b8", createdAt: "2024-04-23T08:59:37.550Z" }]
responses: ["pjogp5a1wyxon6umplmf49b8"]
lastDisplayAt: "2024-04-23T08:59:37.550Z"
"403":
description: Forbidden
content:
application/json:
schema:
type: object
example:
code: bad_request
message: userId is required
details:
environmentId: clurwouax000azffxt7n5unn3
/api/v1/client/{environmentId}/people/{userId}/attributes:
properties:
code:
type: string
example: "forbidden"
message:
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:
tags:
- Client API > People
summary: Update Person
- Client API > Contacts
summary: Update Contact (Attributes)
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.
requestBody:
content:
@@ -713,6 +1006,280 @@ paths:
Unable to complete request: Expected property name or '}' in
JSON at position 29
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:
post:
tags:

View File

@@ -18,6 +18,7 @@ FROM node:22-alpine3.20 AS base
FROM base AS installer
# Enable corepack and prepare pnpm
RUN npm install -g corepack@latest
RUN corepack enable
# 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
#
FROM base AS runner
RUN npm install -g corepack@latest
RUN corepack enable
RUN apk add --no-cache curl \

View File

@@ -1,8 +1,8 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { useTranslate } from "@tolgee/react";
import { ArrowRight } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { cn } from "@formbricks/lib/cn";
@@ -23,7 +23,7 @@ export const ConnectWithFormbricks = ({
widgetSetupCompleted,
channel,
}: ConnectWithFormbricksProps) => {
const t = useTranslations();
const { t } = useTranslate();
const router = useRouter();
const handleFinishOnboarding = async () => {
router.push(`/environments/${environment.id}/surveys`);

View File

@@ -4,7 +4,7 @@ import { Button } from "@/modules/ui/components/button";
import { CodeBlock } from "@/modules/ui/components/code-block";
import { Html5Icon, NpmIcon } from "@/modules/ui/components/icons";
import { TabBar } from "@/modules/ui/components/tab-bar";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import Link from "next/link";
import "prismjs/themes/prism.css";
import { useState } from "react";
@@ -29,7 +29,7 @@ export const OnboardingSetupInstructions = ({
channel,
widgetSetupCompleted,
}: OnboardingSetupInstructionsProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [activeTab, setActiveTab] = useState(tabs[0].id);
const htmlSnippetForAppSurveys = `<!-- START Formbricks Surveys -->
<script type="text/javascript">

View File

@@ -1,8 +1,8 @@
import { ConnectWithFormbricks } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
import { XIcon } from "lucide-react";
import { getTranslations } from "next-intl/server";
import Link from "next/link";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
@@ -16,7 +16,7 @@ interface ConnectPageProps {
const Page = async (props: ConnectPageProps) => {
const params = await props.params;
const t = await getTranslations();
const t = await getTranslate();
const environment = await getEnvironment(params.environmentId);
if (!environment) {

View File

@@ -5,8 +5,8 @@ import { getXMTemplates } from "@/app/(app)/(onboarding)/environments/[environme
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { createSurveyAction } from "@/modules/surveys/components/TemplateList/actions";
import { useTranslate } from "@tolgee/react";
import { ActivityIcon, ShoppingCartIcon, SmileIcon, StarIcon, ThumbsUpIcon, UsersIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
@@ -23,7 +23,7 @@ interface XMTemplateListProps {
export const XMTemplateList = ({ project, user, environmentId }: XMTemplateListProps) => {
const [activeTemplateId, setActiveTemplateId] = useState<number | null>(null);
const t = useTranslations();
const { t } = useTranslate();
const router = useRouter();
const createSurvey = async (activeTemplate: TXMTemplate) => {
@@ -47,7 +47,7 @@ export const XMTemplateList = ({ project, user, environmentId }: XMTemplateListP
const handleTemplateClick = (templateIdx: number) => {
setActiveTemplateId(templateIdx);
const template = getXMTemplates(user.locale)[templateIdx];
const template = getXMTemplates(t)[templateIdx];
const newTemplate = replacePresetPlaceholders(template, project);
createSurvey(newTemplate);
};

View File

@@ -5,7 +5,7 @@ import { TXMTemplate } from "@formbricks/types/templates";
// replace all occurences of projectName with the actual project name in the current template
export const replacePresetPlaceholders = (template: TXMTemplate, project: TProject) => {
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) => {
return replaceQuestionPresetPlaceholders(question, project);
});

View File

@@ -1,25 +1,18 @@
import { getDefaultEndingCard } from "@/app/lib/templates";
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 { 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) {
console.error(`Error in ${context}:`, error);
}
export const getXMSurveyDefault = (locale: string): TXMTemplate => {
export const getXMSurveyDefault = (t: TFnType): TXMTemplate => {
try {
if (!validateLocale(locale)) {
throw new Error("Invalid locale");
}
return {
name: "",
endings: [getDefaultEndingCard([], locale)],
endings: [getDefaultEndingCard([], t)],
questions: [],
styling: {
overwriteThemeStyling: true,
@@ -31,24 +24,24 @@ export const getXMSurveyDefault = (locale: string): TXMTemplate => {
}
};
const NPSSurvey = (locale: string): TXMTemplate => {
const npsSurvey = (t: TFnType): TXMTemplate => {
return {
...getXMSurveyDefault(locale),
name: translate("nps_survey_name", locale),
...getXMSurveyDefault(t),
name: t("templates.nps_survey_name"),
questions: [
{
id: createId(),
type: TSurveyQuestionTypeEnum.NPS,
headline: { default: translate("nps_survey_question_1_headline", locale) },
headline: { default: t("templates.nps_survey_question_1_headline") },
required: true,
lowerLabel: { default: translate("nps_survey_question_1_lower_label", locale) },
upperLabel: { default: translate("nps_survey_question_1_upper_label", locale) },
lowerLabel: { default: t("templates.nps_survey_question_1_lower_label") },
upperLabel: { default: t("templates.nps_survey_question_1_upper_label") },
isColorCodingEnabled: true,
},
{
id: createId(),
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("nps_survey_question_2_headline", locale) },
headline: { default: t("templates.nps_survey_question_2_headline") },
required: false,
inputType: "text",
charLimit: {
@@ -58,7 +51,7 @@ const NPSSurvey = (locale: string): TXMTemplate => {
{
id: createId(),
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("nps_survey_question_3_headline", locale) },
headline: { default: t("templates.nps_survey_question_3_headline") },
required: false,
inputType: "text",
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 defaultSurvey = getXMSurveyDefault(locale);
const defaultSurvey = getXMSurveyDefault(t);
return {
...defaultSurvey,
name: translate("star_rating_survey_name", locale),
name: t("templates.star_rating_survey_name"),
questions: [
{
id: reusableQuestionIds[0],
@@ -112,15 +105,15 @@ const StarRatingSurvey = (locale: string): TXMTemplate => {
],
range: 5,
scale: "number",
headline: { default: translate("star_rating_survey_question_1_headline", locale) },
headline: { default: t("templates.star_rating_survey_question_1_headline") },
required: true,
lowerLabel: { default: translate("star_rating_survey_question_1_lower_label", locale) },
upperLabel: { default: translate("star_rating_survey_question_1_upper_label", locale) },
lowerLabel: { default: t("templates.star_rating_survey_question_1_lower_label") },
upperLabel: { default: t("templates.star_rating_survey_question_1_upper_label") },
isColorCodingEnabled: false,
},
{
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,
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,
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,
},
{
id: reusableQuestionIds[2],
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,
subheader: { default: "Help us improve your experience." },
buttonLabel: { default: "Send" },
placeholder: { default: "Type your answer here..." },
subheader: { default: t("templates.star_rating_survey_question_3_subheader") },
buttonLabel: { default: t("templates.star_rating_survey_question_3_button_label") },
placeholder: { default: t("templates.star_rating_survey_question_3_placeholder") },
inputType: "text",
charLimit: {
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 defaultSurvey = getXMSurveyDefault(locale);
const defaultSurvey = getXMSurveyDefault(t);
return {
...defaultSurvey,
name: translate("csat_survey_name", locale),
name: t("templates.csat_survey_name"),
questions: [
{
id: reusableQuestionIds[0],
@@ -214,10 +207,10 @@ const CSATSurvey = (locale: string): TXMTemplate => {
],
range: 5,
scale: "smiley",
headline: { default: translate("csat_survey_question_1_headline", locale) },
headline: { default: t("templates.csat_survey_question_1_headline") },
required: true,
lowerLabel: { default: translate("csat_survey_question_1_lower_label", locale) },
upperLabel: { default: translate("csat_survey_question_1_upper_label", locale) },
lowerLabel: { default: t("templates.csat_survey_question_1_lower_label") },
upperLabel: { default: t("templates.csat_survey_question_1_upper_label") },
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,
placeholder: { default: translate("csat_survey_question_2_placeholder", locale) },
placeholder: { default: t("templates.csat_survey_question_2_placeholder") },
inputType: "text",
charLimit: {
enabled: false,
@@ -260,9 +253,9 @@ const CSATSurvey = (locale: string): TXMTemplate => {
{
id: reusableQuestionIds[2],
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("csat_survey_question_3_headline", locale) },
headline: { default: t("templates.csat_survey_question_3_headline") },
required: false,
placeholder: { default: translate("csat_survey_question_3_placeholder", locale) },
placeholder: { default: t("templates.csat_survey_question_3_placeholder") },
inputType: "text",
charLimit: {
enabled: false,
@@ -272,28 +265,28 @@ const CSATSurvey = (locale: string): TXMTemplate => {
};
};
const CESSurvey = (locale: string): TXMTemplate => {
const cessSurvey = (t: TFnType): TXMTemplate => {
return {
...getXMSurveyDefault(locale),
name: translate("cess_survey_name", locale),
...getXMSurveyDefault(t),
name: t("templates.cess_survey_name"),
questions: [
{
id: createId(),
type: TSurveyQuestionTypeEnum.Rating,
range: 5,
scale: "number",
headline: { default: translate("cess_survey_question_1_headline", locale) },
headline: { default: t("templates.cess_survey_question_1_headline") },
required: true,
lowerLabel: { default: translate("cess_survey_question_1_lower_label", locale) },
upperLabel: { default: translate("cess_survey_question_1_upper_label", locale) },
lowerLabel: { default: t("templates.cess_survey_question_1_lower_label") },
upperLabel: { default: t("templates.cess_survey_question_1_upper_label") },
isColorCodingEnabled: false,
},
{
id: createId(),
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("cess_survey_question_2_headline", locale) },
headline: { default: t("templates.cess_survey_question_2_headline") },
required: true,
placeholder: { default: translate("cess_survey_question_2_placeholder", locale) },
placeholder: { default: t("templates.cess_survey_question_2_placeholder") },
inputType: "text",
charLimit: {
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 defaultSurvey = getXMSurveyDefault(locale);
const defaultSurvey = getXMSurveyDefault(t);
return {
...defaultSurvey,
name: translate("smileys_survey_name", locale),
name: t("templates.smileys_survey_name"),
questions: [
{
id: reusableQuestionIds[0],
@@ -346,15 +339,15 @@ const SmileysRatingSurvey = (locale: string): TXMTemplate => {
],
range: 5,
scale: "smiley",
headline: { default: translate("smileys_survey_question_1_headline", locale) },
headline: { default: t("templates.smileys_survey_question_1_headline") },
required: true,
lowerLabel: { default: translate("smileys_survey_question_1_lower_label", locale) },
upperLabel: { default: translate("smileys_survey_question_1_upper_label", locale) },
lowerLabel: { default: t("templates.smileys_survey_question_1_lower_label") },
upperLabel: { default: t("templates.smileys_survey_question_1_upper_label") },
isColorCodingEnabled: false,
},
{
id: reusableQuestionIds[1],
html: { default: translate("smileys_survey_question_2_html", locale) },
html: { default: t("templates.smileys_survey_question_2_html") },
type: TSurveyQuestionTypeEnum.CTA,
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,
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,
},
{
id: reusableQuestionIds[2],
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("smileys_survey_question_3_headline", locale) },
headline: { default: t("templates.smileys_survey_question_3_headline") },
required: true,
subheader: { default: translate("smileys_survey_question_3_subheader", locale) },
buttonLabel: { default: translate("smileys_survey_question_3_button_label", locale) },
placeholder: { default: translate("smileys_survey_question_3_placeholder", locale) },
subheader: { default: t("templates.smileys_survey_question_3_subheader") },
buttonLabel: { default: t("templates.smileys_survey_question_3_button_label") },
placeholder: { default: t("templates.smileys_survey_question_3_placeholder") },
inputType: "text",
charLimit: {
enabled: false,
@@ -405,26 +398,26 @@ const SmileysRatingSurvey = (locale: string): TXMTemplate => {
};
};
const eNPSSurvey = (locale: string): TXMTemplate => {
const enpsSurvey = (t: TFnType): TXMTemplate => {
return {
...getXMSurveyDefault(locale),
name: translate("enps_survey_name", locale),
...getXMSurveyDefault(t),
name: t("templates.enps_survey_name"),
questions: [
{
id: createId(),
type: TSurveyQuestionTypeEnum.NPS,
headline: {
default: translate("enps_survey_question_1_headline", locale),
default: t("templates.enps_survey_question_1_headline"),
},
required: false,
lowerLabel: { default: translate("enps_survey_question_1_lower_label", locale) },
upperLabel: { default: translate("enps_survey_question_1_upper_label", locale) },
lowerLabel: { default: t("templates.enps_survey_question_1_lower_label") },
upperLabel: { default: t("templates.enps_survey_question_1_upper_label") },
isColorCodingEnabled: true,
},
{
id: createId(),
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("enps_survey_question_2_headline", locale) },
headline: { default: t("templates.enps_survey_question_2_headline") },
required: false,
inputType: "text",
charLimit: {
@@ -434,7 +427,7 @@ const eNPSSurvey = (locale: string): TXMTemplate => {
{
id: createId(),
type: TSurveyQuestionTypeEnum.OpenText,
headline: { default: translate("enps_survey_question_3_headline", locale) },
headline: { default: t("templates.enps_survey_question_3_headline") },
required: false,
inputType: "text",
charLimit: {
@@ -445,18 +438,15 @@ const eNPSSurvey = (locale: string): TXMTemplate => {
};
};
export const getXMTemplates = (locale: string): TXMTemplate[] => {
export const getXMTemplates = (t: TFnType): TXMTemplate[] => {
try {
if (!validateLocale(locale)) {
throw new Error("Invalid locale");
}
return [
NPSSurvey(locale),
StarRatingSurvey(locale),
CSATSurvey(locale),
CESSurvey(locale),
SmileysRatingSurvey(locale),
eNPSSurvey(locale),
npsSurvey(t),
starRatingSurvey(t),
csatSurvey(t),
cessSurvey(t),
smileysRatingSurvey(t),
enpsSurvey(t),
];
} catch (error) {
logError(error, "getXMTemplates");

View File

@@ -3,9 +3,9 @@ import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import Link from "next/link";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProjectByEnvironmentId, getUserProjects } from "@formbricks/lib/project/service";
@@ -21,7 +21,7 @@ const Page = async (props: XMTemplatePageProps) => {
const params = await props.params;
const session = await getServerSession(authOptions);
const environment = await getEnvironment(params.environmentId);
const t = await getTranslations();
const t = await getTranslate();
if (!session) {
throw new Error(t("common.session_not_found"));
}

View File

@@ -17,9 +17,9 @@ import {
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
import { useTranslate } from "@tolgee/react";
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
import { signOut } from "next-auth/react";
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -44,7 +44,7 @@ export const LandingSidebar = ({
}: LandingSidebarProps) => {
const [openCreateOrganizationModal, setOpenCreateOrganizationModal] = useState<boolean>(false);
const t = useTranslations();
const { t } = useTranslate();
const router = useRouter();
@@ -127,7 +127,7 @@ export const LandingSidebar = ({
await signOut({ callbackUrl: "/auth/login" });
await formbricksLogout();
}}
icon={<LogOutIcon className="h-4 w-4" strokeWidth={1.5} />}>
icon={<LogOutIcon className="mr-2 h-4 w-4" strokeWidth={1.5} />}>
{t("common.logout")}
</DropdownMenuItem>

View File

@@ -2,15 +2,15 @@ import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organiza
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";
const Page = async (props) => {
const params = await props.params;
const t = await getTranslations();
const t = await getTranslate();
const session = await getServerSession(authOptions);
if (!session || !session.user) {
return redirect(`/auth/login`);

View File

@@ -1,8 +1,8 @@
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getTranslate } from "@/tolgee/server";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { canUserAccessOrganization } from "@formbricks/lib/organization/auth";
import { getOrganization } from "@formbricks/lib/organization/service";
@@ -14,7 +14,7 @@ const ProjectOnboardingLayout = async (props) => {
const { children } = props;
const t = await getTranslations();
const t = await getTranslate();
const session = await getServerSession(authOptions);
if (!session || !session.user) {
return redirect(`/auth/login`);

View File

@@ -2,9 +2,9 @@ import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizatio
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
import { PictureInPicture2Icon, SendIcon, XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import Link from "next/link";
import { redirect } from "next/navigation";
import { getUserProjects } from "@formbricks/lib/project/service";
@@ -22,7 +22,7 @@ const Page = async (props: ChannelPageProps) => {
return redirect(`/auth/login`);
}
const t = await getTranslations();
const t = await getTranslate();
const channelOptions = [
{
title: t("organizations.projects.new.channel.link_and_email_surveys"),

View File

@@ -1,7 +1,7 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getOrganizationProjectsLimit } from "@/modules/ee/license-check/lib/utils";
import { getTranslate } from "@/tolgee/server";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
@@ -12,7 +12,7 @@ const OnboardingLayout = async (props) => {
const params = await props.params;
const { children } = props;
const t = await getTranslations();
const t = await getTranslate();
const session = await getServerSession(authOptions);
if (!session || !session.user) {

View File

@@ -2,9 +2,9 @@ import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizatio
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import Link from "next/link";
import { redirect } from "next/navigation";
import { getUserProjects } from "@formbricks/lib/project/service";
@@ -22,7 +22,7 @@ const Page = async (props: ModePageProps) => {
return redirect(`/auth/login`);
}
const t = await getTranslations();
const t = await getTranslate();
const channelOptions = [
{
title: t("organizations.projects.new.mode.formbricks_surveys"),

View File

@@ -1,6 +1,7 @@
"use client";
import { createProjectAction } from "@/app/(app)/environments/[environmentId]/actions";
import { previewSurvey } from "@/app/lib/templates";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { TOrganizationTeam } from "@/modules/ee/teams/project-teams/types/team";
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 { SurveyInline } from "@/modules/ui/components/survey";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { FORMBRICKS_SURVEYS_FILTERS_KEY_LS } from "@formbricks/lib/localStorage";
import { getPreviewSurvey } from "@formbricks/lib/styling/constants";
import {
TProjectConfigChannel,
TProjectConfigIndustry,
@@ -43,7 +43,6 @@ interface ProjectSettingsProps {
defaultBrandColor: string;
organizationTeams: TOrganizationTeam[];
canDoRoleManagement: boolean;
locale: string;
userProjectsCount: number;
}
@@ -55,13 +54,12 @@ export const ProjectSettings = ({
defaultBrandColor,
organizationTeams,
canDoRoleManagement = false,
locale,
userProjectsCount,
}: ProjectSettingsProps) => {
const [createTeamModalOpen, setCreateTeamModalOpen] = useState(false);
const router = useRouter();
const t = useTranslations();
const { t } = useTranslate();
const addProject = async (data: TProjectUpdateInput) => {
try {
const createProjectResponse = await createProjectAction({
@@ -233,7 +231,7 @@ export const ProjectSettings = ({
<p className="text-sm text-slate-400">{t("common.preview")}</p>
<div className="z-0 h-3/4 w-3/4">
<SurveyInline
survey={getPreviewSurvey(locale, projectName || "my Product")}
survey={previewSurvey(projectName || "my Product", t)}
styling={{ brandColor: { light: brandColor } }}
isBrandingEnabled={false}
languageCode="default"

View File

@@ -4,15 +4,14 @@ import { authOptions } from "@/modules/auth/lib/authOptions";
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { getTranslate } from "@/tolgee/server";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import Link from "next/link";
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 { getUserProjects } from "@formbricks/lib/project/service";
import { getUserLocale } from "@formbricks/lib/user/service";
import { TProjectConfigChannel, TProjectConfigIndustry, TProjectMode } from "@formbricks/types/project";
interface ProjectSettingsPageProps {
@@ -29,7 +28,7 @@ interface ProjectSettingsPageProps {
const Page = async (props: ProjectSettingsPageProps) => {
const searchParams = await props.searchParams;
const params = await props.params;
const t = await getTranslations();
const t = await getTranslate();
const session = await getServerSession(authOptions);
if (!session || !session.user) {
@@ -39,7 +38,6 @@ const Page = async (props: ProjectSettingsPageProps) => {
const channel = searchParams.channel || null;
const industry = searchParams.industry || null;
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 organizationTeams = await getTeamsByOrganizationId(params.organizationId);
@@ -70,7 +68,6 @@ const Page = async (props: ProjectSettingsPageProps) => {
defaultBrandColor={DEFAULT_BRAND_COLOR}
organizationTeams={organizationTeams}
canDoRoleManagement={canDoRoleManagement}
locale={locale ?? DEFAULT_LOCALE}
userProjectsCount={projects.length}
/>
{projects.length >= 1 && (

View File

@@ -4,8 +4,8 @@ import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]
import { authOptions } from "@/modules/auth/lib/authOptions";
import { DevEnvironmentBanner } from "@/modules/ui/components/dev-environment-banner";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getTranslate } from "@/tolgee/server";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getEnvironment } from "@formbricks/lib/environment/service";
@@ -18,7 +18,7 @@ const SurveyEditorEnvironmentLayout = async (props) => {
const { children } = props;
const t = await getTranslations();
const t = await getTranslate();
const session = await getServerSession(authOptions);
if (!session || !session.user) {
return redirect(`/auth/login`);

View File

@@ -1,7 +1,7 @@
"use client";
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 { TSurvey } from "@formbricks/types/surveys/types";
import { CreateNewActionTab } from "./CreateNewActionTab";
@@ -28,7 +28,7 @@ export const AddActionModal = ({
isReadOnly,
environmentId,
}: AddActionModalProps) => {
const t = useTranslations();
const { t } = useTranslate();
const tabs = [
{
title: t("environments.surveys.edit.select_saved_action"),

View File

@@ -1,7 +1,7 @@
"use client";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { TSurvey } from "@formbricks/types/surveys/types";
interface AddEndingCardButtonProps {
@@ -11,7 +11,7 @@ interface AddEndingCardButtonProps {
}
export const AddEndingCardButton = ({ localSurvey, addEndingCard }: AddEndingCardButtonProps) => {
const t = useTranslations();
const { t } = useTranslate();
return (
<div
className="group inline-flex rounded-lg border border-slate-300 bg-slate-50 hover:cursor-pointer hover:bg-white"

View File

@@ -3,8 +3,8 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
import {
@@ -19,14 +19,13 @@ interface AddQuestionButtonProps {
addQuestion: (question: any) => void;
project: TProject;
isCxMode: boolean;
locale: string;
}
export const AddQuestionButton = ({ addQuestion, project, isCxMode, locale }: AddQuestionButtonProps) => {
const t = useTranslations();
export const AddQuestionButton = ({ addQuestion, project, isCxMode }: AddQuestionButtonProps) => {
const { t } = useTranslate();
const [open, setOpen] = useState(false);
const [hoveredQuestionId, setHoveredQuestionId] = useState<string | null>(null);
const availableQuestionTypes = isCxMode ? getCXQuestionTypes(locale) : getQuestionTypes(locale);
const availableQuestionTypes = isCxMode ? getCXQuestionTypes(t) : getQuestionTypes(t);
const [parent] = useAutoAnimate();
return (
@@ -60,7 +59,7 @@ export const AddQuestionButton = ({ addQuestion, project, isCxMode, locale }: Ad
onClick={() => {
addQuestion({
...universalQuestionPresets,
...getQuestionDefaults(questionType.id, project, locale),
...getQuestionDefaults(questionType.id, project, t),
id: createId(),
type: questionType.id,
});

View File

@@ -4,8 +4,8 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
import { Button } from "@/modules/ui/components/button";
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useEffect } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
@@ -34,7 +34,7 @@ export const AddressQuestionForm = ({
locale,
}: AddressQuestionFormProps): JSX.Element => {
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages ?? []);
const t = useTranslations();
const { t } = useTranslate();
const fields = [
{
id: "addressLine1",

View File

@@ -5,8 +5,8 @@ import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/
import { Slider } from "@/modules/ui/components/slider";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { UseFormReturn } from "react-hook-form";
import { cn } from "@formbricks/lib/cn";
import { TProjectStyling } from "@formbricks/types/project";
@@ -34,7 +34,7 @@ export const BackgroundStylingCard = ({
isUnsplashConfigured,
form,
}: BackgroundStylingCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [parent] = useAutoAnimate();
return (

View File

@@ -6,7 +6,7 @@ import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { type JSX, useState } from "react";
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
@@ -34,7 +34,7 @@ export const CTAQuestionForm = ({
setSelectedLanguageCode,
locale,
}: CTAQuestionFormProps): JSX.Element => {
const t = useTranslations();
const { t } = useTranslate();
const options = [
{
value: "internal",

View File

@@ -1,10 +1,12 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useEffect, useState } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
@@ -34,7 +36,7 @@ export const CalQuestionForm = ({
}: CalQuestionFormProps): JSX.Element => {
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
const [isCalHostEnabled, setIsCalHostEnabled] = useState(!!question.calHost);
const t = useTranslations();
const { t } = useTranslate();
useEffect(() => {
if (!isCalHostEnabled) {
updateQuestion(questionIdx, { calHost: undefined });

View File

@@ -8,8 +8,8 @@ import { Slider } from "@/modules/ui/components/slider";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import React from "react";
import { UseFormReturn } from "react-hook-form";
import { cn } from "@formbricks/lib/cn";
@@ -36,7 +36,7 @@ export const CardStylingSettings = ({
setOpen,
form,
}: CardStylingSettingsProps) => {
const t = useTranslations();
const { t } = useTranslate();
const isAppSurvey = surveyType === "app";
const surveyTypeDerived = isAppSurvey ? "App" : "Link";
const isLogoVisible = !!project.logo?.url;

View File

@@ -1,3 +1,5 @@
"use client";
import { LogicEditor } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor";
import {
getDefaultOperatorForQuestion,
@@ -13,6 +15,7 @@ import {
import { Label } from "@/modules/ui/components/label";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import {
ArrowDownIcon,
ArrowUpIcon,
@@ -22,7 +25,6 @@ import {
SplitIcon,
TrashIcon,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useMemo } from "react";
import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
@@ -41,7 +43,7 @@ export function ConditionalLogic({
questionIdx,
updateQuestion,
}: ConditionalLogicProps) {
const t = useTranslations();
const { t } = useTranslate();
const transformedSurvey = useMemo(() => {
let modifiedSurvey = replaceHeadlineRecall(localSurvey, "default");
modifiedSurvey = replaceEndingCardHeadlineRecall(modifiedSurvey, "default");

View File

@@ -3,7 +3,7 @@
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Label } from "@/modules/ui/components/label";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { type JSX, useState } from "react";
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
@@ -30,7 +30,7 @@ export const ConsentQuestionForm = ({
locale,
}: ConsentQuestionFormProps): JSX.Element => {
const [firstRender, setFirstRender] = useState(true);
const t = useTranslations();
const { t } = useTranslate();
return (
<form>
<QuestionFormInput

View File

@@ -4,8 +4,8 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
import { Button } from "@/modules/ui/components/button";
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useEffect } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TSurvey, TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
@@ -33,7 +33,7 @@ export const ContactInfoQuestionForm = ({
setSelectedLanguageCode,
locale,
}: ContactInfoQuestionFormProps): JSX.Element => {
const t = useTranslations();
const { t } = useTranslate();
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages ?? []);
const fields = [

View File

@@ -1,3 +1,5 @@
"use client";
import { isValidCssSelector } from "@/app/lib/actionClass/actionClass";
import { Button } from "@/modules/ui/components/button";
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 { TabToggle } from "@/modules/ui/components/tab-toggle";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { useMemo } from "react";
import { FormProvider, useForm } from "react-hook-form";
import toast from "react-hot-toast";
@@ -38,7 +40,7 @@ export const CreateNewActionTab = ({
setLocalSurvey,
environmentId,
}: CreateNewActionTabProps) => {
const t = useTranslations();
const { t } = useTranslate();
const actionClassNames = useMemo(
() => actionClasses.map((actionClass) => actionClass.name),
[actionClasses]

View File

@@ -1,10 +1,12 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import type { JSX } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
@@ -48,7 +50,7 @@ export const DateQuestionForm = ({
locale,
}: IDateQuestionFormProps): JSX.Element => {
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
const t = useTranslations();
const { t } = useTranslate();
const [parent] = useAutoAnimate();
return (
<form>

View File

@@ -14,8 +14,8 @@ import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { createId } from "@paralleldrive/cuid2";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { GripIcon, Handshake, Undo2 } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
@@ -59,7 +59,7 @@ export const EditEndingCard = ({
locale,
}: EditEndingCardProps) => {
const endingCard = localSurvey.endings[endingCardIndex];
const t = useTranslations();
const { t } = useTranslate();
const isRedirectToUrlDisabled = isFormbricksCloud
? plan === "free" && endingCard.type !== "redirectToUrl"
: false;
@@ -231,7 +231,6 @@ export const EditEndingCard = ({
updateCard={() => {}}
addCard={addEndingCard}
cardType="ending"
locale={locale}
/>
</div>
</div>

View File

@@ -6,8 +6,8 @@ import { FileInput } from "@/modules/ui/components/file-input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { Hand } from "lucide-react";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
@@ -35,7 +35,7 @@ export const EditWelcomeCard = ({
setSelectedLanguageCode,
locale,
}: EditWelcomeCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [firstRender, setFirstRender] = useState(true);
const path = usePathname();
const environmentId = path?.split("/environments/")[1]?.split("/")[0];

View File

@@ -13,13 +13,13 @@ import {
} from "@/modules/ui/components/dropdown-menu";
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, EllipsisIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import {
QUESTIONS_ICON_MAP,
getCXQuestionNameMap,
getQuestionDefaults,
getQuestionIconMap,
getQuestionNameMap,
} from "@formbricks/lib/utils/questions";
import { TProject } from "@formbricks/types/project";
@@ -44,7 +44,6 @@ interface EditorCardMenuProps {
cardType: "question" | "ending";
project?: TProject;
isCxMode?: boolean;
locale: string;
}
export const EditorCardMenu = ({
@@ -60,9 +59,9 @@ export const EditorCardMenu = ({
addCard,
cardType,
isCxMode = false,
locale,
}: EditorCardMenuProps) => {
const t = useTranslations();
const { t } = useTranslate();
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
const [logicWarningModal, setLogicWarningModal] = useState(false);
const [changeToType, setChangeToType] = useState(() => {
if (card.type !== "endScreen" && card.type !== "redirectToUrl") {
@@ -76,7 +75,7 @@ export const EditorCardMenu = ({
? survey.questions.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) => {
if (!type) return;
@@ -84,7 +83,7 @@ export const EditorCardMenu = ({
const { headline, required, subheader, imageUrl, videoUrl, buttonLabel, backButtonLabel } =
card as TSurveyQuestion;
const questionDefaults = getQuestionDefaults(type, project, locale);
const questionDefaults = getQuestionDefaults(type, project, t);
if (
(type === TSurveyQuestionTypeEnum.MultipleChoiceSingle &&
@@ -123,7 +122,7 @@ export const EditorCardMenu = ({
};
const addQuestionCardBelow = (type: TSurveyQuestionTypeEnum) => {
const questionDefaults = getQuestionDefaults(type, project, locale);
const questionDefaults = getQuestionDefaults(type, project, t);
addCard(
{

View File

@@ -5,7 +5,7 @@ import { RecallWrapper } from "@/modules/surveys/components/QuestionFormInput/co
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { useState } from "react";
import { useRef } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
@@ -34,7 +34,7 @@ export const EndScreenForm = ({
endingCard,
locale,
}: EndScreenFormProps) => {
const t = useTranslations();
const { t } = useTranslate();
const inputRef = useRef<HTMLInputElement>(null);
const [showEndingCardCTA, setshowEndingCardCTA] = useState<boolean>(
endingCard.type === "endScreen" &&

View File

@@ -6,8 +6,8 @@ import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { useGetBillingInfo } from "@/modules/utils/hooks/useGetBillingInfo";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon, XCircleIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { type JSX, useMemo, useState } from "react";
import { toast } from "react-hot-toast";
@@ -45,7 +45,7 @@ export const FileUploadQuestionForm = ({
locale,
}: FileUploadFormProps): JSX.Element => {
const [extension, setExtension] = useState("");
const t = useTranslations();
const { t } = useTranslate();
const [isMaxSizeError, setMaxSizeError] = useState(false);
const {
billingInfo,
@@ -227,7 +227,7 @@ export const FileUploadQuestionForm = ({
className="underline"
target="_blank"
href={`/environments/${localSurvey.environmentId}/settings/billing`}>
{t("environments.surveys.edit.upgrade_your_plan")}
{t("common.please_upgrade_your_plan")}
</Link>
</p>
)}

View File

@@ -5,8 +5,8 @@ import { ColorPicker } from "@/modules/ui/components/color-picker";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { CheckIcon, SparklesIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import React from "react";
import { UseFormReturn } from "react-hook-form";
import { cn } from "@formbricks/lib/cn";
@@ -30,7 +30,7 @@ export const FormStylingSettings = ({
setOpen,
form,
}: FormStylingSettingsProps) => {
const t = useTranslations();
const { t } = useTranslate();
const brandColor = form.watch("brandColor.light") || COLOR_DEFAULTS.brandColor;
const background = form.watch("background");
const highlightBorderColor = form.watch("highlightBorderColor");

View File

@@ -8,8 +8,8 @@ import { Switch } from "@/modules/ui/components/switch";
import { Tag } from "@/modules/ui/components/tag";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { EyeOff } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
@@ -32,7 +32,7 @@ export const HiddenFieldsCard = ({
}: HiddenFieldsCardProps) => {
const open = activeQuestionId == "hidden";
const [hiddenField, setHiddenField] = useState<string>("");
const t = useTranslations();
const { t } = useTranslate();
const setOpen = (open: boolean) => {
if (open) {
setActiveQuestionId("hidden");

View File

@@ -1,16 +1,16 @@
"use client";
import { getDefaultEndingCard } from "@/app/lib/templates";
import { Badge } from "@/modules/ui/components/badge";
import { Label } from "@/modules/ui/components/label";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { AlertCircleIcon, CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useEffect, useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { getDefaultEndingCard } from "@formbricks/lib/templates";
import { TEnvironment } from "@formbricks/types/environment";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
@@ -19,13 +19,12 @@ interface HowToSendCardProps {
localSurvey: TSurvey;
setLocalSurvey: (survey: TSurvey | ((TSurvey: TSurvey) => TSurvey)) => void;
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 [appSetupCompleted, setAppSetupCompleted] = useState(false);
const t = useTranslations();
const { t } = useTranslate();
useEffect(() => {
if (environment) {
setAppSetupCompleted(environment.appSetupCompleted);
@@ -35,7 +34,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment, locale
const setSurveyType = (type: TSurveyType) => {
const endingsTemp = localSurvey.endings;
if (type === "link" && localSurvey.endings.length === 0) {
endingsTemp.push(getDefaultEndingCard(localSurvey.languages, locale));
endingsTemp.push(getDefaultEndingCard(localSurvey.languages, t));
}
setLocalSurvey((prevSurvey) => ({
...prevSurvey,

View File

@@ -1,3 +1,5 @@
"use client";
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 {
@@ -7,11 +9,11 @@ import {
SelectTrigger,
SelectValue,
} from "@/modules/ui/components/select";
import { useTranslate } from "@tolgee/react";
import { ArrowRightIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { ReactElement, useMemo } from "react";
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";
interface LogicEditorProps {
@@ -33,8 +35,8 @@ export function LogicEditor({
logicIdx,
isLast,
}: LogicEditorProps) {
const t = useTranslations();
const { t } = useTranslate();
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
const fallbackOptions = useMemo(() => {
let options: {
icon?: ReactElement;

View File

@@ -1,3 +1,5 @@
"use client";
import {
getActionObjectiveOptions,
getActionOperatorOptions,
@@ -14,8 +16,8 @@ import {
} from "@/modules/ui/components/dropdown-menu";
import { InputCombobox } from "@/modules/ui/components/input-combo-box";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { CopyIcon, CornerDownRightIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { getUpdatedActionBody } from "@formbricks/lib/surveyLogic/utils";
import {
TActionNumberVariableCalculateOperator,
@@ -46,7 +48,7 @@ export function LogicEditorActions({
questionIdx,
}: LogicEditorActions) {
const actions = logicItem.actions;
const t = useTranslations();
const { t } = useTranslate();
const handleActionsChange = (
operation: "remove" | "addBelow" | "duplicate" | "update",
actionIdx: number,

View File

@@ -1,3 +1,5 @@
"use client";
import {
getConditionOperatorOptions,
getConditionValueOptions,
@@ -13,8 +15,8 @@ import {
import { InputCombobox, TComboboxOption } from "@/modules/ui/components/input-combo-box";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { CopyIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon, WorkflowIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { cn } from "@formbricks/lib/cn";
import {
addConditionBelow,
@@ -54,7 +56,7 @@ export function LogicEditorConditions({
updateQuestion,
depth = 0,
}: LogicEditorConditionsProps) {
const t = useTranslations();
const { t } = useTranslate();
const [parent] = useAutoAnimate();
const handleAddConditionBelow = (resourceId: string) => {

View File

@@ -6,8 +6,8 @@ import { Label } from "@/modules/ui/components/label";
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import type { JSX } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
@@ -37,7 +37,7 @@ export const MatrixQuestionForm = ({
locale,
}: MatrixQuestionFormProps): JSX.Element => {
const languageCodes = extractLanguageCodes(localSurvey.languages);
const t = useTranslations();
const { t } = useTranslate();
// Function to add a new Label input field
const handleAddLabel = (type: "row" | "column") => {
if (type === "row") {

View File

@@ -9,8 +9,8 @@ import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
@@ -46,7 +46,7 @@ export const MultipleChoiceQuestionForm = ({
setSelectedLanguageCode,
locale,
}: MultipleChoiceQuestionFormProps): JSX.Element => {
const t = useTranslations();
const { t } = useTranslate();
const lastChoiceRef = useRef<HTMLInputElement>(null);
const [isNew, setIsNew] = useState(true);
const [isInvalidValue, setisInvalidValue] = useState<string | null>(null);

View File

@@ -4,8 +4,8 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import type { JSX } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TSurvey, TSurveyNPSQuestion } from "@formbricks/types/surveys/types";
@@ -34,7 +34,7 @@ export const NPSQuestionForm = ({
setSelectedLanguageCode,
locale,
}: NPSQuestionFormProps): JSX.Element => {
const t = useTranslations();
const { t } = useTranslate();
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
// Auto animate
const [parent] = useAutoAnimate();

View File

@@ -7,8 +7,8 @@ import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { HashIcon, LinkIcon, MailIcon, MessageSquareTextIcon, PhoneIcon, PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { JSX, useEffect, useState } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import {
@@ -40,7 +40,7 @@ export const OpenQuestionForm = ({
setSelectedLanguageCode,
locale,
}: OpenQuestionFormProps): JSX.Element => {
const t = useTranslations();
const { t } = useTranslate();
const questionTypes = [
{ value: "text", label: t("common.text"), icon: <MessageSquareTextIcon className="h-4 w-4" /> },
{ value: "email", label: t("common.email"), icon: <MailIcon className="h-4 w-4" /> },

View File

@@ -1,3 +1,5 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { FileInput } from "@/modules/ui/components/file-input";
@@ -5,8 +7,8 @@ import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import type { JSX } from "react";
import { cn } from "@formbricks/lib/cn";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
@@ -37,7 +39,7 @@ export const PictureSelectionForm = ({
}: PictureSelectionFormProps): JSX.Element => {
const environmentId = localSurvey.environmentId;
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
const t = useTranslations();
const { t } = useTranslate();
const handleChoiceDeletion = (choiceValue: string) => {
// Filter out the deleted choice from the choices array
const newChoices = question.choices?.filter((choice) => choice.id !== choiceValue) || [];

View File

@@ -3,18 +3,10 @@
import { Label } from "@/modules/ui/components/label";
import { getPlacementStyle } from "@/modules/ui/components/preview-survey/lib/utils";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { cn } from "@formbricks/lib/cn";
import { TPlacement } from "@formbricks/types/common";
const placements = [
{ name: "common.bottom_right", value: "bottomRight", disabled: false },
{ name: "common.top_right", value: "topRight", disabled: false },
{ name: "common.top_left", value: "topLeft", disabled: false },
{ name: "common.bottom_left", value: "bottomLeft", disabled: false },
{ name: "common.centered_modal", value: "center", disabled: false },
];
interface TPlacementProps {
currentPlacement: TPlacement;
setCurrentPlacement: (placement: TPlacement) => void;
@@ -32,7 +24,14 @@ export const Placement = ({
setClickOutsideClose,
clickOutsideClose,
}: TPlacementProps) => {
const t = useTranslations();
const { t } = useTranslate();
const placements = [
{ name: t("common.bottom_right"), value: "bottomRight", disabled: false },
{ name: t("common.top_right"), value: "topRight", disabled: false },
{ name: t("common.top_left"), value: "topLeft", disabled: false },
{ name: t("common.bottom_left"), value: "bottomLeft", disabled: false },
{ name: t("common.centered_modal"), value: "center", disabled: false },
];
const overlayStyle =
currentPlacement === "center" && overlay === "dark" ? "bg-slate-700/80" : "bg-slate-200";
return (

View File

@@ -10,11 +10,11 @@ import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { ChevronDownIcon, ChevronRightIcon, GripIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { QUESTIONS_ICON_MAP, getTSurveyQuestionTypeEnumName } from "@formbricks/lib/utils/questions";
import { getQuestionIconMap, getTSurveyQuestionTypeEnumName } from "@formbricks/lib/utils/questions";
import { recallToHeadline } from "@formbricks/lib/utils/recall";
import { TProject } from "@formbricks/types/project";
import {
@@ -84,7 +84,8 @@ export const QuestionCard = ({
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: question.id,
});
const t = useTranslations();
const { t } = useTranslate();
const QUESTIONS_ICON_MAP = getQuestionIconMap(t);
const open = activeQuestionId === question.id;
const [openAdvanced, setOpenAdvanced] = useState(question.logic && question.logic.length > 0);
const [parent] = useAutoAnimate();
@@ -225,7 +226,7 @@ export const QuestionCard = ({
selectedLanguageCode
] ?? ""
)
: getTSurveyQuestionTypeEnumName(question.type, locale)}
: getTSurveyQuestionTypeEnumName(question.type, t)}
</p>
{!open && (
<p className="mt-1 truncate text-xs text-slate-500">
@@ -251,7 +252,6 @@ export const QuestionCard = ({
addCard={addQuestion}
cardType="question"
isCxMode={isCxMode}
locale={locale}
/>
</div>
</div>

View File

@@ -1,10 +1,12 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useTranslate } from "@tolgee/react";
import { GripVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { cn } from "@formbricks/lib/cn";
import { createI18nString } from "@formbricks/lib/i18n/utils";
import {
@@ -56,7 +58,7 @@ export const QuestionOptionChoice = ({
updateQuestion,
locale,
}: ChoiceProps) => {
const t = useTranslations();
const { t } = useTranslate();
const isDragDisabled = choice.id === "other";
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id: choice.id,

View File

@@ -3,6 +3,7 @@
import { AddEndingCardButton } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddEndingCardButton";
import { SurveyVariablesCard } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCard";
import { findQuestionUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { getDefaultEndingCard } from "@/app/lib/templates";
import { MultiLanguageCard } from "@/modules/ee/multi-language-surveys/components/multi-language-card";
import {
DndContext,
@@ -15,13 +16,12 @@ import {
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import React, { SetStateAction, useEffect, useMemo } from "react";
import toast from "react-hot-toast";
import { addMultiLanguageLabels, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
import { isConditionGroup } from "@formbricks/lib/surveyLogic/utils";
import { getDefaultEndingCard } from "@formbricks/lib/templates";
import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/utils/recall";
import { TOrganizationBillingPlan } from "@formbricks/types/organizations";
import { TProject } from "@formbricks/types/project";
@@ -80,7 +80,7 @@ export const QuestionsView = ({
isCxMode,
locale,
}: QuestionsViewProps) => {
const t = useTranslations();
const { t } = useTranslate();
const internalQuestionIdMap = useMemo(() => {
return localSurvey.questions.reduce((acc, question) => {
acc[question.id] = createId();
@@ -338,7 +338,7 @@ export const QuestionsView = ({
const addEndingCard = (index: number) => {
const updatedSurvey = structuredClone(localSurvey);
const newEndingCard = getDefaultEndingCard(localSurvey.languages, locale);
const newEndingCard = getDefaultEndingCard(localSurvey.languages, t);
updatedSurvey.endings.splice(index, 0, newEndingCard);
setActiveQuestionId(newEndingCard.id);
@@ -461,7 +461,7 @@ export const QuestionsView = ({
/>
</DndContext>
<AddQuestionButton addQuestion={addQuestion} project={project} isCxMode={isCxMode} locale={locale} />
<AddQuestionButton addQuestion={addQuestion} project={project} isCxMode={isCxMode} />
<div className="mt-5 flex flex-col gap-5" ref={parent}>
<hr className="border-t border-dashed" />
<DndContext

View File

@@ -8,8 +8,8 @@ import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useEffect, useRef, useState } from "react";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TI18nString, TSurvey, TSurveyRankingQuestion } from "@formbricks/types/surveys/types";
@@ -38,7 +38,7 @@ export const RankingQuestionForm = ({
setSelectedLanguageCode,
locale,
}: RankingQuestionFormProps): JSX.Element => {
const t = useTranslations();
const { t } = useTranslate();
const lastChoiceRef = useRef<HTMLInputElement>(null);
const [isInvalidValue, setIsInvalidValue] = useState<string | null>(null);

View File

@@ -1,10 +1,12 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslate } from "@tolgee/react";
import { HashIcon, PlusIcon, SmileIcon, StarIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { TSurvey, TSurveyRatingQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
@@ -32,7 +34,7 @@ export const RatingQuestionForm = ({
setSelectedLanguageCode,
locale,
}: RatingQuestionFormProps) => {
const t = useTranslations();
const { t } = useTranslate();
const surveyLanguageCodes = extractLanguageCodes(localSurvey.languages);
const [parent] = useAutoAnimate();
return (

View File

@@ -6,10 +6,10 @@ import { Label } from "@/modules/ui/components/label";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { TSurvey } from "@formbricks/types/surveys/types";
interface DisplayOption {
@@ -18,29 +18,6 @@ interface DisplayOption {
description: string;
}
const displayOptions: DisplayOption[] = [
{
id: "displayOnce",
name: "environments.surveys.edit.show_only_once",
description: "environments.surveys.edit.the_survey_will_be_shown_once_even_if_person_doesnt_respond",
},
{
id: "displaySome",
name: "environments.surveys.edit.show_multiple_times",
description: "environments.surveys.edit.the_survey_will_be_shown_multiple_times_until_they_respond",
},
{
id: "displayMultiple",
name: "environments.surveys.edit.until_they_submit_a_response",
description: "environments.surveys.edit.if_you_really_want_that_answer_ask_until_you_get_it",
},
{
id: "respondMultiple",
name: "environments.surveys.edit.keep_showing_while_conditions_match",
description: "environments.surveys.edit.even_after_they_submitted_a_response_e_g_feedback_box",
},
];
interface RecontactOptionsCardProps {
localSurvey: TSurvey;
setLocalSurvey: (survey: TSurvey) => void;
@@ -52,7 +29,38 @@ export const RecontactOptionsCard = ({
setLocalSurvey,
environmentId,
}: RecontactOptionsCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const displayOptions: DisplayOption[] = useMemo(
() => [
{
id: "displayOnce",
name: t("environments.surveys.edit.show_only_once"),
description: t(
"environments.surveys.edit.the_survey_will_be_shown_once_even_if_person_doesnt_respond"
),
},
{
id: "displaySome",
name: t("environments.surveys.edit.show_multiple_times"),
description: t(
"environments.surveys.edit.the_survey_will_be_shown_multiple_times_until_they_respond"
),
},
{
id: "displayMultiple",
name: t("environments.surveys.edit.until_they_submit_a_response"),
description: t("environments.surveys.edit.if_you_really_want_that_answer_ask_until_you_get_it"),
},
{
id: "respondMultiple",
name: t("environments.surveys.edit.keep_showing_while_conditions_match"),
description: t("environments.surveys.edit.even_after_they_submitted_a_response_e_g_feedback_box"),
},
],
[t]
);
const [open, setOpen] = useState(false);
const ignoreWaiting = localSurvey.recontactDays !== null;
const [inputDays, setInputDays] = useState(

View File

@@ -1,7 +1,9 @@
"use client";
import { RecallWrapper } from "@/modules/surveys/components/QuestionFormInput/components/RecallWrapper";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { useRef } from "react";
import { headlineToRecall, recallToHeadline } from "@formbricks/lib/utils/recall";
import { TSurvey, TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types";
@@ -14,7 +16,7 @@ interface RedirectUrlFormProps {
export const RedirectUrlForm = ({ localSurvey, endingCard, updateSurvey }: RedirectUrlFormProps) => {
const selectedLanguageCode = "default";
const t = useTranslations();
const { t } = useTranslate();
const inputRef = useRef<HTMLInputElement>(null);
return (

View File

@@ -7,8 +7,8 @@ import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { ArrowUpRight, CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { KeyboardEventHandler, useEffect, useState } from "react";
import toast from "react-hot-toast";
@@ -26,7 +26,7 @@ export const ResponseOptionsCard = ({
setLocalSurvey,
responseCount,
}: ResponseOptionsCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [open, setOpen] = useState(localSurvey.type === "link" ? true : false);
const autoComplete = localSurvey.autoComplete !== null;
const [runOnDateToggle, setRunOnDateToggle] = useState(false);

View File

@@ -1,6 +1,8 @@
"use client";
import { Input } from "@/modules/ui/components/input";
import { useTranslate } from "@tolgee/react";
import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { TActionClass } from "@formbricks/types/action-classes";
import { TSurvey } from "@formbricks/types/surveys/types";
@@ -18,7 +20,7 @@ export const SavedActionsTab = ({
setLocalSurvey,
setOpen,
}: SavedActionsTabProps) => {
const t = useTranslations();
const { t } = useTranslate();
const availableActions = actionClasses.filter(
(actionClass) => !localSurvey.triggers.some((trigger) => trigger.actionClass.id === actionClass.id)
);

View File

@@ -23,7 +23,6 @@ interface SettingsViewProps {
responseCount: number;
membershipRole?: TOrganizationRole;
isUserTargetingAllowed?: boolean;
locale: string;
projectPermission: TTeamPermission | null;
isFormbricksCloud: boolean;
}
@@ -38,7 +37,6 @@ export const SettingsView = ({
responseCount,
membershipRole,
isUserTargetingAllowed = false,
locale,
projectPermission,
isFormbricksCloud,
}: SettingsViewProps) => {
@@ -46,12 +44,7 @@ export const SettingsView = ({
return (
<div className="mt-12 space-y-3 p-5">
<HowToSendCard
localSurvey={localSurvey}
setLocalSurvey={setLocalSurvey}
environment={environment}
locale={locale}
/>
<HowToSendCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} environment={environment} />
{localSurvey.type === "app" ? (
<div>

View File

@@ -1,3 +1,5 @@
"use client";
import { AlertDialog } from "@/modules/ui/components/alert-dialog";
import { Button } from "@/modules/ui/components/button";
import {
@@ -9,8 +11,8 @@ import {
FormProvider,
} from "@/modules/ui/components/form";
import { Switch } from "@/modules/ui/components/switch";
import { useTranslate } from "@tolgee/react";
import { RotateCcwIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import React, { useEffect, useMemo, useState } from "react";
import { UseFormReturn, useForm } from "react-hook-form";
@@ -50,7 +52,7 @@ export const StylingView = ({
isUnsplashConfigured,
isCxMode,
}: StylingViewProps) => {
const t = useTranslations();
const { t } = useTranslate();
const form = useForm<TSurveyStyling>({
defaultValues: { ...defaultStyling, ...project.styling, ...localSurvey.styling },

View File

@@ -1,6 +1,8 @@
"use client";
import { TabBar } from "@/modules/ui/components/tab-bar";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { useEffect, useState } from "react";
import { AnimatedSurveyBg } from "./AnimatedSurveyBg";
import { ColorSurveyBg } from "./ColorSurveyBg";
@@ -25,7 +27,7 @@ export const SurveyBgSelectorTab = ({
isUnsplashConfigured,
}: SurveyBgSelectorTabProps) => {
const [activeTab, setActiveTab] = useState(bgType || "color");
const t = useTranslations();
const { t } = useTranslate();
const [parent] = useAutoAnimate();
const [colorBackground, setColorBackground] = useState(bg);
const [animationBackground, setAnimationBackground] = useState(bg);

View File

@@ -217,7 +217,6 @@ export const SurveyEditor = ({
responseCount={responseCount}
membershipRole={membershipRole}
isUserTargetingAllowed={isUserTargetingAllowed}
locale={locale}
projectPermission={projectPermission}
isFormbricksCloud={isFormbricksCloud}
/>

View File

@@ -1,6 +1,8 @@
"use client";
import { ProBadge } from "@/modules/ui/components/pro-badge";
import { useTranslate } from "@tolgee/react";
import { MailIcon, PaintbrushIcon, Rows3Icon, SettingsIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useMemo } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSurveyEditorTabs } from "@formbricks/types/surveys/types";
@@ -27,27 +29,27 @@ export const SurveyEditorTabs = ({
isCxMode,
isSurveyFollowUpsAllowed = false,
}: SurveyEditorTabsProps) => {
const t = useTranslations();
const { t } = useTranslate();
const tabsComputed = useMemo(() => {
const tabs: Tab[] = [
{
id: "questions",
label: "common.questions",
label: t("common.questions"),
icon: <Rows3Icon className="h-5 w-5" />,
},
{
id: "styling",
label: "common.styling",
label: t("common.styling"),
icon: <PaintbrushIcon className="h-5 w-5" />,
},
{
id: "settings",
label: "common.settings",
label: t("common.settings"),
icon: <SettingsIcon className="h-5 w-5" />,
},
{
id: "followUps",
label: "environments.surveys.edit.follow_ups",
label: t("environments.surveys.edit.follow_ups"),
icon: <MailIcon className="h-5 w-5" />,
isPro: !isSurveyFollowUpsAllowed,
},
@@ -78,7 +80,7 @@ export const SurveyEditorTabs = ({
)}
aria-current={tab.id === activeId ? "page" : undefined}>
{tab.icon && <div className="mr-2 h-5 w-5">{tab.icon}</div>}
{t(tab.label)}
{tab.label}
{tab.isPro && <ProBadge />}
</button>
))}

View File

@@ -6,9 +6,9 @@ import { AlertDialog } from "@/modules/ui/components/alert-dialog";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
import { useTranslate } from "@tolgee/react";
import { isEqual } from "lodash";
import { AlertTriangleIcon, ArrowLeftIcon, SettingsIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
@@ -57,7 +57,7 @@ export const SurveyMenuBar = ({
isCxMode,
locale,
}: SurveyMenuBarProps) => {
const t = useTranslations();
const { t } = useTranslate();
const router = useRouter();
const [audiencePrompt, setAudiencePrompt] = useState(true);
const [isLinkSurvey, setIsLinkSurvey] = useState(true);

View File

@@ -4,8 +4,8 @@ import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useState } from "react";
import { TPlacement } from "@formbricks/types/common";
@@ -23,7 +23,7 @@ export const SurveyPlacementCard = ({
setLocalSurvey,
environmentId,
}: SurveyPlacementCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [open, setOpen] = useState(false);
const { projectOverwrites } = localSurvey ?? {};

View File

@@ -2,8 +2,8 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { FileDigitIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { cn } from "@formbricks/lib/cn";
import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { SurveyVariablesCardItem } from "./SurveyVariablesCardItem";
@@ -24,7 +24,7 @@ export const SurveyVariablesCard = ({
setActiveQuestionId,
}: SurveyVariablesCardProps) => {
const open = activeQuestionId === variablesCardId;
const t = useTranslations();
const { t } = useTranslate();
const [parent] = useAutoAnimate();
const setOpenState = (state: boolean) => {

View File

@@ -13,8 +13,8 @@ import {
SelectValue,
} from "@/modules/ui/components/select";
import { createId } from "@paralleldrive/cuid2";
import { useTranslate } from "@tolgee/react";
import { TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import React, { useCallback, useEffect } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
@@ -34,7 +34,7 @@ export const SurveyVariablesCardItem = ({
setLocalSurvey,
mode,
}: SurveyVariablesCardItemProps) => {
const t = useTranslations();
const { t } = useTranslate();
const form = useForm<TSurveyVariable>({
defaultValues: variable ?? {
id: createId(),

View File

@@ -1,7 +1,9 @@
"use client";
import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { LockIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
interface TargetingLockedCardProps {
@@ -10,7 +12,7 @@ interface TargetingLockedCardProps {
}
export const TargetingLockedCard = ({ isFormbricksCloud, environmentId }: TargetingLockedCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [open, setOpen] = useState(false);
return (

View File

@@ -3,9 +3,9 @@
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
import { useTranslate } from "@tolgee/react";
import { debounce } from "lodash";
import { SearchIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import UnsplashImage from "next/image";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
@@ -121,7 +121,7 @@ const defaultImages = [
];
export const ImageFromUnsplashSurveyBg = ({ handleBgChange }: ImageFromUnsplashSurveyBgProps) => {
const t = useTranslations();
const { t } = useTranslate();
const inputFocus = useRef<HTMLInputElement>(null);
const [isLoading, setIsLoading] = useState(false);
const [query, setQuery] = useState("");

View File

@@ -3,7 +3,7 @@
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { useTranslations } from "next-intl";
import { useTranslate } from "@tolgee/react";
import { useState } from "react";
import toast from "react-hot-toast";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
@@ -22,7 +22,7 @@ export const UpdateQuestionId = ({
questionIdx,
updateQuestion,
}: UpdateQuestionIdProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [currentValue, setCurrentValue] = useState(question.id);
const [prevValue, setPrevValue] = useState(question.id);
const [isInputInvalid, setIsInputInvalid] = useState(

View File

@@ -7,6 +7,7 @@ import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import {
CheckIcon,
Code2Icon,
@@ -15,7 +16,6 @@ import {
SparklesIcon,
Trash2Icon,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useMemo, useState } from "react";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { TActionClass } from "@formbricks/types/action-classes";
@@ -40,7 +40,7 @@ export const WhenToSendCard = ({
membershipRole,
projectPermission,
}: WhenToSendCardProps) => {
const t = useTranslations();
const { t } = useTranslate();
const [open, setOpen] = useState(localSurvey.type === "app" ? true : false);
const [isAddActionModalOpen, setAddActionModalOpen] = useState(false);
const [actionClasses, setActionClasses] = useState<TActionClass[]>(propActionClasses);

View File

@@ -1,6 +1,7 @@
import { TFnType } from "@tolgee/react";
import { TSurveyQuestionTypeEnum, ZSurveyLogicConditionsOperator } from "@formbricks/types/surveys/types";
export const getLogicRules = (t: (key: string) => string) => {
export const getLogicRules = (t: TFnType) => {
return {
question: {
[`${TSurveyQuestionTypeEnum.OpenText}.text`]: {

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