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

+34 -10
View File
@@ -2872,6 +2872,7 @@ export function StudyAccountPage() {
const [pwMessage, setPwMessage] = useState('')
const [pwSuccess, setPwSuccess] = useState(false)
const [certOpen, setCertOpen] = useState<Record<string, boolean>>({})
const [signOutBusy, setSignOutBusy] = useState(false)
const [signOutAllBusy, setSignOutAllBusy] = useState(false)
const [signOutAllMessage, setSignOutAllMessage] = useState('')
@@ -3298,18 +3299,41 @@ export function StudyAccountPage() {
})}
</section>
{overview.certificates.length > 0 && (
{enrolledProgress.length > 0 && (
<section className="account-card" aria-label="Certificates">
<div className="account-card-head"><h2>Certificates</h2></div>
{overview.certificates.map(cert => (
<div className="account-cert" key={cert.studySlug}>
<span>🏅 {cert.studyTitle} <small>— {new Date(cert.issuedAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}</small></span>
<div className="account-cert-actions">
<a href={cert.url} target="_blank" rel="noopener noreferrer" className="btn-secondary">View</a>
<button type="button" className="btn-secondary" onClick={() => { navigator.clipboard?.writeText(cert.url).catch(() => {}) }}>Copy link</button>
</div>
</div>
))}
<p className="account-muted">Finish every lesson in a study to earn a certificate of completion you can view, download and share.</p>
{enrolledProgress.map(item => {
const cert = overview.certificates.find(c => c.studySlug === item.slug)
const ready = !cert && item.completedLessons >= item.totalLessons
return (
<details
className="account-cert-study"
key={item.slug}
open={certOpen[item.slug] ?? ready}
onToggle={e => {
const isOpen = (e.currentTarget as HTMLDetailsElement).open
setCertOpen(prev => (prev[item.slug] === isOpen ? prev : { ...prev, [item.slug]: isOpen }))
}}
>
<summary>
<span className="account-cert-title">{cert ? '🏅' : '🎓'} {item.title}</span>
{cert
? <span className="account-chip account-chip--ok">Earned {new Date(cert.issuedAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}</span>
: ready
? <span className="account-chip account-chip--warn">Ready to claim</span>
: <span className="account-chip account-chip--muted">{item.completedLessons} of {item.totalLessons} lessons</span>}
</summary>
{cert && (
<div className="account-cert-actions">
<Link to={new URL(cert.url).pathname} target="_blank" rel="noopener noreferrer" className="btn-primary">View certificate</Link>
<button type="button" className="btn-secondary" onClick={() => { navigator.clipboard?.writeText(cert.url).catch(() => {}) }}>Copy link</button>
</div>
)}
<StudyCertificate studySlug={item.slug} onIssued={() => { void refreshOverview() }} />
</details>
)
})}
</section>
)}