diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/LinkTab.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/LinkTab.tsx
index 14725110de..eee5232091 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/LinkTab.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/LinkTab.tsx
@@ -16,6 +16,7 @@ interface LinkTabProps {
export const LinkTab = ({ survey, webAppUrl, surveyUrl, setSurveyUrl, locale }: LinkTabProps) => {
const { t } = useTranslate();
+
const docsLinks = [
{
title: t("environments.surveys.summary.data_prefilling"),
@@ -48,6 +49,7 @@ export const LinkTab = ({ survey, webAppUrl, surveyUrl, setSurveyUrl, locale }:
locale={locale}
/>
+
{t("environments.surveys.summary.you_can_do_a_lot_more_with_links_surveys")} 💡
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/get-qr-code-options.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/get-qr-code-options.ts
new file mode 100644
index 0000000000..300c58c271
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/get-qr-code-options.ts
@@ -0,0 +1,36 @@
+import { Options } from "qr-code-styling";
+
+export const getQRCodeOptions = (width: number, height: number): Options => ({
+ width,
+ height,
+ type: "svg",
+ data: "",
+ margin: 0,
+ qrOptions: {
+ typeNumber: 0,
+ mode: "Byte",
+ errorCorrectionLevel: "L",
+ },
+ imageOptions: {
+ saveAsBlob: true,
+ hideBackgroundDots: false,
+ imageSize: 0,
+ margin: 0,
+ },
+ dotsOptions: {
+ type: "extra-rounded",
+ color: "#000000",
+ roundSize: true,
+ },
+ backgroundOptions: {
+ color: "#ffffff",
+ },
+ cornersSquareOptions: {
+ type: "dot",
+ color: "#000000",
+ },
+ cornersDotOptions: {
+ type: "dot",
+ color: "#000000",
+ },
+});
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/survey-qr-code.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/survey-qr-code.tsx
new file mode 100644
index 0000000000..2a74b21961
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/survey-qr-code.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import { getQRCodeOptions } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/get-qr-code-options";
+import { useTranslate } from "@tolgee/react";
+import QRCodeStyling from "qr-code-styling";
+import { useEffect, useRef } from "react";
+import { toast } from "react-hot-toast";
+
+export const useSurveyQRCode = (surveyUrl: string) => {
+ const qrCodeRef = useRef(null);
+ const qrInstance = useRef(null);
+ const { t } = useTranslate();
+
+ useEffect(() => {
+ try {
+ if (!qrInstance.current) {
+ qrInstance.current = new QRCodeStyling(getQRCodeOptions(70, 70));
+ }
+
+ if (surveyUrl && qrInstance.current) {
+ qrInstance.current.update({ data: surveyUrl });
+
+ if (qrCodeRef.current) {
+ qrCodeRef.current.innerHTML = "";
+ qrInstance.current.append(qrCodeRef.current);
+ }
+ }
+ } catch (error) {
+ toast.error(t("environments.surveys.summary.failed_to_generate_qr_code"));
+ }
+ }, [surveyUrl]);
+
+ const downloadQRCode = () => {
+ try {
+ const downloadInstance = new QRCodeStyling(getQRCodeOptions(500, 500));
+ downloadInstance.update({ data: surveyUrl });
+ downloadInstance.download({ name: "survey-qr", extension: "png" });
+ } catch (error) {
+ toast.error(t("environments.surveys.summary.failed_to_generate_qr_code"));
+ }
+ };
+
+ return { qrCodeRef, downloadQRCode };
+};
diff --git a/apps/web/modules/analysis/components/ShareSurveyLink/index.tsx b/apps/web/modules/analysis/components/ShareSurveyLink/index.tsx
index 33528d9f51..70b983eb1b 100644
--- a/apps/web/modules/analysis/components/ShareSurveyLink/index.tsx
+++ b/apps/web/modules/analysis/components/ShareSurveyLink/index.tsx
@@ -1,10 +1,11 @@
"use client";
+import { useSurveyQRCode } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/survey-qr-code";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { generateSingleUseIdAction } from "@/modules/survey/list/actions";
import { Button } from "@/modules/ui/components/button";
import { useTranslate } from "@tolgee/react";
-import { Copy, RefreshCcw, SquareArrowOutUpRight } from "lucide-react";
+import { Copy, QrCode, RefreshCcw, SquareArrowOutUpRight } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { TSurvey } from "@formbricks/types/surveys/types";
@@ -68,6 +69,8 @@ export const ShareSurveyLink = ({
getUrl();
}, [survey, getUrl, language]);
+ const { downloadQRCode } = useSurveyQRCode(surveyUrl);
+
return (
@@ -100,6 +103,14 @@ export const ShareSurveyLink = ({
{t("common.copy")}
+
{survey.singleUse?.enabled && (