diff --git a/apps/cms/config/plugins.ts b/apps/cms/config/plugins.ts index ab3c202..5dd829b 100644 --- a/apps/cms/config/plugins.ts +++ b/apps/cms/config/plugins.ts @@ -162,10 +162,10 @@ export default ({ env }) => ({ integration: { indexName: env("MEILISEARCH_INTEGRATIONS_INDEX_NAME"), entriesQuery: { - populate: ["logo", "categories", "url_alias"], + populate: ["logo", "labels", "categories", "url_alias"], }, settings: { - sortableAttributes: ["createdAt"], + sortableAttributes: ["labels.featured"], filterableAttributes: ["categories"], }, }, diff --git a/apps/cms/src/api/integration/content-types/integration/schema.json b/apps/cms/src/api/integration/content-types/integration/schema.json index 03b77f1..8b749c8 100644 --- a/apps/cms/src/api/integration/content-types/integration/schema.json +++ b/apps/cms/src/api/integration/content-types/integration/schema.json @@ -71,6 +71,11 @@ "targetField": "name", "required": true, "regex": "^[A-Za-z0-9-_.~@]*$" + }, + "labels": { + "type": "component", + "component": "shared.labels", + "repeatable": true } } } diff --git a/apps/cms/src/components/shared/labels.json b/apps/cms/src/components/shared/labels.json index cda6f15..209af08 100644 --- a/apps/cms/src/components/shared/labels.json +++ b/apps/cms/src/components/shared/labels.json @@ -13,26 +13,6 @@ "featured": { "type": "boolean", "default": false - }, - "paid": { - "type": "boolean", - "default": false - }, - "buy_link": { - "type": "string", - "conditions": { - "visible": { - "==": [{ "var": "paid" }, true] - } - } - }, - "price": { - "type": "string", - "conditions": { - "visible": { - "==": [{ "var": "paid" }, true] - } - } } }, "config": {} diff --git a/apps/cms/src/extensions/better-auth/content-types/jwks/schema.json b/apps/cms/src/extensions/better-auth/content-types/jwks/schema.json index 3f439be..14bbf10 100644 --- a/apps/cms/src/extensions/better-auth/content-types/jwks/schema.json +++ b/apps/cms/src/extensions/better-auth/content-types/jwks/schema.json @@ -11,10 +11,10 @@ }, "pluginOptions": { "content-manager": { - "visible": false + "visible": true }, "content-type-builder": { - "visible": false + "visible": true } }, "attributes": { diff --git a/apps/cms/types/generated/components.d.ts b/apps/cms/types/generated/components.d.ts index 9237624..4c915a2 100644 --- a/apps/cms/types/generated/components.d.ts +++ b/apps/cms/types/generated/components.d.ts @@ -133,11 +133,8 @@ export interface SharedLabels extends Struct.ComponentSchema { icon: 'check'; }; attributes: { - buy_link: Schema.Attribute.String; featured: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo; official: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo; - paid: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo; - price: Schema.Attribute.String; }; } diff --git a/apps/cms/types/generated/contentTypes.d.ts b/apps/cms/types/generated/contentTypes.d.ts index 2059bc0..6f20242 100644 --- a/apps/cms/types/generated/contentTypes.d.ts +++ b/apps/cms/types/generated/contentTypes.d.ts @@ -718,6 +718,7 @@ export interface ApiIntegrationIntegration extends Struct.CollectionTypeSchema { Schema.Attribute.Private; description: Schema.Attribute.Text & Schema.Attribute.Required; image: Schema.Attribute.Media<'images'>; + labels: Schema.Attribute.Component<'shared.labels', true>; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< 'oneToMany', diff --git a/apps/web/public/fonts/poppins-400.ttf b/apps/web/public/fonts/poppins-400.ttf new file mode 100644 index 0000000..e48144e Binary files /dev/null and b/apps/web/public/fonts/poppins-400.ttf differ diff --git a/apps/web/public/fonts/poppins-700.ttf b/apps/web/public/fonts/poppins-700.ttf new file mode 100644 index 0000000..89b46e7 Binary files /dev/null and b/apps/web/public/fonts/poppins-700.ttf differ diff --git a/apps/web/public/fonts/poppins-900.ttf b/apps/web/public/fonts/poppins-900.ttf new file mode 100644 index 0000000..67bccc8 Binary files /dev/null and b/apps/web/public/fonts/poppins-900.ttf differ diff --git a/apps/web/src/app/api/og/route.tsx b/apps/web/src/app/api/og/route.tsx new file mode 100644 index 0000000..e98a4ce --- /dev/null +++ b/apps/web/src/app/api/og/route.tsx @@ -0,0 +1,268 @@ +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { ImageResponse } from "next/og"; +import type { NextRequest } from "next/server"; + +export async function GET(request: NextRequest) { + const { searchParams } = request.nextUrl; + const name = searchParams.get("name") ?? ""; + const description = searchParams.get("description") ?? ""; + const icon = searchParams.get("icon"); + const type = searchParams.get("type") ?? "Plugin"; + + const [font400, font700, font900] = await Promise.all([ + readFile(join(process.cwd(), "public/fonts/poppins-400.ttf")), + readFile(join(process.cwd(), "public/fonts/poppins-700.ttf")), + readFile(join(process.cwd(), "public/fonts/poppins-900.ttf")), + ]); + + return new ImageResponse( +
+ {/* Top accent bar */} +
+ + {/* Right glow */} +
+ + {/* Left: content */} +
+ {/* Eyebrow */} +
+ + + Strapi Community + +
+ + {/* Badge + name + description */} +
+
+
+ {type.toUpperCase()} +
+
+
30 ? 52 : 64, + fontWeight: 700, + color: "#ffffff", + lineHeight: 1.1, + letterSpacing: "-0.02em", + marginBottom: 20, + }} + > + {name} +
+ {description && ( +
+ {description.length > 70 + ? `${description.slice(0, 70)}…` + : description} +
+ )} +
+ + {/* Domain */} +
+ community.strapi.io +
+
+ + {/* Right: icon */} +
+ {icon ? ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + +
+ ) : ( +
+ +
+ )} +
+
, + { + width: 1200, + height: 630, + fonts: [ + { name: "Poppins", data: font400, weight: 400, style: "normal" }, + { name: "Poppins", data: font700, weight: 700, style: "normal" }, + { name: "Poppins", data: font900, weight: 900, style: "normal" }, + ], + }, + ); +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 3171fab..5ba7902 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,10 +1,31 @@ import "./globals.css"; import { StrapiFooter, StrapiNavbar } from "@repo/strapi-ui"; +import type { Metadata } from "next"; import { Poppins } from "next/font/google"; import { Toaster } from "sonner"; import { AuthProviders } from "@/features/auth/components/providers"; import { isAuthEnabled } from "@/features/auth/lib/is-enabled"; +export const metadata: Metadata = { + metadataBase: new URL( + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io", + ), + title: { + template: "%s | Strapi Community", + default: "Strapi Community", + }, + description: + "Discover plugins, templates, and integrations for Strapi — built by the community.", + openGraph: { + siteName: "Strapi Community", + type: "website", + locale: "en_US", + }, + twitter: { + card: "summary_large_image", + }, +}; + const poppins = Poppins({ weight: ["400", "500", "600", "700"], }); diff --git a/apps/web/src/features/cms/lib/og-image-url.ts b/apps/web/src/features/cms/lib/og-image-url.ts new file mode 100644 index 0000000..cf6de66 --- /dev/null +++ b/apps/web/src/features/cms/lib/og-image-url.ts @@ -0,0 +1,12 @@ +export function buildOgImageUrl( + base: string, + params: { name: string; type: string; icon?: string; description?: string }, +): string { + const url = new URL("/api/og", base); + url.searchParams.set("name", params.name); + url.searchParams.set("type", params.type); + if (params.icon) url.searchParams.set("icon", params.icon); + if (params.description) + url.searchParams.set("description", params.description.slice(0, 120)); + return url.toString(); +} diff --git a/apps/web/src/features/cms/pages/help-page/metadata.ts b/apps/web/src/features/cms/pages/help-page/metadata.ts index dddbf88..932288e 100644 --- a/apps/web/src/features/cms/pages/help-page/metadata.ts +++ b/apps/web/src/features/cms/pages/help-page/metadata.ts @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const helpPageMetadata = async ( @@ -8,10 +9,45 @@ export const helpPageMetadata = async ( .collection("api::help-page.help-page") .findOne(documentId, { fields: ["title", "description"], + populate: { url_alias: true }, }); + const { title, description } = document.data; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; + + const desc = description ?? undefined; + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: title ?? "", + type: "Help", + description: desc, + }); + return { - title: document.data.title, - description: document.data.description ?? undefined, + title, + description: desc, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title: title ?? undefined, + description: desc, + type: "website", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: title ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: title ?? undefined, + description: desc, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/home/metadata.ts b/apps/web/src/features/cms/pages/home/metadata.ts index bab5992..e364034 100644 --- a/apps/web/src/features/cms/pages/home/metadata.ts +++ b/apps/web/src/features/cms/pages/home/metadata.ts @@ -1,10 +1,11 @@ import type { Metadata } from "next"; import type { OpenGraphType } from "next/dist/lib/metadata/types/opengraph-types"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const homeMetadata = async (): Promise => { const document = await cmsClient.single("api::home.home").find({ - fields: ["documentId"], + fields: ["title"], populate: { seo: { populate: { @@ -14,37 +15,49 @@ export const homeMetadata = async (): Promise => { }, }); - const { seo: metadata } = document.data; + const { title, seo: metadata } = document.data; - if (!metadata) { - return {}; - } + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageTitle = metadata?.metaTitle ?? title; + const pageDescription = metadata?.metaDescription ?? undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: pageTitle ?? "", + type: "Community", + description: pageDescription, + }); return { - metadataBase: process.env.NEXT_PUBLIC_CMS_URL, - title: metadata.metaTitle, - description: metadata.metaDescription, - keywords: metadata.keywords, - viewport: metadata.metaViewport, - robots: metadata.metaRobots, + title: pageTitle, + description: pageDescription, + keywords: metadata?.keywords, + viewport: metadata?.metaViewport, + robots: metadata?.metaRobots, alternates: { - canonical: metadata.canonicalURL, + canonical: metadata?.canonicalURL, }, - openGraph: metadata.openGraph + openGraph: metadata?.openGraph ? { - title: metadata.openGraph.ogTitle || "", - description: metadata.openGraph.ogDescription || "", + title: metadata.openGraph.ogTitle || pageTitle || "", + description: + metadata.openGraph.ogDescription || pageDescription || "", url: metadata.openGraph.ogUrl || "", type: (metadata.openGraph.ogType as OpenGraphType) || "website", - images: metadata.openGraph.ogImage - ? [ - { - url: metadata.openGraph.ogImage.url, - alt: metadata.openGraph.ogImage.alternativeText, - }, - ] - : undefined, + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: pageTitle ?? "" }, + ], } - : {}, + : { + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: pageTitle ?? "" }, + ], + }, + twitter: { + card: "summary_large_image", + title: metadata?.openGraph?.ogTitle || pageTitle || undefined, + description: + metadata?.openGraph?.ogDescription || pageDescription || undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/home/page.tsx b/apps/web/src/features/cms/pages/home/page.tsx index 65c6a67..f205957 100644 --- a/apps/web/src/features/cms/pages/home/page.tsx +++ b/apps/web/src/features/cms/pages/home/page.tsx @@ -33,6 +33,13 @@ export type HomePageData = Modules.Documents.Result< const packagesQuery = { populate: ["icon", "owner", "url_alias"], + filters: { + labels: { + featured: { + $eq: true, + }, + }, + }, // @ts-expect-error - issue in the strapi-client pagination: { limit: 3, @@ -45,6 +52,13 @@ export type HomePackages = Modules.Documents.Result< const templatesQuery = { populate: ["preview_image", "owner", "url_alias"], + filters: { + labels: { + featured: { + $eq: true, + }, + }, + }, // @ts-expect-error - issue in the strapi-client pagination: { limit: 3, diff --git a/apps/web/src/features/cms/pages/integrations/metadata.ts b/apps/web/src/features/cms/pages/integrations/metadata.ts index 0e6eb69..da7059d 100644 --- a/apps/web/src/features/cms/pages/integrations/metadata.ts +++ b/apps/web/src/features/cms/pages/integrations/metadata.ts @@ -1,5 +1,7 @@ import type { Metadata } from "next"; import type { OpenGraphType } from "next/dist/lib/metadata/types/opengraph-types"; +import { cmsImageUrl } from "@/features/cms/lib/image-url"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const integrationMetadata = async ( @@ -8,47 +10,74 @@ export const integrationMetadata = async ( const document = await cmsClient .collection("api::integration.integration") .findOne(documentId, { - fields: ["documentId"], + fields: ["name", "description"], populate: { seo: { populate: { openGraph: true, }, }, + logo: true, + url_alias: true, }, }); - const { seo: metadata } = document.data; + const { name, description, seo: metadata } = document.data; + const logo = (document.data as Record).logo as + | { url?: string } + | null + | undefined; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; - if (!metadata) { - return {}; - } + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + const pageTitle = metadata?.metaTitle ?? name; + const pageDescription = metadata?.metaDescription ?? description ?? undefined; + const logoUrl = logo?.url ? cmsImageUrl(logo.url) : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: pageTitle ?? "", + type: "Integration", + icon: logoUrl, + description: pageDescription, + }); return { - metadataBase: process.env.NEXT_PUBLIC_CMS_URL, - title: metadata.metaTitle, - description: metadata.metaDescription, - keywords: metadata.keywords, - viewport: metadata.metaViewport, - robots: metadata.metaRobots, + title: pageTitle, + description: pageDescription, + keywords: metadata?.keywords, + viewport: metadata?.metaViewport, + robots: metadata?.metaRobots, alternates: { - canonical: metadata.canonicalURL, + canonical: pageUrl ?? metadata?.canonicalURL, }, - openGraph: metadata.openGraph + openGraph: metadata?.openGraph ? { - title: metadata.openGraph.ogTitle || "", - description: metadata.openGraph.ogDescription || "", - url: metadata.openGraph.ogUrl || "", + title: metadata.openGraph.ogTitle || pageTitle || "", + description: + metadata.openGraph.ogDescription || pageDescription || "", + url: pageUrl ?? (metadata.openGraph.ogUrl || ""), type: (metadata.openGraph.ogType as OpenGraphType) || "website", - images: metadata.openGraph.ogImage - ? [ - { - url: metadata.openGraph.ogImage.url, - alt: metadata.openGraph.ogImage.alternativeText, - }, - ] - : undefined, + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: pageTitle ?? "" }, + ], } - : {}, + : { + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: pageTitle ?? "" }, + ], + }, + twitter: { + card: "summary_large_image", + title: metadata?.openGraph?.ogTitle || pageTitle || undefined, + description: + metadata?.openGraph?.ogDescription || pageDescription || undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/organization/metadata.ts b/apps/web/src/features/cms/pages/organization/metadata.ts index 037afff..bc738db 100644 --- a/apps/web/src/features/cms/pages/organization/metadata.ts +++ b/apps/web/src/features/cms/pages/organization/metadata.ts @@ -1,4 +1,6 @@ import type { Metadata } from "next"; +import { cmsImageUrl } from "@/features/cms/lib/image-url"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const organizationMetadata = async ( @@ -6,10 +8,60 @@ export const organizationMetadata = async ( ): Promise => { const document = await cmsClient .collection("plugin::better-auth.organization") - .findOne(documentId); + .findOne(documentId, { + populate: { url_alias: true }, + }); + + const { name, metadata: bio } = document.data as { + name?: string | null; + metadata?: string | null; + logo?: string | null; + } & Record; + const logo = (document.data as Record).logo as + | string + | null + | undefined; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; + + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + + const logoUrl = logo + ? logo.startsWith("http") + ? logo + : cmsImageUrl(logo) + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: name ?? "", + type: "Organization", + icon: logoUrl, + description: bio ?? undefined, + }); return { - title: document.data?.name, - description: document.data?.metadata, + title: name, + description: bio, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title: name ?? undefined, + description: bio ?? undefined, + type: "website", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: name ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: name ?? undefined, + description: bio ?? undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/overview-page/metadata.ts b/apps/web/src/features/cms/pages/overview-page/metadata.ts index c03be7c..2de0262 100644 --- a/apps/web/src/features/cms/pages/overview-page/metadata.ts +++ b/apps/web/src/features/cms/pages/overview-page/metadata.ts @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import type { OpenGraphType } from "next/dist/lib/metadata/types/opengraph-types"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const overviewPageMetadata = async ( @@ -8,7 +9,7 @@ export const overviewPageMetadata = async ( const document = await cmsClient .collection("api::overview-page.overview-page") .findOne(documentId, { - fields: ["documentId"], + fields: ["title", "description"], populate: { seo: { populate: { @@ -18,37 +19,49 @@ export const overviewPageMetadata = async ( }, }); - const { seo: metadata } = document.data; + const { title, description, seo: metadata } = document.data; - if (!metadata) { - return {}; - } + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageTitle = metadata?.metaTitle ?? title; + const pageDescription = metadata?.metaDescription ?? description ?? undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: pageTitle ?? "", + type: "Strapi", + description: pageDescription, + }); return { - metadataBase: process.env.NEXT_PUBLIC_CMS_URL, - title: metadata.metaTitle, - description: metadata.metaDescription, - keywords: metadata.keywords, - viewport: metadata.metaViewport, - robots: metadata.metaRobots, + title: pageTitle, + description: pageDescription, + keywords: metadata?.keywords, + viewport: metadata?.metaViewport, + robots: metadata?.metaRobots, alternates: { - canonical: metadata.canonicalURL, + canonical: metadata?.canonicalURL, }, - openGraph: metadata.openGraph + openGraph: metadata?.openGraph ? { - title: metadata.openGraph.ogTitle || "", - description: metadata.openGraph.ogDescription || "", + title: metadata.openGraph.ogTitle || pageTitle || "", + description: + metadata.openGraph.ogDescription || pageDescription || "", url: metadata.openGraph.ogUrl || "", type: (metadata.openGraph.ogType as OpenGraphType) || "website", - images: metadata.openGraph.ogImage - ? [ - { - url: metadata.openGraph.ogImage.url, - alt: metadata.openGraph.ogImage.alternativeText, - }, - ] - : undefined, + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: pageTitle ?? "" }, + ], } - : {}, + : { + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: pageTitle ?? "" }, + ], + }, + twitter: { + card: "summary_large_image", + title: metadata?.openGraph?.ogTitle || pageTitle || undefined, + description: + metadata?.openGraph?.ogDescription || pageDescription || undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/package-category/metadata.ts b/apps/web/src/features/cms/pages/package-category/metadata.ts index 8955d45..1c24d81 100644 --- a/apps/web/src/features/cms/pages/package-category/metadata.ts +++ b/apps/web/src/features/cms/pages/package-category/metadata.ts @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const packageCategoryMetadata = async ( @@ -8,10 +9,44 @@ export const packageCategoryMetadata = async ( .collection("api::package-category.package-category") .findOne(documentId, { fields: ["name", "description"], + populate: { url_alias: true }, }); + const { name, description } = document.data; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; + + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: name ?? "", + type: "Plugins", + description: description ?? undefined, + }); + return { - title: document.data.name, - description: document.data.description, + title: name, + description, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title: name ?? undefined, + description: description ?? undefined, + type: "website", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: name ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: name ?? undefined, + description: description ?? undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/package/metadata.ts b/apps/web/src/features/cms/pages/package/metadata.ts index 056f6d0..e5c19e7 100644 --- a/apps/web/src/features/cms/pages/package/metadata.ts +++ b/apps/web/src/features/cms/pages/package/metadata.ts @@ -1,5 +1,8 @@ import type { Metadata } from "next"; +import { cmsImageUrl } from "@/features/cms/lib/image-url"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; +import type { Owner } from "@/utils/types"; export const packageMetadata = async ( documentId: string, @@ -8,10 +11,48 @@ export const packageMetadata = async ( .collection("api::package.package") .findOne(documentId, { fields: ["name", "description"], + populate: { + icon: true, + url_alias: true, + owner: true, + }, }); + const { name, description, icon, url_alias } = document.data; + const owner = document.data.owner as Owner; + + const iconUrl = icon?.url ? cmsImageUrl(icon.url) : undefined; + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = url_alias?.[0]?.url_path + ? `${webUrl}${url_alias[0].url_path}` + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: name ?? "", + type: "Package", + icon: iconUrl, + description: description ?? undefined, + }); + + console.log("owner", owner); + return { - title: document.data.name, - description: document.data.description, + title: name, + description, + authors: owner?.name ? [{ name: owner.name }] : undefined, + alternates: { canonical: pageUrl }, + openGraph: { + title: name ?? undefined, + description: description ?? undefined, + type: "website", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: name ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: name ?? undefined, + description: description ?? undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/template-category/metadata.ts b/apps/web/src/features/cms/pages/template-category/metadata.ts index c00beeb..5547027 100644 --- a/apps/web/src/features/cms/pages/template-category/metadata.ts +++ b/apps/web/src/features/cms/pages/template-category/metadata.ts @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const templateCategoryMetadata = async ( @@ -8,10 +9,44 @@ export const templateCategoryMetadata = async ( .collection("api::template-category.template-category") .findOne(documentId, { fields: ["name", "description"], + populate: { url_alias: true }, }); + const { name, description } = document.data; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; + + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: name ?? "", + type: "Templates", + description: description ?? undefined, + }); + return { - title: document.data.name, - description: document.data.description, + title: name, + description, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title: name ?? undefined, + description: description ?? undefined, + type: "website", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: name ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: name ?? undefined, + description: description ?? undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/template/metadata.ts b/apps/web/src/features/cms/pages/template/metadata.ts index 24a9ba3..dc337c3 100644 --- a/apps/web/src/features/cms/pages/template/metadata.ts +++ b/apps/web/src/features/cms/pages/template/metadata.ts @@ -1,4 +1,6 @@ import type { Metadata } from "next"; +import { cmsImageUrl } from "@/features/cms/lib/image-url"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const templateMetadata = async ( @@ -8,10 +10,57 @@ export const templateMetadata = async ( .collection("api::template.template") .findOne(documentId, { fields: ["name", "description"], + populate: { + preview_image: true, + url_alias: true, + owner: { fields: ["name"] }, + }, }); + const { name, description } = document.data; + const owner = document.data.owner as { name?: string | null } | null; + const previewImage = (document.data as Record) + .preview_image as { url?: string } | null | undefined; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; + + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + + const previewUrl = previewImage?.url + ? cmsImageUrl(previewImage.url) + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: name ?? "", + type: "Template", + icon: previewUrl, + description: description ?? undefined, + }); + return { - title: document.data.name, - description: document.data.description, + title: name, + description, + authors: owner?.name ? [{ name: owner.name }] : undefined, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title: name ?? undefined, + description: description ?? undefined, + type: "website", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: name ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: name ?? undefined, + description: description ?? undefined, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/cms/pages/user/metadata.ts b/apps/web/src/features/cms/pages/user/metadata.ts index d4982cb..ec74dfe 100644 --- a/apps/web/src/features/cms/pages/user/metadata.ts +++ b/apps/web/src/features/cms/pages/user/metadata.ts @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { buildOgImageUrl } from "@/features/cms/lib/og-image-url"; import { cmsClient } from "@/features/cms/lib/strapi"; export const userMetadata = async (id: string): Promise => { @@ -7,11 +8,51 @@ export const userMetadata = async (id: string): Promise => { .findOne(id, { populate: { profile: true, + url_alias: true, }, }); + const { name, image, profile } = document.data as { + name?: string | null; + image?: string | null; + profile?: { bio?: string | null } | null; + } & Record; + const urlAlias = (document.data as Record).url_alias as + | { url_path?: string }[] + | null + | undefined; + + const description = profile?.bio ?? undefined; + const webUrl = + process.env.NEXT_PUBLIC_WEB_URL ?? "https://community.strapi.io"; + const pageUrl = urlAlias?.[0]?.url_path + ? `${webUrl}${urlAlias[0].url_path}` + : undefined; + const ogImageUrl = buildOgImageUrl(webUrl, { + name: name ?? "", + type: "Member", + icon: image ?? undefined, + description, + }); + return { - title: document.data.name, - description: document.data.profile?.bio, + title: name, + description, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title: name ?? undefined, + description, + type: "profile", + url: pageUrl, + images: [{ url: ogImageUrl, width: 1200, height: 630, alt: name ?? "" }], + }, + twitter: { + card: "summary_large_image", + title: name ?? undefined, + description, + images: [ogImageUrl], + }, }; }; diff --git a/apps/web/src/features/search/indexes/integrations/integrations.tsx b/apps/web/src/features/search/indexes/integrations/integrations.tsx index 937a78a..038f33c 100644 --- a/apps/web/src/features/search/indexes/integrations/integrations.tsx +++ b/apps/web/src/features/search/indexes/integrations/integrations.tsx @@ -7,7 +7,7 @@ import { Hit } from "./components"; const idx = process.env.NEXT_PUBLIC_MEILISEARCH_INTEGRATIONS_INDEX_NAME!; const IntegrationsSearch = () => ( - + @@ -21,14 +21,6 @@ const IntegrationsSearch = () => ( - diff --git a/apps/web/src/features/search/indexes/recipes/recipes.tsx b/apps/web/src/features/search/indexes/recipes/recipes.tsx index e548e5c..72d0ed4 100644 --- a/apps/web/src/features/search/indexes/recipes/recipes.tsx +++ b/apps/web/src/features/search/indexes/recipes/recipes.tsx @@ -13,11 +13,6 @@ const RecipesSearch = () => ( - diff --git a/apps/web/src/features/search/indexes/showcases/showcases.tsx b/apps/web/src/features/search/indexes/showcases/showcases.tsx index e737b9a..7b599cb 100644 --- a/apps/web/src/features/search/indexes/showcases/showcases.tsx +++ b/apps/web/src/features/search/indexes/showcases/showcases.tsx @@ -18,14 +18,6 @@ const ShowcasesSearch = () => ( -