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

View File

@@ -0,0 +1,238 @@
import React, { useEffect, useState } from 'react';
import {
View,
Text,
Modal,
StyleSheet,
TouchableOpacity,
Animated,
} from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
import { colors, borderRadius, spacing, typography, shadows } from '../../theme/colors';
interface BiometricModalProps {
visible: boolean;
onSuccess: () => void;
onCancel: () => void;
title?: string;
message?: string;
isDark?: boolean;
}
export default function BiometricModal({
visible,
onSuccess,
onCancel,
title = 'Captain\'s Verification',
message = 'Verify your identity to continue',
isDark = false,
}: BiometricModalProps) {
const [scanAnimation] = useState(new Animated.Value(0));
const [pulseAnimation] = useState(new Animated.Value(1));
const [isScanning, setIsScanning] = useState(false);
useEffect(() => {
if (visible) {
setIsScanning(false);
scanAnimation.setValue(0);
// Pulse animation
Animated.loop(
Animated.sequence([
Animated.timing(pulseAnimation, {
toValue: 1.08,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(pulseAnimation, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
])
).start();
}
}, [visible]);
const handleScan = () => {
setIsScanning(true);
Animated.loop(
Animated.sequence([
Animated.timing(scanAnimation, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(scanAnimation, {
toValue: 0,
duration: 800,
useNativeDriver: true,
}),
]),
{ iterations: 2 }
).start(() => {
setTimeout(() => {
onSuccess();
}, 300);
});
};
const backgroundColor = isDark ? colors.vault.cardBackground : colors.white;
const textColor = isDark ? colors.vault.text : colors.nautical.navy;
const accentColor = isDark ? colors.vault.primary : colors.nautical.teal;
const accentGradient: [string, string] = isDark
? [colors.vault.primary, colors.vault.secondary]
: [colors.nautical.teal, colors.nautical.seafoam];
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={onCancel}
>
<View style={styles.overlay}>
<View style={[styles.container, { backgroundColor }, shadows.medium]}>
{/* Ship wheel watermark */}
<View style={styles.watermark}>
<MaterialCommunityIcons
name="ship-wheel"
size={150}
color={isDark ? colors.vault.primary : colors.nautical.lightMint}
style={{ opacity: 0.15 }}
/>
</View>
<Text style={[styles.title, { color: textColor }]}>{title}</Text>
<Text style={[styles.message, { color: isDark ? colors.vault.textSecondary : colors.nautical.sage }]}>
{message}
</Text>
<TouchableOpacity
style={styles.fingerprintButton}
onPress={handleScan}
activeOpacity={0.8}
>
<Animated.View
style={[
styles.fingerprintOuter,
{
backgroundColor: `${accentColor}15`,
transform: [{ scale: pulseAnimation }],
},
]}
/>
<Animated.View
style={[
styles.fingerprintContainer,
{
opacity: scanAnimation.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.7],
}),
transform: [
{
scale: scanAnimation.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.05],
}),
},
],
},
]}
>
<LinearGradient
colors={accentGradient}
style={styles.fingerprintGradient}
>
<Ionicons
name={isScanning ? "finger-print" : "finger-print-outline"}
size={48}
color="#fff"
/>
</LinearGradient>
</Animated.View>
<Text style={[styles.scanText, { color: accentColor }]}>
{isScanning ? 'Verifying...' : 'Tap to Verify'}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<Text style={[styles.cancelText, { color: isDark ? colors.vault.textSecondary : colors.nautical.sage }]}>
Cancel
</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(26, 58, 74, 0.7)',
justifyContent: 'center',
alignItems: 'center',
padding: spacing.lg,
},
container: {
width: '100%',
borderRadius: borderRadius.xxl,
padding: spacing.xl,
alignItems: 'center',
overflow: 'hidden',
},
watermark: {
position: 'absolute',
top: -30,
right: -30,
},
title: {
fontSize: typography.fontSize.lg,
fontWeight: '600',
marginBottom: spacing.sm,
textAlign: 'center',
fontFamily: typography.fontFamily.serif,
},
message: {
fontSize: typography.fontSize.base,
textAlign: 'center',
marginBottom: spacing.xl,
lineHeight: typography.fontSize.base * 1.5,
},
fingerprintButton: {
alignItems: 'center',
marginBottom: spacing.lg,
},
fingerprintOuter: {
position: 'absolute',
width: 150,
height: 150,
borderRadius: 75,
},
fingerprintContainer: {
marginBottom: spacing.md,
},
fingerprintGradient: {
width: 110,
height: 110,
borderRadius: 55,
justifyContent: 'center',
alignItems: 'center',
},
scanText: {
fontSize: typography.fontSize.base,
fontWeight: '600',
},
cancelButton: {
paddingVertical: spacing.md,
paddingHorizontal: spacing.xl,
},
cancelText: {
fontSize: typography.fontSize.base,
fontWeight: '500',
},
});

View File

