feat(web/icons): Add dynamic OpenGraph image to /icons page (#1271)

* feat(web): add og image for /icons page

* refactor(web): change description to fit project

Signed-off-by: Bjorn Lammers <bjorn@lammers.media>

* feat: Add daily release workflow for CF

* refactor(web): Update descriptions and consistency (#1264)

* refactor(web): Update descriptions and consistency

* revert: Issue templates

* refactor(web): More extensive search placeholder

---------

Signed-off-by: Bjorn Lammers <bjorn@lammers.media>
Co-authored-by: Thomas Camlong <thomas@ajnart.fr>

* feat(icons): add greenlight (#1275)

Co-authored-by: Dashboard Icons Manager <193821040+dashboard-icons-manager[bot]@users.noreply.github.com>

* chore: Move SEO audit to /web

Signed-off-by: Bjorn Lammers <bjorn@lammers.media>

* feat(web/icons): implement fixed representative icons for OpenGraph image

- Added a predefined list of representative icons to be used in the OpenGraph image.
- Updated the logic to display the number of icons based on the rounded total instead of a fixed selection.

---------

Signed-off-by: Bjorn Lammers <bjorn@lammers.media>
Co-authored-by: Bjorn Lammers <bjorn@lammers.media>
Co-authored-by: dashboard-icons-manager[bot] <193821040+dashboard-icons-manager[bot]@users.noreply.github.com>
This commit is contained in:
Thomas Camlong 2025-04-25 22:49:29 +02:00 committed by GitHub
parent 36c0a3ebd5
commit 1d44dcd6fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -0,0 +1,283 @@
import { getAllIcons } from "@/lib/api"
import { ImageResponse } from "next/og"
export const dynamic = "force-static"
export const size = {
width: 1200,
height: 630,
}
// Define a fixed list of representative icons
const representativeIcons = [
"github",
"discord",
"slack",
"docker",
"kubernetes",
"grafana",
"prometheus",
"nextcloud",
"homeassistant",
"cloudflare",
"nginx",
"traefik",
"portainer",
"plex",
"jellyfin",
]
export default async function Image() {
const iconsData = await getAllIcons()
const totalIcons = Object.keys(iconsData).length
// Round down to the nearest 100
const roundedTotalIcons = Math.floor(totalIcons / 100) * 100
const iconImages = representativeIcons.map((icon) => ({
name: icon
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" "),
url: `https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/${icon}.png`,
}))
return new ImageResponse(
<div
style={{
display: "flex",
width: "100%",
height: "100%",
position: "relative",
fontFamily: "Inter, system-ui, sans-serif",
overflow: "hidden",
backgroundColor: "white",
backgroundImage:
"radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)",
backgroundSize: "100px 100px",
}}
>
<div
style={{
position: "absolute",
display: "flex",
top: -100,
left: -100,
width: 400,
height: 400,
borderRadius: "50%",
background: "linear-gradient(135deg, rgba(56, 189, 248, 0.1) 0%, rgba(59, 130, 246, 0.1) 100%)",
filter: "blur(80px)",
zIndex: 2,
}}
/>
<div
style={{
position: "absolute",
display: "flex",
bottom: -150,
right: -150,
width: 500,
height: 500,
borderRadius: "50%",
background: "linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(234, 88, 12, 0.1) 100%)",
filter: "blur(100px)",
zIndex: 2,
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
padding: "50px",
zIndex: 10,
gap: "30px",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "16px",
marginBottom: "10px",
}}
>
<div
style={{
fontSize: 64,
display: "flex",
fontWeight: 800,
fontFamily: "monospace",
color: "#0f172a",
lineHeight: 1.1,
textAlign: "center",
}}
>
Dashboard Icons
</div>
<div
style={{
fontSize: 28,
display: "flex",
fontWeight: 500,
color: "#64748b",
lineHeight: 1.4,
textAlign: "center",
maxWidth: 1100,
}}
>
A curated collection of {roundedTotalIcons}+ free icons for dashboards and app directories
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
gap: "20px",
width: "1100px",
margin: "0 auto",
}}
>
{iconImages.map((icon, index) => (
<div
key={index}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
background: "white",
borderRadius: 16,
boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05)",
padding: "20px",
position: "relative",
overflow: "hidden",
width: "120px",
height: "75px",
margin: "0",
}}
>
<div
style={{
display: "flex",
position: "absolute",
inset: 0,
background: "linear-gradient(145deg, #ffffff 0%, #f8fafc 100%)",
zIndex: 0,
}}
/>
<img
src={icon.url}
alt={icon.name}
width={50}
height={50}
style={{
objectFit: "contain",
position: "relative",
zIndex: 1,
filter: "drop-shadow(0 5px 10px rgba(0, 0, 0, 0.1))",
}}
/>
</div>
))}
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
background: "white",
borderRadius: 16,
boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05)",
padding: "20px",
position: "relative",
overflow: "hidden",
width: "120px",
height: "75px",
margin: "0",
}}
>
<div
style={{
display: "flex",
position: "absolute",
inset: 0,
background: "linear-gradient(145deg, #ffffff 0%, #f8fafc 100%)",
zIndex: 0,
}}
/>
<div
style={{
display: "flex",
fontSize: 20,
fontWeight: 600,
color: "#64748b",
zIndex: 1,
}}
>
+{totalIcons - representativeIcons.length}
</div>
</div>
</div>
<div
style={{
display: "flex",
gap: 16,
marginTop: 10,
}}
/>
</div>
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 80,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#ffffff",
borderTop: "2px solid rgba(0, 0, 0, 0.05)",
zIndex: 20,
}}
>
<div
style={{
display: "flex",
fontSize: 24,
fontWeight: 600,
color: "#334155",
alignItems: "center",
gap: 10,
}}
>
<div
style={{
display: "flex",
width: 8,
height: 8,
borderRadius: "50%",
backgroundColor: "#3b82f6",
marginRight: 4,
}}
/>
dashboardicons.com
</div>
</div>
</div>,
{
...size,
},
)
}