Files
Sean Oliver 07fe32ace2 feat: implement inline expandable TableQuickstart for new homepage
- Created new inline TableQuickstart component with expandable card pattern
- Added feature flag support (control, ai, templates variants)
- Implemented smooth Framer Motion animations for expand/collapse
- Ported AI generation and template selection from legacy components
- Integrated with GettingStartedSection using PostHog feature flag
- Organized components into inline/ and legacy/ folders for clean separation
- Maintains all existing functionality while providing better UX

Key improvements:
- No navigation away from Getting Started section
- Progressive disclosure with smooth animations
- Supports A/B testing with feature flag variants
- Reuses existing API endpoints and logic
2025-09-19 10:15:11 -07:00

178 lines
5.2 KiB
TypeScript

import { useState, useCallback } from 'react'
import { useRouter } from 'next/router'
import { useParams } from 'common'
import { BASE_PATH } from 'lib/constants'
import { constructHeaders } from 'data/fetchers'
import { toast } from 'sonner'
import type { TableSuggestion, TableField, AIGeneratedSchema } from './types'
import { useQuickstartStore } from './quickstartStore'
const mapColumnType = (type: string): TableField['type'] => {
const typeMap: Record<string, TableField['type']> = {
bigint: 'int8',
integer: 'int4',
smallint: 'int2',
boolean: 'bool',
text: 'text',
varchar: 'varchar',
uuid: 'uuid',
timestamp: 'timestamp',
timestamptz: 'timestamptz',
'timestamp with time zone': 'timestamptz',
date: 'date',
time: 'time',
json: 'json',
jsonb: 'jsonb',
numeric: 'numeric',
real: 'float4',
'double precision': 'float8',
bytea: 'bytea',
}
return typeMap[type.toLowerCase()] || 'text'
}
const convertAISchemaToTableSuggestions = (schema: AIGeneratedSchema): TableSuggestion[] => {
return schema.tables.map((table) => ({
tableName: table.name,
fields: table.columns.map((column) => ({
name: column.name,
type: mapColumnType(column.type),
nullable: column.isNullable ?? undefined,
unique: column.isUnique ?? undefined,
default: column.defaultValue ?? undefined,
description:
column.isPrimary || column.name === 'id'
? 'Primary key'
: column.isForeign && column.references
? `References ${column.references}`
: undefined,
// Ensure 'id' fields are always marked as primary
isPrimary: column.name === 'id' ? true : column.isPrimary ?? undefined,
isForeign: column.isForeign ?? undefined,
references: column.references ?? undefined,
})),
rationale: table.description,
source: 'ai' as const,
relationships: table.relationships ?? undefined,
}))
}
export const useQuickstart = () => {
const router = useRouter()
const { ref } = useParams()
const projectId = ref as string
const quickstartStore = useQuickstartStore()
const [currentStep, setCurrentStep] = useState<'input' | 'preview'>('input')
const [candidates, setCandidates] = useState<TableSuggestion[]>([])
const [selectedTable, setSelectedTable] = useState<TableSuggestion | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [userInput, setUserInput] = useState<string>('')
const [isGenerating, setIsGenerating] = useState(false)
const onTablesReady = useCallback((tables: TableSuggestion[], input: string) => {
setCandidates(tables)
setUserInput(input)
setCurrentStep('preview')
setIsGenerating(false)
}, [])
const handleAiGenerate = useCallback(
async (prompt: string) => {
setIsGenerating(true)
setError(null)
setUserInput(prompt)
setCurrentStep('preview')
setCandidates([])
try {
const headers = await constructHeaders()
headers.set('Content-Type', 'application/json')
const response = await fetch(`${BASE_PATH}/api/ai/table-quickstart/generate-schemas`, {
method: 'POST',
headers,
credentials: 'include',
body: JSON.stringify({
prompt,
}),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to generate schemas')
}
const data: AIGeneratedSchema = await response.json()
const tables = convertAISchemaToTableSuggestions(data)
onTablesReady(tables, prompt)
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Failed to generate schemas'
console.error('AI generation failed:', e)
toast.error('Unable to generate tables. Please try again with a different description.', {
description: errorMessage,
duration: 5000,
})
setError(errorMessage)
setIsGenerating(false)
setCurrentStep('input')
}
},
[onTablesReady]
)
const handleSelectTable = useCallback(
async (table: TableSuggestion) => {
setSelectedTable(table)
setLoading(true)
setError(null)
try {
if (!table.tableName || !Array.isArray(table.fields) || table.fields.length === 0) {
throw new Error('Invalid table structure')
}
quickstartStore.setQuickstartData(table)
await router.push(`/project/${projectId}/editor`)
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Failed to navigate to table editor'
setError(errorMessage)
setLoading(false)
setCurrentStep('preview')
quickstartStore.clearQuickstartData()
}
},
[projectId, router, quickstartStore]
)
const handleBack = useCallback(() => {
if (currentStep === 'preview') {
setCurrentStep('input')
setCandidates([])
setError(null)
setSelectedTable(null)
setIsGenerating(false)
}
}, [currentStep])
return {
currentStep,
candidates,
selectedTable,
loading,
error,
userInput,
isGenerating,
onTablesReady,
handleAiGenerate,
handleSelectTable,
handleBack,
}
}