Update with more optimistic updates
This commit is contained in:
parent
bb99f99a22
commit
3c5fe488cd
@ -37,11 +37,15 @@ public class TodosController : ApiController
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<ActionResult<string>> CreateTodo(
|
public async Task<ActionResult<string>> CreateTodo(
|
||||||
[FromBody] CreateTodoRequest request)
|
[FromBody] CreateTodoRequest request)
|
||||||
=> Ok(await Mediator.Send(request.To()));
|
{
|
||||||
|
return Ok(await Mediator.Send(request.To()));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("{todoId}")]
|
[HttpGet("{todoId}")]
|
||||||
public async Task<ActionResult<TodoViewModel>> GetTodoById([FromRoute] string todoId)
|
public async Task<ActionResult<TodoViewModel>> GetTodoById([FromRoute] string todoId)
|
||||||
=> await Mediator.Send(new GetTodoByIdQuery(todoId));
|
{
|
||||||
|
return await Mediator.Send(new GetTodoByIdQuery(todoId));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<TodoViewModel>>> GetTodos([FromQuery] bool onlyActive = false)
|
public async Task<ActionResult<IEnumerable<TodoViewModel>>> GetTodos([FromQuery] bool onlyActive = false)
|
||||||
|
@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Todo.Core.Application.Notifications.Todo;
|
using Todo.Core.Application.Notifications.Todo;
|
||||||
|
using Todo.Core.Application.Queries.Todos;
|
||||||
using Todo.Core.Interfaces.Persistence;
|
using Todo.Core.Interfaces.Persistence;
|
||||||
using Todo.Core.Interfaces.User;
|
using Todo.Core.Interfaces.User;
|
||||||
|
|
||||||
@ -10,9 +11,9 @@ namespace Todo.Core.Application.Commands.Todo;
|
|||||||
public record CreateTodoCommand(
|
public record CreateTodoCommand(
|
||||||
[Required] string TodoTitle,
|
[Required] string TodoTitle,
|
||||||
string? TodoProject,
|
string? TodoProject,
|
||||||
string? TodoDescription) : IRequest<string>
|
string? TodoDescription) : IRequest<TodoViewModel>
|
||||||
{
|
{
|
||||||
internal class Handler : IRequestHandler<CreateTodoCommand, string>
|
internal class Handler : IRequestHandler<CreateTodoCommand, TodoViewModel>
|
||||||
{
|
{
|
||||||
private readonly ICurrentUserService _currentUserService;
|
private readonly ICurrentUserService _currentUserService;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
@ -28,7 +29,7 @@ public record CreateTodoCommand(
|
|||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Handle(
|
public async Task<TodoViewModel> Handle(
|
||||||
CreateTodoCommand request,
|
CreateTodoCommand request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -47,7 +48,7 @@ public record CreateTodoCommand(
|
|||||||
new TodoCreated(todo.Id),
|
new TodoCreated(todo.Id),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
return todo.Id;
|
return TodoViewModel.From(todo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"sw.js","sources":["../../../../../../../../tmp/2966604b4704df68e7ceebb70ae0c809/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/home/kjuulh/git/git.front.kjuulh.io/kjuulh/todo/src/client/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/home/kjuulh/git/git.front.kjuulh.io/kjuulh/todo/src/client/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/home/kjuulh/git/git.front.kjuulh.io/kjuulh/todo/src/client/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/home/kjuulh/git/git.front.kjuulh.io/kjuulh/todo/src/client/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({request, response, event, state}) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, {status: 200, statusText: 'OK', headers: response.headers}); } return response; } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGiK;EACjK;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa;EAUbC,IAAI,CAACC,WAAL;AAEAC,sBAAyB;AAIzBC,uBAA6B,CAAC,GAAD,EAAM,IAAIC,oBAAJ,CAAoC;EAAE,eAAY,WAAd;EAA2BC,EAAAA,OAAO,EAAE,CAAC;EAAEC,IAAAA,eAAe,EAAE,OAAO;EAACC,MAAAA,OAAD;EAAUC,MAAAA,QAAV;EAAoBC,MAAAA,KAApB;EAA2BC,MAAAA;EAA3B,KAAP,KAA6C;EAAE,UAAIF,QAAQ,IAAIA,QAAQ,CAACG,IAAT,KAAkB,gBAAlC,EAAoD;EAAE,eAAO,IAAIC,QAAJ,CAAaJ,QAAQ,CAACK,IAAtB,EAA4B;EAACC,UAAAA,MAAM,EAAE,GAAT;EAAcC,UAAAA,UAAU,EAAE,IAA1B;EAAgCC,UAAAA,OAAO,EAAER,QAAQ,CAACQ;EAAlD,SAA5B,CAAP;EAAiG;;EAAC,aAAOR,QAAP;EAAkB;EAA5O,GAAD;EAApC,CAApC,CAAN,EAAmU,KAAnU,CAA7B;AACAL,uBAA6B,CAAC,KAAD,EAAQ,IAAIc,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBZ,EAAAA,OAAO,EAAE;EAA9B,CAAnC,CAAR,EAAgF,KAAhF,CAA7B;;"}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1
src/client/public/workbox-4a677df8.js
Normal file
1
src/client/public/workbox-4a677df8.js
Normal file
File diff suppressed because one or more lines are too long
@ -21,7 +21,9 @@ export const TodoItem: FC<TodoItemProps> = (props) => {
|
|||||||
<div
|
<div
|
||||||
className="py-3 border-b border-gray-300 dark:border-gray-600 relative"
|
className="py-3 border-b border-gray-300 dark:border-gray-600 relative"
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
prefetchTodo(props.todo.id);
|
if (!props.todo.id.startsWith("temp")) {
|
||||||
|
prefetchTodo(props.todo.id);
|
||||||
|
}
|
||||||
setIsHovering(true);
|
setIsHovering(true);
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => setIsHovering(false)}
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||||
import { baseQueryWithReauth } from "@src/infrastructure/apis/utilities";
|
import { baseQueryWithReauth } from "@src/infrastructure/apis/utilities";
|
||||||
import { Todo } from "@src/core/entities/todo";
|
import { Todo } from "@src/core/entities/todo";
|
||||||
|
import { nanoid } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
export const todosApi = createApi({
|
export const todosApi = createApi({
|
||||||
reducerPath: "api",
|
reducerPath: "api",
|
||||||
@ -12,7 +13,7 @@ export const todosApi = createApi({
|
|||||||
providesTags: (result, error, id) => [{ type: "todo", id }],
|
providesTags: (result, error, id) => [{ type: "todo", id }],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getAllTodos: build.query<Todo[], void>({
|
getAllTodos: build.query<Todo[], string>({
|
||||||
query: () => ({ url: `api/todos` }),
|
query: () => ({ url: `api/todos` }),
|
||||||
providesTags: (result) =>
|
providesTags: (result) =>
|
||||||
result
|
result
|
||||||
@ -39,7 +40,7 @@ export const todosApi = createApi({
|
|||||||
: [],
|
: [],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createTodo: build.mutation<string, Partial<Todo>>({
|
createTodo: build.mutation<Todo, Todo>({
|
||||||
query(body) {
|
query(body) {
|
||||||
return {
|
return {
|
||||||
url: "/api/todos",
|
url: "/api/todos",
|
||||||
@ -47,7 +48,44 @@ export const todosApi = createApi({
|
|||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
invalidatesTags: (result, error, id) => [{ type: "todo", id: "LIST" }],
|
invalidatesTags: (result, error, id) => [{ type: "todo", id: result.id }],
|
||||||
|
async onQueryStarted({ id, ...body }, { dispatch, queryFulfilled }) {
|
||||||
|
const tempId = nanoid();
|
||||||
|
const replaceGetAllResult = dispatch(
|
||||||
|
todosApi.util.updateQueryData(
|
||||||
|
"getActiveTodos",
|
||||||
|
undefined,
|
||||||
|
(draft) => {
|
||||||
|
const todo: Todo = { id: "temp" + tempId, ...body };
|
||||||
|
if (todo) {
|
||||||
|
draft.push(todo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await queryFulfilled;
|
||||||
|
const finishUpdated = dispatch(
|
||||||
|
todosApi.util.updateQueryData(
|
||||||
|
"getActiveTodos",
|
||||||
|
undefined,
|
||||||
|
(draft) => {
|
||||||
|
const todo = draft.find((t) => t.id == "temp" + tempId);
|
||||||
|
if (todo) {
|
||||||
|
Object.assign(todo, data.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
replaceGetAllResult.undo();
|
||||||
|
dispatch(
|
||||||
|
todosApi.util.invalidateTags([{ type: "todo", id: "LIST" }])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
replaceTodo: build.mutation<string, Partial<Todo>>({
|
replaceTodo: build.mutation<string, Partial<Todo>>({
|
||||||
@ -67,11 +105,28 @@ export const todosApi = createApi({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const replaceGetAllResult = dispatch(
|
||||||
|
todosApi.util.updateQueryData(
|
||||||
|
"getActiveTodos",
|
||||||
|
undefined,
|
||||||
|
(draft) => {
|
||||||
|
const todo = draft.find((t) => t.id === id);
|
||||||
|
if (todo) {
|
||||||
|
Object.assign(todo, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await queryFulfilled;
|
await queryFulfilled;
|
||||||
} catch {
|
} catch {
|
||||||
|
replaceGetAllResult.undo();
|
||||||
replaceResult.undo();
|
replaceResult.undo();
|
||||||
dispatch(todosApi.util.invalidateTags([{ type: "todo", id }]));
|
dispatch(todosApi.util.invalidateTags([{ type: "todo", id }]));
|
||||||
|
dispatch(
|
||||||
|
todosApi.util.invalidateTags([{ type: "todo", id: "LIST" }])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -25,7 +25,7 @@ export const SocketProvider: FC = (props) => {
|
|||||||
const { todoId } = JSON.parse(todoCreated) as { todoId: string };
|
const { todoId } = JSON.parse(todoCreated) as { todoId: string };
|
||||||
if (todoId) {
|
if (todoId) {
|
||||||
dispatch(todosApi.endpoints.getTodoById.initiate(todoId));
|
dispatch(todosApi.endpoints.getTodoById.initiate(todoId));
|
||||||
dispatch(todosApi.util.invalidateTags([{ type: "todo", id: "LIST" }]));
|
// dispatch(todosApi.util.invalidateTags([{ type: "todo", id: "LIST" }]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export const SocketProvider: FC = (props) => {
|
|||||||
const { todoId } = JSON.parse(todoUpdated) as { todoId: string };
|
const { todoId } = JSON.parse(todoUpdated) as { todoId: string };
|
||||||
if (todoId) {
|
if (todoId) {
|
||||||
dispatch(todosApi.util.invalidateTags([{ type: "todo", id: todoId }]));
|
dispatch(todosApi.util.invalidateTags([{ type: "todo", id: todoId }]));
|
||||||
dispatch(todosApi.util.invalidateTags([{ type: "todo", id: "LIST" }]));
|
// dispatch(todosApi.util.invalidateTags([{ type: "todo", id: "LIST" }]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { useAppSelector } from "@src/infrastructure/store";
|
|||||||
import { todosSelectors } from "@src/infrastructure/state/todo";
|
import { todosSelectors } from "@src/infrastructure/state/todo";
|
||||||
import { todosApi } from "@src/infrastructure/apis/todosApi";
|
import { todosApi } from "@src/infrastructure/apis/todosApi";
|
||||||
import { Todo } from "@src/core/entities/todo";
|
import { Todo } from "@src/core/entities/todo";
|
||||||
|
import { nanoid } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
export const useSelectInboxTodos = () => {
|
export const useSelectInboxTodos = () => {
|
||||||
const todos = useAppSelector(todosSelectors.selectAll);
|
const todos = useAppSelector(todosSelectors.selectAll);
|
||||||
@ -19,6 +20,9 @@ export const useCreateTodo = () => {
|
|||||||
return {
|
return {
|
||||||
createTodo: (todoName: string, project: string, description: string) => {
|
createTodo: (todoName: string, project: string, description: string) => {
|
||||||
createTodo({
|
createTodo({
|
||||||
|
id: nanoid(),
|
||||||
|
created: 0,
|
||||||
|
status: false,
|
||||||
title: todoName,
|
title: todoName,
|
||||||
project,
|
project,
|
||||||
description,
|
description,
|
||||||
|
Loading…
Reference in New Issue
Block a user