Fix certificate View link (wrong /study/certificate URL), certificates in My Account, name on certificate; v2.10.10
Publish Container / build (push) Successful in 15m29s

- Certificate URLs in the account overview and the issuance email pointed at /study/certificate/:token,
  which the router treated as a lesson page; now /certificate/:token (old links redirect)
- My Account Certificates card lists every enrolled study: progress, Generate, View, Copy link, Download PNG, Share
- Certificate canvas used a non-existent /api/study-account route and always printed 'Student'; now reads the account name
- Panel no longer collapses after generating

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-09 11:13:44 -04:00
1 parent 899e715067
commit 7301982bb1
8 files changed
+123 -30

No files matched your search

+23 -14
View File
@@ -11,6 +11,7 @@ interface CertificateStatus {
interface Props {
studySlug: string
onIssued?: () => void
}
function formatDate(iso: string | null) {
@@ -115,7 +116,7 @@ function drawCertificate(
ctx.fillText('versebyversewithnate.us', W / 2, 490)
}
export function StudyCertificate({ studySlug }: Props) {
export function StudyCertificate({ studySlug, onIssued }: Props) {
const [status, setStatus] = useState<CertificateStatus | null>(null)
const [loading, setLoading] = useState(true)
const [issuing, setIssuing] = useState(false)
@@ -124,20 +125,27 @@ export function StudyCertificate({ studySlug }: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
fetch(`/api/study-certificate/${studySlug}`)
.then(r => r.json())
.then(data => {
let cancelled = false
async function load() {
try {
const res = await fetch(`/api/study-certificate/${studySlug}`)
if (!res.ok) throw new Error('unavailable')
const data = await res.json() as CertificateStatus
if (cancelled) return
setStatus(data)
// Try to get display name from account
return fetch('/api/study-account')
})
.then(r => r.json())
.then(data => {
if (data?.displayName) setDisplayName(data.displayName)
else if (data?.username) setDisplayName(data.username)
})
.catch(() => {})
.finally(() => setLoading(false))
// The name printed on the certificate comes from the signed-in account
const auth = await fetch('/api/study-auth/status').then(r => (r.ok ? r.json() : null)).catch(() => null)
if (cancelled) return
if (auth?.displayName) setDisplayName(auth.displayName)
else if (auth?.username) setDisplayName(auth.username)
} catch {
// leave status null; the panel renders nothing
} finally {
if (!cancelled) setLoading(false)
}
}
void load()
return () => { cancelled = true }
}, [studySlug])
useEffect(() => {
@@ -159,6 +167,7 @@ export function StudyCertificate({ studySlug }: Props) {
const data = await res.json()
if (!res.ok) { setError(data.message ?? 'Failed to issue certificate.'); return }
setStatus(prev => prev ? { ...prev, token: data.token, issuedAt: data.issuedAt } : prev)
onIssued?.()
} catch {
setError('Network error. Please try again.')
} finally {