Skip to content

feat: profile page revamp#1484

Open
rohanchkrabrty wants to merge 1 commit intofeat-profilw-revampfrom
feat-profile-revamp
Open

feat: profile page revamp#1484
rohanchkrabrty wants to merge 1 commit intofeat-profilw-revampfrom
feat-profile-revamp

Conversation

@rohanchkrabrty
Copy link
Copy Markdown
Contributor

Summary

  • Add ProfileView component in views-new/profile/ using @raystack/apsara-v1 primitives
  • Implements avatar upload via ImageUpload, form fields for full name and (read-only) email, and an Update button with dirty-state guard
  • Reuses existing hooks and API calls (useFrontier, useMutation, FrontierServiceQueries) — no business logic rewritten
  • Follows the same ViewContainer / ViewHeader layout pattern established by the general and preferences revamps
  • Wired into client-demo with a route at /:orgId/settings/profile and a sidebar nav item

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Mar 27, 2026 11:50am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6dd2e945-085c-4cae-a728-cabd881875b0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Upgrades React and React-DOM from v18 to v19 in client-demo, introduces new SDK React components (ImageUpload, ViewContainer, ViewHeader) and views (GeneralView, PreferencesView, DeleteOrganizationDialog), adds corresponding CSS modules and design tokens, integrates these into client-demo with new organization settings routes and UI, and updates package dependencies and SDK exports.

Changes

Cohort / File(s) Summary
React & Dependency Upgrade
web/apps/client-demo/package.json, web/sdk/package.json, web/sdk/tsup.config.ts
Updated React/React-DOM from ^18.3.1 to ^19.2.1 in client-demo; updated SDK peer dependencies to ^19, removed Stitches dev dependency, added Apsara and BaseUI dependencies, marked @types/react as optional peer, and configured react-dom as external in build.
Client-Demo App Integration
web/apps/client-demo/src/App.tsx, web/apps/client-demo/src/Router.tsx, web/apps/client-demo/src/Home.tsx, web/apps/client-demo/src/styles.css
Added CSS imports and stylesheet (normalize, local styles), added nested route structure for organization settings, rendered new settings navigation link on home page organization cards with "NEW UI" label.
Client-Demo Settings Pages
web/apps/client-demo/src/pages/Settings.tsx, web/apps/client-demo/src/pages/settings/General.tsx, web/apps/client-demo/src/pages/settings/Preferences.tsx
Created Settings layout component with sidebar navigation and nested routes, added General settings page wired to delete success callback, added Preferences page rendering PreferencesView.
SDK Image Upload Component
web/sdk/react/components/image-upload/image-upload.tsx, web/sdk/react/components/image-upload/image-upload.module.css, web/sdk/react/components/image-upload/index.ts
Added ImageUpload component with integrated crop dialog, canvas-based image processing, base64 conversion, and accompanying CSS module for button/crop styling; exported via index barrel.
SDK View Container Component
web/sdk/react/components/view-container/view-container.tsx, web/sdk/react/components/view-container/view-container.module.css, web/sdk/react/components/view-container/index.ts
Added ViewContainer component with nested Flex layout structure, optional content props, and CSS module for container/content background and scrolling; exported via index barrel.
SDK View Header Component
web/sdk/react/components/view-header/view-header.tsx, web/sdk/react/components/view-header/index.ts
Added ViewHeader component rendering title and optional description with right-aligned content slot; exported via index barrel.
SDK General View & Delete Dialog
web/sdk/react/views-new/general/general-view.tsx, web/sdk/react/views-new/general/general-view.module.css, web/sdk/react/views-new/general/components/delete-organization-dialog.tsx, web/sdk/react/views-new/general/components/delete-organization-dialog.module.css, web/sdk/react/views-new/general/index.ts
Added GeneralView component with form validation, image upload, organization title/name/URL editing, update/delete mutations, and permission checks; includes DeleteOrganizationDialog with title confirmation, acknowledgment flow, and protobuf-based deletion mutation.
SDK Preferences View & Row
web/sdk/react/views-new/preferences/preferences-view.tsx, web/sdk/react/views-new/preferences/preferences-view.module.css, web/sdk/react/views-new/preferences/components/preference-row.tsx, web/sdk/react/views-new/preferences/components/preferences-row.module.css, web/sdk/react/views-new/preferences/index.ts
Added PreferencesView with theme/newsletter Select controls and preference mutations; added reusable PreferenceRow component with optional loading skeleton and flexible content layout; exported both via index barrel.
SDK Exports & Admin Update
web/sdk/react/index.ts, web/sdk/admin/components/SheetFooter.tsx
Added re-exports for new components (ImageUpload, ViewContainer, ViewHeader) and views (GeneralView, PreferencesView, PreferenceRow), imported Apsara CSS; removed optional css prop from SheetFooter and updated border-color to use CSS variable reference.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • feat: move sdk security #1422: Modifies SDK React views and exports for organization settings, adding/moving General and Preferences views alongside SDK react index exports.
  • feat: move sdk general #1414: Modifies SDK General settings area and DeleteOrganizationDialog with prop-controlled open state and onDeleteSuccess callback.
  • feat: move sdk billing #1425: Aligns with exposure of new SDK React view components (GeneralView, PreferencesView, ImageUpload, ViewContainer, ViewHeader) as part of refactored organization pages surface.

