refactor: use server-side loading for shared worksheets
Switch from client-side sessionStorage to server-side database loading: Server-Side Loading (page.tsx): - Accept searchParams with optional ?share=abc123X query param - loadWorksheetSettings() now takes optional shareId parameter - If shareId provided, loads from worksheet_shares table - Falls back to user settings if share not found - Uses same parseAdditionConfig() for consistency Shared Worksheet Viewer: - Simplified handleOpenInEditor to pass share ID in URL - No longer uses sessionStorage for config transfer - Navigates to /create/worksheets?share=abc123X - Server handles loading on next page Client Component: - Removed sessionStorage logic and useSearchParams - Removed useEffect and useState for config loading - Simplified back to original implementation - Just receives initialSettings from server Benefits: - Consistent loading pattern for both user and shared configs - Server-side validation and parsing before rendering - No client-side state management for config loading - Eliminates race conditions and hydration issues - Share loading identical to database settings loading 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { css } from '@styled/css'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'
|
||||
import type { WorksheetFormState } from '@/app/create/worksheets/types'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
@@ -26,34 +24,11 @@ export function AdditionWorksheetClient({
|
||||
initialSettings,
|
||||
initialPreview,
|
||||
}: AdditionWorksheetClientProps) {
|
||||
const searchParams = useSearchParams()
|
||||
const isFromShare = searchParams.get('from') === 'share'
|
||||
|
||||
// Check for shared config in sessionStorage
|
||||
const [effectiveSettings, setEffectiveSettings] = useState(initialSettings)
|
||||
|
||||
useEffect(() => {
|
||||
if (isFromShare && typeof window !== 'undefined') {
|
||||
const sharedConfigStr = sessionStorage.getItem('sharedWorksheetConfig')
|
||||
if (sharedConfigStr) {
|
||||
try {
|
||||
const sharedConfig = JSON.parse(sharedConfigStr)
|
||||
console.log('[Worksheet Client] Loading shared config:', sharedConfig)
|
||||
setEffectiveSettings(sharedConfig)
|
||||
// Clear from sessionStorage after loading
|
||||
sessionStorage.removeItem('sharedWorksheetConfig')
|
||||
} catch (err) {
|
||||
console.error('Failed to parse shared config:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isFromShare])
|
||||
|
||||
console.log('[Worksheet Client] Component render, effectiveSettings:', {
|
||||
problemsPerPage: effectiveSettings.problemsPerPage,
|
||||
cols: effectiveSettings.cols,
|
||||
pages: effectiveSettings.pages,
|
||||
seed: effectiveSettings.seed,
|
||||
console.log('[Worksheet Client] Component render, initialSettings:', {
|
||||
problemsPerPage: initialSettings.problemsPerPage,
|
||||
cols: initialSettings.cols,
|
||||
pages: initialSettings.pages,
|
||||
seed: initialSettings.seed,
|
||||
})
|
||||
|
||||
const t = useTranslations('create.worksheets.addition')
|
||||
@@ -61,7 +36,7 @@ export function AdditionWorksheetClient({
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
// State management (formState, debouncedFormState, updateFormState)
|
||||
const { formState, debouncedFormState, updateFormState } = useWorksheetState(effectiveSettings)
|
||||
const { formState, debouncedFormState, updateFormState } = useWorksheetState(initialSettings)
|
||||
|
||||
// Generation workflow (status, error, generate, reset)
|
||||
const { status, error, generate, reset } = useWorksheetGeneration()
|
||||
|
||||
124
apps/web/src/app/create/worksheets/page.tsx
Normal file
124
apps/web/src/app/create/worksheets/page.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
import { db, schema } from '@/db'
|
||||
import { getViewerId } from '@/lib/viewer'
|
||||
import { parseAdditionConfig, defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
|
||||
import { AdditionWorksheetClient } from './components/AdditionWorksheetClient'
|
||||
import { WorksheetErrorBoundary } from './components/WorksheetErrorBoundary'
|
||||
import type { WorksheetFormState } from '@/app/create/worksheets/types'
|
||||
import { generateWorksheetPreview } from './generatePreview'
|
||||
import { worksheetShares } from '@/db/schema'
|
||||
|
||||
/**
|
||||
* Get current date formatted as "Month Day, Year"
|
||||
*/
|
||||
function getDefaultDate(): string {
|
||||
const now = new Date()
|
||||
return now.toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Load worksheet settings from database or shared link (server-side)
|
||||
*/
|
||||
async function loadWorksheetSettings(
|
||||
shareId?: string
|
||||
): Promise<Omit<WorksheetFormState, 'date' | 'rows' | 'total'>> {
|
||||
try {
|
||||
// If loading from a shared link
|
||||
if (shareId) {
|
||||
const share = await db.query.worksheetShares.findFirst({
|
||||
where: eq(worksheetShares.id, shareId),
|
||||
})
|
||||
|
||||
if (share) {
|
||||
// Parse the shared config (already stored with serializeAdditionConfig)
|
||||
const config = parseAdditionConfig(share.config)
|
||||
return {
|
||||
...config,
|
||||
seed: Date.now() % 2147483647,
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
}
|
||||
// If share not found, fall through to load user's settings
|
||||
console.warn(`Share ID ${shareId} not found, loading user settings instead`)
|
||||
}
|
||||
|
||||
// Load user's saved settings
|
||||
const viewerId = await getViewerId()
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(schema.worksheetSettings)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.worksheetSettings.userId, viewerId),
|
||||
eq(schema.worksheetSettings.worksheetType, 'addition')
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (!row) {
|
||||
// No saved settings, return defaults with a stable seed
|
||||
return {
|
||||
...defaultAdditionConfig,
|
||||
seed: Date.now() % 2147483647,
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
}
|
||||
|
||||
// Parse and validate config (auto-migrates to latest version)
|
||||
const config = parseAdditionConfig(row.config)
|
||||
return {
|
||||
...config,
|
||||
seed: Date.now() % 2147483647,
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
} catch (error) {
|
||||
console.error('Failed to load worksheet settings:', error)
|
||||
// Return defaults on error with a stable seed
|
||||
return {
|
||||
...defaultAdditionConfig,
|
||||
seed: Date.now() % 2147483647,
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
}
|
||||
}
|
||||
|
||||
export default async function AdditionWorksheetPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ share?: string }>
|
||||
}) {
|
||||
const { share: shareId } = await searchParams
|
||||
const initialSettings = await loadWorksheetSettings(shareId)
|
||||
|
||||
// Calculate derived state needed for preview
|
||||
// Use defaults for required fields (loadWorksheetSettings should always provide these, but TypeScript needs guarantees)
|
||||
const problemsPerPage = initialSettings.problemsPerPage ?? 20
|
||||
const pages = initialSettings.pages ?? 1
|
||||
const cols = initialSettings.cols ?? 5
|
||||
|
||||
const rows = Math.ceil((problemsPerPage * pages) / cols)
|
||||
const total = problemsPerPage * pages
|
||||
|
||||
// Create full config for preview generation
|
||||
const fullConfig: WorksheetFormState = {
|
||||
...initialSettings,
|
||||
rows,
|
||||
total,
|
||||
date: getDefaultDate(),
|
||||
}
|
||||
|
||||
// Pre-generate worksheet preview on the server
|
||||
console.log('[SSR] Generating worksheet preview on server...')
|
||||
const previewResult = generateWorksheetPreview(fullConfig)
|
||||
console.log('[SSR] Preview generation complete:', previewResult.success ? 'success' : 'failed')
|
||||
|
||||
// Pass settings and preview to client, wrapped in error boundary
|
||||
return (
|
||||
<WorksheetErrorBoundary>
|
||||
<AdditionWorksheetClient
|
||||
initialSettings={initialSettings}
|
||||
initialPreview={previewResult.success ? previewResult.pages : undefined}
|
||||
/>
|
||||
</WorksheetErrorBoundary>
|
||||
)
|
||||
}
|
||||
@@ -54,10 +54,9 @@ export default function SharedWorksheetPage() {
|
||||
const handleOpenInEditor = () => {
|
||||
if (!shareData) return
|
||||
|
||||
// Navigate to the worksheet creator with the shared config
|
||||
// Store config in sessionStorage so it can be loaded by the editor
|
||||
sessionStorage.setItem('sharedWorksheetConfig', JSON.stringify(shareData.config))
|
||||
router.push('/create/worksheets?from=share')
|
||||
// Navigate to the worksheet creator with the share ID
|
||||
// The server-side page will load the config from the database
|
||||
router.push(`/create/worksheets?share=${shareData.id}`)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
|
||||
Reference in New Issue
Block a user