feat(search-frontend): add public landing page, search page with map view, filters, and property cards

- Create (public) route group with landing page (hero, featured listings, district links, stats, CTA)
- Create search page with filter sidebar, list/map/split view modes, URL-synced filters, pagination
- Build ListingMap component with CSS-based marker visualization and popup details
- Build FilterBar with transaction type, property type, city, price range, area, bedrooms filters
- Build PropertyCard and SearchResults components with responsive grid layout
- Update middleware to allow public access to / and /search routes
- Move dashboard home to /dashboard to avoid route conflict
- All content in Vietnamese, mobile responsive

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 02:02:42 +07:00
parent ad7713968a
commit 5e44456d11
10 changed files with 1254 additions and 9 deletions

View File

@@ -1,12 +1,16 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const publicPaths = ['/login', '/register'];
const publicPaths = ['/login', '/register', '/search'];
const publicExactPaths = ['/'];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const isPublicPath = publicPaths.some((path) => pathname.startsWith(path));
const isPublicPath =
publicExactPaths.includes(pathname) ||
publicPaths.some((path) => pathname.startsWith(path));
// We check for the token cookie or rely on client-side auth store.
// For SSR-safe auth, check a lightweight cookie set by the client after login.
@@ -18,8 +22,9 @@ export function middleware(request: NextRequest) {
return NextResponse.redirect(loginUrl);
}
if (isPublicPath && hasAuthCookie) {
return NextResponse.redirect(new URL('/', request.url));
const isAuthOnlyPath = ['/login', '/register'].some((path) => pathname.startsWith(path));
if (isAuthOnlyPath && hasAuthCookie) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();