Suggested reviewers

  • rsbh
  • rohilsurana
  • paanSinghCoder

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (4)
web/apps/client-demo/package.json (1)

22-22: Consider removing @types/react-router-dom.

react-router-dom v7+ includes built-in TypeScript definitions. The @types/react-router-dom package (v5.3.3) is for older versions and is unnecessary here—it may even cause type conflicts.

♻️ Suggested removal
  "devDependencies": {
    "@types/node": "^22.10.0",
    "@types/react": "^19.2.2",
    "@types/react-dom": "^19.2.2",
-   "@types/react-router-dom": "^5.3.3",
    "@types/uuid": "^10.0.0",
web/sdk/tsup.config.ts (1)

41-43: Consider externalizing react-dom for Hooks APIs for consistency, but it's not currently required.

The React APIs build (line 25) externalizes react-dom alongside react, but the Hooks APIs build (line 42) only externalizes react. However, analysis shows the hooks code currently has no react-dom imports, so there's no actual risk of bundling duplicate instances. Adding 'react-dom' to the Hooks APIs external list (line 42) would be a consistency measure rather than addressing an existing issue.

web/apps/client-demo/src/pages/Home.tsx (1)

63-68: Prefer org.id for the settings URL and test-id key.

Using org.name makes this path/selector more fragile (renames/encoding). org.id is the safer canonical identifier.

♻️ Suggested change
-              <Link
-                to={`/${org.name}/settings`}
-                data-test-id={`[organization-new-ui-link-${org.name}]`}
-              >
+              <Link
+                to={`/${org.id}/settings`}
+                data-test-id={`[organization-new-ui-link-${org.id}]`}
+              >
                 {org.title} (NEW UI)
               </Link>
web/sdk/react/views-new/general/general-view.tsx (1)

286-293: Make the delete opener an explicit non-submit button.

If Button keeps native button semantics, omitting type here turns the delete opener into a submit control because it sits inside the update form. type="button" avoids accidentally submitting the form when the dialog is opened.

Minimal fix
                   <Button
+                    type="button"
                     variant="solid"
                     color="danger"
                     onClick={() => setShowDeleteDialog(true)}
                     disabled={!canDeleteWorkspace}

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b5c09c1c-9df2-4153-ac28-26648adadc75

📥 Commits

Reviewing files that changed from the base of the PR and between 5417d82 and fd07c82.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • web/apps/client-demo/package.json
  • web/apps/client-demo/src/App.tsx
  • web/apps/client-demo/src/Router.tsx
  • web/apps/client-demo/src/pages/Home.tsx
  • web/apps/client-demo/src/pages/Settings.tsx
  • web/apps/client-demo/src/pages/settings/General.tsx
  • web/apps/client-demo/src/pages/settings/Preferences.tsx
  • web/apps/client-demo/src/styles.css
  • web/sdk/admin/components/SheetFooter.tsx
  • web/sdk/package.json
  • web/sdk/react/components/image-upload/image-upload.module.css
  • web/sdk/react/components/image-upload/image-upload.tsx
  • web/sdk/react/components/image-upload/index.ts
  • web/sdk/react/components/view-container/index.ts
  • web/sdk/react/components/view-container/view-container.module.css
  • web/sdk/react/components/view-container/view-container.tsx
  • web/sdk/react/components/view-header/index.ts
  • web/sdk/react/components/view-header/view-header.tsx
  • web/sdk/react/index.ts
  • web/sdk/react/views-new/general/components/delete-organization-dialog.module.css
  • web/sdk/react/views-new/general/components/delete-organization-dialog.tsx
  • web/sdk/react/views-new/general/general-view.module.css
  • web/sdk/react/views-new/general/general-view.tsx
  • web/sdk/react/views-new/general/index.ts
  • web/sdk/react/views-new/preferences/components/preference-row.tsx
  • web/sdk/react/views-new/preferences/components/preferences-row.module.css
  • web/sdk/react/views-new/preferences/index.ts
  • web/sdk/react/views-new/preferences/preferences-view.module.css
  • web/sdk/react/views-new/preferences/preferences-view.tsx
  • web/sdk/tsup.config.ts

Comment on lines +17 to +23
useEffect(() => {
if (!orgId || organizations.length === 0) return;
const org = organizations.find(_org => _org.id === orgId || _org.name === orgId);
if (org && activeOrganization?.id !== org.id) {
setActiveOrganization(org);
}
}, [orgId, organizations, activeOrganization?.id, setActiveOrganization]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n web/apps/client-demo/src/pages/Settings.tsx | head -100

Repository: raystack/frontier

Length of output: 2649


Synchronize the organization before rendering nested settings.

activeOrganization is only corrected after this effect runs, and when orgId has no match in the organizations list it is never corrected at all. This lets nested settings routes render the previous organization's data for one paint—or indefinitely if the URL contains an invalid orgId. Check for a valid organization match first, and only render <Outlet /> once the context is in sync or after redirecting invalid orgId values.

Comment on lines +26 to +29
<Route path="/:orgId/settings" element={<Settings />}>
<Route path="general" element={<General />} />
<Route path="preferences" element={<Preferences />} />
</Route>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Protect the new settings route with the same auth guard pattern used elsewhere.

This route currently depends on Settings for enforcement, and the provided web/apps/client-demo/src/pages/Settings.tsx snippet has no auth redirect. Please gate /:orgId/settings to prevent unauthenticated access to the settings shell.

@@ -0,0 +1,231 @@
'use client';

import { useRef, useState } from 'react';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "createObjectURL|revokeObjectURL" web/sdk/react/components/image-upload/image-upload.tsx

Repository: raystack/frontier

Length of output: 114


🏁 Script executed:

cat -n web/sdk/react/components/image-upload/image-upload.tsx

Repository: raystack/frontier

Length of output: 7562


Add cleanup for blob URLs to prevent memory leaks across repeated uploads.

Line 180 creates a blob URL with URL.createObjectURL(), but there is no corresponding cleanup. When a new file is selected, the dialog closes, or the component unmounts, the previous blob URLs accumulate in memory and are never released.

The cleanup must revoke blob URLs:

  • Before creating a new one (when a new file is selected)
  • When the component unmounts

Add useEffect to the imports and implement cleanup:

Proposed fix
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
@@
   const inputRef = useRef<HTMLInputElement>(null);
   const [imgSrc, setImgSrc] = useState('');
   const [showCropDialog, setShowCropDialog] = useState(false);
+
+  useEffect(() => {
+    return () => {
+      if (imgSrc.startsWith('blob:')) URL.revokeObjectURL(imgSrc);
+    };
+  }, [imgSrc]);
@@
   function onFileChange(e: ChangeEvent<HTMLInputElement>) {
     const files = e.target.files || [];
     if (files.length > 0) {
       const file = files[0];
       const imageUrl = URL.createObjectURL(file);
-      setImgSrc(imageUrl);
+      setImgSrc(prev => {
+        if (prev.startsWith('blob:')) URL.revokeObjectURL(prev);
+        return imageUrl;
+      });
       setShowCropDialog(true);
       e.target.files = null;
     }
   }

Also applies to: 169-170, 176-184, 223-227

Comment on lines +59 to +63
const height = ((crop?.height || 0) * image.height) / 100;
const width = ((crop?.width || 0) * image.width) / 100;
const x = ((crop?.x || 0) * image.width) / 100;
const y = ((crop?.y || 0) * image.width) / 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix Y-axis crop math (wrong dimension used).

Line 62 computes y using image.width; it must use image.height. Current code can shift crop selection vertically and produce incorrect output.

🐛 Proposed fix
-    const y = ((crop?.y || 0) * image.width) / 100;
+    const y = ((crop?.y || 0) * image.height) / 100;
📝 Committable suggestion

‼️ 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.

Suggested change
const height = ((crop?.height || 0) * image.height) / 100;
const width = ((crop?.width || 0) * image.width) / 100;
const x = ((crop?.x || 0) * image.width) / 100;
const y = ((crop?.y || 0) * image.width) / 100;
const height = ((crop?.height || 0) * image.height) / 100;
const width = ((crop?.width || 0) * image.width) / 100;
const x = ((crop?.x || 0) * image.width) / 100;
const y = ((crop?.y || 0) * image.height) / 100;

@@ -0,0 +1,149 @@
import { useState } from 'react';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd delete-organization-dialog.tsx

Repository: raystack/frontier

Length of output: 193


🏁 Script executed:

cat -n web/sdk/react/views-new/general/components/delete-organization-dialog.tsx

Repository: raystack/frontier

Length of output: 5602


Reset the confirmation state between dialog sessions.

This component stays mounted while open toggles, so the typed organization name and acknowledgment checkbox survive close/reopen. This weakens the confirmation flow for a destructive action. Additionally, after a successful deletion, the dialog remains open if onDeleteSuccess is not supplied, since onOpenChange(false) is never called.

Fix: Import useEffect, extract the reset function from useForm, and add an effect that resets both the form and isAcknowledged when the dialog closes. Also call onOpenChange(false) after successful deletion:

Minimal fix
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
@@
   const {
     register,
     handleSubmit,
+    reset,
     watch,
     setError,
     formState: { errors, isSubmitting }
   } = useForm({
     resolver: yupResolver(deleteOrgSchema)
   });
+
+  useEffect(() => {
+    if (!open) {
+      reset({ title: '' });
+      setIsAcknowledged(false);
+    }
+  }, [open, reset]);
@@
       await deleteOrganization(req);
+      onOpenChange(false);
       toastManager.add({
         title: `${orgLabel} deleted`,
         type: 'success'
       });

Comment on lines +63 to +76
async function onDeleteSubmit(data: { title?: string }) {
if (!organization?.id) return;
if (data.title !== organization.title) {
setError('title', {
message: `The ${orgLabelLower} name does not match`
});
return;
}

try {
const req = create(DeleteOrganizationRequestSchema, {
id: organization.id
});
await deleteOrganization(req);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "delete-organization-dialog.tsx" | head -5

Repository: raystack/frontier

Length of output: 197


🏁 Script executed:

cat -n "web/sdk/react/views-new/general/components/delete-organization-dialog.tsx" | head -150

Repository: raystack/frontier

Length of output: 5602


🏁 Script executed:

wc -l "web/sdk/react/views-new/general/components/delete-organization-dialog.tsx"

Repository: raystack/frontier

Length of output: 138


🏁 Script executed:

cat -n "web/sdk/react/views-new/general/components/delete-organization-dialog.tsx"

Repository: raystack/frontier

Length of output: 5602


Add isAcknowledged check to the submit handler.

The acknowledgment guard only exists on the button's disabled state. Users can bypass this by submitting the form via Enter key in the text input or other non-click submission methods. The form reaches deleteOrganization once the title matches, regardless of the checkbox state. Add a check inside onDeleteSubmit before attempting the delete:

if (!isAcknowledged) return;

Alternatively, move the acknowledgment into the form schema so validation is enforced for all submission paths.

Comment on lines +99 to +127
const { mutateAsync: updateOrganization } = useMutation(
FrontierServiceQueries.updateOrganization,
{
onSuccess: data => {
if (data.organization) {
setActiveOrganization(data.organization);
queryClient.invalidateQueries({
queryKey: createConnectQueryKey({
schema: FrontierServiceQueries.getOrganization,
transport,
input: { id: organization?.id || '' },
cardinality: 'finite'
})
});
}
toastManager.add({
title: `Updated ${t.organization({ case: 'lower' })}`,
type: 'success'
});
},
onError: (error: Error) => {
toastManager.add({
title: 'Something went wrong',
description: error?.message || 'Failed to update',
type: 'error'
});
}
}
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its total line count
wc -l web/sdk/react/views-new/general/general-view.tsx

Repository: raystack/frontier

Length of output: 113


🏁 Script executed:

# Read the specific lines mentioned in the review comment (99-127)
sed -n '99,127p' web/sdk/react/views-new/general/general-view.tsx

Repository: raystack/frontier

Length of output: 945


🏁 Script executed:

# Also check the secondary location mentioned (146-169)
sed -n '146,169p' web/sdk/react/views-new/general/general-view.tsx

Repository: raystack/frontier

Length of output: 694


🏁 Script executed:

# Search for the onUpdateSubmit function and its surrounding context
sed -n '70,180p' web/sdk/react/views-new/general/general-view.tsx

Repository: raystack/frontier

Length of output: 3013


🏁 Script executed:

# Search for other useMutation patterns in the file to identify the secondary location
rg "useMutation|mutateAsync" web/sdk/react/views-new/general/general-view.tsx -n

Repository: raystack/frontier

Length of output: 140


🏁 Script executed:

# Get more context around lines 146-200 to see if there's another mutation
sed -n '130,200p' web/sdk/react/views-new/general/general-view.tsx

Repository: raystack/frontier

Length of output: 2153


Remove the catch block to prevent duplicate error toasts.

The mutation's onError handler already displays an error toast when the request fails. The surrounding try-catch block catches the same rejection and displays another toast with an identical message, resulting in two notifications for a single failure. Rely on the mutation's error handler alone.

  async function onUpdateSubmit(data: FormData) {
    if (!organization?.id) return;
-
-    try {
-      const req = create(UpdateOrganizationRequestSchema, {
-        id: organization.id,
-        body: {
-          title: data.title,
-          name: data.name,
-          avatar: data.avatar
-        }
-      });
-      await updateOrganization(req);
-    } catch (error: unknown) {
-      toastManager.add({
-        title: 'Something went wrong',
-        description:
-          error instanceof Error
-            ? error.message
-            : `Failed to update ${t.organization({ case: 'lower' })}`,
-        type: 'error'
-      });
-    }
+    const req = create(UpdateOrganizationRequestSchema, {
+      id: organization.id,
+      body: {
+        title: data.title,
+        name: data.name,
+        avatar: data.avatar
+      }
+    });
+    await updateOrganization(req);
  }

const { theme, setTheme } = useTheme();
const { preferences, isLoading, isFetching, updatePreferences } =
usePreferences({});
console.log(theme);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug logging from render path.

console.log(theme) should not ship in this view.

<ViewContainer>
<ViewHeader
title="Preferences"
description="Manage members for this domain."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Header description copy appears mismatched for this page.

“Manage members for this domain.” reads like a members page, not Preferences.

Comment on lines +32 to +33
<Select defaultValue={theme} onValueChange={setTheme}>
<Select.Trigger className={styles.selectTrigger}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In @raystack/apsara-v1Select API, is controlled usage withvalue+onValueChangesupported and recommended overdefaultValue for async state?

💡 Result:

Yes, controlled usage with value + onValueChange is supported and recommended over defaultValue for async state management in the @raystack/apsara-v1 Select API.

Citations:


🏁 Script executed:

# Find the file mentioned in the review
find . -name "preferences-view.tsx" -type f

Repository: raystack/frontier

Length of output: 119


🏁 Script executed:

# Check the file contents around the specified lines
if [ -f "web/sdk/react/views-new/preferences/preferences-view.tsx" ]; then
  wc -l "web/sdk/react/views-new/preferences/preferences-view.tsx"
fi

Repository: raystack/frontier

Length of output: 120


🏁 Script executed:

cat -n "web/sdk/react/views-new/preferences/preferences-view.tsx"

Repository: raystack/frontier

Length of output: 3501


Replace defaultValue with controlled value prop in Select components to keep UI in sync with async state updates.

Both Select instances use defaultValue, which is initialization-only. The theme value from useTheme() can update externally, and newsletterValue is derived from async preference fetches. This causes stale selections when state changes after mount. Use controlled value prop instead:

Suggested fix
-          <Select defaultValue={theme} onValueChange={setTheme}>
+          <Select value={theme} onValueChange={setTheme}>
             <Select.Trigger className={styles.selectTrigger}>
               <Select.Value placeholder="Theme" />
             </Select.Trigger>
-          <Select
-            defaultValue={newsletterValue}
+          <Select
+            value={newsletterValue}
             onValueChange={value => {
               updatePreferences([
                 { name: PREFERENCE_OPTIONS.NEWSLETTER, value }
               ]);
             }}

@rohanchkrabrty rohanchkrabrty changed the base branch from main to feat-profilw-revamp March 27, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant