Repository: dimas-fahmi/tusktask
Pull request #12: Finalize name & username section for settings page
Author: login: dimas-fahmi
Created: 2025-12-10T12:44:33Z | Merged: 2025-12-10T14:42:32Z



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

* **New Features**
  * Real-time username availability checks with live validation status and save flow.
  * Improved profile name editing with controlled form, validation, and save notifications.
  * Username length validation enforced (4–100 characters).

* **Utilities**
  * New helpers to extract first and last names from profile data.

* **Bug Fixes / UI**
  * Fixed heading copy typo and adjusted account icon SVG fill behavior.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app/dashboard/settings/sections/AccountsSection.tsx b/app/dashboard/settings/sections/AccountsSection.tsx
index e3fc467..2595e1f 100644
--- a/app/dashboard/settings/sections/AccountsSection.tsx
+++ b/app/dashboard/settings/sections/AccountsSection.tsx
@@ -21,7 +21,7 @@ const AccountCard = ({
       <div className="flex items-center gap-4">
         {/* Icon */}
         <div>
-          <Icon className="w-6 h-6 opacity-70" />
+          <Icon className="w-6 h-6 opacity-70 fill-current" />
         </div>
 
         {/* Name & Status */}
diff --git a/app/dashboard/settings/sections/NameSection.tsx b/app/dashboard/settings/sections/NameSection.tsx
index 5efed96..daabd16 100644
--- a/app/dashboard/settings/sections/NameSection.tsx
+++ b/app/dashboard/settings/sections/NameSection.tsx
@@ -1,8 +1,65 @@
+"use client";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useEffect } from "react";
+import { Controller, useForm } from "react-hook-form";
+import z from "zod";
+import { useGetSelfProfile } from "@/src/lib/queries/hooks/useGetSelfProfile";
+import { useUpdateUserProfile } from "@/src/lib/queries/hooks/useUpdateUserProfile";
+import { useNotificationStore } from "@/src/lib/stores/notification";
+import { nameSchema } from "@/src/lib/zod";
 import Input from "@/src/ui/components/ui/Input/input";
 import { Button } from "@/src/ui/shadcn/components/ui/button";
 import SectionHeader from "../components/SectionHeader";
 
 const NameSection = () => {
+  const { data: profile } = useGetSelfProfile();
+  const { triggerToast } = useNotificationStore();
+
+  const {
+    control,
+    handleSubmit,
+    reset,
+    watch,
+    formState: { isValid },
+  } = useForm({
+    resolver: zodResolver(
+      z.object({
+        name: nameSchema,
+        firstName: z.string().optional(),
+        lastName: z.string().optional(),
+      }),
+    ),
+    mode: "onChange",
+    defaultValues: {
+      firstName: "",
+      lastName: "",
+      name: "",
+    },
+  });
+
+  const name = watch("name");
+  const firstName = watch("firstName");
+  const lastName = watch("lastName");
+
+  const isIdentical =
+    profile?.result?.name === name &&
+    profile?.result?.firstName === firstName &&
+    profile?.result?.lastName === lastName;
+
+  useEffect(() => {
+    if (profile) {
+      reset({
+        name: profile?.result?.name ?? "",
+        firstName: profile?.result?.firstName ?? "",
+        lastName: profile?.result?.lastName ?? "",
+      });
+    }
+  }, [profile, reset]);
+
+  const { mutate: updateProfile, isPending: isUpdatingProfile } =
+    useUpdateUserProfile();
+
   return (
     <section id="name-section" className="space-y-4">
       {/* Header */}
@@ -12,32 +69,107 @@ const NameSection = () => {
       />
 
       {/* Form */}
-      <form action="" className="space-y-4">
+      <form
+        className="space-y-4"
+        onSubmit={handleSubmit((data) => {
+          if (!profile?.result || isUpdatingProfile || isIdentical) return;
+
+          updateProfile(
+            { ...data },
+            {
+              onSuccess: () => {
+                reset({
+                  name: profile?.result?.name ?? "",
+                  firstName: profile?.result?.firstName ?? "",
+                  lastName: profile?.result?.lastName ?? "",
+                });
+                triggerToast(
+                  "Changes Saved",
+                  {
+                    description: "All changes saved successfully",
+                  },
+                  "success",
+                );
+              },
+              onError: () => {
+                triggerToast(
+                  "Something Went Wrong",
+                  {
+                    description:
+                      "Failed to save changes. If the issue persists, please contact the developer.",
+                  },
+                  "error",
+                );
+              },
+            },
+          );
+        })}
+      >
         {/* Fields */}
         <div className="space-y-4">
-          <div>
-            <Input
-              label="Display Name"
-              inputProps={{ placeholder: "Arthur Morgan" }}
-            />
-          </div>
+          <Controller
+            control={control}
+            name="name"
+            render={({ field, fieldState }) => (
+              <div>
+                <Input
+                  label="Display Name"
+                  inputProps={{
+                    placeholder: profile?.result?.name ?? "",
+                    ...field,
+                    autoComplete: "off",
+                  }}
+                  message={fieldState?.error?.message ?? ""}
+                  messageVariants={{ variant: "negative" }}
+                />
+              </div>
+            )}
+          />
 
           <div className="grid grid-cols-2 gap-2">
-            <Input
-              label="Display Name"
-              inputProps={{ placeholder: "Arthur Morgan" }}
+            <Controller
+              control={control}
+              name="firstName"
+              render={({ field, fieldState }) => (
+                <Input
+                  label="First Name"
+                  inputProps={{
+                    placeholder: profile?.result?.firstName ?? "",
+                    ...field,
+                    autoComplete: "off",
+                  }}
+                  message={fieldState?.error?.message ?? ""}
+                  messageVariants={{ variant: "negative" }}
+                />
+              )}
             />
-            <Input
-              label="Display Name"
-              inputProps={{ placeholder: "Arthur Morgan" }}
+
+            <Controller
+              control={control}
+              name="lastName"
+              render={({ field, fieldState }) => (
+                <Input
+                  label="Last Name"
+                  inputProps={{
+                    placeholder: profile?.result?.lastName ?? "",
+                    ...field,
+                    autoComplete: "off",
+                  }}
+                  message={fieldState?.error?.message ?? ""}
+                  messageVariants={{ variant: "negative" }}
+                />
+              )}
             />
           </div>
         </div>
 
         {/* Footer */}
         <footer>
-          <Button className="ms-auto block" disabled>
-            Save
+          <Button
+            className="ms-auto block"
+            disabled={!isValid || isUpdatingProfile || isIdentical}
+          >
+            {isUpdatingProfile ? "Saving" : "Save"}
           </Button>
         </footer>
       </form>
diff --git a/app/dashboard/settings/sections/ProfileSection.tsx b/app/dashboard/settings/sections/ProfileSection.tsx
index 45d05d0..3aac026 100644
--- a/app/dashboard/settings/sections/ProfileSection.tsx
+++ b/app/dashboard/settings/sections/ProfileSection.tsx
@@ -1,10 +1,12 @@
 "use client";
 
 import { useGetSelfProfile } from "@/src/lib/queries/hooks/useGetSelfProfile";
+import { getFirstName } from "@/src/lib/utils/getNames";
 import AvatarPicker from "@/src/ui/components/ui/AvatarPicker";
 
 const ProfileSection = () => {
   const { data: profile } = useGetSelfProfile();
+  const name = getFirstName(profile?.result);
 
   return (
     <section id="profile-picture-section" className="space-y-4">
@@ -15,9 +17,9 @@ const ProfileSection = () => {
 
       {/* Name & Username */}
       <div className="flex flex-col items-center">
-        <h1 className="text-4xl">Hi, {profile?.result?.name}!</h1>
+        <h1 className="text-4xl">Hi, {name}!</h1>
         <p className="mt-1.5 font-light">
-          You can costumize your settings here
+          You can customize your settings here
         </p>
       </div>
     </section>
diff --git a/app/dashboard/settings/sections/UsernameSection.tsx b/app/dashboard/settings/sections/UsernameSection.tsx
index 84a3829..c952255 100644
--- a/app/dashboard/settings/sections/UsernameSection.tsx
+++ b/app/dashboard/settings/sections/UsernameSection.tsx
@@ -1,8 +1,108 @@
+"use client";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useQuery } from "@tanstack/react-query";
+import { useEffect, useState } from "react";
+import { Controller, useForm } from "react-hook-form";
+import z from "zod";
+import { getUsernameAvailability } from "@/src/lib/queries/fetchers/getUsernameAvailability";
+import { useGetSelfProfile } from "@/src/lib/queries/hooks/useGetSelfProfile";
+import { useUpdateUserProfile } from "@/src/lib/queries/hooks/useUpdateUserProfile";
+import { useNotificationStore } from "@/src/lib/stores/notification";
+import { usernameSchema } from "@/src/lib/zod";
 import Input from "@/src/ui/components/ui/Input/input";
 import { Button } from "@/src/ui/shadcn/components/ui/button";
 import SectionHeader from "../components/SectionHeader";
 
 const UsernameSection = () => {
+  const { data: profile } = useGetSelfProfile();
+  const { triggerToast } = useNotificationStore();
+
+  const {
+    control,
+    handleSubmit,
+    watch,
+    reset,
+    formState: { isValid },
+  } = useForm({
+    resolver: zodResolver(
+      z.object({
+        username: usernameSchema,
+      }),
+    ),
+    mode: "onChange",
+    defaultValues: {
+      username: "",
+    },
+  });
+
+  const username = watch("username");
+
+  // Debouncer
+  const [usernameKey, setUsernameKey] = useState<string>("");
+  const [isTyping, setIsTyping] = useState(false);
+  const [isAvailable, setIsAvailable] = useState(false);
+
+  // biome-ignore lint/correctness/useExhaustiveDependencies: No need
+  useEffect(() => {
+    setIsTyping(true);
+    const debouncer = setTimeout(() => {
+      if (usernameKey !== username) {
+        setUsernameKey(username);
+      }
+      setIsTyping(false);
+    }, 1000);
+
+    return () => clearTimeout(debouncer);
+  }, [username]);
+
+  const { data: usernameCheck, isFetching: isCheckingUsername } = useQuery({
+    queryKey: [
+      "username",
+      "availability",
+      usernameKey.length > 3 ? usernameKey : "undefined",
+    ],
+    queryFn: () => getUsernameAvailability({ username: usernameKey }),
+    enabled: usernameKey.length > 3 && isValid && !isTyping,
+  });
+
+  useEffect(() => {
+    if (usernameCheck?.result?.takenById) {
+      setIsAvailable(false);
+      return;
+    }
+    setIsAvailable(true);
+  }, [usernameCheck]);
+
+  const { mutate: updateProfile, isPending: isUpdating } =
+    useUpdateUserProfile();
+
+  const getStatus = () => {
+    if (!isValid) return;
+
+    if (usernameKey === profile?.result?.username) {
+      return (
+        <span className="text-xs text-destructive">
+          Already used by yourself
+        </span>
+      );
+    }
+
+    if (isCheckingUsername) {
+      return <span className="text-xs">Checking username</span>;
+    }
+
+    if (usernameKey.length > 3 && !isCheckingUsername && !isAvailable) {
+      return (
+        <span className="text-xs text-destructive">Username is taken</span>
+      );
+    }
+
+    if (usernameKey.length > 3 && !isCheckingUsername && isAvailable) {
+      return <span className="text-xs text-success">Username available</span>;
+    }
+  };
+
   return (
     <section id="username-section" className="space-y-4">
       {/* Header */}
@@ -12,16 +112,81 @@ const UsernameSection = () => {
       />
 
       {/* Form */}
-      <form action="" className="space-y-4">
+      <form
+        className="space-y-4"
+        onSubmit={handleSubmit((data) => {
+          if (
+            !isValid ||
+            !isAvailable ||
+            isCheckingUsername ||
+            usernameKey === profile?.result?.username ||
+            isUpdating ||
+            isTyping
+          ) {
+            return;
+          }
+
+          updateProfile(
+            { ...data },
+            {
+              onSuccess: () => {
+                reset({ username: "" });
+                triggerToast(
+                  "Changes Saved",
+                  {
+                    description: "Username successfully changed",
+                  },
+                  "success",
+                );
+              },
+              onError: () => {
+                triggerToast(
+                  "Something Went Wrong",
+                  {
+                    description:
+                      "Failed to save changes, if issue persist please contact developer.",
+                  },
+                  "error",
+                );
+              },
+            },
+          );
+        })}
+      >
         {/* Field */}
         <div>
-          <Input label="Username" inputProps={{ placeholder: "dimas-fahmi" }} />
+          <Controller
+            control={control}
+            name="username"
+            render={({ field, fieldState }) => (
+              <Input
+                label="Username"
+                inputProps={{
+                  placeholder: profile?.result?.username ?? "",
+                  ...field,
+                }}
+                message={fieldState?.error?.message ?? ""}
+                messageVariants={{ variant: "negative" }}
+                labelRight={getStatus()}
+              />
+            )}
+          />
         </div>
 
         {/* Footer */}
         <footer>
-          <Button className="block ms-auto" disabled>
-            Save
+          <Button
+            className="block ms-auto"
+            disabled={
+              !isValid ||
+              !isAvailable ||
+              isCheckingUsername ||
+              usernameKey === profile?.result?.username ||
+              isUpdating ||
+              isTyping
+            }
+          >
+            {isUpdating ? "Saving" : "Save"}
           </Button>
         </footer>
       </form>
diff --git a/src/lib/utils/getNames.ts b/src/lib/utils/getNames.ts
new file mode 100644
index 0000000..c70ff78
--- /dev/null
+++ b/src/lib/utils/getNames.ts
@@ -0,0 +1,10 @@
+import type { UserType } from "@/src/db/schema/auth-schema";
+
+export const getFirstName = (profile?: UserType) => {
+  return profile?.firstName || profile?.name?.split(" ")?.[0] || "[no-name]";
+};
+
+export const getLastName = (profile?: UserType) => {
+  const splited = profile?.name?.split(" ");
+  return profile?.lastName || splited?.[splited.length - 1] || "[no-name]";
+};
diff --git a/src/lib/zod/index.ts b/src/lib/zod/index.ts
index 17feb53..c045dbf 100644
--- a/src/lib/zod/index.ts
+++ b/src/lib/zod/index.ts
@@ -7,6 +7,8 @@ export const boolean = z.boolean();
 export const usernameRegex = /^[a-z](?:[a-z0-9]*)(?:-[a-z0-9]+)?[a-z0-9]$/;
 export const usernameSchema = z
   .string()
+  .min(4)
+  .max(100)
   .regex(
     usernameRegex,
     "Usernames may only include lowercase letters, numbers, and single hyphens. Additionally, they must not start or end with any special characters.",


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

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

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (2)</summary><blockquote>

`58-66`: **Consider using a more explicit query key pattern and aligning length checks with schema.**

Using `"undefined"` as a string in the query key is a workaround that works but could be clearer. Also, `length > 3` is correct but `>= 4` would be more readable since `usernameSchema` uses `.min(4)`.



```diff
   const { data: usernameCheck, isFetching: isCheckingUsername } = useQuery({
-    queryKey: [
-      "username",
-      "availability",
-      usernameKey.length > 3 ? usernameKey : "undefined",
-    ],
+    queryKey: ["username", "availability", usernameKey],
     queryFn: () => getUsernameAvailability({ username: usernameKey }),
-    enabled: usernameKey.length > 3 && isValid && !isTyping,
+    enabled: usernameKey.length >= 4 && isValid && !isTyping,
   });
```

---

`68-74`: **`isAvailable` defaults to `false` but is set to `true` when no `takenById` exists.**

The initial state `isAvailable: false` combined with the effect logic means that before any availability check runs, the form treats the username as unavailable. This is likely fine since the button is also gated by `isValid`, but consider initializing based on whether a check has occurred yet to avoid potential edge cases.

</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 56b17d9d2cd0f2265b832aef71901d482cef069b and 3cea16a14ecf9888ebbb6102feba9d67b78f35c9.

</details>

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

* `app/dashboard/settings/sections/AccountsSection.tsx` (1 hunks)
* `app/dashboard/settings/sections/NameSection.tsx` (2 hunks)
* `app/dashboard/settings/sections/ProfileSection.tsx` (2 hunks)
* `app/dashboard/settings/sections/UsernameSection.tsx` (2 hunks)
* `src/lib/utils/getNames.ts` (1 hunks)
* `src/lib/zod/index.ts` (1 hunks)

</details>

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

<details>
<summary>🧬 Code graph analysis (4)</summary>

<details>
<summary>app/dashboard/settings/sections/ProfileSection.tsx (2)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/utils/getNames.ts (1)</summary>

* `getFirstName` (3-5)

</details>

</blockquote></details>
<details>
<summary>src/lib/utils/getNames.ts (1)</summary><blockquote>

<details>
<summary>src/db/schema/auth-schema.ts (1)</summary>

* `UserType` (35-35)

</details>

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (5)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/stores/notification.ts (1)</summary>

* `useNotificationStore` (46-64)

</details>
<details>
<summary>src/lib/zod/index.ts (1)</summary>

* `usernameSchema` (8-15)

</details>
<details>
<summary>src/lib/queries/fetchers/getUsernameAvailability.ts (1)</summary>

* `getUsernameAvailability` (6-21)

</details>
<details>
<summary>src/lib/queries/hooks/useUpdateUserProfile.ts (1)</summary>

* `useUpdateUserProfile` (7-50)

</details>

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/NameSection.tsx (4)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/stores/notification.ts (1)</summary>

* `useNotificationStore` (46-64)

</details>
<details>
<summary>src/lib/zod/index.ts (1)</summary>

* `nameSchema` (3-3)

</details>
<details>
<summary>src/lib/queries/hooks/useUpdateUserProfile.ts (1)</summary>

* `useUpdateUserProfile` (7-50)

</details>

</blockquote></details>

</details>

</details>

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

<details>
<summary>app/dashboard/settings/sections/AccountsSection.tsx (1)</summary><blockquote>

`24-24`: **Icon color inheritance change looks good**

Adding `fill-current` makes the SVG icon fill follow the current text color while preserving existing sizing and opacity, which should improve visual consistency across the app without affecting behavior.

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/ProfileSection.tsx (1)</summary><blockquote>

`4-9`: **Good use of the utility function for consistent name extraction.**

Using `getFirstName` for the greeting provides a more personalized user experience.

</blockquote></details>
<details>
<summary>src/lib/zod/index.ts (1)</summary><blockquote>

`8-15`: **LGTM! Sensible length constraints added to username validation.**

The `min(4)` and `max(100)` constraints appropriately tighten username validation. Placing length checks before the regex is efficient as they fail fast on invalid lengths.

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (1)</summary><blockquote>

`79-103`: **Good implementation of status indicator logic.**

The `getStatus` function provides clear feedback for all relevant states: self-use, checking, taken, and available. The conditional rendering is well-structured.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-12-10T12:47:29Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (1)</summary><blockquote>

`145-146`: **Fix grammar in error message.**

The error message has a grammatical error.



Apply this diff:

```diff
                     description:
-                      "Failed to save changes, if issue persist please contact developer.",
+                      "Failed to save changes. If the issue persists, please contact the developer.",
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (2)</summary><blockquote>

`68-74`: **Improve availability state logic.**

The current logic sets `isAvailable=true` when `usernameCheck` is undefined (query hasn't run yet or is disabled). This is semantically incorrect—a username shouldn't be considered "available" until explicitly verified.

While the submission guards prevent actual bugs, this creates confusing intermediate state that could cause issues if guards are modified later.



Apply this diff to handle the undefined case explicitly:

```diff
   useEffect(() => {
+    if (!usernameCheck) {
+      setIsAvailable(false);
+      return;
+    }
     if (usernameCheck?.result?.takenById) {
       setIsAvailable(false);
       return;
     }
     setIsAvailable(true);
   }, [usernameCheck]);
```

Alternatively, consider removing the `isAvailable` state entirely and computing it directly from `usernameCheck` where needed, which would eliminate the synchronization concern.

---

`117-126`: **Consider extracting submission guard conditions.**

The same complex boolean conditions appear in both the submission handler (lines 117-126) and the button's disabled prop (lines 179-186). This duplication makes maintenance harder and increases the risk of inconsistencies.



Extract the conditions into a computed variable:

```diff
+  const isSubmitDisabled =
+    !isValid ||
+    !isAvailable ||
+    isCheckingUsername ||
+    usernameKey === profile?.result?.username ||
+    isUpdating ||
+    isTyping;
+
   return (
     <section id="username-section" className="space-y-4">
       {/* ... */}
       <form
         className="space-y-4"
         onSubmit={handleSubmit((data) => {
-          if (
-            !isValid ||
-            !isAvailable ||
-            isCheckingUsername ||
-            usernameKey === profile?.result?.username ||
-            isUpdating ||
-            isTyping
-          ) {
+          if (isSubmitDisabled) {
             return;
           }
           {/* ... */}
         })}
       >
         {/* ... */}
         <Button
           className="block ms-auto"
-          disabled={
-            !isValid ||
-            !isAvailable ||
-            isCheckingUsername ||
-            usernameKey === profile?.result?.username ||
-            isUpdating ||
-            isTyping
-          }
+          disabled={isSubmitDisabled}
         >
```

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/NameSection.tsx (2)</summary><blockquote>

`50-58`: **Resetting on every `profile` change can wipe in‑progress edits**

Because `profile` likely comes from React Query, its identity can change on background refetch (e.g., `refetchOnWindowFocus`, polling). With `profile` in the dependency array, this effect will call `reset` on each such change and overwrite any unsaved edits the user is currently making.

Consider only initializing once when `profile.result` first becomes available:

```diff
-import { useEffect } from "react";
+import { useEffect, useRef } from "react";
@@
-  useEffect(() => {
-    if (profile) {
-      reset({
-        name: profile?.result?.name ?? "",
-        firstName: profile?.result?.firstName ?? "",
-        lastName: profile?.result?.lastName ?? "",
-      });
-    }
-  }, [profile, reset]);
+  const hasInitializedProfile = useRef(false);
+
+  useEffect(() => {
+    if (!profile?.result || hasInitializedProfile.current) return;
+
+    reset({
+      name: profile.result.name ?? "",
+      firstName: profile.result.firstName ?? "",
+      lastName: profile.result.lastName ?? "",
+    });
+
+    hasInitializedProfile.current = true;
+  }, [profile?.result, reset]);
```

This keeps the initial populate behavior without clobbering user input on later refetches.

---

`77-86`: **`reset` in `onSuccess` can use stale `profile` and is redundant with the profile-driven reset**

The `onSuccess` callback closes over `profile`, so it may call `reset` with an outdated snapshot of the profile if the query updates after the mutation. Since you already reset from `profile` in the effect, this extra reset is both redundant and a potential source of flicker (old values briefly replacing what the user just saved).

You can simplify by relying on the query + effect and dropping the explicit reset:

```diff
-              onSuccess: () => {
-                reset({
-                  name: profile?.result?.name ?? "",
-                  firstName: profile?.result?.firstName ?? "",
-                  lastName: profile?.result?.lastName ?? "",
-                });
-                triggerToast(
+              onSuccess: () => {
+                triggerToast(
                   "Changes Saved",
                   {
                     description: "All changes saved successfully",
                   },
```

</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 3cea16a14ecf9888ebbb6102feba9d67b78f35c9 and eecc23809395069b0ec11b7a7d6b3bc003b1c185.

</details>

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

* `app/dashboard/settings/sections/NameSection.tsx` (2 hunks)
* `app/dashboard/settings/sections/ProfileSection.tsx` (2 hunks)
* `app/dashboard/settings/sections/UsernameSection.tsx` (2 hunks)

</details>

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

<details>
<summary>🧬 Code graph analysis (3)</summary>

<details>
<summary>app/dashboard/settings/sections/ProfileSection.tsx (2)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/utils/getNames.ts (1)</summary>

* `getFirstName` (3-5)

</details>

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (5)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/stores/notification.ts (1)</summary>

* `useNotificationStore` (46-64)

</details>
<details>
<summary>src/lib/zod/index.ts (1)</summary>

* `usernameSchema` (8-15)

</details>
<details>
<summary>src/lib/queries/fetchers/getUsernameAvailability.ts (1)</summary>

* `getUsernameAvailability` (6-21)

</details>
<details>
<summary>src/lib/queries/hooks/useUpdateUserProfile.ts (1)</summary>

* `useUpdateUserProfile` (7-50)

</details>

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/NameSection.tsx (4)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/stores/notification.ts (1)</summary>

* `useNotificationStore` (46-64)

</details>
<details>
<summary>src/lib/zod/index.ts (1)</summary>

* `nameSchema` (3-3)

</details>
<details>
<summary>src/lib/queries/hooks/useUpdateUserProfile.ts (1)</summary>

* `useUpdateUserProfile` (7-50)

</details>

</blockquote></details>

</details>

</details>

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

<details>
<summary>app/dashboard/settings/sections/ProfileSection.tsx (2)</summary><blockquote>

`4-4`: **LGTM! Clean refactoring of name extraction.**

The introduction of the `getFirstName` utility improves code organization and provides consistent name handling with a proper fallback.




Also applies to: 9-9, 20-20

---

`22-22`: **Typo corrected.**

The spelling fix addresses the previous review feedback.

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (4)</summary><blockquote>

`1-15`: **LGTM!**

The imports are well-organized and appropriate for a client-side form component with real-time validation and API integration.

---

`17-37`: **LGTM!**

Form setup correctly uses zodResolver with the username schema and enables real-time validation through `onChange` mode, providing immediate feedback to users.

---

`76-103`: **LGTM!**

The status display logic provides clear, real-time feedback to users about username availability, checking state, and validation errors.

---

`157-189`: **LGTM!**

The form UI properly integrates react-hook-form with the Input component, provides clear user feedback through validation messages and availability status, and handles the loading state during submission.

</blockquote></details>
<details>
<summary>app/dashboard/settings/sections/NameSection.tsx (1)</summary><blockquote>

`60-62`: **Submission guards and button disabling logic are well-structured**

The combination of `!profile?.result || isUpdatingProfile || isIdentical` in the submit handler plus `disabled={!isValid || isUpdatingProfile || isIdentical}` on the button gives a clean UX: no double submits, no calls without a loaded profile, and no-op edits are short‑circuited (once the `isIdentical` normalization issue is fixed).




Also applies to: 72-76, 168-173

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-12-10T14:10:59Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (1)</summary><blockquote>

`147-147`: **Fix grammar in error message.**

The error description has a grammatical error.



Apply this diff:

```diff
-                    "Failed to save changes, if issue persist please contact developer.",
+                    "Failed to save changes. If the issue persists, please contact the developer.",
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (1)</summary><blockquote>

`46-57`: **Optional simplification: Remove unnecessary conditional check.**

The dependency array correctly includes only `username`, fixing the previous debounce issue. However, the conditional check on lines 50-52 is now unnecessary since the effect only re-runs when `username` changes, making it impossible for `usernameKey === username` to be true at that point.



Apply this diff to simplify:

```diff
   useEffect(() => {
     setIsTyping(true);
     const debouncer = setTimeout(() => {
-      if (usernameKey !== username) {
-        setUsernameKey(username);
-      }
+      setUsernameKey(username);
       setIsTyping(false);
     }, 1000);
 
     return () => clearTimeout(debouncer);
   }, [username]);
```

</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 eecc23809395069b0ec11b7a7d6b3bc003b1c185 and 55dd0ed1599fb120e278fc38aee41ab05c6aab6a.

</details>

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

* `app/dashboard/settings/sections/UsernameSection.tsx` (2 hunks)

</details>

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

<details>
<summary>🧬 Code graph analysis (1)</summary>

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (5)</summary><blockquote>

<details>
<summary>src/lib/queries/hooks/useGetSelfProfile.ts (1)</summary>

* `useGetSelfProfile` (4-10)

</details>
<details>
<summary>src/lib/stores/notification.ts (1)</summary>

* `useNotificationStore` (46-64)

</details>
<details>
<summary>src/lib/zod/index.ts (1)</summary>

* `usernameSchema` (8-15)

</details>
<details>
<summary>src/lib/queries/fetchers/getUsernameAvailability.ts (1)</summary>

* `getUsernameAvailability` (6-21)

</details>
<details>
<summary>src/lib/queries/hooks/useUpdateUserProfile.ts (1)</summary>

* `useUpdateUserProfile` (7-50)

</details>

</blockquote></details>

</details>

</details>

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

<details>
<summary>app/dashboard/settings/sections/UsernameSection.tsx (7)</summary><blockquote>

`142-151`: **Past issue resolved: onError callback now executes correctly.**

The previous critical bug where the `onError` callback used square brackets (creating an array) has been fixed. The callback now properly uses curly braces and will execute the toast on error.

---

`1-16`: **LGTM!**

All imports are properly organized and appropriate for the component's functionality.

---

`17-37`: **LGTM!**

Form initialization properly uses `zodResolver` with the imported `usernameSchema`, enabling real-time validation via `onChange` mode. The hook integrations for profile data and notifications are correct.

---

`59-75`: **LGTM!**

The username availability check is properly gated by length, validity, and typing state. The query key construction and the effect that updates `isAvailable` based on the response both follow correct patterns.

---

`80-104`: **LGTM!**

The status function correctly handles all validation states and provides clear, user-friendly feedback for each scenario.

---

`158-173`: **LGTM!**

The `Controller` implementation correctly integrates react-hook-form with the custom `Input` component, properly spreading field props, displaying validation errors, and showing dynamic status via `labelRight`.

---

`178-190`: **LGTM!**

The submit button's disabled state correctly mirrors all the submission blocking conditions, and the dynamic label provides clear feedback during the save operation.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-12-10T14:31:14Z

Review comments:
diff_hunk: @@ -1,8 +1,43 @@
+"use client";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Controller, useForm } from "react-hook-form";
+import z from "zod";
+import { useGetSelfProfile } from "@/src/lib/queries/hooks/useGetSelfProfile";
+import { useUpdateUserProfile } from "@/src/lib/queries/hooks/useUpdateUserProfile";
+import { useNotificationStore } from "@/src/lib/stores/notification";
+import { nameSchema } from "@/src/lib/zod";
 import Input from "@/src/ui/components/ui/Input/input";
 import { Button } from "@/src/ui/shadcn/components/ui/button";
 import SectionHeader from "../components/SectionHeader";
 
 const NameSection = () => {
+  const { data: profile } = useGetSelfProfile();
+  const { triggerToast } = useNotificationStore();
+
+  const {
+    control,
+    handleSubmit,
+    formState: { isValid },
+  } = useForm({
+    resolver: zodResolver(
+      z.object({
+        name: nameSchema,
+        firstName: z.string().optional(),
+        lastName: z.string().optional(),
+      }),
+    ),
+    mode: "onChange",
+    defaultValues: {
+      firstName: "",
+      lastName: "",
+      name: "",
+    },
+  });
path: app/dashboard/settings/sections/NameSection.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🟠 Major_

**Form not populated with existing profile data; comparison logic will always detect changes.**

The form initializes with empty strings, but the submit handler compares against `profile.result` values. Since the form starts empty, users must re-enter all values, and submissions will always appear as "changes" even if they match existing data.

Consider populating the form with profile data using the `values` option or a `useEffect` to reset when profile loads:



```diff
   const {
     control,
     handleSubmit,
+    reset,
     formState: { isValid },
   } = useForm({
     resolver: zodResolver(
       z.object({
         name: nameSchema,
         firstName: z.string().optional(),
         lastName: z.string().optional(),
       }),
     ),
     mode: "onChange",
-    defaultValues: {
-      firstName: "",
-      lastName: "",
-      name: "",
-    },
+    defaultValues: {
+      firstName: profile?.result?.firstName ?? "",
+      lastName: profile?.result?.lastName ?? "",
+      name: profile?.result?.name ?? "",
+    },
   });
+
+  useEffect(() => {
+    if (profile?.result) {
+      reset({
+        firstName: profile.result.firstName ?? "",
+        lastName: profile.result.lastName ?? "",
+        name: profile.result.name ?? "",
+      });
+    }
+  }, [profile?.result, reset]);
```

<!-- 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
  } = useForm({
    resolver: zodResolver(
      z.object({
        name: nameSchema,
        firstName: z.string().optional(),
        lastName: z.string().optional(),
      }),
    ),
    mode: "onChange",
    defaultValues: {
      firstName: profile?.result?.firstName ?? "",
      lastName: profile?.result?.lastName ?? "",
      name: profile?.result?.name ?? "",
    },
  });

  useEffect(() => {
    if (profile?.result) {
      reset({
        firstName: profile.result.firstName ?? "",
        lastName: profile.result.lastName ?? "",
        name: profile.result.name ?? "",
      });
    }
  }, [profile?.result, reset]);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/dashboard/settings/sections/NameSection.tsx around lines 22 to 36, the
form is initialized with empty defaultValues causing comparisons against
profile.result to always appear as changes; populate the form with the loaded
profile data by passing the profile values into the form (use the
values/defaultValues option) or call react-hook-form's reset() in a useEffect
when profile.result becomes available so the form fields mirror the existing
profile; ensure optional fields match the zod schema and that the effect runs
only when profile data changes to avoid overwriting user edits.
```

</details>

<!-- fingerprinting:phantom:medusa:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-12-10T12:47:27Z
author_association: NONE
diff_hunk: @@ -12,32 +47,115 @@ const NameSection = () => {
       />
 
       {/* Form */}
-      <form action="" className="space-y-4">
+      <form
+        className="space-y-4"
+        onSubmit={handleSubmit((data) => {
+          if (!profile?.result || isUpdatingProfile) return;
+          const { firstName, lastName, name } = profile.result;
+          if (
+            data.firstName === firstName &&
+            data.lastName === lastName &&
+            data.name === name
+          ) {
+            triggerToast(
+              "Nothing to Save",
+              {
+                description: "Data identical, nothing to save.",
+              },
+              "info",
+            );
+
+            return;
+          }
+
+          updateProfile(
+            { ...data },
+            {
+              onSuccess: () => {
+                triggerToast(
+                  "Changes Saved",
+                  {
+                    description: "All changes is saved successfully",
+                  },
+                  "success",
+                );
+              },
+              onError: () => {
+                triggerToast(
+                  "Something Went Wrong",
+                  {
+                    description:
+                      "Failed to save changes, if issue persit please contact developer.",
+                  },
+                  "error",
+                );
path: app/dashboard/settings/sections/NameSection.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🟡 Minor_

**Typos in toast messages.**

- Line 78: "All changes is saved" → "All changes are saved"  
- Line 88: "persit" → "persist"



```diff
               onSuccess: () => {
                 triggerToast(
                   "Changes Saved",
                   {
-                    description: "All changes is saved successfully",
+                    description: "All changes saved successfully",
                   },
                   "success",
                 );
               },
               onError: () => {
                 triggerToast(
                   "Something Went Wrong",
                   {
                     description:
-                      "Failed to save changes, if issue persit please contact developer.",
+                      "Failed to save changes. If the issue persists, please contact the developer.",
                   },
                   "error",
                 );
               },
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/dashboard/settings/sections/NameSection.tsx around lines 74 to 91, fix
the typos in the toast messages: change "All changes is saved successfully" to
"All changes are saved successfully" and change "Failed to save changes, if
issue persit please contact developer." to "Failed to save changes; if the issue
persists, please contact the developer." Ensure punctuation and grammar are
correct and preserve the existing triggerToast call structure and toast types.
```

</details>

<!-- fingerprinting:phantom:medusa:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-12-10T12:47:28Z
author_association: NONE
diff_hunk: @@ -15,7 +17,7 @@ const ProfileSection = () => {
 
       {/* Name & Username */}
       <div className="flex flex-col items-center">
-        <h1 className="text-4xl">Hi, {profile?.result?.name}!</h1>
+        <h1 className="text-4xl">Hi, {name}!</h1>
         <p className="mt-1.5 font-light">
           You can costumize your settings here
         </p>
path: app/dashboard/settings/sections/ProfileSection.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🟡 Minor_

**Typo in user-facing text: "costumize" → "customize".**



```diff
         <p className="mt-1.5 font-light">
-          You can costumize your settings here
+          You can customize your settings here
         </p>
```

<!-- 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
        <p className="mt-1.5 font-light">
          You can customize your settings here
        </p>
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/dashboard/settings/sections/ProfileSection.tsx around lines 21 to 23, the
user-facing copy contains a typo ("costumize"); update the string to "customize"
to correct spelling and keep the sentence as "You can customize your settings
here". Ensure no other formatting or punctuation changes are made.
```

</details>

<!-- fingerprinting:phantom:medusa:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-12-10T12:47:28Z
author_association: NONE
diff_hunk: @@ -12,16 +111,81 @@ const UsernameSection = () => {
       />
 
       {/* Form */}
-      <form action="" className="space-y-4">
+      <form
+        className="space-y-4"
+        onSubmit={handleSubmit((data) => {
+          if (
+            !isValid ||
+            !isAvailable ||
+            isCheckingUsername ||
+            usernameKey === profile?.result?.username ||
+            isUpdating ||
+            isTyping
+          ) {
+            return;
+          }
+
+          updateProfile(
+            { ...data },
+            {
+              onSuccess: () => {
+                reset({ username: "" });
+                triggerToast(
+                  "Changes Saved",
+                  {
+                    description: "Username successfully changed",
+                  },
+                  "success",
+                );
+              },
+              onError: () => [
+                triggerToast(
+                  "Something Went Wrong",
+                  {
+                    description:
+                      "Failed to save changes, if issue persist please contact developer.",
+                  },
+                  "error",
+                ),
+              ],
path: app/dashboard/settings/sections/UsernameSection.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🔴 Critical_

**Bug: `onError` callback returns an array instead of executing the toast.**

The square bracket `[` on line 141 creates an array expression instead of a block statement. The toast won't be triggered on error.



```diff
             onError: () => {
-              onError: () => [
-                triggerToast(
-                  "Something Went Wrong",
-                  {
-                    description:
-                      "Failed to save changes, if issue persist please contact developer.",
-                  },
-                  "error",
-                ),
-              ],
+              onError: () => {
+                triggerToast(
+                  "Something Went Wrong",
+                  {
+                    description:
+                      "Failed to save changes. If the issue persists, please contact the developer.",
+                  },
+                  "error",
+                );
+              },
```

<!-- 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
              onError: () => {
                triggerToast(
                  "Something Went Wrong",
                  {
                    description:
                      "Failed to save changes. If the issue persists, please contact the developer.",
                  },
                  "error",
                );
              },
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/dashboard/settings/sections/UsernameSection.tsx around lines 141 to 150,
the onError arrow function mistakenly uses square brackets which creates and
returns an array instead of executing the toast; replace the array expression
with a block body (use { ... }) and call triggerToast directly (no explicit
return of an array) so the toast is executed when an error occurs, keeping the
same message, options, and "error" type.
```

</details>

<!-- fingerprinting:phantom:medusa:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-12-10T12:47:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,10 @@
+import type { UserType } from "@/src/db/schema/auth-schema";
+
+export const getFirstName = (profile?: UserType) => {
+  return profile?.firstName || profile?.name?.split(" ")?.[0] || "[no-name]";
+};
+
+export const getLastName = (profile?: UserType) => {
+  const splited = profile?.name?.split(" ");
+  return profile?.lastName || splited?.[splited.length - 1] || "[no-name]";
path: src/lib/utils/getNames.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🟡 Minor_

**Logic issue: `getLastName` returns the same value as first name for single-word names.**

When `profile.name` is a single word (e.g., "John"), `splited[splited.length - 1]` returns "John", which would be identical to what `getFirstName` returns. Consider returning an empty string or `"[no-name]"` when there's only one name segment.

Also, minor typo: `splited` → `split`.



```diff
 export const getLastName = (profile?: UserType) => {
-  const splited = profile?.name?.split(" ");
-  return profile?.lastName || splited?.[splited.length - 1] || "[no-name]";
+  const split = profile?.name?.split(" ");
+  const hasMultipleNames = split && split.length > 1;
+  return profile?.lastName || (hasMultipleNames ? split[split.length - 1] : "[no-name]");
 };
```

<!-- 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
export const getLastName = (profile?: UserType) => {
  const split = profile?.name?.split(" ");
  const hasMultipleNames = split && split.length > 1;
  return profile?.lastName || (hasMultipleNames ? split[split.length - 1] : "[no-name]");
};
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/lib/utils/getNames.ts around lines 7 to 9, the function returns the same
value as first name for single-word names and has a typo in the variable name;
rename `splited` to `split` (or `parts`) and change the logic so that when
profile.name splits into only one segment the function returns an empty string
(or "[no-name]" per project convention) instead of that single segment,
otherwise return profile.lastName || parts[parts.length - 1] || "[no-name]".
```

</details>

<!-- fingerprinting:phantom:medusa:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-12-10T12:47:28Z
author_association: NONE
diff_hunk: @@ -1,8 +1,65 @@
+"use client";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useEffect } from "react";
+import { Controller, useForm } from "react-hook-form";
+import z from "zod";
+import { useGetSelfProfile } from "@/src/lib/queries/hooks/useGetSelfProfile";
+import { useUpdateUserProfile } from "@/src/lib/queries/hooks/useUpdateUserProfile";
+import { useNotificationStore } from "@/src/lib/stores/notification";
+import { nameSchema } from "@/src/lib/zod";
 import Input from "@/src/ui/components/ui/Input/input";
 import { Button } from "@/src/ui/shadcn/components/ui/button";
 import SectionHeader from "../components/SectionHeader";
 
 const NameSection = () => {
+  const { data: profile } = useGetSelfProfile();
+  const { triggerToast } = useNotificationStore();
+
+  const {
+    control,
+    handleSubmit,
+    reset,
+    watch,
+    formState: { isValid },
+  } = useForm({
+    resolver: zodResolver(
+      z.object({
+        name: nameSchema,
+        firstName: z.string().optional(),
+        lastName: z.string().optional(),
+      }),
+    ),
+    mode: "onChange",
+    defaultValues: {
+      firstName: "",
+      lastName: "",
+      name: "",
+    },
+  });
+
+  const name = watch("name");
+  const firstName = watch("firstName");
+  const lastName = watch("lastName");
+
+  const isIdentical =
+    profile?.result?.name === name &&
+    profile?.result?.firstName === firstName &&
+    profile?.result?.lastName === lastName;
path: app/dashboard/settings/sections/NameSection.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🟠 Major_

**`isIdentical` can be false even when form matches profile because of undefined vs `""`**

`reset` normalizes `name/firstName/lastName` to empty strings (`?? ""`), but `isIdentical` compares against the raw `profile?.result` values. If the API returns `undefined` for `firstName`/`lastName`, `isIdentical` is always `false` even when the user hasn't changed anything, so the submit guard and disabled Save state won't behave as intended.

Normalize on both sides to keep the identity check consistent:

```diff
-  const isIdentical =
-    profile?.result?.name === name &&
-    profile?.result?.firstName === firstName &&
-    profile?.result?.lastName === lastName;
+  const isIdentical =
+    (profile?.result?.name ?? "") === name &&
+    (profile?.result?.firstName ?? "") === firstName &&
+    (profile?.result?.lastName ?? "") === lastName;
```

<!-- 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 name = watch("name");
  const firstName = watch("firstName");
  const lastName = watch("lastName");

  const isIdentical =
    (profile?.result?.name ?? "") === name &&
    (profile?.result?.firstName ?? "") === firstName &&
    (profile?.result?.lastName ?? "") === lastName;
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/dashboard/settings/sections/NameSection.tsx around lines 41 to 48, the
identity check compares form values (which reset normalizes to empty strings)
against raw profile.result fields that may be undefined, causing isIdentical to
be false even when unchanged; fix by normalizing the profile values the same way
(e.g. use profile?.result?.name ?? "" and profile?.result?.firstName ?? "" and
profile?.result?.lastName ?? "") before comparing to the watched form values so
undefined and "" are treated equivalently.
```

</details>

<!-- fingerprinting:phantom:poseidon:olive -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-12-10T14:10:59Z
author_association: NONE
diff_hunk: @@ -1,8 +1,107 @@
+"use client";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useQuery } from "@tanstack/react-query";
+import { useEffect, useState } from "react";
+import { Controller, useForm } from "react-hook-form";
+import z from "zod";
+import { getUsernameAvailability } from "@/src/lib/queries/fetchers/getUsernameAvailability";
+import { useGetSelfProfile } from "@/src/lib/queries/hooks/useGetSelfProfile";
+import { useUpdateUserProfile } from "@/src/lib/queries/hooks/useUpdateUserProfile";
+import { useNotificationStore } from "@/src/lib/stores/notification";
+import { usernameSchema } from "@/src/lib/zod";
 import Input from "@/src/ui/components/ui/Input/input";
 import { Button } from "@/src/ui/shadcn/components/ui/button";
 import SectionHeader from "../components/SectionHeader";
 
 const UsernameSection = () => {
+  const { data: profile } = useGetSelfProfile();
+  const { triggerToast } = useNotificationStore();
+
+  const {
+    control,
+    handleSubmit,
+    watch,
+    reset,
+    formState: { isValid },
+  } = useForm({
+    resolver: zodResolver(
+      z.object({
+        username: usernameSchema,
+      }),
+    ),
+    mode: "onChange",
+    defaultValues: {
+      username: "",
+    },
+  });
+
+  const username = watch("username");
+
+  // Debouncer
+  const [usernameKey, setUsernameKey] = useState<string>("");
+  const [isTyping, setIsTyping] = useState(false);
+  const [isAvailable, setIsAvailable] = useState(false);
+
+  useEffect(() => {
+    setIsTyping(true);
+    const debouncer = setTimeout(() => {
+      if (usernameKey !== username) {
+        setUsernameKey(username);
+      }
+      setIsTyping(false);
+    }, 1000);
+
+    return () => clearTimeout(debouncer);
+  }, [username, usernameKey]);
path: app/dashboard/settings/sections/UsernameSection.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_ | _🔴 Critical_

**Critical: Debounce logic broken by incorrect dependency array.**

The useEffect dependency array includes `usernameKey`, which causes the effect to re-run when `usernameKey` is updated inside the effect itself (line 50). This breaks the debouncing behavior:

1. User types → effect runs → sets `isTyping=true` → after 1s updates `usernameKey`
2. `usernameKey` change triggers effect again → sets `isTyping=true` again → waits another 1s

This defeats the purpose of debouncing and causes unnecessary delays in checking username availability.



Apply this diff to fix the debounce logic:

```diff
   useEffect(() => {
     setIsTyping(true);
     const debouncer = setTimeout(() => {
-      if (usernameKey !== username) {
-        setUsernameKey(username);
-      }
+      setUsernameKey(username);
       setIsTyping(false);
     }, 1000);
 
     return () => clearTimeout(debouncer);
-  }, [username, usernameKey]);
+  }, [username]);
```

<!-- 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(() => {
    setIsTyping(true);
    const debouncer = setTimeout(() => {
      setUsernameKey(username);
      setIsTyping(false);
    }, 1000);

    return () => clearTimeout(debouncer);
  }, [username]);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In app/dashboard/settings/sections/UsernameSection.tsx around lines 46 to 56,
the useEffect debounce is incorrect because it lists usernameKey in the
dependency array causing the effect to re-run when usernameKey is updated;
change the dependency array to only [username] so the timeout only restarts when
the user types, keep the same timeout/cleanup logic (setIsTyping(true) at start,
setUsernameKey(username) and setIsTyping(false) inside the timeout, and return
() => clearTimeout(debouncer)) to restore proper debouncing behavior.
```

</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 55dd0ed
created_at: 2025-12-10T14:10:59Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-12-10T12:44:39Z
body: [vc]: #YsGWyuzVfoaFSH2dmjSPkg5q8UkGaMiqLaRgVT/j03k=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0dXNrdGFzayIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9kaW1hcy1mYWhtaXMtcHJvamVjdHMvdHVza3Rhc2svQkI0dm1lVDNwR1B5OTU3cFpGZmYyOHo4WGdLYiIsInByZXZpZXdVcmwiOiJ0dXNrdGFzay1naXQtZmVhdC1hdXRoLWRpbWFzLWZhaG1pcy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InR1c2t0YXNrLWdpdC1mZWF0LWF1dGgtZGltYXMtZmFobWlzLXByb2plY3RzLnZlcmNlbC5hcHAifSwicm9vdERpcmVjdG9yeSI6bnVsbH1dfQ==
The latest updates on your projects. Learn more about [Vercel for GitHub](https://vercel.link/github-learn-more).

| Project | Deployment | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| [tusktask](https://vercel.com/dimas-fahmis-projects/tusktask) | ![Ready](https://vercel.com/static/status/ready.svg) [Ready](https://vercel.com/dimas-fahmis-projects/tusktask/BB4vmeT3pGPy957pZFff28z8XgKb) | [Preview](https://tusktask-git-feat-auth-dimas-fahmis-projects.vercel.app) | [Comment](https://vercel.live/open-feedback/tusktask-git-feat-auth-dimas-fahmis-projects.vercel.app?via=pr-comment-feedback-link) | Dec 10, 2025 2:29pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-12-10T12:44:46Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

Refactors settings: converts NameSection and UsernameSection to controlled react-hook-form flows with zod validation and client-side availability checks, adds getFirstName/getLastName utilities, tightens username zod schema, tweaks an Icon class, and wires profile update mutations with toast feedback.

## Changes

| Cohort / File(s) | Summary |
|---|---|
| **Validation schema update** <br> `src/lib/zod/index.ts` | Added `.min(4)` and `.max(100)` to `usernameSchema` before the existing `.regex(...)` validation. |
| **Name extraction utilities** <br> `src/lib/utils/getNames.ts` | New module exporting `getFirstName(profile?: UserType)` and `getLastName(profile?: UserType)` to derive name segments with fallbacks. |
| **Name form & profile display** <br> `app/dashboard/settings/sections/NameSection.tsx`, `app/dashboard/settings/sections/ProfileSection.tsx` | `NameSection`: migrated to react-hook-form + zod, initializes from fetched profile, implements change-detection, mutation-based updates with success/error toasts, controlled inputs, and validation/disable logic. `ProfileSection`: uses `getFirstName(...)` and fixes a typo. |
| **Username form with availability checks** <br> `app/dashboard/settings/sections/UsernameSection.tsx` | New controlled username flow: react-hook-form + zodResolver, debounced usernameKey (1s), availability query when valid & len>3, live status messaging, guarded submit (checks availability/identical/typing/updating), mutation update with toasts and loading states, and dynamic label/status in the input. |
| **Icon styling tweak** <br> `app/dashboard/settings/sections/AccountsSection.tsx` | Added Tailwind `fill-current` class to the Icon component's class list. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant NameForm as NameSection (Client)
    participant UsernameForm as UsernameSection (Client)
    participant API as Profile API
    participant Availability as Username Availability API
    participant Toast

    Note over User,NameForm: Name update flow
    User->>NameForm: edit name fields + submit
    NameForm->>API: updateProfile mutation (if changed)
    API-->>NameForm: success / error
    NameForm->>Toast: show success or error
    NameForm-->>User: reset form / show feedback

    Note over User,UsernameForm: Username availability & update flow
    User->>UsernameForm: type username
    UsernameForm->>Availability: debounced availability check (when valid & len>3)
    Availability-->>UsernameForm: available / taken / checking
    UsernameForm->>User: show inline status
    User->>UsernameForm: submit
    UsernameForm->>API: updateProfile mutation (if available & changed)
    API-->>UsernameForm: success / error
    UsernameForm->>Toast: show success or error
    UsernameForm-->>User: reset field / show feedback
```

## Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

- Pay extra attention to:
  - `app/dashboard/settings/sections/UsernameSection.tsx` — debounce logic, availability query triggers/conditions, race conditions between typing/checking/updating, and UI status consistency.
  - `app/dashboard/settings/sections/NameSection.tsx` — initialization/reset from fetched profile and isIdentical change-detection.
  - `src/lib/zod/index.ts` — ensure length constraints align with backend expectations.
  - `src/lib/utils/getNames.ts` — edge cases for missing/malformed name fields.

## Possibly related PRs

- dimas-fahmi/tusktask#11 — edits the same settings-section files (AccountsSection, NameSection, ProfileSection, UsernameSection) and related utils/zod changes; likely directly related.

## Poem

> 🐰 I hopped through inputs, zod in tow,  
> Debounced a name so it wouldn’t blow,  
> A tiny class for icons bright,  
> Toasts cheered on each saving night,  
> A rabbit’s nibble, soft and slow. 🥕

<!-- walkthrough_end -->


<!-- pre_merge_checks_walkthrough_start -->

## Pre-merge checks and finishing touches
<details>
<summary>✅ Passed checks (3 passed)</summary>

|     Check name     | Status   | Explanation                                                                                                                                                                      |
| :----------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|  Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                      |
|     Title check    | ✅ Passed | The title accurately describes the main changes: implementing and finalizing the name and username sections for the settings page with form validation and user profile updates. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.                                                                       |

</details>

<!-- pre_merge_checks_walkthrough_end -->

<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate docstrings
<details>
<summary>🧪 Generate unit tests (beta)</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post copyable unit tests in a comment
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `feat/auth`

</details>

</details>

<!-- finishing_touch_checkbox_end -->

<!-- tips_start -->

---

Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=dimas-fahmi/tusktask&utm_content=12)! 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>

<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>

<!-- tips_end -->

<!-- internal state start -->


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

<!-- internal state end -->
author_association: NONE