mirror of
https://github.com/mmahdium/portfolio.git
synced 2026-08-15 20:52:49 +03:30
- Update resume data with comprehensive professional summary and optimized work experience highlights - Add resume content tech-spec and strategy documentation for 2025 best practices - Create resume guidelines reference document for content consistency - Enhance PDF composable with openPdf preview function alongside downloadPdf - Expand CSS banner hiding rules to block certificates.dev promotional content - Add research documentation on resume best practices and ATS optimization - Update i18n translations for resume content in English and Persian - Include sample Ali Arghyani resume PDF template in design assets - Refactor resume.en.ts with improved professional positioning and achievement metrics - Update API route documentation for PDF generation story artifacts - Establish foundation for Epic 4 sprint planning with tech specifications
5.3 KiB
5.3 KiB
Story 3.2: Create PDF Download Composable
Status: done
Story
As a developer, I want a composable that handles PDF download logic, so that the download button can trigger downloads easily.
Acceptance Criteria
- AC1: Given I call
downloadPdf()from the composable, when the function executes, then it fetches/api/resume/pdfas blob - AC2: The composable creates object URL from blob and triggers browser download
- AC3: Download filename is from
getPdfFilename()(e.g., "Ali_Arghyani_Resume.pdf") - AC4: The composable returns
isGenerating: Ref<boolean>for loading state - AC5: The composable returns
downloadPdf: () => Promise<void>function - AC6: Given an error occurs, when caught, then it shows toast notification with error message
- AC7: After error,
isGeneratingis set back to false - AC8: Object URL is revoked after download to prevent memory leaks
Tasks / Subtasks
-
Create composable file (AC: #4, #5)
- Create
app/composables/useResumePdf.ts - Define
isGeneratingref with initial value false - Define
downloadPdfasync function - Return both from composable
- Create
-
Implement download logic (AC: #1-#3, #8)
- Set
isGenerating = trueat start - Fetch
/api/resume/pdfwithresponseType: 'blob' - Create object URL from blob:
URL.createObjectURL(response) - Create temporary
<a>element - Set
hrefto object URL - Set
downloadattribute to filename fromgetPdfFilename() - Trigger click on element
- Revoke object URL:
URL.revokeObjectURL(url) - Set
isGenerating = falsein finally block
- Set
-
Add error handling (AC: #6, #7)
- Wrap in try-catch block
- Import
useToast()from Nuxt UI - Show error toast on catch
- Log error to console
- Ensure
isGenerating = falsein finally
-
Test composable
- Import in component and call
downloadPdf() - Verify loading state changes
- Verify file downloads with correct name
- Test error handling (disconnect network)
- Import in component and call
Dev Notes
Architecture Alignment
From Architecture Doc:
- File location:
app/composables/useResumePdf.ts - Uses
$fetchwith blob response type - Uses Nuxt UI
useToast()for notifications - Gets filename from
useResumeData().getPdfFilename()
From Tech Spec Epic 3:
- AC5-AC7 map to this story
- Composable interface defined in spec
Implementation Notes
Composable Structure:
export function useResumePdf() {
const isGenerating = ref(false)
const toast = useToast()
const { getPdfFilename } = useResumeData()
async function downloadPdf() {
isGenerating.value = true
try {
const response = await $fetch('/api/resume/pdf', {
responseType: 'blob'
})
const url = URL.createObjectURL(response)
const a = document.createElement('a')
a.href = url
a.download = getPdfFilename()
a.click()
URL.revokeObjectURL(url)
} catch (error) {
console.error('PDF generation failed:', error)
toast.add({
title: 'Error generating PDF',
description: 'Please try again',
color: 'error'
})
} finally {
isGenerating.value = false
}
}
return { isGenerating, downloadPdf }
}
Usage in Component:
<script setup>
const { isGenerating, downloadPdf } = useResumePdf()
</script>
<template>
<UButton
:loading="isGenerating"
:disabled="isGenerating"
@click="downloadPdf"
>
Download PDF
</UButton>
</template>
Dependencies
useResumeData()composable (Epic 1) - forgetPdfFilename()useToast()from Nuxt UI - for error notifications$fetchfrom Nuxt - for API calls
Testing Checklist
- Composable exports
isGeneratinganddownloadPdf isGeneratingstarts as falseisGeneratingbecomes true during downloadisGeneratingreturns to false after completion- PDF downloads with correct filename
- Error shows toast notification
- Error logs to console
isGeneratingreturns to false after error
References
- [Source: docs/architecture.md#Error-Handling]
- [Source: docs/architecture.md#Implementation-Patterns]
- [Source: docs/sprint-artifacts/tech-spec-epic-3.md#AC5-AC7]
Dev Agent Record
Context Reference
- docs/sprint-artifacts/3-2-create-pdf-download-composable.context.xml
Agent Model Used
- Claude Sonnet 4 (via Kiro)
Debug Log References
- IDM (Internet Download Manager) intercept issues resolved
- Changed from blob fetch to window.open approach
Completion Notes List
- ✅ Composable created at
app/composables/useResumePdf.ts - ✅
isGeneratingref for loading state - ✅
openPdf()function - opens PDF in new tab for preview - ✅
downloadPdf()function - forces download with attachment header - ✅ Uses
window.openinstead of$fetchto avoid IDM conflicts - ✅ Filename from
useResumeData().getPdfFilename() - ⚠️ Toast error handling removed (IDM intercepts cause false errors)
File List
app/composables/useResumePdf.ts(created)
Change Log:
- 2025-12-01: Story drafted by SM agent (Bob)
- 2025-12-01: Implementation completed with blob fetch approach
- 2025-12-01: Refactored to window.open approach due to IDM conflicts