Initial LinkedSign platform

This commit is contained in:
2026-04-06 20:33:59 -05:00
commit 1074c7c9d2
159 changed files with 19072 additions and 0 deletions
@@ -0,0 +1,153 @@
import { Alert, FlatList, StyleSheet, Text, View } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import ScreenContainer from '../../components/ScreenContainer';
import PrimaryButton from '../../components/PrimaryButton';
import { colors } from '../../theme/colors';
import { useAuth } from '../../context/AuthContext';
import { useSync } from '../../context/SyncContext';
import { uploadDocument } from '../../api/documents';
import { downloadAndShareFile } from '../../utils/files';
import { API_BASE_URL } from '../../config/env';
function SectionCard({ title, rows, emptyText, actionLabel, onActionPress, onDownload, token, type }) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text>
{actionLabel ? <PrimaryButton title={actionLabel} onPress={onActionPress} /> : null}
{rows.length === 0 ? (
<Text style={styles.emptyText}>{emptyText}</Text>
) : (
<FlatList
data={rows}
keyExtractor={(item) => `${type}-${item.id}`}
scrollEnabled={false}
ItemSeparatorComponent={() => <View style={{ height: 10 }} />}
renderItem={({ item }) => (
<View style={styles.itemCard}>
<Text style={styles.itemTitle}>{item.title || item.signed_filename}</Text>
<Text style={styles.itemMeta}>
{item.status || item.document_title || 'Documento'}
</Text>
<PrimaryButton
title="Descargar / Compartir"
variant="secondary"
onPress={() => onDownload(item, token)}
/>
</View>
)}
/>
)}
</View>
);
}
export default function DocumentsScreen() {
const { tokens } = useAuth();
const { documents, signedDocuments, syncNow, refreshLocalCache } = useSync();
const handleUpload = async () => {
const picked = await DocumentPicker.getDocumentAsync({
type: 'application/pdf',
copyToCacheDirectory: true,
});
if (picked.canceled) return;
const file = picked.assets[0];
const formData = new FormData();
formData.append('file', {
uri: file.uri,
name: file.name,
type: 'application/pdf',
});
try {
await uploadDocument(formData);
await syncNow();
refreshLocalCache();
Alert.alert('Documentos', 'PDF subido correctamente.');
} catch (error) {
Alert.alert('Documentos', error.response?.data?.error || 'No se pudo subir el PDF.');
}
};
const handleSync = async () => {
try {
await syncNow();
Alert.alert('Documentos', 'Documentos sincronizados.');
} catch (error) {
Alert.alert('Documentos', error.response?.data?.error || 'No se pudo sincronizar.');
}
};
const handleDownloadOriginal = async (item) => {
await downloadAndShareFile({
url: `${API_BASE_URL}/documents/${item.id}/download`,
token: tokens.accessToken,
filename: item.filename || `${item.title}.pdf`,
});
};
const handleDownloadSigned = async (item) => {
await downloadAndShareFile({
url: `${API_BASE_URL}/signatures/${item.id}/download`,
token: tokens.accessToken,
filename: item.signed_filename,
});
};
return (
<ScreenContainer>
<PrimaryButton title="Sincronizar documentos" onPress={handleSync} variant="secondary" />
<SectionCard
title="Originales"
rows={documents}
emptyText="No hay documentos sincronizados en el teléfono."
actionLabel="Subir PDF"
onActionPress={handleUpload}
onDownload={handleDownloadOriginal}
token={tokens.accessToken}
type="original"
/>
<SectionCard
title="Firmados"
rows={signedDocuments}
emptyText="No hay documentos firmados sincronizados."
onDownload={handleDownloadSigned}
token={tokens.accessToken}
type="signed"
/>
</ScreenContainer>
);
}
const styles = StyleSheet.create({
section: {
backgroundColor: colors.surface,
borderRadius: 18,
padding: 16,
gap: 12,
},
sectionTitle: {
fontSize: 18,
fontWeight: '700',
color: colors.text,
},
emptyText: {
color: colors.textMuted,
},
itemCard: {
borderWidth: 1,
borderColor: colors.border,
borderRadius: 14,
padding: 14,
gap: 8,
},
itemTitle: {
color: colors.text,
fontWeight: '700',
},
itemMeta: {
color: colors.textMuted,
fontSize: 12,
},
});