1818 @update-thread =" () => updateThreadLocal()"
1919 />
2020 </div >
21+ <div v-else class =" flex flex-col items-center justify-center space-y-3 py-12" >
22+ <MessageIcon class =" size-12 text-secondary" />
23+ <p class =" text-lg text-secondary" >No messages yet</p >
24+ </div >
2125
2226 <template v-if =" reportClosed " >
2327 <p class =" text-secondary" >This thread is closed and new messages cannot be sent to it.</p >
24- <ButtonStyled v-if =" isStaff(auth.user)" color =" green" class =" mt-2 w-full sm:w-auto " >
28+ <ButtonStyled v-if =" isStaff(auth.user)" color =" green" class =" mt-2" >
2529 <button
26- class =" flex w-full items-center justify-center gap-2 sm:w-auto"
30+ class =" w-full gap-2 sm:w-auto"
2731 @click =" reopenReport()"
2832 >
2933 <CheckCircleIcon class =" size-4" />
3337 </template >
3438
3539 <template v-else >
36- <div class = " mt-4 " >
40+ <div >
3741 <MarkdownEditor
3842 v-model =" replyBody"
3943 :placeholder =" sortedMessages.length > 0 ? 'Reply to thread...' : 'Send a message...'"
4549 class =" mt-4 flex flex-col items-stretch justify-between gap-3 sm:flex-row sm:items-center sm:gap-2"
4650 >
4751 <div class =" flex flex-col items-stretch gap-2 sm:flex-row sm:items-center" >
48- <ButtonStyled v-if =" sortedMessages.length > 0" color =" brand" class = " w-full sm:w-auto " >
52+ <ButtonStyled v-if =" sortedMessages.length > 0" color =" brand" >
4953 <button
5054 :disabled =" !replyBody"
51- class =" flex w-full items-center justify-center gap-2 sm:w-auto"
55+ class =" w-full gap-2 sm:w-auto"
5256 @click =" sendReply()"
5357 >
5458 <ReplyIcon class =" size-4" />
5559 Reply
5660 </button >
5761 </ButtonStyled >
58- <ButtonStyled v-else color =" brand" class = " w-full sm:w-auto " >
62+ <ButtonStyled v-else color =" brand" >
5963 <button
6064 :disabled =" !replyBody"
61- class =" flex w-full items-center justify-center gap-2 sm:w-auto"
65+ class =" w-full gap-2 sm:w-auto"
6266 @click =" sendReply()"
6367 >
6468 <SendIcon class =" size-4" />
6569 Send
6670 </button >
6771 </ButtonStyled >
68- <ButtonStyled v-if =" isStaff(auth.user)" class = " w-full sm:w-auto " >
72+ <ButtonStyled v-if =" isStaff(auth.user)" >
6973 <button
7074 :disabled =" !replyBody"
71- class =" flex w-full items-center justify-center gap-2 sm:w-auto"
75+ class =" w-full sm:w-auto"
7276 @click =" sendReply(true)"
7377 >
7478 Add note
8488
8589 <div class =" flex flex-col items-stretch gap-2 sm:flex-row sm:items-center" >
8690 <template v-if =" isStaff (auth .user )" >
87- <ButtonStyled v-if =" replyBody" color =" red" class = " w-full sm:w-auto " >
91+ <ButtonStyled v-if =" replyBody" color =" red" >
8892 <button
89- class =" flex w-full items-center justify-center gap-2 sm:w-auto"
93+ class =" w-full gap-2 sm:w-auto"
9094 @click =" closeReport(true)"
9195 >
9296 <CheckCircleIcon class =" size-4" />
9397 Reply and close
9498 </button >
9599 </ButtonStyled >
96- <ButtonStyled v-else color =" red" class = " w-full sm:w-auto " >
100+ <ButtonStyled v-else color =" red" >
97101 <button
98- class =" flex w-full items-center justify-center gap-2 sm:w-auto"
102+ class =" w-full gap-2 sm:w-auto"
99103 @click =" closeReport()"
100104 >
101105 <CheckCircleIcon class =" size-4" />
110114</template >
111115
112116<script setup lang="ts">
113- import { CheckCircleIcon , ReplyIcon , SendIcon } from ' @modrinth/assets'
117+ import { CheckCircleIcon , MessageIcon , ReplyIcon , SendIcon } from ' @modrinth/assets'
114118import {
115119 type ExtendedReport ,
116120 reportQuickReplies ,
@@ -136,11 +140,13 @@ import ThreadMessage from './ThreadMessage.vue'
136140const { addNotification } = injectNotificationManager ()
137141
138142const visibleQuickReplies = computed <OverflowMenuOption []>(() => {
143+ if (! props .report ) return []
144+
139145 return reportQuickReplies
140146 .filter ((reply ) => {
141147 if (reply .shouldShow === undefined ) return true
142148 if (typeof reply .shouldShow === ' function' ) {
143- return reply .shouldShow (props .report )
149+ return reply .shouldShow (props .report ! as ExtendedReport )
144150 }
145151
146152 return reply .shouldShow
@@ -156,13 +162,20 @@ const visibleQuickReplies = computed<OverflowMenuOption[]>(() => {
156162
157163const props = defineProps <{
158164 thread: Thread
159- reporter: User
160- report: ExtendedReport
165+ reporter? : User
166+ report? : Partial <ExtendedReport > & {
167+ id? : string
168+ thread_id? : string
169+ closed? : boolean
170+ created? : string
171+ }
161172}>()
162173
163174async function handleQuickReply(reply : ReportQuickReply ) {
175+ if (! props .report ) return
176+
164177 const message =
165- typeof reply .message === ' function' ? await reply .message (props .report ) : reply .message
178+ typeof reply .message === ' function' ? await reply .message (props .report as ExtendedReport ) : reply .message
166179
167180 await nextTick ()
168181 setReplyContent (message )
@@ -181,8 +194,9 @@ const emit = defineEmits<{
181194const flags = useFeatureFlags ()
182195
183196const members = computed (() => {
184- const membersMap: Record <string , User > = {
185- [props .reporter .id ]: props .reporter ,
197+ const membersMap: Record <string , User > = {}
198+ if (props .reporter ) {
199+ membersMap [props .reporter .id ] = props .reporter
186200 }
187201 for (const member of props .thread .members ) {
188202 membersMap [member .id ] = member
@@ -196,21 +210,25 @@ function setReplyContent(content: string) {
196210}
197211
198212const sortedMessages = computed (() => {
199- const messages: TypeThreadMessage [] = [
200- {
213+ const messages: TypeThreadMessage [] = []
214+
215+ // Add synthetic first message for reports
216+ if (props .report ?.created && props .reporter ) {
217+ messages .push ({
201218 id: null ,
202219 author_id: props .reporter .id ,
203220 body: {
204221 type: ' text' ,
205- body: props .report .body || ' Report opened.' ,
222+ body: props .report .body || ' Thread opened.' ,
206223 private: false ,
207224 replying_to: null ,
208225 associated_images: [],
209226 },
210227 created: props .report .created ,
211228 hide_identity: false ,
212- },
213- ]
229+ })
230+ }
231+
214232 if (props .thread ) {
215233 messages .push (
216234 ... [... props .thread .messages ].sort (
@@ -223,7 +241,7 @@ const sortedMessages = computed(() => {
223241})
224242
225243async function updateThreadLocal() {
226- const threadId = props .report .thread_id
244+ const threadId = props .report ? .thread_id || props . thread . id
227245 if (threadId ) {
228246 try {
229247 const thread = (await useBaseFetch (` thread/${threadId } ` )) as Thread
@@ -284,6 +302,8 @@ const reportClosed = computed(() => {
284302})
285303
286304async function closeReport(reply = false ) {
305+ if (! props .report ?.id ) return
306+
287307 if (reply ) {
288308 await sendReply ()
289309 }
@@ -307,6 +327,8 @@ async function closeReport(reply = false) {
307327}
308328
309329async function reopenReport() {
330+ if (! props .report ?.id ) return
331+
310332 try {
311333 await useBaseFetch (` report/${props .report .id } ` , {
312334 method: ' PATCH' ,
0 commit comments