editing messages

This commit is contained in:
Stepan Usatiuk
2023-12-27 17:16:27 +01:00
parent 335f761eac
commit 2c501557d0
6 changed files with 134 additions and 28 deletions

View File

@@ -6,6 +6,7 @@ import "./Chat.scss";
import "./PostForm.scss"; import "./PostForm.scss";
import { Message } from "./Message"; import { Message } from "./Message";
import { useEffect } from "react"; import { useEffect } from "react";
import { getTokenUserUuid } from "./api/utils";
export function Chat() { export function Chat() {
const loaderData = useLoaderData() as LoaderToType<typeof chatLoader>; const loaderData = useLoaderData() as LoaderToType<typeof chatLoader>;
@@ -51,7 +52,12 @@ export function Chat() {
</fetcher.Form> </fetcher.Form>
<div className={"messages"}> <div className={"messages"}>
{sortedMessages.map((m) => ( {sortedMessages.map((m) => (
<Message key={m.id} message={m} chat={chat} /> <Message
key={m.id}
message={m}
chat={chat}
actions={m.authorUuid == getTokenUserUuid()}
/>
))} ))}
</div> </div>
</div> </div>

View File

@@ -14,7 +14,7 @@
&.messageEditing { &.messageEditing {
padding: 0; padding: 0;
min-height: 6rem; min-height: 3rem;
border: 1px solid #D0D0D0; border: 1px solid #D0D0D0;
@include post-editor; @include post-editor;

View File

@@ -1,14 +1,53 @@
import "./Message.scss"; import "./Message.scss";
import { TChatTo, TMessageTo } from "./api/dto"; import { TChatTo, TMessageTo } from "./api/dto";
import { Link } from "react-router-dom"; import { Form, Link, useNavigation, useSubmit } from "react-router-dom";
import { useState } from "react";
export function Message({ export function Message({
message, message,
chat, chat,
actions,
}: { }: {
message: TMessageTo; message: TMessageTo;
chat: TChatTo; chat: TChatTo;
actions: boolean;
}) { }) {
const [editing, setEditing] = useState(false);
const submit = useSubmit();
const navigation = useNavigation();
const busy = navigation.state === "submitting";
if (editing) {
return (
<div className={"message messageEditing"}>
<Form className={"postForm"} method="patch">
<input
hidden={true}
name={"messageId"}
value={message.id}
/>
<textarea
placeholder={"Write something!"}
name="text"
defaultValue={message.contents}
/>
<button
name="intent"
value="updateMessage"
type="submit"
onClick={(e) => {
setEditing(false);
submit(e.currentTarget);
}}
disabled={busy}
>
Save
</button>
</Form>
</div>
);
}
return ( return (
<div className={"message"}> <div className={"message"}>
<span className={"text"}>{message.contents}</span> <span className={"text"}>{message.contents}</span>
@@ -24,26 +63,26 @@ export function Message({
by {message.authorUsername} by {message.authorUsername}
</Link> </Link>
</div> </div>
{/*{actions && (*/} {actions && (
{/* <div className={"actions"}>*/} <div className={"actions"}>
{/* {<button onClick={() => setEditing(true)}>edit</button>}*/} {<button onClick={() => setEditing(true)}>edit</button>}
{/* <Form method={"delete"}>*/} <Form method={"delete"}>
{/* <input*/} <input
{/* hidden={true}*/} hidden={true}
{/* name={"postToDeleteId"}*/} name={"messageToDeleteId"}
{/* value={id}*/} value={message.id}
{/* />*/} />
{/* <button*/} <button
{/* name="intent"*/} name="intent"
{/* value="deletePost"*/} value="deleteMessage"
{/* type={"submit"}*/} type={"submit"}
{/* disabled={busy}*/} disabled={busy}
{/* >*/} >
{/* delete*/} delete
{/* </button>*/} </button>
{/* </Form>*/} </Form>
{/* </div>*/} </div>
{/*)}*/} )}
</div> </div>
</div> </div>
); );

View File

@@ -11,7 +11,7 @@ import { isError } from "./api/dto";
import { deleteToken, getTokenUserUuid, setToken } from "./api/utils"; import { deleteToken, getTokenUserUuid, setToken } from "./api/utils";
import { createPost, deletePost, updatePost } from "./api/Post"; import { createPost, deletePost, updatePost } from "./api/Post";
import { createChat } from "./api/Chat"; import { createChat } from "./api/Chat";
import { addMessagesToChat } from "./api/Message"; import { addMessagesToChat, deleteMessage, editMessage } from "./api/Message";
export type ActionToType<T extends (...args: any) => any> = export type ActionToType<T extends (...args: any) => any> =
| Exclude<Awaited<ReturnType<T>>, Response> | Exclude<Awaited<ReturnType<T>>, Response>
@@ -117,8 +117,20 @@ export async function newChatAction({ request }: ActionFunctionArgs) {
export async function chatAction({ request }: ActionFunctionArgs) { export async function chatAction({ request }: ActionFunctionArgs) {
const formData = await request.formData(); const formData = await request.formData();
return await addMessagesToChat( const intent = formData.get("intent")!.toString();
Number(formData.get("chatId")!.toString()), if (intent == "addMessage") {
formData.get("text")!.toString(), return await addMessagesToChat(
); Number(formData.get("chatId")!.toString()),
formData.get("text")!.toString(),
);
} else if (intent == "deleteMessage") {
return await deleteMessage(
Number(formData.get("messageToDeleteId")!.toString()),
);
} else if (intent == "updateMessage") {
return await editMessage(
Number(formData.get("messageId")!.toString()),
formData.get("text")!.toString(),
);
}
} }

View File

@@ -1,8 +1,10 @@
import { import {
MessagesToResp, MessagesToResp,
MessageToResp, MessageToResp,
NoContentToResp,
TMessagesToResp, TMessagesToResp,
TMessageToResp, TMessageToResp,
TNoContentToResp,
} from "./dto"; } from "./dto";
import { fetchJSONAuth } from "./utils"; import { fetchJSONAuth } from "./utils";
@@ -20,3 +22,27 @@ export async function addMessagesToChat(
contents: messageContents, contents: messageContents,
}); });
} }
export async function editMessage(
messageId: number,
messageContents: string,
): Promise<TMessageToResp> {
return fetchJSONAuth(
"/message/by-id/" + messageId,
"PATCH",
MessageToResp,
{
contents: messageContents,
},
);
}
export async function deleteMessage(
messageId: number,
): Promise<TNoContentToResp> {
return fetchJSONAuth(
"/message/by-id/" + messageId,
"DELETE",
NoContentToResp,
);
}

View File

@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.security.Principal; import java.security.Principal;
import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
@RestController @RestController
@@ -52,4 +53,26 @@ public class MessageController {
messageService.create(message); messageService.create(message);
return messageMapper.makeDto(message); return messageMapper.makeDto(message);
} }
@PatchMapping(path = "/by-id/{id}")
public MessageTo update(Principal principal, @PathVariable long id, @RequestBody MessageCreateTo messageCreateTo) {
var message = messageService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!Objects.equals(message.getAuthor().getUuid(), principal.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
message.setContents(messageCreateTo.contents());
messageService.update(message);
return messageMapper.makeDto(message);
}
@DeleteMapping(path = "/by-id/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(Principal principal, @PathVariable long id) {
var read = messageService.readById(id);
if (read.isEmpty()) return;
if (!Objects.equals(read.get().getAuthor().getId(), principal.getName())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
messageService.deleteById(id);
}
} }