mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 21:39:39 +02:00
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>
12 KiB
12 KiB
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:
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:
// 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:
// 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:
{
/* 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:
<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:
// 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
-
Unit Testing:
- Test the
generateFigurefunction with various inputs - Validate form submission with different combinations of data
- Test the
-
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
-
Error Handling:
- Test with invalid inputs to ensure proper error messages
- Test with network failures to ensure graceful degradation
Deployment Steps
- Deploy the updated code to your Expo project
- Verify that the edge function is accessible and working
- Monitor the first few figure creations to ensure everything works as expected
Future Enhancements
- Progress Tracking: Add a progress bar during figure generation
- Image Preview: Show a preview of the generated image before saving
- Social Sharing: Add ability to share figures directly to social media
- Figure Collections: Allow users to organize figures into collections
- Figure Editing: Allow users to edit their existing figures