Add user profile customization with name, role, email, and phone

- Add UserProfileSchema to ClientSettings with name, role, email, phone fields
- Update database layer to persist profile data in client_settings table
- Rewrite ProfileOverlay component as editable form with save/cancel actions
- Update Topbar to display user's initials from profile name
- Profile data loaded on app mount and saved via settings.update RPC

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 00:26:57 -04:00
parent 0624026af3
commit c8a39e6416
4 changed files with 126 additions and 6 deletions

View File

@@ -187,6 +187,8 @@ export type ClientSettings = z.infer<typeof import("./rpcSchemas.js").ClientSett
export type ServerSettings = z.infer<typeof import("./rpcSchemas.js").ServerSettingsSchema>;
export type UserProfile = z.infer<typeof import("./rpcSchemas.js").UserProfileSchema>;
export type RpcRequestMap = {
"portfolio.get": undefined;
"portfolio.addHolding": { ticker: string };

View File

@@ -28,12 +28,20 @@ const tickerString = z.string().trim().min(1).max(16);
const nonNegativeIndex = z.number().int().min(0);
const unknownRecord = z.record(z.unknown());
export const UserProfileSchema = z.object({
name: z.string().min(1).default(""),
role: z.string().min(1).default(""),
email: z.string().email().optional(),
phone: z.string().optional(),
});
export const ClientSettingsSchema = z.object({
theme: z.enum(["light", "dark", "system"]),
density: z.enum(["comfortable", "compact", "dense"]),
sidebarWidth: z.number().int().min(160).max(520),
navCollapsed: z.record(z.boolean()),
keybindings: z.record(z.string()),
profile: UserProfileSchema.partial().default({}),
});
export const ServerSettingsSchema = z.object({

View File

@@ -25,7 +25,7 @@ import type {
ClientSettings,
ServerSettings,
} from "@mosaiciq/contracts/rpc";
import { ClientSettingsSchema, ServerSettingsSchema } from "@mosaiciq/contracts/rpcSchemas";
import { ClientSettingsSchema, ServerSettingsSchema, UserProfileSchema } from "@mosaiciq/contracts/rpcSchemas";
export function parseJsonWithSchema<T>(
value: string,
@@ -982,6 +982,7 @@ const DEFAULT_CLIENT_SETTINGS: ClientSettings = {
"navigation.agents": "Cmd+4",
"navigation.home": "Cmd+0",
},
profile: {},
};
export function getClientSettings(db: Db): ClientSettings {
@@ -999,6 +1000,9 @@ export function getClientSettings(db: Db): ClientSettings {
} else if (row.key === "navCollapsed") {
const navCollapsed = z.record(z.boolean()).safeParse(parsed);
if (navCollapsed.success) settings.navCollapsed = { ...settings.navCollapsed, ...navCollapsed.data };
} else if (row.key === "profile") {
const profile = UserProfileSchema.partial().safeParse(parsed);
if (profile.success) settings.profile = { ...settings.profile, ...profile.data };
} else {
const candidate = { ...settings, [row.key]: parsed };
const validated = ClientSettingsSchema.safeParse(candidate);
@@ -1041,6 +1045,9 @@ export function updateClientSettings(db: Db, settings: Partial<ClientSettings>):
if (settings.keybindings !== undefined) {
updateClientSetting(db, "keybindings", settings.keybindings);
}
if (settings.profile !== undefined) {
updateClientSetting(db, "profile", settings.profile);
}
}
export function getServerSettings(db: Db): ServerSettings {