mirror of
https://github.com/walkxcode/dashboard-icons.git
synced 2025-11-19 01:57:29 +01:00
feat(community): add individual community icon pages
- Add dynamic route for community icons at /community/[icon] - Add opengraph image generation for community icons - Update community page and community utilities - Enable viewing and sharing individual community-submitted icons
This commit is contained in:
366
web/src/app/community/[icon]/opengraph-image.tsx
Normal file
366
web/src/app/community/[icon]/opengraph-image.tsx
Normal file
@@ -0,0 +1,366 @@
|
||||
import { permanentRedirect, redirect } from "next/navigation"
|
||||
import { ImageResponse } from "next/og"
|
||||
import { getCommunityGalleryRecord, getCommunitySubmissionByName, getCommunitySubmissions } from "@/lib/community"
|
||||
|
||||
export const revalidate = 21600 // 6 hours
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const icons = await getCommunitySubmissions()
|
||||
const validIcons = icons.filter((icon) => icon.name)
|
||||
if (process.env.CI_MODE === "false") {
|
||||
return validIcons.slice(0, 5).map((icon) => ({
|
||||
icon: icon.name,
|
||||
}))
|
||||
}
|
||||
return validIcons.map((icon) => ({
|
||||
icon: icon.name,
|
||||
}))
|
||||
}
|
||||
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export default async function Image({ params }: { params: Promise<{ icon: string }> }) {
|
||||
const { icon } = await params
|
||||
|
||||
if (!icon) {
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
fontSize: 48,
|
||||
fontWeight: 600,
|
||||
color: "#64748b",
|
||||
}}
|
||||
>
|
||||
Icon not found
|
||||
</div>,
|
||||
{ ...size },
|
||||
)
|
||||
}
|
||||
|
||||
const iconData = await getCommunitySubmissionByName(icon)
|
||||
|
||||
if (!iconData) {
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
fontSize: 48,
|
||||
fontWeight: 600,
|
||||
color: "#64748b",
|
||||
}}
|
||||
>
|
||||
Icon not found
|
||||
</div>,
|
||||
{ ...size },
|
||||
)
|
||||
}
|
||||
|
||||
const record = await getCommunityGalleryRecord(icon)
|
||||
if (record?.status === "added_to_collection") {
|
||||
permanentRedirect(`/icons/${icon}/opengraph-image`)
|
||||
}
|
||||
|
||||
const status = record?.status || "pending"
|
||||
const allIcons = await getCommunitySubmissions()
|
||||
const totalIcons = allIcons.length
|
||||
const index = allIcons.findIndex((i) => i.name === icon)
|
||||
|
||||
const formattedIconName = icon
|
||||
.split("-")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ")
|
||||
|
||||
const getStatusDisplayName = (status: string) => {
|
||||
switch (status) {
|
||||
case "pending":
|
||||
return "Awaiting Review"
|
||||
case "approved":
|
||||
return "Approved"
|
||||
case "rejected":
|
||||
return "Rejected"
|
||||
case "added_to_collection":
|
||||
return "Added to Collection"
|
||||
default:
|
||||
return "Awaiting Review"
|
||||
}
|
||||
}
|
||||
|
||||
const statusColors: Record<string, { bg: string; text: string; border: string }> = {
|
||||
approved: {
|
||||
bg: "#dbeafe",
|
||||
text: "#1e40af",
|
||||
border: "#93c5fd",
|
||||
},
|
||||
pending: {
|
||||
bg: "#fef3c7",
|
||||
text: "#92400e",
|
||||
border: "#fde68a",
|
||||
},
|
||||
rejected: {
|
||||
bg: "#fee2e2",
|
||||
text: "#991b1b",
|
||||
border: "#fca5a5",
|
||||
},
|
||||
}
|
||||
|
||||
const statusConfig = statusColors[status] || statusColors.pending
|
||||
const statusLabel = getStatusDisplayName(status)
|
||||
|
||||
const mainIconUrl = typeof iconData.data.base === "string" && iconData.data.base.startsWith("http") ? iconData.data.base : null
|
||||
|
||||
let iconDataBuffer: Buffer | null = null
|
||||
if (mainIconUrl) {
|
||||
try {
|
||||
const response = await fetch(mainIconUrl)
|
||||
if (response.ok) {
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
iconDataBuffer = Buffer.from(arrayBuffer)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch icon image for ${icon}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
const iconUrl = iconDataBuffer
|
||||
? `data:image/png;base64,${iconDataBuffer.toString("base64")}`
|
||||
: `https://placehold.co/600x400?text=${formattedIconName}`
|
||||
|
||||
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",
|
||||
}}
|
||||
>
|
||||
{/* Status Badge */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 30,
|
||||
right: 30,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: statusConfig.bg,
|
||||
color: statusConfig.text,
|
||||
border: `2px solid ${statusConfig.border}`,
|
||||
borderRadius: 12,
|
||||
padding: "10px 20px",
|
||||
fontSize: 20,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "0.5px",
|
||||
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
||||
zIndex: 30,
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
{statusLabel}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
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",
|
||||
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: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: "60px",
|
||||
gap: "70px",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 320,
|
||||
height: 320,
|
||||
borderRadius: 32,
|
||||
background: "white",
|
||||
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)",
|
||||
padding: 30,
|
||||
flexShrink: 0,
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
background: "linear-gradient(145deg, #ffffff 0%, #f8fafc 100%)",
|
||||
zIndex: 0,
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
src={iconUrl}
|
||||
alt={formattedIconName}
|
||||
width={260}
|
||||
height={260}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
filter: "drop-shadow(0 10px 15px rgba(0, 0, 0, 0.1))",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
gap: 28,
|
||||
maxWidth: 650,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
fontSize: 64,
|
||||
fontWeight: 800,
|
||||
color: "#0f172a",
|
||||
lineHeight: 1.1,
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
Download {formattedIconName} icon (Community)
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
fontSize: 32,
|
||||
fontWeight: 500,
|
||||
color: "#64748b",
|
||||
lineHeight: 1.4,
|
||||
position: "relative",
|
||||
paddingLeft: 16,
|
||||
borderLeft: "4px solid #94a3b8",
|
||||
}}
|
||||
>
|
||||
Amongst {totalIcons} other community-submitted icons
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 12,
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#fef3c7",
|
||||
color: "#92400e",
|
||||
border: "2px solid #fde68a",
|
||||
borderRadius: 12,
|
||||
padding: "8px 16px",
|
||||
fontSize: 18,
|
||||
fontWeight: 600,
|
||||
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)",
|
||||
}}
|
||||
>
|
||||
COMMUNITY
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "#3b82f6",
|
||||
marginRight: 4,
|
||||
}}
|
||||
/>
|
||||
dashboardicons.com
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
...size,
|
||||
},
|
||||
)
|
||||
}
|
||||
208
web/src/app/community/[icon]/page.tsx
Normal file
208
web/src/app/community/[icon]/page.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
import type { Metadata, ResolvingMetadata } from "next"
|
||||
import { notFound, permanentRedirect, redirect } from "next/navigation"
|
||||
import { IconDetails } from "@/components/icon-details"
|
||||
import { BASE_URL, WEB_URL } from "@/constants"
|
||||
import { getAllIcons } from "@/lib/api"
|
||||
import { getCommunityGalleryRecord, getCommunitySubmissionByName, getCommunitySubmissions } from "@/lib/community"
|
||||
|
||||
export const dynamicParams = false
|
||||
export const revalidate = 21600 // 6 hours
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const icons = await getCommunitySubmissions()
|
||||
return icons
|
||||
.filter((icon) => icon.name)
|
||||
.map((icon) => ({
|
||||
icon: icon.name,
|
||||
}))
|
||||
}
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ icon: string }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props, _parent: ResolvingMetadata): Promise<Metadata> {
|
||||
const { icon } = await params
|
||||
const iconData = await getCommunitySubmissionByName(icon)
|
||||
|
||||
if (!iconData) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const record = await getCommunityGalleryRecord(icon)
|
||||
if (record?.status === "added_to_collection") {
|
||||
permanentRedirect(`/icons/${icon}`)
|
||||
}
|
||||
|
||||
const allIcons = await getCommunitySubmissions()
|
||||
const totalIcons = allIcons.length
|
||||
const updateDate = new Date(iconData.data.update.timestamp)
|
||||
const authorName = iconData.data.update.author.name || "Community"
|
||||
|
||||
console.debug(`Generated metadata for community icon ${icon} by ${authorName} updated at ${updateDate.toLocaleString()}`)
|
||||
|
||||
const pageUrl = `${WEB_URL}/community/${icon}`
|
||||
const formattedIconName = icon
|
||||
.split("-")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ")
|
||||
|
||||
const mainIconUrl =
|
||||
typeof iconData.data.base === "string" && iconData.data.base.startsWith("http") ? iconData.data.base : `${BASE_URL}/svg/${icon}.svg`
|
||||
|
||||
return {
|
||||
title: `${formattedIconName} Icon (Community) | Dashboard Icons`,
|
||||
description: `Download the ${formattedIconName} community-submitted icon. Part of a collection of ${totalIcons} community icons awaiting review and addition to the Dashboard Icons collection.`,
|
||||
assets: [mainIconUrl],
|
||||
keywords: [
|
||||
`${formattedIconName} icon`,
|
||||
`${formattedIconName} icon download`,
|
||||
`${formattedIconName} icon community`,
|
||||
`${icon} icon`,
|
||||
"community icon",
|
||||
"user submitted icon",
|
||||
"dashboard icon",
|
||||
],
|
||||
icons: {
|
||||
icon: mainIconUrl,
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
nocache: false,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
noimageindex: false,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
},
|
||||
},
|
||||
abstract: `Download the ${formattedIconName} community-submitted icon. Part of a collection of ${totalIcons} community icons awaiting review and addition to the Dashboard Icons collection.`,
|
||||
openGraph: {
|
||||
title: `${formattedIconName} Icon (Community) | Dashboard Icons`,
|
||||
description: `Download the ${formattedIconName} community-submitted icon. Part of a collection of ${totalIcons} community icons awaiting review and addition to the Dashboard Icons collection.`,
|
||||
type: "website",
|
||||
url: pageUrl,
|
||||
siteName: "Dashboard Icons",
|
||||
images: [
|
||||
{
|
||||
url: mainIconUrl,
|
||||
width: 512,
|
||||
height: 512,
|
||||
alt: `${formattedIconName} icon`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: `${formattedIconName} Icon (Community) | Dashboard Icons`,
|
||||
description: `Download the ${formattedIconName} community-submitted icon. Part of a collection of ${totalIcons} community icons awaiting review and addition to the Dashboard Icons collection.`,
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${WEB_URL}/community/${icon}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default async function CommunityIconPage({ params }: { params: Promise<{ icon: string }> }) {
|
||||
const { icon } = await params
|
||||
const iconData = await getCommunitySubmissionByName(icon)
|
||||
|
||||
if (!iconData) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const record = await getCommunityGalleryRecord(icon)
|
||||
if (record?.status === "added_to_collection") {
|
||||
permanentRedirect(`/icons/${icon}`)
|
||||
}
|
||||
|
||||
const allIcons = await getAllIcons()
|
||||
|
||||
const authorData = {
|
||||
id: 0,
|
||||
name: iconData.data.update.author.name || "Community",
|
||||
login: iconData.data.update.author.name || "community",
|
||||
avatar_url: "",
|
||||
html_url: "",
|
||||
}
|
||||
|
||||
const mainIconUrl =
|
||||
typeof iconData.data.base === "string" && iconData.data.base.startsWith("http")
|
||||
? iconData.data.base
|
||||
: (iconData.data as any).mainIconUrl || `${BASE_URL}/svg/${icon}.svg`
|
||||
|
||||
const iconDataForDisplay = {
|
||||
...iconData.data,
|
||||
base: (iconData.data as any).baseFormat || "svg",
|
||||
mainIconUrl: mainIconUrl,
|
||||
assetUrls: (iconData.data as any).assetUrls || [mainIconUrl],
|
||||
}
|
||||
|
||||
const status = record?.status || "pending"
|
||||
|
||||
const getStatusDisplayName = (status: string) => {
|
||||
switch (status) {
|
||||
case "pending":
|
||||
return "Awaiting Review"
|
||||
case "approved":
|
||||
return "Approved"
|
||||
case "rejected":
|
||||
return "Rejected"
|
||||
case "added_to_collection":
|
||||
return "Added to Collection"
|
||||
default:
|
||||
return "Awaiting Review"
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "approved":
|
||||
return "bg-blue-500/10 text-blue-400 font-bold border-blue-500/20"
|
||||
case "rejected":
|
||||
return "bg-red-500/10 text-red-500 border-red-500/20"
|
||||
case "pending":
|
||||
return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
|
||||
case "added_to_collection":
|
||||
return "bg-green-500/10 text-green-500 border-green-500/20"
|
||||
default:
|
||||
return "bg-gray-500/10 text-gray-500 border-gray-500/20"
|
||||
}
|
||||
}
|
||||
|
||||
const statusDisplayName = getStatusDisplayName(status)
|
||||
const statusColor = getStatusColor(status)
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ImageObject",
|
||||
contentUrl: mainIconUrl,
|
||||
license: "https://creativecommons.org/licenses/by/4.0/",
|
||||
acquireLicensePage: `${WEB_URL}/license`,
|
||||
creator: {
|
||||
"@type": "Person",
|
||||
name: authorData.name || authorData.login,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<IconDetails
|
||||
icon={icon}
|
||||
iconData={iconDataForDisplay as any}
|
||||
authorData={authorData}
|
||||
allIcons={allIcons}
|
||||
status={status}
|
||||
statusDisplayName={statusDisplayName}
|
||||
statusColor={statusColor}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -38,8 +38,6 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
}
|
||||
|
||||
export const revalidate = 600
|
||||
|
||||
export default async function CommunityPage() {
|
||||
const icons = await getCommunitySubmissions()
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { IconWithName } from "@/types/icons"
|
||||
|
||||
/**
|
||||
* Server-side utility functions for community gallery (public submissions view)
|
||||
* Uses unstable_cache with tags for on-demand revalidation
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -17,17 +18,25 @@ function createServerPB() {
|
||||
|
||||
/**
|
||||
* Transform a CommunityGallery item to IconWithName format for use with IconSearch
|
||||
* For community icons, base is the full HTTP URL to the main icon asset
|
||||
* Additional assets are stored but not exposed in the standard Icon format
|
||||
*/
|
||||
function transformGalleryToIcon(item: CommunityGallery): any {
|
||||
const pbUrl = process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090"
|
||||
|
||||
const fileUrl = item.assets?.[0] ? `${pbUrl}/api/files/community_gallery/${item.id}/${item.assets[0]}` : ""
|
||||
const mainIcon = item.assets?.[0] ? `${pbUrl}/api/files/community_gallery/${item.id}/${item.assets[0]}` : ""
|
||||
|
||||
const mainAssetExt = item.assets?.[0]?.split(".").pop()?.toLowerCase() || "svg"
|
||||
const baseFormat = mainAssetExt === "svg" ? "svg" : mainAssetExt === "png" ? "png" : "webp"
|
||||
|
||||
const transformed = {
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
data: {
|
||||
base: fileUrl || "svg",
|
||||
base: mainIcon || "svg",
|
||||
baseFormat,
|
||||
mainIconUrl: mainIcon,
|
||||
assetUrls: item.assets?.map((asset) => `${pbUrl}/api/files/community_gallery/${item.id}/${asset}`) || [],
|
||||
aliases: item.extras?.aliases || [],
|
||||
categories: item.extras?.categories || [],
|
||||
update: {
|
||||
@@ -49,9 +58,8 @@ function transformGalleryToIcon(item: CommunityGallery): any {
|
||||
* Fetch community gallery items (not added to collection)
|
||||
* Uses the community_gallery view collection for public-facing data
|
||||
* This is the raw fetch function without caching
|
||||
* Filters out items without assets
|
||||
*/
|
||||
export async function fetchCommunitySubmissions(): Promise<IconWithName[]> {
|
||||
async function fetchCommunitySubmissions(): Promise<IconWithName[]> {
|
||||
try {
|
||||
const pb = createServerPB()
|
||||
|
||||
@@ -70,9 +78,67 @@ export async function fetchCommunitySubmissions(): Promise<IconWithName[]> {
|
||||
/**
|
||||
* Cached version of fetchCommunitySubmissions
|
||||
* Uses unstable_cache with tags for on-demand revalidation
|
||||
* Revalidates every 600 seconds (10 minutes)
|
||||
* Revalidates every 21600 seconds (6 hours) to match page revalidate time
|
||||
* Can be invalidated on-demand using revalidateTag("community-gallery")
|
||||
*/
|
||||
export const getCommunitySubmissions = unstable_cache(fetchCommunitySubmissions, ["community-gallery"], {
|
||||
revalidate: 600,
|
||||
export const getCommunitySubmissions = unstable_cache(fetchCommunitySubmissions, ["community-submissions-list"], {
|
||||
revalidate: 21600,
|
||||
tags: ["community-gallery"],
|
||||
})
|
||||
|
||||
/**
|
||||
* Fetch a single community submission by name (raw function)
|
||||
* Returns null if not found
|
||||
*/
|
||||
async function fetchCommunitySubmissionByName(name: string): Promise<IconWithName | null> {
|
||||
try {
|
||||
const pb = createServerPB()
|
||||
|
||||
const record = await pb.collection("community_gallery").getFirstListItem<CommunityGallery>(`name="${name}"`)
|
||||
return transformGalleryToIcon(record)
|
||||
} catch (error) {
|
||||
console.error(`Error fetching community submission ${name}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached version of fetchCommunitySubmissionByName
|
||||
* Uses unstable_cache with tags for on-demand revalidation
|
||||
* Revalidates every 21600 seconds (6 hours)
|
||||
* Cache key: community-submission-{name}
|
||||
*/
|
||||
export function getCommunitySubmissionByName(name: string): Promise<IconWithName | null> {
|
||||
return unstable_cache(async () => fetchCommunitySubmissionByName(name), [`community-submission-${name}`], {
|
||||
revalidate: 21600,
|
||||
tags: ["community-gallery", "community-submission"],
|
||||
})()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch raw CommunityGallery record by name (raw function, for status checks)
|
||||
*/
|
||||
async function fetchCommunityGalleryRecord(name: string): Promise<CommunityGallery | null> {
|
||||
try {
|
||||
const pb = createServerPB()
|
||||
|
||||
const record = await pb.collection("community_gallery").getFirstListItem<CommunityGallery>(`name="${name}"`)
|
||||
return record
|
||||
} catch (error) {
|
||||
console.error(`Error fetching community gallery record ${name}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached version of fetchCommunityGalleryRecord
|
||||
* Uses unstable_cache with tags for on-demand revalidation
|
||||
* Revalidates every 21600 seconds (6 hours)
|
||||
* Cache key: community-gallery-record-{name}
|
||||
*/
|
||||
export function getCommunityGalleryRecord(name: string): Promise<CommunityGallery | null> {
|
||||
return unstable_cache(async () => fetchCommunityGalleryRecord(name), [`community-gallery-record-${name}`], {
|
||||
revalidate: 21600,
|
||||
tags: ["community-gallery", "community-gallery-record"],
|
||||
})()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user