Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import {
2 DEFAULT_AVATAR,
3 STATIC_IMAGES_URL,
4 type TRANSFORMS
5} from "@hey/data/constants";
6import { ERRORS } from "@hey/data/errors";
7import imageKit from "@hey/helpers/imageKit";
8import sanitizeDStorageUrl from "@hey/helpers/sanitizeDStorageUrl";
9import type { ApolloClientError } from "@hey/types/errors";
10import type { ChangeEvent } from "react";
11import { useCallback, useState } from "react";
12import type { Area } from "react-easy-crop";
13import { toast } from "sonner";
14import uploadCroppedImage, { readFile } from "@/helpers/accountPictureUtils";
15import getCroppedImg from "@/helpers/cropUtils";
16import errorToast from "@/helpers/errorToast";
17
18interface UseImageCropUploadProps {
19 src: string;
20 setSrc: (src: string) => void;
21 aspect: number;
22 transform: (typeof TRANSFORMS)[keyof typeof TRANSFORMS];
23 label: "avatar" | "cover";
24}
25
26const useImageCropUpload = ({
27 src,
28 setSrc,
29 aspect,
30 transform,
31 label
32}: UseImageCropUploadProps) => {
33 const [pictureSrc, setPictureSrc] = useState(src);
34 const [showModal, setShowModal] = useState(false);
35 const [uploadedPicture, setUploadedPicture] = useState("");
36 const [uploading, setUploading] = useState(false);
37 const [area, setArea] = useState<Area | null>(null);
38 const [crop, setCrop] = useState({ x: 0, y: 0 });
39 const [zoom, setZoom] = useState(1);
40
41 const onError = useCallback((error: ApolloClientError) => {
42 errorToast(error);
43 }, []);
44
45 const handleUploadAndSave = async () => {
46 try {
47 setUploading(true);
48 const croppedImage = await getCroppedImg(pictureSrc, area);
49
50 if (!croppedImage) {
51 return toast.error(ERRORS.SomethingWentWrong);
52 }
53
54 const decentralizedUrl = await uploadCroppedImage(croppedImage);
55 const dataUrl = croppedImage.toDataURL("image/png");
56
57 setSrc(decentralizedUrl);
58 setUploadedPicture(dataUrl);
59 } catch (error) {
60 onError(error);
61 } finally {
62 setArea(null);
63 setCrop({ x: 0, y: 0 });
64 setZoom(1);
65 setShowModal(false);
66 setUploading(false);
67 }
68 };
69
70 const onFileChange = async (evt: ChangeEvent<HTMLInputElement>) => {
71 const file = evt.target.files?.[0];
72 if (file) {
73 setPictureSrc(await readFile(file));
74 setShowModal(true);
75 }
76 };
77
78 const onCropComplete = (_: Area, croppedAreaPixels: Area) => {
79 setArea(croppedAreaPixels);
80 };
81
82 const pictureUrl =
83 pictureSrc ||
84 (label === "avatar"
85 ? DEFAULT_AVATAR
86 : `${STATIC_IMAGES_URL}/patterns/2.svg`);
87 const renderPictureUrl = pictureUrl
88 ? imageKit(sanitizeDStorageUrl(pictureUrl), transform)
89 : "";
90
91 const handleModalClose = () => {
92 setPictureSrc("");
93 setShowModal(false);
94 };
95
96 return {
97 aspect,
98 crop,
99 handleModalClose,
100 handleUploadAndSave,
101 onCropComplete,
102 onFileChange,
103 pictureSrc,
104 renderPictureUrl,
105 setCrop,
106 setZoom,
107 showModal,
108 uploadedPicture,
109 uploading,
110 zoom
111 };
112};
113
114export default useImageCropUpload;