diff --git a/apps/web/src/app/api/create/calendar/preview/route.ts b/apps/web/src/app/api/create/calendar/preview/route.ts
index 33a1740f..997cb7fd 100644
--- a/apps/web/src/app/api/create/calendar/preview/route.ts
+++ b/apps/web/src/app/api/create/calendar/preview/route.ts
@@ -5,6 +5,7 @@ import { join } from 'path'
import { execSync } from 'child_process'
import { generateMonthlyTypst, getDaysInMonth } from '../utils/typstGenerator'
import { generateCalendarComposite } from '@/utils/calendar/generateCalendarComposite'
+import { generateAbacusElement } from '@/utils/calendar/generateCalendarAbacus'
interface PreviewRequest {
month: number
@@ -26,34 +27,137 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Invalid month or year' }, { status: 400 })
}
- // Only generate preview for monthly format
- if (format !== 'monthly') {
- return NextResponse.json({ svg: null })
- }
-
// Dynamic import to avoid Next.js bundler issues
const { renderToStaticMarkup } = await import('react-dom/server')
- // Create temp directory for SVG file
+ // Create temp directory for SVG file(s)
tempDir = join(tmpdir(), `calendar-preview-${Date.now()}-${Math.random()}`)
mkdirSync(tempDir, { recursive: true })
- // Generate and write composite SVG
- const calendarSvg = generateCalendarComposite({
- month,
- year,
- renderToString: renderToStaticMarkup,
- })
- writeFileSync(join(tempDir, 'calendar.svg'), calendarSvg)
-
// Generate Typst document content
const daysInMonth = getDaysInMonth(year, month)
- const typstContent = generateMonthlyTypst({
- month,
- year,
- paperSize: 'us-letter',
- daysInMonth,
- })
+ let typstContent: string
+
+ if (format === 'monthly') {
+ // Generate and write composite SVG
+ const calendarSvg = generateCalendarComposite({
+ month,
+ year,
+ renderToString: renderToStaticMarkup,
+ })
+ writeFileSync(join(tempDir, 'calendar.svg'), calendarSvg)
+
+ typstContent = generateMonthlyTypst({
+ month,
+ year,
+ paperSize: 'us-letter',
+ daysInMonth,
+ })
+ } else {
+ // Daily format: Create a SINGLE composite SVG (like monthly) to avoid multi-image export issue
+
+ // Generate individual abacus SVGs
+ const daySvg = renderToStaticMarkup(generateAbacusElement(1, 2))
+ if (!daySvg || daySvg.trim().length === 0) {
+ throw new Error('Generated empty SVG for day 1')
+ }
+
+ const yearColumns = Math.max(1, Math.ceil(Math.log10(year + 1)))
+ const yearSvg = renderToStaticMarkup(generateAbacusElement(year, yearColumns))
+ if (!yearSvg || yearSvg.trim().length === 0) {
+ throw new Error(`Generated empty SVG for year ${year}`)
+ }
+
+ // Create composite SVG with both year and day abacus
+ const monthName = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ][month - 1]
+ const dayOfWeek = new Date(year, month - 1, 1).toLocaleDateString('en-US', {
+ weekday: 'long',
+ })
+
+ // Extract SVG content (remove outer
(null)
+ // Detect default paper size based on user's locale (client-side only)
+ useEffect(() => {
+ // Get user's locale
+ const locale = navigator.language || navigator.languages?.[0] || 'en-US'
+ const country = locale.split('-')[1]?.toUpperCase()
+
+ // Countries that use US Letter (8.5" × 11")
+ const letterCountries = ['US', 'CA', 'MX', 'GT', 'PA', 'DO', 'PR', 'PH']
+
+ const detectedSize = letterCountries.includes(country || '') ? 'us-letter' : 'a4'
+ setPaperSize(detectedSize)
+ }, [])
+
const handleGenerate = async () => {
setIsGenerating(true)
try {