Compare commits

...

6 Commits

Author SHA1 Message Date
mintlify[bot]
0ce58b592a docs: fix images in actions and targeting (#4781)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2025-02-20 05:21:27 -08:00
mintlify[bot]
578346840e docs: Rewrite Actions and Advanced Targeting Docs (#4779)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2025-02-20 12:49:13 +00:00
Piyush Gupta
56bcb46d6c fix: User Management Docs (#4775)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Johannes <johannes@formbricks.com>
2025-02-20 09:28:14 +00:00
Piyush Gupta
91405c48e0 chore: remove SHORT_URL_BASE env key (#4766) 2025-02-20 08:22:09 +00:00
Paribesh Nepal
b40dff621a feat: Support HEIC format for images (#4719)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
2025-02-20 04:00:49 +00:00
Matti Nannt
7d4409e2b4 docs: add monitoring page to self-hosting (#4774) 2025-02-20 00:55:19 +01:00
45 changed files with 444 additions and 160 deletions

View File

@@ -138,7 +138,7 @@ export const EditLogo = ({ project, environmentId, isReadOnly }: EditLogoProps)
) : (
<FileInput
id="logo-input"
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
environmentId={environmentId}
onFileUpload={(files: string[]) => {
setLogoUrl(files[0]);
@@ -151,7 +151,7 @@ export const EditLogo = ({ project, environmentId, isReadOnly }: EditLogoProps)
<Input
ref={fileInputRef}
type="file"
accept="image/jpeg, image/png, image/webp"
accept="image/jpeg, image/png, image/webp, image/heic"
className="hidden"
disabled={isReadOnly}
onChange={handleFileChange}

View File

@@ -308,7 +308,7 @@ export const QuestionFormInput = ({
{showImageUploader && id === "headline" && (
<FileInput
id="question-image"
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
environmentId={localSurvey.environmentId}
onFileUpload={(url: string[] | undefined, fileType: "image" | "video") => {
if (url) {

View File

@@ -114,7 +114,7 @@ export const EditWelcomeCard = ({
<div className="mt-3 flex w-full items-center justify-center">
<FileInput
id="welcome-card-image"
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
environmentId={environmentId}
onFileUpload={(url: string[]) => {
updateSurvey({ fileUrl: url[0] });

View File

@@ -16,7 +16,7 @@ export const UploadImageSurveyBg = ({
<div className="flex w-full items-center justify-center">
<FileInput
id="survey-bg-file-input"
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
environmentId={environmentId}
onFileUpload={(url: string[]) => {
if (url.length > 0) {

View File

@@ -133,7 +133,7 @@ export const PictureSelectionForm = ({
<div className="mt-3 flex w-full items-center justify-center">
<FileInput
id="choices-file-input"
allowedFileExtensions={["png", "jpeg", "jpg", "webp"]}
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
environmentId={environmentId}
onFileUpload={handleFileInputChanges}
fileUrl={question?.choices?.map((choice) => choice.imageUrl)}

View File

@@ -68,7 +68,7 @@ export const FileInput = ({
toast.error(t("common.only_one_file_allowed"));
}
const allowedFiles = getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
const allowedFiles = await getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
if (allowedFiles.length === 0) {
return;
@@ -137,7 +137,7 @@ export const FileInput = ({
};
const handleUploadMore = async (files: File[]) => {
const allowedFiles = getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
const allowedFiles = await getAllowedFiles(files, allowedFileExtensions, maxSizeInMB);
if (allowedFiles.length === 0) {
return;
}

View File

@@ -0,0 +1,29 @@
"use server";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { z } from "zod";
const ZConvertHeicToJpegInput = z.object({
file: z.instanceof(File),
});
export const convertHeicToJpegAction = authenticatedActionClient
.schema(ZConvertHeicToJpegInput)
.action(async ({ parsedInput }) => {
if (!parsedInput.file || !parsedInput.file.name.endsWith(".heic")) return parsedInput.file;
const convert = (await import("heic-convert")).default;
const arrayBuffer = await parsedInput.file.arrayBuffer();
const nodeBuffer = Buffer.from(arrayBuffer) as unknown as ArrayBufferLike;
const convertedBuffer = await convert({
buffer: nodeBuffer,
format: "JPEG",
quality: 0.9,
});
return new File([convertedBuffer], parsedInput.file.name.replace(/\.heic$/, ".jpg"), {
type: "image/jpeg",
});
});

View File

@@ -2,6 +2,7 @@
import { toast } from "react-hot-toast";
import { TAllowedFileExtension } from "@formbricks/types/common";
import { convertHeicToJpegAction } from "./actions";
export const uploadFile = async (
file: File | Blob,
@@ -15,8 +16,6 @@ export const uploadFile = async (
const fileBuffer = await file.arrayBuffer();
// check the file size
const bufferBytes = fileBuffer.byteLength;
const bufferKB = bufferBytes / 1024;
@@ -74,7 +73,6 @@ export const uploadFile = async (
});
}
// Add the actual file to be uploaded
formData.append("file", file);
const uploadResponse = await fetch(signedUrl, {
@@ -96,34 +94,63 @@ export const uploadFile = async (
}
};
export const getAllowedFiles = (
const isFileSizeExceed = (fileSizeInMB: number, maxSizeInMB?: number) => {
if (maxSizeInMB && fileSizeInMB > maxSizeInMB) {
return true;
}
return false;
};
export const getAllowedFiles = async (
files: File[],
allowedFileExtensions: string[],
maxSizeInMB?: number
): File[] => {
): Promise<File[]> => {
const sizeExceedFiles: string[] = [];
const unsupportedExtensionFiles: string[] = [];
const convertedFiles: File[] = [];
const allowedFiles = files.filter((file) => {
for (const file of files) {
if (!file || !file.type) {
return false;
continue;
}
const extension = file.name.split(".").pop();
const fileSizeInMB = file.size / 1000000; // Kb -> Mb
const extension = file.name.split(".").pop()?.toLowerCase();
const fileSizeInMB = file.size / 1000000;
if (!allowedFileExtensions.includes(extension as TAllowedFileExtension)) {
unsupportedExtensionFiles.push(file.name);
return false; // Exclude file if extension not allowed
} else if (maxSizeInMB && fileSizeInMB > maxSizeInMB) {
sizeExceedFiles.push(file.name);
return false; // Exclude files larger than the maximum size
continue;
}
return true;
});
if (isFileSizeExceed(fileSizeInMB, maxSizeInMB)) {
sizeExceedFiles.push(file.name);
continue;
}
if (extension === "heic") {
const convertedFileResponse = await convertHeicToJpegAction({ file });
if (!convertedFileResponse?.data) {
unsupportedExtensionFiles.push(file.name);
continue;
} else {
const convertedFileSizeInMB = convertedFileResponse.data.size / 1000000;
if (isFileSizeExceed(convertedFileSizeInMB, maxSizeInMB)) {
sizeExceedFiles.push(file.name);
continue;
}
const convertedFile = new File([convertedFileResponse.data], file.name.replace(/\.heic$/, ".jpg"), {
type: "image/jpeg",
});
convertedFiles.push(convertedFile);
continue;
}
}
convertedFiles.push(file);
}
// Constructing toast messages based on the issues found
let toastMessage = "";
if (sizeExceedFiles.length > 0) {
toastMessage += `Files exceeding size limit (${maxSizeInMB} MB): ${sizeExceedFiles.join(", ")}. `;
@@ -134,7 +161,7 @@ export const getAllowedFiles = (
if (toastMessage) {
toast.error(toastMessage);
}
return allowedFiles;
return convertedFiles;
};
export const checkForYoutubePrivacyMode = (url: string): boolean => {

View File

@@ -83,6 +83,7 @@
"file-loader": "6.2.0",
"framer-motion": "11.15.0",
"googleapis": "144.0.0",
"heic-convert": "2.1.0",
"https-proxy-agent": "7.0.6",
"jiti": "2.4.1",
"jsonwebtoken": "9.0.2",
@@ -129,6 +130,7 @@
"@formbricks/eslint-config": "workspace:*",
"@neshca/cache-handler": "1.9.0",
"@types/bcryptjs": "2.4.6",
"@types/heic-convert": "2.1.0",
"@types/lodash": "4.17.13",
"@types/markdown-it": "14.1.2",
"@types/nodemailer": "6.4.17",

View File

@@ -50,9 +50,6 @@ x-environment: &environment
############################################## OPTIONAL (APP CONFIGURATION) ##############################################
# Set the below value if you have and want to use a custom URL for the links created by the Link Shortener
# SHORT_URL_BASE:
# Set the below to 0 to enable Email Verification for new signups (will required Email Configuration)
EMAIL_VERIFICATION_DISABLED: 1

1
docs/CodeAction Normal file

File diff suppressed because one or more lines are too long

1
docs/CreateSegment Normal file

File diff suppressed because one or more lines are too long

1
docs/NoCodeAction-Click Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
docs/NoCodeAction-Scroll Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

1
docs/Segments-Attributes Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
docs/Segments-Segments Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
---
title: "How to setup SAML with Identity Providers"
title: "Setup SAML with Identity Providers"
description: "This guide explains the settings you need to use to configure SAML with your Identity Provider. Once configured, obtain an XML metadata file and upload it on your Formbricks instance."
---
### SAML Registration with Identity Providers
@@ -8,7 +9,7 @@ This guide explains the settings you need to use to configure SAML with your Ide
> **Note:** Please do not add a trailing slash at the end of the URLs. Create them exactly as shown below.
**Assertion consumer service URL / Single Sign-On URL / Destination URL:** https://app.formbricks.com/api/auth/saml/callback
**Assertion consumer service URL / Single Sign-On URL / Destination URL:** [https://app.formbricks.com/api/auth/saml/callback](https://app.formbricks.com/api/auth/saml/callback)
**Entity ID / Identifier / Audience URI / Audience Restriction:** [https://saml.formbricks.com](https://saml.formbricks.com)
@@ -22,55 +23,72 @@ This guide explains the settings you need to use to configure SAML with your Ide
**Mapping Attributes / Attribute Statements:**
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier -> id
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress -> email
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname -> firstName
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname -> lastName
* [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier) -> id
* [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress) -> email
* [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname) -> firstName
* [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname) -> lastName
### SAML With Okta
<Steps>
<Step title="Create an application with your OIDC provider">
For example, in Okta, once you create an account, you can click on Applications on the sidebar menu:
<img src="/images/development/guides/auth-and-provision/okta/okta-applications.webp" />
</Step>
<Step title="Click on Create App Integration">
<img src="/images/development/guides/auth-and-provision/okta/create-app-integration.webp" />
</Step>
<Step title="Select SAML 2.0 in the modal form, and click Next">
<img src="/images/development/guides/auth-and-provision/okta/select-saml-2.0.webp" />
</Step>
<Step title="Fill the general settings as shown and click Next">
<img src="/images/development/guides/auth-and-provision/okta/general-settings.webp" />
</Step>
<Step title="Fill the fields mapping as shown and click Next">
<img src="/images/development/guides/auth-and-provision/okta/fields-mapping.webp" />
</Step>
<Step title="Enter the SAML Integration Settings as shown and click Next">
<img src="/images/development/guides/auth-and-provision/okta/saml-integration-settings.webp" />
</Step>
<Step title="Check the internal app checkbox and click Finish">
<img src="/images/development/guides/auth-and-provision/okta/internal-app.webp" />
</Step>
<Step title="Check that the app is created successfully">
<img src="/images/development/guides/auth-and-provision/okta/app-created.webp" />
</Step>
<Step title="Click on the app and head over to the Assignments tab">
<img src="/images/development/guides/auth-and-provision/okta/assignments-tab.webp" />
</Step>
<Step title="Click on Assign button and select Assign to People">
<img src="/images/development/guides/auth-and-provision/okta/assign-to-people.webp" />
</Step>
<Step title="Select the users you want to assign the app to and click Assign">
<img src="/images/development/guides/auth-and-provision/okta/select-users.webp" />
</Step>
<Step title="Head over to the Sign On tab and scroll to the bottom to get the metadata, click on the Actions button">
<img src="/images/development/guides/auth-and-provision/okta/actions-button.webp" />
</Step>
<Step title="Click on View IdP metadata">
<img src="/images/development/guides/auth-and-provision/okta/view-idp-metadata.webp" />
</Step>
<Step title="Copy the metadata and paste it in the Formbricks SAML configuration"></Step>
<Step title="Copy the metadata and paste it in the Formbricks SAML configuration" />
</Steps>
That's it. Now when you try to login with SSO, your application on Okta will handle the authentication.
That's it. Now when you try to login with SSO, your application on Okta will handle the authentication.

BIN
docs/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@@ -244,7 +244,8 @@
"pages": [
"self-hosting/setup/one-click",
"self-hosting/setup/docker",
"self-hosting/setup/cluster-setup"
"self-hosting/setup/cluster-setup",
"self-hosting/setup/monitoring"
]
},
{

View File

@@ -909,7 +909,6 @@ Formbricks v1.2 introduces new features for Link Surveys and enhances security.
| Environment Variable | Required | Recommended Generation | Comments |
| -------------------- | -------- | ----------------------- | ----------------------------------------------------------- |
| `ENCRYPTION_KEY` | true | `openssl rand -hex 32` | Needed for 2 Factor Authentication |
| `SHORT_URL_BASE` | false | `<your-short-base-url>` | Needed if you want to enable shorter links for Link Surveys |
### Deprecated / Removed Environment Variables

View File

@@ -0,0 +1,91 @@
---
title: "Monitoring"
description: "Monitoring your Formbricks installation for optimal performance."
icon: "magnifying-glass-chart"
---
## Logging
Formbricks follows Next.js best practices with all logs being written to stdout/stderr, making it easy to collect and forward logs to your preferred logging solution.
### Docker Container Logs
```bash
# One-Click setup
cd formbricks
docker compose logs
# Standard Docker commands
docker logs <container-name>
docker logs -f <container-name> # Follow logs
```
### Kubernetes Pod Logs
```bash
kubectl logs <pod-name> -n <namespace>
kubectl logs -f <pod-name> -n <namespace> # Follow logs
```
### Log Forwarding
Since all logs are written to stdout/stderr, you can integrate with various logging solutions:
- ELK Stack (Elasticsearch, Logstash, Kibana)
- Fluentd/Fluent Bit
- Datadog
- Splunk
- CloudWatch Logs (AWS)
## OpenTelemetry Integration (Beta)
Formbricks leverages Next.js's built-in OpenTelemetry instrumentation for comprehensive observability. When enabled, it automatically instruments various aspects of your application.
Set the following environment variables:
```env
OTEL_ENABLED=true
OTEL_ENDPOINT=<your-collector-endpoint>
OTEL_SERVICE_NAME=formbricks
NEXT_OTEL_VERBOSE=1 # Optional: enables detailed tracing
```
### Default Instrumentation
The OpenTelemetry integration automatically tracks:
- HTTP requests and responses
- Route rendering
- API route execution
- Server-side operations
- Database queries
- External API calls
### Supported Backends
OpenTelemetry can export data to:
- Jaeger
- Zipkin
- Prometheus
- New Relic
- Datadog
- Azure Monitor
### Key Metrics
- HTTP request duration
- Database performance
- Memory usage
- Response times
- Error rates
## Health Checks
Available endpoints:
```
GET /health
```
Use these endpoints for monitoring system health in container orchestration and monitoring tools.

View File

@@ -1,7 +1,6 @@
---
title: "User Management"
description:
"Assign different roles to organization members to grant them specific rights like creating surveys, viewing responses, or managing organization members."
description: "Assign different roles to organization members to grant them specific rights like creating surveys, viewing responses, or managing organization members."
icon: "user"
---
@@ -14,9 +13,8 @@ Learn about the different organization-level and team-level roles and how they a
Permissions in Formbricks are broadly handled using organization-level roles, which apply to all teams and projects in the organization. Users on a self-hosting and Enterprise plan, have access to team-level roles, which enable more granular permissions.
<Note>
Access Roles is a feature of the **Enterprise Edition**. In the **Community
Edition** and on the **Free** and **Startup** plan in the Cloud you can invite
unlimited organization members as `Owner`.
Access Roles is a feature of the **Enterprise Edition**. In the **Community Edition** and on the **Free**
and **Startup** plan in the Cloud you can invite unlimited organization members as `Owner`.
</Note>
Here are the different access permissions, ranked from highest to lowest access
@@ -30,16 +28,16 @@ Here are the different access permissions, ranked from highest to lowest access
All users and their organization-level roles are listed in **Organization Settings > General**. Users can hold any of the following org-level roles:
- **Owner** have full access to the organization, its data, and settings. Org Owners can perform Team Admin actions without needing to join the team.
- **Manager** have full management access to all teams and projects. They can also manage the organization's membership. Org Managers can perform Team Admin actions without needing to join the team. They cannot change other organization settings.
- **Billing** users can manage payment and compliance details in the organization.
- **Org Members** can view most data in the organization and act in the projects they are members of. They cannot join projects on their own and need to be assigned.
- **Org Managers** have full management access to all teams and projects. They can also manage the organization's membership. Org Managers can perform Team Admin actions without needing to join the team. They cannot change other organization settings.
- **Org Owners** have full access to the organization, its data, and settings. Org Owners can perform Team Admin actions without needing to join the team.
- **Member** can view most data in the organization and act in the projects they are members of. They cannot join projects on their own and need to be assigned.
### Permissions at project level
- **read**: read access to all resources (except settings) in the project.
- **read & write**: read & write access to all resources (except settings) in the project.
- **manage**: read & write access to all resources including settings in the project.
- **Read**: Read access to all resources (except settings) in the project.
- **Read & write**: Read & write access to all resources (except settings) in the project.
- **Manage**: Read & write access to all resources including settings in the project.
### Team-level Roles
@@ -53,31 +51,32 @@ For more information on user roles & permissions, see below:
| **Organization** | | | | |
| Update organization | ✅ | ❌ | ❌ | ❌ |
| Delete organization | ✅ | ❌ | ❌ | ❌ |
| Add new Member | ✅ | ✅ | ❌ | ❌ |
| Delete Member | ✅ | ✅ | ❌ | ❌ |
| Update Member Access | ✅ | ✅ | ❌ | ❌ |
| Update Billing | ✅ | ✅ | ✅ | ❌ |
| Add new member | ✅ | ✅ | ❌ | ❌ |
| Delete member | ✅ | ✅ | ❌ | ❌ |
| Update member access | ✅ | ✅ | ❌ | ❌ |
| Update billing | ✅ | ✅ | ✅ | ❌ |
| **Project** | | | | |
| Create Project | ✅ | ✅ | ❌ | ❌ |
| Update Project Name | ✅ | ✅ | ❌ | ✅\*\* |
| Update Project Recontact Options | ✅ | ✅ | ❌ | ✅\*\* |
| Update Look & Feel | ✅ | ✅ | ❌ | ✅\*\* |
| Update Survey Languages | ✅ | ✅ | ❌ | ✅\*\* |
| Delete Project | ✅ | ✅ | ❌ | ❌ |
| Create project | ✅ | ✅ | ❌ | ❌ |
| Update project name | ✅ | ✅ | ❌ | ✅\*\* |
| Update project recontact options | ✅ | ✅ | ❌ | ✅\*\* |
| Update look & feel | ✅ | ✅ | ❌ | ✅\*\* |
| Update survey languages | ✅ | ✅ | ❌ | ✅\*\* |
| Delete project | ✅ | ✅ | ❌ | ❌ |
| **Surveys** | | | | |
| Create New Survey | ✅ | ✅ | ❌ | ✅\* |
| Edit Survey | ✅ | ✅ | ❌ | ✅\* |
| Delete Survey | ✅ | ✅ | ❌ | ✅\* |
| Create new survey | ✅ | ✅ | ❌ | ✅\* |
| Edit survey | ✅ | ✅ | ❌ | ✅\* |
| Delete survey | ✅ | ✅ | ❌ | ✅\* |
| View survey results | ✅ | ✅ | ❌ | ✅ |
| **Response** | | | | |
| Delete response | ✅ | ✅ | ❌ | ✅\* |
| Add tags on response | ✅ | ✅ | ❌ | ✅\* |
| Edit tags on response | ✅ | ✅ | ❌ | ✅\* |
| Download survey responses (CSV) | ✅ | ✅ | ❌ | ✅\* |
| **Actions** | | | | |
| Create Action | ✅ | ✅ | ❌ | ✅\* |
| Update Action | ✅ | ✅ | ❌ | ✅\* |
| Delete Action | ✅ | ✅ | ❌ | ✅\* |
| **API Keys** | | | | |
| Create action | ✅ | ✅ | ❌ | ✅\* |
| Update action | ✅ | ✅ | ❌ | ✅\* |
| Delete action | ✅ | ✅ | ❌ | ✅\* |
| **API keys** | | | | |
| Create API key | ✅ | ✅ | ❌ | ✅\*\* |
| Update API key | ✅ | ✅ | ❌ | ✅\*\* |
| Delete API key | ✅ | ✅ | ❌ | ✅\*\* |
@@ -85,10 +84,10 @@ For more information on user roles & permissions, see below:
| Create tags | ✅ | ✅ | ❌ | ✅\* |
| Update tags | ✅ | ✅ | ❌ | ✅\* |
| Delete tags | ✅ | ✅ | ❌ | ✅\*\* |
| **People** | | | | |
| Delete Person | ✅ | ✅ | ❌ | ✅\* |
| **Contacts** | | | | |
| Delete contact | ✅ | ✅ | ❌ | ✅\* |
| **Integrations** | | | | |
| Manage Integrations | ✅ | ✅ | ❌ | ✅\* |
| Manage integrations | ✅ | ✅ | ❌ | ✅\* |
\* - for the read & write permissions team members
@@ -108,16 +107,13 @@ There are two ways to invite organization members: One by one or in bulk.
![Add member Button Position](/images/xm-and-surveys/core-features/access-roles/add-member.webp)
3. In the modal, add the Name, Email and Role of the organization member you want to invite:
![Individual Invite Modal Tab](/images/xm-and-surveys/core-features/access-roles/individual-invite.webp)
<Note>
Access Roles is a feature of the **Enterprise Edition**. In the **Community
Edition** and on the **Free** and **Startup** plan in the Cloud you can invite
unlimited organization members as `Owners`.
Access Roles is a feature of the **Enterprise Edition**. In the **Community Edition** and on the **Free**
and **Startup** plan in the Cloud you can invite unlimited organization members as `Owners`.
</Note>
Formbricks sends an email to the organization member with an invitation link. The organization member can accept the invitation or create a new account by clicking on the link.
@@ -128,7 +124,6 @@ Formbricks sends an email to the organization member with an invitation link. Th
![Where to find the Menu Item for Organization Settings](/images/xm-and-surveys/core-features/access-roles/organization-settings-menu.webp)
2. Click on the `Add member` button:
![Add member Button Position](/images/xm-and-surveys/core-features/access-roles/add-member.webp)

View File

@@ -6,91 +6,130 @@ icon: "code"
## **How Do Actions Work?**
Actions in Formbricks App Surveys are deeply integrated with user activities within your app. When a user performs a specified action, the Formbricks widget detects this activity and can present a survey to that specific user if the trigger conditions match for that survey. This capability ensures that surveys are triggered at the right time. You can set up these actions through a user-friendly No-Code interface within the Formbricks dashboard.
<Steps>
<Step title="User performs action">
The user performs an action in your application.
</Step>
## **Why Are Actions Useful?**
<Step title="Formbricks widget detects action">
The embedded widget (SDK) detects the action, if you set up a [No Code Action](/xm-and-surveys/surveys/website-app-surveys/actions#setting-up-no-code-actions) or a [Code Action](/xm-and-surveys/surveys/website-app-surveys/actions#setting-up-code-actions) to do so.
</Step>
Actions are invaluable for enhancing survey relevance and effectiveness:
<Step title="Check for survey trigger">
If a survey is set to trigger on that action, Formbricks checks if the user or visitor qualifies.
</Step>
- **Personalized Engagement**: Surveys triggered by user actions ensure content is highly relevant and engaging, matching each users current context.
<Step title="Check for user or visitor qualification">
If the user or visitor already filled out a survey in the past couple of days (see [Global Waiting Time](/xm-and-surveys/surveys/website-app-surveys/recontact#project-wide-global-waiting-time)), the survey will not be shown to prevent survey fatigue.
- **User Attributes**: By tying surveys to specific user attributes, such as activity levels or feature usage, you can customize the survey experience to reflect individual user profiles.
Also, if this user or visitor has seen this specific survey before, Formbricks might not show it as this is dependent on the [Recontact Options](/xm-and-surveys/surveys/website-app-surveys/recontact).
</Step>
- **User Targeting**: Precise targeting based on user attributes ensures that surveys are shown only to users who meet certain criteria, enhancing the relevance and effectiveness of each survey.
<Step title="Show survey">
If all conditions match, the survey is shown.
</Step>
</Steps>
<Tip>
Tying surveys to specific user actions enables **context-aware surveying**: You make sure that your user research is relevant to the current user context which leads to sign**ificantly higher response and completions rates** as well as lower survey fatigue.
</Tip>
## **Setting Up No-Code Actions**
Formbricks offers an intuitive No-Code interface that allows you to configure actions without needing to write any code.
To add a No-Code Action:
<Note>
No Code Actions are **not available for surveys in mobile apps**. No Code Action tracking are heavily dependent on JavaScript and most mobile apps are compiled into a different programming language. To track user actions in mobile apps use [Code Actions.](/xm-and-surveys/surveys/website-app-surveys/actions#setting-up-code-actions)
</Note>
1. Visit the Formbricks Dashboard & switch to the Actions tab:
<Steps>
<Step title="Visit the Actions tab via the main navigation">
![Action overview on Formbricks Open Source Survey Solution](/docs/image.png "Action overview on Formbricks Open Source Survey Solution")
</Step>
![setup checklist ui of survey popup for app surveys](/images/xm-and-surveys/surveys/website-app-surveys/actions/i1.webp)
<Step title="Click on “Add Action” in the top right corner to see the following:">
![Add action to open source in app survey](/images/xm-and-surveys/surveys/website-app-surveys/actions/i2.webp "Add action to open source in app survey")
</Step>
</Steps>
1. Now click on “Add Action
There are four types of No-Code actions:
![add action](/images/xm-and-surveys/surveys/website-app-surveys/actions/i2.webp)
### **1. Click Actio**n![](/docs/NoCodeAction-Click)
Here are four types of No-Code actions you can set up:
A Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text, CSS selector or both to trigger the survey.
### **1. Click Action**
* **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
Click Action is triggered when a user clicks on a specific element within your application. You can define the element's inner text or CSS selector to trigger the survey.
* **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
- **Inner Text**: Checks if the innerText of a clicked HTML element, like a button label, matches a specific text. This action allows you to display a survey based on text interactions within your application.
* **Both**: Only if both is true, the action is triggered
- **CSS Selector**: Verifies if a clicked HTML element matches a provided CSS selector, such as a class, ID, or any other CSS selector used in your website. It enables survey triggers based on element interactions.
### **2. Page View Action**
### **2. Page view Action**
![](/docs/NoCodeAction-PageView)
This action is triggered when a user visits a page within your application.
### **3. Exit Intent Action**
![](/docs/NoCodeAction-ExitIntent)
This action is triggered when a user is about to leave your application. It helps capture user feedback before they exit, providing valuable insights into user experiences and potential improvements.
### **4. 50% Scroll Action**
![](/docs/NoCodeAction-Scroll)
This action is triggered when a user scrolls through 50% of a page within your application. It helps capture user feedback at a specific point in their journey, enabling you to gather insights based on user interactions.
This action is triggered when a user visits a specific page within your application. You can define the URL match conditions as follows:
You can combine the url filters with any of the no-code actions to trigger the survey based on the URL match conditions.
### **URL Match Conditions**
### **Page Filter**
- **exactMatch**: Triggers the action when the URL exactly matches the specified string.
You can limit action tracking to specific subpages of your website or web app by using the Page Filter. Here you can use a variety of URL filter settings:
- **contains**: Activates when the URL contains the specified substring.
* **exactMatch**: Triggers the action when the URL exactly matches the specified string.
- **startsWith**: Fires when the URL starts with the specified string.
* **contains**: Activates when the URL contains the specified substring.
- **endsWith**: Executes when the URL ends with the specified string.
* **startsWith**: Fires when the URL starts with the specified string.
- **notMatch**: Triggers when the URL does not match the specified condition.
* **endsWith**: Executes when the URL ends with the specified string.
- **notContains**: Activates when the URL does not contain the specified substring.
* **notMatch**: Triggers when the URL does not match the specified condition.
* **notContains**: Activates when the URL does not contain the specified substring.
## **Setting Up Code Actions**
For more granular control, you can implement actions directly in your codebase:
For more granular control, you can implement actions directly in your code:
1. **Configure the Action**: First, add the action via the Formbricks web interface to make it available for survey configuration.
After that you can fire an action using `formbricks.track()`
<Steps>
<Step title="Configure action in Formbricks">
First, add the action via the Formbricks web interface to make it available for survey configuration:
2. **Track an Action**: Use formbricks.track() to send an action event to Formbricks.
![](/docs/CodeAction)
</Step>
```javascript Track an action formbricks.track("Action Name");
<Step title="Add action tracking to your code">
Use formbricks.track() to send an action event to Formbricks:
```
```
formbricks.track("action");
```
Here is an example of how to fire an action when a user clicks a button:
Here is an example of how to fire an action when a user clicks a button:
```javascript Track Button Click
const handleClick = () => {
formbricks.track("Button Clicked");
};
```javascript
const handleClick = () => {
formbricks.track("Button Clicked");
};
return <button onClick={handleClick}>Click Me</button>;
```
return <button onClick={handleClick}>Click Me</button>;
```
</Step>
</Steps>

View File

@@ -1,28 +1,63 @@
---
title: "Advanced Targeting"
description: "Advanced Targeting allows you to show surveys to the right group of people. You can target surveys based on user attributes, device type, and more instead of spraying and praying. This helps you get more relevant feedback and make data-driven decisions. All of this without writing a single line of code."
description: "Advanced Targeting allows you to show surveys to a specific segment of your users. You can target surveys based on user attributes, device type, and more. This helps you get more relevant insights while keeping survey fatigue at a minimum. After the initial setup, you can target any segment without touching code."
icon: "bullseye"
---
<Info>
Advanced Targeting is available on paid plans for both Formbricks Cloud and On Premise.
</Info>
## How to setup Advanced Targeting
### When to use Advanced Targeting?
<Note>
Advanced Targeting is only available on the Pro plan!
</Note>
Advanced Targeting helps you achieve a number of goals:
* On the Formbricks dashboard, click on **People** tab from the top navigation bar.
1. **Relevance**: Keep survey content relevant to respondents.
* Switch to the **Segments** tab & click on **Create Segment**.
2. **Cohort-analysis**: Survey specific user cohorts only.
* Give your segment a title & a description to help you remember what this segment is about.
3. **Statistical Relevance:** When surveying a smaller subset of users, statistical relevance is reached with a lot less responses.
* Now click on the **Add Filter** button to add a filter. You can filter based on user attributes, other segments, devices, and more.
## How does Advanced Targeting work?
* To group a set of filters together, click on the Three Dots icon on the right side of the filter and click on **Create Group**.
<Steps>
<Step title="Create Segment">
To get started, go to the Contacts tab and create a new Segment:
* Try playing around with different filters & conditions that we have provided to see how the segment size changes.
![](/docs/CreateSegment)
</Step>
* Once you are happy with the segment, click on **Save Segment**.
<Step title="Configure Segment based on attributes">
In the Segment editor, you can configure your Segment with a combination of Attributes, Segments and Devices. If a user matches either or all of the criteria, they become part of the Segment. See [Segment Configuration](/xm-and-surveys/surveys/website-app-surveys/advanced-targeting#segment-configuration) below.
</Step>
* Now, when you create a survey, you can select this segment to target your survey to.
<Step title="Create a survey of type Website & App">
Create a new survey and go to Settings to change it to Website & App survey:
![](/docs/images/Screenshot2025-02-20at13.25.29.png)
</Step>
<Step title="Choose Segment in Targeting options">
![](/docs/images/Screenshot2025-02-20at13.26.48.png)
</Step>
<Step title="Publish your survey" icon="party-horn" iconType="solid" />
</Steps>
### Segment Configuration
There are three means to move Contacts in or out of Segments: **Attributes**, other **Segments** and **Devices**:
1. **Attributes**: If the value of a specific attribute matches, the user becomes part of the Segment.
![](/docs/Segments-Attributes-2)
2. **Segments**: You can nest Segments meaning that if a user is or is not part of another Segment, they can be included or excluded
![](/docs/Screenshot2025-02-20at13.17.47.png)
3. **Devices**: If a user uses a Phone or Desktop, you can include or exclude them
![](/docs/images/Screenshot2025-02-20at13.23.30.png)
4. **Filter Groups:** You can group any of the above conditions in group and connect them logically with `AND` or `OR`. This allows for maximum granularity.

View File

@@ -1,5 +1,5 @@
---
title: "Show Survey to % of Users"
title: "Survey % of Users"
description: "Formbricks allows you to display surveys to only a percentage of your targeted users."
icon: "percent"
---
@@ -25,15 +25,15 @@ Set up this feature to control how many users see your survey, using a simple sl
![Survey Trigger Options](/images/xm-and-surveys/surveys/general-features/show-survey-to-percent-of-users/step-one.webp)
- Select **`Survey Trigger`** from the menu options.
* Select **`Survey Trigger`** from the menu options.
- Choose **`Survey Display Settings`** to access visibility controls.
* Choose **`Survey Display Settings`** to access visibility controls.
4. **Adjust User Visibility Percentage**:
1. **Adjust User Visibility Percentage**:
- Find the **`Show Survey to % of Users`** toggle. Enable it.
* Find the **`Show Survey to % of Users`** toggle. Enable it.
- Enter the desired percentage (from 0.01% to 100%) of users to whom the survey will be shown.
* Enter the desired percentage (from 0.01% to 100%) of users to whom the survey will be shown.
![Choose a link survey template](/images/xm-and-surveys/surveys/general-features/show-survey-to-percent-of-users/step-two.webp)
@@ -51,12 +51,12 @@ For instance, if you want to expose your survey to only half of your targeted us
It's effective for:
- **A/B Testing**: Compare different user experiences by showing different surveys or survey variations to different segments of your audience.
* **A/B Testing**: Compare different user experiences by showing different surveys or survey variations to different segments of your audience.
- **Gradual Rollouts**: Gradually introduce a new survey to a portion of your users to monitor initial responses and adjust based on feedback before full deployment.
* **Gradual Rollouts**: Gradually introduce a new survey to a portion of your users to monitor initial responses and adjust based on feedback before full deployment.
- **Load Management**: Manage server load and response processing by limiting the number of responses collected at any given time.
* **Load Management**: Manage server load and response processing by limiting the number of responses collected at any given time.
## **Conclusion**
Using the **Show Survey to % of Users** feature allows for precise control over who sees your Formbricks surveys, making it an invaluable feature for targeted data collection and user experience management. By adjusting survey visibility settings, you can strategically engage segments of your audience, enhance survey testing phases, and optimize overall survey effectiveness.
Using the **Show Survey to % of Users** feature allows for precise control over who sees your Formbricks surveys, making it an invaluable feature for targeted data collection and user experience management. By adjusting survey visibility settings, you can strategically engage segments of your audience, enhance survey testing phases, and optimize overall survey effectiveness.

View File

@@ -8,8 +8,6 @@ export const IS_FORMBRICKS_CLOUD = env.IS_FORMBRICKS_CLOUD === "1";
export const WEBAPP_URL =
env.WEBAPP_URL || (env.VERCEL_URL ? `https://${env.VERCEL_URL}` : false) || "http://localhost:3000";
export const SHORT_URL_BASE = env.SHORT_URL_BASE ? env.SHORT_URL_BASE : WEBAPP_URL;
// encryption keys
export const FORMBRICKS_ENCRYPTION_KEY = env.FORMBRICKS_ENCRYPTION_KEY || undefined;
export const ENCRYPTION_KEY = env.ENCRYPTION_KEY;

View File

@@ -73,7 +73,6 @@ export const env = createEnv({
S3_SECRET_KEY: z.string().optional(),
S3_ENDPOINT_URL: z.string().optional(),
S3_FORCE_PATH_STYLE: z.enum(["1", "0"]).optional(),
SHORT_URL_BASE: z.string().url().optional().or(z.string().length(0)),
SIGNUP_DISABLED: z.enum(["1", "0"]).optional(),
SLACK_CLIENT_ID: z.string().optional(),
SLACK_CLIENT_SECRET: z.string().optional(),
@@ -197,7 +196,6 @@ export const env = createEnv({
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
S3_ENDPOINT_URL: process.env.S3_ENDPOINT_URL,
S3_FORCE_PATH_STYLE: process.env.S3_FORCE_PATH_STYLE,
SHORT_URL_BASE: process.env.SHORT_URL_BASE,
SIGNUP_DISABLED: process.env.SIGNUP_DISABLED,
SLACK_CLIENT_ID: process.env.SLACK_CLIENT_ID,
SLACK_CLIENT_SECRET: process.env.SLACK_CLIENT_SECRET,

View File

@@ -19,6 +19,7 @@ export const ZPlacement = z.enum(["bottomLeft", "bottomRight", "topLeft", "topRi
export type TPlacement = z.infer<typeof ZPlacement>;
export const ZAllowedFileExtension = z.enum([
"heic",
"png",
"jpeg",
"jpg",

86
pnpm-lock.yaml generated
View File

@@ -387,6 +387,9 @@ importers:
googleapis:
specifier: 144.0.0
version: 144.0.0(encoding@0.1.13)
heic-convert:
specifier: 2.1.0
version: 2.1.0
https-proxy-agent:
specifier: 7.0.6
version: 7.0.6
@@ -520,6 +523,9 @@ importers:
'@types/bcryptjs':
specifier: 2.4.6
version: 2.4.6
'@types/heic-convert':
specifier: 2.1.0
version: 2.1.0
'@types/lodash':
specifier: 4.17.13
version: 4.17.13
@@ -591,7 +597,7 @@ importers:
version: 8.18.0(eslint@8.57.0)(typescript@5.7.2)
'@vercel/style-guide':
specifier: 6.0.0
version: 6.0.0(@next/eslint-plugin-next@15.1.0)(eslint@8.57.0)(prettier@3.4.2)(typescript@5.7.2)(vitest@3.0.5(tsx@4.19.2))
version: 6.0.0(@next/eslint-plugin-next@15.1.0)(eslint@8.57.0)(prettier@3.4.2)(typescript@5.7.2)(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.10.2)(jiti@2.4.1)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0))
eslint-config-next:
specifier: 15.1.0
version: 15.1.0(eslint@8.57.0)(typescript@5.7.2)
@@ -5467,6 +5473,9 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/heic-convert@2.1.0':
resolution: {integrity: sha512-Cf5Sdc2Gm2pfZ0uN1zjj35wcf3mF1lJCMIzws5OdJynrdMJRTIRUGa5LegbVg0hatzOPkH2uAf2JRjPYgl9apg==}
'@types/istanbul-lib-coverage@2.0.6':
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
@@ -8359,8 +8368,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hast-util-to-jsx-runtime@2.3.2:
resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==}
hast-util-to-jsx-runtime@2.3.3:
resolution: {integrity: sha512-pdpkP8YD4v+qMKn2lnKSiJvZvb3FunDmFYQvVOsoO08+eTNWdaWKPMrC5wwNICtU3dQWHhElj5Sf5jPEnv4qJg==}
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
@@ -8369,6 +8378,14 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
heic-convert@2.1.0:
resolution: {integrity: sha512-1qDuRvEHifTVAj3pFIgkqGgJIr0M3X7cxEPjEp0oG4mo8GFjq99DpCo8Eg3kg17Cy0MTjxpFdoBHOatj7ZVKtg==}
engines: {node: '>=12.0.0'}
heic-decode@2.0.0:
resolution: {integrity: sha512-NU+zsiDvdL+EebyTjrEqjkO2XYI7FgLhQzsbmO8dnnYce3S0PBSDm/ZyI4KpcGPXYEdb5W72vp/AQFuc4F8ASg==}
engines: {node: '>=8.0.0'}
help-me@3.0.0:
resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==}
@@ -8944,6 +8961,9 @@ packages:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
jpeg-js@0.4.4:
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
js-cookie@2.2.1:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
@@ -9133,6 +9153,10 @@ packages:
engines: {node: '>=16'}
hasBin: true
libheif-js@1.18.2:
resolution: {integrity: sha512-4Nk0dKhhRfVS4mECcX2jSDpNU6gcHQLneJjkGQq61N8COGtjSpSA3CI+1Q3kUYv5Vf+SwIqUtaDSdU6JO37c6w==}
engines: {node: '>=8.0.0'}
lighthouse-logger@1.4.2:
resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==}
@@ -10478,6 +10502,10 @@ packages:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
pngjs@6.0.0:
resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==}
engines: {node: '>=12.13.0'}
polished@4.3.1:
resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==}
engines: {node: '>=10'}
@@ -10747,8 +10775,8 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
property-information@6.5.0:
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
property-information@7.0.0:
resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
@@ -18841,6 +18869,8 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/heic-convert@2.1.0': {}
'@types/istanbul-lib-coverage@2.0.6': {}
'@types/istanbul-lib-report@3.0.3':
@@ -19259,7 +19289,7 @@ snapshots:
next: 15.1.2(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
'@vercel/style-guide@6.0.0(@next/eslint-plugin-next@15.1.0)(eslint@8.57.0)(prettier@3.4.2)(typescript@5.7.2)(vitest@3.0.5(tsx@4.19.2))':
'@vercel/style-guide@6.0.0(@next/eslint-plugin-next@15.1.0)(eslint@8.57.0)(prettier@3.4.2)(typescript@5.7.2)(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.10.2)(jiti@2.4.1)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0))':
dependencies:
'@babel/core': 7.26.0
'@babel/eslint-parser': 7.26.5(@babel/core@7.26.0)(eslint@8.57.0)
@@ -19267,7 +19297,7 @@ snapshots:
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.7.2)
eslint-config-prettier: 9.1.0(eslint@8.57.0)
eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0))
eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0)
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0)
eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)
@@ -19279,7 +19309,7 @@ snapshots:
eslint-plugin-testing-library: 6.5.0(eslint@8.57.0)(typescript@5.7.2)
eslint-plugin-tsdoc: 0.2.17
eslint-plugin-unicorn: 51.0.1(eslint@8.57.0)
eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)(vitest@3.0.5(tsx@4.19.2))
eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.10.2)(jiti@2.4.1)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0))
prettier-plugin-packagejson: 2.5.8(prettier@3.4.2)
optionalDependencies:
'@next/eslint-plugin-next': 15.1.0
@@ -19369,7 +19399,7 @@ snapshots:
optionalDependencies:
vite: 5.4.14(@types/node@22.10.2)(lightningcss@1.27.0)(terser@5.37.0)
'@vitest/mocker@3.0.5(vite@6.0.9(tsx@4.19.2))':
'@vitest/mocker@3.0.5(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0))':
dependencies:
'@vitest/spy': 3.0.5
estree-walker: 3.0.3
@@ -21378,9 +21408,9 @@ snapshots:
eslint: 8.57.0
eslint-plugin-turbo: 2.3.3(eslint@8.57.0)
eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)):
eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0):
dependencies:
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)
eslint-import-resolver-node@0.3.9:
dependencies:
@@ -21402,11 +21432,11 @@ snapshots:
is-glob: 4.0.3
stable-hash: 0.0.4
optionalDependencies:
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0):
eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -21461,7 +21491,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -21628,7 +21658,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)(vitest@3.0.5(tsx@4.19.2)):
eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0)(typescript@5.7.2)(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.10.2)(jiti@2.4.1)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)):
dependencies:
'@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.7.2)
eslint: 8.57.0
@@ -22399,7 +22429,7 @@ snapshots:
dependencies:
function-bind: 1.1.2
hast-util-to-jsx-runtime@2.3.2:
hast-util-to-jsx-runtime@2.3.3:
dependencies:
'@types/estree': 1.0.6
'@types/hast': 3.0.4
@@ -22411,7 +22441,7 @@ snapshots:
mdast-util-mdx-expression: 2.0.1
mdast-util-mdx-jsx: 3.2.0
mdast-util-mdxjs-esm: 2.0.1
property-information: 6.5.0
property-information: 7.0.0
space-separated-tokens: 2.0.2
style-to-object: 1.0.8
unist-util-position: 5.0.0
@@ -22425,6 +22455,16 @@ snapshots:
he@1.2.0: {}
heic-convert@2.1.0:
dependencies:
heic-decode: 2.0.0
jpeg-js: 0.4.4
pngjs: 6.0.0
heic-decode@2.0.0:
dependencies:
libheif-js: 1.18.2
help-me@3.0.0:
dependencies:
glob: 7.2.3
@@ -23028,6 +23068,8 @@ snapshots:
joycon@3.1.1: {}
jpeg-js@0.4.4: {}
js-cookie@2.2.1: {}
js-sdsl@4.3.0: {}
@@ -23245,6 +23287,8 @@ snapshots:
dependencies:
isomorphic.js: 0.2.5
libheif-js@1.18.2: {}
lighthouse-logger@1.4.2:
dependencies:
debug: 2.6.9
@@ -24933,6 +24977,8 @@ snapshots:
pngjs@5.0.0: {}
pngjs@6.0.0: {}
polished@4.3.1:
dependencies:
'@babel/runtime': 7.26.7
@@ -25137,7 +25183,7 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
property-information@6.5.0: {}
property-information@7.0.0: {}
proxy-from-env@1.1.0: {}
@@ -25325,7 +25371,7 @@ snapshots:
'@types/hast': 3.0.4
'@types/react': 19.0.1
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.2
hast-util-to-jsx-runtime: 2.3.3
html-url-attributes: 3.0.1
mdast-util-to-hast: 13.2.0
react: 19.0.0
@@ -27274,7 +27320,7 @@ snapshots:
vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.10.2)(jiti@2.4.1)(jsdom@25.0.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0):
dependencies:
'@vitest/expect': 3.0.5
'@vitest/mocker': 3.0.5(vite@6.0.9(tsx@4.19.2))
'@vitest/mocker': 3.0.5(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(lightningcss@1.27.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0))
'@vitest/pretty-format': 3.0.5
'@vitest/runner': 3.0.5
'@vitest/snapshot': 3.0.5

View File

@@ -159,7 +159,6 @@
"S3_REGION",
"S3_SECRET_KEY",
"SENTRY_DSN",
"SHORT_URL_BASE",
"SIGNUP_DISABLED",
"SLACK_CLIENT_ID",
"SLACK_CLIENT_SECRET",