refactor(web): return calendar SVG preview with PDF generation
- Change generate API to return JSON instead of binary PDF - Include base64-encoded PDF and SVG preview in response - Update client to decode base64 PDF and trigger download - Store SVG preview for display after generation - Improve error handling to surface actual error messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
867c7ee172
commit
14a5de0dfa
|
|
@ -37,6 +37,7 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
// Generate SVGs using server-side rendering (API routes can use react-dom/server)
|
||||
const daysInMonth = getDaysInMonth(year, month)
|
||||
let previewSvg: string | null = null
|
||||
|
||||
if (format === 'monthly') {
|
||||
// Generate single composite SVG for monthly calendar (prevents multi-page overflow)
|
||||
|
|
@ -49,6 +50,7 @@ export async function POST(request: NextRequest) {
|
|||
if (!compositeSvg || compositeSvg.trim().length === 0) {
|
||||
throw new Error(`Generated empty composite calendar SVG`)
|
||||
}
|
||||
previewSvg = compositeSvg
|
||||
writeFileSync(join(tempDir, 'calendar.svg'), compositeSvg)
|
||||
} catch (error: any) {
|
||||
console.error(`Error generating composite calendar:`, error.message)
|
||||
|
|
@ -121,18 +123,18 @@ export async function POST(request: NextRequest) {
|
|||
)
|
||||
}
|
||||
|
||||
// Read and return PDF
|
||||
// Read PDF
|
||||
const pdfBuffer = readFileSync(pdfPath)
|
||||
|
||||
// Clean up temp directory
|
||||
rmSync(tempDir, { recursive: true, force: true })
|
||||
tempDir = null
|
||||
|
||||
return new NextResponse(pdfBuffer, {
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment; filename="calendar-${year}-${String(month).padStart(2, '0')}.pdf"`,
|
||||
},
|
||||
// Return JSON with both PDF and SVG preview
|
||||
return NextResponse.json({
|
||||
pdf: pdfBuffer.toString('base64'),
|
||||
svg: previewSvg,
|
||||
filename: `calendar-${year}-${String(month).padStart(2, '0')}.pdf`,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error generating calendar:', error)
|
||||
|
|
@ -146,6 +148,17 @@ export async function POST(request: NextRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Failed to generate calendar' }, { status: 500 })
|
||||
// Surface the actual error for debugging
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorStack = error instanceof Error ? error.stack : undefined
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to generate calendar',
|
||||
message: errorMessage,
|
||||
...(process.env.NODE_ENV === 'development' && { stack: errorStack })
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export default function CalendarCreatorPage() {
|
|||
const [format, setFormat] = useState<'monthly' | 'daily'>('monthly')
|
||||
const [paperSize, setPaperSize] = useState<'us-letter' | 'a4' | 'a3' | 'tabloid'>('us-letter')
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [previewSvg, setPreviewSvg] = useState<string | null>(null)
|
||||
|
||||
const handleGenerate = async () => {
|
||||
setIsGenerating(true)
|
||||
|
|
@ -34,21 +35,31 @@ export default function CalendarCreatorPage() {
|
|||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate calendar')
|
||||
const errorData = await response.json().catch(() => ({}))
|
||||
throw new Error(errorData.message || 'Failed to generate calendar')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const data = await response.json()
|
||||
|
||||
// Store SVG preview for display
|
||||
if (data.svg) {
|
||||
setPreviewSvg(data.svg)
|
||||
}
|
||||
|
||||
// Convert base64 PDF to blob and trigger download
|
||||
const pdfBytes = Uint8Array.from(atob(data.pdf), c => c.charCodeAt(0))
|
||||
const blob = new Blob([pdfBytes], { type: 'application/pdf' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `calendar-${year}-${String(month).padStart(2, '0')}.pdf`
|
||||
a.download = data.filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
document.body.removeChild(a)
|
||||
} catch (error) {
|
||||
console.error('Error generating calendar:', error)
|
||||
alert('Failed to generate calendar. Please try again.')
|
||||
alert(`Failed to generate calendar: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
} finally {
|
||||
setIsGenerating(false)
|
||||
}
|
||||
|
|
@ -122,7 +133,7 @@ export default function CalendarCreatorPage() {
|
|||
/>
|
||||
|
||||
{/* Preview */}
|
||||
<CalendarPreview month={month} year={year} format={format} />
|
||||
<CalendarPreview month={month} year={year} format={format} previewSvg={previewSvg} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue