Localize wallet and ticket pages.
Move the wallet page and all four ticket views (user list, ticket detail, staff queue, admin overview) onto the i18n dictionaries — statuses, priorities, departments, forms, stats, toasts and empty states — using locale-aware Link/router and locale-aware date formatting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import Link from 'next/link';
|
||||
import { Link } from '@/i18n/Link';
|
||||
import { useT, useLocale } from '@/i18n/I18nProvider';
|
||||
import api from '@/lib/api';
|
||||
import { useAuthStore } from '@/lib/store';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -23,6 +24,9 @@ const priorityColors: Record<string, string> = {
|
||||
};
|
||||
|
||||
export default function TicketsPage() {
|
||||
const t = useT();
|
||||
const tk = t.dashboard.tickets;
|
||||
const locale = useLocale();
|
||||
const queryClient = useQueryClient();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
@@ -30,10 +34,10 @@ export default function TicketsPage() {
|
||||
// Staff can only create tickets to other departments
|
||||
const availableDepartments: { value: TicketDepartment; label: string }[] = [];
|
||||
if (user?.role !== 'technical') {
|
||||
availableDepartments.push({ value: 'technical', label: 'Technical Support' });
|
||||
availableDepartments.push({ value: 'technical', label: tk.departmentFull.technical });
|
||||
}
|
||||
if (user?.role !== 'sales') {
|
||||
availableDepartments.push({ value: 'sales', label: 'Sales' });
|
||||
availableDepartments.push({ value: 'sales', label: tk.departmentFull.sales });
|
||||
}
|
||||
const canCreateTicket = availableDepartments.length > 0;
|
||||
const [form, setForm] = useState<CreateTicketDto>({
|
||||
@@ -51,12 +55,12 @@ export default function TicketsPage() {
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: CreateTicketDto) => api.post('/tickets', data).then((r) => r.data),
|
||||
onSuccess: () => {
|
||||
toast.success('Ticket created successfully');
|
||||
toast.success(tk.createdSuccess);
|
||||
queryClient.invalidateQueries({ queryKey: ['my-tickets'] });
|
||||
setShowCreate(false);
|
||||
setForm({ subject: '', department: availableDepartments[0]?.value || 'technical', priority: 'medium', message: '' });
|
||||
},
|
||||
onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to create ticket'),
|
||||
onError: (err: any) => toast.error(err.response?.data?.message || tk.createFailed),
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -68,12 +72,12 @@ export default function TicketsPage() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">My Tickets</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Support tickets and their status</p>
|
||||
<h1 className="text-2xl font-bold text-gray-900">{tk.myTickets}</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">{tk.myTicketsSubtitle}</p>
|
||||
</div>
|
||||
{canCreateTicket && (
|
||||
<button onClick={() => setShowCreate(!showCreate)} className="btn-primary">
|
||||
{showCreate ? <><X className="w-4 h-4 inline" /> Cancel</> : '+ New Ticket'}
|
||||
{showCreate ? <><X className="w-4 h-4 inline" /> {t.common.cancel}</> : `+ ${tk.newTicket}`}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -81,14 +85,14 @@ export default function TicketsPage() {
|
||||
{/* Create Ticket Form */}
|
||||
{showCreate && canCreateTicket && (
|
||||
<form onSubmit={handleSubmit} className="card p-6 space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Create New Ticket</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">{tk.createNewTicket}</h2>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Subject</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{tk.subject}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input-field"
|
||||
placeholder="Brief description of your issue"
|
||||
placeholder={tk.subjectPlaceholder}
|
||||
value={form.subject}
|
||||
onChange={(e) => setForm({ ...form, subject: e.target.value })}
|
||||
required
|
||||
@@ -98,7 +102,7 @@ export default function TicketsPage() {
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Department</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{tk.departmentField}</label>
|
||||
<select
|
||||
className="input-field"
|
||||
value={form.department}
|
||||
@@ -110,24 +114,24 @@ export default function TicketsPage() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Priority</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{tk.priorityField}</label>
|
||||
<select
|
||||
className="input-field"
|
||||
value={form.priority}
|
||||
onChange={(e) => setForm({ ...form, priority: e.target.value as TicketPriority })}
|
||||
>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="low">{tk.priority.low}</option>
|
||||
<option value="medium">{tk.priority.medium}</option>
|
||||
<option value="high">{tk.priority.high}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Message</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{tk.messageField}</label>
|
||||
<textarea
|
||||
className="input-field min-h-[120px]"
|
||||
placeholder="Describe your issue in detail..."
|
||||
placeholder={tk.messagePlaceholder}
|
||||
value={form.message}
|
||||
onChange={(e) => setForm({ ...form, message: e.target.value })}
|
||||
required
|
||||
@@ -137,7 +141,7 @@ export default function TicketsPage() {
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button type="submit" className="btn-primary" disabled={createMutation.isPending}>
|
||||
{createMutation.isPending ? 'Creating...' : 'Submit Ticket'}
|
||||
{createMutation.isPending ? tk.creating : tk.submitTicket}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -151,8 +155,8 @@ export default function TicketsPage() {
|
||||
) : tickets.length === 0 ? (
|
||||
<div className="card p-12 text-center">
|
||||
<TicketIcon className="w-10 h-10 mx-auto text-gray-300" />
|
||||
<h3 className="mt-3 text-lg font-semibold text-gray-700">No tickets yet</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">Create a ticket if you need help</p>
|
||||
<h3 className="mt-3 text-lg font-semibold text-gray-700">{tk.noTickets}</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">{tk.noTicketsHint}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
@@ -167,21 +171,21 @@ export default function TicketsPage() {
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<h3 className="font-semibold text-gray-900 truncate">{ticket.subject}</h3>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${statusColors[ticket.status]}`}>
|
||||
{ticket.status}
|
||||
{(tk.status as Record<string, string>)[ticket.status] ?? ticket.status}
|
||||
</span>
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${priorityColors[ticket.priority]}`}>
|
||||
{ticket.priority}
|
||||
{(tk.priority as Record<string, string>)[ticket.priority] ?? ticket.priority}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-1.5 text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1">{ticket.department === 'technical' ? <><Wrench className="w-3 h-3" /> Technical</> : <><Briefcase className="w-3 h-3" /> Sales</>}</span>
|
||||
<span className="flex items-center gap-1">{ticket.department === 'technical' ? <><Wrench className="w-3 h-3" /> {tk.department.technical}</> : <><Briefcase className="w-3 h-3" /> {tk.department.sales}</>}</span>
|
||||
<span>•</span>
|
||||
<span>{new Date(ticket.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
|
||||
<span>{new Date(ticket.createdAt).toLocaleDateString(locale, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
|
||||
<span>•</span>
|
||||
<span>{ticket.messages?.length || 0} message{(ticket.messages?.length || 0) !== 1 ? 's' : ''}</span>
|
||||
<span>{tk.messages.replace('{n}', String(ticket.messages?.length || 0))}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-gray-400 text-sm">→</span>
|
||||
<span className="text-gray-400 text-sm rtl:rotate-180">→</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user