Improve social milestone sharing flow
This commit is contained in:
15
src/app/(main)/(marketing)/s/m/[token]/og/route.tsx
Normal file
15
src/app/(main)/(marketing)/s/m/[token]/og/route.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function GET(_request: Request, { params }: { params: { token: string } }) {
|
||||
const share = await db.socialMilestone.findFirst({
|
||||
where: { shareToken: params.token, publicShareApprovedAt: { not: null } },
|
||||
select: { cardData: true, language: true },
|
||||
});
|
||||
if (!share) return new Response('Not found', { status: 404, headers: { 'Cache-Control': 'no-store' } });
|
||||
const card = share.cardData as { qrTitle?: string; totalUniqueScans?: number; trend?: { periodDays: number; recentTotal: number } | null } | null;
|
||||
const german = share.language === 'de';
|
||||
return new ImageResponse(<div style={{ height: '100%', width: '100%', display: 'flex', background: '#f8fafc', padding: 46, color: '#061b31' }}><div style={{ display: 'flex', flexDirection: 'column', width: '100%', border: '2px solid #e2e8f0', borderRadius: 24, background: 'white', padding: 44 }}><div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 23, fontWeight: 700 }}><span>QR MASTER</span><span style={{ color: '#059669', background: '#ecfdf5', padding: '8px 14px', borderRadius: 8 }}>Verified milestone</span></div><div style={{ display: 'flex', flexDirection: 'column', marginTop: 58 }}><span style={{ fontSize: 20, color: '#94a3b8', fontWeight: 700 }}>TOTAL UNIQUE SCANS</span><span style={{ fontSize: 116, fontWeight: 700, letterSpacing: -5 }}>{(card?.totalUniqueScans || 0).toLocaleString(german ? 'de-DE' : 'en-US')}</span><span style={{ fontSize: 29, color: '#64748b' }}>{german ? 'eindeutige Scans' : 'unique scans'}</span></div><div style={{ display: 'flex', marginTop: 'auto', paddingTop: 28, borderTop: '2px solid #e2e8f0', justifyContent: 'space-between', fontSize: 25 }}><span>{card?.qrTitle || 'QR code'}</span><span style={{ color: '#0256ff' }}>{card?.trend ? `${card.trend.recentTotal} in ${card.trend.periodDays} days` : (german ? 'Erste Dynamik' : 'Early momentum')}</span></div></div></div>, { width: 1200, height: 630, headers: { 'Cache-Control': 'no-store' } });
|
||||
}
|
||||
38
src/app/(main)/(marketing)/s/m/[token]/page.tsx
Normal file
38
src/app/(main)/(marketing)/s/m/[token]/page.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { db } from '@/lib/db';
|
||||
import { getWwwOrigin } from '@/lib/hosts';
|
||||
|
||||
type Props = { params: { token: string } };
|
||||
|
||||
async function getShare(token: string) {
|
||||
return db.socialMilestone.findFirst({
|
||||
where: { shareToken: token, publicShareApprovedAt: { not: null } },
|
||||
select: { cardData: true, language: true },
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const share = await getShare(params.token);
|
||||
if (!share) return { robots: { index: false, follow: false } };
|
||||
const card = share.cardData as { qrTitle?: string; totalUniqueScans?: number } | null;
|
||||
const count = card?.totalUniqueScans || 0;
|
||||
const title = share.language === 'de'
|
||||
? `${count.toLocaleString('de-DE')} eindeutige QR-Scans erreicht`
|
||||
: `${count.toLocaleString('en-US')} unique QR scans reached`;
|
||||
const url = `${getWwwOrigin()}/s/m/${params.token}`;
|
||||
return {
|
||||
title,
|
||||
description: card?.qrTitle || 'A verified QR Master scan milestone.',
|
||||
robots: { index: false, follow: false },
|
||||
openGraph: { type: 'website', title, description: card?.qrTitle, url, images: [`${url}/og`] },
|
||||
twitter: { card: 'summary_large_image', title, description: card?.qrTitle, images: [`${url}/og`] },
|
||||
};
|
||||
}
|
||||
|
||||
export default async function SocialMilestoneSharePage({ params }: Props) {
|
||||
const share = await getShare(params.token);
|
||||
if (!share) notFound();
|
||||
const card = share.cardData as { qrTitle?: string; totalUniqueScans?: number } | null;
|
||||
return <main className="min-h-screen bg-slate-50 px-6 py-20 text-center text-[#061b31]"><div className="mx-auto max-w-xl rounded-xl border border-slate-200 bg-white p-10 shadow-[0_30px_45px_-30px_rgba(50,50,93,0.35)]"><p className="text-xs font-semibold tracking-[0.14em] text-[#0256ff]">QR MASTER · VERIFIED MILESTONE</p><h1 className="mt-5 text-5xl font-semibold tracking-[-0.05em] tabular-nums">{(card?.totalUniqueScans || 0).toLocaleString(share.language === 'de' ? 'de-DE' : 'en-US')}</h1><p className="mt-2 text-slate-500">{share.language === 'de' ? 'eindeutige Scans' : 'unique scans'}</p><p className="mt-8 text-lg font-medium">{card?.qrTitle}</p></div></main>;
|
||||
}
|
||||
Reference in New Issue
Block a user