-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Summary
Connect app-preview-generator to Node 2 Supabase for data storage.
Quick Start (2 minutes)
1. Setup Doppler
# Install (if needed)
brew install dopplerhq/cli/doppler # macOS
# or
curl -Ls https://cli.doppler.com/install.sh | sudo sh # Linux
# Login & setup
doppler login
doppler setup --project hetzner-infrastructure --config prd
2. Add to your code
// Install
npm install @supabase/supabase-js
// Use in your app
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
)
3. Run your app
doppler run -- npm start
# or for development
doppler run -- npm run dev
Implementation Strategy
Based on analysis of the app-preview-generator codebase, here's how we'll integrate Supabase:
Key Data Points to Store
From the Screen
interface (v002/types/preview-generator.ts), we'll persist:
- Text content: title, subtitle, description
- Visual settings: colors, overlay styles, positions, opacity
- Screenshot data: URLs, positions, transformations
- Image assets: Additional overlays with positioning
- Device configurations: Type, orientation, export settings
Database Schema
-- Projects table
CREATE TABLE projects (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
device_type TEXT DEFAULT 'iphone-69',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
user_id UUID REFERENCES auth.users(id),
metadata JSONB
);
-- Screens table (multiple per project)
CREATE TABLE screens (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
position INT NOT NULL,
-- Text content
title TEXT,
subtitle TEXT,
description TEXT,
-- Visual style settings
overlay_style TEXT DEFAULT 'gradient',
text_position TEXT DEFAULT 'bottom',
device_position TEXT DEFAULT 'center',
bg_style TEXT DEFAULT 'gradient',
layout_style TEXT DEFAULT 'standard',
layer_order TEXT DEFAULT 'front',
-- Colors
primary_color TEXT DEFAULT '#4F46E5',
secondary_color TEXT DEFAULT '#7C3AED',
bg_color TEXT DEFAULT '#F3F4F6',
-- Positions stored as JSONB
position JSONB DEFAULT '{"x":0,"y":0,"scale":100,"rotation":0}',
text_overlay_position JSONB DEFAULT '{"x":0,"y":0}',
opacity JSONB DEFAULT '{"screenshot":100,"overlay":90}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Screenshots table (multiple per screen)
CREATE TABLE screenshots (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
screen_id UUID REFERENCES screens(id) ON DELETE CASCADE,
url TEXT NOT NULL,
position JSONB DEFAULT '{"x":0,"y":0,"scale":100,"rotation":0}',
opacity INT DEFAULT 100,
z_index INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Image assets table (logos, badges, etc.)
CREATE TABLE image_assets (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
screen_id UUID REFERENCES screens(id) ON DELETE CASCADE,
url TEXT NOT NULL,
position JSONB DEFAULT '{"x":0,"y":0}',
size JSONB DEFAULT '{"width":100,"height":100}',
rotation INT DEFAULT 0,
opacity INT DEFAULT 100,
z_index INT DEFAULT 10,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Integration Points in Current Code
-
Save Project (page.tsx:552-556)
- Currently saves to JSON file
- Will save to Supabase
projects
and related tables
-
Load Project (page.tsx:558-578)
- Currently loads from JSON file
- Will fetch from Supabase using project ID
-
Image Upload (page.tsx:403-429)
- Currently stores as data URLs
- Will upload to Supabase Storage, store URLs in DB
-
Real-time Collaboration
- Add Supabase real-time subscriptions
- Multiple users can work on same project
-
Export Functions (page.tsx:520-550)
- Store generated preview URLs
- Track export history
Code Integration Example
// Example integration in page.tsx
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_KEY!
)
// Save project to Supabase
const saveProjectToSupabase = async () => {
const { data: project, error } = await supabase
.from('projects')
.upsert({
name: `Preview-${Date.now()}`,
device_type: deviceType,
metadata: { version: '2.0' }
})
.select()
.single()
if (!error && project) {
// Save screens
for (const [index, screen] of screens.entries()) {
const { data: savedScreen } = await supabase
.from('screens')
.insert({
project_id: project.id,
position: index,
title: screen.title,
subtitle: screen.subtitle,
description: screen.description,
overlay_style: screen.overlayStyle,
text_position: screen.textPosition,
device_position: screen.devicePosition,
bg_style: screen.bgStyle,
layout_style: screen.layoutStyle,
layer_order: screen.layerOrder,
primary_color: screen.primaryColor,
secondary_color: screen.secondaryColor,
bg_color: screen.bgColor,
position: screen.position,
text_overlay_position: screen.textOverlayPosition,
opacity: screen.opacity
})
.select()
.single()
// Save screenshots
if (screen.screenshots?.length > 0) {
await supabase.from('screenshots').insert(
screen.screenshots.map(s => ({
screen_id: savedScreen.id,
url: s.url, // Will be storage URL after upload
position: s.position,
opacity: s.opacity,
z_index: s.zIndex
}))
)
}
// Save image assets
if (screen.imageAssets?.length > 0) {
await supabase.from('image_assets').insert(
screen.imageAssets.map(a => ({
screen_id: savedScreen.id,
url: a.url,
position: a.position,
size: a.size,
rotation: a.rotation,
opacity: a.opacity,
z_index: a.zIndex
}))
)
}
}
}
}
// Load project from Supabase
const loadProjectFromSupabase = async (projectId: string) => {
const { data: project } = await supabase
.from('projects')
.select(`
*,
screens (
*,
screenshots (*),
image_assets (*)
)
`)
.eq('id', projectId)
.single()
if (project) {
// Map to local state format
const mappedScreens = project.screens.map(s => ({
id: s.id,
title: s.title,
subtitle: s.subtitle,
description: s.description,
overlayStyle: s.overlay_style,
textPosition: s.text_position,
devicePosition: s.device_position,
bgStyle: s.bg_style,
layoutStyle: s.layout_style,
layerOrder: s.layer_order,
primaryColor: s.primary_color,
secondaryColor: s.secondary_color,
bgColor: s.bg_color,
position: s.position,
textOverlayPosition: s.text_overlay_position,
opacity: s.opacity,
screenshots: s.screenshots,
imageAssets: s.image_assets,
screenshot: s.screenshots?.[0]?.url || null // Legacy support
}))
setScreens(mappedScreens)
setDeviceType(project.device_type)
}
}
// Upload image to Supabase Storage
const uploadToStorage = async (file: File, bucket = 'screenshots') => {
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${userId}/${fileName}`
const { data, error } = await supabase.storage
.from(bucket)
.upload(filePath, file)
if (!error) {
const { data: { publicUrl } } = supabase.storage
.from(bucket)
.getPublicUrl(filePath)
return publicUrl
}
return null
}
// Real-time subscription for collaboration
supabase
.channel('project-changes')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'screens',
filter: `project_id=eq.${projectId}`
}, (payload) => {
// Update local state with changes
console.log('Screen updated:', payload)
// Refresh screens from database
})
.subscribe()
Benefits
- Persistent Storage: Projects saved in cloud, accessible anywhere
- Collaboration: Multiple users can work on same project
- Asset Management: Centralized storage for all images/screenshots
- Version History: Track changes over time
- Export Tracking: Store generated previews with metadata
- Search & Filter: Find projects by text content, colors, device type
Environment Variables (auto-injected by Doppler)
SUPABASE_URL # API endpoint
SUPABASE_KEY # Access key (anon or service based on needs)
DATABASE_URL # Direct PostgreSQL connection
Implementation Steps
- Get Doppler access from team admin
- Run setup command above
- Create database schema in Supabase
- Add Supabase client to Next.js app
- Implement save/load functions
- Add real-time subscriptions
- Migrate image uploads to Supabase Storage
Notes
- Node 2 Host: 91.98.132.37
- All credentials managed by Doppler - never hardcode
- Use
doppler run
to inject credentials automatically
🤖 Generated with Claude Code
Metadata
Metadata
Assignees
Labels
No labels