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

View File

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