chore: Run Biome checks and apply fixes

This commit is contained in:
Bjorn Lammers 2025-04-25 23:13:43 +02:00
parent 83a678f5ae
commit 860321a6aa
7 changed files with 333 additions and 334 deletions

View File

@ -31,9 +31,7 @@ export default function ErrorPage({
<AlertTriangle className="w-8 h-8" /> <AlertTriangle className="w-8 h-8" />
</div> </div>
<h1 className="text-2xl font-bold">Something went wrong</h1> <h1 className="text-2xl font-bold">Something went wrong</h1>
<p className="text-muted-foreground"> <p className="text-muted-foreground">Unable to load this page. We're looking into the issue.</p>
Unable to load this page. We're looking into the issue.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-4"> <div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
<Button variant="outline" onClick={() => reset()} className="cursor-pointer"> <Button variant="outline" onClick={() => reset()} className="cursor-pointer">
<RefreshCcw className="mr-2 h-4 w-4" /> <RefreshCcw className="mr-2 h-4 w-4" />

View File

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

View File

@ -16,9 +16,7 @@ export default function NotFound({
<AlertTriangle className="w-8 h-8" /> <AlertTriangle className="w-8 h-8" />
</div> </div>
<h1 className="text-2xl sm:text-3xl font-bold mt-6">Not found</h1> <h1 className="text-2xl sm:text-3xl font-bold mt-6">Not found</h1>
<p className="text-muted-foreground mt-3 max-w-md"> <p className="text-muted-foreground mt-3 max-w-md">This icon does not exist or could not be loaded.</p>
This icon does not exist or could not be loaded.
</p>
</div> </div>
<div className="flex flex-col sm:flex-row gap-4 justify-center"> <div className="flex flex-col sm:flex-row gap-4 justify-center">
@ -33,9 +31,7 @@ export default function NotFound({
<div className="border-t border-border pt-8 mt-8"> <div className="border-t border-border pt-8 mt-8">
<div className="text-center mb-6"> <div className="text-center mb-6">
<h2 className="text-xl font-semibold">Missing an icon?</h2> <h2 className="text-xl font-semibold">Missing an icon?</h2>
<p className="text-muted-foreground mt-2"> <p className="text-muted-foreground mt-2">Submit a new icon or suggest improvements to our collection.</p>
Submit a new icon or suggest improvements to our collection.
</p>
</div> </div>
<div className="mt-6"> <div className="mt-6">

View File

@ -216,19 +216,19 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
transition={{ transition={{
duration: 0.5, duration: 0.5,
delay: 0.3, delay: 0.3,
ease: "easeOut" ease: "easeOut",
}} }}
> >
<motion.div <motion.div
animate={{ animate={{
y: [0, -3, 0], y: [0, -3, 0],
rotate: [0, 5, 0] rotate: [0, 5, 0],
}} }}
transition={{ transition={{
duration: 3, duration: 3,
repeat: Infinity, repeat: Number.POSITIVE_INFINITY,
repeatType: "reverse", repeatType: "reverse",
ease: "easeInOut" ease: "easeInOut",
}} }}
> >
<Sparkles className="text-rose-500 h-8 w-8 sm:h-12 sm:w-12 md:h-16 md:w-12" /> <Sparkles className="text-rose-500 h-8 w-8 sm:h-12 sm:w-12 md:h-16 md:w-12" />
@ -242,19 +242,19 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
transition={{ transition={{
duration: 0.5, duration: 0.5,
delay: 0.3, delay: 0.3,
ease: "easeOut" ease: "easeOut",
}} }}
> >
<motion.div <motion.div
animate={{ animate={{
y: [0, -3, 0], y: [0, -3, 0],
rotate: [0, -5, 0] rotate: [0, -5, 0],
}} }}
transition={{ transition={{
duration: 4, duration: 4,
repeat: Infinity, repeat: Number.POSITIVE_INFINITY,
repeatType: "reverse", repeatType: "reverse",
ease: "easeInOut" ease: "easeInOut",
}} }}
> >
<Sparkles className="text-rose-500 h-5 w-5 sm:h-8 sm:w-8 md:h-12 md:w-12" /> <Sparkles className="text-rose-500 h-5 w-5 sm:h-8 sm:w-8 md:h-12 md:w-12" />

View File

@ -9,7 +9,7 @@ import { BASE_URL, REPO_PATH } from "@/constants"
import type { AuthorData, Icon, IconFile } from "@/types/icons" import type { AuthorData, Icon, IconFile } from "@/types/icons"
import confetti from "canvas-confetti" import confetti from "canvas-confetti"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { Check, Copy, Download, FileType, Github, Moon, PaletteIcon, Sun, ArrowRight } from "lucide-react" import { ArrowRight, Check, Copy, Download, FileType, Github, Moon, PaletteIcon, Sun } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
import { useCallback, useState } from "react" import { useCallback, useState } from "react"
@ -238,12 +238,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
variant="outline"
size="icon"
className="h-8 w-8 rounded-lg"
asChild
>
<Link <Link
href={githubUrl} href={githubUrl}
target="_blank" target="_blank"
@ -363,14 +358,16 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
<h3 className="text-sm font-semibold text-muted-foreground mb-2">About this icon</h3> <h3 className="text-sm font-semibold text-muted-foreground mb-2">About this icon</h3>
<div className="text-xs text-muted-foreground space-y-2"> <div className="text-xs text-muted-foreground space-y-2">
<p> <p>
Available in {availableFormats.length > 1 Available in{" "}
{availableFormats.length > 1
? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")}) ` ? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")}) `
: `${availableFormats[0].toUpperCase()} format `} : `${availableFormats[0].toUpperCase()} format `}
with a base format of {iconData.base.toUpperCase()}. with a base format of {iconData.base.toUpperCase()}.
{iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."} {iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."}
</p> </p>
<p> <p>
Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {icon.replace(/-/g, " ")} logo. Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {icon.replace(/-/g, " ")}{" "}
logo.
</p> </p>
</div> </div>
</div> </div>
@ -476,57 +473,63 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
</Card> </Card>
</div> </div>
</div> </div>
{iconData.categories && iconData.categories.length > 0 && (() => { {iconData.categories &&
const MAX_RELATED_ICONS = 16 iconData.categories.length > 0 &&
const currentCategories = iconData.categories || [] (() => {
const MAX_RELATED_ICONS = 16
const currentCategories = iconData.categories || []
const relatedIconsWithScore = Object.entries(allIcons) const relatedIconsWithScore = Object.entries(allIcons)
.map(([name, data]) => { .map(([name, data]) => {
if (name === icon) return null // Exclude the current icon if (name === icon) return null // Exclude the current icon
const otherCategories = data.categories || [] const otherCategories = data.categories || []
const commonCategories = currentCategories.filter((cat) => otherCategories.includes(cat)) const commonCategories = currentCategories.filter((cat) => otherCategories.includes(cat))
const score = commonCategories.length const score = commonCategories.length
return score > 0 ? { name, data, score } : null return score > 0 ? { name, data, score } : null
}) })
.filter((item): item is { name: string; data: Icon; score: number } => item !== null) // Type guard .filter((item): item is { name: string; data: Icon; score: number } => item !== null) // Type guard
.sort((a, b) => b.score - a.score) // Sort by score DESC .sort((a, b) => b.score - a.score) // Sort by score DESC
const topRelatedIcons = relatedIconsWithScore.slice(0, MAX_RELATED_ICONS) const topRelatedIcons = relatedIconsWithScore.slice(0, MAX_RELATED_ICONS)
const viewMoreUrl = `/icons?${currentCategories.map((cat) => `category=${encodeURIComponent(cat)}`).join("&")}` const viewMoreUrl = `/icons?${currentCategories.map((cat) => `category=${encodeURIComponent(cat)}`).join("&")}`
if (topRelatedIcons.length === 0) return null if (topRelatedIcons.length === 0) return null
return ( return (
<section className="container mx-auto mt-12" aria-labelledby="related-icons-title"> <section className="container mx-auto mt-12" aria-labelledby="related-icons-title">
<Card className="bg-background/50 border shadow-lg"> <Card className="bg-background/50 border shadow-lg">
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
<h2 id="related-icons-title">Related Icons</h2> <h2 id="related-icons-title">Related Icons</h2>
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
Other icons from {currentCategories.map((cat) => cat.replace(/-/g, " ")).join(", ")} categories Other icons from {currentCategories.map((cat) => cat.replace(/-/g, " ")).join(", ")} categories
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<IconsGrid filteredIcons={topRelatedIcons} matchedAliases={{}} /> <IconsGrid filteredIcons={topRelatedIcons} matchedAliases={{}} />
{relatedIconsWithScore.length > MAX_RELATED_ICONS && ( {relatedIconsWithScore.length > MAX_RELATED_ICONS && (
<div className="mt-6 text-center"> <div className="mt-6 text-center">
<Button asChild variant="link" className="text-muted-foreground hover:text-primary transition-colors duration-200 hover:no-underline"> <Button
<Link href={viewMoreUrl} className="no-underline"> asChild
View all related icons variant="link"
<ArrowRight className="ml-2 h-4 w-4" /> className="text-muted-foreground hover:text-primary transition-colors duration-200 hover:no-underline"
</Link> >
</Button> <Link href={viewMoreUrl} className="no-underline">
</div> View all related icons
)} <ArrowRight className="ml-2 h-4 w-4" />
</CardContent> </Link>
</Card> </Button>
</section> </div>
) )}
})()} </CardContent>
</Card>
</section>
)
})()}
</div> </div>
) )
} }

View File

@ -25,7 +25,7 @@ export function VirtualizedIconsGrid({ filteredIcons, matchedAliases }: IconsGri
useEffect(() => { useEffect(() => {
setWindowWidth(window.innerWidth) setWindowWidth(window.innerWidth)
const handleResize = () => { const handleResize = () => {
setWindowWidth(window.innerWidth) setWindowWidth(window.innerWidth)
} }

View File

@ -335,10 +335,12 @@ export function IconSearch({ icons }: IconSearchProps) {
Relevance Relevance
</DropdownMenuRadioItem> </DropdownMenuRadioItem>
<DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer"> <DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer">
<ArrowDownAZ className="h-4 w-4 mr-2" />Name (A-Z) <ArrowDownAZ className="h-4 w-4 mr-2" />
Name (A-Z)
</DropdownMenuRadioItem> </DropdownMenuRadioItem>
<DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer"> <DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer">
<ArrowUpZA className="h-4 w-4 mr-2" />Name (Z-A) <ArrowUpZA className="h-4 w-4 mr-2" />
Name (Z-A)
</DropdownMenuRadioItem> </DropdownMenuRadioItem>
<DropdownMenuRadioItem value="newest" className="cursor-pointer"> <DropdownMenuRadioItem value="newest" className="cursor-pointer">
<Calendar className="h-4 w-4 mr-2" /> <Calendar className="h-4 w-4 mr-2" />