feat: improve tickets dialog, navbar buttons, hero section, and fix button bleeding
This commit is contained in:
parent
59cd1fa01a
commit
9f5725bf01
14 changed files with 216 additions and 37 deletions
BIN
frontend/build_errors.txt
Normal file
BIN
frontend/build_errors.txt
Normal file
Binary file not shown.
83
frontend/fix-lines.js
Normal file
83
frontend/fix-lines.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
const fs = require('fs');
|
||||
|
||||
let lines = fs.readFileSync('c:/dev/gohorsejobs/frontend/src/app/dashboard/backoffice/page.tsx', 'utf8').split('\n');
|
||||
|
||||
// 1. Add import ConfirmModal near lucide-react (line 45)
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes('import { Archive, CheckCircle, Copy, ExternalLink, PauseCircle, Plus, RefreshCw, XCircle } from "lucide-react"')) {
|
||||
lines.splice(i + 1, 0, 'import { ConfirmModal } from "@/components/confirm-modal"');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add state deletePlanId near editingPlanId (around line 96)
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes('const [editingPlanId, setEditingPlanId] = useState<string | null>(null)')) {
|
||||
lines.splice(i + 1, 0, ' const [deletePlanId, setDeletePlanId] = useState<string | null>(null)');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Replace handleDeletePlan block
|
||||
let startIdx = -1;
|
||||
let endIdx = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes('const handleDeletePlan = async (id: string) => {')) {
|
||||
startIdx = i;
|
||||
}
|
||||
if (startIdx !== -1 && i > startIdx && lines[i].includes('} catch (error) {')) {
|
||||
// found end of try block
|
||||
}
|
||||
if (startIdx !== -1 && i > startIdx && lines[i].trim() === '}') {
|
||||
endIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIdx !== -1 && endIdx !== -1) {
|
||||
const newFunc = [
|
||||
' const handleDeletePlan = async (id: string) => {',
|
||||
' setDeletePlanId(id)',
|
||||
' }',
|
||||
'',
|
||||
' const confirmDeletePlan = async () => {',
|
||||
' if (!deletePlanId) return',
|
||||
' try {',
|
||||
' await plansApi.delete(deletePlanId)',
|
||||
' toast.success("Plan deleted")',
|
||||
' loadBackoffice(true)',
|
||||
' } catch (error) {',
|
||||
' toast.error("Failed to delete plan")',
|
||||
' } finally {',
|
||||
' setDeletePlanId(null)',
|
||||
' }',
|
||||
' }'
|
||||
].map(l => l + (lines[0].endsWith('\\r') ? '\\r' : ''));
|
||||
|
||||
lines.splice(startIdx, endIdx - startIdx + 1, ...newFunc);
|
||||
}
|
||||
|
||||
// 4. Add ConfirmModal before last closing </div>
|
||||
let lastDivIdx = -1;
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
if (lines[i].includes('</div>')) {
|
||||
lastDivIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastDivIdx !== -1) {
|
||||
const modal = [
|
||||
' <ConfirmModal',
|
||||
' isOpen={!!deletePlanId}',
|
||||
' onClose={() => setDeletePlanId(null)}',
|
||||
' onConfirm={confirmDeletePlan}',
|
||||
' title="Are you sure you want to delete this plan?"',
|
||||
' description="This action cannot be undone."',
|
||||
' />'
|
||||
].map(l => l + (lines[0].endsWith('\\r') ? '\\r' : ''));
|
||||
lines.splice(lastDivIdx, 0, ...modal);
|
||||
}
|
||||
|
||||
fs.writeFileSync('c:/dev/gohorsejobs/frontend/src/app/dashboard/backoffice/page.tsx', lines.join('\n'));
|
||||
console.log('done lines edit');
|
||||
56
frontend/fix.js
Normal file
56
frontend/fix.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
const fs = require('fs');
|
||||
|
||||
let content = fs.readFileSync('c:/dev/gohorsejobs/frontend/src/app/dashboard/backoffice/page.tsx', 'utf8');
|
||||
|
||||
content = content.replace(
|
||||
'import { Archive, CheckCircle, Copy, ExternalLink, PauseCircle, Plus, RefreshCw, XCircle } from "lucide-react"',
|
||||
'import { Archive, CheckCircle, Copy, ExternalLink, PauseCircle, Plus, RefreshCw, XCircle } from "lucide-react"\r\nimport { ConfirmModal } from "@/components/confirm-modal"'
|
||||
);
|
||||
|
||||
content = content.replace(
|
||||
'const [editingPlanId, setEditingPlanId] = useState<string | null>(null)',
|
||||
'const [editingPlanId, setEditingPlanId] = useState<string | null>(null)\r\n const [deletePlanId, setDeletePlanId] = useState<string | null>(null)'
|
||||
);
|
||||
|
||||
const oldFunction = ` const handleDeletePlan = async (id: string) => {\r
|
||||
if (!confirm("Delete this plan?")) return\r
|
||||
try {\r
|
||||
await plansApi.delete(id)\r
|
||||
toast.success("Plan deleted")\r
|
||||
loadBackoffice(true)\r
|
||||
} catch (error) {\r
|
||||
toast.error("Failed to delete plan")\r
|
||||
}\r
|
||||
}`;
|
||||
|
||||
const newFunction = ` const handleDeletePlan = async (id: string) => {\r
|
||||
setDeletePlanId(id)\r
|
||||
}\r
|
||||
\r
|
||||
const confirmDeletePlan = async () => {\r
|
||||
if (!deletePlanId) return\r
|
||||
try {\r
|
||||
await plansApi.delete(deletePlanId)\r
|
||||
toast.success("Plan deleted")\r
|
||||
loadBackoffice(true)\r
|
||||
} catch (error) {\r
|
||||
toast.error("Failed to delete plan")\r
|
||||
} finally {\r
|
||||
setDeletePlanId(null)\r
|
||||
}\r
|
||||
}`;
|
||||
|
||||
content = content.replace(new RegExp(oldFunction.replace(/[.*+?^$\\{\\}()|[\\]\\\\]/g, '\\\\$&').replace(/\\r\\n/g, '\\r?\\n'), 'g'), newFunction);
|
||||
|
||||
content = content.replace(
|
||||
' </TabsContent>\r\n </Tabs>\r\n </div>\r\n )\r\n}',
|
||||
' </TabsContent>\r\n </Tabs>\r\n <ConfirmModal\r\n isOpen={!!deletePlanId}\r\n onClose={() => setDeletePlanId(null)}\r\n onConfirm={confirmDeletePlan}\r\n title="Are you sure you want to delete this plan?"\r\n description="This action cannot be undone."\r\n />\r\n </div>\r\n )\r\n}'
|
||||
);
|
||||
|
||||
content = content.replace(
|
||||
' </TabsContent>\n </Tabs>\n </div>\n )\n}',
|
||||
' </TabsContent>\n </Tabs>\n <ConfirmModal\n isOpen={!!deletePlanId}\n onClose={() => setDeletePlanId(null)}\n onConfirm={confirmDeletePlan}\n title="Are you sure you want to delete this plan?"\n description="This action cannot be undone."\n />\n </div>\n )\n}'
|
||||
);
|
||||
|
||||
fs.writeFileSync('c:/dev/gohorsejobs/frontend/src/app/dashboard/backoffice/page.tsx', content);
|
||||
console.log('done');
|
||||
|
|
@ -322,14 +322,24 @@ export default function AdminTicketsPage() {
|
|||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="bug">Bug</SelectItem>
|
||||
<SelectItem value="feature">Feature Request</SelectItem>
|
||||
<SelectItem value="support">Support</SelectItem>
|
||||
<SelectItem value="billing">Billing</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
<SelectItem value="bug">{t("ticketsPage.category.bug")}</SelectItem>
|
||||
<SelectItem value="feature">{t("ticketsPage.category.feature")}</SelectItem>
|
||||
<SelectItem value="support">{t("ticketsPage.category.support")}</SelectItem>
|
||||
<SelectItem value="billing">{t("ticketsPage.category.billing")}</SelectItem>
|
||||
<SelectItem value="other">{t("ticketsPage.category.other")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="message">{t("ticketsPage.createDialog.message")}</Label>
|
||||
<Textarea
|
||||
id="message"
|
||||
value={newTicket.message}
|
||||
onChange={(e) => setNewTicket(prev => ({ ...prev, message: e.target.value }))}
|
||||
placeholder={t("ticketsPage.createDialog.messagePlaceholder")}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="priority">{t("ticketsPage.createDialog.priority")}</Label>
|
||||
<Select
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ function JobsContent() {
|
|||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
|
|
|
|||
|
|
@ -134,17 +134,6 @@ export default function Home() {
|
|||
{t("home.hero.subtitle")}
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Link href="/jobs">
|
||||
<Button className="h-12 px-8 bg-orange-500 hover:bg-orange-600 text-white font-bold text-lg rounded-lg shadow-lg transition-transform hover:scale-105">
|
||||
{t("home.hero.cta")}
|
||||
</Button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -246,11 +246,11 @@ export function JobCard({ job, isApplied, applicationStatus }: JobCardProps) {
|
|||
|
||||
<CardFooter className="pt-4 border-t mt-auto">
|
||||
<div className="flex gap-2 w-full">
|
||||
<Link href={`/jobs/${job.id}`} className="flex-1">
|
||||
<Button variant="outline" className="w-full cursor-pointer rounded-lg">
|
||||
<Button variant="outline" className="flex-1 cursor-pointer rounded-lg" asChild>
|
||||
<Link href={`/jobs/${job.id}`}>
|
||||
{t('jobs.card.viewDetails')}
|
||||
</Button>
|
||||
</Link>
|
||||
</Link>
|
||||
</Button>
|
||||
{isApplied ? (
|
||||
<Button className="flex-1 w-full cursor-default bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg" variant="secondary">
|
||||
{applicationStatus === 'pending' ? t('jobs.card.applied') :
|
||||
|
|
@ -259,9 +259,11 @@ export function JobCard({ job, isApplied, applicationStatus }: JobCardProps) {
|
|||
t('jobs.card.applied')}
|
||||
</Button>
|
||||
) : (
|
||||
<Link href={`/jobs/${job.id}/apply`} className="flex-1">
|
||||
<Button className="w-full cursor-pointer rounded-lg">{t('jobs.card.apply')}</Button>
|
||||
</Link>
|
||||
<Button className="flex-1 cursor-pointer rounded-lg" asChild>
|
||||
<Link href={`/jobs/${job.id}/apply`}>
|
||||
{t('jobs.card.apply')}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardFooter>
|
||||
|
|
|
|||
|
|
@ -66,21 +66,21 @@ export function Navbar() {
|
|||
<div className="hidden md:flex items-center gap-3">
|
||||
<LanguageSwitcher className="text-white/90 hover:text-white hover:bg-white/10" />
|
||||
{user ? (
|
||||
<Link href="/dashboard">
|
||||
<Button variant="ghost" className="gap-2 text-white hover:text-primary transition-colors">
|
||||
<Button variant="ghost" className="gap-2 text-white hover:text-primary transition-colors" asChild>
|
||||
<Link href="/dashboard">
|
||||
<User className="w-4 h-4" />
|
||||
Dashboard
|
||||
</Button>
|
||||
</Link>
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Link href="/login">
|
||||
<Button variant="outline" className="border-primary text-primary hover:bg-primary/10 px-6 h-9 font-normal">
|
||||
<Button variant="outline" className="bg-white hover:bg-gray-50 border-primary text-primary px-6 h-9 font-normal transition-all">
|
||||
{loginLabel}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/register">
|
||||
<Button className="bg-primary hover:bg-primary/90 text-white px-6 h-9 font-normal">
|
||||
<Button className="bg-primary hover:bg-primary hover:brightness-110 text-white px-6 h-9 font-normal transition-all">
|
||||
{registerLabel}
|
||||
</Button>
|
||||
</Link>
|
||||
|
|
@ -115,12 +115,12 @@ export function Navbar() {
|
|||
|
||||
<div className="flex flex-col gap-2 mt-4 pt-4 border-t">
|
||||
{user ? (
|
||||
<Link href="/dashboard" onClick={() => setIsOpen(false)}>
|
||||
<Button variant="ghost" className="w-full justify-start gap-2">
|
||||
<Button variant="ghost" className="w-full justify-start gap-2" asChild>
|
||||
<Link href="/dashboard" onClick={() => setIsOpen(false)}>
|
||||
<User className="w-4 h-4" />
|
||||
Dashboard
|
||||
</Button>
|
||||
</Link>
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Link href="/login" onClick={() => setIsOpen(false)}>
|
||||
|
|
@ -130,7 +130,7 @@ export function Navbar() {
|
|||
</Button>
|
||||
</Link>
|
||||
<Link href="/register" onClick={() => setIsOpen(false)}>
|
||||
<Button className="w-full justify-start gap-2">
|
||||
<Button className="w-full justify-start gap-2 bg-primary hover:bg-primary hover:brightness-110 text-white transition-all">
|
||||
<User className="w-4 h-4" />
|
||||
{registerLabel}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1321,11 +1321,21 @@
|
|||
"description": "Fill in the details to create a new support ticket.",
|
||||
"subject": "Subject",
|
||||
"subjectPlaceholder": "Describe your issue briefly",
|
||||
"category": "Category",
|
||||
"message": "Message",
|
||||
"messagePlaceholder": "Describe your request in detail",
|
||||
"priority": "Priority",
|
||||
"cancel": "Cancel",
|
||||
"create": "Create Ticket",
|
||||
"creating": "Creating..."
|
||||
},
|
||||
"category": {
|
||||
"bug": "Bug",
|
||||
"feature": "Feature Request",
|
||||
"support": "Support",
|
||||
"billing": "Billing",
|
||||
"other": "Other"
|
||||
},
|
||||
"messages": {
|
||||
"created": "Ticket created successfully",
|
||||
"deleted": "Ticket deleted successfully"
|
||||
|
|
@ -1385,4 +1395,4 @@
|
|||
},
|
||||
"copyright": "GoHorse Jobs. All rights reserved."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1322,11 +1322,21 @@
|
|||
"description": "Completa los detalles para crear un nuevo ticket de soporte.",
|
||||
"subject": "Asunto",
|
||||
"subjectPlaceholder": "Describe brevemente tu problema",
|
||||
"category": "Categoría",
|
||||
"message": "Mensaje",
|
||||
"messagePlaceholder": "Describe detalladamente tu solicitud",
|
||||
"priority": "Prioridad",
|
||||
"cancel": "Cancelar",
|
||||
"create": "Crear Ticket",
|
||||
"creating": "Creando..."
|
||||
},
|
||||
"category": {
|
||||
"bug": "Error (Bug)",
|
||||
"feature": "Nueva Funcionalidad",
|
||||
"support": "Soporte",
|
||||
"billing": "Facturación",
|
||||
"other": "Otro"
|
||||
},
|
||||
"messages": {
|
||||
"created": "Ticket creado exitosamente",
|
||||
"deleted": "Ticket eliminado exitosamente"
|
||||
|
|
@ -1386,4 +1396,4 @@
|
|||
},
|
||||
"copyright": "GoHorse Jobs. Todos los derechos reservados."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1330,11 +1330,21 @@
|
|||
"description": "Preencha os detalhes para criar um novo ticket de suporte.",
|
||||
"subject": "Assunto",
|
||||
"subjectPlaceholder": "Descreva brevemente seu problema",
|
||||
"category": "Categoria",
|
||||
"message": "Mensagem",
|
||||
"messagePlaceholder": "Descreva detalhadamente a sua solicitação",
|
||||
"priority": "Prioridade",
|
||||
"cancel": "Cancelar",
|
||||
"create": "Criar Ticket",
|
||||
"creating": "Criando..."
|
||||
},
|
||||
"category": {
|
||||
"bug": "Erro (Bug)",
|
||||
"feature": "Nova Funcionalidade",
|
||||
"support": "Suporte",
|
||||
"billing": "Faturamento",
|
||||
"other": "Outro"
|
||||
},
|
||||
"messages": {
|
||||
"created": "Ticket criado com sucesso",
|
||||
"deleted": "Ticket excluído com sucesso"
|
||||
|
|
@ -1394,4 +1404,4 @@
|
|||
},
|
||||
"copyright": "GoHorse Jobs. Todos os direitos reservados."
|
||||
}
|
||||
}
|
||||
}
|
||||
1
frontend/tsc2.txt
Normal file
1
frontend/tsc2.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
src/app/dashboard/backoffice/page.tsx(557,1): error TS1128: Declaration or statement expected.
|
||||
6
frontend/tsc3.txt
Normal file
6
frontend/tsc3.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
src/app/dashboard/backoffice/page.tsx(46,10): error TS2300: Duplicate identifier 'ConfirmModal'.
|
||||
src/app/dashboard/backoffice/page.tsx(47,10): error TS2300: Duplicate identifier 'ConfirmModal'.
|
||||
src/app/dashboard/backoffice/page.tsx(99,12): error TS2451: Cannot redeclare block-scoped variable 'deletePlanId'.
|
||||
src/app/dashboard/backoffice/page.tsx(99,26): error TS2451: Cannot redeclare block-scoped variable 'setDeletePlanId'.
|
||||
src/app/dashboard/backoffice/page.tsx(100,12): error TS2451: Cannot redeclare block-scoped variable 'deletePlanId'.
|
||||
src/app/dashboard/backoffice/page.tsx(100,26): error TS2451: Cannot redeclare block-scoped variable 'setDeletePlanId'.
|
||||
1
frontend/tsc_errors.txt
Normal file
1
frontend/tsc_errors.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
src/app/dashboard/backoffice/page.tsx(564,1): error TS1128: Declaration or statement expected.
|
||||
Loading…
Reference in a new issue