mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-22 06:00:51 -06:00
Compare commits
4 Commits
fix-data-r
...
v3.17.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f08fabfb13 | ||
|
|
ee8af9dd74 | ||
|
|
1091b40bd1 | ||
|
|
87a2d727ed |
@@ -37,7 +37,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
helmfile-deploy:
|
helmfile-deploy:
|
||||||
|
|||||||
17
.github/workflows/formbricks-release.yml
vendored
17
.github/workflows/formbricks-release.yml
vendored
@@ -7,12 +7,13 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
|
||||||
ENVIRONMENT: ${{ github.event.release.prerelease && 'staging' || 'production' }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-build:
|
docker-build:
|
||||||
name: Build & release docker image
|
name: Build & release docker image
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
uses: ./.github/workflows/release-docker-github.yml
|
uses: ./.github/workflows/release-docker-github.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
@@ -20,6 +21,9 @@ jobs:
|
|||||||
|
|
||||||
helm-chart-release:
|
helm-chart-release:
|
||||||
name: Release Helm Chart
|
name: Release Helm Chart
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
uses: ./.github/workflows/release-helm-chart.yml
|
uses: ./.github/workflows/release-helm-chart.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
needs:
|
needs:
|
||||||
@@ -29,6 +33,9 @@ jobs:
|
|||||||
|
|
||||||
deploy-formbricks-cloud:
|
deploy-formbricks-cloud:
|
||||||
name: Deploy Helm Chart to Formbricks Cloud
|
name: Deploy Helm Chart to Formbricks Cloud
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
uses: ./.github/workflows/deploy-formbricks-cloud.yml
|
uses: ./.github/workflows/deploy-formbricks-cloud.yml
|
||||||
needs:
|
needs:
|
||||||
@@ -36,7 +43,7 @@ jobs:
|
|||||||
- helm-chart-release
|
- helm-chart-release
|
||||||
with:
|
with:
|
||||||
VERSION: v${{ needs.docker-build.outputs.VERSION }}
|
VERSION: v${{ needs.docker-build.outputs.VERSION }}
|
||||||
ENVIRONMENT: ${{ env.ENVIRONMENT }}
|
ENVIRONMENT: ${{ github.event.release.prerelease && 'staging' || 'production' }}
|
||||||
|
|
||||||
upload-sentry-sourcemaps:
|
upload-sentry-sourcemaps:
|
||||||
name: Upload Sentry Sourcemaps
|
name: Upload Sentry Sourcemaps
|
||||||
@@ -64,4 +71,4 @@ jobs:
|
|||||||
docker_image: ghcr.io/formbricks/formbricks:v${{ needs.docker-build.outputs.VERSION }}
|
docker_image: ghcr.io/formbricks/formbricks:v${{ needs.docker-build.outputs.VERSION }}
|
||||||
release_version: v${{ needs.docker-build.outputs.VERSION }}
|
release_version: v${{ needs.docker-build.outputs.VERSION }}
|
||||||
sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
environment: ${{ env.ENVIRONMENT }}
|
environment: ${{ github.event.release.prerelease && 'staging' || 'production' }}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
SquareStack,
|
SquareStack,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { TSegment } from "@formbricks/types/segment";
|
import { TSegment } from "@formbricks/types/segment";
|
||||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||||
import { TUser } from "@formbricks/types/user";
|
import { TUser } from "@formbricks/types/user";
|
||||||
@@ -77,6 +77,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: string;
|
description: string;
|
||||||
componentType: React.ComponentType<unknown>;
|
componentType: React.ComponentType<unknown>;
|
||||||
componentProps: unknown;
|
componentProps: unknown;
|
||||||
|
disabled?: boolean;
|
||||||
}[] = useMemo(
|
}[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -111,6 +112,7 @@ export const ShareSurveyModal = ({
|
|||||||
isContactsEnabled,
|
isContactsEnabled,
|
||||||
isFormbricksCloud,
|
isFormbricksCloud,
|
||||||
},
|
},
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.WEBSITE_EMBED,
|
id: ShareViaType.WEBSITE_EMBED,
|
||||||
@@ -121,6 +123,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.share.embed_on_website.description"),
|
description: t("environments.surveys.share.embed_on_website.description"),
|
||||||
componentType: WebsiteEmbedTab,
|
componentType: WebsiteEmbedTab,
|
||||||
componentProps: { surveyUrl },
|
componentProps: { surveyUrl },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.EMAIL,
|
id: ShareViaType.EMAIL,
|
||||||
@@ -131,6 +134,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.share.send_email.description"),
|
description: t("environments.surveys.share.send_email.description"),
|
||||||
componentType: EmailTab,
|
componentType: EmailTab,
|
||||||
componentProps: { surveyId: survey.id, email },
|
componentProps: { surveyId: survey.id, email },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.SOCIAL_MEDIA,
|
id: ShareViaType.SOCIAL_MEDIA,
|
||||||
@@ -141,6 +145,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.share.social_media.description"),
|
description: t("environments.surveys.share.social_media.description"),
|
||||||
componentType: SocialMediaTab,
|
componentType: SocialMediaTab,
|
||||||
componentProps: { surveyUrl, surveyTitle: survey.name },
|
componentProps: { surveyUrl, surveyTitle: survey.name },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.QR_CODE,
|
id: ShareViaType.QR_CODE,
|
||||||
@@ -151,6 +156,7 @@ export const ShareSurveyModal = ({
|
|||||||
description: t("environments.surveys.summary.qr_code_description"),
|
description: t("environments.surveys.summary.qr_code_description"),
|
||||||
componentType: QRCodeTab,
|
componentType: QRCodeTab,
|
||||||
componentProps: { surveyUrl },
|
componentProps: { surveyUrl },
|
||||||
|
disabled: survey.singleUse?.enabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ShareViaType.DYNAMIC_POPUP,
|
id: ShareViaType.DYNAMIC_POPUP,
|
||||||
@@ -177,9 +183,9 @@ export const ShareSurveyModal = ({
|
|||||||
t,
|
t,
|
||||||
survey,
|
survey,
|
||||||
publicDomain,
|
publicDomain,
|
||||||
setSurveyUrl,
|
|
||||||
user.locale,
|
user.locale,
|
||||||
surveyUrl,
|
surveyUrl,
|
||||||
|
isReadOnly,
|
||||||
environmentId,
|
environmentId,
|
||||||
segments,
|
segments,
|
||||||
isContactsEnabled,
|
isContactsEnabled,
|
||||||
@@ -188,9 +194,14 @@ export const ShareSurveyModal = ({
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [activeId, setActiveId] = useState<ShareViaType | ShareSettingsType>(
|
const getDefaultActiveId = useCallback(() => {
|
||||||
survey.type === "link" ? ShareViaType.ANON_LINKS : ShareViaType.APP
|
if (survey.type !== "link") {
|
||||||
);
|
return ShareViaType.APP;
|
||||||
|
}
|
||||||
|
return ShareViaType.ANON_LINKS;
|
||||||
|
}, [survey.type]);
|
||||||
|
|
||||||
|
const [activeId, setActiveId] = useState<ShareViaType | ShareSettingsType>(getDefaultActiveId());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -198,11 +209,19 @@ export const ShareSurveyModal = ({
|
|||||||
}
|
}
|
||||||
}, [open, modalView]);
|
}, [open, modalView]);
|
||||||
|
|
||||||
|
// Ensure active tab is not disabled - if it is, switch to default
|
||||||
|
useEffect(() => {
|
||||||
|
const activeTab = linkTabs.find((tab) => tab.id === activeId);
|
||||||
|
if (activeTab?.disabled) {
|
||||||
|
setActiveId(getDefaultActiveId());
|
||||||
|
}
|
||||||
|
}, [activeId, linkTabs, getDefaultActiveId]);
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
setOpen(open);
|
setOpen(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setShowView("start");
|
setShowView("start");
|
||||||
setActiveId(ShareViaType.ANON_LINKS);
|
setActiveId(getDefaultActiveId());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -150,13 +150,13 @@ export const LinkSettingsTab = ({ isReadOnly, locale }: LinkSettingsTabProps) =>
|
|||||||
name: "title",
|
name: "title",
|
||||||
label: t("environments.surveys.share.link_settings.link_title"),
|
label: t("environments.surveys.share.link_settings.link_title"),
|
||||||
description: t("environments.surveys.share.link_settings.link_title_description"),
|
description: t("environments.surveys.share.link_settings.link_title_description"),
|
||||||
placeholder: t("environments.surveys.share.link_settings.link_title_placeholder"),
|
placeholder: survey.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
label: t("environments.surveys.share.link_settings.link_description"),
|
label: t("environments.surveys.share.link_settings.link_description"),
|
||||||
description: t("environments.surveys.share.link_settings.link_description_description"),
|
description: t("environments.surveys.share.link_settings.link_description_description"),
|
||||||
placeholder: t("environments.surveys.share.link_settings.link_description_placeholder"),
|
placeholder: "Please complete this survey.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface ShareViewProps {
|
|||||||
componentProps: any;
|
componentProps: any;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
activeId: ShareViaType | ShareSettingsType;
|
activeId: ShareViaType | ShareSettingsType;
|
||||||
setActiveId: React.Dispatch<React.SetStateAction<ShareViaType | ShareSettingsType>>;
|
setActiveId: React.Dispatch<React.SetStateAction<ShareViaType | ShareSettingsType>>;
|
||||||
@@ -109,12 +110,13 @@ export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
|
|||||||
onClick={() => setActiveId(tab.id)}
|
onClick={() => setActiveId(tab.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full justify-start rounded-md p-2 text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
"flex w-full justify-start rounded-md p-2 text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
||||||
tab.id === activeId
|
tab.id === activeId && !tab.disabled
|
||||||
? "bg-slate-100 font-medium text-slate-900"
|
? "bg-slate-100 font-medium text-slate-900"
|
||||||
: "text-slate-700"
|
: "text-slate-700"
|
||||||
)}
|
)}
|
||||||
tooltip={tab.label}
|
tooltip={tab.label}
|
||||||
isActive={tab.id === activeId}>
|
isActive={tab.id === activeId}
|
||||||
|
disabled={tab.disabled}>
|
||||||
<tab.icon className="h-4 w-4 text-slate-700" />
|
<tab.icon className="h-4 w-4 text-slate-700" />
|
||||||
<span>{tab.label}</span>
|
<span>{tab.label}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
@@ -136,9 +138,10 @@ export const ShareView = ({ tabs, activeId, setActiveId }: ShareViewProps) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setActiveId(tab.id)}
|
onClick={() => setActiveId(tab.id)}
|
||||||
|
disabled={tab.disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-md px-4 py-2",
|
"rounded-md px-4 py-2",
|
||||||
tab.id === activeId
|
tab.id === activeId && !tab.disabled
|
||||||
? "bg-white text-slate-900 shadow-sm hover:bg-white"
|
? "bg-white text-slate-900 shadow-sm hover:bg-white"
|
||||||
: "border-transparent text-slate-700 hover:text-slate-900"
|
: "border-transparent text-slate-700 hover:text-slate-900"
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ describe("contact-survey page", () => {
|
|||||||
params: Promise.resolve({ jwt: "token" }),
|
params: Promise.resolve({ jwt: "token" }),
|
||||||
searchParams: Promise.resolve({}),
|
searchParams: Promise.resolve({}),
|
||||||
});
|
});
|
||||||
expect(meta).toEqual({ title: "Survey", description: "Complete this survey" });
|
expect(meta).toEqual({ title: "Survey", description: "Please complete this survey." });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generateMetadata returns default when verify throws", async () => {
|
test("generateMetadata returns default when verify throws", async () => {
|
||||||
@@ -103,7 +103,7 @@ describe("contact-survey page", () => {
|
|||||||
params: Promise.resolve({ jwt: "token" }),
|
params: Promise.resolve({ jwt: "token" }),
|
||||||
searchParams: Promise.resolve({}),
|
searchParams: Promise.resolve({}),
|
||||||
});
|
});
|
||||||
expect(meta).toEqual({ title: "Survey", description: "Complete this survey" });
|
expect(meta).toEqual({ title: "Survey", description: "Please complete this survey." });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("generateMetadata returns basic metadata when token valid", async () => {
|
test("generateMetadata returns basic metadata when token valid", async () => {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const generateMetadata = async (props: ContactSurveyPageProps): Promise<M
|
|||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
return {
|
return {
|
||||||
title: "Survey",
|
title: "Survey",
|
||||||
description: "Complete this survey",
|
description: "Please complete this survey.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { surveyId } = result.data;
|
const { surveyId } = result.data;
|
||||||
@@ -40,7 +40,7 @@ export const generateMetadata = async (props: ContactSurveyPageProps): Promise<M
|
|||||||
// If the token is invalid, we'll return generic metadata
|
// If the token is invalid, we'll return generic metadata
|
||||||
return {
|
return {
|
||||||
title: "Survey",
|
title: "Survey",
|
||||||
description: "Complete this survey",
|
description: "Please complete this survey.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ describe("Metadata Utils", () => {
|
|||||||
expect(getSurvey).toHaveBeenCalledWith(mockSurveyId);
|
expect(getSurvey).toHaveBeenCalledWith(mockSurveyId);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
title: "Survey",
|
title: "Survey",
|
||||||
description: "Complete this survey",
|
description: "Please complete this survey.",
|
||||||
survey: null,
|
survey: null,
|
||||||
ogImage: undefined,
|
ogImage: undefined,
|
||||||
});
|
});
|
||||||
@@ -108,10 +108,9 @@ describe("Metadata Utils", () => {
|
|||||||
const result = await getBasicSurveyMetadata(mockSurveyId);
|
const result = await getBasicSurveyMetadata(mockSurveyId);
|
||||||
|
|
||||||
expect(getSurvey).toHaveBeenCalledWith(mockSurveyId);
|
expect(getSurvey).toHaveBeenCalledWith(mockSurveyId);
|
||||||
expect(getProjectByEnvironmentId).toHaveBeenCalledWith(mockEnvironmentId);
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
title: "Welcome Headline | Test Project",
|
title: "Welcome Headline",
|
||||||
description: "Complete this survey",
|
description: "Please complete this survey.",
|
||||||
survey: mockSurvey,
|
survey: mockSurvey,
|
||||||
ogImage: undefined,
|
ogImage: undefined,
|
||||||
});
|
});
|
||||||
@@ -129,13 +128,12 @@ describe("Metadata Utils", () => {
|
|||||||
} as TSurvey;
|
} as TSurvey;
|
||||||
|
|
||||||
vi.mocked(getSurvey).mockResolvedValue(mockSurvey);
|
vi.mocked(getSurvey).mockResolvedValue(mockSurvey);
|
||||||
vi.mocked(getProjectByEnvironmentId).mockResolvedValue({ name: "Test Project" } as any);
|
|
||||||
|
|
||||||
const result = await getBasicSurveyMetadata(mockSurveyId);
|
const result = await getBasicSurveyMetadata(mockSurveyId);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
title: "Test Survey | Test Project",
|
title: "Test Survey",
|
||||||
description: "Complete this survey",
|
description: "Please complete this survey.",
|
||||||
survey: mockSurvey,
|
survey: mockSurvey,
|
||||||
ogImage: undefined,
|
ogImage: undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
|
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
|
||||||
import { getPublicDomain } from "@/lib/getPublicUrl";
|
import { getPublicDomain } from "@/lib/getPublicUrl";
|
||||||
|
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||||
import { COLOR_DEFAULTS } from "@/lib/styling/constants";
|
import { COLOR_DEFAULTS } from "@/lib/styling/constants";
|
||||||
import { getSurvey } from "@/modules/survey/lib/survey";
|
import { getSurvey } from "@/modules/survey/lib/survey";
|
||||||
import { getProjectByEnvironmentId } from "@/modules/survey/link/lib/project";
|
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
type TBasicSurveyMetadata = {
|
type TBasicSurveyMetadata = {
|
||||||
@@ -12,22 +12,16 @@ type TBasicSurveyMetadata = {
|
|||||||
ogImage?: string;
|
ogImage?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const getNameForURL = (value: string) => encodeURIComponent(value);
|
||||||
* Utility function to encode name for URL usage
|
|
||||||
*/
|
|
||||||
export const getNameForURL = (url: string) => url.replace(/ /g, "%20");
|
|
||||||
|
|
||||||
/**
|
export const getBrandColorForURL = (value: string) => encodeURIComponent(value);
|
||||||
* Utility function to encode brand color for URL usage
|
|
||||||
*/
|
|
||||||
export const getBrandColorForURL = (url: string) => url.replace(/#/g, "%23");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get basic survey metadata (title and description) based on link metadata, welcome card or survey name
|
* Get basic survey metadata (title and description) based on link metadata, welcome card or survey name
|
||||||
*/
|
*/
|
||||||
export const getBasicSurveyMetadata = async (
|
export const getBasicSurveyMetadata = async (
|
||||||
surveyId: string,
|
surveyId: string,
|
||||||
languageCode?: string
|
languageCode = "default"
|
||||||
): Promise<TBasicSurveyMetadata> => {
|
): Promise<TBasicSurveyMetadata> => {
|
||||||
const survey = await getSurvey(surveyId);
|
const survey = await getSurvey(surveyId);
|
||||||
|
|
||||||
@@ -35,7 +29,7 @@ export const getBasicSurveyMetadata = async (
|
|||||||
if (!survey) {
|
if (!survey) {
|
||||||
return {
|
return {
|
||||||
title: "Survey",
|
title: "Survey",
|
||||||
description: "Complete this survey",
|
description: "Please complete this survey.",
|
||||||
survey: null,
|
survey: null,
|
||||||
ogImage: undefined,
|
ogImage: undefined,
|
||||||
};
|
};
|
||||||
@@ -43,38 +37,33 @@ export const getBasicSurveyMetadata = async (
|
|||||||
|
|
||||||
const metadata = survey.metadata;
|
const metadata = survey.metadata;
|
||||||
const welcomeCard = survey.welcomeCard;
|
const welcomeCard = survey.welcomeCard;
|
||||||
|
const useDefaultLanguageCode =
|
||||||
|
languageCode === "default" ||
|
||||||
|
survey.languages.find((lang) => lang.language.code === languageCode)?.default;
|
||||||
|
|
||||||
// Determine language code to use for metadata
|
// Determine language code to use for metadata
|
||||||
const langCode = languageCode || "default";
|
const langCode = useDefaultLanguageCode ? "default" : languageCode;
|
||||||
|
|
||||||
// Set title - priority: custom link metadata > welcome card > survey name
|
// Set title - priority: custom link metadata > welcome card > survey name
|
||||||
let title = "Survey";
|
const titleFromMetadata = metadata?.title ? getLocalizedValue(metadata.title, langCode) || "" : undefined;
|
||||||
if (metadata.title?.[langCode]) {
|
const titleFromWelcome =
|
||||||
title = metadata.title[langCode];
|
welcomeCard?.enabled && welcomeCard.headline
|
||||||
} else if (welcomeCard.enabled && welcomeCard.headline?.default) {
|
? getLocalizedValue(welcomeCard.headline, langCode) || ""
|
||||||
title = welcomeCard.headline.default;
|
: undefined;
|
||||||
} else {
|
let title = titleFromMetadata || titleFromWelcome || survey.name;
|
||||||
title = survey.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set description - priority: custom link metadata > welcome card > default
|
// Set description - priority: custom link metadata > welcome card > default
|
||||||
let description = "Complete this survey";
|
const descriptionFromMetadata = metadata?.description
|
||||||
if (metadata.description?.[langCode]) {
|
? getLocalizedValue(metadata.description, langCode) || ""
|
||||||
description = metadata.description[langCode];
|
: undefined;
|
||||||
}
|
let description = descriptionFromMetadata || "Please complete this survey.";
|
||||||
|
|
||||||
// Get OG image from link metadata if available
|
// Get OG image from link metadata if available
|
||||||
const { ogImage } = metadata;
|
const { ogImage } = metadata;
|
||||||
|
|
||||||
// Add product name in title if it's Formbricks cloud and not using custom metadata
|
if (!titleFromMetadata) {
|
||||||
if (!metadata.title?.[langCode]) {
|
|
||||||
if (IS_FORMBRICKS_CLOUD) {
|
if (IS_FORMBRICKS_CLOUD) {
|
||||||
title = `${title} | Formbricks`;
|
title = `${title} | Formbricks`;
|
||||||
} else {
|
|
||||||
const project = await getProjectByEnvironmentId(survey.environmentId);
|
|
||||||
if (project) {
|
|
||||||
title = `${title} | ${project.name}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +78,13 @@ export const getBasicSurveyMetadata = async (
|
|||||||
/**
|
/**
|
||||||
* Generate Open Graph metadata for survey
|
* Generate Open Graph metadata for survey
|
||||||
*/
|
*/
|
||||||
export const getSurveyOpenGraphMetadata = (surveyId: string, surveyName: string): Metadata => {
|
export const getSurveyOpenGraphMetadata = (
|
||||||
const brandColor = getBrandColorForURL(COLOR_DEFAULTS.brandColor); // Default color
|
surveyId: string,
|
||||||
|
surveyName: string,
|
||||||
|
surveyBrandColor?: string
|
||||||
|
): Metadata => {
|
||||||
const encodedName = getNameForURL(surveyName);
|
const encodedName = getNameForURL(surveyName);
|
||||||
|
const brandColor = getBrandColorForURL(surveyBrandColor ?? COLOR_DEFAULTS.brandColor);
|
||||||
const ogImgURL = `/api/v1/client/og?brandColor=${brandColor}&name=${encodedName}`;
|
const ogImgURL = `/api/v1/client/og?brandColor=${brandColor}&name=${encodedName}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ vi.mock("./lib/metadata-utils", () => ({
|
|||||||
describe("getMetadataForLinkSurvey", () => {
|
describe("getMetadataForLinkSurvey", () => {
|
||||||
const mockSurveyId = "survey-123";
|
const mockSurveyId = "survey-123";
|
||||||
const mockSurveyName = "Test Survey";
|
const mockSurveyName = "Test Survey";
|
||||||
const mockDescription = "Complete this survey";
|
const mockDescription = "Please complete this survey.";
|
||||||
const mockOgImageUrl = "https://example.com/custom-image.png";
|
const mockOgImageUrl = "https://example.com/custom-image.png";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -60,7 +60,7 @@ describe("getMetadataForLinkSurvey", () => {
|
|||||||
|
|
||||||
expect(getSurveyMetadata).toHaveBeenCalledWith(mockSurveyId);
|
expect(getSurveyMetadata).toHaveBeenCalledWith(mockSurveyId);
|
||||||
expect(getBasicSurveyMetadata).toHaveBeenCalledWith(mockSurveyId, undefined);
|
expect(getBasicSurveyMetadata).toHaveBeenCalledWith(mockSurveyId, undefined);
|
||||||
expect(getSurveyOpenGraphMetadata).toHaveBeenCalledWith(mockSurveyId, mockSurveyName);
|
expect(getSurveyOpenGraphMetadata).toHaveBeenCalledWith(mockSurveyId, mockSurveyName, undefined);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
title: mockSurveyName,
|
title: mockSurveyName,
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ export const getMetadataForLinkSurvey = async (
|
|||||||
|
|
||||||
// Get enhanced metadata that includes custom link metadata
|
// Get enhanced metadata that includes custom link metadata
|
||||||
const { title, description, ogImage } = await getBasicSurveyMetadata(surveyId, languageCode);
|
const { title, description, ogImage } = await getBasicSurveyMetadata(surveyId, languageCode);
|
||||||
|
const surveyBrandColor = survey.styling?.brandColor?.light;
|
||||||
|
|
||||||
// Use the shared function for creating the base metadata but override with custom data
|
// Use the shared function for creating the base metadata but override with custom data
|
||||||
const baseMetadata = getSurveyOpenGraphMetadata(survey.id, title);
|
const baseMetadata = getSurveyOpenGraphMetadata(survey.id, title, surveyBrandColor);
|
||||||
|
|
||||||
// Override with the custom image URL
|
// Override with the custom image URL
|
||||||
if (baseMetadata.openGraph) {
|
if (baseMetadata.openGraph) {
|
||||||
|
|||||||
@@ -765,7 +765,7 @@ export function Survey({
|
|||||||
<LanguageSwitch
|
<LanguageSwitch
|
||||||
surveyLanguages={localSurvey.languages}
|
surveyLanguages={localSurvey.languages}
|
||||||
setSelectedLanguageCode={setselectedLanguage}
|
setSelectedLanguageCode={setselectedLanguage}
|
||||||
hoverColor={styling.inputColor?.light ?? "#000000"}
|
hoverColor={styling.inputColor?.light ?? "#f8fafc"}
|
||||||
borderRadius={styling.roundness ?? 8}
|
borderRadius={styling.roundness ?? 8}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -776,7 +776,7 @@ export function Survey({
|
|||||||
{isCloseButtonVisible && (
|
{isCloseButtonVisible && (
|
||||||
<SurveyCloseButton
|
<SurveyCloseButton
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
hoverColor={styling.inputColor?.light ?? "#000000"}
|
hoverColor={styling.inputColor?.light ?? "#f8fafc"}
|
||||||
borderRadius={styling.roundness ?? 8}
|
borderRadius={styling.roundness ?? 8}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user