import { useCallback, useState, useEffect } from "react"; import Cropper from "react-easy-crop"; import checkIfItFallbackImage from "@calcom/lib/checkIfItFallbackImage"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { ButtonColor } from "../.."; import { Button, Dialog, DialogClose, DialogContent, DialogTrigger, DialogFooter } from "../.."; import { showToast } from "../toast"; import { useFileReader, createImage, Slider } from "./Common"; import type { FileEvent, Area } from "./Common"; type BannerUploaderProps = { id: string; buttonMsg: string; handleAvatarChange: (imageSrc: string) => void; imageSrc?: string; target: string; triggerButtonColor?: ButtonColor; uploadInstruction?: string; disabled?: boolean; height: number; width: number; }; function CropContainer({ onCropComplete, imageSrc, }: { imageSrc: string; onCropComplete: (croppedAreaPixels: Area) => void; }) { const { t } = useLocale(); const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const handleZoomSliderChange = (value: number) => { value < 1 ? setZoom(1) : setZoom(value); }; return (
onCropComplete(croppedAreaPixels)} onZoomChange={setZoom} />
); } export default function BannerUploader({ target, id, buttonMsg, handleAvatarChange, triggerButtonColor, imageSrc, uploadInstruction, disabled = false, height, width, }: BannerUploaderProps) { const { t } = useLocale(); const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); const [{ result }, setFile] = useFileReader({ method: "readAsDataURL", }); const onInputFile = async (e: FileEvent) => { if (!e.target.files?.length) { return; } const limit = 5 * 1000000; // max limit 5mb const file = e.target.files[0]; if (file.size > limit) { showToast(t("image_size_limit_exceed"), "error"); } else { setFile(file); } }; const showCroppedImage = useCallback( async (croppedAreaPixels: Area | null) => { try { if (!croppedAreaPixels) return; const croppedImage = await getCroppedImg( result as string /* result is always string when using readAsDataUrl */, croppedAreaPixels, height, width ); handleAvatarChange(croppedImage); } catch (e) { console.error(e); } }, [result, height, width, handleAvatarChange] ); useEffect(() => { const checkDimensions = async () => { const image = await createImage( result as string /* result is always string when using readAsDataUrl */ ); if (image.naturalWidth !== width || image.naturalHeight !== height) { showToast(t("org_banner_instructions", { height, width }), "warning"); } }; if (result) { checkDimensions(); } }, [result]); return ( { // unset file on close if (!opened) { setFile(null); } }}>
{!result && (
{!imageSrc || checkIfItFallbackImage(imageSrc) ? (

{t("no_target", { target })}

) : ( // eslint-disable-next-line @next/next/no-img-element {target} )}
)} {result && } {uploadInstruction && (

({uploadInstruction})

)}
{t("cancel")} showCroppedImage(croppedAreaPixels)}> {t("save")}
); } async function getCroppedImg( imageSrc: string, pixelCrop: Area, height: number, width: number ): Promise { const image = await createImage(imageSrc); const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("Context is null, this should never happen."); canvas.width = width; canvas.height = height; ctx.drawImage( image, pixelCrop.x, pixelCrop.y, pixelCrop.width, pixelCrop.height, 0, 0, canvas.width, canvas.height ); return canvas.toDataURL("image/png"); }