added_reveal_secret_and_delete_treasure
This commit is contained in:
@@ -51,6 +51,7 @@ export const API_ENDPOINTS = {
|
|||||||
CREATE: '/assets/create',
|
CREATE: '/assets/create',
|
||||||
CLAIM: '/assets/claim',
|
CLAIM: '/assets/claim',
|
||||||
ASSIGN: '/assets/assign',
|
ASSIGN: '/assets/assign',
|
||||||
|
DELETE: '/assets/delete',
|
||||||
},
|
},
|
||||||
|
|
||||||
// AI Services
|
// AI Services
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useAuth } from '../context/AuthContext';
|
|||||||
import { assetsService } from '../services/assets.service';
|
import { assetsService } from '../services/assets.service';
|
||||||
import { getVaultStorageKeys, DEBUG_MODE } from '../config';
|
import { getVaultStorageKeys, DEBUG_MODE } from '../config';
|
||||||
import { SentinelVault } from '../utils/crypto_core';
|
import { SentinelVault } from '../utils/crypto_core';
|
||||||
|
import { storageService } from '../services/storage.service';
|
||||||
import {
|
import {
|
||||||
initialVaultAssets,
|
initialVaultAssets,
|
||||||
mapApiAssetsToVaultAssets,
|
mapApiAssetsToVaultAssets,
|
||||||
@@ -37,6 +38,8 @@ export interface UseVaultAssetsReturn {
|
|||||||
refreshAssets: () => Promise<void>;
|
refreshAssets: () => Promise<void>;
|
||||||
/** Create asset via POST /assets/create; on success refreshes list */
|
/** Create asset via POST /assets/create; on success refreshes list */
|
||||||
createAsset: (params: { title: string; content: string }) => Promise<CreateAssetResult>;
|
createAsset: (params: { title: string; content: string }) => Promise<CreateAssetResult>;
|
||||||
|
/** Delete asset via POST /assets/delete; on success refreshes list */
|
||||||
|
deleteAsset: (assetId: number) => Promise<CreateAssetResult>;
|
||||||
/** True while create request is in flight */
|
/** True while create request is in flight */
|
||||||
isSealing: boolean;
|
isSealing: boolean;
|
||||||
/** Error message from last create failure (non-401) */
|
/** Error message from last create failure (non-401) */
|
||||||
@@ -129,7 +132,7 @@ export function useVaultAssets(isUnlocked: boolean): UseVaultAssetsReturn {
|
|||||||
console.log(' Encrypted: ', content_inner_encrypted);
|
console.log(' Encrypted: ', content_inner_encrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
await assetsService.createAsset(
|
const createdAsset = await assetsService.createAsset(
|
||||||
{
|
{
|
||||||
title: title.trim(),
|
title: title.trim(),
|
||||||
private_key_shard: s1Str,
|
private_key_shard: s1Str,
|
||||||
@@ -137,6 +140,11 @@ export function useVaultAssets(isUnlocked: boolean): UseVaultAssetsReturn {
|
|||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Backup plaintext content locally
|
||||||
|
if (createdAsset && createdAsset.id && user?.id) {
|
||||||
|
await storageService.saveAssetBackup(createdAsset.id, content, user.id);
|
||||||
|
}
|
||||||
await refreshAssets();
|
await refreshAssets();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -166,6 +174,44 @@ export function useVaultAssets(isUnlocked: boolean): UseVaultAssetsReturn {
|
|||||||
[token, user, refreshAssets, signOut]
|
[token, user, refreshAssets, signOut]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const deleteAsset = useCallback(
|
||||||
|
async (assetId: number): Promise<CreateAssetResult> => {
|
||||||
|
if (!token) {
|
||||||
|
return { success: false, error: 'Not logged in.' };
|
||||||
|
}
|
||||||
|
setIsSealing(true);
|
||||||
|
setCreateError(null);
|
||||||
|
try {
|
||||||
|
await assetsService.deleteAsset(assetId, token);
|
||||||
|
await refreshAssets();
|
||||||
|
return { success: true };
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const status =
|
||||||
|
err && typeof err === 'object' && 'status' in err
|
||||||
|
? (err as { status?: number }).status
|
||||||
|
: undefined;
|
||||||
|
const rawMessage =
|
||||||
|
err instanceof Error ? err.message : String(err ?? 'Failed to delete.');
|
||||||
|
const isUnauthorized =
|
||||||
|
status === 401 || /401|Unauthorized/i.test(rawMessage);
|
||||||
|
|
||||||
|
if (isUnauthorized) {
|
||||||
|
signOut();
|
||||||
|
return { success: false, isUnauthorized: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const friendlyMessage = /failed to fetch|network error/i.test(rawMessage)
|
||||||
|
? 'Network error. Please check that the backend is running and reachable.'
|
||||||
|
: rawMessage;
|
||||||
|
setCreateError(friendlyMessage);
|
||||||
|
return { success: false, error: friendlyMessage };
|
||||||
|
} finally {
|
||||||
|
setIsSealing(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[token, refreshAssets, signOut]
|
||||||
|
);
|
||||||
|
|
||||||
const clearCreateError = useCallback(() => setCreateError(null), []);
|
const clearCreateError = useCallback(() => setCreateError(null), []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -173,6 +219,7 @@ export function useVaultAssets(isUnlocked: boolean): UseVaultAssetsReturn {
|
|||||||
setAssets,
|
setAssets,
|
||||||
refreshAssets,
|
refreshAssets,
|
||||||
createAsset,
|
createAsset,
|
||||||
|
deleteAsset,
|
||||||
isSealing,
|
isSealing,
|
||||||
createError,
|
createError,
|
||||||
clearCreateError,
|
clearCreateError,
|
||||||
|
|||||||
@@ -542,12 +542,17 @@ export default function FlowScreen() {
|
|||||||
const encryptedSummary = vault.encryptData(aesKey, generatedSummary).toString('hex');
|
const encryptedSummary = vault.encryptData(aesKey, generatedSummary).toString('hex');
|
||||||
|
|
||||||
// Create asset in backend
|
// Create asset in backend
|
||||||
await assetsService.createAsset({
|
const createdAsset = await assetsService.createAsset({
|
||||||
title: `Chat Summary - ${new Date().toLocaleDateString()}`,
|
title: `Chat Summary - ${new Date().toLocaleDateString()}`,
|
||||||
private_key_shard: shareServer,
|
private_key_shard: shareServer,
|
||||||
content_inner_encrypted: encryptedSummary,
|
content_inner_encrypted: encryptedSummary,
|
||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
|
// Backup plaintext content locally
|
||||||
|
if (createdAsset && createdAsset.id && user?.id) {
|
||||||
|
await storageService.saveAssetBackup(createdAsset.id, generatedSummary, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
setSaveResult({ success: true, message: 'Summary encrypted and saved to your vault successfully.' });
|
setSaveResult({ success: true, message: 'Summary encrypted and saved to your vault successfully.' });
|
||||||
setShowSaveResultModal(true);
|
setShowSaveResultModal(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { useAuth } from '../context/AuthContext';
|
|||||||
import { useVaultAssets } from '../hooks/useVaultAssets';
|
import { useVaultAssets } from '../hooks/useVaultAssets';
|
||||||
import { getVaultStorageKeys } from '../config';
|
import { getVaultStorageKeys } from '../config';
|
||||||
import { mnemonicToEntropy, splitSecret, serializeShare } from '../utils/sss';
|
import { mnemonicToEntropy, splitSecret, serializeShare } from '../utils/sss';
|
||||||
|
import { storageService } from '../services/storage.service';
|
||||||
import { SentinelVault } from '@/utils/crypto_core';
|
import { SentinelVault } from '@/utils/crypto_core';
|
||||||
|
|
||||||
// Asset type configuration with nautical theme
|
// Asset type configuration with nautical theme
|
||||||
@@ -92,41 +93,6 @@ type HeirAssignment = {
|
|||||||
heir: Heir;
|
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) => {
|
const renderAssetTypeIcon = (config: typeof assetTypeConfig[VaultAssetType], size: number, color: string) => {
|
||||||
switch (config.iconType) {
|
switch (config.iconType) {
|
||||||
@@ -149,11 +115,14 @@ export default function VaultScreen() {
|
|||||||
setAssets,
|
setAssets,
|
||||||
refreshAssets,
|
refreshAssets,
|
||||||
createAsset: createVaultAsset,
|
createAsset: createVaultAsset,
|
||||||
|
deleteAsset: deleteVaultAsset,
|
||||||
isSealing,
|
isSealing,
|
||||||
createError: addError,
|
createError: addError,
|
||||||
clearCreateError: clearAddError,
|
clearCreateError: clearAddError,
|
||||||
} = useVaultAssets(isUnlocked);
|
} = useVaultAssets(isUnlocked);
|
||||||
const [showAddModal, setShowAddModal] = useState(false);
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [selectedType, setSelectedType] = useState<VaultAssetType>('custom');
|
const [selectedType, setSelectedType] = useState<VaultAssetType>('custom');
|
||||||
const [newLabel, setNewLabel] = useState('');
|
const [newLabel, setNewLabel] = useState('');
|
||||||
const [showUploadSuccess, setShowUploadSuccess] = useState(false);
|
const [showUploadSuccess, setShowUploadSuccess] = useState(false);
|
||||||
@@ -172,6 +141,8 @@ export default function VaultScreen() {
|
|||||||
const [showMnemonic, setShowMnemonic] = useState(false);
|
const [showMnemonic, setShowMnemonic] = useState(false);
|
||||||
const [showLegacyAssignCta, setShowLegacyAssignCta] = useState(false);
|
const [showLegacyAssignCta, setShowLegacyAssignCta] = useState(false);
|
||||||
const [hasS0, setHasS0] = useState<boolean | null>(null);
|
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 [mnemonicWords, setMnemonicWords] = useState<string[]>([]);
|
||||||
const [mnemonicParts, setMnemonicParts] = useState<string[][]>([]);
|
const [mnemonicParts, setMnemonicParts] = useState<string[][]>([]);
|
||||||
const [mnemonicStep, setMnemonicStep] = useState<1 | 2 | 3 | 4 | 5>(1);
|
const [mnemonicStep, setMnemonicStep] = useState<1 | 2 | 3 | 4 | 5>(1);
|
||||||
@@ -504,6 +475,61 @@ export default function VaultScreen() {
|
|||||||
setSelectedAsset(null);
|
setSelectedAsset(null);
|
||||||
setShowKeyPreview(false);
|
setShowKeyPreview(false);
|
||||||
setShowGuardedBiometric(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 = () => {
|
const handleGuardedAccess = () => {
|
||||||
@@ -513,6 +539,7 @@ export default function VaultScreen() {
|
|||||||
const handleGuardedSuccess = () => {
|
const handleGuardedSuccess = () => {
|
||||||
setShowGuardedBiometric(false);
|
setShowGuardedBiometric(false);
|
||||||
setShowKeyPreview(true);
|
setShowKeyPreview(true);
|
||||||
|
handleFetchBackup();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddVerification = () => {
|
const handleAddVerification = () => {
|
||||||
@@ -1464,6 +1491,14 @@ export default function VaultScreen() {
|
|||||||
<MaterialCommunityIcons name="refresh" size={18} color={colors.vault.primary} />
|
<MaterialCommunityIcons name="refresh" size={18} color={colors.vault.primary} />
|
||||||
<Text style={styles.actionText}>Reset Sentinel Timer</Text>
|
<Text style={styles.actionText}>Reset Sentinel Timer</Text>
|
||||||
</TouchableOpacity>
|
</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>
|
||||||
|
|
||||||
<View style={styles.guardCard}>
|
<View style={styles.guardCard}>
|
||||||
@@ -1474,6 +1509,7 @@ export default function VaultScreen() {
|
|||||||
<Text style={styles.guardText}>
|
<Text style={styles.guardText}>
|
||||||
Plaintext access requires biometric verification and a memory rehearsal step.
|
Plaintext access requires biometric verification and a memory rehearsal step.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.guardButton}
|
style={styles.guardButton}
|
||||||
onPress={handleGuardedAccess}
|
onPress={handleGuardedAccess}
|
||||||
@@ -1481,10 +1517,13 @@ export default function VaultScreen() {
|
|||||||
>
|
>
|
||||||
<Text style={styles.guardButtonText}>Begin Verification</Text>
|
<Text style={styles.guardButtonText}>Begin Verification</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{showKeyPreview && (
|
{showKeyPreview && (
|
||||||
<View style={styles.previewCard}>
|
<View style={styles.previewCard}>
|
||||||
<Text style={styles.previewLabel}>MNEMONIC SHARD (MASKED)</Text>
|
<Text style={styles.previewLabel}>LOCAL PLAINTEXT BACKUP</Text>
|
||||||
<Text style={styles.previewValue}>ocean-anchored-ember-veil</Text>
|
<Text style={styles.previewValue}>
|
||||||
|
{isFetchingBackup ? 'Fetching content...' : (backupContent || 'No local backup found for this treasure')}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -1509,6 +1548,51 @@ export default function VaultScreen() {
|
|||||||
isDark
|
isDark
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</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>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1777,6 +1861,73 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: 'rgba(26, 58, 74, 0.8)',
|
backgroundColor: 'rgba(26, 58, 74, 0.8)',
|
||||||
justifyContent: 'flex-end',
|
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: {
|
modalContent: {
|
||||||
backgroundColor: colors.nautical.cream,
|
backgroundColor: colors.nautical.cream,
|
||||||
borderTopLeftRadius: borderRadius.xxl,
|
borderTopLeftRadius: borderRadius.xxl,
|
||||||
@@ -2581,8 +2732,66 @@ const styles = StyleSheet.create({
|
|||||||
borderColor: colors.sentinel.cardBorder,
|
borderColor: colors.sentinel.cardBorder,
|
||||||
},
|
},
|
||||||
mnemonicSecondaryText: {
|
mnemonicSecondaryText: {
|
||||||
color: colors.sentinel.text,
|
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
letterSpacing: typography.letterSpacing.wide,
|
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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -245,4 +245,44 @@ export const assetsService = {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an asset
|
||||||
|
* @param assetId - ID of the asset to delete
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns Success message
|
||||||
|
*/
|
||||||
|
async deleteAsset(assetId: number, token: string): Promise<{ message: string }> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('Delete Asset', `Using mock mode for ID: ${assetId}`);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ message: 'Asset deleted successfully' });
|
||||||
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.ASSETS.DELETE);
|
||||||
|
logApiDebug('Delete Asset URL', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify({ asset_id: assetId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('Delete Asset Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Failed to delete asset');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Delete asset error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||||||
const STORAGE_KEYS = {
|
const STORAGE_KEYS = {
|
||||||
CHAT_HISTORY: '@sentinel:chat_history',
|
CHAT_HISTORY: '@sentinel:chat_history',
|
||||||
CURRENT_MESSAGES: '@sentinel:current_messages',
|
CURRENT_MESSAGES: '@sentinel:current_messages',
|
||||||
|
ASSET_BACKUP: '@sentinel:asset_backup',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -115,6 +116,32 @@ export const storageService = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error clearing storage data:', e);
|
console.error('Error clearing storage data:', e);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the plaintext backup of an asset locally
|
||||||
|
*/
|
||||||
|
async saveAssetBackup(assetId: number, content: string, userId: string | number): Promise<void> {
|
||||||
|
try {
|
||||||
|
const key = `${this.getUserKey(STORAGE_KEYS.ASSET_BACKUP, userId)}:${assetId}`;
|
||||||
|
await AsyncStorage.setItem(key, content);
|
||||||
|
console.log(`[Storage] Saved asset backup for user ${userId}, asset ${assetId}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error saving asset backup for asset ${assetId}:`, e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the plaintext backup of an asset locally
|
||||||
|
*/
|
||||||
|
async getAssetBackup(assetId: number, userId: string | number): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const key = `${this.getUserKey(STORAGE_KEYS.ASSET_BACKUP, userId)}:${assetId}`;
|
||||||
|
return await AsyncStorage.getItem(key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error getting asset backup for asset ${assetId}:`, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export const VAULT_ASSET_TYPES: VaultAssetType[] = [
|
|||||||
'custom',
|
'custom',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const initialVaultAssets: VaultAsset[] = [];
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Mapping
|
// Mapping
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -60,41 +62,3 @@ export function mapApiAssetsToVaultAssets(apiList: ApiAsset[]): VaultAsset[] {
|
|||||||
return apiList.map(mapApiAssetToVaultAsset);
|
return apiList.map(mapApiAssetToVaultAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// Mock / initial data (fallback when API is unavailable)
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export const initialVaultAssets: 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,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
Reference in New Issue
Block a user