From a3febf20875d7ffb5bb0befbf214b72b4d7f708a Mon Sep 17 00:00:00 2001 From: GoHorse Deploy Date: Sat, 7 Mar 2026 11:06:47 -0300 Subject: [PATCH] fix(frontend): harden home jobs loading states --- frontend/src/app/dashboard/jobs/page.tsx | 1 + frontend/src/app/page.tsx | 43 +++++++++++++++---- .../src/contexts/notification-context.tsx | 10 ++++- frontend/src/i18n/en.json | 8 +++- frontend/src/i18n/es.json | 8 +++- frontend/src/i18n/pt-BR.json | 8 +++- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/dashboard/jobs/page.tsx b/frontend/src/app/dashboard/jobs/page.tsx index 094a713..dd545e7 100644 --- a/frontend/src/app/dashboard/jobs/page.tsx +++ b/frontend/src/app/dashboard/jobs/page.tsx @@ -229,6 +229,7 @@ export default function AdminJobsPage() { {t('admin.jobs.details.title')} + {t('admin.jobs.details.description')} {selectedJob && (
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index f4946d4..28e181b 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -19,6 +19,7 @@ export default function Home() { const { t } = useTranslation() const [jobs, setJobs] = useState([]) const [loading, setLoading] = useState(true) + const [jobsError, setJobsError] = useState(false) const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(true) const [loadingMore, setLoadingMore] = useState(false) @@ -36,10 +37,18 @@ export default function Home() { const fetchJobs = useCallback(async (pageNum: number, isLoadMore = false) => { try { if (isLoadMore) setLoadingMore(true) - else setLoading(true) + else { + setLoading(true) + setJobsError(false) + } const limit = 8 - const res = await jobsApi.list({ page: pageNum, limit }) + const res = await Promise.race([ + jobsApi.list({ page: pageNum, limit }), + new Promise((_, reject) => { + window.setTimeout(() => reject(new Error("JOBS_REQUEST_TIMEOUT")), 12000) + }), + ]) if (res.data) { const newJobs = res.data.map(transformApiJobToFrontend) @@ -52,15 +61,22 @@ export default function Home() { // If we got fewer jobs than the limit, we've reached the end if (newJobs.length < limit) { setHasMore(false) + } else if (!isLoadMore) { + setHasMore(true) } } } catch (error) { console.error("Failed to fetch jobs:", error) + if (!isLoadMore) { + setJobs([]) + setHasMore(false) + setJobsError(true) + } } finally { setLoading(false) setLoadingMore(false) } - }, [t]) + }, []) useEffect(() => { fetchJobs(1) @@ -72,6 +88,9 @@ export default function Home() { fetchJobs(nextPage, true) } + const latestJobs = jobs.slice(0, 8) + const showEmptyState = !loading && !jobsError && jobs.length === 0 + const scrollPrev = useCallback(() => { if (emblaApi) emblaApi.scrollPrev() }, [emblaApi]) @@ -165,8 +184,12 @@ export default function Home() {
{loading ? ( -
Carregando vagas...
- ) : jobs.slice(0, 8).map((job, index) => ( +
{t("jobs.loading")}
+ ) : jobsError ? ( +
{t("home.featuredJobs.error")}
+ ) : showEmptyState ? ( +
{t("home.featuredJobs.empty")}
+ ) : latestJobs.map((job, index) => (
@@ -203,7 +226,11 @@ export default function Home() {
{loading && page === 1 ? ( -
Carregando vagas...
+
{t("jobs.loading")}
+ ) : jobsError ? ( +
{t("home.moreJobs.error")}
+ ) : showEmptyState ? ( +
{t("home.moreJobs.empty")}
) : jobs.map((job, index) => (
@@ -211,14 +238,14 @@ export default function Home() { ))}
- {hasMore && ( + {hasMore && !jobsError && jobs.length > 0 && (
)} diff --git a/frontend/src/contexts/notification-context.tsx b/frontend/src/contexts/notification-context.tsx index fe62e92..add486a 100644 --- a/frontend/src/contexts/notification-context.tsx +++ b/frontend/src/contexts/notification-context.tsx @@ -32,11 +32,19 @@ export function NotificationProvider({ useEffect(() => { const loadNotifications = async () => { + const hasLocalSession = + typeof window !== "undefined" && + Boolean( + localStorage.getItem("job-portal-auth") || + localStorage.getItem("auth_token") || + localStorage.getItem("token") + ); + if (loading) { return; } - if (!user) { + if (!user || !hasLocalSession) { setNotifications([]); return; } diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 1b38a74..707d972 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -236,7 +236,9 @@ "yesterday": "Yesterday", "apply": "Apply now", "viewJob": "View Job", - "favorite": "Favorite" + "favorite": "Favorite", + "empty": "No jobs available right now.", + "error": "Could not load the latest jobs right now." }, "levels": { "junior": "Junior", @@ -246,7 +248,9 @@ "moreJobs": { "title": "More Jobs", "viewAll": "View All Jobs", - "loadMore": "Load More Jobs" + "loadMore": "Load More Jobs", + "empty": "No jobs available right now.", + "error": "Could not load more jobs right now." }, "cta": { "badge": "Social Networks", diff --git a/frontend/src/i18n/es.json b/frontend/src/i18n/es.json index be27afc..ca67fe9 100644 --- a/frontend/src/i18n/es.json +++ b/frontend/src/i18n/es.json @@ -236,7 +236,9 @@ "yesterday": "Ayer", "apply": "Aplicar ahora", "viewJob": "Ver Empleo", - "favorite": "Favorito" + "favorite": "Favorito", + "empty": "No hay empleos disponibles en este momento.", + "error": "No se pudieron cargar los últimos empleos ahora." }, "levels": { "junior": "Junior", @@ -246,7 +248,9 @@ "moreJobs": { "title": "Más Empleos", "viewAll": "Ver Todos los Empleos", - "loadMore": "Cargar Más Empleos" + "loadMore": "Cargar Más Empleos", + "empty": "No hay empleos disponibles en este momento.", + "error": "No se pudieron cargar más empleos ahora." }, "cta": { "badge": "Redes Sociales", diff --git a/frontend/src/i18n/pt-BR.json b/frontend/src/i18n/pt-BR.json index 9403339..51b4f8f 100644 --- a/frontend/src/i18n/pt-BR.json +++ b/frontend/src/i18n/pt-BR.json @@ -276,7 +276,9 @@ "yesterday": "Ontem", "apply": "Aplicar agora", "viewJob": "Ver Vaga", - "favorite": "Favoritar" + "favorite": "Favoritar", + "empty": "Nenhuma vaga encontrada no momento.", + "error": "Não foi possível carregar as últimas vagas agora." }, "levels": { "junior": "Júnior", @@ -286,7 +288,9 @@ "moreJobs": { "title": "Mais Vagas", "viewAll": "Ver Todas Vagas", - "loadMore": "Carregar Mais Vagas" + "loadMore": "Carregar Mais Vagas", + "empty": "Nenhuma vaga disponível no momento.", + "error": "Não foi possível carregar mais vagas agora." }, "cta": { "badge": "Redes Sociais",