@@ -0,0 +1,169 @@
import React from 'react';
import { ViewStyle, TextStyle } from 'react-native';
import { Feather, Ionicons, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
interface IconProps {
name: string;
size?: number;
color?: string;
style?: ViewStyle | TextStyle;
}
// Icon component with unified interface
export const Icon: React.FC<IconProps> = ({
name,
size = 24,
color = '#000',
style
}) => {
// Map custom names to actual icon library icons
switch (name) {
// Tab icons
case 'flow':
return <Ionicons name="water-outline" size={size} color={color} style={style} />;
case 'vault':
return <MaterialCommunityIcons name="safe-square-outline" size={size} color={color} style={style} />;
case 'sentinel':
return <Ionicons name="shield-checkmark-outline" size={size} color={color} style={style} />;
case 'heritage':
return <MaterialCommunityIcons name="file-document-outline" size={size} color={color} style={style} />;
case 'me':
return <Feather name="user" size={size} color={color} style={style} />;
// Asset type icons
case 'game':
return <Ionicons name="game-controller-outline" size={size} color={color} style={style} />;
case 'key':
return <Feather name="key" size={size} color={color} style={style} />;
case 'document':
return <Ionicons name="document-text-outline" size={size} color={color} style={style} />;
case 'photo':
return <Ionicons name="image-outline" size={size} color={color} style={style} />;
case 'will':
return <MaterialCommunityIcons name="script-text-outline" size={size} color={color} style={style} />;
case 'custom':
return <Feather name="package" size={size} color={color} style={style} />;
// Action icons
case 'add':
return <Feather name="plus" size={size} color={color} style={style} />;
case 'check':
return <Feather name="check" size={size} color={color} style={style} />;
case 'lock':
return <Feather name="lock" size={size} color={color} style={style} />;
case 'unlock':
return <Feather name="unlock" size={size} color={color} style={style} />;
case 'shield':
return <Ionicons name="shield-checkmark" size={size} color={color} style={style} />;
case 'fingerprint':
return <Ionicons name="finger-print" size={size} color={color} style={style} />;
case 'encrypted':
return <MaterialCommunityIcons name="lock-check" size={size} color={color} style={style} />;
// Input type icons
case 'text':
return <MaterialCommunityIcons name="format-text" size={size} color={color} style={style} />;
case 'voice':
return <Ionicons name="mic-outline" size={size} color={color} style={style} />;
case 'image':
return <Ionicons name="camera-outline" size={size} color={color} style={style} />;
// Status icons
case 'statusnormal':
return <Ionicons name="checkmark-circle" size={size} color={color} style={style} />;
case 'statuswarning':
return <Ionicons name="warning" size={size} color={color} style={style} />;
case 'statusreleasing':
return <Ionicons name="alert-circle" size={size} color={color} style={style} />;
// Navigation icons
case 'arrowRight':
return <Feather name="arrow-right" size={size} color={color} style={style} />;
case 'arrowUp':
return <Feather name="arrow-up-right" size={size} color={color} style={style} />;
case 'chevronRight':
return <Feather name="chevron-right" size={size} color={color} style={style} />;
case 'close':
return <Feather name="x" size={size} color={color} style={style} />;
// Other icons
case 'heartbeat':
return <MaterialCommunityIcons name="heart-pulse" size={size} color={color} style={style} />;
case 'warningSign':
return <Ionicons name="warning-outline" size={size} color={color} style={style} />;
case 'info':
return <Feather name="info" size={size} color={color} style={style} />;
case 'settings':
return <Feather name="settings" size={size} color={color} style={style} />;
case 'clock':
return <Feather name="clock" size={size} color={color} style={style} />;
case 'calendar':
return <Feather name="calendar" size={size} color={color} style={style} />;
case 'link':
return <Feather name="external-link" size={size} color={color} style={style} />;
case 'github':
return <Feather name="github" size={size} color={color} style={style} />;
case 'mail':
return <Feather name="mail" size={size} color={color} style={style} />;
case 'privacy':
return <Feather name="eye-off" size={size} color={color} style={style} />;
case 'terms':
return <MaterialCommunityIcons name="file-sign" size={size} color={color} style={style} />;
case 'wave':
return <MaterialCommunityIcons name="wave" size={size} color={color} style={style} />;
case 'palm':
return <FontAwesome5 name="tree" size={size} color={color} style={style} />;
case 'sun':
return <Feather name="sun" size={size} color={color} style={style} />;
case 'star':
return <Feather name="star" size={size} color={color} style={style} />;
case 'heart':
return <Feather name="heart" size={size} color={color} style={style} />;
case 'users':
return <Feather name="users" size={size} color={color} style={style} />;
case 'gift':
return <Feather name="gift" size={size} color={color} style={style} />;
case 'zap':
return <Feather name="zap" size={size} color={color} style={style} />;
default:
return <Feather name="circle" size={size} color={color} style={style} />;
}
};
// Predefined icon components for common use cases
export const GameIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="game" {...props} />
);
export const KeyIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="key" {...props} />
);
export const DocumentIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="document" {...props} />
);
export const PhotoIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="photo" {...props} />
);
export const WillIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="will" {...props} />
);
export const CustomIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="custom" {...props} />
);
export const CheckIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="check" {...props} />
);
export const LockIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="lock" {...props} />
);
export const ShieldIcon = (props: Omit<IconProps, 'name'>) => (
<Icon name="shield" {...props} />
);

