Repository: sweetmantech/IN_PROCESS
Pull request #389: profile edit
Author: login: techeng322
Created: 2025-05-02T00:46:04Z | Merged: 2025-05-02T00:53:54Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Profile editing fields now auto-save when clicking outside the editable area, streamlining the editing experience.
- **Improvements**
	- Username and bio fields in profile editing modes are now directly linked to their respective input elements for better control.
	- The login button now displays the username from the updated user profile source.
- **Refactor**
	- Simplified the profile editing status display by removing manual save and close controls.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/components/ArtistPage/DesktopProfile.tsx b/components/ArtistPage/DesktopProfile.tsx
index b4116bac..67e578bc 100644
--- a/components/ArtistPage/DesktopProfile.tsx
+++ b/components/ArtistPage/DesktopProfile.tsx
@@ -15,6 +15,8 @@ const DesktopProfile = () => {
     setUserName,
     setBio,
     isLoading,
+    usernameRef,
+    bioRef,
   } = useProfileProvider();
 
   return (
@@ -24,6 +26,8 @@ const DesktopProfile = () => {
         <div className="flex gap-3 md:gap-6 items-center">
           {isEditing ? (
             <input
+              type="text"
+              ref={usernameRef}
               value={username}
               onChange={(e) => setUserName(e.target.value)}
               className="max-w-[120px] md:!max-w-[200px] text-xl md:text-4xl p-1 md:p-3 bg-grey-moss-50 outline-none ring-0 font-archivo-medium"
@@ -45,6 +49,8 @@ const DesktopProfile = () => {
         </div>
         {isEditing ? (
           <input
+            type="text"
+            ref={bioRef}
             value={bio}
             onChange={(e) => setBio(e.target.value)}
             className="max-w-[150px] md:max-w-auto md:min-w-[250px] outline-none ring-0 p-1 md:p-2 font-spectral text-lg md:text-xl mt-2 md:mt-4 bg-grey-moss-50"
diff --git a/components/ArtistPage/EditingStatus.tsx b/components/ArtistPage/EditingStatus.tsx
index a88e4c81..25bc1865 100644
--- a/components/ArtistPage/EditingStatus.tsx
+++ b/components/ArtistPage/EditingStatus.tsx
@@ -1,14 +1,14 @@
 import { useProfileProvider } from "@/providers/ProfileProvider";
-import { X } from "lucide-react";
 
 const EditingStatus = () => {
-  const { toggleEditing, save, saving } = useProfileProvider();
+  const { saving, statusRef } = useProfileProvider();
 
   return (
     <div
       className={`relative mx-auto md:absolute z-[10000] md:left-1/2 md:bottom-full mt-16 md:m-0
         md:-translate-x-1/2 flex gap-2 items-center w-fit px-6 py-1.5 rounded-full
     ${saving ? "bg-grey-moss-50" : "bg-grey-moss-900"}`}
+      ref={statusRef}
     >
       <div className="flex items-center gap-2">
         {!saving && (
@@ -22,24 +22,6 @@ const EditingStatus = () => {
           {saving ? "saved!" : "editing your profile"}
         </p>
       </div>
-      {!saving && (
-        <>
-          <button
-            type="button"
-            className="bg-grey-moss-100 text-grey-moss-900 text-sm font-archivo px-3 rounded-full"
-            onClick={save}
-          >
-            save
-          </button>
-          <button
-            type="button"
-            className="bg-grey-moss-100 text-grey-moss-900 rounded-full p-0.5"
-            onClick={toggleEditing}
-          >
-            <X className="size-4" />
-          </button>
-        </>
-      )}
     </div>
   );
 };
diff --git a/components/ArtistPage/MobileProfile.tsx b/components/ArtistPage/MobileProfile.tsx
index 42f46634..c4fdb66b 100644
--- a/components/ArtistPage/MobileProfile.tsx
+++ b/components/ArtistPage/MobileProfile.tsx
@@ -14,6 +14,8 @@ const MobileProfile = () => {
     isLoading,
     setUserName,
     setBio,
+    usernameRef,
+    bioRef,
   } = useProfileProvider();
 
   return (
@@ -26,8 +28,10 @@ const MobileProfile = () => {
             className="px-2 py-1 font-archivo !ring-0 !outline-none"
             value={username}
             onChange={(e) => setUserName(e.target.value)}
+            ref={usernameRef}
           />
           <textarea
+            ref={bioRef}
             rows={4}
             className="px-2 py-1 font-spectral !ring-0 !outline-none"
             value={bio}
diff --git a/components/LoginButton/PrivyButton.tsx b/components/LoginButton/PrivyButton.tsx
index c62b9069..83366091 100644
--- a/components/LoginButton/PrivyButton.tsx
+++ b/components/LoginButton/PrivyButton.tsx
@@ -1,13 +1,12 @@
 "use client";
 
-import { useArtistProfile } from "@/hooks/useArtistProfile";
 import useConnectedWallet from "@/hooks/useConnectedWallet";
 import truncateAddress from "@/lib/truncateAddress";
 import { usePrivy } from "@privy-io/react-auth";
-import { Address } from "viem";
 import Image from "next/image";
 import { useLayoutProvider } from "@/providers/LayoutProvider";
 import truncated from "@/lib/truncated";
+import { useUserProvider } from "@/providers/UserProvider";
 
 interface PrivyButtonProps {
   className?: string;
@@ -16,8 +15,8 @@ interface PrivyButtonProps {
 export function PrivyButton({ className = "" }: PrivyButtonProps) {
   const { login, ready } = usePrivy();
   const { connectedWallet } = useConnectedWallet();
-  const { data } = useArtistProfile(connectedWallet as Address);
   const { toggleNavbar, isOpenNavbar } = useLayoutProvider();
+  const { profile } = useUserProvider();
 
   if (!ready) return null;
 
@@ -44,7 +43,7 @@ export function PrivyButton({ className = "" }: PrivyButtonProps) {
         {connectedWallet ? (
           <>
             <p className="min-w-20 text-left">
-              {truncated(data?.username || "", 9) ||
+              {truncated(profile?.username || "", 9) ||
                 truncateAddress(connectedWallet as string)}
             </p>
             <Image
diff --git a/hooks/useProfile.ts b/hooks/useProfile.ts
index dd9e18fd..afcd3b52 100644
--- a/hooks/useProfile.ts
+++ b/hooks/useProfile.ts
@@ -1,9 +1,9 @@
 import { useParams, useSearchParams } from "next/navigation";
-import useConnectedWallet from "./useConnectedWallet";
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
 import { useArtistProfile } from "./useArtistProfile";
 import { Address } from "viem";
 import truncateAddress from "@/lib/truncateAddress";
+import { useUserProvider } from "@/providers/UserProvider";
 
 const saveIndentify = async (
   artistAddress: Address,
@@ -20,17 +20,20 @@ const saveIndentify = async (
 };
 
 const useProfile = () => {
+  const usernameRef = useRef() as any;
+  const bioRef = useRef() as any;
+  const statusRef = useRef() as any;
+  const { getProfile, connectedAddress } = useUserProvider();
   const { data, isLoading } = useArtistProfile();
-  const { connectedWallet } = useConnectedWallet();
   const [socialAccounts, setSocialAccounts] = useState<any>(null);
   const [username, setUserName] = useState<string>("");
   const [bio, setBio] = useState<string>("");
   const { artistAddress } = useParams();
   const searchParams = useSearchParams();
   const canEdit =
-    connectedWallet?.toLowerCase() ===
+    connectedAddress?.toLowerCase() ===
       new String((artistAddress as string) || "").toLowerCase() &&
-    Boolean(connectedWallet);
+    Boolean(connectedAddress);
   const [isEditing, setIsEditing] = useState<boolean>(false);
   const [saving, setSaving] = useState<boolean>(false);
   const toggleEditing = () => setIsEditing(!isEditing);
@@ -50,14 +53,33 @@ const useProfile = () => {
     }
   }, [canEdit, searchParams, artistAddress]);
 
-  const save = async () => {
-    setSaving(true);
-    await saveIndentify(artistAddress as Address, username, bio);
-    setTimeout(() => {
-      toggleEditing();
-      setSaving(false);
-    }, 500);
-  };
+  useEffect(() => {
+    if (!usernameRef.current || !bioRef.current || !statusRef.current) return;
+    if (!isEditing) return;
+    const handleMouseDown = async (e: MouseEvent) => {
+      if (
+        usernameRef.current.contains(e.target) ||
+        bioRef.current.contains(e.target) ||
+        statusRef.current.contains(e.target)
+      )
+        return;
+      setSaving(true);
+      await saveIndentify(
+        artistAddress as Address,
+        usernameRef.current.value,
+        bioRef.current.value,
+      );
+      setTimeout(() => {
+        toggleEditing();
+        setSaving(false);
+        getProfile();
+      }, 500);
+    };
+
+    document.addEventListener("mousedown", handleMouseDown);
+
+    return () => document.removeEventListener("mousedown", handleMouseDown);
+  }, [isEditing, usernameRef, bioRef, statusRef]);
 
   return {
     canEdit,
@@ -68,10 +90,12 @@ const useProfile = () => {
     bio,
     setBio,
     socialAccounts,
-    save,
     isLoading,
     saving,
     setSaving,
+    usernameRef,
+    bioRef,
+    statusRef,
   };
 };
 
diff --git a/hooks/useUser.ts b/hooks/useUser.ts
index 21560e95..44e341cc 100644
--- a/hooks/useUser.ts
+++ b/hooks/useUser.ts
@@ -35,6 +35,7 @@ const useUser = () => {
     isPrepared,
     profile: userProfile.data,
     connectedAddress: signedAddress,
+    getProfile: () => userProfile.refetch(),
   };
 };
 


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>hooks/useUser.ts (1)</summary><blockquote>

`38-38`: **Consider renaming `getProfile` for clarity**

The method `getProfile` doesn't actually return the profile directly - it triggers a refetch operation. A more descriptive name like `refreshProfile` or `refetchProfile` would better communicate its purpose.

```diff
-    getProfile: () => userProfile.refetch(),
+    refreshProfile: () => userProfile.refetch(),
```

</blockquote></details>
<details>
<summary>components/LoginButton/PrivyButton.tsx (1)</summary><blockquote>

`19-20`: **Consider null-safety around `profile`**

`profile` is optional, but you destructure it directly.  If `UserProvider` ever returns `undefined` while `connectedWallet` is truthy, the UI still behaves, yet the call site `profile?.username` will incur a re-render when the object becomes available.

A small micro-optimisation is to destructure with a default to avoid a transient “undefined” object reference during the intermediate render:

```diff
-const { profile } = useUserProvider();
+const { profile = null } = useUserProvider();
```

Not mandatory, just a defensive habit.

</blockquote></details>
<details>
<summary>hooks/useProfile.ts (1)</summary><blockquote>

`34-37`: **`canEdit` computation mixes primitives and objects**

`artistAddress` is a `string | string[] | undefined` returned by `useParams`, whereas `connectedAddress` is presumably a `string`.  Calling `new String(...)` allocates an object wrapper each render and breaks referential equality.

```diff
-  connectedAddress?.toLowerCase() ===
-    new String((artistAddress as string) || "").toLowerCase() &&
-  Boolean(connectedAddress);
+  Boolean(
+    connectedAddress &&
+    artistAddress &&
+    connectedAddress.toLowerCase() === String(artistAddress).toLowerCase()
+  );
```

The short-circuited `Boolean(...)` removes the redundant second line.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 450f8670c70ebaa015dc8dd1e8096e77bcfc569c and 797596987a6266b508d5748a198d67331453791d.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `components/ArtistPage/DesktopProfile.tsx` (3 hunks)
* `components/ArtistPage/EditingStatus.tsx` (1 hunks)
* `components/ArtistPage/MobileProfile.tsx` (2 hunks)
* `components/LoginButton/PrivyButton.tsx` (3 hunks)
* `hooks/useProfile.ts` (4 hunks)
* `hooks/useUser.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>components/ArtistPage/EditingStatus.tsx (1)</summary><blockquote>

<details>
<summary>providers/ProfileProvider.tsx (1)</summary>

* `useProfileProvider` (25-31)

</details>

</blockquote></details>
<details>
<summary>hooks/useProfile.ts (1)</summary><blockquote>

<details>
<summary>hooks/useArtistProfile.ts (1)</summary>

* `useArtistProfile` (17-26)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>hooks/useProfile.ts</summary>

[error] 28-28: 'setSocialAccounts' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>components/ArtistPage/MobileProfile.tsx (1)</summary>

`17-18`: **Implementation of refs for auto-save looks good**

The addition of refs for username and bio fields supports the new auto-save mechanism mentioned in the PR summary, allowing the system to detect clicks outside editable fields.



Also applies to: 31-31, 34-34

</details>
<details>
<summary>components/ArtistPage/DesktopProfile.tsx (1)</summary>

`18-19`: **Good implementation of refs and input attributes**

The addition of refs for username and bio fields supports the new auto-save mechanism. Adding explicit `type="text"` attributes to the inputs is also good practice for accessibility and form behavior.



Also applies to: 29-30, 52-53

</details>
<details>
<summary>components/ArtistPage/EditingStatus.tsx (1)</summary>

`4-4`: **Simplification of EditingStatus component looks good**

Removing the interactive controls and implementing the statusRef aligns well with the overall auto-save mechanism. The component is now more focused on its primary purpose - showing editing status.



Also applies to: 11-11

</details>
<details>
<summary>components/LoginButton/PrivyButton.tsx (2)</summary>

`9-10`: **Dependency switch looks good**

Replacing the legacy `useArtistProfile` with `useUserProvider` keeps the component aligned with the new single-source-of-truth for user data. Import is correct and tree-shakable.

---

`45-47`: **Fallback order 👍**

Using `truncated(profile?.username || "", 9) || truncateAddress(connectedWallet as string)` guarantees a non-empty string even when the username is missing.  Good UX safeguard.

</details>
<details>
<summary>hooks/useProfile.ts (1)</summary>

`80-82`: **Missing dependencies in effect**

`getProfile` is referenced inside the effect but not in the dependency array.  If `getProfile` is recreated by its provider (e.g., when auth state changes), the listener won’t capture the latest reference.

Add it to the dependency list:

```diff
-}, [isEditing, usernameRef, bioRef, statusRef]);
+}, [isEditing, usernameRef, bioRef, statusRef, getProfile]);
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T00:49:43Z

Review comments:
diff_hunk: @@ -20,17 +20,20 @@ const saveIndentify = async (
 };
 
 const useProfile = () => {
+  const usernameRef = useRef() as any;
+  const bioRef = useRef() as any;
+  const statusRef = useRef() as any;
+  const { getProfile, connectedAddress } = useUserProvider();
path: hooks/useProfile.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Type the refs instead of casting to `any`**

The generic parameter on `useRef` provides strong compile-time guarantees and removes the need for `as any`.

```diff
-const usernameRef = useRef() as any;
-const bioRef = useRef() as any;
-const statusRef = useRef() as any;
+const usernameRef = useRef<HTMLInputElement | null>(null);
+const bioRef = useRef<HTMLTextAreaElement | null>(null);
+const statusRef = useRef<HTMLDivElement | null>(null);
```

This immediately surfaces typos such as the `.value` access and improves IntelliSense.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  const usernameRef = useRef<HTMLInputElement | null>(null);
  const bioRef = useRef<HTMLTextAreaElement | null>(null);
  const statusRef = useRef<HTMLDivElement | null>(null);
  const { getProfile, connectedAddress } = useUserProvider();
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:49:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,17 +20,20 @@ const saveIndentify = async (
 };
 
 const useProfile = () => {
+  const usernameRef = useRef() as any;
+  const bioRef = useRef() as any;
+  const statusRef = useRef() as any;
+  const { getProfile, connectedAddress } = useUserProvider();
   const { data, isLoading } = useArtistProfile();
-  const { connectedWallet } = useConnectedWallet();
   const [socialAccounts, setSocialAccounts] = useState<any>(null);
   const [username, setUserName] = useState<string>("");
path: hooks/useProfile.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unused state variable**

`setSocialAccounts` is never invoked. Remove it or implement the intended logic to satisfy ESLint:

```diff
-const [socialAccounts, setSocialAccounts] = useState<any>(null);
+// remove both the state and its return-object entry
```

or wire up the missing social-accounts feature.  



> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 28-28: 'setSocialAccounts' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:49:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -50,14 +53,33 @@ const useProfile = () => {
     }
   }, [canEdit, searchParams, artistAddress]);
 
-  const save = async () => {
-    setSaving(true);
-    await saveIndentify(artistAddress as Address, username, bio);
-    setTimeout(() => {
-      toggleEditing();
-      setSaving(false);
-    }, 500);
-  };
+  useEffect(() => {
+    if (!usernameRef.current || !bioRef.current || !statusRef.current) return;
+    if (!isEditing) return;
+    const handleMouseDown = async (e: MouseEvent) => {
+      if (
+        usernameRef.current.contains(e.target) ||
+        bioRef.current.contains(e.target) ||
+        statusRef.current.contains(e.target)
+      )
+        return;
+      setSaving(true);
+      await saveIndentify(
+        artistAddress as Address,
+        usernameRef.current.value,
+        bioRef.current.value,
+      );
+      setTimeout(() => {
+        toggleEditing();
+        setSaving(false);
+        getProfile();
+      }, 500);
+    };
+
+    document.addEventListener("mousedown", handleMouseDown);
+
+    return () => document.removeEventListener("mousedown", handleMouseDown);
+  }, [isEditing, usernameRef, bioRef, statusRef]);
 
path: hooks/useProfile.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Auto-save effect: race & timing issues**

1. `setTimeout(…, 500)` after an awaited network request adds an arbitrary 0.5 s delay and can feel sluggish on fast networks yet still too short on slow ones.  
2. `getProfile()` is called inside that timeout, so profile refresh waits for the extra delay.

Recommend eliminating the artificial pause and relying on the fetch promise’s resolution:

```diff
-      await saveIndentify(/* … */);
-      setTimeout(() => {
-        toggleEditing();
-        setSaving(false);
-        getProfile();
-      }, 500);
+      await saveIndentify(/* … */);
+      toggleEditing();
+      setSaving(false);
+      await getProfile(); // assuming getProfile is async, otherwise remove await
```

This preserves UX feedback (`saving` spinner) while shortening the perceived latency and avoiding race conditions.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  useEffect(() => {
    if (!usernameRef.current || !bioRef.current || !statusRef.current) return;
    if (!isEditing) return;
    const handleMouseDown = async (e: MouseEvent) => {
      if (
        usernameRef.current.contains(e.target) ||
        bioRef.current.contains(e.target) ||
        statusRef.current.contains(e.target)
      )
        return;
      setSaving(true);
      await saveIndentify(
        artistAddress as Address,
        usernameRef.current.value,
        bioRef.current.value,
      );
      toggleEditing();
      setSaving(false);
      await getProfile(); // assuming getProfile is async, otherwise remove `await`
    };

    document.addEventListener("mousedown", handleMouseDown);
    return () => document.removeEventListener("mousedown", handleMouseDown);
  }, [isEditing, usernameRef, bioRef, statusRef]);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:49:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-02T00:46:08Z
body: [vc]: #gEuUsew3AhHaQ/qNIfuiy129iCrb4AGHUlq4JUyOoSU=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJpbi1wcm9jZXNzIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N3ZWV0bWFudGVjaHMtcHJvamVjdHMvaW4tcHJvY2Vzcy9DOWpVNmRoNVFOQTZUeWpQQzdyRVpCRmtvTno3IiwicHJldmlld1VybCI6ImluLXByb2Nlc3MtZ2l0LXRlY2hlbmdtZS1teWMtMTc2NC03NDEzZTAtc3dlZXRtYW50ZWNocy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwicm9vdERpcmVjdG9yeSI6bnVsbH1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **in-process** | ✅ Ready ([Inspect](https://vercel.com/sweetmantechs-projects/in-process/C9jU6dh5QNA6TyjPC7rEZBFkoNz7)) | [Visit Preview](https://in-process-git-techengme-myc-1764-7413e0-sweetmantechs-projects.vercel.app) | May 2, 2025 0:51am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-02T00:46:09Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce several updates across profile-related components and hooks. The `useProfile` hook is refactored to implement an automatic save mechanism for profile edits, triggered by clicks outside editable fields, using new refs for username, bio, and status. The manual save function is removed. The `DesktopProfile` and `MobileProfile` components are updated to use and assign these refs to their respective input fields. The `EditingStatus` component is simplified by removing interactive controls for saving or toggling editing, now only displaying status. The `PrivyButton` component now uses the `useUserProvider` hook instead of `useArtistProfile`. The `useUser` hook adds a `getProfile` method for refetching user data.

## Changes

| File(s)                                                  | Change Summary                                                                                                                         |
|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
| components/ArtistPage/DesktopProfile.tsx,                | Added `usernameRef` and `bioRef` from `useProfileProvider` and assigned them to input elements. Username and bio inputs set to type text. |
| components/ArtistPage/MobileProfile.tsx                  | Added `usernameRef` and `bioRef` from `useProfileProvider` and assigned them to username input and bio textarea.                        |
| components/ArtistPage/EditingStatus.tsx                  | Removed save/toggle editing controls and related functions; attached `statusRef` to root div; simplified UI to display status only.     |
| components/LoginButton/PrivyButton.tsx                   | Replaced `useArtistProfile` with `useUserProvider`; now uses `profile?.username` for display.                                           |
| hooks/useProfile.ts                                      | Refactored to auto-save on outside click using refs; removed manual save function; uses `useUserProvider` for address and `getProfile`. |
| hooks/useUser.ts                                         | Added `getProfile` method to returned object for refetching user profile data.                                                          |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant ProfileComponent
    participant useProfile (Hook)
    participant useUserProvider (Hook)
    participant Backend

    User->>ProfileComponent: Edit username/bio fields
    ProfileComponent->>useProfile: Assign refs to inputs
    User->>ProfileComponent: Clicks outside editable fields
    ProfileComponent->>useProfile: Detects outside click via refs
    useProfile->>useUserProvider: Calls saveIndentify with new values
    useProfile->>useUserProvider: Calls getProfile to refresh data
    useUserProvider->>Backend: Fetches updated profile
    Backend-->>useUserProvider: Returns updated profile
    useUserProvider-->>useProfile: Provides updated data
    useProfile-->>ProfileComponent: Updates display with new data
```

## Poem

> 🐇  
> A hop and a skip, the profile refines,  
> With clicks outside fields, it auto-aligns.  
> Save buttons retire, the UI’s more sleek,  
> New hooks and new refs—no more manual tweak!  
> A username here, a bio there,  
> Now updates just happen, as quick as a hare.  
>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=sweetmantech/IN_PROCESS&utm_content=389):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR