added_reveal_secret_and_delete_treasure

This commit is contained in:
lusixing
2026-02-02 17:34:03 -08:00
parent b5373c2d9a
commit 5c1172a912
8 changed files with 377 additions and 84 deletions

View File

@@ -27,6 +27,7 @@ import { useAuth } from '../context/AuthContext';
import { useVaultAssets } from '../hooks/useVaultAssets';
import { getVaultStorageKeys } from '../config';
import { mnemonicToEntropy, splitSecret, serializeShare } from '../utils/sss';
import { storageService } from '../services/storage.service';
import { SentinelVault } from '@/utils/crypto_core';
// Asset type configuration with nautical theme
@@ -92,41 +93,6 @@ type HeirAssignment = {
heir: Heir;
};
// 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) {
@@ -149,11 +115,14 @@ export default function VaultScreen() {
setAssets,
refreshAssets,
createAsset: createVaultAsset,
deleteAsset: deleteVaultAsset,
isSealing,
createError: addError,
clearCreateError: clearAddError,
} = useVaultAssets(isUnlocked);
const [showAddModal, setShowAddModal] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [selectedType, setSelectedType] = useState<VaultAssetType>('custom');
const [newLabel, setNewLabel] = useState('');
const [showUploadSuccess, setShowUploadSuccess] = useState(false);
@@ -172,6 +141,8 @@ export default function VaultScreen() {
const [showMnemonic, setShowMnemonic] = useState(false);
const [showLegacyAssignCta, setShowLegacyAssignCta] = useState(false);
const [hasS0, setHasS0] = useState<boolean | null>(null);
const [backupContent, setBackupContent] = useState<string | null>(null);
const [isFetchingBackup, setIsFetchingBackup] = useState(false);
const [mnemonicWords, setMnemonicWords] = useState<string[]>([]);
const [mnemonicParts, setMnemonicParts] = useState<string[][]>([]);
const [mnemonicStep, setMnemonicStep] = useState<1 | 2 | 3 | 4 | 5>(1);
@@ -504,6 +475,61 @@ export default function VaultScreen() {
setSelectedAsset(null);
setShowKeyPreview(false);
setShowGuardedBiometric(false);
setBackupContent(null);
};
const handleFetchBackup = async () => {
if (!selectedAsset || !user?.id) return;
setIsFetchingBackup(true);
try {
const content = await storageService.getAssetBackup(Number(selectedAsset.id), user.id);
if (content) {
setBackupContent(content);
} else {
if (typeof Alert !== 'undefined' && Alert.alert) {
Alert.alert('No Backup Found', 'No local plaintext backup found for this treasure.');
}
}
} catch (error) {
console.error('Fetch backup error:', error);
if (typeof Alert !== 'undefined' && Alert.alert) {
Alert.alert('Error', 'Failed to retrieve local backup.');
}
} finally {
setIsFetchingBackup(false);
}
};
const handleDeleteAsset = async () => {
if (!selectedAsset || isDeleting) return;
setIsDeleting(true);
try {
const result = await deleteVaultAsset(Number(selectedAsset.id));
if (result.success) {
setShowDeleteConfirm(false);
handleCloseDetail();
if (typeof Alert !== 'undefined' && Alert.alert) {
Alert.alert('Success', 'Treasure removed from the vault.');
}
} else if (result.isUnauthorized) {
setShowDeleteConfirm(false);
handleCloseDetail();
if (typeof Alert !== 'undefined' && Alert.alert) {
Alert.alert('Unauthorized', 'Your session has expired. Please sign in again.');
}
} else if (result.error && typeof Alert !== 'undefined' && Alert.alert) {
Alert.alert('Failed', result.error);
}
} catch (error) {
console.error('Delete error:', error);
if (typeof Alert !== 'undefined' && Alert.alert) {
Alert.alert('Error', 'An unexpected error occurred during deletion.');
}
} finally {
setIsDeleting(false);
}
};
const handleGuardedAccess = () => {
@@ -513,6 +539,7 @@ export default function VaultScreen() {
const handleGuardedSuccess = () => {
setShowGuardedBiometric(false);
setShowKeyPreview(true);
handleFetchBackup();
};
const handleAddVerification = () => {
@@ -1464,6 +1491,14 @@ export default function VaultScreen() {
<MaterialCommunityIcons name="refresh" size={18} color={colors.vault.primary} />
<Text style={styles.actionText}>Reset Sentinel Timer</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionRow, styles.deleteActionRow]}
onPress={() => setShowDeleteConfirm(true)}
activeOpacity={0.8}
>
<Feather name="trash-2" size={18} color={colors.vault.warning} />
<Text style={[styles.actionText, styles.deleteActionText]}>Delete Treasure</Text>
</TouchableOpacity>
</View>
<View style={styles.guardCard}>
@@ -1474,6 +1509,7 @@ export default function VaultScreen() {
<Text style={styles.guardText}>
Plaintext access requires biometric verification and a memory rehearsal step.
</Text>
<TouchableOpacity
style={styles.guardButton}
onPress={handleGuardedAccess}
@@ -1481,10 +1517,13 @@ export default function VaultScreen() {
>
<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>
<Text style={styles.previewLabel}>LOCAL PLAINTEXT BACKUP</Text>
<Text style={styles.previewValue}>
{isFetchingBackup ? 'Fetching content...' : (backupContent || 'No local backup found for this treasure')}
</Text>
</View>
)}
</View>
@@ -1509,6 +1548,51 @@ export default function VaultScreen() {
isDark
/>
</Modal>
{/* Delete Confirmation Modal */}
<Modal
visible={showDeleteConfirm}
animationType="fade"
transparent
onRequestClose={() => setShowDeleteConfirm(false)}
>
<View style={styles.modalOverlay}>
<TouchableOpacity
style={styles.modalOverlayDismiss}
activeOpacity={1}
onPress={() => setShowDeleteConfirm(false)}
/>
<View style={styles.deleteConfirmContent}>
<View style={styles.deleteIconContainer}>
<Feather name="alert-triangle" size={32} color={colors.vault.warning} />
</View>
<Text style={styles.deleteTitle}>Remove Treasure?</Text>
<Text style={styles.deleteMessage}>
This action cannot be undone. The treasure will be permanently shredded from the deep vault.
</Text>
<View style={styles.deleteButtons}>
<TouchableOpacity
style={styles.deleteCancelButton}
onPress={() => setShowDeleteConfirm(false)}
disabled={isDeleting}
>
<Text style={styles.deleteCancelText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.deleteConfirmButton}
onPress={handleDeleteAsset}
disabled={isDeleting}
>
{isDeleting ? (
<Text style={styles.deleteConfirmText}>Shredding...</Text>
) : (
<Text style={styles.deleteConfirmText}>Confirm Delete</Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View>
);
@@ -1777,6 +1861,73 @@ const styles = StyleSheet.create({
backgroundColor: 'rgba(26, 58, 74, 0.8)',
justifyContent: 'flex-end',
},
modalOverlayDismiss: {
...StyleSheet.absoluteFillObject,
},
deleteConfirmContent: {
backgroundColor: colors.vault.cardBackground,
marginHorizontal: spacing.lg,
borderRadius: borderRadius.xl,
padding: spacing.xl,
borderWidth: 1,
borderColor: colors.vault.cardBorder,
alignItems: 'center',
marginBottom: spacing.xxl + spacing.lg,
},
deleteIconContainer: {
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: `${colors.vault.warning}20`,
alignItems: 'center',
justifyContent: 'center',
marginBottom: spacing.lg,
},
deleteTitle: {
fontSize: typography.fontSize.lg,
color: colors.vault.text,
fontWeight: '700',
marginBottom: spacing.sm,
fontFamily: typography.fontFamily.serif,
},
deleteMessage: {
fontSize: typography.fontSize.sm,
color: colors.vault.textSecondary,
textAlign: 'center',
lineHeight: typography.fontSize.sm * 1.5,
marginBottom: spacing.xl,
},
deleteButtons: {
flexDirection: 'row',
gap: spacing.md,
width: '100%',
},
deleteCancelButton: {
flex: 1,
paddingVertical: spacing.md,
borderRadius: borderRadius.lg,
backgroundColor: 'rgba(255, 255, 255, 0.08)',
alignItems: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)',
},
deleteCancelText: {
color: colors.vault.textSecondary,
fontSize: typography.fontSize.base,
fontWeight: '600',
},
deleteConfirmButton: {
flex: 2,
paddingVertical: spacing.md,
borderRadius: borderRadius.lg,
backgroundColor: colors.vault.warning,
alignItems: 'center',
},
deleteConfirmText: {
color: colors.vault.text,
fontSize: typography.fontSize.base,
fontWeight: '700',
},
modalContent: {
backgroundColor: colors.nautical.cream,
borderTopLeftRadius: borderRadius.xxl,
@@ -2581,8 +2732,66 @@ const styles = StyleSheet.create({
borderColor: colors.sentinel.cardBorder,
},
mnemonicSecondaryText: {
color: colors.sentinel.text,
fontWeight: '700',
letterSpacing: typography.letterSpacing.wide,
},
deleteActionRow: {
backgroundColor: 'rgba(229, 115, 115, 0.08)',
borderColor: 'rgba(229, 115, 115, 0.2)',
marginTop: spacing.sm,
},
deleteActionText: {
color: colors.vault.warning,
},
confirmModalOverlay: {
flex: 1,
backgroundColor: 'rgba(11, 20, 24, 0.85)',
justifyContent: 'center',
alignItems: 'center',
padding: spacing.xl,
},
confirmModalContent: {
width: '100%',
backgroundColor: colors.vault.cardBackground,
borderRadius: borderRadius.xl,
padding: spacing.lg,
borderWidth: 1,
borderColor: colors.vault.cardBorder,
gap: spacing.md,
},
confirmModalTitle: {
fontSize: typography.fontSize.lg,
color: colors.vault.text,
fontWeight: '700',
textAlign: 'center',
},
confirmModalText: {
fontSize: typography.fontSize.base,
color: colors.vault.textSecondary,
textAlign: 'center',
lineHeight: 22,
},
confirmModalButtons: {
flexDirection: 'row',
gap: spacing.md,
marginTop: spacing.sm,
},
confirmModalButton: {
flex: 1,
paddingVertical: spacing.md,
borderRadius: borderRadius.lg,
alignItems: 'center',
justifyContent: 'center',
},
confirmCancelButton: {
backgroundColor: 'rgba(255, 255, 255, 0.05)',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)',
},
confirmDeleteButton: {
backgroundColor: colors.vault.warning,
},
confirmCancelText: {
color: colors.vault.textSecondary,
},
});