mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
Projects included: - maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing) - manacore (Expo mobile + SvelteKit web + Astro landing) - manadeck (NestJS backend + Expo mobile + SvelteKit web) - memoro (Expo mobile + SvelteKit web + Astro landing) This commit preserves the current state before monorepo restructuring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
246 lines
6.3 KiB
TypeScript
246 lines
6.3 KiB
TypeScript
/**
|
|
* Example: How to integrate credit system into deck creation
|
|
*
|
|
* This is a reference implementation showing how to:
|
|
* 1. Handle credit errors when creating a deck
|
|
* 2. Show insufficient credits modal
|
|
* 3. Display user's credit balance
|
|
*
|
|
* Copy this pattern to your actual deck creation screens.
|
|
*/
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { View, Text, TextInput, TouchableOpacity, Alert, StyleSheet } from 'react-native';
|
|
import { post } from '../utils/apiClient';
|
|
import { InsufficientCreditsModal } from '../components/InsufficientCreditsModal';
|
|
import { useInsufficientCredits } from '../hooks/useInsufficientCredits';
|
|
import { creditService } from '../services/creditService';
|
|
|
|
const BASE_API_URL = process.env.EXPO_PUBLIC_API_URL || 'https://manadeck-backend-111768794939.europe-west3.run.app';
|
|
|
|
export function DeckCreationExample() {
|
|
const [deckName, setDeckName] = useState('');
|
|
const [deckDescription, setDeckDescription] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [creditBalance, setCreditBalance] = useState<number>(0);
|
|
|
|
// Hook to manage insufficient credits modal
|
|
const insufficientCredits = useInsufficientCredits();
|
|
|
|
// Load credit balance on mount
|
|
useEffect(() => {
|
|
loadCreditBalance();
|
|
}, []);
|
|
|
|
const loadCreditBalance = async () => {
|
|
try {
|
|
const balance = await creditService.getBalance();
|
|
setCreditBalance(balance);
|
|
} catch (error) {
|
|
console.error('Failed to load credit balance:', error);
|
|
}
|
|
};
|
|
|
|
const handleCreateDeck = async () => {
|
|
if (!deckName.trim()) {
|
|
Alert.alert('Error', 'Please enter a deck name');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
|
|
try {
|
|
const response = await post(`${BASE_API_URL}/v1/api/decks`, {
|
|
name: deckName,
|
|
description: deckDescription,
|
|
});
|
|
|
|
// Success!
|
|
Alert.alert(
|
|
'Success',
|
|
`Deck created successfully! ${response.creditsUsed} mana used.`,
|
|
[
|
|
{
|
|
text: 'OK',
|
|
onPress: () => {
|
|
// Refresh credit balance
|
|
loadCreditBalance();
|
|
// Reset form
|
|
setDeckName('');
|
|
setDeckDescription('');
|
|
},
|
|
},
|
|
]
|
|
);
|
|
} catch (error: any) {
|
|
// Check if this is a credit error
|
|
const isCreditError = insufficientCredits.handleCreditError(error);
|
|
|
|
if (!isCreditError) {
|
|
// Handle other errors
|
|
console.error('Deck creation error:', error);
|
|
Alert.alert(
|
|
'Error',
|
|
error.message || 'Failed to create deck. Please try again.'
|
|
);
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handlePurchaseCredits = () => {
|
|
// Close modal
|
|
insufficientCredits.hideInsufficientCredits();
|
|
|
|
// Navigate to purchase screen or open purchase flow
|
|
Alert.alert('Purchase Credits', 'This would navigate to the credit purchase screen');
|
|
// In real implementation:
|
|
// navigation.navigate('PurchaseCredits');
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Credit Balance Display */}
|
|
<View style={styles.creditBanner}>
|
|
<Text style={styles.creditLabel}>Your Mana:</Text>
|
|
<Text style={styles.creditValue}>{creditBalance} ⚡</Text>
|
|
</View>
|
|
|
|
{/* Form */}
|
|
<View style={styles.form}>
|
|
<Text style={styles.label}>Deck Name</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={deckName}
|
|
onChangeText={setDeckName}
|
|
placeholder="Enter deck name"
|
|
editable={!loading}
|
|
/>
|
|
|
|
<Text style={styles.label}>Description (Optional)</Text>
|
|
<TextInput
|
|
style={[styles.input, styles.textArea]}
|
|
value={deckDescription}
|
|
onChangeText={setDeckDescription}
|
|
placeholder="Enter deck description"
|
|
multiline
|
|
numberOfLines={4}
|
|
editable={!loading}
|
|
/>
|
|
|
|
{/* Cost Info */}
|
|
<View style={styles.costInfo}>
|
|
<Text style={styles.costLabel}>Cost:</Text>
|
|
<Text style={styles.costValue}>10 mana ⚡</Text>
|
|
</View>
|
|
|
|
{/* Create Button */}
|
|
<TouchableOpacity
|
|
style={[styles.button, loading && styles.buttonDisabled]}
|
|
onPress={handleCreateDeck}
|
|
disabled={loading}
|
|
>
|
|
<Text style={styles.buttonText}>
|
|
{loading ? 'Creating...' : 'Create Deck'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Insufficient Credits Modal */}
|
|
<InsufficientCreditsModal
|
|
visible={insufficientCredits.visible}
|
|
requiredCredits={insufficientCredits.requiredCredits}
|
|
availableCredits={insufficientCredits.availableCredits}
|
|
operation={insufficientCredits.operation}
|
|
onClose={insufficientCredits.hideInsufficientCredits}
|
|
onPurchase={handlePurchaseCredits}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
padding: 16,
|
|
backgroundColor: '#f5f5f5',
|
|
},
|
|
creditBanner: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
backgroundColor: '#3b82f6',
|
|
padding: 16,
|
|
borderRadius: 12,
|
|
marginBottom: 20,
|
|
},
|
|
creditLabel: {
|
|
fontSize: 16,
|
|
color: 'white',
|
|
fontWeight: '500',
|
|
},
|
|
creditValue: {
|
|
fontSize: 20,
|
|
color: 'white',
|
|
fontWeight: '700',
|
|
},
|
|
form: {
|
|
backgroundColor: 'white',
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
},
|
|
label: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: '#374151',
|
|
marginBottom: 8,
|
|
marginTop: 12,
|
|
},
|
|
input: {
|
|
borderWidth: 1,
|
|
borderColor: '#d1d5db',
|
|
borderRadius: 8,
|
|
padding: 12,
|
|
fontSize: 16,
|
|
backgroundColor: 'white',
|
|
},
|
|
textArea: {
|
|
height: 100,
|
|
textAlignVertical: 'top',
|
|
},
|
|
costInfo: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
backgroundColor: '#f3f4f6',
|
|
padding: 12,
|
|
borderRadius: 8,
|
|
marginTop: 16,
|
|
},
|
|
costLabel: {
|
|
fontSize: 14,
|
|
color: '#6b7280',
|
|
fontWeight: '500',
|
|
},
|
|
costValue: {
|
|
fontSize: 16,
|
|
color: '#1f2937',
|
|
fontWeight: '700',
|
|
},
|
|
button: {
|
|
backgroundColor: '#3b82f6',
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
marginTop: 20,
|
|
},
|
|
buttonDisabled: {
|
|
backgroundColor: '#9ca3af',
|
|
},
|
|
buttonText: {
|
|
color: 'white',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
});
|