diff --git a/apps/web/components/seo/head-seo.tsx b/apps/web/components/seo/head-seo.tsx index d0756ddb09..3495d4e731 100644 --- a/apps/web/components/seo/head-seo.tsx +++ b/apps/web/components/seo/head-seo.tsx @@ -4,6 +4,7 @@ import { NextSeo, NextSeoProps } from "next-seo"; import { AppImageProps, constructAppImage, + constructGenericImage, constructMeetingImage, MeetingImageProps, } from "@calcom/lib/OgImages"; @@ -72,12 +73,11 @@ const buildSeoMeta = (pageProps: { export const HeadSeo = (props: HeadSeoProps): JSX.Element => { const defaultUrl = getBrowserInfo()?.url; - const image = getSeoImage("default"); const { title, description, siteName, canonical = defaultUrl, nextSeoProps = {}, app, meeting } = props; - const truncatedDescription = truncate(description, 24); - const longerTruncatedDescriptionOnWords = truncateOnWord(description, 148); + const image = getSeoImage("ogImage") + constructGenericImage({ title, description }); + const truncatedDescription = truncateOnWord(description, 158); const pageTitle = title + " | Cal.com"; let seoObject = buildSeoMeta({ @@ -101,7 +101,7 @@ export const HeadSeo = (props: HeadSeoProps): JSX.Element => { if (app) { const pageImage = - getSeoImage("ogImage") + constructAppImage({ ...app, description: longerTruncatedDescriptionOnWords }); + getSeoImage("ogImage") + constructAppImage({ ...app, description: truncatedDescription }); seoObject = buildSeoMeta({ title: pageTitle, description: truncatedDescription, diff --git a/apps/web/pages/api/social/og/image.tsx b/apps/web/pages/api/social/og/image.tsx index 8445f4de40..fd8295d1dd 100644 --- a/apps/web/pages/api/social/og/image.tsx +++ b/apps/web/pages/api/social/og/image.tsx @@ -3,7 +3,7 @@ import { NextApiRequest } from "next"; import type { SatoriOptions } from "satori"; import { z } from "zod"; -import { Meeting, App } from "@calcom/lib/OgImages"; +import { Meeting, App, Generic } from "@calcom/lib/OgImages"; const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) => res.arrayBuffer() @@ -37,6 +37,12 @@ const appSchema = z.object({ slug: z.string(), }); +const genericSchema = z.object({ + imageType: z.literal("generic"), + title: z.string(), + description: z.string(), +}); + export default async function handler(req: NextApiRequest) { const { searchParams } = new URL(`${req.url}`); const imageType = searchParams.get("type"); @@ -85,6 +91,17 @@ export default async function handler(req: NextApiRequest) { }); return new ImageResponse(, ogConfig); } + + case "generic": { + const { title, description } = genericSchema.parse({ + title: searchParams.get("title"), + description: searchParams.get("description"), + imageType, + }); + + return new ImageResponse(, ogConfig); + } + default: return new Response("What you're looking for is not here..", { status: 404 }); } diff --git a/packages/lib/OgImages.tsx b/packages/lib/OgImages.tsx index d2e0ae7cdc..92d433c2ac 100644 --- a/packages/lib/OgImages.tsx +++ b/packages/lib/OgImages.tsx @@ -21,6 +21,11 @@ export interface AppImageProps { slug: string; } +export interface GenericImageProps { + title: string; + description: string; +} + const joinMultipleNames = (names: string[] = []) => { const lastName = names.pop(); return `${names.length > 0 ? `${names.join(", ")} & ${lastName}` : lastName}`; @@ -42,7 +47,7 @@ export const constructMeetingImage = ({ title, users = [], profile }: MeetingIma profile.image && `&meetingImage=${encodeURIComponent(profile.image)}`, `${users.map((user) => `&names=${encodeURIComponent(user.name)}`).join("")}`, `${users.map((user) => `&usernames=${encodeURIComponent(user.username)}`).join("")}`, - // Joinining a multiline string for readability. + // Joining a multiline string for readability. ].join(""); }; @@ -56,7 +61,16 @@ export const constructAppImage = ({ name, slug, description }: AppImageProps): s `&name=${encodeURIComponent(name)}`, `&slug=${encodeURIComponent(slug)}`, `&description=${encodeURIComponent(description)}`, - // Joinining a multiline string for readability. + // Joining a multiline string for readability. + ].join(""); +}; + +export const constructGenericImage = ({ title, description }: GenericImageProps) => { + return [ + `?type=generic`, + `&title=${encodeURIComponent(title)}`, + `&description=${encodeURIComponent(description)}`, + // Joining a multiline string for readability. ].join(""); }; @@ -199,3 +213,22 @@ export const App = ({ name, description, slug }: AppImageProps) => ( ); + +export const Generic = ({ title, description }: GenericImageProps) => ( + +
+
+ Logo +
+ +
+
+ {title} +
+
+ {description} +
+
+
+
+); diff --git a/packages/ui/v2/core/head-seo.tsx b/packages/ui/v2/core/head-seo.tsx index e4e179d0e1..efe1a2e4be 100644 --- a/packages/ui/v2/core/head-seo.tsx +++ b/packages/ui/v2/core/head-seo.tsx @@ -1,10 +1,10 @@ import merge from "lodash/merge"; import { NextSeo, NextSeoProps } from "next-seo"; -import { constructAppImage, constructMeetingImage } from "@calcom/lib/OgImages"; +import { constructAppImage, constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages"; import { getBrowserInfo } from "@calcom/lib/browser/browser.utils"; import { seoConfig, getSeoImage, HeadSeoProps } from "@calcom/lib/next-seo.config"; -import { truncate, truncateOnWord } from "@calcom/lib/text"; +import { truncateOnWord } from "@calcom/lib/text"; /** * Build full seo tags from title, desc, canonical and url @@ -55,12 +55,11 @@ const buildSeoMeta = (pageProps: { export const HeadSeo = (props: HeadSeoProps): JSX.Element => { const defaultUrl = getBrowserInfo()?.url; - const image = getSeoImage("default"); const { title, description, siteName, canonical = defaultUrl, nextSeoProps = {}, app, meeting } = props; - const truncatedDescription = truncate(description, 24); - const longerTruncatedDescriptionOnWords = truncateOnWord(description, 148); + const image = getSeoImage("ogImage") + constructGenericImage({ title, description }); + const truncatedDescription = truncateOnWord(description, 158); const pageTitle = title + " | Cal.com"; let seoObject = buildSeoMeta({ @@ -84,7 +83,7 @@ export const HeadSeo = (props: HeadSeoProps): JSX.Element => { if (app) { const pageImage = - getSeoImage("ogImage") + constructAppImage({ ...app, description: longerTruncatedDescriptionOnWords }); + getSeoImage("ogImage") + constructAppImage({ ...app, description: truncatedDescription }); seoObject = buildSeoMeta({ title: pageTitle, description: truncatedDescription,