diff --git a/app/api/admin/support/tickets/[id]/reply/route.ts b/app/api/admin/support/tickets/[id]/reply/route.ts
new file mode 100644
index 00000000..e371e7bb
--- /dev/null
+++ b/app/api/admin/support/tickets/[id]/reply/route.ts
@@ -0,0 +1,213 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { createClient } from '@/lib/supabase/server'
+import { sendEmail } from '@/lib/email/support-emails'
+
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const { id } = await params
+
+ console.log('📧 Reply API called for ticket:', id)
+
+ try {
+ const supabase = await createClient()
+
+ // Check if user is admin
+ const { data: { user }, error: authError } = await supabase.auth.getUser()
+
+ if (authError || !user) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ const { data: profile } = await supabase
+ .from('profiles')
+ .select('is_admin, first_name, last_name')
+ .eq('id', user.id)
+ .single()
+
+ if (!profile?.is_admin) {
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
+ }
+
+ const { message } = await request.json()
+
+ if (!message || !message.trim()) {
+ return NextResponse.json({ error: 'Message is required' }, { status: 400 })
+ }
+
+ if (message.length > 2000) {
+ return NextResponse.json({ error: 'Message too long (max 2000 characters)' }, { status: 400 })
+ }
+
+ // Get ticket
+ const { data: ticket, error: ticketError } = await supabase
+ .from('support_tickets')
+ .select('*')
+ .eq('id', id)
+ .single()
+
+ if (ticketError) {
+ console.error('Error fetching ticket:', ticketError)
+ return NextResponse.json({ error: 'Ticket not found', details: ticketError.message }, { status: 404 })
+ }
+
+ if (!ticket) {
+ console.error('Ticket not found in database:', id)
+ return NextResponse.json({ error: 'Ticket not found' }, { status: 404 })
+ }
+
+ // Get user information
+ const { data: userProfile } = await supabase
+ .from('profiles')
+ .select('email, first_name, last_name')
+ .eq('id', ticket.user_id)
+ .single()
+
+ if (!userProfile?.email) {
+ console.error('User email not found for ticket:', id)
+ return NextResponse.json({ error: 'User email not found' }, { status: 400 })
+ }
+
+ console.log('✅ Ticket found:', { id: ticket.id, userEmail: userProfile.email })
+
+ // Prepare email
+ const userName = userProfile.first_name || userProfile.email.split('@')[0] || 'User'
+ const adminName = `${profile.first_name || ''} ${profile.last_name || ''}`.trim() || 'Support Team'
+
+ const emailHtml = getAdminReplyEmail({
+ userName,
+ adminName,
+ ticketId: ticket.id,
+ ticketSubject: ticket.subject,
+ replyMessage: message
+ })
+
+ // Send email to user
+ console.log('📧 Sending reply email to:', userProfile.email)
+
+ const emailResult = await sendEmail({
+ to: userProfile.email,
+ subject: `Re: [Ticket #${ticket.id.substring(0, 8)}] ${ticket.subject}`,
+ html: emailHtml
+ })
+
+ if (!emailResult.success) {
+ console.error('❌ Failed to send reply email:', emailResult.error)
+ return NextResponse.json({ error: 'Failed to send email', details: emailResult.error }, { status: 500 })
+ }
+
+ console.log('✅ Reply email sent successfully')
+
+ // TODO: Save reply to database (for reply history)
+ // This would go in a support_ticket_replies table
+
+ return NextResponse.json({
+ success: true,
+ message: 'Reply sent successfully'
+ })
+ } catch (error) {
+ console.error('Error in reply API:', error)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
+
+// Email template for admin reply
+function getAdminReplyEmail(params: {
+ userName: string
+ adminName: string
+ ticketId: string
+ ticketSubject: string
+ replyMessage: string
+}) {
+ const content = `
+
+ Response to your support ticket
+
+
+
+ Hi ${params.userName},
+
+
+
+ ${params.adminName} from our support team has responded to your ticket:
+
+
+
+
+ Ticket ID: ${params.ticketId}
+
+
+ ${params.ticketSubject}
+
+
+
+
+
+ ${params.adminName} replied:
+
+
+ ${params.replyMessage}
+
+
+
+
+ If you have any follow-up questions, please reply to this email or create a new ticket.
+
+
+
+ View Help Center
+
+ `
+
+ return getEmailTemplate(content)
+}
+
+// Base email template
+function getEmailTemplate(content: string) {
+ return `
+
+
+
+
+
+ Codeunia Support
+
+
+
+
+
+
+
+
+
+ Codeunia Support
+ |
+
+
+
+
+ |
+ ${content}
+ |
+
+
+
+
+ |
+
+ Need help? Reply to this email or visit our Help Center
+
+
+ © ${new Date().getFullYear()} Codeunia. All rights reserved.
+
+ |
+
+
+ |
+
+
+
+
+`
+}
diff --git a/app/api/admin/support/tickets/[id]/route.ts b/app/api/admin/support/tickets/[id]/route.ts
index fc870151..24d23624 100644
--- a/app/api/admin/support/tickets/[id]/route.ts
+++ b/app/api/admin/support/tickets/[id]/route.ts
@@ -1,12 +1,13 @@
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
-import { getStatusUpdateEmail, sendEmail } from '@/lib/email/support-emails'
+// GET single ticket
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
+
try {
const supabase = await createClient()
@@ -27,7 +28,7 @@ export async function GET(
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
- // Fetch ticket
+ // Get ticket
const { data: ticket, error } = await supabase
.from('support_tickets')
.select('*')
@@ -39,13 +40,14 @@ export async function GET(
return NextResponse.json({ error: 'Ticket not found' }, { status: 404 })
}
- // Fetch user information
+ // Get user profile
const { data: userProfile } = await supabase
.from('profiles')
.select('id, email, first_name, last_name, avatar_url')
.eq('id', ticket.user_id)
.single()
+ // Combine ticket with user data
const ticketWithUser = {
...ticket,
user: userProfile || null
@@ -53,16 +55,18 @@ export async function GET(
return NextResponse.json({ ticket: ticketWithUser })
} catch (error) {
- console.error('Error in ticket detail API:', error)
+ console.error('Error in GET ticket:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
+// PATCH update ticket status
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
+
try {
const supabase = await createClient()
@@ -85,27 +89,22 @@ export async function PATCH(
const { status } = await request.json()
- if (!['open', 'in_progress', 'resolved', 'closed'].includes(status)) {
- return NextResponse.json({ error: 'Invalid status' }, { status: 400 })
+ if (!status) {
+ return NextResponse.json({ error: 'Status is required' }, { status: 400 })
}
- // Get current ticket to check old status
- const { data: currentTicket } = await supabase
- .from('support_tickets')
- .select('*, user:profiles!user_id(email, first_name, last_name)')
- .eq('id', id)
- .single()
-
- if (!currentTicket) {
- return NextResponse.json({ error: 'Ticket not found' }, { status: 404 })
+ const validStatuses = ['open', 'in_progress', 'resolved', 'closed']
+ if (!validStatuses.includes(status)) {
+ return NextResponse.json({ error: 'Invalid status' }, { status: 400 })
}
- const oldStatus = currentTicket.status
-
// Update ticket status
const { data: ticket, error } = await supabase
.from('support_tickets')
- .update({ status })
+ .update({
+ status,
+ updated_at: new Date().toISOString()
+ })
.eq('id', id)
.select()
.single()
@@ -115,29 +114,9 @@ export async function PATCH(
return NextResponse.json({ error: 'Failed to update ticket' }, { status: 500 })
}
- // Send status update email to user (only if status actually changed)
- if (oldStatus !== status && currentTicket.user) {
- const userName = currentTicket.user.first_name || currentTicket.user.email?.split('@')[0] || 'User'
- const userEmail = currentTicket.user.email || ''
-
- const statusEmail = getStatusUpdateEmail({
- userName,
- ticketId: ticket.id,
- subject: ticket.subject,
- oldStatus,
- newStatus: status
- })
-
- await sendEmail({
- to: userEmail,
- subject: statusEmail.subject,
- html: statusEmail.html
- })
- }
-
- return NextResponse.json({ ticket })
+ return NextResponse.json({ ticket, success: true })
} catch (error) {
- console.error('Error in ticket update API:', error)
+ console.error('Error in PATCH ticket:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}