Skip to content

Commit

Permalink
feat: add comment feature on candidate forms
Browse files Browse the repository at this point in the history
  • Loading branch information
harshtandiya committed Oct 22, 2024
1 parent fc8af86 commit 80e0fad
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 1 deletion.
74 changes: 74 additions & 0 deletions ballot/api/comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import frappe
from .user import get_user_details


@frappe.whitelist(allow_guest=True)
def get_all_comments(doctype: str, docname: str) -> list:
"""
Get all the comments of a doctype's document.
args:
doctype: doctype name/id
docname: id of document whose comment's are needed
returns:
list: list of all comments
"""
if (
frappe.db.count(
"Comment",
{"reference_doctype": doctype, "reference_name": docname, "comment_type": "Comment"},
)
== 0
):
return []

comments = frappe.db.get_all(
"Comment",
{"reference_doctype": doctype, "reference_name": docname, "comment_type": "Comment"},
[
"comment_email",
"subject",
"content",
"comment_by",
"name",
"reference_doctype",
"reference_name",
"creation",
],
page_length=9999,
)

for comment in comments:
user = get_user_details(comment.comment_email)
comment["full_name"] = user.full_name
comment["user_image"] = user.user_image
comment["replies"] = get_all_comments("Comment", comment.name)

return comments


@frappe.whitelist()
def add_comment(reference_doctype: str, reference_name: str, content: str) -> None:
"""
Add a comment to permitted doctypes
args:
reference_doctype: doctype to add comment to
reference_name: name of the document
content: content of the comment
"""

if reference_doctype not in ["Election Candidate Application", "Comment"]:
frappe.throw("Not allowed to add comment to this doctype", frappe.PermissionError)

