feat(vault): add get/create assets API in workflow
TODO: update vault.service.ts. Use MNEMONIC workflow to create real private_key_shard and content_inner_encrypted
This commit is contained in:
@@ -24,6 +24,7 @@ import { colors, typography, spacing, borderRadius, shadows } from '../theme/col
|
||||
import { VaultAsset, VaultAssetType, Heir } from '../types';
|
||||
import BiometricModal from '../components/common/BiometricModal';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useVaultAssets } from '../hooks/useVaultAssets';
|
||||
|
||||
// Asset type configuration with nautical theme
|
||||
const assetTypeConfig: Record<VaultAssetType, { icon: string; iconType: 'ionicons' | 'feather' | 'material' | 'fontawesome5'; label: string }> = {
|
||||
@@ -89,40 +90,40 @@ type HeirAssignment = {
|
||||
};
|
||||
|
||||
// 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 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) {
|
||||
@@ -140,7 +141,15 @@ const renderAssetTypeIcon = (config: typeof assetTypeConfig[VaultAssetType], siz
|
||||
export default function VaultScreen() {
|
||||
const [isUnlocked, setIsUnlocked] = useState(false);
|
||||
const [showBiometric, setShowBiometric] = useState(false);
|
||||
const [assets, setAssets] = useState<VaultAsset[]>(initialAssets);
|
||||
const {
|
||||
assets,
|
||||
setAssets,
|
||||
refreshAssets,
|
||||
createAsset: createVaultAsset,
|
||||
isSealing,
|
||||
createError: addError,
|
||||
clearCreateError: clearAddError,
|
||||
} = useVaultAssets(isUnlocked);
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<VaultAssetType>('custom');
|
||||
const [newLabel, setNewLabel] = useState('');
|
||||
@@ -170,8 +179,9 @@ export default function VaultScreen() {
|
||||
const [replaceQuery, setReplaceQuery] = useState('');
|
||||
const [progressIndex, setProgressIndex] = useState(0);
|
||||
const [progressAnim] = useState(new Animated.Value(0));
|
||||
const { user } = useAuth();
|
||||
const { user, token } = useAuth();
|
||||
const [isCapturing, setIsCapturing] = useState(false);
|
||||
const [treasureContent, setTreasureContent] = useState('');
|
||||
const mnemonicRef = useRef<View>(null);
|
||||
const progressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
@@ -375,29 +385,52 @@ export default function VaultScreen() {
|
||||
setAccountProvider('bank');
|
||||
};
|
||||
|
||||
const handleAddAsset = () => {
|
||||
if (!newLabel.trim()) return;
|
||||
const handleAddAsset = async () => {
|
||||
if (!newLabel.trim() || !treasureContent.trim()) return;
|
||||
if (!addVerified) return;
|
||||
if (selectedType === 'private_key' && !rehearsalConfirmed) return;
|
||||
if (!token) {
|
||||
Alert.alert('Not logged in', 'Please sign in first to add a Treasure.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newAsset: VaultAsset = {
|
||||
id: Date.now().toString(),
|
||||
type: selectedType,
|
||||
label: newLabel,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
isEncrypted: true,
|
||||
};
|
||||
const result = await createVaultAsset({
|
||||
title: newLabel.trim(),
|
||||
content: treasureContent.trim(),
|
||||
});
|
||||
|
||||
setAssets([newAsset, ...assets]);
|
||||
setNewLabel('');
|
||||
setSelectedType('custom');
|
||||
setShowAddModal(false);
|
||||
setAddVerified(false);
|
||||
setRehearsalConfirmed(false);
|
||||
|
||||
setShowUploadSuccess(true);
|
||||
setTimeout(() => setShowUploadSuccess(false), 2500);
|
||||
if (result.success) {
|
||||
setNewLabel('');
|
||||
setTreasureContent('');
|
||||
setSelectedType('custom');
|
||||
setAddVerified(false);
|
||||
setRehearsalConfirmed(false);
|
||||
setShowAddModal(false);
|
||||
clearAddError();
|
||||
setShowUploadSuccess(true);
|
||||
setTimeout(() => setShowUploadSuccess(false), 2500);
|
||||
if (typeof Alert !== 'undefined' && Alert.alert) {
|
||||
Alert.alert('Success', 'Treasure sealed and saved successfully.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.isUnauthorized) {
|
||||
setShowAddModal(false);
|
||||
clearAddError();
|
||||
if (typeof Alert !== 'undefined' && Alert.alert) {
|
||||
Alert.alert(
|
||||
'Unauthorized',
|
||||
'Your session has expired or you are not logged in. Please sign in again.',
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.error && typeof Alert !== 'undefined' && Alert.alert) {
|
||||
Alert.alert('Failed', result.error);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
@@ -485,7 +518,9 @@ export default function VaultScreen() {
|
||||
? '12/24 Words'
|
||||
: selectedConfig?.label || '--';
|
||||
const canSeal = !!newLabel.trim()
|
||||
&& !!treasureContent.trim()
|
||||
&& addVerified
|
||||
&& !isSealing
|
||||
&& (selectedType !== 'private_key' || rehearsalConfirmed);
|
||||
|
||||
const mnemonicModal = (
|
||||
@@ -499,10 +534,10 @@ export default function VaultScreen() {
|
||||
style={styles.mnemonicOverlay}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
>
|
||||
<View ref={mnemonicRef} collapsable={false}>
|
||||
<LinearGradient
|
||||
colors={[colors.sentinel.cardBackground, colors.sentinel.backgroundGradientEnd]}
|
||||
style={styles.mnemonicCard}
|
||||
ref={mnemonicRef}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.mnemonicClose}
|
||||
@@ -821,6 +856,7 @@ export default function VaultScreen() {
|
||||
<View style={[styles.stepDot, mnemonicStep !== 1 && styles.stepDotActive]} />
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Modal>
|
||||
);
|
||||
@@ -963,6 +999,7 @@ export default function VaultScreen() {
|
||||
style={styles.addButton}
|
||||
onPress={() => {
|
||||
resetAddFlow();
|
||||
clearAddError();
|
||||
setShowAddModal(true);
|
||||
}}
|
||||
activeOpacity={0.9}
|
||||
@@ -1005,7 +1042,7 @@ export default function VaultScreen() {
|
||||
</View>
|
||||
|
||||
<View style={styles.stepRow}>
|
||||
{['Type', 'Method', 'Verify'].map((label, index) => {
|
||||
{['Title', 'Content', 'Verify'].map((label, index) => {
|
||||
const stepIndex = index + 1;
|
||||
const isActive = addStep === stepIndex;
|
||||
const isDone = addStep > stepIndex;
|
||||
@@ -1034,6 +1071,14 @@ export default function VaultScreen() {
|
||||
|
||||
{addStep === 1 && (
|
||||
<>
|
||||
<Text style={styles.modalLabel}>TREASURE TITLE</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="e.g., Main wallet mnemonic"
|
||||
placeholderTextColor={colors.nautical.sage}
|
||||
value={newLabel}
|
||||
onChangeText={setNewLabel}
|
||||
/>
|
||||
<Text style={styles.modalLabel}>TREASURE TYPE</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
@@ -1093,9 +1138,25 @@ export default function VaultScreen() {
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<Text style={styles.modalLabel}>CONTENT</Text>
|
||||
<TextInput
|
||||
style={[styles.input, styles.inputMultiline]}
|
||||
placeholder="Enter content to seal (plaintext is encrypted locally before upload)"
|
||||
placeholderTextColor={colors.nautical.sage}
|
||||
value={treasureContent}
|
||||
onChangeText={setTreasureContent}
|
||||
multiline
|
||||
numberOfLines={6}
|
||||
textAlignVertical="top"
|
||||
/>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedType === 'game_account' && (
|
||||
<>
|
||||
<Text style={styles.modalLabel}>ACCOUNT PROVIDER</Text>
|
||||
@@ -1135,24 +1196,16 @@ export default function VaultScreen() {
|
||||
<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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1201,14 +1254,24 @@ export default function VaultScreen() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{addError ? (
|
||||
<View style={styles.addErrorBox}>
|
||||
<Ionicons name="warning" size={18} color="#c2410c" />
|
||||
<Text style={styles.addErrorText}>{addError}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={() => {
|
||||
if (addStep === 1) {
|
||||
setShowAddModal(false);
|
||||
setTreasureContent('');
|
||||
clearAddError();
|
||||
} else {
|
||||
setAddStep(addStep - 1);
|
||||
clearAddError();
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -1248,7 +1311,7 @@ export default function VaultScreen() {
|
||||
end={{ x: 1, y: 0 }}
|
||||
>
|
||||
<MaterialCommunityIcons name="lock" size={18} color="#fff" />
|
||||
<Text style={styles.confirmButtonText}>Seal Treasure</Text>
|
||||
<Text style={styles.confirmButtonText}>{isSealing ? 'Sealing...' : 'Seal Treasure'}</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
@@ -1344,7 +1407,7 @@ export default function VaultScreen() {
|
||||
<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} />
|
||||
<MaterialCommunityIcons name="refresh" size={18} color={colors.vault.primary} />
|
||||
<Text style={styles.actionText}>Reset Sentinel Timer</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -1835,6 +1898,10 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1,
|
||||
borderColor: colors.nautical.lightMint,
|
||||
},
|
||||
inputMultiline: {
|
||||
minHeight: 120,
|
||||
paddingTop: spacing.base,
|
||||
},
|
||||
encryptionNote: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -1850,6 +1917,23 @@ const styles = StyleSheet.create({
|
||||
color: colors.nautical.teal,
|
||||
lineHeight: typography.fontSize.sm * 1.4,
|
||||
},
|
||||
addErrorBox: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(194, 65, 12, 0.12)',
|
||||
borderRadius: borderRadius.lg,
|
||||
padding: spacing.md,
|
||||
marginBottom: spacing.md,
|
||||
gap: spacing.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(194, 65, 12, 0.3)',
|
||||
},
|
||||
addErrorText: {
|
||||
flex: 1,
|
||||
fontSize: typography.fontSize.sm,
|
||||
color: '#c2410c',
|
||||
lineHeight: typography.fontSize.sm * 1.4,
|
||||
},
|
||||
modalButtons: {
|
||||
flexDirection: 'row',
|
||||
gap: spacing.md,
|
||||
|
||||
Reference in New Issue
Block a user