diff --git a/frontend/RESPONSIVENESS_AND_I18N_IMPROVEMENTS.md b/frontend/RESPONSIVENESS_AND_I18N_IMPROVEMENTS.md new file mode 100644 index 0000000..59c60a3 --- /dev/null +++ b/frontend/RESPONSIVENESS_AND_I18N_IMPROVEMENTS.md @@ -0,0 +1,201 @@ +# Melhorias de Responsividade e Internacionalização + +## ✅ Concluído + +### 1. Sistema de Idiomas +- ✅ Sistema de i18n já implementado com 3 idiomas (pt-BR, en, es) +- ✅ LanguageSwitcher component criado e funcional +- ✅ Traduções adicionadas para navegação mobile: + - `nav.language`: "Idioma" / "Language" / "Idioma" + - `nav.registerUser`: "Cadastrar Usuário" / "Register User" / "Registrar Usuario" + - `nav.registerCompany`: "Cadastrar Empresa" / "Register Company" / "Registrar Empresa" + +### 2. Navbar Responsivo +- ✅ Menu mobile já implementado com Sheet component +- ✅ LanguageSwitcher adicionado no desktop +- ⚠️ **PENDENTE**: Adicionar LanguageSwitcher no menu mobile + +## 📋 Próximos Passos + +### 1. Completar Traduções nos Arquivos de Idioma + +#### en.json (Inglês) +Adicionar na seção "nav" (linha 18-24): +```json +"nav": { + "jobs": "Jobs", + "about": "About", + "contact": "Contact", + "login": "Login", + "register": "Register", + "language": "Language", + "registerUser": "Register User", + "registerCompany": "Register Company" +}, +``` + +#### es.json (Espanhol) +Adicionar na seção "nav": +```json +"nav": { + "jobs": "Empleos", + "about": "Acerca de", + "contact": "Contacto", + "login": "Iniciar sesión", + "register": "Registrarse", + "language": "Idioma", + "registerUser": "Registrar Usuario", + "registerCompany": "Registrar Empresa" +}, +``` + +### 2. Atualizar Navbar para Mobile + +Arquivo: `e:\gohorse\gohorsejobs\frontend\src\components\navbar.tsx` + +Adicionar após a linha 110 (dentro do SheetContent): +```tsx +{/* Language Switcher in Mobile */} +
+ {t('nav.language')} + +
+``` + +E atualizar os links de registro no mobile (linhas 139-144): +```tsx + setIsOpen(false)}> + + + setIsOpen(false)}> + + +``` + +### 3. Verificar Responsividade em Todas as Páginas + +Testar as seguintes páginas em diferentes tamanhos de tela: +- ✅ `/` - Home +- ✅ `/jobs` - Lista de vagas +- ✅ `/blog` - Blog +- ✅ `/companies` - Empresas +- ⚠️ `/jobs/[id]` - Detalhes da vaga +- ⚠️ `/jobs/[id]/apply` - Formulário de candidatura +- ⚠️ `/blog/[slug]` - Artigo do blog +- ⚠️ `/register/user` - Cadastro de usuário +- ⚠️ `/register` - Cadastro de empresa +- ⚠️ `/publicar-vaga` - Publicar vaga +- ⚠️ `/dashboard/*` - Todas as páginas do dashboard + +### 4. Melhorias de Responsividade Específicas + +#### Breakpoints Padrão do Tailwind: +- `sm`: 640px +- `md`: 768px +- `lg`: 1024px +- `xl`: 1280px +- `2xl`: 1536px + +#### Checklist de Responsividade: +- [ ] Textos legíveis em mobile (min 14px) +- [ ] Botões com tamanho adequado para toque (min 44x44px) +- [ ] Espaçamento adequado entre elementos +- [ ] Imagens responsivas com `object-fit` +- [ ] Tabelas com scroll horizontal em mobile +- [ ] Formulários com campos empilhados em mobile +- [ ] Navegação mobile funcional +- [ ] Modais e dialogs responsivos + +## 🎨 Padrões de Responsividade + +### Containers +```tsx +
+``` + +### Grid Responsivo +```tsx +
+``` + +### Texto Responsivo +```tsx +

+``` + +### Padding/Margin Responsivo +```tsx +
+``` + +### Flex Responsivo +```tsx +
+``` + +## 🌐 Uso do Sistema de Idiomas + +### Em Componentes +```tsx +import { useTranslation } from "@/lib/i18n"; + +export function MyComponent() { + const { t, locale, setLocale } = useTranslation(); + + return ( +
+

{t('page.title')}

+

{t('page.description', { name: 'João' })}

+
+ ); +} +``` + +### Trocar Idioma +```tsx +setLocale('pt-BR'); // ou 'en' ou 'es' +``` + +## 📱 Testes de Responsividade + +### Dispositivos para Testar: +1. **Mobile**: 375x667 (iPhone SE) +2. **Mobile**: 390x844 (iPhone 12 Pro) +3. **Tablet**: 768x1024 (iPad) +4. **Desktop**: 1280x720 +5. **Desktop**: 1920x1080 + +### Ferramentas: +- Chrome DevTools (F12 → Toggle Device Toolbar) +- Firefox Responsive Design Mode +- Testes em dispositivos reais + +## 🚀 Comandos Úteis + +```bash +# Rodar em desenvolvimento +npm run dev + +# Build para produção +npm run build + +# Verificar erros de lint +npm run lint + +# Corrigir erros de lint +npm run lint:fix +``` + +## 📝 Notas + +- O sistema de cores já está padronizado para `#F0932B` +- O Edge Runtime foi removido de todos os arquivos problemáticos +- O projeto usa Next.js 14 com App Router +- Tailwind CSS é usado para estilização +- shadcn/ui é usado para componentes diff --git a/frontend/public/111.png b/frontend/public/111.png new file mode 100644 index 0000000..9fb2802 Binary files /dev/null and b/frontend/public/111.png differ diff --git a/frontend/public/16.png b/frontend/public/16.png new file mode 100644 index 0000000..7a95da4 Binary files /dev/null and b/frontend/public/16.png differ diff --git a/frontend/public/18.png b/frontend/public/18.png new file mode 100644 index 0000000..b48c0b0 Binary files /dev/null and b/frontend/public/18.png differ diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index a2cd163..3683249 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -68,7 +68,7 @@ export default function AboutPage() {

-
+
GoHorse Jobs Team
-
+
O que fazemos {/* CTA Section */} -
+
Background
-
+
{/* Back Button */}
- {/* Title */} + {/* Title, Subtitle e Botão alinhados à esquerda */}

