1- 'use client'
2-
3- import type React from 'react'
4- import { use } from 'react'
1+ import { db } from '@sim/db'
2+ import { academyCertificate } from '@sim/db/schema'
3+ import { eq } from 'drizzle-orm'
54import { CheckCircle2 , GraduationCap } from 'lucide-react'
6- import { useAcademyCertificate } from '@/hooks/queries/academy'
5+ import type { Metadata } from 'next'
6+ import { notFound } from 'next/navigation'
7+ import type { AcademyCertificate } from '@/lib/academy/types'
8+
9+ interface CertificatePageProps {
10+ params : Promise < { certificateNumber : string } >
11+ }
12+
13+ export async function generateMetadata ( { params } : CertificatePageProps ) : Promise < Metadata > {
14+ const { certificateNumber } = await params
15+ const certificate = await fetchCertificate ( certificateNumber )
16+ if ( ! certificate ) return { title : 'Certificate Not Found' }
17+ return {
18+ title : `${ certificate . metadata ?. courseTitle } — Certificate` ,
19+ description : `Verified certificate of completion awarded to ${ certificate . metadata ?. recipientName } .` ,
20+ }
21+ }
22+
23+ async function fetchCertificate ( certificateNumber : string ) : Promise < AcademyCertificate | null > {
24+ const [ row ] = await db
25+ . select ( )
26+ . from ( academyCertificate )
27+ . where ( eq ( academyCertificate . certificateNumber , certificateNumber ) )
28+ . limit ( 1 )
29+ return ( row as unknown as AcademyCertificate ) ?? null
30+ }
731
832const DATE_FORMAT : Intl . DateTimeFormatOptions = { year : 'numeric' , month : 'long' , day : 'numeric' }
933function formatDate ( date : string | Date ) {
@@ -19,90 +43,75 @@ function MetaRow({ label, children }: { label: string; children: React.ReactNode
1943 )
2044}
2145
22- interface CertificatePageProps {
23- params : Promise < { certificateNumber : string } >
24- }
46+ export default async function CertificatePage ( { params } : CertificatePageProps ) {
47+ const { certificateNumber } = await params
48+ const certificate = await fetchCertificate ( certificateNumber )
2549
26- export default function CertificatePage ( { params } : CertificatePageProps ) {
27- const { certificateNumber } = use ( params )
28- const { data : certificate , isLoading, error } = useAcademyCertificate ( certificateNumber )
50+ if ( ! certificate ) notFound ( )
2951
3052 return (
3153 < main className = 'flex flex-1 items-center justify-center px-6 py-20' >
32- { isLoading ? (
33- < div className = 'h-5 w-5 animate-spin rounded-full border-2 border-[#ECECEC] border-t-transparent' />
34- ) : error || ! certificate ? (
35- < div className = 'text-center' >
36- < p className = 'mb-3 text-[#999] text-[16px]' > Certificate not found.</ p >
37- < p className = 'text-[#555] text-[14px]' >
38- Certificate number{ ' ' }
39- < span className = 'font-[430] text-[#ECECEC]' > { certificateNumber } </ span > is invalid or has
40- been revoked.
41- </ p >
42- </ div >
43- ) : (
44- < div className = 'w-full max-w-2xl' >
45- < div className = 'rounded-[12px] border border-[#3A4A3A] bg-[#1C2A1C] p-10 text-center' >
46- < div className = 'mb-6 flex justify-center' >
47- < div className = 'flex h-16 w-16 items-center justify-center rounded-full border-2 border-[#4CAF50]/40 bg-[#4CAF50]/10' >
48- < GraduationCap className = 'h-8 w-8 text-[#4CAF50]' />
49- </ div >
54+ < div className = 'w-full max-w-2xl' >
55+ < div className = 'rounded-[12px] border border-[#3A4A3A] bg-[#1C2A1C] p-10 text-center' >
56+ < div className = 'mb-6 flex justify-center' >
57+ < div className = 'flex h-16 w-16 items-center justify-center rounded-full border-2 border-[#4CAF50]/40 bg-[#4CAF50]/10' >
58+ < GraduationCap className = 'h-8 w-8 text-[#4CAF50]' />
5059 </ div >
60+ </ div >
5161
52- < div className = 'mb-2 text-[#4CAF50]/70 text-[13px] uppercase tracking-[0.12em]' >
53- Certificate of Completion
54- </ div >
62+ < div className = 'mb-2 text-[#4CAF50]/70 text-[13px] uppercase tracking-[0.12em]' >
63+ Certificate of Completion
64+ </ div >
5565
56- < h1 className = 'mb-1 font-[430] text-[#ECECEC] text-[28px] leading-[120%]' >
57- { certificate . metadata ?. courseTitle }
58- </ h1 >
66+ < h1 className = 'mb-1 font-[430] text-[#ECECEC] text-[28px] leading-[120%]' >
67+ { certificate . metadata ?. courseTitle }
68+ </ h1 >
5969
60- { certificate . metadata ?. recipientName && (
61- < p className = 'mb-6 text-[#999] text-[16px]' >
62- Awarded to{ ' ' }
63- < span className = 'text-[#ECECEC]' > { certificate . metadata . recipientName } </ span >
64- </ p >
65- ) }
70+ { certificate . metadata ?. recipientName && (
71+ < p className = 'mb-6 text-[#999] text-[16px]' >
72+ Awarded to{ ' ' }
73+ < span className = 'text-[#ECECEC]' > { certificate . metadata . recipientName } </ span >
74+ </ p >
75+ ) }
6676
67- < div className = 'flex items-center justify-center gap-2 text-[#4CAF50]' >
68- < CheckCircle2 className = 'h-4 w-4' />
69- < span className = 'font-[430] text-[14px]' > Verified</ span >
70- </ div >
77+ < div className = 'flex items-center justify-center gap-2 text-[#4CAF50]' >
78+ < CheckCircle2 className = 'h-4 w-4' />
79+ < span className = 'font-[430] text-[14px]' > Verified</ span >
7180 </ div >
81+ </ div >
7282
73- < div className = 'mt-6 divide-y divide-[#2A2A2A] rounded-[8px] border border-[#2A2A2A] bg-[#222]' >
74- < MetaRow label = 'Certificate number' >
75- < span className = 'font-mono text-[#ECECEC] text-[13px]' >
76- { certificate . certificateNumber }
83+ < div className = 'mt-6 divide-y divide-[#2A2A2A] rounded-[8px] border border-[#2A2A2A] bg-[#222]' >
84+ < MetaRow label = 'Certificate number' >
85+ < span className = 'font-mono text-[#ECECEC] text-[13px]' >
86+ { certificate . certificateNumber }
87+ </ span >
88+ </ MetaRow >
89+ < MetaRow label = 'Issued' >
90+ < span className = 'text-[#ECECEC] text-[13px]' > { formatDate ( certificate . issuedAt ) } </ span >
91+ </ MetaRow >
92+ < MetaRow label = 'Status' >
93+ < span
94+ className = { `text-[13px] capitalize ${
95+ certificate . status === 'active' ? 'text-[#4CAF50]' : 'text-[#f44336]'
96+ } `}
97+ >
98+ { certificate . status }
99+ </ span >
100+ </ MetaRow >
101+ { certificate . expiresAt && (
102+ < MetaRow label = 'Expires' >
103+ < span className = 'text-[#ECECEC] text-[13px]' >
104+ { formatDate ( certificate . expiresAt ) }
77105 </ span >
78106 </ MetaRow >
79- < MetaRow label = 'Issued' >
80- < span className = 'text-[#ECECEC] text-[13px]' > { formatDate ( certificate . issuedAt ) } </ span >
81- </ MetaRow >
82- < MetaRow label = 'Status' >
83- < span
84- className = { `text-[13px] capitalize ${
85- certificate . status === 'active' ? 'text-[#4CAF50]' : 'text-[#f44336]'
86- } `}
87- >
88- { certificate . status }
89- </ span >
90- </ MetaRow >
91- { certificate . expiresAt && (
92- < MetaRow label = 'Expires' >
93- < span className = 'text-[#ECECEC] text-[13px]' >
94- { formatDate ( certificate . expiresAt ) }
95- </ span >
96- </ MetaRow >
97- ) }
98- </ div >
99-
100- < p className = 'mt-5 text-center text-[#555] text-[13px]' >
101- This certificate was issued by Sim AI, Inc. and verifies the holder has completed the{ ' ' }
102- { certificate . metadata ?. courseTitle } program.
103- </ p >
107+ ) }
104108 </ div >
105- ) }
109+
110+ < p className = 'mt-5 text-center text-[#555] text-[13px]' >
111+ This certificate was issued by Sim AI, Inc. and verifies the holder has completed the{ ' ' }
112+ { certificate . metadata ?. courseTitle } program.
113+ </ p >
114+ </ div >
106115 </ main >
107116 )
108117}
0 commit comments