Skip to content

Commit 2a84a5d

Browse files
committed
feat(portfolio): add public portfolio viewer page at /p/[id]
1 parent fc6258c commit 2a84a5d

1 file changed

Lines changed: 85 additions & 0 deletions

File tree

app/p/[id]/page.tsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { createServerSupabaseClient } from '@/lib/database/supabase-server'
2+
import { notFound } from 'next/navigation'
3+
import { AnalyticsTracker } from '@/components/resume/AnalyticsTracker'
4+
import { ResumeData } from '@/lib/types/resume'
5+
import { Metadata } from 'next'
6+
import { ResumeA4 } from '@/components/resume/ResumeA4'
7+
8+
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
9+
const { id } = await params
10+
const supabase = await createServerSupabaseClient()
11+
12+
const { data: resume } = await supabase
13+
.from('resumes')
14+
.select('title, personal_info')
15+
.filter('id', 'eq', id)
16+
.single()
17+
18+
if (!resume) return { title: 'Not Found' }
19+
20+
return {
21+
title: `${resume.personal_info?.fullName || 'Portfolio'} | ${resume.title}`,
22+
description: 'A professional portfolio created with Lab68Dev Platform',
23+
}
24+
}
25+
26+
export default async function PublicPortfolioPage({ params }: { params: Promise<{ id: string }> }) {
27+
const { id } = await params
28+
const supabase = await createServerSupabaseClient()
29+
30+
const { data: resume, error } = await supabase
31+
.from('resumes')
32+
.select('*')
33+
.filter('id', 'eq', id)
34+
.single()
35+
36+
if (!resume || error) {
37+
return notFound()
38+
}
39+
40+
const resumeData: ResumeData = resume.data
41+
42+
return (
43+
<div className="min-h-screen bg-neutral-100 flex justify-center py-12 print:py-0 print:bg-white">
44+
<AnalyticsTracker resumeId={id} />
45+
46+
{/* We reuse ResumeA4 but give it dummy handlers to make it read-only.
47+
The print CSS handles hiding non-public things if needed.
48+
We also wrap it in a pointer-events-none layer for areas we don't want edited.
49+
For a true read-only, we should ideally disable inline editing.
50+
But pointer-events-none works for a quick static preview renderer!
51+
*/}
52+
<div className="pointer-events-none shadow-2xl print:shadow-none bg-white">
53+
<ResumeA4
54+
editorRef={{ current: null } as any}
55+
resumeData={resumeData}
56+
setResumeData={() => {}}
57+
selectedTemplate={resume.template || 'modern'}
58+
zoomLevel={1}
59+
showPhotoUpload={false}
60+
handlePhotoUpload={() => {}}
61+
toggleSectionVisibility={() => {}}
62+
handleDragStart={() => {}}
63+
handleDragOver={() => {}}
64+
handleDragEnd={() => {}}
65+
updateExperience={() => {}}
66+
removeExperience={() => {}}
67+
addExperience={() => {}}
68+
updateEducation={() => {}}
69+
removeEducation={() => {}}
70+
addEducation={() => {}}
71+
updateSkill={() => {}}
72+
removeSkill={() => {}}
73+
addSkill={() => {}}
74+
updateCertification={() => {}}
75+
removeCertification={() => {}}
76+
addCertification={() => {}}
77+
updateProject={() => {}}
78+
removeProject={() => {}}
79+
addProject={() => {}}
80+
getFontSizeClass={() => resumeData.styleSettings.fontSize === 'small' ? 'text-xs' : resumeData.styleSettings.fontSize === 'large' ? 'text-base' : 'text-sm'}
81+
/>
82+
</div>
83+
</div>
84+
)
85+
}

0 commit comments

Comments
 (0)