Full UI 180 & Overall improvements
Some checks failed
Luggage List Build / build-web (push) Successful in 31s
Luggage List Build / build-android (push) Failing after 1m24s
Luggage List Build / release (push) Has been skipped

This commit is contained in:
Space-Banane
2026-04-19 00:12:16 +02:00
parent 0057290055
commit 0a8444700e
45 changed files with 9468 additions and 1390 deletions

View File

@@ -1,6 +1,8 @@
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, Text, TextInput, View } from 'react-native';
import Ionicons from '@expo/vector-icons/Ionicons';
import { styles } from '../styles';
import { cn } from '../utils/cn';
export default function BackupModal({
visible,
@@ -8,42 +10,149 @@ export default function BackupModal({
exportJson,
importJson,
setImportJson,
exportFile,
importFile,
applyImport,
}) {
const exportInputRef = useRef(null);
const [showExportJson, setShowExportJson] = useState(false);
const [copyState, setCopyState] = useState('idle');
useEffect(() => {
if (!visible) return;
setShowExportJson(false);
setCopyState('idle');
}, [visible]);
useEffect(() => {
if (copyState !== 'copied') return undefined;
const timeout = setTimeout(() => setCopyState('idle'), 1400);
return () => clearTimeout(timeout);
}, [copyState]);
async function handleCopyData() {
if (Platform.OS === 'web' && typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(exportJson);
setCopyState('copied');
return;
} catch {
// fall through to manual copy fallback
}
}
setShowExportJson(true);
setCopyState('manual');
if (Platform.OS === 'android') {
return;
}
requestAnimationFrame(() => {
exportInputRef.current?.focus?.();
if (exportInputRef.current?.setNativeProps) {
exportInputRef.current.setNativeProps({
selection: { start: 0, end: exportJson.length },
});
}
});
}
return (
<Modal visible={visible} animationType="slide" transparent>
<View style={styles.modalBackdrop}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} style={styles.modalKeyboardWrap}>
<View style={styles.modalCard}>
<View style={styles.sectionRow}>
<Text style={styles.sectionTitle}>Backup & Restore</Text>
<Pressable onPress={onClose}>
<Text style={styles.closeText}>Close</Text>
<Modal visible={visible} animationType="none" transparent>
<View className={styles.modalBackdrop}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} className={styles.modalKeyboardWrap}>
<View className={styles.modalCard}>
<View className={styles.sectionHeader}>
<View className={styles.sectionHeaderLeft}>
<View className={styles.sectionHeaderIconWrap}>
<Ionicons name="cloud-upload-outline" size={16} color="#d4d4d8" />
</View>
<Text className={styles.cardTitle}>Backup & Restore</Text>
</View>
<Pressable className={styles.secondaryBtnTight} onPress={onClose}>
<View className={styles.buttonContent}>
<Ionicons name="close" size={14} color="#f4f4f5" />
<Text className={styles.secondaryBtnText}>Close</Text>
</View>
</Pressable>
</View>
<ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{ paddingBottom: 12 }} showsVerticalScrollIndicator={false}>
<Text style={styles.cardTitle}>Export JSON</Text>
<Text style={styles.cardMeta}>Copy this JSON and store it safely.</Text>
<TextInput
style={styles.inputMultiline}
multiline
value={exportJson}
editable={false}
selectTextOnFocus
/>
<Text style={styles.cardTitle}>Import JSON</Text>
<Text style={styles.cardMeta}>Paste a previous backup. This will replace current data.</Text>
<ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={styles.modalScrollContent} showsVerticalScrollIndicator={false}>
<View className={styles.sectionHeaderLeft}>
<Ionicons name="download-outline" size={14} color="#d4d4d8" />
<Text className={styles.cardTitle}>Export JSON</Text>
</View>
<Text className={styles.cardMeta}>Use Show JSON or Copy Data.</Text>
<View className={styles.actionRow}>
<Pressable className={cn(styles.secondaryBtnTight, styles.flex)} onPress={() => setShowExportJson((prev) => !prev)}>
<View className={styles.buttonContent}>
<Ionicons name={showExportJson ? 'eye-off-outline' : 'eye-outline'} size={14} color="#f4f4f5" />
<Text className={styles.secondaryBtnText}>{showExportJson ? 'Hide JSON' : 'Show JSON'}</Text>
</View>
</Pressable>
<Pressable className={cn(styles.primaryBtnTight, styles.flex)} onPress={handleCopyData}>
<View className={styles.buttonContent}>
<Ionicons name={copyState === 'copied' ? 'checkmark-circle-outline' : 'copy-outline'} size={14} color="#ffffff" />
<Text className={styles.primaryBtnText}>{copyState === 'copied' ? 'Copied' : 'Copy Data'}</Text>
</View>
</Pressable>
</View>
<Pressable className={styles.secondaryBtn} onPress={exportFile}>
<View className={styles.buttonContent}>
<Ionicons name="share-social-outline" size={15} color="#f4f4f5" />
<Text className={styles.secondaryBtnText}>Export File</Text>
</View>
</Pressable>
{copyState === 'manual' ? (
<Text className={styles.cardMeta}>Direct clipboard is unavailable here. JSON is shown for manual copy.</Text>
) : null}
{showExportJson ? (
Platform.OS === 'android' ? (
<View style={styles.exportJsonBlock}>
<Text style={styles.exportJsonText} selectable>{exportJson}</Text>
</View>
) : (
<TextInput
ref={exportInputRef}
style={styles.inputMultiline}
multiline
value={exportJson}
editable={false}
selectTextOnFocus
/>
)
) : null}
<View className={styles.sectionHeaderLeft}>
<Ionicons name="cloud-upload-outline" size={14} color="#d4d4d8" />
<Text className={styles.cardTitle}>Import JSON</Text>
</View>
<Text className={styles.cardMeta}>Paste a previous backup. This will replace current data.</Text>
<Pressable className={styles.secondaryBtn} onPress={importFile}>
<View className={styles.buttonContent}>
<Ionicons name="document-outline" size={15} color="#f4f4f5" />
<Text className={styles.secondaryBtnText}>Pick Backup File</Text>
</View>
</Pressable>
<TextInput
style={styles.inputMultiline}
multiline
value={importJson}
onChangeText={setImportJson}
placeholder="Paste backup JSON here"
placeholderTextColor="#6b7280"
placeholderTextColor="#71717a"
/>
<Pressable style={styles.primaryBtn} onPress={applyImport}>
<Text style={styles.primaryBtnText}>Import & Replace</Text>
<Pressable className={styles.primaryBtn} onPress={applyImport}>
<View className={styles.buttonContent}>
<Ionicons name="sync-outline" size={15} color="#ffffff" />
<Text className={styles.primaryBtnText}>Import & Replace</Text>
</View>
</Pressable>
</ScrollView>
</View>