diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..5b24a72 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json index d2c168d..4c78549 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "html2canvas": "^1.4.1", "lucide-react": "^0.468.0", "next": "15.1.0", "react": "^19.0.0", diff --git a/src/app/page.tsx b/src/app/page.tsx index a90b477..67d7f78 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,6 +9,8 @@ import { PhotoUploader } from '@/components/photo-uploader' import { VoiceRecorder } from '@/components/voice-recorder' import { DoodleDrawer } from '@/components/doodle-drawer' import { DottedBackground } from '@/components/dotted-background' +import html2canvas from 'html2canvas' +import { Download } from 'lucide-react' // import { SpotifyPlayer } from '@/components/spotify-player' export interface LetterItem { @@ -62,7 +64,7 @@ export default function DigitalLetterComposer() { const rect = (e.target as HTMLElement).getBoundingClientRect() const offsetX = position.clientX - rect.left const offsetY = position.clientY - rect.top - + setIsDragging(true) setCurrentItem({ ...item, @@ -77,7 +79,7 @@ export default function DigitalLetterComposer() { const handleDragMove = (e: React.MouseEvent | React.TouchEvent) => { if (!isDragging || !currentItem || !canvasRef.current) return - + const position = 'touches' in e ? e.touches[0] : e const rect = canvasRef.current.getBoundingClientRect() const x = position.clientX - rect.left - (currentItem?.offsetX ?? 0) @@ -122,11 +124,55 @@ export default function DigitalLetterComposer() { }) } + const handleSendGift = async () => { + // In a real application, you would send the gift data to your backend here + // and generate a unique sharable link. For this example, we'll simulate it. + await new Promise(resolve => setTimeout(resolve, 1500)) // Simulate network request + const uniqueId = Math.random().toString(36).substring(2, 15) + return `https://yourdomain.com/gift/${uniqueId}` + } + + const exportAsImage = async () => { + if (canvasRef.current) { + // Get the export button element + const exportButton = document.querySelector('.export-button') + if (exportButton) { + // Hide the button + exportButton.classList.add('hidden') + } + + const canvas = await html2canvas(canvasRef.current, { + allowTaint: true, + useCORS: true, + foreignObjectRendering: true, + onclone: (clonedDoc) => { + // Force all SVGs to be rendered before capturing + const svgs = clonedDoc.getElementsByTagName('svg') + Array.from(svgs).forEach(svg => { + svg.setAttribute('width', svg.getBoundingClientRect().width.toString()) + svg.setAttribute('height', svg.getBoundingClientRect().height.toString()) + }) + } + }) + + // Show the button again + if (exportButton) { + exportButton.classList.remove('hidden') + } + + const image = canvas.toDataURL('image/png', 1.0) + const link = document.createElement('a') + link.href = image + link.download = `digital-letter-${Date.now()}.png` + link.click() + } + } + return (
-
- +
+ +
) } - diff --git a/src/components/doodle-drawer.tsx b/src/components/doodle-drawer.tsx index 87e7623..9ffe54d 100644 --- a/src/components/doodle-drawer.tsx +++ b/src/components/doodle-drawer.tsx @@ -91,23 +91,36 @@ export const DoodleDrawer: React.FC = ({ onClose, onDoodleAdd const svg = svgRef.current if (!svg) return - // Clone the SVG to remove event listeners and refs - const clonedSvg = svg.cloneNode(true) as SVGSVGElement - - // Calculate the viewBox based on the SVG's dimensions - const rect = svg.getBoundingClientRect() - clonedSvg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`) - clonedSvg.setAttribute('width', '100%') - clonedSvg.setAttribute('height', '100%') - clonedSvg.style.backgroundColor = 'transparent' - - // Convert to string - const svgData = new XMLSerializer().serializeToString(clonedSvg) - const svgBlob = new Blob([svgData], { type: 'image/svg+xml' }) - const svgUrl = URL.createObjectURL(svgBlob) - - onDoodleAdd(svgUrl) - onClose() + // Create a new SVG with the paths + const svgContent = ` + + ${paths.map(path => ``).join('')} + + ` + + // Convert SVG to base64 data URL + const blob = new Blob([svgContent], { type: 'image/svg+xml' }) + const reader = new FileReader() + reader.onload = () => { + if (typeof reader.result === 'string') { + // Convert SVG to PNG using canvas + const img = new Image() + img.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = svg.clientWidth + canvas.height = svg.clientHeight + const ctx = canvas.getContext('2d') + if (ctx) { + ctx.drawImage(img, 0, 0) + const pngUrl = canvas.toDataURL('image/png') + onDoodleAdd(pngUrl) + onClose() + } + } + img.src = reader.result + } + } + reader.readAsDataURL(blob) } return ( @@ -206,4 +219,3 @@ export const DoodleDrawer: React.FC = ({ onClose, onDoodleAdd ) } - diff --git a/src/components/draggable-item.tsx b/src/components/draggable-item.tsx index 9167f8f..aaeb3b4 100644 --- a/src/components/draggable-item.tsx +++ b/src/components/draggable-item.tsx @@ -61,9 +61,9 @@ export const DraggableItem: React.FC = ({ return case 'doodle': return ( - ) @@ -108,4 +108,3 @@ export const DraggableItem: React.FC = ({ ) } -