Implemented comment viewing and posting
This commit is contained in:
22
ui/src/api/discussion.ts
Normal file
22
ui/src/api/discussion.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DiscussionComment } from "@shared/types/discussion";
|
||||
|
||||
//@ts-expect-error
|
||||
const addr = import.meta.env.VITE_APIHOST;
|
||||
|
||||
export async function postComment(comment: DiscussionComment) {
|
||||
const res = await fetch(`${addr}/discussions/comment`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(comment),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
return;
|
||||
} else {
|
||||
throw new Error("Failed to submit LOA");
|
||||
}
|
||||
}
|
||||
|
||||
42
ui/src/components/discussion/CommentForm.vue
Normal file
42
ui/src/components/discussion/CommentForm.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import Textarea from '../ui/textarea/Textarea.vue';
|
||||
import Button from '../ui/button/Button.vue';
|
||||
import { postComment } from '@/api/discussion';
|
||||
import { DiscussionComment } from '@shared/types/discussion';
|
||||
|
||||
const props = defineProps<{
|
||||
parentId: number,
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['commentPosted']);
|
||||
const commentText = ref('');
|
||||
const error = ref('');
|
||||
|
||||
async function submitComment(e: Event) {
|
||||
e.preventDefault();
|
||||
error.value = '';
|
||||
if (!commentText.value.trim()) {
|
||||
error.value = 'Comment cannot be empty';
|
||||
return;
|
||||
}
|
||||
let newComment: DiscussionComment = { post_id: props.parentId, content: commentText.value.trim() };
|
||||
await postComment(newComment);
|
||||
emit('commentPosted');
|
||||
commentText.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit="submitComment">
|
||||
<div class="mb-2">
|
||||
<label class="block font-medium mb-1">Add a comment</label>
|
||||
<Textarea rows="3" class="bg-neutral-800 resize-none w-full" v-model="commentText"
|
||||
placeholder="Add a comment…" />
|
||||
<div class="h-4 text-red-500 text-sm mt-1" v-if="error">{{ error }}</div>
|
||||
</div>
|
||||
<div class="mt-2 flex justify-end">
|
||||
<Button type="submit" :disabled="!commentText.trim()">Post Comment</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
31
ui/src/components/discussion/CommentThread.vue
Normal file
31
ui/src/components/discussion/CommentThread.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { DiscussionComment } from '@shared/types/discussion';
|
||||
import DiscussionCommentView from './DiscussionCommentView.vue';
|
||||
import CommentForm from './CommentForm.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
parentId: number;
|
||||
comments: DiscussionComment[];
|
||||
}>();
|
||||
|
||||
function onCommentPosted() {
|
||||
// TODO: Refresh comments if needed
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-10 *:my-5">
|
||||
<h3 class="text-xl font-semibold">Discussion</h3>
|
||||
<div class="comment-thread">
|
||||
<div class="comments-list flex flex-col gap-5">
|
||||
<div v-for="comment in comments" :key="comment.id" class="comment">
|
||||
<DiscussionCommentView :comment="comment"></DiscussionCommentView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CommentForm :parent-id="props.parentId" @commentPosted="onCommentPosted" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
26
ui/src/components/discussion/DiscussionCommentView.vue
Normal file
26
ui/src/components/discussion/DiscussionCommentView.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { DiscussionComment } from '@shared/types/discussion';
|
||||
import MemberCard from '../members/MemberCard.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
comment: DiscussionComment;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="rounded-md border border-neutral-800 p-3 space-y-5">
|
||||
<!-- Comment header -->
|
||||
<div class="flex justify-between">
|
||||
<MemberCard :member-id="comment.poster_id"></MemberCard>
|
||||
<p class="text-muted-foreground">{{ new Date(comment.created_at).toLocaleString("EN-us", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
}) }}</p>
|
||||
</div>
|
||||
<p>{{ comment.content }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -6,6 +6,7 @@
|
||||
import { getModRequest } from '@/api/modRequests';
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
import Badge from '@/components/ui/badge/Badge.vue';
|
||||
import CommentThread from '../discussion/CommentThread.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -41,7 +42,7 @@
|
||||
<div class="max-w-[80rem] mt-5 mx-auto px-2 lg:px-20 w-full">
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="scroll-m-20 text-3xl font-semibold tracking-tight mb-2">Mod Request: {{ post.title }}</h1>
|
||||
<h1 class="scroll-m-20 text-3xl font-semibold tracking-tight mb-2">Mod Request: {{ post?.title }}</h1>
|
||||
<p class="text-muted-foreground" v-if="post">
|
||||
Requested by {{ post.poster_name || 'Unknown' }} on {{ new
|
||||
Date(post.created_at).toLocaleDateString() }}
|
||||
@@ -95,10 +96,8 @@
|
||||
</div>
|
||||
|
||||
<!-- discussion placeholder -->
|
||||
<div class="mt-10">
|
||||
<h3 class="text-xl font-semibold">Discussion</h3>
|
||||
<p class="text-muted-foreground">Comments and thread will appear here once implemented.</p>
|
||||
</div>
|
||||
<CommentThread :parent-id="post.id" :comments="post.comments" />
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="text-muted-foreground">Unable to locate this mod request.</p>
|
||||
|
||||
Reference in New Issue
Block a user