@@ -69,6 +69,8 @@ export default function ProductSearch() {
6969 const [ showDropdown , setShowDropdown ] = useState ( false ) ;
7070 const [ firebaseUser , setFirebaseUser ] = useState < FirebaseUser | null > ( null ) ;
7171 const [ pageLoading , setPageLoading ] = useState ( true ) ;
72+ const [ currentCameraIndex , setCurrentCameraIndex ] = useState ( 0 ) ;
73+ const [ availableCameras , setAvailableCameras ] = useState < MediaDeviceInfo [ ] > ( [ ] ) ;
7274 const videoRef = useRef < HTMLVideoElement > ( null ) ;
7375 const router = useRouter ( ) ;
7476 const { setNavigating } = useTopBar ( ) ;
@@ -129,10 +131,13 @@ export default function ProductSearch() {
129131 const startCamera = async ( ) => {
130132 try {
131133 const devices = await BrowserMultiFormatReader . listVideoInputDevices ( ) ;
134+ setAvailableCameras ( devices ) ;
135+
132136 if ( devices . length > 0 && videoRef . current && active ) {
137+ const selectedDevice = devices [ currentCameraIndex ] || devices [ 0 ] ;
133138 const codeReader = new BrowserMultiFormatReader ( ) ;
134139 controls = await codeReader . decodeFromVideoDevice (
135- devices [ 0 ] . deviceId ,
140+ selectedDevice . deviceId ,
136141 videoRef . current ,
137142 ( result , _err , c ) => {
138143 if ( result && active ) {
@@ -156,7 +161,15 @@ export default function ProductSearch() {
156161 clearTimeout ( timer ) ;
157162 if ( controls ) controls . stop ( ) ;
158163 } ;
159- } , [ router , pageLoading ] ) ;
164+ } , [ router , pageLoading , currentCameraIndex ] ) ;
165+
166+ const flipCamera = ( ) => {
167+ if ( availableCameras . length > 1 ) {
168+ setCurrentCameraIndex ( ( prevIndex ) =>
169+ ( prevIndex + 1 ) % availableCameras . length
170+ ) ;
171+ }
172+ } ;
160173
161174 const handleManualSearch = ( ) => {
162175 handleSearch ( query ) ;
@@ -280,14 +293,45 @@ export default function ProductSearch() {
280293 >
281294 Scan a Barcode
282295 </ h1 >
283- < div
284- className = "rounded-lg overflow-hidden shadow-xl mb-4 w-full max-w-xs flex items-center justify-center aspect-video"
285- style = { {
286- backgroundColor : colours . content . surface ,
287- border : `2px solid ${ colours . card . border } `
288- } }
289- >
290- < video ref = { videoRef } className = "w-full h-auto" />
296+ < div className = "relative" >
297+ < div
298+ className = "rounded-lg overflow-hidden shadow-xl mb-4 w-full max-w-xs flex items-center justify-center aspect-video"
299+ style = { {
300+ backgroundColor : colours . content . surface ,
301+ border : `2px solid ${ colours . card . border } `
302+ } }
303+ >
304+ < video ref = { videoRef } className = "w-full h-auto" />
305+ </ div >
306+ { /* Flip Camera Button */ }
307+ { availableCameras . length > 1 && (
308+ < button
309+ onClick = { flipCamera }
310+ className = "absolute top-2 right-2 p-2 rounded-full shadow-lg transition-all duration-200 hover:scale-110 active:scale-95"
311+ style = { {
312+ backgroundColor : colours . content . surface ,
313+ border : `1px solid ${ colours . card . border } ` ,
314+ color : colours . text . primary
315+ } }
316+ title = "Flip Camera"
317+ >
318+ < svg
319+ width = "20"
320+ height = "20"
321+ viewBox = "0 0 24 24"
322+ fill = "none"
323+ stroke = "currentColor"
324+ strokeWidth = "2"
325+ strokeLinecap = "round"
326+ strokeLinejoin = "round"
327+ >
328+ < path d = "M3 6h4l2-2h6l2 2h4a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z" />
329+ < circle cx = "12" cy = "13" r = "3" />
330+ < path d = "M16 6l2-2" />
331+ < path d = "M16 6l-2-2" />
332+ </ svg >
333+ </ button >
334+ ) }
291335 </ div >
292336 </ div >
293337 </ ContentBox >
0 commit comments