proposal-system/mobile/src/components/FilePickerButton.tsx
Adam Moussa 9aef2a3891 Add mobile navigation, components, and all screens
Role-based bottom-tab navigation (dispatcher dashboard, admin workspace),
shared components (StatusChip, PriorityChip, ProposalCard, LineItemRow,
FilePickerButton, EmptyState), dispatcher screens (dashboard, new proposal
with camera/document picker and offline drafts, proposal detail), admin
screens (dashboard with stats, proposal queue, workspace with approve/
regenerate/PDF, line item editor), login, and settings.
2026-05-17 15:09:43 -04:00

130 lines
3.2 KiB
TypeScript

import React, { useState } from 'react';
import { View, StyleSheet, Alert } from 'react-native';
import { Button, Text, ProgressBar } from 'react-native-paper';
import { launchCamera } from 'react-native-image-picker';
import DocumentPicker from 'react-native-document-picker';
import axios from 'axios';
interface Props {
onFileSelected: (fileName: string, s3Key: string) => void;
getUploadUrl: (
fileName: string,
) => Promise<{ uploadUrl: string; s3Key: string }>;
}
export function FilePickerButton({ onFileSelected, getUploadUrl }: Props) {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [fileName, setFileName] = useState<string | null>(null);
const uploadFile = async (uri: string, name: string, type: string) => {
setUploading(true);
setProgress(0);
setFileName(name);
try {
const { uploadUrl, s3Key } = await getUploadUrl(name);
const file = await fetch(uri);
const blob = await file.blob();
await axios.put(uploadUrl, blob, {
headers: { 'Content-Type': type },
onUploadProgress: (e) => {
if (e.total) setProgress(e.loaded / e.total);
},
});
onFileSelected(name, s3Key);
} catch {
Alert.alert(
'Upload Failed',
'Could not upload the file. Please try again.',
);
setFileName(null);
} finally {
setUploading(false);
}
};
const pickDocument = async () => {
try {
const result = await DocumentPicker.pickSingle({
type: [DocumentPicker.types.pdf],
});
if (result.uri && result.name) {
await uploadFile(
result.uri,
result.name,
result.type || 'application/pdf',
);
}
} catch (err) {
if (!DocumentPicker.isCancel(err)) {
Alert.alert('Error', 'Could not pick document.');
}
}
};
const takePhoto = async () => {
const result = await launchCamera({ mediaType: 'photo', quality: 0.8 });
if (result.assets?.[0]?.uri) {
const asset = result.assets[0];
await uploadFile(
asset.uri!,
asset.fileName || 'vendor-proposal.jpg',
asset.type || 'image/jpeg',
);
}
};
return (
<View style={styles.container}>
<View style={styles.buttons}>
<Button
mode="outlined"
icon="file-pdf-box"
onPress={pickDocument}
disabled={uploading}
style={styles.button}
>
Choose PDF
</Button>
<Button
mode="outlined"
icon="camera"
onPress={takePhoto}
disabled={uploading}
style={styles.button}
>
Camera
</Button>
</View>
{uploading && <ProgressBar progress={progress} style={styles.progress} />}
{fileName && !uploading && (
<Text variant="bodySmall" style={styles.fileName}>
{fileName}
</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
gap: 8,
},
buttons: {
flexDirection: 'row',
gap: 12,
},
button: {
flex: 1,
},
progress: {
marginTop: 4,
},
fileName: {
color: '#2E7D32',
},
});