feat(mobile): align notes runtime with backend
This commit is contained in:
parent
86e2da88eb
commit
8580ad3fc6
@ -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));
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user