From 6ffa499c794e58e84b7897f39a7a77d2b413998a Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Wed, 6 May 2026 22:52:32 +0300 Subject: [PATCH] implement profile, post, and new-post views ProfilePage shows a user's posts and exposes a New Post button when the logged-in user is the owner; admins see a Promote control. PostPage shows the post, its comments, and forms for adding/deleting comments. Post-author and admin can delete posts; admins can feature or unfeature any post into one of the two slots from this view. --- frontend/blog_portal/src/stores/comments.ts | 64 ++++ .../blog_portal/src/views/NewPostPage.vue | 100 +++++- frontend/blog_portal/src/views/PostPage.vue | 285 +++++++++++++++++- .../blog_portal/src/views/ProfilePage.vue | 115 ++++++- 4 files changed, 558 insertions(+), 6 deletions(-) create mode 100644 frontend/blog_portal/src/stores/comments.ts diff --git a/frontend/blog_portal/src/stores/comments.ts b/frontend/blog_portal/src/stores/comments.ts new file mode 100644 index 0000000..de947aa --- /dev/null +++ b/frontend/blog_portal/src/stores/comments.ts @@ -0,0 +1,64 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; +import { apiDelete, apiGet, apiPost } from "@/api/client"; + +export interface CommentItem { + id: number; + postId: number; + userId: number; + authorDisplayName: string; + body: string; + createdAt: string; +} + +interface CommentListPayload { + comments: CommentItem[]; +} + +interface CommentPayload { + comment: CommentItem; +} + +export const useCommentsStore = defineStore("comments", () => { + const items = ref([]); + const error = ref(null); + + async function fetchForPost(postId: number): Promise { + const result = await apiGet(`/posts/${postId}/comments`); + if (result.ok && result.data) { + items.value = result.data.comments; + } else { + error.value = result.error; + items.value = []; + } + } + + async function create(postId: number, body: string): Promise { + const result = await apiPost(`/posts/${postId}/comments`, { body }); + if (!result.ok || !result.data) { + error.value = result.error; + return false; + } + items.value = [...items.value, result.data.comment]; + return true; + } + + async function remove(id: number): Promise { + const result = await apiDelete(`/comments/${id}`); + if (!result.ok) { + error.value = result.error; + return false; + } + items.value = items.value.filter(function (existing) { + return existing.id !== id; + }); + return true; + } + + function clear(): void { + items.value = []; + error.value = null; + } + + return { items, error, fetchForPost, create, remove, clear }; +}); diff --git a/frontend/blog_portal/src/views/NewPostPage.vue b/frontend/blog_portal/src/views/NewPostPage.vue index a10ef01..2cd7729 100644 --- a/frontend/blog_portal/src/views/NewPostPage.vue +++ b/frontend/blog_portal/src/views/NewPostPage.vue @@ -1,7 +1,103 @@