diff --git a/public/locale/en.json b/public/locale/en.json
index ac3ffddcba0..e49c5f2a068 100644
--- a/public/locale/en.json
+++ b/public/locale/en.json
@@ -515,7 +515,7 @@
"by_name": "by {{by}}",
"camera": "Camera",
"camera_bed_link_success": "Camera linked to bed successfully.",
- "camera_permission_denied": "Camera Permission denied",
+ "camera_permission_denied": "Camera Permission Denied",
"camera_was_linked_to_bed": "This camera was linked to this bed",
"cancel": "Cancel",
"cancel_appointment": "Cancel Appointment",
diff --git a/src/Utils/useCamera.ts b/src/Utils/useCamera.ts
new file mode 100644
index 00000000000..ff5723e14d5
--- /dev/null
+++ b/src/Utils/useCamera.ts
@@ -0,0 +1,37 @@
+import { useCallback, useRef } from "react";
+import { useTranslation } from "react-i18next";
+import { toast } from "sonner";
+
+export const useCamera = () => {
+ const toastShownRef = useRef(false);
+ const { t } = useTranslation();
+
+ const requestPermission = useCallback(
+ async (cameraFacingMode: string = "user") => {
+ try {
+ toastShownRef.current = false;
+ const constraints: MediaStreamConstraints = {
+ video: { facingMode: cameraFacingMode },
+ };
+
+ const mediaStream =
+ await navigator.mediaDevices.getUserMedia(constraints);
+
+ if (mediaStream == null) {
+ return { hasPermission: false, mediaStream: null };
+ }
+
+ return { hasPermission: true, mediaStream: mediaStream };
+ } catch (_error) {
+ if (!toastShownRef.current) {
+ toastShownRef.current = true;
+ toast.warning(t("camera_permission_denied"));
+ }
+ return { hasPermission: false, mediaStream: null };
+ }
+ },
+ [t],
+ );
+
+ return { requestPermission };
+};
diff --git a/src/components/Common/AvatarEditModal.tsx b/src/components/Common/AvatarEditModal.tsx
index dd4d6284e57..b5f5b5e6beb 100644
--- a/src/components/Common/AvatarEditModal.tsx
+++ b/src/components/Common/AvatarEditModal.tsx
@@ -23,6 +23,8 @@ import {
import useDragAndDrop from "@/hooks/useDragAndDrop";
+import { useCamera } from "@/Utils/useCamera";
+
interface Props {
title: string;
open: boolean;
@@ -77,6 +79,7 @@ const AvatarEditModal = ({
);
const { t } = useTranslation();
const [isDragging, setIsDragging] = useState(false);
+ const { requestPermission } = useCamera();
const handleSwitchCamera = useCallback(() => {
setConstraint(
@@ -391,9 +394,11 @@ const AvatarEditModal = ({
width={1280}
ref={webRef}
videoConstraints={constraint}
- onUserMediaError={(_e) => {
- setIsCameraOpen(false);
- toast.warning(t("camera_permission_denied"));
+ onUserMediaError={async () => {
+ const hasPermission = await requestPermission("user");
+ if (!hasPermission) {
+ setIsCameraOpen(false);
+ }
}}
/>
>
diff --git a/src/components/Files/CameraCaptureDialog.tsx b/src/components/Files/CameraCaptureDialog.tsx
index 8ee66a313d6..9faccd865c0 100644
--- a/src/components/Files/CameraCaptureDialog.tsx
+++ b/src/components/Files/CameraCaptureDialog.tsx
@@ -15,6 +15,8 @@ import {
import useBreakpoints from "@/hooks/useBreakpoints";
+import { useCamera } from "@/Utils/useCamera";
+
export interface CameraCaptureDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
@@ -26,6 +28,8 @@ export interface CameraCaptureDialogProps {
export default function CameraCaptureDialog(props: CameraCaptureDialogProps) {
const { open, onOpenChange, onCapture, onResetCapture, setPreview } = props;
const isLaptopScreen = useBreakpoints({ lg: true, default: false });
+ const { requestPermission } = useCamera();
+ const [stream, setStream] = useState(null);
const [cameraFacingMode, setCameraFacingMode] = useState(
isLaptopScreen ? "user" : "environment",
@@ -41,17 +45,23 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) {
useEffect(() => {
if (!open) return;
- let stream: MediaStream | null = null;
- navigator.mediaDevices
- .getUserMedia({ video: { facingMode: cameraFacingMode } })
- .then((mediaStream) => {
- stream = mediaStream;
- })
- .catch(() => {
- toast.warning(t("camera_permission_denied"));
+ const getCameraStream = async () => {
+ const hasPermission = await requestPermission(cameraFacingMode);
+ if (!hasPermission.hasPermission) {
onOpenChange(false);
- });
+ return;
+ }
+
+ try {
+ const mediaStream = hasPermission.mediaStream;
+ setStream(mediaStream);
+ } catch (error) {
+ console.error("Error accessing camera:", error);
+ }
+ };
+
+ getCameraStream();
return () => {
if (stream) {