update posts

This commit is contained in:
Stepan Usatiuk
2023-12-17 15:04:19 +01:00
parent 8940b3d6c6
commit 18ac8ded71
10 changed files with 109 additions and 31 deletions

View File

@@ -6,6 +6,17 @@
padding: 7px; padding: 7px;
resize: none; resize: none;
margin: 1rem 0; margin: 1rem 0;
transition: height 0.5s;
&.postEditing {
padding: 0;
&:focus-within {
border-color: mediumpurple;
outline: solid mediumpurple 2px;
}
height: 6rem;
transition: height 0.5s;
}
.text { .text {
word-wrap: anywhere; word-wrap: anywhere;
@@ -39,8 +50,18 @@
} }
.actions { .actions {
display: flex;
align-self: end; align-self: end;
> * {
margin-left: 0.5rem;
}
> *:first-child {
margin-left: 0;
}
button { button {
background: none; background: none;
border: none; border: none;

View File

@@ -1,5 +1,8 @@
import "./Post.scss"; import "./Post.scss";
import { Form, Link } from "react-router-dom"; import { Form, Link, useSubmit } from "react-router-dom";
import { useState } from "react";
import "./PostForm.scss";
export function Post({ export function Post({
text, text,
@@ -14,6 +17,35 @@ export function Post({
actions: boolean; actions: boolean;
id: number; id: number;
}) { }) {
const [editing, setEditing] = useState(false);
const submit = useSubmit();
if (editing) {
return (
<div className={"post postEditing"}>
<Form className={"postForm"} method="patch">
<input hidden={true} name={"postId"} value={id} />
<textarea
placeholder={"Write something!"}
name="text"
defaultValue={text}
/>
<button
name="intent"
value="updatePost"
type="submit"
onClick={(e) => {
setEditing(false);
submit(e.currentTarget);
}}
>
save
</button>
</Form>
</div>
);
}
return ( return (
<div className={"post"}> <div className={"post"}>
<span className={"text"}>{text}</span> <span className={"text"}>{text}</span>
@@ -29,6 +61,7 @@ export function Post({
</div> </div>
{actions && ( {actions && (
<div className={"actions"}> <div className={"actions"}>
{<button onClick={() => setEditing(true)}>edit</button>}
<Form method={"delete"}> <Form method={"delete"}>
<input <input
hidden={true} hidden={true}

24
client/src/PostForm.scss Normal file
View File

@@ -0,0 +1,24 @@
.postForm {
min-height: 100%;
display: flex;
flex-direction: row;
flex: auto;
textarea {
flex-grow: 1;
border: 0px;
border-radius: 7px 0 0 7px;
padding: 7px;
resize: none;
&:focus {
outline: none;
}
}
button {
border: 0px;
border-radius: 0 7px 7px 0;
}
}

View File

@@ -37,30 +37,6 @@
outline: solid mediumpurple 2px; outline: solid mediumpurple 2px;
} }
form {
min-height: 100%;
display: flex;
flex-direction: row;
flex: auto;
textarea {
flex-grow: 1;
border: 0px;
border-radius: 7px 0 0 7px;
padding: 7px;
resize: none;
&:focus {
outline: none;
}
}
button {
border: 0px;
border-radius: 0 7px 7px 0;
}
}
} }
.posts { .posts {

View File

@@ -1,4 +1,3 @@
import "./Profile.scss";
import { Form, Link, useLoaderData } from "react-router-dom"; import { Form, Link, useLoaderData } from "react-router-dom";
import { LoaderToType, profileLoader } from "./loaders"; import { LoaderToType, profileLoader } from "./loaders";
import { isError } from "./api/dto"; import { isError } from "./api/dto";
@@ -6,6 +5,9 @@ import { Post } from "./Post";
import { useHomeContext } from "./HomeContext"; import { useHomeContext } from "./HomeContext";
import { PostList } from "./PostList"; import { PostList } from "./PostList";
import "./PostForm.scss";
import "./Profile.scss";
export interface IProfileProps { export interface IProfileProps {
self: boolean; self: boolean;
} }
@@ -35,7 +37,7 @@ export function Profile({ self }: IProfileProps) {
</div> </div>
{self && ( {self && (
<div className={"newPost"}> <div className={"newPost"}>
<Form method="post"> <Form className={"postForm"} method="post">
<textarea <textarea
placeholder={"Write something!"} placeholder={"Write something!"}
name="text" name="text"

View File

@@ -3,7 +3,7 @@ import { ActionFunctionArgs, redirect } from "react-router-dom";
import { login } from "./api/Token"; import { login } from "./api/Token";
import { isError } from "./api/dto"; import { isError } from "./api/dto";
import { deleteToken, setToken } from "./api/utils"; import { deleteToken, setToken } from "./api/utils";
import { createPost, deletePost } from "./api/Post"; import { createPost, deletePost, updatePost } from "./api/Post";
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>
@@ -59,6 +59,11 @@ export async function profileSelfAction({ request }: ActionFunctionArgs) {
return await deletePost( return await deletePost(
parseInt(formData.get("postToDeleteId")!.toString()), parseInt(formData.get("postToDeleteId")!.toString()),
); );
} else if (intent == "updatePost") {
return await updatePost(
formData.get("text")!.toString(),
parseInt(formData.get("postId")!.toString()),
);
} }
} }

View File

@@ -14,6 +14,14 @@ export async function createPost(text: string): Promise<TPostToResp> {
}); });
} }
export async function updatePost(
text: string,
postId: number,
): Promise<TPostToResp> {
return fetchJSONAuth("/post/" + postId, "PATCH", PostToResp, {
text,
});
}
export async function deletePost(id: number): Promise<TNoContentToResp> { export async function deletePost(id: number): Promise<TNoContentToResp> {
return fetchJSONAuth(`/post/${id.toString()}`, "DELETE", NoContentToResp); return fetchJSONAuth(`/post/${id.toString()}`, "DELETE", NoContentToResp);
} }

View File

@@ -64,6 +64,15 @@ public class PostController {
return PostMapper.makeDto(post.get()); return PostMapper.makeDto(post.get());
} }
@PatchMapping(path = "/{id}")
public PostTo update(Principal principal, @PathVariable long id, @RequestBody PostCreateTo postCreateTo) {
var post = postService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!Objects.equals(post.getAuthor().getUuid(), principal.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
post.setText(postCreateTo.text());
postService.update(post);
return PostMapper.makeDto(post);
}
@DeleteMapping(path = "/{id}") @DeleteMapping(path = "/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)

View File

@@ -12,7 +12,7 @@ public interface CrudService<T extends EntityWithId<ID>, ID extends Serializable
Iterable<T> readAll(); Iterable<T> readAll();
void update(ID id, T e); void update(T e);
void deleteById(ID id); void deleteById(ID id);
} }

View File

@@ -27,8 +27,8 @@ public abstract class CrudServiceImpl<T extends EntityWithId<ID>, ID extends Ser
} }
@Override @Override
public void update(ID id, T e) { public void update(T e) {
getRepository().save(e);
} }
@Override @Override