Files
luggage-list/src/modals/BackupModal.js
Space-Banane 0a8444700e
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
Full UI 180 & Overall improvements
2026-04-19 00:12:16 +02:00

164 lines
6.6 KiB
JavaScript

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,
onClose,
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="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={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="#71717a"
/>
<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>
</KeyboardAvoidingView>
</View>
</Modal>
);
}