diff --git a/client/src/Login.tsx b/client/src/Login.tsx index 37f2161..1df192d 100644 --- a/client/src/Login.tsx +++ b/client/src/Login.tsx @@ -1,20 +1,14 @@ import "./Auth.scss"; import { Form, Link, useActionData, useNavigation } from "react-router-dom"; -import { loginAction } from "./actions"; +import { ActionToType, loginAction } from "./actions"; import { isError } from "./api/dto"; export function Login() { - const data = useActionData() as - | Awaited> - | undefined; - - if (data && !isError(data)) { - return
Login success
; - } + const data = useActionData() as ActionToType; let errors: JSX.Element[] = []; - if (data) { + if (isError(data)) { errors = data.errors.map((e) => { return {e}; }); diff --git a/client/src/Post.scss b/client/src/Post.scss new file mode 100644 index 0000000..7646b78 --- /dev/null +++ b/client/src/Post.scss @@ -0,0 +1,19 @@ +.post { + display: flex; + flex-direction: column; + border: 1px solid #E0E0E0; + border-radius: 7px; + padding: 7px; + resize: none; + margin: 1rem 0; + + .text { + word-wrap: anywhere; + } + + .createdDate { + margin-top: 0.3rem; + font-size: 0.7rem; + color: #A0A0A0; + } +} \ No newline at end of file diff --git a/client/src/Post.tsx b/client/src/Post.tsx new file mode 100644 index 0000000..fc55ed4 --- /dev/null +++ b/client/src/Post.tsx @@ -0,0 +1,16 @@ +import "./Post.scss"; + +export function Post({ + text, + createdDate, +}: { + text: string; + createdDate: string; +}) { + return ( +
+ {text} + {createdDate} +
+ ); +} diff --git a/client/src/Profile.scss b/client/src/Profile.scss index dae793a..4d402ae 100644 --- a/client/src/Profile.scss +++ b/client/src/Profile.scss @@ -55,6 +55,6 @@ } .posts { - padding: 2rem; + padding: 0 2rem 2rem; } } \ No newline at end of file diff --git a/client/src/Profile.tsx b/client/src/Profile.tsx index bcf4974..e371aa4 100644 --- a/client/src/Profile.tsx +++ b/client/src/Profile.tsx @@ -3,6 +3,7 @@ import { Form, Link, useLoaderData } from "react-router-dom"; import { LoaderToType, profileSelfLoader } from "./loaders"; import { isError } from "./api/dto"; import { ProfileCard } from "./ProfileCard"; +import { Post } from "./Post"; export interface IProfileProps { self: boolean; @@ -16,6 +17,11 @@ export function Profile(props: IProfileProps) { if (!loaderData || isError(loaderData)) { return
Error
; } + + const sortedPosts = loaderData.posts?.sort( + (a, b) => b.createdAt - a.createdAt, + ); + return (
@@ -29,12 +35,15 @@ export function Profile(props: IProfileProps) {
- {loaderData.posts && - loaderData.posts.map((p) => { + {sortedPosts && + sortedPosts.map((p) => { + const date = new Date(p.createdAt * 1000); return ( -
- {p.text} -
+ ); })}
diff --git a/client/src/Signup.tsx b/client/src/Signup.tsx index 14b66ac..6d564f2 100644 --- a/client/src/Signup.tsx +++ b/client/src/Signup.tsx @@ -1,20 +1,14 @@ import "./Auth.scss"; import { Form, Link, useActionData, useNavigation } from "react-router-dom"; -import { signupAction } from "./actions"; +import { ActionToType, signupAction } from "./actions"; import { isError } from "./api/dto"; export function Signup() { - const data = useActionData() as - | Awaited> - | undefined; - - if (data && !isError(data)) { - return
Signup success
; - } + const data = useActionData() as ActionToType; let errors: JSX.Element[] = []; - if (data) { + if (isError(data)) { errors = data.errors.map((e) => { return {e}; }); diff --git a/client/src/actions.ts b/client/src/actions.ts index c22d1fa..d1944d6 100644 --- a/client/src/actions.ts +++ b/client/src/actions.ts @@ -5,6 +5,10 @@ import { isError } from "./api/dto"; import { setToken } from "./api/utils"; import { post } from "./api/Post"; +export type ActionToType any> = + | Exclude>, Response> + | undefined; + export async function loginAction({ request }: ActionFunctionArgs) { const formData = await request.formData(); const ret = await login( diff --git a/client/src/api/dto.ts b/client/src/api/dto.ts index 3a8888f..9af6570 100644 --- a/client/src/api/dto.ts +++ b/client/src/api/dto.ts @@ -45,6 +45,7 @@ export const PostTo = z.object({ id: z.number(), authorUuid: z.string(), text: z.string(), + createdAt: z.number(), }); export type TPostTo = z.infer; diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/dto/PostTo.java b/server/src/main/java/com/usatiuk/tjv/y/server/dto/PostTo.java index 1182b1c..4d59ca1 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/dto/PostTo.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/dto/PostTo.java @@ -2,5 +2,5 @@ package com.usatiuk.tjv.y.server.dto; import com.usatiuk.tjv.y.server.entity.Post; -public record PostTo(Long id, String authorUuid, String text) { +public record PostTo(Long id, String authorUuid, String text, Long createdAt) { } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PostMapper.java b/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PostMapper.java index 3b16f76..f88912d 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PostMapper.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/dto/converters/PostMapper.java @@ -1,12 +1,10 @@ package com.usatiuk.tjv.y.server.dto.converters; -import com.usatiuk.tjv.y.server.dto.PersonTo; import com.usatiuk.tjv.y.server.dto.PostTo; -import com.usatiuk.tjv.y.server.entity.Person; import com.usatiuk.tjv.y.server.entity.Post; public class PostMapper { public static PostTo makeDto(Post post) { - return new PostTo(post.getId(), post.getAuthor().getUuid(), post.getText()); + return new PostTo(post.getId(), post.getAuthor().getUuid(), post.getText(), post.getCreatedAt().getEpochSecond()); } } diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java index 171fed3..a1ed589 100644 --- a/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java +++ b/server/src/main/java/com/usatiuk/tjv/y/server/entity/Post.java @@ -1,11 +1,18 @@ package com.usatiuk.tjv.y.server.entity; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.experimental.Accessors; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.Instant; @Entity @Getter @@ -21,9 +28,12 @@ public class Post implements EntityWithId { @ManyToOne private Person author; - @Lob + @NotBlank private String text; + @CreationTimestamp + private Instant createdAt; + @Override public Long getId() { return id;