From a59a9a90713c68d71cbf1a03792e08e8a74f7aef Mon Sep 17 00:00:00 2001 From: Erik Silva Date: Mon, 1 Dec 2025 01:56:58 -0300 Subject: [PATCH] feat(frontend): Complete authentication and dashboard pages with task management --- frontend-next/app/(auth)/layout.tsx | 22 ++ frontend-next/app/(auth)/login/page.tsx | 119 +++++++ frontend-next/app/(auth)/signup/page.tsx | 151 +++++++++ frontend-next/app/(dashboard)/layout.tsx | 26 ++ frontend-next/app/(dashboard)/tasks/page.tsx | 322 +++++++++++++++++++ frontend-next/app/page.tsx | 77 ++--- 6 files changed, 658 insertions(+), 59 deletions(-) create mode 100644 frontend-next/app/(auth)/layout.tsx create mode 100644 frontend-next/app/(auth)/login/page.tsx create mode 100644 frontend-next/app/(auth)/signup/page.tsx create mode 100644 frontend-next/app/(dashboard)/layout.tsx create mode 100644 frontend-next/app/(dashboard)/tasks/page.tsx diff --git a/frontend-next/app/(auth)/layout.tsx b/frontend-next/app/(auth)/layout.tsx new file mode 100644 index 0000000..d493598 --- /dev/null +++ b/frontend-next/app/(auth)/layout.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/lib/stores'; + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + const router = useRouter(); + const { user } = useAuth(); + + useEffect(() => { + if (user) { + router.push('/dashboard/tasks'); + } + }, [user, router]); + + return <>{children}; +} diff --git a/frontend-next/app/(auth)/login/page.tsx b/frontend-next/app/(auth)/login/page.tsx new file mode 100644 index 0000000..093de48 --- /dev/null +++ b/frontend-next/app/(auth)/login/page.tsx @@ -0,0 +1,119 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/lib/stores'; +import { Button, Input, Card, CardContent, CardHeader, CardTitle } from '@/components'; + +export default function LoginPage() { + const router = useRouter(); + const { login, isLoading, error } = useAuth(); + + const [formData, setFormData] = useState({ + email: '', + password: '', + }); + const [validationErrors, setValidationErrors] = useState<{ + email: string; + password: string; + }>({ + email: '', + password: '', + }); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + setValidationErrors((prev) => ({ ...prev, [name]: '' })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setValidationErrors({ email: '', password: '' }); + + // Validação básica + const errors: { email: string; password: string } = { email: '', password: '' }; + if (!formData.email) errors.email = 'Email é obrigatório'; + if (!formData.password) errors.password = 'Senha é obrigatória'; + + if (Object.keys(errors).length > 0) { + setValidationErrors(errors); + return; + } + + try { + await login(formData); + router.push('/dashboard/tasks'); + } catch (err) { + // Erro já está no store + console.error('Login error:', err); + } + }; + + return ( +
+ + + 📋 Task Manager +

+ Faça login para gerenciar suas tarefas +

+
+ + +
+ + + + + {error && ( +
+ {error} +
+ )} + + +
+ +
+

+ Não tem conta?{' '} + + Criar conta + +

+
+
+
+
+ ); +} diff --git a/frontend-next/app/(auth)/signup/page.tsx b/frontend-next/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..33c23c5 --- /dev/null +++ b/frontend-next/app/(auth)/signup/page.tsx @@ -0,0 +1,151 @@ +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/lib/stores'; +import { Button, Input, Card, CardContent, CardHeader, CardTitle } from '@/components'; + +export default function SignupPage() { + const router = useRouter(); + const { signup, isLoading, error } = useAuth(); + + const [formData, setFormData] = useState({ + email: '', + password: '', + confirmPassword: '', + }); + const [validationErrors, setValidationErrors] = useState<{ + email: string; + password: string; + confirmPassword: string; + }>({ + email: '', + password: '', + confirmPassword: '', + }); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + setValidationErrors((prev) => ({ ...prev, [name]: '' })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setValidationErrors({ email: '', password: '', confirmPassword: '' }); + + // Validação + const errors: typeof validationErrors = { email: '', password: '', confirmPassword: '' }; + + if (!formData.email) { + errors.email = 'Email é obrigatório'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + errors.email = 'Email inválido'; + } + + if (!formData.password) { + errors.password = 'Senha é obrigatória'; + } else if (formData.password.length < 6) { + errors.password = 'Senha deve ter no mínimo 6 caracteres'; + } + + if (!formData.confirmPassword) { + errors.confirmPassword = 'Confirmação de senha é obrigatória'; + } else if (formData.password !== formData.confirmPassword) { + errors.confirmPassword = 'Senhas não correspondem'; + } + + if (Object.values(errors).some((e) => e)) { + setValidationErrors(errors); + return; + } + + try { + await signup({ + email: formData.email, + password: formData.password, + }); + router.push('/dashboard/tasks'); + } catch (err) { + console.error('Signup error:', err); + } + }; + + return ( +
+ + + 📋 Task Manager +

