diff --git a/apps/web/app/(dashboard)/page.tsx b/apps/web/app/(dashboard)/dashboard/page.tsx
similarity index 87%
rename from apps/web/app/(dashboard)/page.tsx
rename to apps/web/app/(dashboard)/dashboard/page.tsx
index 7ef7384..f5c02e9 100644
--- a/apps/web/app/(dashboard)/page.tsx
+++ b/apps/web/app/(dashboard)/dashboard/page.tsx
@@ -2,13 +2,13 @@ import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-export default function HomePage() {
+export default function DashboardPage() {
return (
-
Chào mừng đến GoodGo
+
Bảng điều khiển
- Nền tảng bất động sản thông minh tại Việt Nam
+ Quản lý tin đăng bất động sản của bạn
@@ -43,7 +43,7 @@ export default function HomePage() {
Tìm bất động sản phù hợp nhu cầu
-
+
diff --git a/apps/web/app/(dashboard)/layout.tsx b/apps/web/app/(dashboard)/layout.tsx
index 2700c50..c1fe4d9 100644
--- a/apps/web/app/(dashboard)/layout.tsx
+++ b/apps/web/app/(dashboard)/layout.tsx
@@ -7,7 +7,7 @@ import { useAuthStore } from '@/lib/auth-store';
import { Button } from '@/components/ui/button';
const navItems = [
- { href: '/', label: 'Trang chủ', icon: '🏠' },
+ { href: '/dashboard', label: 'Bảng điều khiển', icon: '🏠' },
{ href: '/listings', label: 'Tin đăng', icon: '📋' },
{ href: '/listings/new', label: 'Đăng tin', icon: '➕' },
];
diff --git a/apps/web/app/(public)/layout.tsx b/apps/web/app/(public)/layout.tsx
new file mode 100644
index 0000000..d7f7f85
--- /dev/null
+++ b/apps/web/app/(public)/layout.tsx
@@ -0,0 +1,114 @@
+'use client';
+
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import { cn } from '@/lib/utils';
+import { useAuthStore } from '@/lib/auth-store';
+import { Button } from '@/components/ui/button';
+
+export default function PublicLayout({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname();
+ const { user } = useAuthStore();
+
+ return (
+
+ );
+}
diff --git a/apps/web/app/(public)/page.tsx b/apps/web/app/(public)/page.tsx
new file mode 100644
index 0000000..2548b4c
--- /dev/null
+++ b/apps/web/app/(public)/page.tsx
@@ -0,0 +1,253 @@
+'use client';
+
+import * as React from 'react';
+import Link from 'next/link';
+import { useRouter } from 'next/navigation';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Select } from '@/components/ui/select';
+import { Card, CardContent } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { PropertyCard } from '@/components/search/property-card';
+import { listingsApi, type ListingDetail } from '@/lib/listings-api';
+import { PROPERTY_TYPES, TRANSACTION_TYPES } from '@/lib/validations/listings';
+
+const DISTRICTS = [
+ { name: 'Quận 1', city: 'Hồ Chí Minh', img: null },
+ { name: 'Quận 2', city: 'Hồ Chí Minh', img: null },
+ { name: 'Quận 7', city: 'Hồ Chí Minh', img: null },
+ { name: 'Bình Thạnh', city: 'Hồ Chí Minh', img: null },
+ { name: 'Thủ Đức', city: 'Hồ Chí Minh', img: null },
+ { name: 'Ba Đình', city: 'Hà Nội', img: null },
+ { name: 'Hoàn Kiếm', city: 'Hà Nội', img: null },
+ { name: 'Hải Châu', city: 'Đà Nẵng', img: null },
+];
+
+const STATS = [
+ { label: 'Tin đăng', value: '10,000+', icon: '🏠' },
+ { label: 'Người dùng', value: '50,000+', icon: '👥' },
+ { label: 'Giao dịch thành công', value: '2,000+', icon: '✅' },
+ { label: 'Tỉnh thành', value: '63', icon: '📍' },
+];
+
+export default function LandingPage() {
+ const router = useRouter();
+ const [searchQuery, setSearchQuery] = React.useState('');
+ const [transactionType, setTransactionType] = React.useState('');
+ const [propertyType, setPropertyType] = React.useState('');
+ const [featuredListings, setFeaturedListings] = React.useState
([]);
+ const [loadingFeatured, setLoadingFeatured] = React.useState(true);
+
+ React.useEffect(() => {
+ listingsApi
+ .search({ status: 'ACTIVE', limit: 6 })
+ .then((res) => setFeaturedListings(res.data))
+ .catch(() => {})
+ .finally(() => setLoadingFeatured(false));
+ }, []);
+
+ const handleSearch = (e: React.FormEvent) => {
+ e.preventDefault();
+ const params = new URLSearchParams();
+ if (searchQuery) params.set('q', searchQuery);
+ if (transactionType) params.set('transactionType', transactionType);
+ if (propertyType) params.set('propertyType', propertyType);
+ router.push(`/search?${params.toString()}`);
+ };
+
+ return (
+
+ {/* Hero Section */}
+
+
+
+
+ Tìm kiếm bất động sản
+ hoàn hảo
+
+
+ Nền tảng bất động sản thông minh tại Việt Nam — mua bán, cho thuê nhà đất dễ dàng
+
+
+ {/* Search Bar */}
+
+
+ {/* Quick property type links */}
+
+ {PROPERTY_TYPES.map((pt) => (
+
+
+ {pt.label}
+
+
+ ))}
+
+
+
+
+
+ {/* Featured Listings */}
+
+
+
+
+
Tin đăng nổi bật
+
+ Khám phá các bất động sản được quan tâm nhất
+
+
+
+
+
+
+
+ {loadingFeatured ? (
+
+ ) : featuredListings.length > 0 ? (
+
+ {featuredListings.map((listing) => (
+
+ ))}
+
+ ) : (
+
+
Chưa có tin đăng nổi bật
+
+ )}
+
+
+
+ {/* Districts / Quick Links */}
+
+
+
Khu vực nổi bật
+
+ Tìm kiếm theo quận huyện phổ biến
+
+
+
+ {DISTRICTS.map((district) => (
+
+
+
+
+ {district.name}
+ {district.city}
+
+
+
+ ))}
+
+
+
+
+ {/* Market Stats */}
+
+
+
+
GoodGo trong số liệu
+
+ Nền tảng bất động sản đáng tin cậy tại Việt Nam
+
+
+
+
+ {STATS.map((stat) => (
+
+
{stat.icon}
+
{stat.value}
+
{stat.label}
+
+ ))}
+
+
+
+
+ {/* CTA Section */}
+
+
+
+ Bạn có bất động sản muốn đăng?
+
+
+ Đăng tin miễn phí ngay hôm nay, tiếp cận hàng ngàn người mua tiềm năng
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/app/(public)/search/page.tsx b/apps/web/app/(public)/search/page.tsx
new file mode 100644
index 0000000..d68e96e
--- /dev/null
+++ b/apps/web/app/(public)/search/page.tsx
@@ -0,0 +1,264 @@
+'use client';
+
+import * as React from 'react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { Button } from '@/components/ui/button';
+import { FilterBar, type SearchFilters } from '@/components/search/filter-bar';
+import { SearchResults } from '@/components/search/search-results';
+import { ListingMap } from '@/components/map/listing-map';
+import { listingsApi, type ListingDetail, type PaginatedResult } from '@/lib/listings-api';
+
+type ViewMode = 'list' | 'map' | 'split';
+
+const defaultFilters: SearchFilters = {
+ transactionType: '',
+ propertyType: '',
+ city: '',
+ district: '',
+ minPrice: '',
+ maxPrice: '',
+ minArea: '',
+ maxArea: '',
+ bedrooms: '',
+ sort: '',
+};
+
+function SearchContent() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const [filters, setFilters] = React.useState(() => ({
+ ...defaultFilters,
+ transactionType: searchParams.get('transactionType') || '',
+ propertyType: searchParams.get('propertyType') || '',
+ city: searchParams.get('city') || '',
+ district: searchParams.get('district') || '',
+ minPrice: searchParams.get('minPrice') || '',
+ maxPrice: searchParams.get('maxPrice') || '',
+ bedrooms: searchParams.get('bedrooms') || '',
+ sort: searchParams.get('sort') || '',
+ }));
+
+ const [page, setPage] = React.useState(Number(searchParams.get('page')) || 1);
+ const [result, setResult] = React.useState | null>(null);
+ const [loading, setLoading] = React.useState(true);
+ const [viewMode, setViewMode] = React.useState('list');
+ const [showMobileFilters, setShowMobileFilters] = React.useState(false);
+
+ const fetchListings = React.useCallback(() => {
+ setLoading(true);
+ const params: Record = {
+ page,
+ limit: 12,
+ status: 'ACTIVE',
+ };
+ if (filters.transactionType) params['transactionType'] = filters.transactionType;
+ if (filters.propertyType) params['propertyType'] = filters.propertyType;
+ if (filters.city) params['city'] = filters.city;
+ if (filters.district) params['district'] = filters.district;
+ if (filters.minPrice) params['minPrice'] = filters.minPrice;
+ if (filters.maxPrice) params['maxPrice'] = filters.maxPrice;
+ if (filters.minArea) params['minArea'] = Number(filters.minArea);
+ if (filters.maxArea) params['maxArea'] = Number(filters.maxArea);
+ if (filters.bedrooms) params['bedrooms'] = Number(filters.bedrooms);
+
+ listingsApi
+ .search(params)
+ .then(setResult)
+ .catch(() => setResult(null))
+ .finally(() => setLoading(false));
+ }, [filters, page]);
+
+ React.useEffect(() => {
+ fetchListings();
+ }, [fetchListings]);
+
+ // Sync filters to URL
+ React.useEffect(() => {
+ const params = new URLSearchParams();
+ Object.entries(filters).forEach(([key, value]) => {
+ if (value) params.set(key, value);
+ });
+ if (page > 1) params.set('page', String(page));
+ const qs = params.toString();
+ router.replace(`/search${qs ? `?${qs}` : ''}`, { scroll: false });
+ }, [filters, page, router]);
+
+ const handleFilterChange = (newFilters: SearchFilters) => {
+ setFilters(newFilters);
+ setPage(1);
+ };
+
+ const handleSearch = () => {
+ setPage(1);
+ fetchListings();
+ };
+
+ const activeFilterCount = Object.entries(filters).filter(
+ ([key, value]) => value && key !== 'sort',
+ ).length;
+
+ return (
+
+ {/* Header */}
+
+
Tìm kiếm bất động sản
+
+ Tìm bất động sản phù hợp với nhu cầu của bạn
+
+
+
+ {/* View Mode Toggle + Mobile Filter Button */}
+
+
+
+
+
+
+
+
+
+
+ {/* Desktop horizontal filter bar */}
+
+
+
+
+ {/* Mobile filter panel */}
+ {showMobileFilters && (
+
+ {
+ handleSearch();
+ setShowMobileFilters(false);
+ }}
+ layout="sidebar"
+ />
+
+ )}
+
+ {/* Content Area */}
+
+ {/* Sidebar filters (desktop, split/list mode) */}
+ {viewMode !== 'map' && (
+
+ )}
+
+ {/* Main content */}
+
+ {viewMode === 'list' && (
+
handleFilterChange({ ...filters, sort })}
+ />
+ )}
+
+ {viewMode === 'map' && (
+
+ )}
+
+ {viewMode === 'split' && (
+
+
+ handleFilterChange({ ...filters, sort })}
+ />
+
+
+
+
+
+ )}
+
+
+
+ );
+}
+
+export default function SearchPage() {
+ return (
+
+
+
+ }
+ >
+