mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
The token usage profile header had too much top spacing, the edit action competed with the content from the top-right corner, and the profile subtitle was fixed to the default project link. This keeps the profile controls close to the identity block, moves the modal scrim to the document body so it covers the whole desktop shell, and persists a user-editable second line with URL auto-linking. Constraint: Preserve existing profile preferences and backfill older records that do not have a subtitle field. Rejected: Keep the top-right edit button | it kept the action visually detached from the profile content. Rejected: Store the second line only in desktop local state | the preference already has a server-backed profile shape. Confidence: high Scope-risk: narrow Directive: Keep profile preference schema changes backward-compatible with older desktop-ui preference files. Tested: cd desktop && bun run test --run src/pages/ActivitySettings.test.tsx src/api/desktopUiPreferences.test.ts Tested: bun test src/server/__tests__/desktop-ui-preferences.test.ts Tested: bun run check:persistence-upgrade Tested: bun run check:server Tested: bun run check:desktop Tested: git diff --check Not-tested: Full Tauri native shell smoke; renderer flow was smoke-tested in browser only.
86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
import { ApiError, api, getApiUrl, getAuthToken } from './client'
|
|
|
|
export type SidebarProjectPreferences = {
|
|
projectOrder: string[]
|
|
pinnedProjects: string[]
|
|
hiddenProjects: string[]
|
|
projectOrganization: 'project' | 'recentProject' | 'time'
|
|
projectSortBy: 'createdAt' | 'updatedAt'
|
|
}
|
|
|
|
export type DesktopProfilePreferences = {
|
|
displayName: string
|
|
subtitle: string
|
|
avatarFile: string | null
|
|
avatarUpdatedAt: string | null
|
|
}
|
|
|
|
export type DesktopUiPreferences = {
|
|
schemaVersion: number
|
|
sidebar: SidebarProjectPreferences
|
|
profile: DesktopProfilePreferences
|
|
}
|
|
|
|
export type DesktopUiPreferencesResponse = {
|
|
preferences: DesktopUiPreferences
|
|
exists: boolean
|
|
}
|
|
|
|
export const desktopUiPreferencesApi = {
|
|
getPreferences() {
|
|
return api.get<DesktopUiPreferencesResponse>('/api/desktop-ui/preferences')
|
|
},
|
|
|
|
updateSidebarPreferences(sidebar: SidebarProjectPreferences) {
|
|
return api.put<{ ok: true; preferences: DesktopUiPreferences }>(
|
|
'/api/desktop-ui/preferences/sidebar',
|
|
sidebar,
|
|
)
|
|
},
|
|
|
|
updateProfilePreferences(profile: Pick<DesktopProfilePreferences, 'displayName' | 'subtitle'>) {
|
|
return api.put<{ ok: true; preferences: DesktopUiPreferences }>(
|
|
'/api/desktop-ui/preferences/profile',
|
|
profile,
|
|
)
|
|
},
|
|
|
|
async uploadProfileAvatar(file: File) {
|
|
return uploadProfileAvatar(file)
|
|
},
|
|
|
|
deleteProfileAvatar() {
|
|
return api.delete<{ ok: true; preferences: DesktopUiPreferences }>(
|
|
'/api/desktop-ui/preferences/profile/avatar',
|
|
)
|
|
},
|
|
}
|
|
|
|
export function getProfileAvatarUrl(updatedAt: string | null | undefined) {
|
|
const suffix = updatedAt ? `?v=${encodeURIComponent(updatedAt)}` : ''
|
|
return getApiUrl(`/api/desktop-ui/preferences/profile/avatar${suffix}`)
|
|
}
|
|
|
|
async function uploadProfileAvatar(file: File): Promise<{ ok: true; preferences: DesktopUiPreferences }> {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': file.type || 'application/octet-stream',
|
|
}
|
|
const token = getAuthToken()
|
|
if (token) {
|
|
headers.Authorization = `Bearer ${token}`
|
|
}
|
|
|
|
const res = await fetch(getApiUrl('/api/desktop-ui/preferences/profile/avatar'), {
|
|
method: 'PUT',
|
|
headers,
|
|
body: file,
|
|
})
|
|
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => res.text())
|
|
throw new ApiError(res.status, body)
|
|
}
|
|
|
|
return res.json() as Promise<{ ok: true; preferences: DesktopUiPreferences }>
|
|
}
|