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 { Message } from "./Message";
import { useEffect } from "react";
import { getTokenUserUuid } from "./api/utils";
export function Chat() {
const loaderData = useLoaderData() as LoaderToType<typeof chatLoader>;
@@ -51,7 +52,12 @@ export function Chat() {
</fetcher.Form>
<div className={"messages"}>
{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>

View File

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

View File

@@ -1,14 +1,53 @@
import "./Message.scss";
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({
message,
chat,
actions,
}: {
message: TMessageTo;
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 (
<div className={"message"}>
<span className={"text"}>{message.contents}</span>
@@ -24,26 +63,26 @@ export function Message({
by {message.authorUsername}
</Link>
</div>
{/*{actions && (*/}
{/* <div className={"actions"}>*/}
{/* {<button onClick={() => setEditing(true)}>edit</button>}*/}
{/* <Form method={"delete"}>*/}
{/* <input*/}
{/* hidden={true}*/}
{/* name={"postToDeleteId"}*/}
{/* value={id}*/}
{/* />*/}
{/* <button*/}
{/* name="intent"*/}
{/* value="deletePost"*/}
{/* type={"submit"}*/}
{/* disabled={busy}*/}
{/* >*/}
{/* delete*/}
{/* </button>*/}
{/* </Form>*/}
{/* </div>*/}
{/*)}*/}
{actions && (
<div className={"actions"}>
{<button onClick={() => setEditing(true)}>edit</button>}
<Form method={"delete"}>
<input
hidden={true}
name={"messageToDeleteId"}
value={message.id}
/>
<button
name="intent"
value="deleteMessage"
type={"submit"}
disabled={busy}
>
delete
</button>
</Form>
</div>
)}
</div>
</div>
);

View File

@@ -11,7 +11,7 @@ import { isError } from "./api/dto";
import { deleteToken, getTokenUserUuid, setToken } from "./api/utils";
import { createPost, deletePost, updatePost } from "./api/Post";
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> =
| Exclude<Awaited<ReturnType<T>>, Response>
@@ -117,8 +117,20 @@ export async function newChatAction({ request }: ActionFunctionArgs) {
export async function chatAction({ request }: ActionFunctionArgs) {
const formData = await request.formData();
return await addMessagesToChat(
Number(formData.get("chatId")!.toString()),
formData.get("text")!.toString(),
);
const intent = formData.get("intent")!.toString();
if (intent == "addMessage") {
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 {
MessagesToResp,
MessageToResp,
NoContentToResp,
TMessagesToResp,
TMessageToResp,
TNoContentToResp,
} from "./dto";
import { fetchJSONAuth } from "./utils";
@@ -20,3 +22,27 @@ export async function addMessagesToChat(
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 java.security.Principal;
import java.util.Objects;
import java.util.stream.Stream;
@RestController
@@ -52,4 +53,26 @@ public class MessageController {
messageService.create(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);
}
}