Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/cms/config/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
"targetField": "name",
"required": true,
"regex": "^[A-Za-z0-9-_.~@]*$"
},
"labels": {
"type": "component",
"component": "shared.labels",
"repeatable": true
}
}
}
20 changes: 0 additions & 20 deletions apps/cms/src/components/shared/labels.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
},
"pluginOptions": {
"content-manager": {
"visible": false
"visible": true
},
"content-type-builder": {
"visible": false
"visible": true
}
},
"attributes": {
Expand Down
3 changes: 0 additions & 3 deletions apps/cms/types/generated/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<false>;
official: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
paid: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
price: Schema.Attribute.String;
};
}

Expand Down
1 change: 1 addition & 0 deletions apps/cms/types/generated/contentTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Binary file added apps/web/public/fonts/poppins-400.ttf
Binary file not shown.
Binary file added apps/web/public/fonts/poppins-700.ttf
Binary file not shown.
Binary file added apps/web/public/fonts/poppins-900.ttf
Binary file not shown.
268 changes: 268 additions & 0 deletions apps/web/src/app/api/og/route.tsx
Original file line number Diff line number Diff line change
@@ -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(
<div
style={{
background: "#030712",
width: "100%",
height: "100%",
display: "flex",
position: "relative",
fontFamily: "Poppins",
overflow: "hidden",
}}
>
{/* Top accent bar */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 4,
background:
"linear-gradient(90deg, #4945FF 0%, #7b79ff 55%, transparent 100%)",
display: "flex",
}}
/>

{/* Right glow */}
<div
style={{
position: "absolute",
right: -60,
top: 0,
width: 620,
height: 630,
background:
"radial-gradient(ellipse at 55% 50%, rgba(73,69,255,0.22) 0%, rgba(73,69,255,0.07) 45%, transparent 70%)",
display: "flex",
}}
/>

{/* Left: content */}
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: "56px 0px 48px 80px",
flex: 1,
position: "relative",
}}
>
{/* Eyebrow */}
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<svg
width="40"
height="40"
viewBox="0 0 600 600"
fill="none"
aria-hidden="true"
>
<path
d="M0 208C0 109.948 0 60.9218 30.4609 30.4609C60.9218 0 109.948 0 208 0H392C490.052 0 539.078 0 569.539 30.4609C600 60.9218 600 109.948 600 208V392C600 490.052 600 539.078 569.539 569.539C539.078 600 490.052 600 392 600H208C109.948 600 60.9218 600 30.4609 569.539C0 539.078 0 490.052 0 392V208Z"
fill="#4945FF"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M414 182H212V285H315V388H418V186C418 183.791 416.209 182 414 182Z"
fill="white"
/>
<rect x="311" y="285" width="4" height="4" fill="white" />
<path
d="M212 285H311C313.209 285 315 286.791 315 289V388H216C213.791 388 212 386.209 212 384V285Z"
fill="#9593FF"
/>
<path
d="M315 388H418L318.414 487.586C317.154 488.846 315 487.953 315 486.172V388Z"
fill="#9593FF"
/>
<path
d="M212 285H113.828C112.046 285 111.154 282.846 112.414 281.586L212 182V285Z"
fill="#9593FF"
/>
</svg>
<span style={{ color: "#ffffff", fontSize: 28, fontWeight: 700 }}>
Strapi Community
</span>
</div>

{/* Badge + name + description */}
<div style={{ display: "flex", flexDirection: "column" }}>
<div style={{ display: "flex", marginBottom: 20 }}>
<div
style={{
display: "flex",
background: "#4945FF",
color: "#ffffff",
fontSize: 25,
fontWeight: 900,
letterSpacing: "0.1em",
padding: "6px 16px",
borderRadius: 100,
}}
>
{type.toUpperCase()}
</div>
</div>
<div
style={{
fontSize: name.length > 30 ? 52 : 64,
fontWeight: 700,
color: "#ffffff",
lineHeight: 1.1,
letterSpacing: "-0.02em",
marginBottom: 20,
}}
>
{name}
</div>
{description && (
<div
style={{
fontSize: 32,
color: "#ffffff",
lineHeight: 1.5,
fontWeight: 400,
}}
>
{description.length > 70
? `${description.slice(0, 70)}…`
: description}
</div>
)}
</div>

{/* Domain */}
<div
style={{
display: "flex",
color: "#ffffff",
fontSize: 28,
fontWeight: 500,
}}
>
community.strapi.io
</div>
</div>

{/* Right: icon */}
<div
style={{
paddingRight: 120,
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: 440,
flexShrink: 0,
}}
>
{icon ? (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#ffffff",
padding: 10,
overflow: "hidden",
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={icon}
width={180}
height={180}
alt=""
style={{ objectFit: "contain" }}
/>
</div>
) : (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 220,
height: 220,
background: "rgba(73,69,255,0.12)",
borderRadius: 32,
border: "1px solid rgba(123,121,255,0.2)",
}}
>
<svg
width="100"
height="100"
viewBox="0 0 600 600"
fill="none"
aria-hidden="true"
>
<path
d="M0 208C0 109.948 0 60.9218 30.4609 30.4609C60.9218 0 109.948 0 208 0H392C490.052 0 539.078 0 569.539 30.4609C600 60.9218 600 109.948 600 208V392C600 490.052 600 539.078 569.539 569.539C539.078 600 490.052 600 392 600H208C109.948 600 60.9218 600 30.4609 569.539C0 539.078 0 490.052 0 392V208Z"
fill="#4945FF"
fillOpacity="0.3"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M414 182H212V285H315V388H418V186C418 183.791 416.209 182 414 182Z"
fill="white"
fillOpacity="0.5"
/>
<rect
x="311"
y="285"
width="4"
height="4"
fill="white"
fillOpacity="0.5"
/>
<path
d="M212 285H311C313.209 285 315 286.791 315 289V388H216C213.791 388 212 386.209 212 384V285Z"
fill="#9593FF"
fillOpacity="0.4"
/>
<path
d="M315 388H418L318.414 487.586C317.154 488.846 315 487.953 315 486.172V388Z"
fill="#9593FF"
fillOpacity="0.4"
/>
<path
d="M212 285H113.828C112.046 285 111.154 282.846 112.414 281.586L212 182V285Z"
fill="#9593FF"
fillOpacity="0.4"
/>
</svg>
</div>
)}
</div>
</div>,
{
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" },
],
},
);
}
21 changes: 21 additions & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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"],
});
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/features/cms/lib/og-image-url.ts
Original file line number Diff line number Diff line change
@@ -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();
}
Loading
Loading