Added capture photo logic in livewire
Some checks failed
Gemini PR Review / Gemini PR Review (pull_request) Waiting to run
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Waiting to run
Laravel Larastan / larastan (pull_request) Waiting to run
Laravel Pint / pint (pull_request) Waiting to run
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled

This commit is contained in:
dhanabalan
2026-05-25 15:46:47 +05:30
parent 915a766aa0
commit d6acded332
3 changed files with 231 additions and 0 deletions

32
app/Livewire/Webcam.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class Webcam extends Component
{
public string $capturedPhoto = '';
// Called from JavaScript when a photo is taken
public function setPhoto(string $photo): void
{
$this->capturedPhoto = $photo;
// Fires a browser event that the Filament form will listen to
$this->dispatch('photo-captured', photo: $photo);
}
// Called from JavaScript when user clicks "Retake"
public function clearPhoto(): void
{
$this->capturedPhoto = '';
$this->dispatch('photo-captured', photo: '');
}
public function render()
{
return view('livewire.webcam');
}
}

View File

@@ -0,0 +1,10 @@
<div>
<x-filament::section>
<x-slot name="heading">
Visitor Photo
</x-slot>
<livewire:webcam-capture />
</x-filament::section>
</div>

View File

@@ -0,0 +1,189 @@
<div
x-data="{
cameraActive: false,
captured: false,
errorMessage: '',
async startCamera() {
this.errorMessage = '';
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480, facingMode: 'user' }
});
this.$refs.video.srcObject = stream;
await this.$refs.video.play();
this.cameraActive = true;
} catch (err) {
if (err.name === 'NotAllowedError') {
this.errorMessage = 'Camera permission denied. Please allow camera access in your browser and try again.';
} else if (err.name === 'NotFoundError') {
this.errorMessage = 'No camera found. Please connect a webcam and try again.';
} else {
this.errorMessage = 'Could not access camera: ' + err.message;
}
}
},
capture() {
const canvas = this.$refs.canvas;
const video = this.$refs.video;
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
canvas.getContext('2d').drawImage(video, 0, 0);
const photoData = canvas.toDataURL('image/jpeg', 0.85);
// Stop the camera stream after capture
if (video.srcObject) {
video.srcObject.getTracks().forEach(track => track.stop());
}
this.cameraActive = false;
this.captured = true;
// Send photo data to PHP via Livewire
$wire.setPhoto(photoData);
},
retake() {
this.captured = false;
$wire.clearPhoto();
this.$nextTick(() => this.startCamera());
}
}"
style="font-family: inherit;"
>
{{-- ── Error message ── --}}
<template x-if="errorMessage">
<div style="
background: #3b1a1a;
border: 1px solid #7f2020;
color: #f87171;
padding: 10px 14px;
border-radius: 8px;
margin-bottom: 12px;
font-size: 13px;
" x-text="errorMessage"></div>
</template>
{{-- ── Live video feed (shown while camera is active) ── --}}
<div x-show="cameraActive && !captured" style="position: relative;">
<video
x-ref="video"
autoplay
playsinline
muted
style="
width: 100%;
max-width: 480px;
border-radius: 10px;
display: block;
background: #111;
"
></video>
</div>
{{-- ── Captured photo preview (shown after capture) ── --}}
<div x-show="captured" style="position: relative;">
<img
x-bind:src="$refs.canvas ? $refs.canvas.toDataURL('image/jpeg') : ''"
style="
width: 100%;
max-width: 480px;
border-radius: 10px;
display: block;
border: 2px solid #16a34a;
"
alt="Captured visitor photo"
/>
<div style="
position: absolute;
top: 10px; left: 10px;
background: #16a34a;
color: white;
padding: 3px 10px;
border-radius: 20px;
font-size: 12px;
">✓ Photo captured</div>
</div>
{{-- ── Placeholder (before camera starts) ── --}}
<div
x-show="!cameraActive && !captured"
style="
width: 100%;
max-width: 480px;
height: 200px;
background: #1a1a1a;
border: 2px dashed #444;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 14px;
"
>
📷 Camera not started yet
</div>
{{-- ── Hidden canvas used for capturing the frame ── --}}
<canvas x-ref="canvas" style="display: none;"></canvas>
{{-- ── Buttons ── --}}
<div style="display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap;">
{{-- Start Camera button --}}
<button
type="button"
x-show="!cameraActive && !captured"
x-on:click="startCamera()"
style="
background: #2563eb;
color: white;
border: none;
padding: 9px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
"
>
📷 Start Camera
</button>
{{-- Capture button --}}
<button
type="button"
x-show="cameraActive && !captured"
x-on:click="capture()"
style="
background: #16a34a;
color: white;
border: none;
padding: 9px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
"
>
📸 Capture Photo
</button>
{{-- Retake button --}}
<button
type="button"
x-show="captured"
x-on:click="retake()"
style="
background: #b45309;
color: white;
border: none;
padding: 9px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
"
>
🔄 Retake Photo
</button>
</div>
</div>