Add dynamic footer badge marquee with admin management.
Store badges in Postgres, expose public/admin APIs, render an infinite footer slider, and ship /admin for paste-embed CRUD.
This commit is contained in:
+31
-16
@@ -1,6 +1,8 @@
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import { FooterBadgeMarquee } from "@/components/FooterBadgeMarquee";
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { prisma } from "@/lib/db";
|
||||
import {
|
||||
DOCKER_HUB_URL,
|
||||
DOCS_URL,
|
||||
@@ -92,13 +94,38 @@ const STATUS_URL = "https://status.atakanozban.com/status/2";
|
||||
const DOCS_INTRO_URL = `${DOCS_URL.replace(/\/$/, "")}/docs/intro`;
|
||||
const DOCS_API_URL = `${DOCS_URL.replace(/\/$/, "")}/docs/api/overview`;
|
||||
|
||||
export function Footer() {
|
||||
export async function Footer() {
|
||||
const year = new Date().getFullYear();
|
||||
let initialBadges: {
|
||||
id: string;
|
||||
name: string;
|
||||
embedHtml: string | null;
|
||||
imageUrl: string | null;
|
||||
linkUrl: string | null;
|
||||
sortOrder: number;
|
||||
}[] = [];
|
||||
|
||||
try {
|
||||
initialBadges = await prisma.footerBadge.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
embedHtml: true,
|
||||
imageUrl: true,
|
||||
linkUrl: true,
|
||||
sortOrder: true,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
// Table may not exist yet during migrate; marquee client will retry via API.
|
||||
}
|
||||
|
||||
return (
|
||||
<footer className="mt-20 w-full border-t border-gray-800 bg-black/40 pb-8 pt-16 text-sm text-gray-400">
|
||||
<div className="mx-auto max-w-6xl px-6">
|
||||
<div className="grid grid-cols-1 gap-8 pb-12 md:grid-cols-12">
|
||||
<div className="grid grid-cols-1 gap-8 pb-8 md:grid-cols-12">
|
||||
<div className="flex flex-col gap-4 md:col-span-5">
|
||||
<Logo size="sm" beta />
|
||||
<p className="max-w-sm text-gray-500">
|
||||
@@ -125,20 +152,6 @@ export function Footer() {
|
||||
<DockerIcon className="h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
href="https://www.producthunt.com/products/songs2vid?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-songs2vid"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-1 inline-block"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
alt="Songs2VID - Bulk render audio & artworks into videos in seconds | Product Hunt"
|
||||
width={250}
|
||||
height={54}
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1206681&theme=dark&t=1785422871345"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-8 sm:grid-cols-3 md:col-span-7">
|
||||
@@ -185,6 +198,8 @@ export function Footer() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FooterBadgeMarquee initialBadges={initialBadges} />
|
||||
|
||||
<div className="flex flex-col items-center gap-3 border-t border-gray-900 pt-8 text-xs text-gray-500 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 sm:justify-start">
|
||||
<span>© {year} Songs2VID. All rights reserved.</span>
|
||||
|
||||
Reference in New Issue
Block a user