managarten/games/figgos/apps/mobile/Readmes/CreatePageIntegration.md
Till-JS 05d074c57e 🔧 refactor(figgos): restructure to standard monorepo pattern
Migrate figgos from single Expo app to multi-app monorepo structure:
- Move mobile app to apps/mobile/
- Add apps/web/ (SvelteKit) and apps/backend/ (NestJS) scaffolds
- Add packages/shared/ for shared types and constants
- Update root package.json with new dev commands
- Temporarily skip type-check (run pnpm install first)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:27:15 +01:00

467 lines
12 KiB
Markdown

# Figgos Create Page Integration
This document outlines the integration between the Create page and the Supabase figgos-generator edge function.
## Database Structure
We've created a new `figures` table with the following structure:
```sql
CREATE TABLE public.figures (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR NOT NULL,
subject VARCHAR NOT NULL,
image_url VARCHAR NOT NULL,
theme VARCHAR NOT NULL,
rarity VARCHAR NOT NULL,
character_description TEXT,
style_description TEXT,
accessory1_description TEXT NOT NULL,
accessory2_description TEXT NOT NULL,
accessory3_description TEXT NOT NULL,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
likes INTEGER DEFAULT 0,
is_public BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ
);
```
This table includes:
- Basic figure information (name, image URL)
- Direct storage of theme and rarity values as strings
- Character and accessory descriptions
- User ownership, likes, and public/private setting
- Timestamps for creation and updates
## Integration Steps
### 1. Create a Supabase Client Utility
First, we need to enhance our existing Supabase client to handle the figure generation:
```typescript
// utils/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';
import { ExtendedFigureData } from '../components/SidebarCreateFigureForm';
import * as FileSystem from 'expo-file-system';
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
// Function to fetch themes from the database
export async function fetchThemes() {
const { data, error } = await supabase.from('themes').select('name, details');
if (error) {
console.error('Error fetching themes:', error);
return [];
}
return data;
}
// Function to fetch rarities from the database
export async function fetchRarities() {
const { data, error } = await supabase.from('rarity').select('name, details');
if (error) {
console.error('Error fetching rarities:', error);
return [];
}
return data;
}
// Function to generate a figure using the edge function
export async function generateFigure(
formData: ExtendedFigureData,
themeName: string,
rarityName: string
) {
try {
// Convert image to base64 if it exists
let faceImageBase64 = null;
if (formData.characterImage) {
// For web
if (formData.characterImage.startsWith('data:')) {
faceImageBase64 = formData.characterImage.split(',')[1];
}
// For native
else {
const base64 = await FileSystem.readAsStringAsync(formData.characterImage, {
encoding: FileSystem.EncodingType.Base64,
});
faceImageBase64 = base64;
}
}
// Prepare the request payload
const payload = {
subject: formData.name,
theme: formData.style?.description || 'Dark Professional',
rarity: 'common', // Default rarity, can be customized
clothing_description: formData.characterDescription,
accessory1_description: formData.artifacts[0].description || 'Standard accessory',
accessory2_description: formData.artifacts[1].description || 'Standard accessory',
accessory3_description: formData.artifacts[2].description || 'Standard accessory',
face_image: faceImageBase64,
};
// Call the edge function
const { data, error } = await supabase.functions.invoke('figgos-generator', {
body: JSON.stringify(payload),
});
if (error) {
console.error('Error generating figure:', error);
throw new Error(error.message);
}
// Save the figure to the database
const { data: figureData, error: figureError } = await supabase
.from('figures')
.insert({
name: formData.name,
subject: formData.name,
image_url: data.image_url,
theme: themeName,
rarity: rarityName,
character_description: formData.characterDescription,
style_description: formData.style?.description,
accessory1_description: formData.artifacts[0].description,
accessory2_description: formData.artifacts[1].description,
accessory3_description: formData.artifacts[2].description,
is_public: isPublic,
user_id: (await supabase.auth.getUser()).data.user?.id,
})
.select()
.single();
if (figureError) {
console.error('Error saving figure:', figureError);
throw new Error(figureError.message);
}
return figureData;
} catch (error) {
console.error('Error in generateFigure:', error);
throw error;
}
}
```
### 2. Update the SidebarCreateFigureForm Component
Modify the form component to use the new figure generation function:
```typescript
// components/SidebarCreateFigureForm.tsx
// Add imports
import { useState, useEffect } from 'react';
import { fetchThemes, fetchRarities, generateFigure } from '~/utils/supabaseClient';
import { useAuth } from '~/utils/AuthContext';
import { router } from 'expo-router';
// Add state for themes and rarities
const [themes, setThemes] = useState([]);
const [rarities, setRarities] = useState([]);
const [selectedTheme, setSelectedTheme] = useState('');
const [selectedRarity, setSelectedRarity] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const { user } = useAuth();
// Add useEffect to fetch themes and rarities
useEffect(() => {
async function loadData() {
const themesData = await fetchThemes();
const raritiesData = await fetchRarities();
setThemes(themesData);
setRarities(raritiesData);
// Set defaults
if (themesData.length > 0) setSelectedTheme(themesData[0].name);
if (raritiesData.length > 0) setSelectedRarity(raritiesData[0].name);
}
loadData();
}, []);
// Update the handleSubmit function
const handleSubmit = async () => {
// Validate form
if (!formData.name.trim()) {
Alert.alert('Error', 'Please enter a name.');
return;
}
if (!formData.characterDescription.trim()) {
Alert.alert('Error', 'Please describe your character.');
return;
}
// Check if all three artifacts have descriptions
const artifactDescriptions = formData.artifacts.map((a) => a.description.trim());
if (artifactDescriptions.some((desc) => desc === '')) {
Alert.alert('Error', 'Please describe all three accessories.');
return;
}
if (!formData.style?.description.trim()) {
Alert.alert('Error', 'Please describe the style.');
return;
}
if (!user) {
Alert.alert('Error', 'You must be logged in to create a figure.');
return;
}
try {
setIsGenerating(true);
// Generate the figure
const figure = await generateFigure(formData, selectedTheme, selectedRarity);
// Show success message
Alert.alert('Success!', 'Your action figure has been created!', [
{
text: 'View My Shelf',
onPress: () => router.push('/myshelf'),
},
{
text: 'OK',
style: 'cancel',
},
]);
// Reset form
setFormData({
name: '',
characterDescription: '',
characterImage: null,
artifacts: [
{ description: '', image: null },
{ description: '', image: null },
{ description: '', image: null },
],
style: {
description: '',
},
});
} catch (error) {
console.error('Error creating figure:', error);
Alert.alert('Error', 'There was a problem creating your figure. Please try again.');
} finally {
setIsGenerating(false);
}
};
```
### 3. Add Theme and Rarity Selection to the Form
Add dropdowns to the form to allow users to select themes and rarities:
```jsx
{
/* Theme Selection */
}
<View style={styles.sectionContainer}>
<View style={styles.sectionHeader}>
<View style={styles.iconContainer}>
<FontAwesome name="image" size={20} color={theme.colors.text} />
</View>
<Text style={[styles.sectionTitle, { color: theme.colors.text }]}>Theme</Text>
</View>
<View
style={[
styles.pickerContainer,
{ borderColor: theme.colors.border, backgroundColor: theme.colors.input },
]}
>
<Picker
selectedValue={selectedTheme}
onValueChange={(itemValue) => setSelectedTheme(itemValue)}
style={{ color: theme.colors.text }}
>
{themes.map((themeItem) => (
<Picker.Item key={themeItem.name} label={themeItem.name} value={themeItem.name} />
))}
</Picker>
</View>
</View>;
{
/* Rarity Selection */
}
<View style={styles.sectionContainer}>
<View style={styles.sectionHeader}>
<View style={styles.iconContainer}>
<FontAwesome name="star" size={20} color={theme.colors.text} />
</View>
<Text style={[styles.sectionTitle, { color: theme.colors.text }]}>Rarity</Text>
</View>
<View
style={[
styles.pickerContainer,
{ borderColor: theme.colors.border, backgroundColor: theme.colors.input },
]}
>
<Picker
selectedValue={selectedRarity}
onValueChange={(itemValue) => setSelectedRarity(itemValue)}
style={{ color: theme.colors.text }}
>
{rarities.map((rarityItem) => (
<Picker.Item key={rarityItem.name} label={rarityItem.name} value={rarityItem.name} />
))}
</Picker>
</View>
</View>;
{
/* Visibility Selection */
}
<View style={styles.sectionContainer}>
<View style={styles.sectionHeader}>
<View style={styles.iconContainer}>
<FontAwesome name="globe" size={20} color={theme.colors.text} />
</View>
<Text style={[styles.sectionTitle, { color: theme.colors.text }]}>Visibility</Text>
</View>
<View
style={[
styles.switchContainer,
{ borderColor: theme.colors.border, backgroundColor: theme.colors.input },
]}
>
<Text style={{ color: theme.colors.text }}>Make this figure public</Text>
<Switch
value={isPublic}
onValueChange={setIsPublic}
trackColor={{ false: theme.colors.border, true: theme.colors.primary }}
thumbColor={isPublic ? theme.colors.card : '#f4f3f4'}
/>
</View>
</View>;
```
### 4. Add Loading State to the Submit Button
Update the submit button to show a loading indicator:
```jsx
<TouchableOpacity
style={[
styles.submitButton,
{
backgroundColor: isGenerating ? theme.colors.border : theme.colors.primary,
opacity: isGenerating ? 0.7 : 1,
},
]}
onPress={handleSubmit}
disabled={isGenerating}
>
{isGenerating ? (
<ActivityIndicator color="#fff" size="small" style={{ marginRight: 10 }} />
) : (
<FontAwesome name="check" size={20} color="#fff" style={{ marginRight: 10 }} />
)}
<Text style={styles.submitButtonText}>{isGenerating ? 'Creating...' : 'Create Figure'}</Text>
</TouchableOpacity>
```
## 5. Update the MyShelf Page
Enhance the MyShelf page to display the user's created figures:
```typescript
// app/(tabs)/myshelf.tsx
import { useEffect, useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { supabase } from '~/utils/supabaseClient';
import { useAuth } from '~/utils/AuthContext';
import { HorizontalFigureCard } from '~/components/HorizontalFigureCard';
export default function MyShelf() {
const [figures, setFigures] = useState([]);
const [loading, setLoading] = useState(true);
const { user } = useAuth();
useEffect(() => {
async function loadFigures() {
if (!user) return;
try {
setLoading(true);
const { data, error } = await supabase
.from('figures')
.select(
`
id,
name,
subject,
image_url,
theme,
rarity,
likes,
is_public
`
)
.eq('user_id', user.id)
.order('created_at', { ascending: false });
if (error) {
console.error('Error loading figures:', error);
return;
}
setFigures(data || []);
} catch (error) {
console.error('Error in loadFigures:', error);
} finally {
setLoading(false);
}
}
loadFigures();
}, [user]);
// Rest of the component...
}
```
## Testing Plan
1. **Unit Testing**:
- Test the `generateFigure` function with various inputs
- Validate form submission with different combinations of data
2. **Integration Testing**:
- Test the end-to-end flow from form submission to figure creation
- Verify that the figure appears in the user's shelf after creation
3. **Error Handling**:
- Test with invalid inputs to ensure proper error messages
- Test with network failures to ensure graceful degradation
## Deployment Steps
1. Deploy the updated code to your Expo project
2. Verify that the edge function is accessible and working
3. Monitor the first few figure creations to ensure everything works as expected
## Future Enhancements
1. **Progress Tracking**: Add a progress bar during figure generation
2. **Image Preview**: Show a preview of the generated image before saving
3. **Social Sharing**: Add ability to share figures directly to social media
4. **Figure Collections**: Allow users to organize figures into collections
5. **Figure Editing**: Allow users to edit their existing figures