Steven Frontend
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface Heir {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
status: HeirStatus;
|
||||
releaseLevel: number; // 1-3
|
||||
releaseOrder: number;
|
||||
|
||||
Reference in New Issue
Block a user