Steven Frontend

This commit is contained in:
2026-01-26 00:34:58 -04:00
parent 7944b9f7ed
commit b8c241c1a0
6 changed files with 1977 additions and 108 deletions

View File

@@ -6,7 +6,6 @@ import { colors, borderRadius, typography } from '../theme/colors';
// Screens
import FlowScreen from '../screens/FlowScreen';
import VaultScreen from '../screens/VaultScreen';
import SentinelScreen from '../screens/SentinelScreen';
import HeritageScreen from '../screens/HeritageScreen';
import MeScreen from '../screens/MeScreen';
@@ -89,22 +88,6 @@ export default function TabNavigator() {
),
}}
/>
<Tab.Screen
name="Vault"
component={VaultScreen}
options={{
tabBarIcon: ({ focused }) => (
<TabBarItem
name="Vault"
label="Vault"
focused={focused}
color={focused ? colors.vault.primary : colors.vault.textSecondary}
isDark
/>
),
tabBarStyle: styles.tabBarDark,
}}
/>
<Tab.Screen
name="Sentinel"
component={SentinelScreen}

View File

@@ -13,6 +13,7 @@ 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 { Heir, HeirStatus, PaymentStrategy } from '../types';
import VaultScreen from './VaultScreen';
// Mock heirs data
const initialHeirs: Heir[] = [
@@ -20,6 +21,7 @@ const initialHeirs: Heir[] = [
id: '1',
name: 'John Smith',
email: 'john.smith@email.com',
phone: '+1 415 555 0132',
status: 'confirmed',
releaseLevel: 3,
releaseOrder: 1,
@@ -29,6 +31,7 @@ const initialHeirs: Heir[] = [
id: '2',
name: 'Jane Doe',
email: 'jane.doe@email.com',
phone: '+1 212 555 0184',
status: 'confirmed',
releaseLevel: 2,
releaseOrder: 2,
@@ -38,6 +41,7 @@ const initialHeirs: Heir[] = [
id: '3',
name: 'Alice Johnson',
email: 'alice.j@email.com',
phone: '+1 646 555 0149',
status: 'invited',
releaseLevel: 1,
releaseOrder: 3,
@@ -69,6 +73,7 @@ export default function HeritageScreen() {
const [showAddModal, setShowAddModal] = useState(false);
const [showDetailModal, setShowDetailModal] = useState(false);
const [selectedHeir, setSelectedHeir] = useState<Heir | null>(null);
const [showVault, setShowVault] = useState(false);
const [newHeirName, setNewHeirName] = useState('');
const [newHeirEmail, setNewHeirEmail] = useState('');
const [newHeirLevel, setNewHeirLevel] = useState(1);
@@ -147,6 +152,26 @@ export default function HeritageScreen() {
</View>
</View>
{/* Shadow Vault Access */}
<View style={styles.vaultAccessCard}>
<View style={styles.vaultAccessIcon}>
<MaterialCommunityIcons name="treasure-chest" size={22} color={colors.nautical.teal} />
</View>
<View style={styles.vaultAccessContent}>
<Text style={styles.vaultAccessTitle}>Shadow Vault</Text>
<Text style={styles.vaultAccessText}>
Access sealed assets from inside the Heritage layer.
</Text>
</View>
<TouchableOpacity
style={styles.vaultAccessButton}
onPress={() => setShowVault(true)}
activeOpacity={0.8}
>
<Text style={styles.vaultAccessButtonText}>Open</Text>
</TouchableOpacity>
</View>
{/* Release Stages Info */}
<View style={styles.stagesSection}>
<View style={styles.sectionHeader}>
@@ -366,6 +391,24 @@ export default function HeritageScreen() {
</View>
</Modal>
{/* Vault Modal */}
<Modal
visible={showVault}
animationType="slide"
onRequestClose={() => setShowVault(false)}
>
<View style={styles.vaultModalContainer}>
<VaultScreen />
<TouchableOpacity
style={styles.vaultCloseButton}
onPress={() => setShowVault(false)}
activeOpacity={0.85}
>
<Ionicons name="close" size={20} color={colors.nautical.cream} />
</TouchableOpacity>
</View>
</Modal>
{/* Heir Detail Modal */}
<Modal
visible={showDetailModal}
@@ -504,6 +547,61 @@ const styles = StyleSheet.create({
color: colors.heritage.textSecondary,
lineHeight: typography.fontSize.sm * 1.5,
},
contactHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
marginBottom: spacing.sm,
},
contactTitle: {
fontSize: typography.fontSize.base,
fontWeight: '600',
color: colors.heritage.text,
},
vaultAccessCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.heritage.cardBackground,
borderRadius: borderRadius.xl,
padding: spacing.base,
marginBottom: spacing.lg,
borderWidth: 1,
borderColor: colors.heritage.cardBorder,
...shadows.soft,
},
vaultAccessIcon: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: colors.nautical.lightMint,
alignItems: 'center',
justifyContent: 'center',
marginRight: spacing.md,
},
vaultAccessContent: {
flex: 1,
},
vaultAccessTitle: {
fontSize: typography.fontSize.base,
fontWeight: '600',
color: colors.heritage.text,
marginBottom: 2,
},
vaultAccessText: {
fontSize: typography.fontSize.sm,
color: colors.heritage.textSecondary,
},
vaultAccessButton: {
backgroundColor: colors.nautical.teal,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
},
vaultAccessButtonText: {
color: colors.nautical.cream,
fontWeight: '700',
fontSize: typography.fontSize.sm,
},
stagesSection: {
marginBottom: spacing.lg,
},
@@ -825,6 +923,21 @@ const styles = StyleSheet.create({
justifyContent: 'center',
padding: spacing.lg,
},
vaultModalContainer: {
flex: 1,
backgroundColor: colors.vault.background,
},
vaultCloseButton: {
position: 'absolute',
top: spacing.lg,
right: spacing.lg,
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: 'rgba(26, 58, 74, 0.65)',
alignItems: 'center',
justifyContent: 'center',
},
detailModal: {
backgroundColor: colors.heritage.cardBackground,
borderRadius: borderRadius.xxl,

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,8 @@ import {
TextInput,
SafeAreaView,
Animated,
Linking,
Alert,
} from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
@@ -18,7 +20,7 @@ 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' },
game_account: { icon: 'account-key', iconType: 'material', label: 'Account Login' },
private_key: { icon: 'key', iconType: 'fontawesome5', label: 'Secret Key' },
document: { icon: 'scroll', iconType: 'fontawesome5', label: 'Document' },
photo: { icon: 'image', iconType: 'ionicons', label: 'Sealed Photo' },
@@ -26,6 +28,13 @@ const assetTypeConfig: Record<VaultAssetType, { icon: string; iconType: 'ionicon
custom: { icon: 'gem', iconType: 'fontawesome5', label: 'Treasure' },
};
const accountProviderOptions = [
{ key: 'bank', label: 'Bank', icon: 'bank', iconType: 'material' as const },
{ key: 'steam', label: 'Steam', icon: 'steam', iconType: 'fontawesome5' as const },
{ key: 'facebook', label: 'Facebook', icon: 'facebook-f', iconType: 'fontawesome5' as const },
{ key: 'custom', label: 'Other', icon: 'shield-account', iconType: 'material' as const },
];
// Mock data
const initialAssets: VaultAsset[] = [
{
@@ -85,6 +94,16 @@ export default function VaultScreen() {
const [showUploadSuccess, setShowUploadSuccess] = useState(false);
const [fadeAnim] = useState(new Animated.Value(0));
const [pulseAnim] = useState(new Animated.Value(1));
const [selectedAsset, setSelectedAsset] = useState<VaultAsset | null>(null);
const [showDetail, setShowDetail] = useState(false);
const [showGuardedBiometric, setShowGuardedBiometric] = useState(false);
const [showKeyPreview, setShowKeyPreview] = useState(false);
const [addStep, setAddStep] = useState(1);
const [addMethod, setAddMethod] = useState<'text' | 'file' | 'scan'>('text');
const [addVerified, setAddVerified] = useState(false);
const [rehearsalConfirmed, setRehearsalConfirmed] = useState(false);
const [showAddBiometric, setShowAddBiometric] = useState(false);
const [accountProvider, setAccountProvider] = useState<'bank' | 'steam' | 'facebook' | 'custom'>('bank');
useEffect(() => {
if (!isUnlocked) {
@@ -129,8 +148,20 @@ export default function VaultScreen() {
setIsUnlocked(true);
};
const resetAddFlow = () => {
setAddStep(1);
setAddMethod('text');
setAddVerified(false);
setRehearsalConfirmed(false);
setSelectedType('custom');
setNewLabel('');
setAccountProvider('bank');
};
const handleAddAsset = () => {
if (!newLabel.trim()) return;
if (!addVerified) return;
if (selectedType === 'private_key' && !rehearsalConfirmed) return;
const newAsset: VaultAsset = {
id: Date.now().toString(),
@@ -145,6 +176,8 @@ export default function VaultScreen() {
setNewLabel('');
setSelectedType('custom');
setShowAddModal(false);
setAddVerified(false);
setRehearsalConfirmed(false);
setShowUploadSuccess(true);
setTimeout(() => setShowUploadSuccess(false), 2500);
@@ -158,6 +191,86 @@ export default function VaultScreen() {
});
};
const handleOpenDetail = (asset: VaultAsset) => {
setSelectedAsset(asset);
setShowDetail(true);
setShowKeyPreview(false);
};
const handleCloseDetail = () => {
setShowDetail(false);
setSelectedAsset(null);
setShowKeyPreview(false);
setShowGuardedBiometric(false);
};
const handleGuardedAccess = () => {
setShowGuardedBiometric(true);
};
const handleGuardedSuccess = () => {
setShowGuardedBiometric(false);
setShowKeyPreview(true);
};
const handleAddVerification = () => {
setShowAddBiometric(true);
};
const handleAddVerificationSuccess = () => {
setShowAddBiometric(false);
setAddVerified(true);
};
const openProviderLogin = async () => {
if (accountProvider === 'bank') {
Alert.alert(
'Bank App Needed',
'Provide the bank app deep link scheme to enable one-tap login.'
);
return;
}
const providerLinks = {
steam: {
app: 'steam://openurl/https://store.steampowered.com/login/',
web: 'https://store.steampowered.com/login/',
},
facebook: {
app: 'fb://facewebmodal/f?href=https://www.facebook.com/login',
web: 'https://www.facebook.com/login',
},
custom: {
app: '',
web: '',
},
} as const;
const target = providerLinks[accountProvider];
if (!target?.app) {
return;
}
const canOpen = await Linking.canOpenURL(target.app);
if (canOpen) {
await Linking.openURL(target.app);
} else if (target.web) {
await Linking.openURL(target.web);
}
};
const selectedConfig = selectedAsset ? assetTypeConfig[selectedAsset.type] : null;
const detailHeading = selectedAsset?.type === 'private_key'
? 'Key Material'
: selectedConfig?.label || 'Vault Asset';
const detailMetaLabel = selectedAsset?.type === 'private_key' ? 'KEY MATERIAL' : 'ASSET CLASS';
const detailMetaValue = selectedAsset?.type === 'private_key'
? '12/24 Words'
: selectedConfig?.label || '--';
const canSeal = !!newLabel.trim()
&& addVerified
&& (selectedType !== 'private_key' || rehearsalConfirmed);
// Lock screen
if (!isUnlocked) {
return (
@@ -253,6 +366,7 @@ export default function VaultScreen() {
key={asset.id}
style={styles.assetCard}
activeOpacity={0.7}
onPress={() => handleOpenDetail(asset)}
>
<View style={styles.assetIconContainer}>
{renderAssetTypeIcon(config, 22, colors.vault.primary)}
@@ -277,7 +391,10 @@ export default function VaultScreen() {
{/* Add Button */}
<TouchableOpacity
style={styles.addButton}
onPress={() => setShowAddModal(true)}
onPress={() => {
resetAddFlow();
setShowAddModal(true);
}}
activeOpacity={0.9}
>
<LinearGradient
@@ -316,78 +433,395 @@ export default function VaultScreen() {
<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;
<View style={styles.stepRow}>
{['Type', 'Method', 'Verify'].map((label, index) => {
const stepIndex = index + 1;
const isActive = addStep === stepIndex;
const isDone = addStep > stepIndex;
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 key={label} style={styles.stepItem}>
<View style={[
styles.stepCircle,
isActive && styles.stepCircleActive,
isDone && styles.stepCircleDone,
]}>
<Text style={[
styles.stepNumber,
isActive && styles.stepNumberActive,
isDone && styles.stepNumberDone,
]}>
{stepIndex}
</Text>
</View>
<Text style={[styles.typeButtonLabel, isSelected && styles.typeButtonLabelActive]}>
{config.label}
<Text style={[styles.stepLabel, (isActive || isDone) && styles.stepLabelActive]}>
{label}
</Text>
</TouchableOpacity>
</View>
);
})}
</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>
{addStep === 1 && (
<>
<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>
</>
)}
{addStep === 2 && (
<>
{selectedType !== 'game_account' && (
<>
<Text style={styles.modalLabel}>CAPTURE METHOD</Text>
<View style={styles.methodRow}>
{[
{ key: 'text', label: 'Text', icon: 'text' },
{ key: 'file', label: 'File', icon: 'file-tray' },
{ key: 'scan', label: 'Scan', icon: 'camera' },
].map((item) => {
const isActive = addMethod === item.key;
return (
<TouchableOpacity
key={item.key}
style={[styles.methodButton, isActive && styles.methodButtonActive]}
onPress={() => setAddMethod(item.key as typeof addMethod)}
>
<Ionicons
name={item.icon as any}
size={18}
color={isActive ? colors.nautical.teal : colors.nautical.sage}
/>
<Text style={[styles.methodLabel, isActive && styles.methodLabelActive]}>
{item.label}
</Text>
</TouchableOpacity>
);
})}
</View>
</>
)}
{selectedType === 'game_account' && (
<>
<Text style={styles.modalLabel}>ACCOUNT PROVIDER</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.typeScroll}
contentContainerStyle={styles.typeScrollContent}
>
{accountProviderOptions.map((option) => {
const isSelected = accountProvider === option.key;
return (
<TouchableOpacity
key={option.key}
style={[styles.typeButton, isSelected && styles.typeButtonActive]}
onPress={() => setAccountProvider(option.key as typeof accountProvider)}
>
<View style={[styles.typeIconContainer, isSelected && styles.typeIconContainerActive]}>
{renderAssetTypeIcon(
{ icon: option.icon, iconType: option.iconType, label: option.label },
22,
isSelected ? colors.nautical.teal : colors.nautical.sage
)}
</View>
<Text style={[styles.typeButtonLabel, isSelected && styles.typeButtonLabelActive]}>
{option.label}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
<TouchableOpacity
style={styles.loginButton}
onPress={openProviderLogin}
activeOpacity={0.85}
>
<Ionicons name="log-in-outline" size={18} color={colors.nautical.cream} />
<Text style={styles.loginButtonText}>Open App to Login</Text>
</TouchableOpacity>
</>
)}
<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}>
Data is encrypted on-device. Plaintext is shredded after sealing.
</Text>
</View>
</>
)}
{addStep === 3 && (
<>
<Text style={styles.modalLabel}>IDENTITY VERIFICATION</Text>
<View style={styles.verifyCard}>
<View style={styles.verifyRow}>
<Ionicons name="finger-print" size={18} color={colors.nautical.teal} />
<Text style={styles.verifyText}>Biometric required before sealing.</Text>
</View>
<TouchableOpacity
style={[styles.verifyButton, addVerified && styles.verifyButtonDone]}
onPress={handleAddVerification}
activeOpacity={0.85}
>
<Text style={styles.verifyButtonText}>
{addVerified ? 'Verified' : 'Verify Now'}
</Text>
</TouchableOpacity>
</View>
{selectedType === 'private_key' && (
<TouchableOpacity
style={styles.rehearsalRow}
onPress={() => setRehearsalConfirmed(!rehearsalConfirmed)}
activeOpacity={0.8}
>
<Ionicons
name={rehearsalConfirmed ? 'checkbox' : 'square-outline'}
size={20}
color={rehearsalConfirmed ? colors.nautical.teal : colors.nautical.sage}
/>
<Text style={styles.rehearsalText}>
I rehearsed the mnemonic once (required).
</Text>
</TouchableOpacity>
)}
<View style={styles.encryptionNote}>
<MaterialCommunityIcons name="lock" size={16} color={colors.nautical.teal} />
<Text style={styles.encryptionNoteText}>
Only a masked shard can be revealed, and access is time-limited.
</Text>
</View>
</>
)}
<View style={styles.modalButtons}>
<TouchableOpacity
style={styles.cancelButton}
onPress={() => {
setShowAddModal(false);
setNewLabel('');
if (addStep === 1) {
setShowAddModal(false);
} else {
setAddStep(addStep - 1);
}
}}
>
<Text style={styles.cancelButtonText}>Cancel</Text>
<Text style={styles.cancelButtonText}>
{addStep === 1 ? 'Cancel' : 'Back'}
</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 }}
{addStep < 3 ? (
<TouchableOpacity
style={styles.confirmButton}
onPress={() => setAddStep(addStep + 1)}
>
<MaterialCommunityIcons name="lock" size={18} color="#fff" />
<Text style={styles.confirmButtonText}>Seal Treasure</Text>
</LinearGradient>
</TouchableOpacity>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.seafoam]}
style={styles.confirmButtonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Text style={styles.confirmButtonText}>Continue</Text>
</LinearGradient>
</TouchableOpacity>
) : (
<TouchableOpacity
style={styles.confirmButton}
onPress={handleAddAsset}
activeOpacity={canSeal ? 0.9 : 1}
disabled={!canSeal}
>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.seafoam]}
style={[
styles.confirmButtonGradient,
!canSeal && styles.confirmButtonGradientDisabled,
]}
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>
<BiometricModal
visible={showAddBiometric}
onSuccess={handleAddVerificationSuccess}
onCancel={() => setShowAddBiometric(false)}
title="Confirm Identity"
message="Verify before sealing this treasure"
isDark
/>
{/* Detail Modal */}
<Modal
visible={showDetail}
animationType="slide"
onRequestClose={handleCloseDetail}
>
<View style={styles.detailContainer}>
<LinearGradient
colors={[colors.vault.backgroundGradientStart, colors.vault.backgroundGradientEnd]}
style={styles.detailGradient}
>
<SafeAreaView style={styles.detailSafeArea}>
<View style={styles.detailHeader}>
<TouchableOpacity
style={styles.detailBackButton}
onPress={handleCloseDetail}
activeOpacity={0.8}
>
<Ionicons name="chevron-back" size={20} color={colors.vault.text} />
</TouchableOpacity>
<Text style={styles.detailTitle}>{detailHeading}</Text>
{selectedAsset?.type === 'private_key' && (
<View style={styles.riskBadge}>
<Text style={styles.riskBadgeText}>HIGH-RISK</Text>
</View>
)}
</View>
<ScrollView
style={styles.detailScroll}
contentContainerStyle={styles.detailContent}
showsVerticalScrollIndicator={false}
>
<View style={styles.detailHeroCard}>
<View style={styles.detailHeroIcon}>
<MaterialCommunityIcons name="key-variant" size={28} color={colors.vault.primary} />
</View>
<View style={styles.detailHeroInfo}>
<Text style={styles.detailHeroLabel}>SEALED ASSET</Text>
<Text style={styles.detailHeroTitle}>{selectedAsset?.label}</Text>
<Text style={styles.detailHeroSubtitle}>Encrypted at rest · No plaintext stored</Text>
</View>
</View>
<View style={styles.metaGrid}>
<View style={styles.metaCard}>
<Text style={styles.metaLabel}>CREATED</Text>
<Text style={styles.metaValue}>
{selectedAsset ? formatDate(selectedAsset.createdAt) : '--'}
</Text>
</View>
<View style={styles.metaCard}>
<Text style={styles.metaLabel}>LAST VERIFIED</Text>
<Text style={styles.metaValue}>
{selectedAsset ? formatDate(selectedAsset.updatedAt) : '--'}
</Text>
</View>
<View style={styles.metaCard}>
<Text style={styles.metaLabel}>STORAGE</Text>
<Text style={styles.metaValue}>Cipher Pack</Text>
</View>
<View style={styles.metaCard}>
<Text style={styles.metaLabel}>{detailMetaLabel}</Text>
<Text style={styles.metaValue}>{detailMetaValue}</Text>
</View>
</View>
<View style={styles.actionGroup}>
<Text style={styles.sectionTitle}>CONTROLLED ACTIONS</Text>
<TouchableOpacity style={styles.actionRow} activeOpacity={0.8}>
<Ionicons name="finger-print" size={18} color={colors.vault.primary} />
<Text style={styles.actionText}>View Encrypted Digest</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionRow} activeOpacity={0.8}>
<MaterialCommunityIcons name="file-lock" size={18} color={colors.vault.primary} />
<Text style={styles.actionText}>Export Cipher Pack</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionRow} activeOpacity={0.8}>
<MaterialCommunityIcons name="timer-reset" size={18} color={colors.vault.primary} />
<Text style={styles.actionText}>Reset Sentinel Timer</Text>
</TouchableOpacity>
</View>
<View style={styles.guardCard}>
<View style={styles.guardHeader}>
<MaterialCommunityIcons name="shield-lock" size={18} color={colors.vault.warning} />
<Text style={styles.guardTitle}>Guarded Reveal</Text>
</View>
<Text style={styles.guardText}>
Plaintext access requires biometric verification and a memory rehearsal step.
</Text>
<TouchableOpacity
style={styles.guardButton}
onPress={handleGuardedAccess}
activeOpacity={0.85}
>
<Text style={styles.guardButtonText}>Begin Verification</Text>
</TouchableOpacity>
{showKeyPreview && (
<View style={styles.previewCard}>
<Text style={styles.previewLabel}>MNEMONIC SHARD (MASKED)</Text>
<Text style={styles.previewValue}>ocean-anchored-ember-veil</Text>
</View>
)}
</View>
<View style={styles.warningCard}>
<Ionicons name="warning" size={18} color={colors.vault.warning} />
<Text style={styles.warningText}>
Any reveal attempt is time-limited and logged. Screenshots are blocked by policy.
</Text>
</View>
</ScrollView>
</SafeAreaView>
</LinearGradient>
</View>
<BiometricModal
visible={showGuardedBiometric}
onSuccess={handleGuardedSuccess}
onCancel={() => setShowGuardedBiometric(false)}
title="Verify Access"
message="Confirm to reveal a masked shard of the mnemonic"
isDark
/>
</Modal>
</View>
);
}
@@ -636,6 +1070,53 @@ const styles = StyleSheet.create({
fontWeight: '600',
color: colors.nautical.navy,
},
stepRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: spacing.lg,
},
stepItem: {
alignItems: 'center',
flex: 1,
},
stepCircle: {
width: 28,
height: 28,
borderRadius: 14,
borderWidth: 1,
borderColor: colors.nautical.lightMint,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.nautical.paleAqua,
},
stepCircleActive: {
borderColor: colors.nautical.teal,
backgroundColor: colors.nautical.lightMint,
},
stepCircleDone: {
borderColor: colors.nautical.teal,
backgroundColor: colors.nautical.teal,
},
stepNumber: {
fontSize: typography.fontSize.xs,
color: colors.nautical.sage,
fontWeight: '600',
},
stepNumberActive: {
color: colors.nautical.teal,
},
stepNumberDone: {
color: colors.nautical.cream,
},
stepLabel: {
fontSize: typography.fontSize.xs,
color: colors.nautical.sage,
marginTop: spacing.xs,
},
stepLabelActive: {
color: colors.nautical.teal,
fontWeight: '600',
},
modalLabel: {
fontSize: typography.fontSize.xs,
color: colors.nautical.sage,
@@ -643,6 +1124,49 @@ const styles = StyleSheet.create({
letterSpacing: typography.letterSpacing.wider,
fontWeight: '600',
},
methodRow: {
flexDirection: 'row',
gap: spacing.sm,
marginBottom: spacing.lg,
},
methodButton: {
flex: 1,
paddingVertical: spacing.sm,
borderRadius: borderRadius.lg,
alignItems: 'center',
gap: spacing.xs,
backgroundColor: colors.nautical.paleAqua,
borderWidth: 1,
borderColor: colors.nautical.lightMint,
},
methodButtonActive: {
borderColor: colors.nautical.teal,
backgroundColor: colors.nautical.lightMint,
},
methodLabel: {
fontSize: typography.fontSize.sm,
color: colors.nautical.sage,
fontWeight: '600',
},
methodLabelActive: {
color: colors.nautical.teal,
},
loginButton: {
marginTop: spacing.sm,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: spacing.sm,
backgroundColor: colors.nautical.teal,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
},
loginButtonText: {
color: colors.nautical.cream,
fontSize: typography.fontSize.sm,
fontWeight: '700',
letterSpacing: 0.6,
},
typeScroll: {
marginBottom: spacing.lg,
},
@@ -740,9 +1264,267 @@ const styles = StyleSheet.create({
paddingVertical: spacing.md,
gap: spacing.sm,
},
confirmButtonGradientDisabled: {
opacity: 0.5,
},
confirmButtonText: {
fontSize: typography.fontSize.base,
color: '#fff',
fontWeight: '600',
},
verifyCard: {
backgroundColor: colors.nautical.paleAqua,
borderRadius: borderRadius.lg,
padding: spacing.base,
marginBottom: spacing.md,
borderWidth: 1,
borderColor: colors.nautical.lightMint,
gap: spacing.sm,
},
verifyRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
verifyText: {
color: colors.nautical.navy,
fontSize: typography.fontSize.sm,
},
verifyButton: {
alignSelf: 'flex-start',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
backgroundColor: colors.nautical.teal,
},
verifyButtonDone: {
backgroundColor: colors.nautical.sage,
},
verifyButtonText: {
color: colors.nautical.cream,
fontWeight: '700',
},
rehearsalRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
marginBottom: spacing.md,
},
rehearsalText: {
color: colors.nautical.navy,
fontSize: typography.fontSize.sm,
flex: 1,
},
detailContainer: {
flex: 1,
backgroundColor: colors.vault.background,
},
detailGradient: {
flex: 1,
},
detailSafeArea: {
flex: 1,
},
detailHeader: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.lg,
paddingBottom: spacing.md,
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
detailBackButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: 'rgba(255, 255, 255, 0.08)',
alignItems: 'center',
justifyContent: 'center',
},
detailTitle: {
flex: 1,
fontSize: typography.fontSize.lg,
color: colors.vault.text,
fontWeight: '700',
letterSpacing: typography.letterSpacing.wide,
},
riskBadge: {
backgroundColor: `${colors.vault.warning}25`,
borderRadius: borderRadius.full,
paddingHorizontal: spacing.sm,
paddingVertical: 4,
},
riskBadgeText: {
fontSize: typography.fontSize.xs,
color: colors.vault.warning,
fontWeight: '700',
letterSpacing: 1,
},
detailScroll: {
flex: 1,
},
detailContent: {
padding: spacing.lg,
paddingTop: spacing.sm,
gap: spacing.lg,
},
detailHeroCard: {
backgroundColor: colors.vault.cardBackground,
borderRadius: borderRadius.xl,
padding: spacing.lg,
borderWidth: 1,
borderColor: colors.vault.cardBorder,
flexDirection: 'row',
gap: spacing.base,
alignItems: 'center',
},
detailHeroIcon: {
width: 54,
height: 54,
borderRadius: 27,
backgroundColor: 'rgba(184, 224, 229, 0.15)',
alignItems: 'center',
justifyContent: 'center',
},
detailHeroInfo: {
flex: 1,
},
detailHeroLabel: {
fontSize: typography.fontSize.xs,
color: colors.vault.textSecondary,
letterSpacing: 1,
fontWeight: '600',
marginBottom: 4,
},
detailHeroTitle: {
fontSize: typography.fontSize.lg,
color: colors.vault.text,
fontWeight: '700',
marginBottom: 4,
},
detailHeroSubtitle: {
fontSize: typography.fontSize.sm,
color: colors.vault.textSecondary,
},
metaGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.md,
},
metaCard: {
width: '48%',
backgroundColor: 'rgba(255, 255, 255, 0.05)',
borderRadius: borderRadius.lg,
padding: spacing.base,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.08)',
},
metaLabel: {
fontSize: typography.fontSize.xs,
color: colors.vault.textSecondary,
letterSpacing: 1,
marginBottom: spacing.xs,
fontWeight: '600',
},
metaValue: {
fontSize: typography.fontSize.sm,
color: colors.vault.text,
fontWeight: '600',
},
actionGroup: {
gap: spacing.sm,
},
sectionTitle: {
fontSize: typography.fontSize.xs,
color: colors.vault.textSecondary,
letterSpacing: 1,
fontWeight: '700',
},
actionRow: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
paddingVertical: spacing.sm,
paddingHorizontal: spacing.md,
borderRadius: borderRadius.lg,
backgroundColor: 'rgba(255, 255, 255, 0.06)',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.08)',
},
actionText: {
color: colors.vault.text,
fontSize: typography.fontSize.base,
fontWeight: '600',
},
guardCard: {
backgroundColor: 'rgba(229, 115, 115, 0.12)',
borderRadius: borderRadius.lg,
padding: spacing.base,
gap: spacing.sm,
borderWidth: 1,
borderColor: 'rgba(229, 115, 115, 0.3)',
},
guardHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
},
guardTitle: {
fontSize: typography.fontSize.base,
color: colors.vault.warning,
fontWeight: '700',
},
guardText: {
fontSize: typography.fontSize.sm,
color: colors.vault.textSecondary,
lineHeight: typography.fontSize.sm * 1.5,
},
guardButton: {
alignSelf: 'flex-start',
backgroundColor: colors.vault.warning,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
},
guardButtonText: {
color: colors.vault.text,
fontWeight: '700',
letterSpacing: 0.6,
},
previewCard: {
backgroundColor: colors.vault.cardBackground,
borderRadius: borderRadius.lg,
padding: spacing.base,
borderWidth: 1,
borderColor: colors.vault.cardBorder,
},
previewLabel: {
fontSize: typography.fontSize.xs,
color: colors.vault.textSecondary,
letterSpacing: 1,
marginBottom: spacing.xs,
fontWeight: '600',
},
previewValue: {
fontFamily: typography.fontFamily.mono,
color: colors.vault.text,
fontSize: typography.fontSize.base,
letterSpacing: 0.6,
},
warningCard: {
flexDirection: 'row',
alignItems: 'flex-start',
gap: spacing.sm,
backgroundColor: 'rgba(26, 58, 74, 0.6)',
borderRadius: borderRadius.lg,
padding: spacing.base,
borderWidth: 1,
borderColor: 'rgba(229, 115, 115, 0.35)',
},
warningText: {
flex: 1,
color: colors.vault.textSecondary,
fontSize: typography.fontSize.sm,
lineHeight: typography.fontSize.sm * 1.5,
},
});

View File

@@ -54,6 +54,7 @@ export interface Heir {
id: string;
name: string;
email: string;
phone?: string;
status: HeirStatus;
releaseLevel: number; // 1-3
releaseOrder: number;