Skip to content

Commit 67f5f1b

Browse files
committed
Fix: Persist inquiry read status and archival state to server
1 parent ef7fffc commit 67f5f1b

3 files changed

Lines changed: 100 additions & 5 deletions

File tree

web/prisma/schema.prisma

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ model Ticket {
2323
userId String
2424
createdAt DateTime @default(now())
2525
updatedAt DateTime @updatedAt
26-
messages Message[]
27-
user User @relation(fields: [userId], references: [id])
26+
messages Message[]
27+
user User @relation(fields: [userId], references: [id])
28+
lastReadMessageId String?
29+
isArchived Boolean @default(false)
2830
2931
@@index([userId])
3032
}

web/src/actions/tickets.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,66 @@ export async function userReplyToTicket(ticketId: string, text: string) {
271271
return { success: false, error: 'Failed to send follow-up' };
272272
}
273273
}
274+
275+
export async function updateTicketReadStatus(ticketId: string, messageId: string) {
276+
try {
277+
const clerkUser = await currentUser();
278+
const userEmail = clerkUser?.emailAddresses[0]?.emailAddress;
279+
280+
if (!userEmail) {
281+
return { success: false, error: 'Unauthorized' };
282+
}
283+
284+
// Verify ownership
285+
const ticket = await prisma.ticket.findUnique({
286+
where: { id: ticketId },
287+
include: { user: true }
288+
});
289+
290+
if (!ticket || ticket.user.email !== userEmail) {
291+
return { success: false, error: 'Unauthorized' };
292+
}
293+
294+
await prisma.ticket.update({
295+
where: { id: ticketId },
296+
data: { lastReadMessageId: messageId }
297+
});
298+
299+
return { success: true };
300+
} catch (error) {
301+
console.error('Error updating read status:', error);
302+
return { success: false, error: 'Failed to update' };
303+
}
304+
}
305+
306+
export async function archiveTicket(ticketId: string) {
307+
try {
308+
const clerkUser = await currentUser();
309+
const userEmail = clerkUser?.emailAddresses[0]?.emailAddress;
310+
311+
if (!userEmail) {
312+
return { success: false, error: 'Unauthorized' };
313+
}
314+
315+
// Verify ownership
316+
const ticket = await prisma.ticket.findUnique({
317+
where: { id: ticketId },
318+
include: { user: true }
319+
});
320+
321+
if (!ticket || ticket.user.email !== userEmail) {
322+
return { success: false, error: 'Unauthorized' };
323+
}
324+
325+
await prisma.ticket.update({
326+
where: { id: ticketId },
327+
data: { isArchived: true }
328+
});
329+
330+
revalidatePath('/ask');
331+
return { success: true };
332+
} catch (error) {
333+
console.error('Error archiving ticket:', error);
334+
return { success: false, error: 'Failed to archive' };
335+
}
336+
}

web/src/context/InquiryContext.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,26 @@ export function InquiryProvider({ children }: { children: React.ReactNode }) {
5353

5454
try {
5555
const data = await getTickets();
56-
setTickets(data as Ticket[]);
56+
const ticketsData = data as Ticket[];
57+
setTickets(ticketsData);
58+
59+
// Populate state from DB persistence
60+
const dbReadMessages: { [key: string]: string } = {};
61+
const dbAcknowledgedTickets: string[] = [];
62+
63+
ticketsData.forEach((t: any) => {
64+
if (t.lastReadMessageId) {
65+
dbReadMessages[t.id] = t.lastReadMessageId;
66+
}
67+
if (t.isArchived) {
68+
dbAcknowledgedTickets.push(t.id);
69+
}
70+
});
71+
72+
// Merge with local state to avoid race conditions/glitches, prioritizing DB if present
73+
setReadMessages(prev => ({ ...prev, ...dbReadMessages }));
74+
setAcknowledgedTickets(prev => Array.from(new Set([...prev, ...dbAcknowledgedTickets])));
75+
5776
} catch (error) {
5877
console.error('Failed to fetch tickets in context:', error);
5978
} finally {
@@ -89,17 +108,28 @@ export function InquiryProvider({ children }: { children: React.ReactNode }) {
89108
}
90109
}, [isLoaded, isSignedIn, fetchTickets]);
91110

92-
const markAsRead = (ticketId: string, messageId: string) => {
111+
const markAsRead = async (ticketId: string, messageId: string) => {
112+
// Optimistic update
93113
const next = { ...readMessages, [ticketId]: messageId };
94114
setReadMessages(next);
95115
localStorage.setItem('krishnasagar_read_messages', JSON.stringify(next));
116+
117+
// Server update
118+
// Dynamically import to avoid circular dependency issues if any
119+
const { updateTicketReadStatus } = await import('@/actions/tickets');
120+
await updateTicketReadStatus(ticketId, messageId);
96121
};
97122

98-
const acknowledgeTicket = (ticketId: string) => {
123+
const acknowledgeTicket = async (ticketId: string) => {
99124
if (!acknowledgedTickets.includes(ticketId)) {
125+
// Optimistic update
100126
const next = [...acknowledgedTickets, ticketId];
101127
setAcknowledgedTickets(next);
102128
localStorage.setItem('krishnasagar_acknowledged_tickets', JSON.stringify(next));
129+
130+
// Server update
131+
const { archiveTicket } = await import('@/actions/tickets');
132+
await archiveTicket(ticketId);
103133
}
104134
};
105135

0 commit comments

Comments
 (0)