import styles from "./styles.module.scss"; import { DataURIToBlob } from "@/services/data"; import { useApi } from "@/api"; import { IPalmistryFinger, IPalmistryLine } from "@/api/resources/Palmistry"; import { IPalmistryFingerLocal } from "@/store/palmistry"; import { useDispatch } from "react-redux"; import { actions } from "@/store"; import { useNavigate } from "react-router-dom"; import routes from "@/routes"; import { useEffect, useMemo, useState } from "react"; import Loader, { LoaderColor } from "@/components/Loader"; import UploadModal from "@/components/palmistry/upload-modal/upload-modal"; import Toast from "@/components/pages/ABDesign/v1/components/Toast"; import { useTranslations } from "@/hooks/translations"; import { ELocalesPlacement } from "@/locales"; import CameraModal from "../../components/CameraModal"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService"; import Modal from "@/components/Modal"; import Title from "@/components/Title"; import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash"; const isProduction = import.meta.env.MODE === "production"; enum EToastVisible { "try_again" = "try_again", "try_again_or_next" = "try_again_or_next", "no_access_camera" = "no_access_camera", "reload_page" = "reload_page", } function Camera() { const isIphoneSafari = useMemo((): boolean => { const userAgent = navigator.userAgent; const isIOS = /iPhone/i.test(userAgent); const isSafari = /Safari/i.test(userAgent) && !/CriOS/i.test(userAgent) && // не Chrome !/FxiOS/i.test(userAgent) && // не Firefox !/EdgiOS/i.test(userAgent) && // не Edge !/OPiOS/i.test(userAgent); // не Opera return isIOS && isSafari; }, []); const { translate } = useTranslations(ELocalesPlacement.PalmistryV1); const navigate = useNavigate(); const api = useApi(); const dispatch = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [uploadMenuModalIsOpen, setUploadMenuModalIsOpen] = useState(false); const [toastVisible, setToastVisible] = useState(null); const [cameraKey, setCameraKey] = useState(0); const [isCameraModalOpen, setIsCameraModalOpen] = useState(false); // const { flags, ready } = useMetricABFlags(); const { isReady, variant: cameraRequestModalPalmistryV1 } = useUnleash({ flag: EUnleashFlags.cameraRequestModalPalmistryV1 }); // const isCameraRequestModal = flags?.cameraRequestModal?.[0] !== "without"; const isCameraRequestModal = cameraRequestModalPalmistryV1 !== "hide"; const [isRequestCameraModalOpen, setIsRequestCameraModalOpen] = useState((isIphoneSafari || !isCameraRequestModal) ? false : true); const handleNext = () => { metricService.reachGoal(EGoals.CAMERA_SUCCESS, [EMetrics.YANDEX, EMetrics.KLAVIYO]); navigate(routes.client.palmistryV1ScannedPhoto()); }; const fingersNames = { thumb: translate("thumb"), index_finger: translate("index_finger"), middle_finger: translate("middle_finger"), ring_finger: translate("ring_finger"), pinky: translate("pinky"), }; const setFingersNames = ( fingers: IPalmistryFinger[] ): IPalmistryFingerLocal[] => { if (!fingers) return []; return fingers.map((finger) => { return { ...finger, fingerName: fingersNames[finger.name as keyof typeof fingersNames], }; }); }; /** * Check if the palmistry lines are valid for the next step. * If the length of the lines is less than 2, show the "try_again" toast. * If the length of the lines is 2, show the "try_again_or_next" toast. * Otherwise, return true. * @param {IPalmistryLine[]} lines - The palmistry lines. * @returns {boolean} Whether the palmistry lines are valid for the next step. */ const checkPalmistryLines = (lines: IPalmistryLine[]): boolean => { if (!lines.length || lines.length < 2) { setToastVisible(EToastVisible.try_again); return false; } if (lines.length === 2) { setToastVisible(EToastVisible.try_again_or_next); return false; } return true; }; const getLines = async (file: File | Blob) => { setIsLoading(true); const formData = new FormData(); formData.append("file", file); try { const result = await api.getPalmistryLines({ formData }); const fingers = setFingersNames(result?.fingers); dispatch( actions.palmistry.update({ lines: result?.lines, fingers, }) ); return result; } catch (error) { dispatch( actions.palmistry.update({ lines: [], fingers: [], }) ); return null; } finally { setIsLoading(false); } }; const onTakePhoto = async (photo: string) => { setUploadMenuModalIsOpen(false); try { const file = DataURIToBlob(photo); const result = await getLines(file); URL.revokeObjectURL(URL.createObjectURL(file)); dispatch( actions.palmistry.update({ photo, }) ); if (!checkPalmistryLines(result?.lines || [])) return; handleNext(); } catch (error) { console.error('Ошибка при обработке фото:', error); } finally { // Принудительный запуск сборщика мусора (не гарантировано, но может помочь) if (window.gc) { window.gc(); } } }; const onSelectFile = async (event: React.ChangeEvent) => { setToastVisible(null); setUploadMenuModalIsOpen(false); if (!event.target.files || event.target.files.length === 0) return; const result = await getLines(event.target.files[0]); const reader = new FileReader(); reader.onloadend = () => { dispatch( actions.palmistry.update({ photo: reader.result as string, }) ); if (!checkPalmistryLines(result?.lines || [])) return; handleNext(); }; reader.readAsDataURL(event.target.files[0]); }; useEffect(() => { metricService.reachGoal(EGoals.CAMERA_OPEN, [EMetrics.YANDEX, EMetrics.KLAVIYO]); }, []); useEffect(() => { if (toastVisible === EToastVisible.try_again) { metricService.reachGoal(EGoals.CAMERA_ERROR, [EMetrics.YANDEX, EMetrics.KLAVIYO]); } }, [toastVisible]); const cameraError = (error: string | DOMException) => { console.error("Camera error", error) if (!isIphoneSafari || !isCameraRequestModal) return; if (error === "Video is not ready") { return setToastVisible(EToastVisible.no_access_camera) } return setIsRequestCameraModalOpen(true) } const requestCameraPermission = async () => { if (cameraKey > 2) { setIsRequestCameraModalOpen(false); setToastVisible(EToastVisible.reload_page); return; } try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); setToastVisible(null); stream.getTracks().forEach(track => track.stop()); setCameraKey(prev => prev + 1); } catch (error) { setCameraKey(prev => prev + 1); console.error("Ошибка при запросе доступа к камере:", error); setIsRequestCameraModalOpen(true); } }; if (!isReady) return ; return ( <> { }} className={styles.modal} containerClassName={styles["modal-container"]} > {translate("/camera.modal.title")}
{ setIsRequestCameraModalOpen(false); setToastVisible(EToastVisible.no_access_camera); }}>

{translate("/camera.modal.cancel")}

{ setIsRequestCameraModalOpen(false) if (isIphoneSafari) { requestCameraPermission() } else { setIsCameraModalOpen(true) } }}>

{translate("/camera.modal.allow")}

{!isProduction && uploadMenuModalIsOpen && ( setUploadMenuModalIsOpen(false)} onSelectFile={onSelectFile} onChooseCamera={() => true} /> )} {!isProduction && ( )} {!isLoading && !uploadMenuModalIsOpen && ( // console.log("close")} // onTakePhoto={onTakePhoto} // /> console.log("close")} onTakePhoto={onTakePhoto} onError={cameraError} isCameraVisible={(isIphoneSafari || isCameraRequestModal) ? true : isCameraModalOpen} reinitializeKey={cameraKey} /> )} {isLoading && ( )} {toastVisible === EToastVisible.try_again && (
{translate("/camera.bad_photo")}
)} {toastVisible === EToastVisible.no_access_camera && (
{translate("/camera.no_access_camera")}
)} {toastVisible === EToastVisible.reload_page && (
{translate("/camera.reload_page")}
)} {toastVisible === EToastVisible.try_again_or_next && (
{translate("/camera.do_better")}
)} ); } export default Camera;