替代 PropertyGuru / iProperty 的房产刊登、搜索、经纪人档案与潜客管理系统
* Preview is for reference only. Actual results may vary depending on the AI model, variable values, and tools used.
You are a senior full-stack developer with expertise in Next.js 16, Tailwind CSS 4, Supabase, and the Malaysian real estate industry. Build a complete Property Listing & Agent Portal for {agency_name}, replacing the core features of PropertyGuru / iProperty.
---
## Project Overview
- Agency Name: {agency_name}
- Agency Type: {agency_type}
- Number of Agents: {num_agents}
- Coverage Area: {coverage_area}
- Primary Brand Color: {primary_color}
---
## Tech Stack
- **Frontend**: Next.js 16 (App Router) + TypeScript
- **Styling**: Tailwind CSS 4 + shadcn/ui (new-york style)
- **Backend**: Supabase (Postgres + Auth + Storage + RLS)
- **Maps**: Google Maps JavaScript API (area-based search)
- **Deployment**: Vercel
- **Fonts**: Inter + Noto Sans SC (via next/font)
---
## Database Schema (Supabase Postgres)
### Table: agents
```
id: uuid PK default gen_random_uuid()
user_id: uuid FK -> auth.users(id) UNIQUE
full_name: text NOT NULL
phone: text NOT NULL
email: text NOT NULL
ren_number: text -- Malaysian estate agent registration (REN/REA/PEA)
profile_photo_url: text
bio: text
specialization: text[] -- e.g. ['condo', 'landed', 'commercial']
coverage_areas: text[] -- e.g. ['Mont Kiara', 'Bangsar', 'KLCC']
years_experience: integer default 0
languages: text[] default ['Chinese', 'English', 'Bahasa']
is_active: boolean default true
created_at: timestamptz default now()
updated_at: timestamptz default now()
```
### Table: properties
```
id: uuid PK default gen_random_uuid()
agent_id: uuid FK -> agents(id) NOT NULL
slug: text UNIQUE NOT NULL
title: text NOT NULL
description: text
listing_type: text NOT NULL CHECK (listing_type IN ('sale', 'rent'))
property_type: text NOT NULL CHECK (property_type IN ('condo', 'apartment', 'terrace', 'semi-d', 'bungalow', 'townhouse', 'penthouse', 'studio', 'commercial-shop', 'commercial-office'))
status: text default 'active' CHECK (status IN ('active', 'pending', 'sold', 'rented', 'withdrawn'))
price: numeric NOT NULL -- in RM
price_per_sqft: numeric
built_up_sqft: integer
land_area_sqft: integer
bedrooms: integer
bathrooms: integer
car_parks: integer
furnishing: text CHECK (furnishing IN ('unfurnished', 'partially-furnished', 'fully-furnished'))
floor_level: text
facing: text
title_type: text CHECK (title_type IN ('freehold', 'leasehold'))
built_year: integer
-- Address
address_line: text
area: text NOT NULL -- e.g. 'Mont Kiara', 'Bangsar'
city: text default 'Kuala Lumpur'
state: text NOT NULL -- Malaysian state
postcode: text
latitude: numeric
longitude: numeric
-- Facilities
facilities: text[] -- e.g. ['swimming-pool', 'gym', 'playground', '24hr-security']
-- Media
photos: text[] -- Supabase Storage URLs
floor_plan_url: text
virtual_tour_url: text
-- SEO
meta_title: text
meta_description: text
-- Stats
view_count: integer default 0
enquiry_count: integer default 0
featured: boolean default false
featured_until: timestamptz
published_at: timestamptz
created_at: timestamptz default now()
updated_at: timestamptz default now()
```
### Table: enquiries
```
id: uuid PK default gen_random_uuid()
property_id: uuid FK -> properties(id) NOT NULL
agent_id: uuid FK -> agents(id) NOT NULL
name: text NOT NULL
phone: text NOT NULL
email: text
message: text
source: text default 'website' CHECK (source IN ('website', 'whatsapp', 'phone', 'walk-in', 'referral'))
status: text default 'new' CHECK (status IN ('new', 'contacted', 'viewing-scheduled', 'negotiating', 'converted', 'lost'))
follow_up_date: date
notes: text
created_at: timestamptz default now()
updated_at: timestamptz default now()
```
### Table: property_views
```
id: bigint PK generated always as identity
property_id: uuid FK -> properties(id)
viewer_ip: text
user_agent: text
created_at: timestamptz default now()
```
### Required Indexes
```sql
CREATE INDEX idx_properties_agent ON properties(agent_id);
CREATE INDEX idx_properties_status ON properties(status);
CREATE INDEX idx_properties_listing_type ON properties(listing_type);
CREATE INDEX idx_properties_property_type ON properties(property_type);
CREATE INDEX idx_properties_area ON properties(area);
CREATE INDEX idx_properties_state ON properties(state);
CREATE INDEX idx_properties_price ON properties(price);
CREATE INDEX idx_properties_bedrooms ON properties(bedrooms);
CREATE INDEX idx_properties_slug ON properties(slug);
CREATE INDEX idx_enquiries_agent ON enquiries(agent_id);
CREATE INDEX idx_enquiries_property ON enquiries(property_id);
CREATE INDEX idx_enquiries_status ON enquiries(status);
CREATE INDEX idx_property_views_property ON property_views(property_id);
```
### RLS Policies
```
-- properties: public read for active listings, agents can only edit their own
-- enquiries: agents can only view their own leads
-- agents: public read for is_active agents
-- property_views: public INSERT, agents read their own property views
```
---
## Page Structure (App Router)
```
src/app/
├── page.tsx # Homepage (featured listings + search)
├── properties/
│ ├── page.tsx # Property search (filters + list/map)
│ └── [slug]/
│ └── page.tsx # Property detail (gallery + enquiry)
├── agents/
│ ├── page.tsx # Agent directory
│ └── [id]/
│ └── page.tsx # Agent profile + their listings
├── mortgage-calculator/
│ └── page.tsx # Mortgage calculator
├── dashboard/
│ ├── layout.tsx # Agent dashboard layout (auth required)
│ ├── page.tsx # Dashboard home (stats overview)
│ ├── listings/
│ │ ├── page.tsx # My listings table
│ │ ├── new/page.tsx # Create new listing
│ │ └── [id]/edit/page.tsx # Edit listing
│ ├── enquiries/
│ │ └── page.tsx # Lead management (kanban/list)
│ └── profile/
│ └── page.tsx # Edit agent profile
├── api/
│ ├── properties/route.ts # GET listings API
│ ├── enquiries/route.ts # POST submit enquiry
│ └── upload/route.ts # Image upload to Supabase Storage
```
---
## Core Feature Specifications
### 1. Homepage (/)
- Hero section: full-width background + search bar (area, type, buy/rent, price range)
- Featured listings carousel (featured = true properties)
- Popular area cards (Mont Kiara, Bangsar, KLCC, Petaling Jaya, etc.)
- Agency intro section showcasing {agency_name}
- Team preview showing top {num_agents} agents
### 2. Property Search (/properties)
- Top search bar: keyword search
- Sidebar filters:
- Buy/Rent toggle
- Area selection (within {coverage_area})
- State select (Selangor, KL, Johor, Penang, etc.)
- Property type multi-select (Condo, Terrace, Semi-D, Bungalow, Apartment, etc.)
- Price range slider (Sale: RM 100K - RM 10M; Rent: RM 500 - RM 50K/month)
- Bedrooms (Studio, 1-5+)
- Bathrooms (1-5+)
- Built-up area range
- Title type (Freehold / Leasehold)
- Furnishing level
- Results area:
- List view / Map view toggle
- Sort: Newest, Price low-high, Price high-low, Size
- Property card: cover photo, price (RM format), title, area, bed/bath/size icons, agent avatar
- Infinite scroll pagination (cursor-based)
### 3. Property Detail (/properties/[slug])
- Photo gallery (lightbox mode with swipe support)
- Property info card: price, type, size, bedrooms, bathrooms, car parks, furnishing, facing, floor, title type, built year
- Address and Google Maps embed
- Facilities tag list
- Floor plan viewer
- Mortgage calculator widget (default rate 4.5%, 30 year term)
- Agent contact card (photo, name, REN number, phone, WhatsApp link)
- Enquiry form (name, phone, email, message, notifies agent on submit)
- Similar properties (same area and type)
- Schema.org PropertyListing structured data
- Dynamic generateMetadata for SEO
### 4. Agent Profile (/agents/[id])
- Profile photo + name + REN number
- Bio, specialty areas, property types, languages, years of experience
- All active listings by this agent (card grid)
- WhatsApp one-click contact button
- Schema.org RealEstateAgent structured data
### 5. Agent Dashboard (/dashboard)
- **Stats cards**: total listings, active listings, total views, total enquiries, new enquiries this month
- **Lead pipeline** (Kanban style):
- Columns: New -> Contacted -> Viewing Scheduled -> Negotiating -> Converted / Lost
- Drag-and-drop to update enquiry status
- Each card shows: lead name, linked property, date, follow-up status
- **My Listings**: table view with search, status filter, edit, withdraw
- **Listing Editor**:
- Multi-step form: Basic Info -> Details -> Location -> Photos Upload -> Preview & Publish
- Multi-image upload (drag-drop + reorder, stored in Supabase Storage)
- Location input: State > Area cascading dropdowns
- Auto-calculate price_per_sqft
- Auto-generate slug
### 6. Mortgage Calculator (/mortgage-calculator)
- Inputs: property price (RM), down payment % (default 10%), interest rate (default 4.5%), loan tenure (default 30 years)
- Outputs: monthly payment, total interest, total repayment
- Amortization schedule chart (principal vs interest over time)
- Stamp duty calculator (Malaysian rates):
- First RM 100K: 1%
- RM 100K - 500K: 2%
- RM 500K - 1M: 3%
- Above RM 1M: 4%
- Legal fees estimation
---
## UI/UX Design Specifications
- **Primary color**: {primary_color}
- **Background**: White (#FFFFFF) + Light gray (#F8FAFC)
- **Text**: Dark (#0F172A) headings, Slate (#475569) body
- **Cards**: White bg + subtle border (#E2E8F0) + hover shadow
- **Price display**: Large font, bold, primary color (format: RM 1,200,000 or RM 3,500/mo)
- **Badges**: Property type as pills, status with color coding (active=green, pending=yellow, sold=red)
- **Responsive**: Mobile-first, search filters in Sheet drawer on mobile
- **Images**: Use next/image, property card covers at 16:10 ratio, blur placeholder
- **Loading states**: Skeleton loading animations
- **Empty states**: Friendly illustrations with guidance copy
---
## Sample Data (Real Malaysian Properties)
Include in seed.sql:
1. **i-Zen Kiara I, Mont Kiara** - Condo, Sale, RM 850,000, 1,200 sqft, 3 bed 2 bath, Freehold, Fully Furnished, Facilities: Pool, Gym, 24hr Security
2. **Bangsar Semi-D** - Semi-Detached, Sale, RM 3,800,000, 3,500 sqft built-up / 4,000 sqft land, 5 bed 4 bath, Freehold, Partially Furnished
3. **The Sentral Residences, KL Sentral** - Condo, Rent, RM 5,500/month, 1,100 sqft, 2 bed 2 bath, Leasehold, Fully Furnished, KLCC View
4. **Setia Eco Park, Shah Alam** - Bungalow, Sale, RM 4,200,000, 5,000 sqft built-up / 8,000 sqft land, 6 bed 5 bath, Freehold
5. **Sky Suites KLCC** - Studio, Rent, RM 2,800/month, 550 sqft, Studio 1 bath, Freehold, Fully Furnished
6. **Taman Tun Dr Ismail Terrace** - Terrace, Sale, RM 1,350,000, 1,800 sqft, 4 bed 3 bath, Freehold, Unfurnished
7. **Pavilion Damansara Heights** - Penthouse, Sale, RM 8,500,000, 4,800 sqft, 4 bed 5 bath, Freehold, Fully Furnished, Private Pool
8. **Tropicana Gardens, Kota Damansara** - Apartment, Rent, RM 2,200/month, 850 sqft, 2 bed 1 bath, Leasehold, Partially Furnished
Sample agents (2-3):
- Tan Wei Ming (REN 12345), specializes in Mont Kiara / Bangsar, 8 years experience
- Sarah Lim (REN 23456), specializes in KLCC / KL Sentral, 5 years experience
- David Wong (REN 34567), specializes in Petaling Jaya / Shah Alam, 12 years experience
---
## Business Logic
1. **Enquiry notifications**: On new enquiry submission, trigger Supabase Edge Function to send WhatsApp/Email notification to the assigned agent
2. **Auto-delist**: When property status changes to sold/rented, automatically hide from public search results
3. **Featured listings**: Show properties with featured = true AND featured_until > now() on homepage
4. **View tracking**: Record property_views on each detail page visit, increment view_count
5. **Slug generation**: Auto-generate URL-friendly slug from property title + area
6. **Image handling**: Compress uploads to reasonable size, generate thumbnail URLs
7. **Search logic**: Combine filter conditions into Supabase query, support multi-condition AND queries
8. **State/Area cascading**: After selecting a state, area dropdown shows only areas within that state
---
## Malaysian-Specific Requirements
- Currency: RM (Malaysian Ringgit) with thousands separator
- Area unit: sq ft (square feet)
- States: Kuala Lumpur, Selangor, Johor, Penang, Perak, Sabah, Sarawak, Melaka, Negeri Sembilan, Kedah, Pahang, Kelantan, Terengganu, Perlis, Putrajaya, Labuan
- Property types: Condo, Apartment, Terrace / Terraced House, Semi-Detached (Semi-D), Bungalow, Townhouse, Penthouse, Studio, Commercial Shop, Commercial Office
- SPA terms: Sale and Purchase Agreement, Stamp Duty, Legal Fees, Loan Agreement
- Agent registration: REN (Real Estate Negotiator), REA (Real Estate Agent), PEA (Probationary Estate Agent)
- Language: English-primary interface (Malaysian property market convention), Chinese secondary
---
## Performance Requirements
- Search results page LCP < 2.5s
- Use Server Components for property data fetching, no API round-trip
- Images via next/image with auto-optimization + lazy loading
- Search filters stored in URL searchParams (shareable links)
- Cursor-based pagination (no OFFSET)
- React Suspense + Skeleton for streaming loading
Generate complete, runnable code including all files. Ensure clean architecture, componentized structure, and full type safety.