Skip to content

Commit 15d7248

Browse files
committed
feat: upgrade inquiry management to focused Modal-based conversation UX v1.1.0
1 parent 2681142 commit 15d7248

3 files changed

Lines changed: 98 additions & 97 deletions

File tree

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "web",
3-
"version": "1.0.9",
3+
"version": "1.1.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

web/public/version.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"version": "1.0.9",
3-
"buildTime": "2025-12-18T16:05:00.000Z"
2+
"version": "1.1.0",
3+
"buildTime": "2025-12-18T16:07:00.000Z"
44
}

web/src/app/admin/tickets/page.tsx

Lines changed: 95 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -103,114 +103,115 @@ export default function AdminTicketsPage() {
103103
</div>
104104
</div>
105105

106-
<div className="grid gap-6">
106+
<div className="grid gap-3">
107107
{tickets.length === 0 ? (
108-
<div className="text-center py-20 bg-gray-50 rounded-xl border-2 border-dashed border-gray-200">
109-
<MessageCircle className="w-12 h-12 text-gray-300 mx-auto mb-4" />
110-
<h3 className="text-xl font-medium text-gray-900">No tickets yet</h3>
111-
<p className="text-gray-500">User inquiries will appear here.</p>
108+
<div className="text-center py-20 bg-white rounded-3xl border border-dashed border-gray-200 shadow-sm">
109+
<MessageCircle className="w-12 h-12 text-gray-200 mx-auto mb-4" />
110+
<h3 className="text-xl font-black text-gray-900 tracking-tight">No inquiries yet</h3>
111+
<p className="text-sm text-gray-400 font-medium">Seeker inquiries will appear here.</p>
112112
</div>
113113
) : (
114114
tickets.map((ticket) => (
115-
<div key={ticket.id} className={`bg-white rounded-xl shadow-sm border transition-all ${expandedTicketId === ticket.id ? 'ring-2 ring-ochre border-transparent' : 'border-gray-200'}`}>
116-
{/* Ticket Summary Header */}
117-
<div
118-
onClick={() => setExpandedTicketId(expandedTicketId === ticket.id ? null : ticket.id)}
119-
className="p-4 md:p-6 cursor-pointer hover:bg-gray-50 flex items-center justify-between"
120-
>
121-
<div className="flex items-start space-x-3 md:space-x-4 min-w-0 flex-1">
122-
<div className="w-10 h-10 md:w-12 md:h-12 bg-gray-100 rounded-xl overflow-hidden flex-none border border-gray-100 shadow-sm">
123-
{ticket.user.image ? (
124-
<img src={ticket.user.image} alt={ticket.user.name || ''} className="w-full h-full object-cover" />
125-
) : (
126-
<div className="w-full h-full flex items-center justify-center">
127-
<User className="w-5 h-5 text-gray-400" />
128-
</div>
129-
)}
130-
</div>
131-
<div className="min-w-0 flex-1">
132-
<div className="flex flex-wrap items-center gap-2">
133-
<h3 className="font-bold text-sm md:text-lg text-gray-900 truncate max-w-[150px] md:max-w-none">{ticket.subject}</h3>
134-
<span className={`px-2 py-0.5 rounded-md text-[8px] md:text-[10px] font-black uppercase tracking-widest ${ticket.status === 'OPEN' ? 'bg-orange-100 text-ochre' :
135-
ticket.status === 'ANSWERED' ? 'bg-blue-100 text-blue-700' : 'bg-gray-100 text-gray-600'
136-
}`}>
137-
{ticket.status}
138-
</span>
139-
</div>
140-
<div className="flex flex-wrap items-center text-[10px] md:text-sm text-gray-500 mt-1 gap-x-2 gap-y-1">
141-
<span className="font-bold text-gray-700 truncate max-w-[100px] md:max-w-none">{ticket.user.name || ticket.user.email}</span>
142-
<span className="hidden xs:inline text-gray-300"></span>
143-
<span className="flex items-center"><Clock className="w-3 h-3 mr-1 opacity-60" /> {new Date(ticket.createdAt).toLocaleDateString()}</span>
144-
<span className="hidden xs:inline text-gray-300"></span>
145-
<span className="bg-gray-100 px-1.5 py-0.5 rounded font-medium">{ticket.messages.length} msg</span>
115+
<div
116+
key={ticket.id}
117+
onClick={() => setExpandedTicketId(ticket.id)}
118+
className="bg-white p-4 rounded-2xl shadow-sm border border-gray-100 flex items-center justify-between gap-3 cursor-pointer hover:border-ochre/30 transition-all active:scale-[0.98]"
119+
>
120+
<div className="flex items-start gap-3 min-w-0 flex-1">
121+
<div className="w-10 h-10 bg-gray-50 rounded-xl overflow-hidden flex-none border border-gray-100">
122+
{ticket.user.image ? (
123+
<img src={ticket.user.image} alt={ticket.user.name || ''} className="w-full h-full object-cover" />
124+
) : (
125+
<div className="w-full h-full flex items-center justify-center">
126+
<User className="w-5 h-5 text-gray-300" />
146127
</div>
128+
)}
129+
</div>
130+
<div className="min-w-0 flex-1">
131+
<div className="flex items-center gap-2 mb-0.5">
132+
<h3 className="font-bold text-sm text-gray-900 truncate">{ticket.subject}</h3>
133+
<span className={`px-1.5 py-0.5 rounded text-[8px] font-black uppercase tracking-widest flex-none ${ticket.status === 'OPEN' ? 'bg-orange-100 text-ochre' :
134+
ticket.status === 'ANSWERED' ? 'bg-blue-100 text-blue-700' : 'bg-gray-100 text-gray-500'
135+
}`}>
136+
{ticket.status}
137+
</span>
138+
</div>
139+
<div className="flex items-center text-[10px] text-gray-400 font-bold uppercase tracking-tight gap-2">
140+
<span className="truncate">{ticket.user.name || 'Anonymous'}</span>
141+
<span className="opacity-30"></span>
142+
<span>{new Date(ticket.createdAt).toLocaleDateString()}</span>
147143
</div>
148144
</div>
149-
<div className="ml-4 flex-none">
150-
{expandedTicketId === ticket.id ? <ChevronUp className="w-5 h-5 text-gray-300" /> : <ChevronDown className="w-5 h-5 text-gray-300" />}
145+
</div>
146+
<div className="flex flex-col items-end gap-1 flex-none">
147+
<div className="bg-gray-50 px-2 py-0.5 rounded-md text-[9px] font-black text-gray-400 border border-gray-100">
148+
{ticket.messages.length} MSG
151149
</div>
152150
</div>
151+
</div>
152+
))
153+
)}
154+
</div>
153155

154-
{/* Expanded Content */}
155-
{expandedTicketId === ticket.id && (
156-
<div className="border-t border-gray-100 bg-gray-50 rounded-b-xl">
157-
{/* Message Thread */}
158-
<div className="p-4 md:p-6 space-y-4 max-h-[400px] overflow-y-auto no-scrollbar">
159-
{ticket.messages.map((msg, idx) => (
160-
<div key={idx} className={`flex ${msg.sender === 'USER' ? 'justify-start' : 'justify-end'}`}>
161-
<div className={`max-w-[90%] md:max-w-[85%] rounded-2xl p-3 md:p-4 shadow-sm ${msg.sender === 'USER'
162-
? 'bg-white border border-gray-100 text-gray-800 rounded-tl-none'
163-
: 'bg-ochre text-white rounded-tr-none'
164-
}`}>
165-
<p className="text-xs md:text-sm leading-relaxed font-medium">{msg.text}</p>
166-
<div className={`text-[9px] md:text-[10px] mt-2 font-black uppercase tracking-widest ${msg.sender === 'USER' ? 'text-gray-400' : 'text-orange-100/70'}`}>
167-
{new Date(msg.createdAt).toLocaleString()}
168-
</div>
169-
</div>
170-
</div>
171-
))}
156+
{/* Conversation Focused Modal */}
157+
<Modal
158+
isOpen={!!expandedTicketId}
159+
onClose={() => setExpandedTicketId(null)}
160+
title={tickets.find(t => t.id === expandedTicketId)?.subject || 'Inquiry'}
161+
>
162+
<div className="flex flex-col h-[60vh] -mx-6 -mt-4">
163+
{/* Thread Content */}
164+
<div className="flex-1 overflow-y-auto p-4 space-y-3 no-scrollbar bg-gray-50/50">
165+
{tickets.find(t => t.id === expandedTicketId)?.messages.map((msg, idx) => (
166+
<div key={idx} className={`flex ${msg.sender === 'USER' ? 'justify-start' : 'justify-end'}`}>
167+
<div className={`max-w-[90%] rounded-2xl px-3 py-2 shadow-sm ${msg.sender === 'USER'
168+
? 'bg-white border border-gray-100 text-gray-800 rounded-tl-none'
169+
: 'bg-ochre text-white rounded-tr-none shadow-ochre/20'
170+
}`}>
171+
<p className="text-xs font-medium leading-relaxed">{msg.text}</p>
172+
<div className={`text-[8px] mt-1 font-black uppercase tracking-widest ${msg.sender === 'USER' ? 'text-gray-300' : 'text-orange-100/70'
173+
}`}>
174+
{new Date(msg.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
172175
</div>
176+
</div>
177+
</div>
178+
))}
179+
</div>
173180

174-
{/* Action Area */}
175-
{ticket.status !== 'CLOSED' && (
176-
<div className="p-4 md:p-6 border-t border-gray-100 bg-white rounded-b-xl space-y-4">
177-
<div className="relative">
178-
<textarea
179-
value={replyText[ticket.id] || ''}
180-
onChange={(e) => setReplyText(prev => ({ ...prev, [ticket.id]: e.target.value }))}
181-
placeholder="Type your spiritual guidance here..."
182-
rows={3}
183-
className="w-full border border-gray-100 rounded-xl p-3 md:p-4 focus:ring-2 focus:ring-ochre/20 focus:border-ochre outline-none text-sm resize-none bg-gray-50/50"
184-
/>
185-
</div>
186-
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
187-
<button
188-
onClick={() => setConfirmCloseId(ticket.id)}
189-
className="w-full sm:w-auto text-gray-400 hover:text-red-500 text-[10px] md:text-xs font-bold uppercase tracking-widest flex items-center justify-center transition-all px-3 py-2 rounded-lg hover:bg-red-50"
190-
>
191-
<CheckCircle2 className="w-4 h-4 mr-2" /> Close Inquiry
192-
</button>
193-
<button
194-
disabled={isSubmitting[ticket.id] || !replyText[ticket.id]?.trim()}
195-
onClick={() => handleReply(ticket.id)}
196-
className="w-full sm:w-auto bg-ochre text-white px-8 py-3 rounded-xl font-black text-xs uppercase tracking-widest hover:bg-orange-700 transition-all disabled:opacity-50 flex items-center justify-center shadow-lg shadow-ochre/20 active:scale-95"
197-
>
198-
{isSubmitting[ticket.id] ? (
199-
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
200-
) : (
201-
<Send className="w-4 h-4 mr-2" />
202-
)}
203-
Send Guidance
204-
</button>
205-
</div>
206-
</div>
181+
{/* Footer / Reply Area */}
182+
{expandedTicketId && tickets.find(t => t.id === expandedTicketId)?.status !== 'CLOSED' && (
183+
<div className="p-4 bg-white border-t border-gray-100 space-y-3">
184+
<textarea
185+
value={replyText[expandedTicketId] || ''}
186+
onChange={(e) => setReplyText(prev => ({ ...prev, [expandedTicketId!]: e.target.value }))}
187+
placeholder="Type spiritual guidance..."
188+
rows={2}
189+
className="w-full border-none bg-gray-50 rounded-xl p-3 text-xs font-medium focus:ring-2 focus:ring-ochre/20 outline-none resize-none"
190+
/>
191+
<div className="flex items-center justify-between gap-3">
192+
<button
193+
onClick={() => setConfirmCloseId(expandedTicketId)}
194+
className="text-[9px] font-black text-gray-300 uppercase tracking-widest hover:text-red-500 transition-colors py-2"
195+
>
196+
Close Inquiry
197+
</button>
198+
<button
199+
disabled={isSubmitting[expandedTicketId] || !replyText[expandedTicketId]?.trim()}
200+
onClick={() => handleReply(expandedTicketId!)}
201+
className="bg-ochre text-white px-6 py-2.5 rounded-xl font-black text-[10px] uppercase tracking-widest hover:bg-orange-700 transition-all disabled:opacity-30 shadow-lg shadow-ochre/20 flex items-center"
202+
>
203+
{isSubmitting[expandedTicketId] ? (
204+
<Loader2 className="w-3 h-3 mr-2 animate-spin" />
205+
) : (
206+
<Send className="w-3 h-3 mr-2" />
207207
)}
208-
</div>
209-
)}
208+
Send
209+
</button>
210+
</div>
210211
</div>
211-
))
212-
)}
213-
</div>
212+
)}
213+
</div>
214+
</Modal>
214215

215216
{/* Premium UI Components */}
216217
<Modal

0 commit comments

Comments
 (0)