mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-21 22:28:19 +02:00
Improve photo capture reliability and address camera access issues
Adds enhanced camera setup, error handling, and canvas drawing in `useCamera.tsx`, `CameraModal.tsx`, and `NewSaleForm.tsx`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: dec54137-d5a2-4406-8ad4-5f2f9fb110bf Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/e6a2493b-9ed8-4bd8-be32-29049cc60fa3.jpg
This commit is contained in:
1 parent
77d77ab658
commit
fbbe1375ab
3 files changed
+118
-25
No files matched your search
@@ -26,7 +26,31 @@ export default function CameraModal({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && videoRef.current) {
|
if (isOpen && videoRef.current) {
|
||||||
// Ensure video starts playing when modal opens
|
// Ensure video starts playing when modal opens
|
||||||
videoRef.current.play().catch(console.error);
|
const video = videoRef.current;
|
||||||
|
|
||||||
|
const handleLoadedMetadata = () => {
|
||||||
|
console.log('Video loaded metadata:', video.videoWidth, 'x', video.videoHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCanPlay = () => {
|
||||||
|
console.log('Video can play');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (error: Event) => {
|
||||||
|
console.error('Video error:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
video.addEventListener('loadedmetadata', handleLoadedMetadata);
|
||||||
|
video.addEventListener('canplay', handleCanPlay);
|
||||||
|
video.addEventListener('error', handleError);
|
||||||
|
|
||||||
|
video.play().catch(console.error);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
video.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
||||||
|
video.removeEventListener('canplay', handleCanPlay);
|
||||||
|
video.removeEventListener('error', handleError);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}, [isOpen, videoRef]);
|
}, [isOpen, videoRef]);
|
||||||
|
|
||||||
@@ -57,10 +81,13 @@ export default function CameraModal({
|
|||||||
|
|
||||||
{/* Overlay pour aider à cadrer la pièce d'identité */}
|
{/* Overlay pour aider à cadrer la pièce d'identité */}
|
||||||
<div className="absolute inset-0 pointer-events-none">
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
<div className="absolute inset-4 border-2 border-white border-dashed rounded-lg opacity-50"></div>
|
<div className="absolute inset-4 border-2 border-white border-dashed rounded-lg opacity-70"></div>
|
||||||
<div className="absolute bottom-2 left-2 text-white text-sm bg-black bg-opacity-50 px-2 py-1 rounded">
|
<div className="absolute bottom-2 left-2 text-white text-sm bg-black bg-opacity-70 px-2 py-1 rounded">
|
||||||
Cadrez la pièce d'identité dans le rectangle
|
Cadrez la pièce d'identité dans le rectangle
|
||||||
</div>
|
</div>
|
||||||
|
<div className="absolute top-2 right-2 text-white text-xs bg-black bg-opacity-70 px-2 py-1 rounded">
|
||||||
|
{photoType === 'recto' ? 'RECTO' : 'VERSO'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,7 +99,7 @@ export default function CameraModal({
|
|||||||
<Button
|
<Button
|
||||||
onClick={onCapture}
|
onClick={onCapture}
|
||||||
disabled={isCapturing}
|
disabled={isCapturing}
|
||||||
className="bg-primary hover:bg-primary/90 flex-1"
|
className="bg-green-600 hover:bg-green-700 text-white flex-1"
|
||||||
>
|
>
|
||||||
<Camera className="h-4 w-4 mr-2" />
|
<Camera className="h-4 w-4 mr-2" />
|
||||||
{isCapturing ? 'Capture...' : 'Prendre la photo'}
|
{isCapturing ? 'Capture...' : 'Prendre la photo'}
|
||||||
|
|||||||
@@ -205,7 +205,10 @@ export default function NewSaleForm() {
|
|||||||
if (!currentPhotoType) return;
|
if (!currentPhotoType) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('Attempting to capture photo...');
|
||||||
const photoData = await capturePhoto();
|
const photoData = await capturePhoto();
|
||||||
|
console.log('Photo captured successfully, data length:', photoData.length);
|
||||||
|
|
||||||
form.setValue(currentPhotoType === 'recto' ? 'photoRecto' : 'photoVerso', photoData);
|
form.setValue(currentPhotoType === 'recto' ? 'photoRecto' : 'photoVerso', photoData);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@@ -216,6 +219,7 @@ export default function NewSaleForm() {
|
|||||||
|
|
||||||
stopCamera();
|
stopCamera();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Photo capture error:', error);
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur de capture",
|
title: "Erreur de capture",
|
||||||
description: error instanceof Error ? error.message : "Erreur lors de la capture",
|
description: error instanceof Error ? error.message : "Erreur lors de la capture",
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ export function useCamera() {
|
|||||||
|
|
||||||
const startCamera = useCallback(async (photoType: PhotoType) => {
|
const startCamera = useCallback(async (photoType: PhotoType) => {
|
||||||
try {
|
try {
|
||||||
|
// Vérification des permissions avant d'essayer
|
||||||
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
||||||
|
console.log('Available video devices:', videoDevices.length);
|
||||||
|
|
||||||
|
if (videoDevices.length === 0) {
|
||||||
|
throw new Error('Aucune caméra détectée sur cet appareil');
|
||||||
|
}
|
||||||
|
|
||||||
// Configuration avancée pour tablettes et appareils mobiles
|
// Configuration avancée pour tablettes et appareils mobiles
|
||||||
const constraints = {
|
const constraints = {
|
||||||
video: {
|
video: {
|
||||||
@@ -28,6 +37,7 @@ export function useCamera() {
|
|||||||
try {
|
try {
|
||||||
// Essai avec caméra arrière
|
// Essai avec caméra arrière
|
||||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
console.log('Back camera started successfully');
|
||||||
} catch (backCameraError) {
|
} catch (backCameraError) {
|
||||||
console.warn('Caméra arrière non disponible, essai avec caméra avant:', backCameraError);
|
console.warn('Caméra arrière non disponible, essai avec caméra avant:', backCameraError);
|
||||||
|
|
||||||
@@ -44,6 +54,7 @@ export function useCamera() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
stream = await navigator.mediaDevices.getUserMedia(frontConstraints);
|
stream = await navigator.mediaDevices.getUserMedia(frontConstraints);
|
||||||
|
console.log('Front camera started successfully');
|
||||||
} catch (frontCameraError) {
|
} catch (frontCameraError) {
|
||||||
console.warn('Caméra avant non disponible, essai basique:', frontCameraError);
|
console.warn('Caméra avant non disponible, essai basique:', frontCameraError);
|
||||||
|
|
||||||
@@ -51,6 +62,7 @@ export function useCamera() {
|
|||||||
stream = await navigator.mediaDevices.getUserMedia({
|
stream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: true
|
video: true
|
||||||
});
|
});
|
||||||
|
console.log('Basic camera started successfully');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +72,14 @@ export function useCamera() {
|
|||||||
|
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.srcObject = stream;
|
videoRef.current.srcObject = stream;
|
||||||
|
|
||||||
|
// Attendre que la vidéo soit prête
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
videoRef.current!.onloadedmetadata = () => {
|
||||||
|
console.log('Video metadata loaded');
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -83,36 +103,78 @@ export function useCamera() {
|
|||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
setIsCapturing(false);
|
||||||
reject(new Error('Canvas context not available'));
|
reject(new Error('Canvas context not available'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vérifier que la vidéo a des dimensions valides
|
||||||
|
if (video.videoWidth === 0 || video.videoHeight === 0) {
|
||||||
|
console.error('Video dimensions:', video.videoWidth, 'x', video.videoHeight);
|
||||||
|
setIsCapturing(false);
|
||||||
|
reject(new Error('Video not ready - no dimensions'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Video dimensions:', video.videoWidth, 'x', video.videoHeight);
|
||||||
|
|
||||||
|
// Définir les dimensions du canvas
|
||||||
canvas.width = video.videoWidth;
|
canvas.width = video.videoWidth;
|
||||||
canvas.height = video.videoHeight;
|
canvas.height = video.videoHeight;
|
||||||
|
|
||||||
context.drawImage(video, 0, 0);
|
// Dessiner l'image de la vidéo sur le canvas
|
||||||
|
try {
|
||||||
|
// Effacer le canvas avant de dessiner
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
console.log('Image drawn on canvas successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to draw image on canvas:', error);
|
||||||
|
setIsCapturing(false);
|
||||||
|
reject(new Error('Failed to draw image on canvas'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
canvas.toBlob(
|
// Méthode alternative plus robuste - utiliser toDataURL directement
|
||||||
(blob) => {
|
try {
|
||||||
if (!blob) {
|
const dataURL = canvas.toDataURL('image/jpeg', 0.8);
|
||||||
reject(new Error('Failed to create image blob'));
|
console.log('Canvas dataURL generated, length:', dataURL.length);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
if (!dataURL || dataURL === 'data:,') {
|
||||||
reader.onload = (e) => {
|
setIsCapturing(false);
|
||||||
setIsCapturing(false);
|
reject(new Error('Failed to create image data'));
|
||||||
resolve(e.target?.result as string);
|
return;
|
||||||
};
|
}
|
||||||
reader.onerror = () => {
|
|
||||||
setIsCapturing(false);
|
setIsCapturing(false);
|
||||||
reject(new Error('Failed to read image'));
|
resolve(dataURL);
|
||||||
};
|
} catch (error) {
|
||||||
reader.readAsDataURL(blob);
|
console.error('Canvas toDataURL error:', error);
|
||||||
},
|
|
||||||
'image/jpeg',
|
// Fallback avec toBlob si toDataURL échoue
|
||||||
0.8
|
canvas.toBlob(
|
||||||
);
|
(blob) => {
|
||||||
|
if (!blob) {
|
||||||
|
setIsCapturing(false);
|
||||||
|
reject(new Error('Failed to create image blob'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
setIsCapturing(false);
|
||||||
|
resolve(e.target?.result as string);
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
setIsCapturing(false);
|
||||||
|
reject(new Error('Failed to read image'));
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
},
|
||||||
|
'image/jpeg',
|
||||||
|
0.8
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user