comment = frappe.get_doc(
{
"doctype": "Comment",
"comment_type": "Comment",
"reference_doctype": reference_doctype,
"reference_name": reference_name,
"content": content,
}
)
comment.insert(ignore_permissions=True)
88 changes: 88 additions & 0 deletions frontend/src/components/comments/CommentBox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<template>
<div
v-if="!session.user"
class="w-full h-28 rounded-lg bg-primary-50 border flex flex-col gap-2 items-center justify-center"
>
<h5 class="text-lg font-semibold">Login to comment</h5>
<Button label="Login" variant="solid" size="md" @click="redirectToLogin" />
</div>
<div v-else>
<div class="space-y-2 pt-2">
<Editor
v-model="commentContent"
placeholder="Add comment..."
editor-style="height: 150px"
>
<template #toolbar>
<span class="ql-formats">
<button v-tooltip.bottom="'Bold'" class="ql-bold"></button>
<button v-tooltip.bottom="'Italic'" class="ql-italic"></button>
<button
v-tooltip.bottom="'Underline'"
class="ql-underline"
></button>
<button v-tooltip.bottom="'Link'" class="ql-link"></button>
</span>
</template>
</Editor>
<ErrorMessage :message="errorMessages" />
<Button label="Add Comment" variant="solid" @click="handleComment" />
</div>
</div>
</template>
<script setup>
import { inject, ref } from 'vue'
import Editor from 'primevue/editor'
import { createResource, ErrorMessage } from 'frappe-ui'
import { toast } from 'vue-sonner'
const errorMessages = ref('')
const commentContent = ref('')
const session = inject('$session')
const emit = defineEmits(['refresh-comments'])
const model = defineModel({ type: String, required: true })
const props = defineProps({
doctype: {
type: String,
required: true,
},
docname: {
type: String,
required: true,
},
})
const redirectToLogin = () => {
window.location.href = `/login?redirect-to=${window.location.pathname}`
}
const addComment = createResource({
url: 'ballot.api.comments.add_comment',
makeParams() {
return {
reference_doctype: props.doctype,
reference_name: props.docname,
content: commentContent.value,
}
},
onSuccess() {
toast('Comment added')
emit('refresh-comments')
commentContent.value = ''
},
onError(err) {
toast.error('Error while adding comment!')
errorMessages.value = err.messages
},
})
const handleComment = () => {
if (!commentContent.value) {
errorMessages.value = 'Cannot post a blank comment'
return
}
errorMessages.value = ''
addComment.fetch()
}
</script>
40 changes: 40 additions & 0 deletions frontend/src/components/comments/CommentSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<div class="space-y-2 my-5 w-full">
<div class="space-x-2">
<h5 class="text-lg font-semibold">Comments</h5>
</div>
<p class="text-base text-primary-600">
Comments will be public and non-anonymous.
</p>
<CommentBox
:doctype="doctype"
:docname="docname"
@refresh-comments="refreshCommentsList"
/>
<CommentsList ref="commentsList" :doctype="doctype" :docname="docname" />
</div>
</template>
<script setup>
import CommentBox from './CommentBox.vue'
import CommentsList from './CommentsList.vue'
import { ref } from 'vue'
const props = defineProps({
doctype: {
type: String,
required: true,
},
docname: {
type: String,
required: true,
},
})
const commentsList = ref(null)
const refreshCommentsList = () => {
if (commentsList.value) {
commentsList.value.reloadComments()
}
}
</script>
41 changes: 41 additions & 0 deletions frontend/src/components/comments/CommentsList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<div
v-for="comment in comments.data"
:key="comment.name"
class="flex flex-col py-2 w-full"
>
<RenderComment :comment="comment" @refresh-comments="reloadComments" />
</div>
</template>
<script setup>
import { createResource } from 'frappe-ui'
import RenderComment from './RenderComment.vue'
const props = defineProps({
doctype: {
type: String,
required: true,
},
docname: {
type: String,
required: true,
},
})
const comments = createResource({
url: 'ballot.api.comments.get_all_comments',
makeParams() {
return {
doctype: props.doctype,
docname: props.docname,
}
},
auto: true,
})
const reloadComments = () => {
comments.fetch()
}
defineExpose({ reloadComments })
</script>
62 changes: 62 additions & 0 deletions frontend/src/components/comments/RenderComment.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template>
<div class="flex flex-col">
<div class="flex items-center gap-2">
<Avatar v-if="comment.user_image" :image="comment.user_image" />
<Avatar v-else :label="comment.full_name.charAt(0)" />
<h5 class="text-base font-medium">{{ comment.full_name }}</h5>
<span class="text-sm font-light">
{{ getRelativeTime(comment.creation) }}
</span>
</div>
<div class="px-6 ml-4 pt-1 text-base space-y-2 border-l border-gray-500">
<div class="[&>*]:!p-0" v-html="comment.content"></div>
<div v-if="session.user != null" class="flex gap-2">
<Button
icon-left="corner-up-left"
label="Reply"
variant="ghost"
@click="toggleReply"
/>
</div>
<CommentBox
v-if="showReply"
doctype="Comment"
:docname="comment.name"
@refresh-comments="handleReply"
/>
</div>
</div>
<div v-if="comment.replies.length > 0" class="flex ml-4 w-full">
<div class="w-5 h-5 rounded-bl border-l border-b border-gray-500"></div>
<div class="flex flex-col w-full">
<CommentsList doctype="Comment" :docname="comment.name" />
</div>
</div>
</template>
<script setup>
import CommentBox from './CommentBox.vue'
import CommentsList from './CommentsList.vue'
import Avatar from 'primevue/avatar'
import { getRelativeTime } from '@/utils/helpers'
import { inject, ref } from 'vue'
const showReply = ref(false)
const toggleReply = () => {
showReply.value = !showReply.value
}
const emit = defineEmits(['refresh-comments'])
const session = inject('$session')
const props = defineProps({
comment: {
type: Object,
required: true,
},
})
const handleReply = () => {
showReply.value = false
emit('refresh-comments')
}
</script>
4 changes: 4 additions & 0 deletions frontend/src/pages/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const sidebarItems = [
label: 'Dashboard',
route: '/dashboard',
},
{
label: 'My Submissions',
route: '/my-submissions',
},
{
label: 'All Elections',
route: '/elections',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/pages/election/CandidatureSubmissionPublic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
:field="field"
/>
</div>
<CommentSection
doctype="Election Candidate Application"
:docname="route.params.id"
/>
</div>
</div>
</div>
Expand All @@ -44,6 +48,7 @@ import Breadcrumb from '@/components/Breadcrumb.vue'
import Avatar from 'primevue/avatar'
import Header from '@/components/Header.vue'
import RenderFieldData from '@/components/candidature/RenderFieldData.vue'
import CommentSection from '@/components/comments/CommentSection.vue'
import { createResource, LoadingText } from 'frappe-ui'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/pages/submissions/MySubmissions.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<template>
<div></div>
<div class="px-4 py-6 flex flex-col gap-6">
<div>
<h1 class="text-3xl font-sans font-bold">My Submissions</h1>
</div>
</div>
</template>
<script setup></script>

0 comments on commit 80e0fad

Please sign in to comment.