+ Crie sua conta para começar +

+
+ + +
+ + + + + + + {error && ( +
+ {error} +
+ )} + + +
+ +
+

+ Já tem conta?{' '} + + Faça login + +

+
+
+
+
+ ); +} diff --git a/frontend-next/app/(dashboard)/layout.tsx b/frontend-next/app/(dashboard)/layout.tsx new file mode 100644 index 0000000..9486ca3 --- /dev/null +++ b/frontend-next/app/(dashboard)/layout.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/lib/stores'; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + const router = useRouter(); + const { user } = useAuth(); + + useEffect(() => { + if (!user) { + router.push('/auth/login'); + } + }, [user, router]); + + if (!user) { + return null; + } + + return <>{children}; +} diff --git a/frontend-next/app/(dashboard)/tasks/page.tsx b/frontend-next/app/(dashboard)/tasks/page.tsx new file mode 100644 index 0000000..9272a53 --- /dev/null +++ b/frontend-next/app/(dashboard)/tasks/page.tsx @@ -0,0 +1,322 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth, useTasks } from '@/lib/stores'; +import { Button, Card, CardContent, CardHeader, CardTitle, Checkbox, Input } from '@/components'; +import { Trash2, Edit2, Plus, LogOut } from 'lucide-react'; + +export default function DashboardPage() { + const router = useRouter(); + const { user, logout } = useAuth(); + const { tasks, filters, setFilters, fetchTasks, createTask, updateTask, deleteTask, isLoading } = useTasks(); + + const [showCreateForm, setShowCreateForm] = useState(false); + const [newTask, setNewTask] = useState({ + title: '', + description: '', + dueDate: '', + }); + + useEffect(() => { + if (!user) { + router.push('/auth/login'); + return; + } + fetchTasks(); + }, [user, router, fetchTasks]); + + const handleCreateTask = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newTask.title.trim()) return; + + try { + await createTask({ + title: newTask.title, + description: newTask.description, + dueDate: newTask.dueDate ? new Date(newTask.dueDate).toISOString() : undefined, + }); + setNewTask({ title: '', description: '', dueDate: '' }); + setShowCreateForm(false); + fetchTasks(); + } catch (err) { + console.error('Error creating task:', err); + } + }; + + const handleToggleTask = async (taskId: string, completed: boolean) => { + try { + await updateTask(taskId, { completed: !completed }); + fetchTasks(); + } catch (err) { + console.error('Error updating task:', err); + } + }; + + const handleDeleteTask = async (taskId: string) => { + if (!confirm('Tem certeza que deseja deletar essa tarefa?')) return; + try { + await deleteTask(taskId); + fetchTasks(); + } catch (err) { + console.error('Error deleting task:', err); + } + }; + + const handleLogout = async () => { + await logout(); + router.push('/auth/login'); + }; + + const filteredTasks = tasks.filter((task) => { + if (filters.completed !== undefined && task.completed !== filters.completed) { + return false; + } + if (filters.category && task.category !== filters.category) { + return false; + } + if (filters.priority && task.priority !== filters.priority) { + return false; + } + return true; + }); + + return ( +
+ {/* Header */} +
+
+
+

📋 Task Manager

+

Bem-vindo, {user?.email}

+
+ +
+
+ + {/* Main Content */} +
+ {/* Filters and Create Button */} +
+
+

Minhas Tarefas

+ +
+ + {/* Filters */} +
+ + + + + +
+
+ + {/* Create Task Form */} + {showCreateForm && ( + + + Criar Nova Tarefa + + +
+ setNewTask({ ...newTask, title: e.target.value })} + placeholder="Título da tarefa" + required + /> + +
+ +