From 681a84a436bcc6d3dff975afe3cd1ddadd512582 Mon Sep 17 00:00:00 2001 From: Alex Meah Date: Wed, 24 Jul 2024 10:05:48 +0100 Subject: [PATCH] Task 1 --- src/api/note/index.ts | 14 ++++++++++- src/api/note/mocks.ts | 1 + src/api/note/schema.ts | 6 +++++ src/mocks/handlers.ts | 20 +++++++++++++++- src/pages/Note/Note.tsx | 52 +++++++++++++++++++++++++++++------------ 5 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/api/note/index.ts b/src/api/note/index.ts index 9ecf459..16bf7a1 100644 --- a/src/api/note/index.ts +++ b/src/api/note/index.ts @@ -1,7 +1,14 @@ import ms from "ms"; import { type ServiceArgs, createQueryService, createService } from "../helpers/createService"; import { jsonFetch } from "../helpers/jsonFetch"; -import type { CreateNote, Note, NoteParams, NoteSearchParams, Notes } from "./schema"; +import type { + CreateNote, + FavouriteNote, + Note, + NoteParams, + NoteSearchParams, + Notes, +} from "./schema"; export const notesService = { getAll: createQueryService({ @@ -36,4 +43,9 @@ export const notesService = { body: JSON.stringify(args.body), }), }), + favourite: createService({ + url: "/notes/:noteId/favourite", + call: (url, args: ServiceArgs<{ urlParams: NoteParams; body: FavouriteNote }>) => + jsonFetch({ ...args, url, method: "POST", body: JSON.stringify(args.body) }), + }), }; diff --git a/src/api/note/mocks.ts b/src/api/note/mocks.ts index 0ddcc3a..b2f9971 100644 --- a/src/api/note/mocks.ts +++ b/src/api/note/mocks.ts @@ -9,5 +9,6 @@ export const createNoteMock = (opts?: PartialDeep): Note => ({ id: randUuid(), title: randWord({ length: 5 }).join(" "), content: randSentence(), + isFavourite: false, ...opts, }); diff --git a/src/api/note/schema.ts b/src/api/note/schema.ts index 4ec6390..c84e52d 100644 --- a/src/api/note/schema.ts +++ b/src/api/note/schema.ts @@ -8,6 +8,7 @@ export const NoteSchema = z.object({ id: z.string().uuid(), title: z.string(), content: z.string(), + isFavourite: z.boolean().default(false).optional(), }); export type Note = z.infer; @@ -18,3 +19,8 @@ export const CreateNoteSchema = NoteSchema.pick({ title: true, content: true }); export type CreateNote = z.infer; export type NoteSearchParams = { search: string }; + +export const FavouriteNoteSchema = z.object({ + isFavourite: z.boolean(), +}); +export type FavouriteNote = z.infer; diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index a103e83..9e0fe74 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,8 +1,9 @@ import { seed } from "@ngneat/falso"; import { http, HttpResponse } from "msw"; +import { z } from "zod"; import { api } from "../api"; import { createNoteMock } from "../api/note/mocks"; -import { CreateNoteSchema } from "../api/note/schema"; +import { CreateNoteSchema, FavouriteNoteSchema } from "../api/note/schema"; seed("42"); @@ -61,6 +62,23 @@ export const handlers = [ Object.assign(existingNote, newNote.data); + return HttpResponse.json(existingNote, { status: 200 }); + }), + http.post(api.notes.favourite.url, async ({ request, params }) => { + const { noteId } = params; + const body = FavouriteNoteSchema.safeParse(await request.json()); + const existingNote = mockNotes.find((n) => n.id === noteId); + + if (!existingNote) { + return HttpResponse.json({}, { status: 404 }); + } + + if (!body.success) { + return HttpResponse.json(body.error.flatten(), { status: 400 }); + } + + existingNote.isFavourite = body.data.isFavourite; + return HttpResponse.json(existingNote, { status: 200 }); }), ]; diff --git a/src/pages/Note/Note.tsx b/src/pages/Note/Note.tsx index 5db9a8d..1860470 100644 --- a/src/pages/Note/Note.tsx +++ b/src/pages/Note/Note.tsx @@ -5,7 +5,7 @@ import toast from "react-hot-toast"; import { useParams } from "react-router-dom"; import invariant from "tiny-invariant"; import { api } from "../../api"; -import { type CreateNote, CreateNoteSchema } from "../../api/note/schema"; +import { type CreateNote, CreateNoteSchema, FavouriteNote } from "../../api/note/schema"; import { Button } from "../../components/Button"; import { Input, Textarea } from "../../components/Form"; import { Heading } from "../../components/Typography"; @@ -27,7 +27,7 @@ export const Note = () => { // urlParams: { noteId: id! }, // }); - const { mutate } = useMutation({ + const edit = useMutation({ mutationFn: (body: Partial) => api.notes.edit.call({ urlParams: { noteId: id }, body }), onSuccess() { @@ -53,6 +53,18 @@ export const Note = () => { }, }); + const favourite = useMutation({ + mutationFn: (body: FavouriteNote) => + api.notes.favourite.call({ urlParams: { noteId: id }, body }), + onSuccess() { + return refetch(); + }, + }); + /** + * Optimistic update + */ + const isFavourite = (favourite.isPending && favourite.variables.isFavourite) || note.isFavourite; + const handleSubmit = (event: FormEvent) => { event.preventDefault(); @@ -65,24 +77,34 @@ export const Note = () => { content: formData.get("content"), }); - mutate(data); + edit.mutate(data); }; return (
- - {editMode ? ( - <> - - - - ) : ( - note.title - )} - +
+ + {editMode ? ( + <> + + + + ) : ( + note.title + )} + + +
{editMode ? ( <>