frontend first version

This commit is contained in:
Ada
2026-01-20 21:11:04 -08:00
commit 7944b9f7ed
29 changed files with 16468 additions and 0 deletions

748
src/screens/VaultScreen.tsx Normal file
View File

@@ -0,0 +1,748 @@
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Modal,
TextInput,
SafeAreaView,
Animated,
} from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors';
import { VaultAsset, VaultAssetType } from '../types';
import BiometricModal from '../components/common/BiometricModal';
// Asset type configuration with nautical theme
const assetTypeConfig: Record<VaultAssetType, { icon: string; iconType: 'ionicons' | 'feather' | 'material' | 'fontawesome5'; label: string }> = {
game_account: { icon: 'gamepad', iconType: 'fontawesome5', label: 'Digital Account' },
private_key: { icon: 'key', iconType: 'fontawesome5', label: 'Secret Key' },
document: { icon: 'scroll', iconType: 'fontawesome5', label: 'Document' },
photo: { icon: 'image', iconType: 'ionicons', label: 'Sealed Photo' },
will: { icon: 'file-signature', iconType: 'fontawesome5', label: 'Testament' },
custom: { icon: 'gem', iconType: 'fontawesome5', label: 'Treasure' },
};
// Mock data
const initialAssets: VaultAsset[] = [
{
id: '1',
type: 'private_key',
label: 'ETH Main Wallet Key',
createdAt: new Date('2024-01-10'),
updatedAt: new Date('2024-01-10'),
isEncrypted: true,
},
{
id: '2',
type: 'game_account',
label: 'Steam Account Credentials',
createdAt: new Date('2024-01-08'),
updatedAt: new Date('2024-01-08'),
isEncrypted: true,
},
{
id: '3',
type: 'document',
label: 'Insurance Policy Scan',
createdAt: new Date('2024-01-05'),
updatedAt: new Date('2024-01-05'),
isEncrypted: true,
},
{
id: '4',
type: 'will',
label: 'Testament Draft v2',
createdAt: new Date('2024-01-02'),
updatedAt: new Date('2024-01-15'),
isEncrypted: true,
},
];
const renderAssetTypeIcon = (config: typeof assetTypeConfig[VaultAssetType], size: number, color: string) => {
switch (config.iconType) {
case 'ionicons':
return <Ionicons name={config.icon as any} size={size} color={color} />;
case 'feather':
return <Feather name={config.icon as any} size={size} color={color} />;
case 'material':
return <MaterialCommunityIcons name={config.icon as any} size={size} color={color} />;
case 'fontawesome5':
return <FontAwesome5 name={config.icon as any} size={size} color={color} />;
}
};
export default function VaultScreen() {
const [isUnlocked, setIsUnlocked] = useState(false);
const [showBiometric, setShowBiometric] = useState(false);
const [assets, setAssets] = useState<VaultAsset[]>(initialAssets);
const [showAddModal, setShowAddModal] = useState(false);
const [selectedType, setSelectedType] = useState<VaultAssetType>('custom');
const [newLabel, setNewLabel] = useState('');
const [showUploadSuccess, setShowUploadSuccess] = useState(false);
const [fadeAnim] = useState(new Animated.Value(0));
const [pulseAnim] = useState(new Animated.Value(1));
useEffect(() => {
if (!isUnlocked) {
const timer = setTimeout(() => {
setShowBiometric(true);
}, 500);
return () => clearTimeout(timer);
}
}, []);
useEffect(() => {
if (isUnlocked) {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 600,
useNativeDriver: true,
}).start();
}
}, [isUnlocked]);
useEffect(() => {
if (!isUnlocked) {
Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.05,
duration: 1500,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
}),
])
).start();
}
}, [isUnlocked]);
const handleUnlock = () => {
setShowBiometric(false);
setIsUnlocked(true);
};
const handleAddAsset = () => {
if (!newLabel.trim()) return;
const newAsset: VaultAsset = {
id: Date.now().toString(),
type: selectedType,
label: newLabel,
createdAt: new Date(),
updatedAt: new Date(),
isEncrypted: true,
};
setAssets([newAsset, ...assets]);
setNewLabel('');
setSelectedType('custom');
setShowAddModal(false);
setShowUploadSuccess(true);
setTimeout(() => setShowUploadSuccess(false), 2500);
};
const formatDate = (date: Date) => {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
};
// Lock screen
if (!isUnlocked) {
return (
<View style={styles.lockContainer}>
<LinearGradient
colors={[colors.vault.backgroundGradientStart, colors.vault.backgroundGradientEnd]}
style={styles.lockGradient}
>
<SafeAreaView style={styles.lockSafeArea}>
<View style={styles.lockContent}>
<Animated.View style={[styles.lockIconContainer, { transform: [{ scale: pulseAnim }] }]}>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.deepTeal]}
style={styles.lockIconGradient}
>
<MaterialCommunityIcons name="treasure-chest" size={64} color={colors.vault.primary} />
</LinearGradient>
</Animated.View>
<Text style={styles.lockTitle}>THE DEEP VAULT</Text>
<Text style={styles.lockSubtitle}>Where treasures rest in silence</Text>
<View style={styles.waveContainer}>
<MaterialCommunityIcons name="waves" size={48} color={colors.vault.secondary} style={{ opacity: 0.3 }} />
</View>
<TouchableOpacity
style={styles.unlockButton}
onPress={() => setShowBiometric(true)}
activeOpacity={0.8}
>
<LinearGradient
colors={[colors.vault.primary, colors.vault.secondary]}
style={styles.unlockButtonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Ionicons name="finger-print" size={20} color={colors.vault.background} />
<Text style={styles.unlockButtonText}>Captain's Verification</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</SafeAreaView>
</LinearGradient>
<BiometricModal
visible={showBiometric}
onSuccess={handleUnlock}
onCancel={() => setShowBiometric(false)}
title="Enter the Vault"
message="Verify your identity to access your treasures"
isDark
/>
</View>
);
}
return (
<View style={styles.container}>
<LinearGradient
colors={[colors.vault.backgroundGradientStart, colors.vault.backgroundGradientEnd]}
style={styles.gradient}
>
<SafeAreaView style={styles.safeArea}>
<Animated.View style={[styles.content, { opacity: fadeAnim }]}>
{/* Header */}
<View style={styles.header}>
<View style={styles.headerTop}>
<View style={styles.headerTitleRow}>
<MaterialCommunityIcons name="treasure-chest" size={26} color={colors.vault.primary} />
<Text style={styles.title}>THE VAULT</Text>
</View>
<View style={styles.securityBadge}>
<Ionicons name="shield-checkmark" size={14} color={colors.vault.success} />
<Text style={styles.securityText}>SEALED</Text>
</View>
</View>
<Text style={styles.subtitle}>
{assets.length} treasures · Encrypted at rest
</Text>
</View>
{/* Asset List */}
<ScrollView
style={styles.assetList}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.assetListContent}
>
{assets.map((asset) => {
const config = assetTypeConfig[asset.type];
return (
<TouchableOpacity
key={asset.id}
style={styles.assetCard}
activeOpacity={0.7}
>
<View style={styles.assetIconContainer}>
{renderAssetTypeIcon(config, 22, colors.vault.primary)}
</View>
<View style={styles.assetInfo}>
<Text style={styles.assetType}>{config.label}</Text>
<Text style={styles.assetLabel}>{asset.label}</Text>
<View style={styles.assetMetaRow}>
<Feather name="clock" size={10} color={colors.vault.textSecondary} />
<Text style={styles.assetMeta}>Sealed {formatDate(asset.createdAt)}</Text>
</View>
</View>
<View style={styles.encryptedBadge}>
<MaterialCommunityIcons name="lock" size={16} color="#fff" />
</View>
</TouchableOpacity>
);
})}
<View style={{ height: 100 }} />
</ScrollView>
{/* Add Button */}
<TouchableOpacity
style={styles.addButton}
onPress={() => setShowAddModal(true)}
activeOpacity={0.9}
>
<LinearGradient
colors={[colors.vault.primary, colors.vault.secondary]}
style={styles.addButtonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<FontAwesome5 name="plus" size={16} color={colors.vault.background} />
<Text style={styles.addButtonText}>Add Treasure</Text>
</LinearGradient>
</TouchableOpacity>
{/* Upload Success Toast */}
{showUploadSuccess && (
<Animated.View style={styles.successToast}>
<Ionicons name="checkmark-circle" size={20} color="#fff" />
<Text style={styles.successText}>Treasure sealed successfully</Text>
</Animated.View>
)}
</Animated.View>
</SafeAreaView>
</LinearGradient>
{/* Add Modal */}
<Modal
visible={showAddModal}
animationType="slide"
transparent
onRequestClose={() => setShowAddModal(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<View style={styles.modalHandle} />
<View style={styles.modalHeader}>
<FontAwesome5 name="gem" size={22} color={colors.nautical.teal} />
<Text style={styles.modalTitle}>Add New Treasure</Text>
</View>
<Text style={styles.modalLabel}>TREASURE TYPE</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.typeScroll}
contentContainerStyle={styles.typeScrollContent}
>
{(Object.keys(assetTypeConfig) as VaultAssetType[]).map((type) => {
const config = assetTypeConfig[type];
const isSelected = selectedType === type;
return (
<TouchableOpacity
key={type}
style={[styles.typeButton, isSelected && styles.typeButtonActive]}
onPress={() => setSelectedType(type)}
>
<View style={[styles.typeIconContainer, isSelected && styles.typeIconContainerActive]}>
{renderAssetTypeIcon(config, 22, isSelected ? colors.nautical.teal : colors.nautical.sage)}
</View>
<Text style={[styles.typeButtonLabel, isSelected && styles.typeButtonLabelActive]}>
{config.label}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
<Text style={styles.modalLabel}>TREASURE NAME</Text>
<TextInput
style={styles.input}
placeholder="e.g., Main wallet mnemonic"
placeholderTextColor={colors.nautical.sage}
value={newLabel}
onChangeText={setNewLabel}
/>
<View style={styles.encryptionNote}>
<MaterialCommunityIcons name="anchor" size={16} color={colors.nautical.teal} />
<Text style={styles.encryptionNoteText}>
Your treasure will be encrypted and sealed. Original data will be securely destroyed.
</Text>
</View>
<View style={styles.modalButtons}>
<TouchableOpacity
style={styles.cancelButton}
onPress={() => {
setShowAddModal(false);
setNewLabel('');
}}
>
<Text style={styles.cancelButtonText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.confirmButton}
onPress={handleAddAsset}
>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.seafoam]}
style={styles.confirmButtonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<MaterialCommunityIcons name="lock" size={18} color="#fff" />
<Text style={styles.confirmButtonText}>Seal Treasure</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
gradient: {
flex: 1,
},
safeArea: {
flex: 1,
},
content: {
flex: 1,
},
lockContainer: {
flex: 1,
},
lockGradient: {
flex: 1,
},
lockSafeArea: {
flex: 1,
},
lockContent: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: spacing.xl,
},
lockIconContainer: {
marginBottom: spacing.lg,
},
lockIconGradient: {
width: 130,
height: 130,
borderRadius: 65,
justifyContent: 'center',
alignItems: 'center',
...shadows.glow,
},
lockTitle: {
fontSize: typography.fontSize.xxl,
fontWeight: '700',
color: colors.vault.text,
letterSpacing: typography.letterSpacing.widest,
marginBottom: spacing.sm,
fontFamily: typography.fontFamily.serif,
},
lockSubtitle: {
fontSize: typography.fontSize.base,
color: colors.vault.textSecondary,
marginBottom: spacing.xl,
textAlign: 'center',
fontStyle: 'italic',
},
waveContainer: {
marginBottom: spacing.xl,
},
unlockButton: {
borderRadius: borderRadius.lg,
overflow: 'hidden',
},
unlockButtonGradient: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.md,
paddingHorizontal: spacing.xl,
gap: spacing.sm,
},
unlockButtonText: {
fontSize: typography.fontSize.base,
color: colors.vault.background,
fontWeight: '600',
},
header: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.lg,
paddingBottom: spacing.md,
},
headerTop: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.xs,
},
headerTitleRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
title: {
fontSize: typography.fontSize.xl,
fontWeight: '700',
color: colors.vault.text,
letterSpacing: typography.letterSpacing.wider,
fontFamily: typography.fontFamily.serif,
},
securityBadge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: `${colors.vault.success}20`,
paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs,
borderRadius: borderRadius.full,
gap: spacing.xs,
},
securityText: {
fontSize: typography.fontSize.xs,
color: colors.vault.success,
fontWeight: '700',
letterSpacing: 1,
},
subtitle: {
fontSize: typography.fontSize.sm,
color: colors.vault.textSecondary,
},
assetList: {
flex: 1,
},
assetListContent: {
padding: spacing.lg,
paddingTop: spacing.sm,
},
assetCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.vault.cardBackground,
borderRadius: borderRadius.xl,
padding: spacing.base,
marginBottom: spacing.md,
borderWidth: 1,
borderColor: colors.vault.cardBorder,
},
assetIconContainer: {
width: 52,
height: 52,
borderRadius: 26,
backgroundColor: `${colors.vault.primary}15`,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.base,
},
assetInfo: {
flex: 1,
},
assetType: {
fontSize: typography.fontSize.xs,
color: colors.vault.textSecondary,
textTransform: 'uppercase',
letterSpacing: 1,
marginBottom: 2,
fontWeight: '600',
},
assetLabel: {
fontSize: typography.fontSize.base,
color: colors.vault.text,
fontWeight: '600',
marginBottom: 4,
},
assetMetaRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
},
assetMeta: {
fontSize: typography.fontSize.xs,
color: colors.vault.textSecondary,
},
encryptedBadge: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: colors.vault.success,
justifyContent: 'center',
alignItems: 'center',
},
addButton: {
position: 'absolute',
bottom: 100,
left: spacing.lg,
right: spacing.lg,
borderRadius: borderRadius.lg,
overflow: 'hidden',
},
addButtonGradient: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: spacing.md,
gap: spacing.sm,
},
addButtonText: {
fontSize: typography.fontSize.base,
color: colors.vault.background,
fontWeight: '700',
},
successToast: {
position: 'absolute',
bottom: 170,
left: spacing.lg,
right: spacing.lg,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.vault.success,
paddingVertical: spacing.md,
borderRadius: borderRadius.lg,
gap: spacing.sm,
},
successText: {
fontSize: typography.fontSize.base,
color: '#fff',
fontWeight: '600',
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(26, 58, 74, 0.8)',
justifyContent: 'flex-end',
},
modalContent: {
backgroundColor: colors.nautical.cream,
borderTopLeftRadius: borderRadius.xxl,
borderTopRightRadius: borderRadius.xxl,
padding: spacing.lg,
paddingBottom: spacing.xxl,
},
modalHandle: {
width: 40,
height: 4,
backgroundColor: colors.nautical.lightMint,
borderRadius: 2,
alignSelf: 'center',
marginBottom: spacing.lg,
},
modalHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
marginBottom: spacing.lg,
},
modalTitle: {
fontSize: typography.fontSize.lg,
fontWeight: '600',
color: colors.nautical.navy,
},
modalLabel: {
fontSize: typography.fontSize.xs,
color: colors.nautical.sage,
marginBottom: spacing.sm,
letterSpacing: typography.letterSpacing.wider,
fontWeight: '600',
},
typeScroll: {
marginBottom: spacing.lg,
},
typeScrollContent: {
gap: spacing.sm,
},
typeButton: {
alignItems: 'center',
paddingVertical: spacing.sm,
paddingHorizontal: spacing.base,
borderRadius: borderRadius.lg,
backgroundColor: colors.nautical.paleAqua,
borderWidth: 2,
borderColor: 'transparent',
minWidth: 100,
},
typeButtonActive: {
borderColor: colors.nautical.teal,
backgroundColor: colors.nautical.lightMint,
},
typeIconContainer: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: colors.nautical.cream,
justifyContent: 'center',
alignItems: 'center',
marginBottom: spacing.xs,
},
typeIconContainerActive: {
backgroundColor: `${colors.nautical.teal}15`,
},
typeButtonLabel: {
fontSize: typography.fontSize.xs,
color: colors.nautical.sage,
textAlign: 'center',
fontWeight: '500',
},
typeButtonLabelActive: {
color: colors.nautical.teal,
fontWeight: '600',
},
input: {
backgroundColor: colors.nautical.paleAqua,
borderRadius: borderRadius.lg,
padding: spacing.base,
fontSize: typography.fontSize.base,
color: colors.nautical.navy,
marginBottom: spacing.md,
borderWidth: 1,
borderColor: colors.nautical.lightMint,
},
encryptionNote: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.nautical.lightMint,
borderRadius: borderRadius.lg,
padding: spacing.md,
marginBottom: spacing.lg,
gap: spacing.sm,
},
encryptionNoteText: {
flex: 1,
fontSize: typography.fontSize.sm,
color: colors.nautical.teal,
lineHeight: typography.fontSize.sm * 1.4,
},
modalButtons: {
flexDirection: 'row',
gap: spacing.md,
},
cancelButton: {
flex: 1,
paddingVertical: spacing.md,
borderRadius: borderRadius.lg,
backgroundColor: colors.nautical.paleAqua,
alignItems: 'center',
borderWidth: 1,
borderColor: colors.nautical.lightMint,
},
cancelButtonText: {
fontSize: typography.fontSize.base,
color: colors.nautical.sage,
fontWeight: '600',
},
confirmButton: {
flex: 1,
borderRadius: borderRadius.lg,
overflow: 'hidden',
},
confirmButtonGradient: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: spacing.md,
gap: spacing.sm,
},
confirmButtonText: {
fontSize: typography.fontSize.base,
color: '#fff',
fontWeight: '600',
},
});