|
8 | 8 | } from "@remix-run/server-runtime"; |
9 | 9 | import { z } from "zod"; |
10 | 10 | import { EditPencilIcon } from "~/assets/icons/EditPencilIcon"; |
| 11 | +import { ProfilePhotoEditor } from "~/components/ProfilePhotoEditor"; |
11 | 12 | import { UserProfilePhoto } from "~/components/UserProfilePhoto"; |
12 | 13 | import { |
13 | 14 | MainHorizontallyCenteredContainer, |
@@ -447,6 +448,62 @@ function useProfileFieldUpdate({ |
447 | 448 | return { fetcher, error, setError, isSubmitting: fetcher.state !== "idle" }; |
448 | 449 | } |
449 | 450 |
|
| 451 | +function ChangeProfilePhotoButton() { |
| 452 | + const [isOpen, setIsOpen] = useState(false); |
| 453 | + const fetcher = useFetcher<{ avatarUrl?: string; error?: string }>(); |
| 454 | + const toast = useToast(); |
| 455 | + const isSaving = fetcher.state !== "idle"; |
| 456 | + const submitSeenRef = useRef(false); |
| 457 | + |
| 458 | + useEffect(() => { |
| 459 | + if (fetcher.state !== "idle") { |
| 460 | + submitSeenRef.current = true; |
| 461 | + return; |
| 462 | + } |
| 463 | + if (!submitSeenRef.current) return; |
| 464 | + submitSeenRef.current = false; |
| 465 | + |
| 466 | + if (fetcher.data?.avatarUrl) { |
| 467 | + // oxlint-disable-next-line react/set-state-in-effect -- Closes the modal once the upload has landed. |
| 468 | + setIsOpen(false); |
| 469 | + toast.success("Your profile picture has been updated."); |
| 470 | + return; |
| 471 | + } |
| 472 | + |
| 473 | + toast.error(fetcher.data?.error ?? "Something went wrong. Please try again."); |
| 474 | + }, [fetcher.state, fetcher.data, toast]); |
| 475 | + |
| 476 | + const save = (blob: Blob) => { |
| 477 | + const formData = new FormData(); |
| 478 | + formData.append("image", blob, "avatar.png"); |
| 479 | + fetcher.submit(formData, { |
| 480 | + method: "post", |
| 481 | + action: "/resources/account/avatar", |
| 482 | + encType: "multipart/form-data", |
| 483 | + }); |
| 484 | + }; |
| 485 | + |
| 486 | + return ( |
| 487 | + <> |
| 488 | + <button |
| 489 | + type="button" |
| 490 | + onClick={() => setIsOpen(true)} |
| 491 | + title="Change your profile picture" |
| 492 | + aria-label="Change your profile picture" |
| 493 | + className="focus-custom group cursor-pointer rounded-full outline-hidden" |
| 494 | + > |
| 495 | + <UserProfilePhoto className="size-8 transition group-hover:opacity-60" strokeWidth={1.5} /> |
| 496 | + </button> |
| 497 | + <ProfilePhotoEditor |
| 498 | + open={isOpen} |
| 499 | + onOpenChange={setIsOpen} |
| 500 | + onSave={save} |
| 501 | + isSaving={isSaving} |
| 502 | + /> |
| 503 | + </> |
| 504 | + ); |
| 505 | +} |
| 506 | + |
450 | 507 | function EditNameButton() { |
451 | 508 | const user = useUser(); |
452 | 509 | const [isOpen, setIsOpen] = useState(false); |
@@ -900,7 +957,7 @@ export default function Page() { |
900 | 957 | <Label>Profile picture</Label> |
901 | 958 | </InputGroup> |
902 | 959 | <div className="flex flex-none items-center"> |
903 | | - <UserProfilePhoto className="size-8" strokeWidth={1.5} /> |
| 960 | + <ChangeProfilePhotoButton /> |
904 | 961 | </div> |
905 | 962 | </div> |
906 | 963 | </div> |
|
0 commit comments