feat(mobile): align notes runtime with backend

This commit is contained in:
saravanakumardb1 2026-03-10 12:08:31 -07:00
parent 86e2da88eb
commit 8580ad3fc6
3 changed files with 111 additions and 51 deletions

View File

@ -1,54 +1,80 @@
import { createPlatformClient } from '@bytelyst/platform-client';
import { API_CONFIG } from './config';
import { getAuthClient } from './auth';
import { getApiClient } from './client'; import { getApiClient } from './client';
import { listWorkspaces, type MobileWorkspace } from './workspaces';
export type MobileNote = { export type MobileNote = {
id: string; id: string;
workspaceId: string;
title: string; title: string;
body: string; body: string;
workspaceName: string; workspaceName: string;
status: 'draft' | 'active' | 'archived';
updatedAt: string;
}; };
const FALLBACK_NOTES: MobileNote[] = [ type NoteDoc = {
{ id: string;
id: 'note-1', workspaceId: string;
title: 'Product launch checklist', title: string;
body: 'Finalize launch narrative and mobile scope cut line.', body: string;
workspaceName: 'Ops', status: 'draft' | 'active' | 'archived';
}, updatedAt: string;
{ };
id: 'note-2',
title: 'Agent review policy draft',
body: 'Human approval required for destructive and external actions.',
workspaceName: 'Platform',
},
];
function getClient() { type NoteListResponse = {
const auth = getAuthClient(); items: NoteDoc[];
};
return createPlatformClient({ function mapWorkspaceNames(workspaces: MobileWorkspace[]) {
baseUrl: API_CONFIG.productBaseUrl, return new Map(workspaces.map((workspace) => [workspace.id, workspace.name]));
productId: API_CONFIG.productId, }
getAccessToken: () => auth.getAccessToken(),
refreshAccessToken: () => auth.refreshAccessToken(), function toMobileNote(note: NoteDoc, workspaceNames: Map<string, string>): MobileNote {
timeoutMs: API_CONFIG.timeoutMs, return {
}); id: note.id,
workspaceId: note.workspaceId,
title: note.title,
body: note.body,
workspaceName: workspaceNames.get(note.workspaceId) ?? note.workspaceId,
status: note.status,
updatedAt: note.updatedAt,
};
} }
export async function listNotes(): Promise<MobileNote[]> { export async function listNotes(): Promise<MobileNote[]> {
try { const [response, workspaces] = await Promise.all([
return await getApiClient().fetch<MobileNote[]>('/notes'); getApiClient().fetch<NoteListResponse>('/notes'),
} catch { listWorkspaces(),
return FALLBACK_NOTES; ]);
}
const workspaceNames = mapWorkspaceNames(workspaces);
return response.items.map((note) => toMobileNote(note, workspaceNames));
} }
export async function getNote(id: string): Promise<MobileNote | null> { export async function getNote(id: string, workspaceId: string): Promise<MobileNote | null> {
try { const [note, workspaces] = await Promise.all([
return await getClient().get<MobileNote>(`/notes/${id}`); getApiClient().fetch<NoteDoc>(`/notes/${id}?workspaceId=${encodeURIComponent(workspaceId)}`),
} catch { listWorkspaces(),
return FALLBACK_NOTES.find((note) => note.id === id) ?? null; ]);
}
return toMobileNote(note, mapWorkspaceNames(workspaces));
}
export async function updateNote(
id: string,
workspaceId: string,
title: string,
body: string,
): Promise<MobileNote> {
const [updated, workspaces] = await Promise.all([
getApiClient().fetch<NoteDoc>(`/notes/${id}?workspaceId=${encodeURIComponent(workspaceId)}`, {
method: 'PATCH',
body: JSON.stringify({
title,
body,
}),
}),
listWorkspaces(),
]);
return toMobileNote(updated, mapWorkspaceNames(workspaces));
} }

View File

@ -1,12 +1,24 @@
import { getApiClient } from './client';
export type MobileWorkspace = { export type MobileWorkspace = {
id: string; id: string;
name: string; name: string;
description?: string;
};
type WorkspaceListResponse = {
items: Array<{
id: string;
name: string;
description?: string;
}>;
}; };
export async function listWorkspaces(): Promise<MobileWorkspace[]> { export async function listWorkspaces(): Promise<MobileWorkspace[]> {
return [ const response = await getApiClient().fetch<WorkspaceListResponse>('/workspaces');
{ id: 'ws-1', name: 'Ops' }, return response.items.map((workspace) => ({
{ id: 'ws-2', name: 'Platform' }, id: workspace.id,
{ id: 'ws-3', name: 'Research' }, name: workspace.name,
]; description: workspace.description,
}));
} }

View File

@ -1,5 +1,5 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { getNote, listNotes, type MobileNote } from '../api/notes'; import { getNote, listNotes, updateNote as persistNote, type MobileNote } from '../api/notes';
export type NotesState = { export type NotesState = {
notes: MobileNote[]; notes: MobileNote[];
@ -8,7 +8,7 @@ export type NotesState = {
hydrate: () => Promise<void>; hydrate: () => Promise<void>;
openNote: (id: string) => Promise<void>; openNote: (id: string) => Promise<void>;
saveDraft: (title: string, body: string) => void; saveDraft: (title: string, body: string) => void;
updateNote: (id: string, title: string, body: string) => void; updateNote: (id: string, title: string, body: string) => Promise<void>;
}; };
export const useNotesStore = create<NotesState>((set, get) => ({ export const useNotesStore = create<NotesState>((set, get) => ({
@ -22,20 +22,29 @@ export const useNotesStore = create<NotesState>((set, get) => ({
}, },
async openNote(id: string) { async openNote(id: string) {
set({ isLoading: true }); set({ isLoading: true });
const note = await getNote(id); const current = get().notes.find((note: MobileNote) => note.id === id);
if (!current) {
set({ selectedNote: null, isLoading: false });
return;
}
const note = await getNote(id, current.workspaceId);
set({ selectedNote: note, isLoading: false }); set({ selectedNote: note, isLoading: false });
}, },
saveDraft(title: string, body: string) { saveDraft(title: string, body: string) {
const draft: MobileNote = { const draft: MobileNote = {
id: `draft-${Date.now()}`, id: `draft-${Date.now()}`,
workspaceId: 'drafts',
title: title.trim() || 'Untitled draft', title: title.trim() || 'Untitled draft',
body, body,
workspaceName: 'Drafts', workspaceName: 'Drafts',
status: 'draft',
updatedAt: new Date().toISOString(),
}; };
set({ notes: [draft, ...get().notes], selectedNote: draft }); set({ notes: [draft, ...get().notes], selectedNote: draft });
}, },
updateNote(id: string, title: string, body: string) { async updateNote(id: string, title: string, body: string) {
const nextTitle = title.trim() || 'Untitled draft'; const nextTitle = title.trim() || 'Untitled draft';
const current = get().notes.find((note: MobileNote) => note.id === id); const current = get().notes.find((note: MobileNote) => note.id === id);
@ -43,15 +52,28 @@ export const useNotesStore = create<NotesState>((set, get) => ({
return; return;
} }
const updated: MobileNote = { if (current.workspaceId === 'drafts') {
...current, const updated: MobileNote = {
title: nextTitle, ...current,
body, title: nextTitle,
}; body,
updatedAt: new Date().toISOString(),
};
set({
notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)),
selectedNote: updated,
});
return;
}
set({ isLoading: true });
const updated = await persistNote(id, current.workspaceId, nextTitle, body);
set({ set({
notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)), notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)),
selectedNote: updated, selectedNote: updated,
isLoading: false,
}); });
}, },
})); }));