diff --git a/web/sdk/react/components/organization/preferences/index.tsx b/web/sdk/react/components/organization/preferences/index.tsx
index fe555ff6c..207758bc8 100644
--- a/web/sdk/react/components/organization/preferences/index.tsx
+++ b/web/sdk/react/components/organization/preferences/index.tsx
@@ -1,7 +1,10 @@
'use client';
+import { useRouteContext } from '@tanstack/react-router';
import { PreferencesPage } from '~/react/views/preferences';
+import { RouterContext } from '../routes';
export default function UserPreferences() {
- return ;
+ const { theme, onThemeChange } = useRouteContext({ from: '__root__' }) as RouterContext;
+ return ;
}
diff --git a/web/sdk/react/components/organization/profile.tsx b/web/sdk/react/components/organization/profile.tsx
index 42b1fb892..e1ad8ee32 100644
--- a/web/sdk/react/components/organization/profile.tsx
+++ b/web/sdk/react/components/organization/profile.tsx
@@ -1,3 +1,4 @@
+import { useMemo } from 'react';
import {
RouterProvider,
createMemoryHistory,
@@ -30,7 +31,9 @@ export const OrganizationProfile = ({
showPreferences = false,
hideToast = false,
customScreens = [],
- onLogout = () => {}
+ onLogout = () => { },
+ theme,
+ onThemeChange,
}: OrganizationProfileProps) => {
const memoryHistory = createMemoryHistory({
initialEntries: [defaultRoute]
@@ -40,21 +43,47 @@ export const OrganizationProfile = ({
const routeTree = getRootTree({ customScreens });
- const memoryRouter = createRouter({
- routeTree,
- history: memoryHistory,
- context: {
- organizationId,
- showBilling,
- showTokens,
- showAPIKeys,
- hideToast,
- showPreferences,
- customRoutes,
- onLogout
- }
- });
- return ;
+ const memoryRouter = useMemo(
+ () =>
+ createRouter({
+ routeTree,
+ history: memoryHistory,
+ context: {
+ organizationId,
+ showBilling,
+ showTokens,
+ showAPIKeys,
+ hideToast,
+ showPreferences,
+ customRoutes,
+ onLogout,
+ theme,
+ onThemeChange
+ }
+ }),
+ // Router is created once; dynamic context flows through RouterProvider's
+ // context prop so updates don't recreate the router and reset navigation.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
+ return (
+
+ );
};
declare module '@tanstack/react-router' {
diff --git a/web/sdk/react/components/organization/routes.tsx b/web/sdk/react/components/organization/routes.tsx
index 0e80fbee2..417f19bf2 100644
--- a/web/sdk/react/components/organization/routes.tsx
+++ b/web/sdk/react/components/organization/routes.tsx
@@ -38,6 +38,8 @@ export interface CustomScreen {
component: RouteComponent;
}
+export type Theme = 'light' | 'dark' | 'system';
+
export interface OrganizationProfileProps {
organizationId: string;
defaultRoute?: string;
@@ -48,6 +50,8 @@ export interface OrganizationProfileProps {
hideToast?: boolean;
customScreens?: CustomScreen[];
onLogout?: () => void;
+ theme?: Theme;
+ onThemeChange?: (theme: Theme) => void;
}
export interface CustomRoutes {
@@ -55,7 +59,7 @@ export interface CustomRoutes {
User: Pick[];
}
-type RouterContext = Pick<
+export type RouterContext = Pick<
OrganizationProfileProps,
| 'organizationId'
| 'showBilling'
@@ -63,6 +67,8 @@ type RouterContext = Pick<
| 'showAPIKeys'
| 'hideToast'
| 'showPreferences'
+ | 'theme'
+ | 'onThemeChange'
> & { customRoutes: CustomRoutes; onLogout?: () => void };
export function getCustomRoutes(customScreens: CustomScreen[] = []) {
diff --git a/web/sdk/react/views-new/preferences/preferences-view.tsx b/web/sdk/react/views-new/preferences/preferences-view.tsx
index b6169b183..faecfe549 100644
--- a/web/sdk/react/views-new/preferences/preferences-view.tsx
+++ b/web/sdk/react/views-new/preferences/preferences-view.tsx
@@ -10,13 +10,48 @@ import { useTheme } from '@raystack/apsara-v1';
import styles from './preferences-view.module.css';
import { ReactNode } from 'react';
+const THEME_OPTIONS = {
+ light: {
+ label: 'Light',
+ icon:
+ },
+ dark: {
+ label: 'Dark',
+ icon:
+ },
+ system: {
+ label: 'System',
+ icon:
+ }
+}
+type Theme = keyof typeof THEME_OPTIONS;
+
interface PreferencesViewProps {
children?: ReactNode;
+ /**
+ * The theme to use for Theme Select.
+ * If not provided, the theme will be fetched from ThemeProvider.
+ */
+ theme?: Theme;
+ /**
+ * The callback to call when the theme is changed.
+ * If not provided, the theme will be set in the ThemeProvider.
+ */
+ onThemeChange?: (theme: Theme) => void;
}
-export function PreferencesView({ children }: PreferencesViewProps) {
+export function PreferencesView({ children, theme: providedTheme, onThemeChange }: PreferencesViewProps) {
const { theme, setTheme } = useTheme();
const { preferences, isLoading, isFetching, updatePreferences } =
usePreferences({});
+ const computedTheme = providedTheme ?? theme;
+
+ const handleThemeChange = (theme: string) => {
+ if (onThemeChange) {
+ onThemeChange(theme as Theme);
+ } else {
+ setTheme(theme);
+ }
+ }
const newsletterValue =
preferences?.[PREFERENCE_OPTIONS.NEWSLETTER]?.value ?? 'false';
@@ -32,20 +67,16 @@ export function PreferencesView({ children }: PreferencesViewProps) {
title="Theme"
description="Customise your interface color scheme."
>
-