Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

juni

프로필 아바타 및 이미지 수정 본문

인턴

프로필 아바타 및 이미지 수정

juni_shin 2024. 11. 12. 22:34

 

프로필 이미지를 아바타와 이미지로 별도 관리 예시

const profileFormSchema = z.object({
  name: z
    .string()
    .min(1, {
      message: "Your name must be at least 1 characters.",
    })
    .max(20, {
      message: "Your name must be at most 20 characters.",
    }),
  nickname: z
    .string()
    .min(1, {
      message: "Your nickname must be at least 1 characters.",
    })
    .max(20, {
      message: "Your nickname must be at most 20 characters.",
    }),
  description: z.string().optional(),
  avatar: z.array(z.string()).optional(),
  profileImage: z.string().optional(),
});

  const form = useForm<ProfileFormValues>({
    resolver: zodResolver(profileFormSchema),
    defaultValues: {
      name: defaultValues?.name || "",
      nickname: defaultValues?.nickname || "",
      description: defaultValues?.description || "",
      avatar: defaultValues?.avatar || [],
      profileImage: defaultValues?.profileImage || "",
    },
    mode: "onChange",
  });
  
              <div className="flex justify-center gap-6">
              <div>
                <div
                  className="w-[120px] h-[120px] relative rounded-full border bg-border-div cursor-pointer mb-2"
                  onClick={() => router.push("/mypage/avatar")}
                >
                  <IPFSImageLayer
                    hashes={user?.avatar}
                    className="w-full h-full object-contain rounded-full"
                  />
                </div>

                <div
                  className="text-center text-body_s text-accent-tertiary cursor-pointer"
                  onClick={() => router.push("/mypage/avatar")}
                >
                  아바타 편집
                </div>
              </div>

              <div>
                <div className="w-[120px] h-[120px] relative rounded-full border bg-border-div mb-2">
                  <ImageUploader
                    ref={imageUploaderRef}
                    defaultImage={user?.image}
                    onFileSelected={setProfileImage}
                    shape="circle"
                  />
                </div>

                <div
                  className="text-center text-body_s text-accent-tertiary cursor-pointer"
                  onClick={handleButtonClick} // 클릭 시 handleButtonClick 호출
                >
                  사진 업로드
                </div>
              </div>
            </div>

 

이미지 변경 API 예시

// 프로필 이미지 변경
export const updateUserProfile = async ({
  user_profile,
}: {
  user_profile: File[];
}) => {
  const formData = new FormData();
  if (user_profile && user_profile.length > 0) {
    formData.append("user_profile", user_profile[0]); // 파일 배열에서 첫 번째 파일을 추가
  }

  return await patchApi(`/users/profile`, formData, true);
};

 

부위별로 분리된 아바타 하나로 합쳐주는 컴포넌트 예시

const IPFSImageLayer: React.FC<IPFSImageLayerProps> = ({
  hashes = [
    "uploads/user/avatar/background.webp",
    "uploads/user/avatar/clothes.webp",
    "uploads/user/avatar/face.webp",
    "uploads/user/avatar/eye.webp",
    "uploads/user/avatar/hair.webp",
    "uploads/user/avatar/mouth.webp",
    "uploads/user/avatar/accessory.webp",
  ],
  className,
}) => {
  if (!hashes) {
    return;
  }

  return hashes.map((url, index) => {
    if (!hashes[index]) {
      return;
    }

    return (
      <Image
        key={index}
        src={`${DEFAULT_IMAGE_URL}${hashes[index]}`} // DB에서 받아오는 아바타 이미지
        alt={`Layer ${url}`}
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          width: "100%",
          height: "100%",
        }}
        className={className}
        width={300}
        height={300}
        priority={true}
      />
    );
  });
};

 

이미지 업로드 예시

    useImperativeHandle(ref, () => fileInputRef.current!); // 외부 ref가 내부 fileInputRef를 참조하도록 설정

    const handleFileChange = async (
      event: React.ChangeEvent<HTMLInputElement>
    ) => {
      if (disabled) return;

      const file = event.target.files?.[0];
      if (file) {
        const fileType = file.type.split("/")[0];
        if (fileType === "image") {
          try {
            const webpBlob = await convertToWebP(file);
            if (webpBlob) {
              const webpUrl = URL.createObjectURL(webpBlob);
              setMediaSource(webpUrl);
              setMediaType(fileType);
              onFileSelected?.(convertBlobToFile(webpBlob, file.name), webpUrl);
            }
          } catch (error) {
            console.error("Error converting file to WebP:", error);
          }
        } else {
          window.alert("Please upload an image format.");
        }
      }
    };