This commit is contained in:
everythingonblack
2025-07-05 15:11:15 +07:00
parent 68fd4e6b33
commit 23ee6fe309
2 changed files with 326 additions and 2 deletions

317
src/Camera.js Normal file
View File

@@ -0,0 +1,317 @@
import React, { useState, useRef, useEffect } from 'react';
const CameraPage = ({ handleClose, handleUploadImage }) => {
const [image, setImage] = useState(null);
const [isCameraActive, setIsCameraActive] = useState(true);
const [uploading, setUploading] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const videoRef = useRef(null);
const canvasRef = useRef(null);
const fileInputRef = useRef(null);
// Check if device is mobile
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
// Start the camera when the component mounts
useEffect(() => {
const startCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing camera:', error);
}
};
startCamera();
// Cleanup camera stream when component unmounts
return () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = videoRef.current.srcObject.getTracks();
tracks.forEach(track => track.stop());
}
};
}, []);
// Capture the image from the video stream
const captureImage = () => {
const canvas = canvasRef.current;
const video = videoRef.current;
if (video && canvas) {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const capturedImage = canvas.toDataURL('image/jpeg');
setImage(capturedImage);
setIsCameraActive(false);
}
};
// Handle image upload from file input
const handleFileUpload = e => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setImage(reader.result);
setIsCameraActive(false);
};
reader.readAsDataURL(file);
}
};
// Cancel the image capture or file upload and restart the camera
const cancelCapture = () => {
setImage(null);
setIsCameraActive(true);
const startCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
} catch (error) {
console.error('Error accessing camera:', error);
}
};
startCamera();
};
// Trigger file input click
const triggerFileInput = () => {
fileInputRef.current?.click();
};
const mainContent = (
<div style={containerStyle}>
{/* Camera/Image Display Area */}
<div style={cameraContainerStyle}>
{isCameraActive && (
<video ref={videoRef} autoPlay playsInline style={videoStyle} />
)}
{!isCameraActive && image && (
<img src={image} alt="Captured or Uploaded" style={imageStyle} />
)}
{/* Floating Controls */}
<div style={controlsStyle}>
{isCameraActive ? (
<>
<button
onClick={handleClose}
style={cancelButtonStyle}
aria-label="Cancel and retake"
>
</button>
<button
onClick={captureImage}
style={captureButtonStyle}
aria-label="Capture photo"
>
📸
</button>
<button
onClick={triggerFileInput}
style={uploadButtonStyle}
aria-label="Upload from gallery"
>
📁
</button>
</>
) : (
<>
<button
onClick={cancelCapture}
style={cancelButtonStyle}
aria-label="Cancel and retake"
>
</button>
<button
onClick={() => handleUploadImage(image)} // Pass image data here
style={confirmButtonStyle}
disabled={uploading}
aria-label={uploading ? 'Uploading...' : 'Confirm upload'}
>
{uploading ? '⏳' : '✅'}
</button>
</>
)}
</div>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handleFileUpload}
/>
</div>
{/* Hidden canvas element for capturing the image */}
<canvas ref={canvasRef} style={{ display: 'none' }}></canvas>
</div>
);
// Desktop layout with left and right sidebars
if (!isMobile) {
return (
<div style={desktopLayoutStyle}>
{/* Left Sidebar */}
<div style={sidebarStyle}></div>
{/* Main content */}
<div style={mainContentStyle}>{mainContent}</div>
{/* Right Sidebar */}
<div style={sidebarStyle}></div>
</div>
);
}
// Mobile layout (full screen)
return mainContent;
};
// Styles
const containerStyle = {
position: 'absolute',
width: '100%',
height: '100vh',
backgroundColor: '#000',
overflow: 'hidden',
};
const cameraContainerStyle = {
position: 'relative',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
};
const videoStyle = {
width: '100%',
height: '100%',
objectFit: 'cover',
};
const imageStyle = {
width: '100%',
height: '100%',
objectFit: 'cover',
};
const controlsStyle = {
position: 'absolute',
bottom: '30px',
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
gap: '20px',
alignItems: 'center',
};
const baseButtonStyle = {
width: '60px',
height: '60px',
borderRadius: '50%',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
transition: 'all 0.2s ease',
};
const captureButtonStyle = {
...baseButtonStyle,
backgroundColor: '#fff',
color: '#000',
};
const uploadButtonStyle = {
...baseButtonStyle,
backgroundColor: '#4CAF50',
color: '#fff',
};
const cancelButtonStyle = {
...baseButtonStyle,
backgroundColor: '#f44336',
color: '#fff',
};
const confirmButtonStyle = {
...baseButtonStyle,
backgroundColor: '#4CAF50',
color: '#fff',
};
// Desktop styles
const desktopLayoutStyle = {
display: 'flex',
height: '100vh',
fontFamily: 'Arial, sans-serif',
};
const sidebarStyle = {
width: '250px',
backgroundColor: '#fff',
color: '#333',
padding: '20px',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
borderRight: '1px solid #e0e0e0',
};
const sidebarTitleStyle = {
margin: '0 0 30px 0',
fontSize: '24px',
fontWeight: 'bold',
color: '#333',
};
const sidebarContentStyle = {
display: 'flex',
flexDirection: 'column',
gap: '15px',
};
const sidebarTextStyle = {
margin: '0',
fontSize: '16px',
lineHeight: '1.4',
color: '#666',
};
const mainContentStyle = {
flex: 1,
position: 'relative',
};
export default CameraPage;

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import styles from './ChatBot.module.css';
import Camera from './Camera'
const ChatBot = ({ existingConversation }) => {
const [messages, setMessages] = useState([
@@ -22,6 +23,8 @@ const ChatBot = ({ existingConversation }) => {
const [isPoppedUp, setIsPoppedUp] = useState('');
const [name, setName] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [isOpenCamera, setIsOpenCamera] = useState(false);
useEffect(() => {
if (existingConversation && existingConversation.length > 0) {
@@ -129,6 +132,9 @@ function formatBoldText(text) {
});
}
const handleUploadImage = (e) => {
console.log(e)
}
return (
<div className={styles.chatContainer} >
@@ -175,11 +181,11 @@ function formatBoldText(text) {
))}
<div
className={styles.quickReply}
onClick={() => sendMessage('analist')}
onClick={() => setIsOpenCamera(true)}
style={{color: 'white', backgroundColor: '#075e54', display: 'flex', flexDirection: 'row', alignItems:'center'}}
>
<img style={{marginRight: '5px', height: '14px', filter: 'invert(1)'}}src={'/face.png'}/>
Analisa Wajah
Analisa Kulit
</div>
</div>
)}
@@ -260,6 +266,7 @@ function formatBoldText(text) {
</div>
</div>
}
{isOpenCamera && <Camera handleClose={()=>setIsOpenCamera(false)} handleUploadImage={(e)=>handleUploadImage(e)}/>}
</div>
);
};