View File

@@ -0,0 +1,250 @@
import React, { useEffect, useRef } from 'react';
import { View, Text, Animated, StyleSheet, Modal } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { MaterialCommunityIcons, Ionicons, FontAwesome5 } from '@expo/vector-icons';
import { colors, typography, spacing, borderRadius } from '../../theme/colors';
interface VaultDoorAnimationProps {
visible: boolean;
onComplete: () => void;
}
export default function VaultDoorAnimation({
visible,
onComplete,
}: VaultDoorAnimationProps) {
const doorLeft = useRef(new Animated.Value(0)).current;
const doorRight = useRef(new Animated.Value(0)).current;
const lockRotation = useRef(new Animated.Value(0)).current;
const lockScale = useRef(new Animated.Value(1)).current;
const fadeOut = useRef(new Animated.Value(1)).current;
const checkScale = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (visible) {
// Reset animations
doorLeft.setValue(0);
doorRight.setValue(0);
lockRotation.setValue(0);
lockScale.setValue(1);
fadeOut.setValue(1);
checkScale.setValue(0);
// Play closing animation sequence
Animated.sequence([
// Doors slide in
Animated.parallel([
Animated.timing(doorLeft, {
toValue: 1,
duration: 600,
useNativeDriver: true,
}),
Animated.timing(doorRight, {
toValue: 1,
duration: 600,
useNativeDriver: true,
}),
]),
// Lock rotates and pulses
Animated.parallel([
Animated.timing(lockRotation, {
toValue: 1,
duration: 400,
useNativeDriver: true,
}),
Animated.sequence([
Animated.timing(lockScale, {
toValue: 1.2,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(lockScale, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
]),
]),
// Show checkmark
Animated.spring(checkScale, {
toValue: 1,
friction: 5,
useNativeDriver: true,
}),
// Pause
Animated.delay(500),
// Fade out
Animated.timing(fadeOut, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}),
]).start(() => {
onComplete();
});
}
}, [visible]);
if (!visible) return null;
return (
<Modal transparent visible={visible} animationType="fade">
<Animated.View style={[styles.container, { opacity: fadeOut }]}>
<View style={styles.doorContainer}>
<Animated.View
style={[
styles.door,
styles.doorLeft,
{
transform: [
{
translateX: doorLeft.interpolate({
inputRange: [0, 1],
outputRange: [-200, 0],
}),
},
],
},
]}
>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.deepTeal]}
style={styles.doorGradient}
>
<FontAwesome5 name="anchor" size={32} color={colors.vault.primary} style={{ opacity: 0.3 }} />
</LinearGradient>
</Animated.View>
<Animated.View
style={[
styles.door,
styles.doorRight,
{
transform: [
{
translateX: doorRight.interpolate({
inputRange: [0, 1],
outputRange: [200, 0],
}),
},
],
},
]}
>
<LinearGradient
colors={[colors.nautical.deepTeal, colors.nautical.teal]}
style={styles.doorGradient}
>
<FontAwesome5 name="anchor" size={32} color={colors.vault.primary} style={{ opacity: 0.3 }} />
</LinearGradient>
</Animated.View>
<Animated.View
style={[
styles.lockContainer,
{
transform: [
{
rotate: lockRotation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
}),
},
{ scale: lockScale },
],
},
]}
>
<LinearGradient
colors={[colors.vault.primary, colors.vault.secondary]}
style={styles.lockGradient}
>
<MaterialCommunityIcons name="lock" size={28} color={colors.vault.background} />
</LinearGradient>
</Animated.View>
</View>
<Animated.View
style={[
styles.checkContainer,
{
transform: [{ scale: checkScale }],
opacity: checkScale,
},
]}
>
<Ionicons name="checkmark-circle" size={52} color={colors.vault.success} />
</Animated.View>
<Text style={styles.statusText}>Treasure Sealed</Text>
<Text style={styles.subText}>Your legacy is now protected</Text>
</Animated.View>
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'rgba(26, 58, 74, 0.98)',
justifyContent: 'center',
alignItems: 'center',
},
doorContainer: {
width: 200,
height: 200,
position: 'relative',
overflow: 'hidden',
borderRadius: borderRadius.xl,
marginBottom: spacing.lg,
},
door: {
position: 'absolute',
width: 100,
height: 200,
},
doorLeft: {
left: 0,
},
doorRight: {
right: 0,
},
doorGradient: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: colors.vault.primary,
},
lockContainer: {
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -32,
marginLeft: -32,
width: 64,
height: 64,
zIndex: 10,
},
lockGradient: {
flex: 1,
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center',
},
checkContainer: {
marginBottom: spacing.md,
},
statusText: {
fontSize: typography.fontSize.xl,
color: colors.vault.primary,
fontWeight: '600',
marginBottom: spacing.xs,
fontFamily: typography.fontFamily.serif,
},
subText: {
fontSize: typography.fontSize.base,
color: colors.vault.textSecondary,
fontStyle: 'italic',
},
});