Skip to content

Commit ae304ed

Browse files
authored
Merge pull request #107 from cdryampi/codex/fix-bugs-in-playas-feature
Fix Playas: legacy redirect, update links and comparison gating
2 parents e474f64 + 33035b9 commit ae304ed

7 files changed

Lines changed: 169 additions & 5 deletions

File tree

frontend/src/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { EventDetailPage } from "@/features/agenda/pages/EventDetailPage";
3333
import { AgendaPage } from "@/features/agenda/pages/AgendaPage";
3434
import { PlacesPage } from "@/features/places/pages/PlacesPage";
3535
import { PlaceDetailPage } from "@/features/places/pages/PlaceDetailPage";
36+
import { LegacyBeachPlaceRedirect } from "@/features/places/pages/LegacyBeachPlaceRedirect";
3637
import { RankingsPage } from "@/features/gamification/pages/RankingsPage";
3738
import { CategoriesPage } from "@/features/categories/pages/CategoriesPage";
3839
import { CategoryDetailPage } from "@/features/categories/pages/CategoryDetailPage";
@@ -400,7 +401,8 @@ export default function App() {
400401
<Route path="/agenda" element={<AgendaPage />} />
401402
<Route path="/agenda/:slug" element={<EventDetailPage />} />
402403
<Route path="/lugares" element={<PlacesPage />} />
403-
<Route path="/lugares/:slug" element={<PlaceDetailPage />} />
404+
<Route path="/lugares/:slug" element={<LegacyBeachPlaceRedirect />} />
405+
<Route path="/playas/:slug" element={<PlaceDetailPage />} />
404406
<Route path="/categorias" element={<CategoriesPage />} />
405407
<Route path="/categorias/:slug" element={<CategoryDetailPage />} />
406408
<Route path="/rankings" element={<RankingsPage />} />

frontend/src/data/headerNav.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const HEADER_NAV: HeaderNavItem[] = [
1313
{ label: "Categorías", href: "/categorias" },
1414
{ label: "Patrimonio", href: "/lugares?category=heritage" },
1515
{ label: "Naturaleza", href: "/lugares?category=nature" },
16-
{ label: "Playas", href: "/lugares?category=beaches" },
16+
{ label: "Playas", href: "/categorias/beaches" },
1717
{ label: "Cultura", href: "/lugares?category=culture" },
1818
{ label: "Gastronomía", href: "/lugares?category=restaurants" },
1919
{ label: "Alojamiento", href: "/lugares?category=accommodations" },
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type { ReactNode } from "react";
2+
import { describe, it, expect, vi } from "vitest";
3+
import { render, screen } from "@testing-library/react";
4+
import { MemoryRouter } from "react-router-dom";
5+
6+
import NatureCategoryLayout from "./NatureCategoryLayout";
7+
8+
vi.mock("@/components/animated/MotionReveal", () => ({
9+
MotionReveal: ({ children }: { children: ReactNode }) => <>{children}</>,
10+
}));
11+
12+
vi.mock("@/features/places/components/PlaceCard", () => ({
13+
PlaceCard: ({ place }: { place: { title: string } }) => <div>{place.title}</div>,
14+
}));
15+
16+
const baseCategory = {
17+
id: 1,
18+
slug: "beaches",
19+
nombre: "Playas",
20+
descripcion: "Descubre nuestras playas",
21+
taxonomy: "places",
22+
parent: null,
23+
icon: "umbrella",
24+
is_published: true,
25+
featured_media: null,
26+
attachments: [],
27+
created_at: "2025-01-01",
28+
updated_at: "2025-01-01",
29+
};
30+
31+
const makePlace = (id: number) => ({
32+
id,
33+
slug: `playa-${id}`,
34+
title: `Playa ${id}`,
35+
description: "",
36+
location_text: `Zona ${id}`,
37+
latitude: 41,
38+
longitude: 2,
39+
phone: id === 1 ? "123" : "",
40+
email: "",
41+
website: "",
42+
booking_url: "",
43+
is_published: true,
44+
category: 1,
45+
template_key: "beaches",
46+
featured_media: null,
47+
attachments: [],
48+
created_at: "2025-01-01",
49+
updated_at: "2025-01-01",
50+
});
51+
52+
describe("NatureCategoryLayout beaches comparison", () => {
53+
it("shows comparison only when exactly two beaches are available", () => {
54+
render(
55+
<MemoryRouter>
56+
<NatureCategoryLayout
57+
category={baseCategory as any}
58+
places={[makePlace(1), makePlace(2)] as any}
59+
events={[] as any}
60+
isLoadingPlaces={false}
61+
isLoadingEvents={false}
62+
/>
63+
</MemoryRouter>,
64+
);
65+
66+
expect(screen.getByText("Compara las dos playas publicadas")).toBeInTheDocument();
67+
expect(screen.queryByText("Seleccion editorial")).not.toBeInTheDocument();
68+
});
69+
70+
it("shows editorial fallback when published beaches are not exactly two", () => {
71+
render(
72+
<MemoryRouter>
73+
<NatureCategoryLayout
74+
category={baseCategory as any}
75+
places={[makePlace(1), makePlace(2), makePlace(3), makePlace(4)] as any}
76+
events={[] as any}
77+
isLoadingPlaces={false}
78+
isLoadingEvents={false}
79+
/>
80+
</MemoryRouter>,
81+
);
82+
83+
expect(screen.getByText("Seleccion editorial")).toBeInTheDocument();
84+
expect(screen.queryByText("Compara las dos playas publicadas")).not.toBeInTheDocument();
85+
});
86+
});

frontend/src/features/categories/templates/layouts/NatureCategoryLayout.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default function NatureCategoryLayout({
9090
</div>
9191

9292
<Link
93-
to={`/lugares?category=${category.slug}`}
93+
to={`/categorias/${category.slug}`}
9494
className="inline-flex items-center gap-2 rounded-full border border-primary/12 bg-primary/5 px-4 py-2.5 text-sm font-semibold text-primary"
9595
>
9696
<Map className="h-4 w-4" />
@@ -140,6 +140,48 @@ export default function NatureCategoryLayout({
140140
)}
141141
</section>
142142

143+
{category.slug === "beaches" ? (
144+
<MotionReveal>
145+
{places.length === 2 ? (
146+
<section className="card-surface space-y-6 p-6 md:p-8">
147+
<SectionHeader
148+
eyebrow="Comparativa"
149+
title="Compara las dos playas publicadas"
150+
description="Vista rapida para decidir cual visitar segun ubicacion y contacto disponible."
151+
/>
152+
<div className="overflow-x-auto">
153+
<table className="min-w-full text-left text-sm text-slate-700">
154+
<thead>
155+
<tr className="border-b border-slate-200 text-xs uppercase tracking-[0.14em] text-slate-500">
156+
<th className="px-3 py-3">Playa</th>
157+
<th className="px-3 py-3">Ubicacion</th>
158+
<th className="px-3 py-3">Telefono</th>
159+
</tr>
160+
</thead>
161+
<tbody>
162+
{places.map((place) => (
163+
<tr key={place.id} className="border-b border-slate-100 last:border-b-0">
164+
<td className="px-3 py-3 font-semibold text-slate-900">{place.title}</td>
165+
<td className="px-3 py-3">{place.location_text}</td>
166+
<td className="px-3 py-3">{place.phone || "-"}</td>
167+
</tr>
168+
))}
169+
</tbody>
170+
</table>
171+
</div>
172+
</section>
173+
) : (
174+
<section className="card-surface space-y-4 p-6 md:p-8">
175+
<SectionHeader
176+
eyebrow="Seleccion editorial"
177+
title="El equipo municipal destaca las mejores opciones para hoy"
178+
description="Cuando hay mas o menos de dos playas publicadas, mostramos una narrativa editorial en lugar de forzar una comparativa."
179+
/>
180+
</section>
181+
)}
182+
</MotionReveal>
183+
) : null}
184+
143185
{places.length === 0 && !isLoadingPlaces ? (
144186
<div className="py-24 text-center">
145187
<TreePine className="mx-auto mb-6 h-16 w-16 text-slate-300" />

frontend/src/features/hero/components/HomeExperienceGrid.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const EXPERIENCES: ExperienceItem[] = [
3939
},
4040
{
4141
title: "Playas",
42-
href: "/lugares?category=beaches",
42+
href: "/categorias/beaches",
4343
iconKey: "beaches",
4444
color: "text-[#3EC5FF]",
4545
bg: "bg-[#3EC5FF]/16 ring-[#3EC5FF]/40",

frontend/src/features/hero/components/HomePillars.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const PILLARS: PillarItem[] = [
4646
},
4747
{
4848
title: "Platges",
49-
href: "/lugares?category=beaches",
49+
href: "/categorias/beaches",
5050
icon: Umbrella,
5151
color: "bg-[#3EC5FF]",
5252
lightColor: "hover:bg-[#3EC5FF]/90",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useEffect } from "react";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { useNavigate, useParams } from "react-router-dom";
4+
5+
import { getPlaces } from "../api";
6+
import { PlaceDetailPage } from "./PlaceDetailPage";
7+
8+
export function LegacyBeachPlaceRedirect() {
9+
const { slug } = useParams<{ slug: string }>();
10+
const navigate = useNavigate();
11+
12+
const { data: isBeachSlug, isLoading } = useQuery({
13+
queryKey: ["legacy-beach-redirect", slug],
14+
queryFn: async () => {
15+
if (!slug) return false;
16+
const response = await getPlaces({ category: "beaches", is_published: true, limit: 200 });
17+
const beaches = Array.isArray(response) ? response : response.results || [];
18+
return beaches.some((place) => place.slug === slug);
19+
},
20+
enabled: !!slug,
21+
});
22+
23+
useEffect(() => {
24+
if (isBeachSlug && slug) {
25+
navigate(`/playas/${slug}`, { replace: true });
26+
}
27+
}, [isBeachSlug, navigate, slug]);
28+
29+
if (isLoading) {
30+
return <div className="page-shell-offset" />;
31+
}
32+
33+
return <PlaceDetailPage />;
34+
}

0 commit comments

Comments
 (0)