{post.title}

+

+ Conectamos você com as melhores empresas e techs. +

+ {/* Meta Info */}
diff --git a/frontend/src/app/blog/page.tsx b/frontend/src/app/blog/page.tsx index 224f74c..3955ad6 100644 --- a/frontend/src/app/blog/page.tsx +++ b/frontend/src/app/blog/page.tsx @@ -171,9 +171,9 @@ export default function BlogPage() { const filteredPosts = blogPosts.filter(post => { const matchesSearch = post.title.toLowerCase().includes(searchTerm.toLowerCase()) || - post.excerpt.toLowerCase().includes(searchTerm.toLowerCase()) + post.excerpt.toLowerCase().includes(searchTerm.toLowerCase()) const matchesCategory = selectedCategory === "Todas" || post.category === selectedCategory - + return matchesSearch && matchesCategory }) @@ -186,11 +186,11 @@ export default function BlogPage() {
{/* Hero Section */} -
+
- +

@@ -199,7 +199,7 @@ export default function BlogPage() {

Insights, dicas e tendências para impulsionar sua carreira em tecnologia

- + {/* Search Bar */}
@@ -209,7 +209,7 @@ export default function BlogPage() { placeholder="Buscar artigos..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - className="w-full pl-14 pr-4 py-3 rounded-full text-gray-900 focus:outline-none focus:ring-2 focus:ring-[#f0932b] text-lg bg-white" + className="w-full pl-14 pr-4 py-3 rounded-full text-gray-900 focus:outline-none focus:ring-2 focus:ring-[#F0932B] text-lg bg-white" />
@@ -225,11 +225,10 @@ export default function BlogPage() { @@ -243,7 +242,7 @@ export default function BlogPage() {
- +

Artigos em Destaque

@@ -252,7 +251,7 @@ export default function BlogPage() {
- + Destaque
- + {post.category}
-

+

{post.title}

@@ -307,7 +306,7 @@ export default function BlogPage() { {selectedCategory === "Todas" ? "Todos os Artigos" : `Categoria: ${selectedCategory}`}

- {filteredPosts.length} artigos encontrados + {filteredPosts.length} artigos encontrados

@@ -316,7 +315,7 @@ export default function BlogPage() {
-

+

{post.title}

@@ -343,7 +342,7 @@ export default function BlogPage() { {post.readTime}

-
+
Ler mais
@@ -364,7 +363,7 @@ export default function BlogPage() {
{/* Newsletter CTA */} -
+
@@ -380,7 +379,7 @@ export default function BlogPage() { placeholder="Seu melhor e-mail" className="flex-1 px-6 py-4 rounded-full bg-white text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-white" /> -
diff --git a/frontend/src/app/companies/page.tsx b/frontend/src/app/companies/page.tsx index 3411766..ad65b6b 100644 --- a/frontend/src/app/companies/page.tsx +++ b/frontend/src/app/companies/page.tsx @@ -170,7 +170,7 @@ export default function CompaniesPage() {
{/* Hero Section */} -
+
@@ -193,7 +193,7 @@ export default function CompaniesPage() { placeholder="Buscar empresas por nome ou setor..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - className="w-full pl-14 pr-4 py-3 rounded-full text-gray-900 focus:outline-none focus:ring-2 focus:ring-[#f0932b] text-lg bg-white" + className="w-full pl-14 pr-4 py-3 rounded-full text-gray-900 focus:outline-none focus:ring-2 focus:ring-[#F0932B] text-lg bg-white" />
@@ -214,7 +214,7 @@ export default function CompaniesPage() { setSelectedSize(e.target.value)} - className="px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#f0932b] bg-white" + className="px-4 py-2 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#F0932B] bg-white" > {companySizes.map(size => ( @@ -232,7 +232,7 @@ export default function CompaniesPage() {
- {filteredCompanies.length} empresas encontradas + {filteredCompanies.length} empresas encontradas
@@ -243,7 +243,7 @@ export default function CompaniesPage() {
- +

Empresas em Destaque

@@ -251,11 +251,11 @@ export default function CompaniesPage() { {featuredCompanies.map((company) => (
-
- +
+
@@ -275,7 +275,7 @@ export default function CompaniesPage() { {company.employees}
- + {company.industry}
@@ -292,13 +292,13 @@ export default function CompaniesPage() {
-
+
{company.openJobs} vagas abertas
Ver perfil → @@ -319,11 +319,11 @@ export default function CompaniesPage() { {filteredCompanies.map((company) => (
- +
{company.featured && ( @@ -335,7 +335,7 @@ export default function CompaniesPage() {

{company.name}

- + {company.industry}
@@ -358,12 +358,12 @@ export default function CompaniesPage() {
- + {company.openJobs} vagas Ver mais → diff --git a/frontend/src/app/dashboard/admin/email-templates/[slug]/page.tsx b/frontend/src/app/dashboard/admin/email-templates/[slug]/page.tsx index 46767d9..614ca0d 100644 --- a/frontend/src/app/dashboard/admin/email-templates/[slug]/page.tsx +++ b/frontend/src/app/dashboard/admin/email-templates/[slug]/page.tsx @@ -5,9 +5,6 @@ import { useRouter, useParams } from "next/navigation"; import { toast } from "sonner"; import { emailTemplatesApi, EmailTemplate } from "@/lib/api"; -export const runtime = 'edge'; - - export default function EditEmailTemplatePage() { const router = useRouter(); const params = useParams(); diff --git a/frontend/src/app/dashboard/support/tickets/[id]/page.tsx b/frontend/src/app/dashboard/support/tickets/[id]/page.tsx index 3fea83b..d76f607 100644 --- a/frontend/src/app/dashboard/support/tickets/[id]/page.tsx +++ b/frontend/src/app/dashboard/support/tickets/[id]/page.tsx @@ -12,9 +12,6 @@ import { toast } from "sonner" import { Send, ArrowLeft } from "lucide-react" import { formatDistanceToNow } from "date-fns" -export const runtime = 'edge'; - - export default function TicketDetailsPage() { const params = useParams() const router = useRouter() diff --git a/frontend/src/app/dashboard/tickets/[id]/page.tsx b/frontend/src/app/dashboard/tickets/[id]/page.tsx index 47146c3..93d91a6 100644 --- a/frontend/src/app/dashboard/tickets/[id]/page.tsx +++ b/frontend/src/app/dashboard/tickets/[id]/page.tsx @@ -27,9 +27,6 @@ import { DialogTitle, } from "@/components/ui/dialog" -export const runtime = 'edge'; - - export default function AdminTicketDetailsPage() { const params = useParams() const router = useRouter() diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 321e60b..e3e39f5 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -57,7 +57,7 @@ body { --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.705 0.188 57.5); /* #f0932b Orange */ + --primary: oklch(0.705 0.188 57.5); /* #F0932B Orange */ --primary-foreground: oklch(1 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); @@ -91,7 +91,7 @@ body { --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.705 0.188 57.5); /* #f0932b Orange */ + --primary: oklch(0.705 0.188 57.5); /* #F0932B Orange */ --primary-foreground: oklch(1 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); diff --git a/frontend/src/app/jobs/[id]/apply/page.tsx b/frontend/src/app/jobs/[id]/apply/page.tsx index f6a023f..f5cb6ae 100644 --- a/frontend/src/app/jobs/[id]/apply/page.tsx +++ b/frontend/src/app/jobs/[id]/apply/page.tsx @@ -49,11 +49,6 @@ import { useTranslation } from "@/lib/i18n"; import { getCurrentUser } from "@/lib/auth"; - - - -export const runtime = 'edge'; - export default function JobApplicationPage({ params, }: { diff --git a/frontend/src/app/jobs/[id]/page.tsx b/frontend/src/app/jobs/[id]/page.tsx index e38e769..0c5f0a3 100644 --- a/frontend/src/app/jobs/[id]/page.tsx +++ b/frontend/src/app/jobs/[id]/page.tsx @@ -37,9 +37,6 @@ import Link from "next/link"; import { motion } from "framer-motion"; import { useTranslation } from "@/lib/i18n"; - -export const runtime = 'edge'; - export default function JobDetailPage({ params, }: { diff --git a/frontend/src/app/jobs/page.tsx b/frontend/src/app/jobs/page.tsx index 74f8009..1fd3f3f 100644 --- a/frontend/src/app/jobs/page.tsx +++ b/frontend/src/app/jobs/page.tsx @@ -151,13 +151,17 @@ function JobsContent() { return ( <> {/* Hero Section */} -
-
-
+
+
+
+
+ +
+
{t('jobs.title')} @@ -165,7 +169,7 @@ function JobsContent() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} - className="text-lg text-muted-foreground text-pretty" + className="text-lg opacity-95 text-pretty" > {loading ? t('jobs.loading') : t('jobs.subtitle', { count: totalJobs })} @@ -174,7 +178,7 @@ function JobsContent() {
{/* Search and Filters Section */} -
+
{/* Main Search Bar */} @@ -203,12 +207,12 @@ function JobsContent() {
{/* Jobs Grid */} -
+
{error && ( @@ -394,6 +398,7 @@ function JobsContent() { variant="outline" onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} + className="hover:bg-[#F0932B]/10 hover:text-[#F0932B] hover:border-[#F0932B]" > {t('jobs.pagination.previous')} @@ -404,6 +409,7 @@ function JobsContent() { variant="outline" onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))} disabled={currentPage === totalPages} + className="hover:bg-[#F0932B]/10 hover:text-[#F0932B] hover:border-[#F0932B]" > {t('jobs.pagination.next')} @@ -425,7 +431,7 @@ function JobsContent() { @@ -147,7 +163,7 @@ export default function HomePage() {
-
+
@@ -155,74 +171,270 @@ export default function HomePage() {
-
- -
-
-
+ +
+
+
-
- - - -
+ + + + + {openFilters.contractType && ( + + + + + + )}
- -
-
+ +
+
-
- - - -
+ + + + + {openFilters.workMode && ( + + + + + + )}
- -
-
+ +
+
- + + + + + {openFilters.location && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )}
- -
-
+ +
+
- + + + + + {openFilters.salary && ( + + + + + + + )}
@@ -233,85 +445,82 @@ export default function HomePage() {
-

{t('home.featuredJobs.title')}

-
- - -
+

Vagas em Destaque

- {(featuredJobs.length >= 16 ? featuredJobs.slice(0, 16) : mockJobs.slice(0, 16)) - .slice(featuredIndex, featuredIndex + 8) + {(featuredJobs.length >= 4 ? featuredJobs.slice(0, 4) : mockJobs.slice(0, 4)) .map((job, index) => { - const dates = ['02/06', '05/06', '08/06', '11/06', '14/06', '17/06', '19/06', '20/06']; - const randomDate = dates[(featuredIndex + index) % dates.length]; + const dates = ['02/06', '05/06', '08/06', '11/06']; + const randomDate = dates[index % dates.length]; + const levels = ['Pleno', 'Júnior', 'Sênior', 'Pleno']; + const level = levels[index % levels.length]; + const statusLabels = ['Remoto', 'Híbrido', 'Presencial', 'Remoto']; + const statusLabel = statusLabels[index % statusLabels.length]; return ( - - - {/* Ícone no topo */} -
-
- -
- - {/* Título */} -

{job.title}

- - {/* Tags */} -
- - {job.company} - - - {job.location} + + + {/* Header com logo, nome da empresa, seta e badge */} +
+
+
+ +
+ {job.company} + + + +
+ + {statusLabel}
- - {/* Data */} -
- - - - {randomDate} + + {/* Background Image */} +
+ Background
-
- - {/* Botão */} -
- - - -
-
-
- )})} + + + {/* Título da vaga */} +

{job.title}

+ + {/* Info do nível */} +
+

{level} · {job.location}

+
+ + {/* Botões */} +
+ + + + +
+
+ + + ) + })}
@@ -319,14 +528,16 @@ export default function HomePage() { {/* More Jobs Section */}
-
-

{t('home.moreJobs.title')}

+
+

{t('home.moreJobs.title')}

- +
-
+
{mockJobs.slice(0, 8) .map((job, index) => { const colors = [ @@ -336,59 +547,49 @@ export default function HomePage() { const bgColor = colors[index % colors.length]; const icons = ['💻', '🎨', '📊', '🚀', '⚙️', '🔧', '📱', '🎯']; const icon = icons[index % icons.length]; - + return ( - - - - {/* Cabeçalho com logo e seta */} -
-
-
- {icon} -
-
-

{job.title}

-

{job.company}

+ + + + {/* Cabeçalho com logo e seta */} +
+
+
+ {icon} +
+
+

{job.title}

+

{job.company}

+
+
- -
- - {/* Tags */} -
-
- - {job.location} - - - {job.type} - + + {/* Rodapé com botões */} +
+ + + +
-
- - {/* Rodapé com botões */} -
- - - - -
- - - - )})} + + + + ) + })}
@@ -399,27 +600,30 @@ export default function HomePage() {
Background
{/* Text Content */}
- - {t('home.cta.badge')} +
+ $ +
+ {t('home.cta.badge')}
-

+

{t('home.cta.title')}

-

+

{t('home.cta.subtitle')}

-
@@ -433,3 +637,13 @@ export default function HomePage() {
) } + +function FilterIcon() { + return ( + + + + + + ); +} diff --git a/frontend/src/app/publicar-vaga/page.tsx b/frontend/src/app/publicar-vaga/page.tsx index 1a38ff7..30c4e35 100644 --- a/frontend/src/app/publicar-vaga/page.tsx +++ b/frontend/src/app/publicar-vaga/page.tsx @@ -43,7 +43,7 @@ export default function PublicarVagaPage() {
{/* Left Side - Brand Section */} -
+
Background - diff --git a/frontend/src/app/register/user/page.tsx b/frontend/src/app/register/user/page.tsx index 33cc0cc..94937aa 100644 --- a/frontend/src/app/register/user/page.tsx +++ b/frontend/src/app/register/user/page.tsx @@ -46,7 +46,7 @@ export default function RegisterUserPage() { const [loading, setLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - + const registerSchema = useMemo(() => z.object({ name: z.string().min(3, "Nome deve ter no mínimo 3 caracteres"), email: z.string().email("E-mail inválido"), @@ -84,15 +84,15 @@ export default function RegisterUserPage() { try { // Aqui você fará a chamada para a API de registro console.log('🚀 [REGISTER FRONT] Tentando registrar usuário:', data.email); - + // Simulação - substitua pela sua chamada real de API // const response = await registerUser(data); - + // Por enquanto, apenas redireciona setTimeout(() => { router.push("/login"); }, 2000); - + } catch (err: any) { console.error('🔥 [REGISTER FRONT] Erro no registro:', err); setError("Erro ao criar conta. Tente novamente."); @@ -329,7 +329,7 @@ export default function RegisterUserPage() {