Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import { PhotoIcon } from "@heroicons/react/24/outline";
2import { TRANSFORMS } from "@hey/data/constants";
3import imageKit from "@hey/helpers/imageKit";
4import sanitizeDStorageUrl from "@hey/helpers/sanitizeDStorageUrl";
5import type { ApolloClientError } from "@hey/types/errors";
6import type { ChangeEvent, Ref } from "react";
7import { useCallback, useState } from "react";
8import { Image, Spinner } from "@/components/Shared/UI";
9import cn from "@/helpers/cn";
10import errorToast from "@/helpers/errorToast";
11import { uploadFileToIPFS } from "@/helpers/uploadToIPFS";
12
13interface CoverImageProps {
14 cover: string;
15 imageRef: Ref<HTMLImageElement>;
16 isNew: boolean;
17 setCover: (previewUri: string, url: string, mimeType: string) => void;
18}
19
20const CoverImage = ({
21 cover,
22 imageRef,
23 isNew = false,
24 setCover
25}: CoverImageProps) => {
26 const [isSubmitting, setIsSubmitting] = useState(false);
27
28 const onError = useCallback((error: ApolloClientError) => {
29 setIsSubmitting(false);
30 errorToast(error);
31 }, []);
32
33 const onChange = async (event: ChangeEvent<HTMLInputElement>) => {
34 if (event.target.files?.length) {
35 try {
36 setIsSubmitting(true);
37 const file = event.target.files[0];
38 const attachment = await uploadFileToIPFS(file);
39 setCover(
40 URL.createObjectURL(file),
41 attachment.uri,
42 file.type || "image/jpeg"
43 );
44 } catch (error) {
45 onError(error);
46 }
47 }
48 };
49
50 return (
51 <div className="group relative flex-none overflow-hidden">
52 <button className="flex focus:outline-hidden" type="button">
53 <Image
54 alt={`attachment-audio-cover-${cover}`}
55 className="size-24 rounded-xl object-cover md:size-40 md:rounded-none"
56 draggable={false}
57 onError={({ currentTarget }) => {
58 currentTarget.src = cover ? sanitizeDStorageUrl(cover) : cover;
59 }}
60 ref={imageRef}
61 src={
62 cover
63 ? imageKit(sanitizeDStorageUrl(cover), TRANSFORMS.ATTACHMENT)
64 : cover
65 }
66 />
67 </button>
68 {isNew && (
69 <label
70 className={cn(
71 { invisible: cover, visible: isSubmitting && !cover },
72 "absolute top-0 grid size-24 cursor-pointer place-items-center bg-gray-100 backdrop-blur-lg group-hover:visible md:size-40 dark:bg-gray-900"
73 )}
74 >
75 {isSubmitting && !cover ? (
76 <Spinner size="sm" />
77 ) : (
78 <div className="flex flex-col items-center text-sm opacity-60">
79 <PhotoIcon className="size-5" />
80 <span>Add cover</span>
81 </div>
82 )}
83 <input
84 accept=".png, .jpg, .jpeg, .svg"
85 className="hidden w-full"
86 onChange={onChange}
87 type="file"
88 />
89 </label>
90 )}
91 </div>
92 );
93};
94
95export default CoverImage;