learning_ai_notes/mobile/src/store/notes-store.ts
2026-03-31 00:17:41 -07:00

142 lines
3.7 KiB
TypeScript

import { create } from 'zustand';
import { createNote as persistNewNote, getNote, listNotes, updateNote as persistNote, type MobileNote } from '../api/notes';
import { enqueueNoteCreate, enqueueNoteUpdate } from '../lib/offline-queue';
function createOfflineNoteId(): string {
const randomUUID = globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function'
? globalThis.crypto.randomUUID.bind(globalThis.crypto)
: null;
if (randomUUID) {
return randomUUID();
}
return `offline-${Date.now()}`;
}
export type NotesState = {
notes: MobileNote[];
selectedNote: MobileNote | null;
isLoading: boolean;
hydrate: () => Promise<void>;
openNote: (id: string) => Promise<void>;
saveDraft: (workspaceId: string | null, title: string, body: string) => Promise<boolean>;
updateNote: (id: string, title: string, body: string) => Promise<void>;
};
export const useNotesStore = create<NotesState>((set, get) => ({
notes: [],
selectedNote: null,
isLoading: false,
async hydrate() {
set({ isLoading: true });
const notes = await listNotes();
set({ notes, isLoading: false });
},
async openNote(id: string) {
set({ isLoading: true });
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 });
},
async saveDraft(workspaceId: string | null, title: string, body: string) {
if (!workspaceId) {
return false;
}
set({ isLoading: true });
try {
const created = await persistNewNote(workspaceId, title, body);
set({
notes: [created, ...get().notes],
selectedNote: created,
isLoading: false,
});
} catch {
const queuedNote: MobileNote = {
id: createOfflineNoteId(),
workspaceId,
workspaceName: workspaceId,
title: title.trim() || 'Untitled draft',
body,
status: 'draft',
updatedAt: new Date().toISOString(),
};
enqueueNoteCreate({
id: queuedNote.id,
workspaceId,
title: queuedNote.title,
body,
});
set({
notes: [queuedNote, ...get().notes],
selectedNote: queuedNote,
isLoading: false,
});
}
return true;
},
async updateNote(id: string, title: string, body: string) {
const nextTitle = title.trim() || 'Untitled draft';
const current = get().notes.find((note: MobileNote) => note.id === id);
if (!current) {
return;
}
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 });
try {
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,
});
} catch {
const updated: MobileNote = {
...current,
title: nextTitle,
body,
updatedAt: new Date().toISOString(),
};
enqueueNoteUpdate({
id,
workspaceId: current.workspaceId,
title: nextTitle,
body,
});
set({
notes: get().notes.map((note: MobileNote) => (note.id === id ? updated : note)),
selectedNote: updated,
isLoading: false,
});
}
},
}));