From 7f50ea4d8f12a71f691db197fb512110b4fbe107 Mon Sep 17 00:00:00 2001 From: FORGE Date: Mon, 6 Apr 2026 20:08:06 +0000 Subject: [PATCH 01/22] feat: Frontend Project Setup - Config Files Run: e59d39c7-91f6-466a-9a7c-46014dbfef09 Task: 20cf341f-8022-465d-a3f7-6bf6a94765a5 Agent: builder --- ARCHITECTURE.md | 129 ++++++++++++++++ SETUP.md | 57 +++++++ index.html | 13 ++ package.json | 30 ++++ src/data/mockAgents.ts | 89 +++++++++++ src/data/mockNeighborhoods.ts | 105 +++++++++++++ src/data/mockProperties.ts | 246 ++++++++++++++++++++++++++++++ src/index.css | 1 + src/main.tsx | 25 ++++ src/types/models.ts | 143 ++++++++++++++++++ src/vite-env.d.ts | 1 + tests/models.test.ts | 271 ++++++++++++++++++++++++++++++++++ tsconfig.app.json | 27 ++++ tsconfig.json | 6 + vite.config.ts | 18 +++ 15 files changed, 1161 insertions(+) create mode 100644 ARCHITECTURE.md create mode 100644 SETUP.md create mode 100644 index.html create mode 100644 package.json create mode 100644 src/data/mockAgents.ts create mode 100644 src/data/mockNeighborhoods.ts create mode 100644 src/data/mockProperties.ts create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 src/types/models.ts create mode 100644 src/vite-env.d.ts create mode 100644 tests/models.test.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..92100c8 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,129 @@ +# Real Estate Website — Architecture Document + +## Overview + +A modern real estate listing website built with React, Vite, TypeScript, and Tailwind CSS. The application displays property listings, agent profiles, and neighborhood information with a responsive, professional design. + +## Tech Stack + +| Layer | Technology | +| -------------- | --------------------------------- | +| Framework | React 18 | +| Build Tool | Vite 5 | +| Language | TypeScript 5 (strict mode) | +| Styling | Tailwind CSS 4 (via @tailwindcss/vite) | +| Routing | React Router v6 | +| Icons | Lucide React | +| Images | Unsplash source URLs (mock data) | + +## Project Structure + +``` +├── ARCHITECTURE.md +├── SETUP.md +├── index.html +├── package.json +├── tsconfig.json +├── tsconfig.app.json +├── vite.config.ts +├── src/ +│ ├── main.tsx # Application entry point +│ ├── App.tsx # Root component with router +│ ├── index.css # Tailwind CSS imports & global styles +│ ├── vite-env.d.ts # Vite client type declarations +│ ├── types/ +│ │ └── models.ts # TypeScript interfaces & type aliases +│ ├── data/ +│ │ ├── mockProperties.ts # Mock property listings +│ │ ├── mockAgents.ts # Mock agent profiles +│ │ └── mockNeighborhoods.ts # Mock neighborhood data +│ ├── components/ +│ │ ├── atoms/ # Small, reusable UI primitives +│ │ ├── molecules/ # Composite components +│ │ └── layout/ # Header, Footer, Layout wrappers +│ └── pages/ # Route-level page components +│ ├── HomePage.tsx +│ ├── PropertiesPage.tsx +│ ├── PropertyDetailPage.tsx +│ ├── AgentsPage.tsx +│ ├── NeighborhoodsPage.tsx +│ └── ContactPage.tsx +└── tests/ + └── *.test.ts +``` + +## Component Hierarchy + +### Atoms +- **Button** — Reusable button with variant props +- **Badge** — Status/tag badge (e.g., "For Sale", "Pending") +- **PriceTag** — Formatted currency display +- **Input** — Styled form input + +### Molecules +- **PropertyCard** — Thumbnail card for property listings +- **AgentCard** — Agent profile summary card +- **NeighborhoodCard** — Neighborhood overview card +- **SearchBar** — Property search/filter bar +- **ContactForm** — Contact inquiry form +- **ImageGallery** — Property image carousel/gallery + +### Layout +- **Header** — Top navigation bar with logo and links +- **Footer** — Site footer with links and info +- **Layout** — Wraps Header + main content + Footer + +### Pages +- **HomePage** — Hero section, featured properties, neighborhoods +- **PropertiesPage** — Full listings grid with filters +- **PropertyDetailPage** — Single property with gallery, details, agent info +- **AgentsPage** — Team/agent directory +- **NeighborhoodsPage** — Neighborhood guide +- **ContactPage** — Contact form and office info + +## Routing Structure (React Router v6) + +| Path | Component | +| -------------------------- | -------------------- | +| `/` | HomePage | +| `/properties` | PropertiesPage | +| `/properties/:slug` | PropertyDetailPage | +| `/agents` | AgentsPage | +| `/neighborhoods` | NeighborhoodsPage | +| `/contact` | ContactPage | + +## Data Models + +All TypeScript interfaces are defined in `src/types/models.ts`: + +- **Property** — Full property listing with address, specs, images, agent reference +- **Agent** — Real estate agent profile +- **Neighborhood** — Area/neighborhood information +- **ContactFormData** — Contact form submission payload +- **PropertyType** — Union type: `'house' | 'condo' | 'townhouse' | 'apartment' | 'land'` +- **PropertyStatus** — Union type: `'for-sale' | 'pending' | 'sold'` +- **PreferredContact** — Union type: `'email' | 'phone' | 'either'` + +## Mock Data Strategy + +During development, all data comes from static mock arrays in `src/data/`. Property and agent images use specific Unsplash photo URLs with `w`, `h`, and `fit=crop` query parameters for consistent sizing. + +Helper functions are exported alongside the data arrays for common lookups (by slug, by status, by ID, featured items). + +## Styling Approach + +- **Tailwind CSS 4** via the `@tailwindcss/vite` plugin (no PostCSS config needed) +- Utility-first classes applied directly in JSX +- Design tokens: + - Primary color: blue-600 / blue-700 + - Accent color: amber-500 + - Neutral palette: slate-50 through slate-900 + - Border radius: rounded-lg (cards), rounded-xl (hero elements) + - Shadows: shadow-md (cards), shadow-lg (modals/overlays) + +## Build Tooling + +- **Vite 5** — Fast HMR, optimized production builds +- **TypeScript 5** — Strict mode enabled in tsconfig +- **ESLint** — Linting (configured separately) +- **Prettier** — Code formatting (configured separately) diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..d40ff92 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,57 @@ +# Setup Instructions + +## Prerequisites + +- Node.js >= 18.0.0 +- npm >= 9.0.0 + +## Install Dependencies + +```bash +npm install +``` + +This will generate `package-lock.json` and `node_modules/` — both are excluded from version control. + +## Development Server + +```bash +npm run dev +``` + +Opens the app at [http://localhost:5173](http://localhost:5173) with hot module replacement. + +## Run Tests + +```bash +npm test +``` + +Or in watch mode: + +```bash +npm run test:watch +``` + +## Production Build + +```bash +npm run build +``` + +Output is written to `dist/`. + +## Preview Production Build + +```bash +npm run preview +``` + +## Generated Files (Do Not Commit) + +The following are generated by tooling and must not be hand-written or committed: + +- `node_modules/` +- `package-lock.json` +- `dist/` +- `.vite/` diff --git a/index.html b/index.html new file mode 100644 index 0000000..00d010f --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Real Estate — Find Your Dream Home + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..255bbfe --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "real-estate-website", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "A modern real estate listing website built with React, Vite, and TypeScript", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "tailwindcss": "^4.0.0", + "typescript": "~5.6.0", + "vite": "^6.0.0", + "vitest": "^2.1.0" + } +} diff --git a/src/data/mockAgents.ts b/src/data/mockAgents.ts new file mode 100644 index 0000000..1572988 --- /dev/null +++ b/src/data/mockAgents.ts @@ -0,0 +1,89 @@ +/** + * Mock agent data for development. + * + * Provides a static array of agent profiles and a lookup helper. + */ + +import type { Agent } from "../types/models"; + +/** Sample real estate agents. */ +export const MOCK_AGENTS: Agent[] = [ + { + id: "agent-1", + name: "James Mitchell", + title: "Senior Real Estate Agent", + phone: "(555) 123-4567", + email: "james.mitchell@realestate.com", + photo: + "https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&h=400&fit=crop", + bio: "With over 15 years of experience in luxury residential real estate, James has helped hundreds of families find their dream homes. He specializes in waterfront properties and historic estates.", + specialties: ["Luxury Homes", "Waterfront Properties", "Historic Estates"], + propertiesCount: 24, + rating: 4.9, + socialLinks: { + linkedin: "https://linkedin.com/in/jamesmitchell", + twitter: "https://twitter.com/jamesmitchell", + }, + }, + { + id: "agent-2", + name: "Sarah Chen", + title: "Buyer's Specialist", + phone: "(555) 234-5678", + email: "sarah.chen@realestate.com", + photo: + "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=400&fit=crop", + bio: "Sarah is passionate about helping first-time buyers navigate the market with confidence. Her deep knowledge of urban condos and townhouses makes her an invaluable resource.", + specialties: ["First-Time Buyers", "Condos", "Townhouses"], + propertiesCount: 18, + rating: 4.8, + socialLinks: { + linkedin: "https://linkedin.com/in/sarahchen", + instagram: "https://instagram.com/sarahchenrealty", + }, + }, + { + id: "agent-3", + name: "Michael Torres", + title: "Commercial & Residential Agent", + phone: "(555) 345-6789", + email: "michael.torres@realestate.com", + photo: + "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=400&fit=crop", + bio: "Michael brings a unique dual expertise in both commercial and residential markets. His analytical approach and strong negotiation skills consistently deliver exceptional results for his clients.", + specialties: ["Commercial Properties", "Investment Properties", "Negotiation"], + propertiesCount: 31, + rating: 4.7, + socialLinks: { + linkedin: "https://linkedin.com/in/michaeltorres", + facebook: "https://facebook.com/michaeltorresrealty", + }, + }, + { + id: "agent-4", + name: "Emily Rodriguez", + title: "Luxury Property Specialist", + phone: "(555) 456-7890", + email: "emily.rodriguez@realestate.com", + photo: + "https://images.unsplash.com/photo-1580489944761-15a19d654956?w=400&h=400&fit=crop", + bio: "Emily's eye for design and deep connections in the luxury market set her apart. She works closely with architects and designers to stage and present properties at their absolute best.", + specialties: ["Luxury Homes", "Interior Staging", "New Construction"], + propertiesCount: 15, + rating: 5.0, + socialLinks: { + linkedin: "https://linkedin.com/in/emilyrodriguez", + instagram: "https://instagram.com/emilyrodriguezluxury", + }, + }, +]; + +/** + * Look up a single agent by their unique identifier. + * + * @param id - The agent ID to search for. + * @returns The matching Agent, or undefined if not found. + */ +export function getAgentById(id: string): Agent | undefined { + return MOCK_AGENTS.find((agent) => agent.id === id); +} diff --git a/src/data/mockNeighborhoods.ts b/src/data/mockNeighborhoods.ts new file mode 100644 index 0000000..eb29056 --- /dev/null +++ b/src/data/mockNeighborhoods.ts @@ -0,0 +1,105 @@ +/** + * Mock neighborhood data for development. + * + * Provides a static array of neighborhoods and a lookup helper. + */ + +import type { Neighborhood } from "../types/models"; + +/** Sample neighborhoods. */ +export const MOCK_NEIGHBORHOODS: Neighborhood[] = [ + { + id: "neighborhood-1", + name: "Lakewood Hills", + slug: "lakewood-hills", + city: "Austin", + state: "TX", + description: + "An exclusive lakeside community known for its luxury estates, scenic trails, and top-rated schools. Residents enjoy private lake access, boat docks, and a peaceful retreat minutes from downtown.", + image: + "https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=800&h=600&fit=crop", + averagePrice: 1_100_000, + walkScore: 45, + transitScore: 30, + highlights: [ + "Private Lake Access", + "Top-Rated Schools", + "Scenic Hiking Trails", + "Gated Communities", + ], + featuredProperties: ["prop-1"], + }, + { + id: "neighborhood-2", + name: "Downtown", + slug: "downtown", + city: "Austin", + state: "TX", + description: + "The vibrant urban core with world-class dining, live music venues, and stunning skyline views. High-rise condos and lofts put you steps away from everything the city has to offer.", + image: + "https://images.unsplash.com/photo-1444723121867-7a241cacace9?w=800&h=600&fit=crop", + averagePrice: 750_000, + walkScore: 95, + transitScore: 85, + highlights: [ + "Live Music Capital", + "Walkable Everywhere", + "Rooftop Bars & Restaurants", + "Lady Bird Lake Access", + ], + featuredProperties: ["prop-2"], + }, + { + id: "neighborhood-3", + name: "South Congress", + slug: "south-congress", + city: "Austin", + state: "TX", + description: + "Eclectic and colorful, SoCo is famous for its boutique shopping, food trucks, and artistic vibe. Charming bungalows and renovated homes line the tree-shaded streets just south of the river.", + image: + "https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?w=800&h=600&fit=crop", + averagePrice: 550_000, + walkScore: 82, + transitScore: 60, + highlights: [ + "Boutique Shopping", + "Food Truck Paradise", + "Art Galleries", + "Historic Architecture", + ], + featuredProperties: ["prop-3"], + }, + { + id: "neighborhood-4", + name: "Westlake Hills", + slug: "westlake-hills", + city: "Austin", + state: "TX", + description: + "A prestigious enclave in the Texas Hill Country offering panoramic views, sprawling estates, and acclaimed Eanes ISD schools. The area blends natural beauty with sophisticated luxury living.", + image: + "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&h=600&fit=crop", + averagePrice: 1_500_000, + walkScore: 25, + transitScore: 15, + highlights: [ + "Hill Country Views", + "Eanes ISD Schools", + "Nature Preserves", + "Luxury Estates", + ], + featuredProperties: ["prop-4"], + }, +]; + +/** + * Look up a single neighborhood by its URL slug. + * + * @param slug - The URL-friendly slug to search for. + * @returns The matching Neighborhood, or undefined if not found. + */ +export function getNeighborhoodBySlug(slug: string): Neighborhood | undefined { + return MOCK_NEIGHBORHOODS.find((n) => n.slug === slug); +} diff --git a/src/data/mockProperties.ts b/src/data/mockProperties.ts new file mode 100644 index 0000000..01e2938 --- /dev/null +++ b/src/data/mockProperties.ts @@ -0,0 +1,246 @@ +/** + * Mock property listing data for development. + * + * Provides a static array of properties and several lookup / filter helpers. + */ + +import type { Property, PropertyStatus } from "../types/models"; +import { MOCK_AGENTS } from "./mockAgents"; + +/** Sample property listings. */ +export const MOCK_PROPERTIES: Property[] = [ + { + id: "prop-1", + title: "Modern Lakefront Estate", + slug: "modern-lakefront-estate", + price: 1_250_000, + address: "742 Lakeview Drive", + city: "Austin", + state: "TX", + zipCode: "78701", + propertyType: "house", + bedrooms: 5, + bathrooms: 4.5, + squareFeet: 4_200, + lotSize: 12_000, + yearBuilt: 2021, + description: + "Stunning modern estate overlooking the lake with floor-to-ceiling windows, an open-concept living area, gourmet kitchen with premium appliances, and a resort-style backyard featuring an infinity pool and outdoor kitchen.", + features: [ + "Infinity Pool", + "Smart Home System", + "Wine Cellar", + "3-Car Garage", + "Home Theater", + "Waterfront Access", + ], + images: [ + "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + ], + agent: MOCK_AGENTS[0]!, + neighborhood: "Lakewood Hills", + listingDate: "2024-10-15", + status: "for-sale", + }, + { + id: "prop-2", + title: "Downtown Luxury Penthouse", + slug: "downtown-luxury-penthouse", + price: 875_000, + address: "100 Congress Ave, Unit PH-1", + city: "Austin", + state: "TX", + zipCode: "78702", + propertyType: "condo", + bedrooms: 3, + bathrooms: 2.5, + squareFeet: 2_800, + yearBuilt: 2023, + description: + "Breathtaking penthouse in the heart of downtown with panoramic skyline views, designer finishes throughout, a private rooftop terrace, and access to world-class building amenities.", + features: [ + "Rooftop Terrace", + "Concierge Service", + "Floor-to-Ceiling Windows", + "Fitness Center", + "Valet Parking", + ], + images: [ + "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + ], + agent: MOCK_AGENTS[1]!, + neighborhood: "Downtown", + listingDate: "2024-11-01", + status: "for-sale", + }, + { + id: "prop-3", + title: "Charming Craftsman Bungalow", + slug: "charming-craftsman-bungalow", + price: 525_000, + address: "318 Oak Street", + city: "Austin", + state: "TX", + zipCode: "78704", + propertyType: "house", + bedrooms: 3, + bathrooms: 2, + squareFeet: 1_850, + lotSize: 6_500, + yearBuilt: 1935, + description: + "Beautifully restored 1935 Craftsman bungalow with original hardwood floors, built-in bookshelves, updated kitchen and bathrooms, and a lush private garden. Walking distance to vibrant South Congress shops and restaurants.", + features: [ + "Original Hardwood Floors", + "Updated Kitchen", + "Private Garden", + "Front Porch", + "Detached Studio", + ], + images: [ + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + ], + agent: MOCK_AGENTS[3]!, + neighborhood: "South Congress", + listingDate: "2024-09-20", + status: "for-sale", + }, + { + id: "prop-4", + title: "Contemporary Hillside Villa", + slug: "contemporary-hillside-villa", + price: 1_750_000, + address: "5500 Mountain Ridge Road", + city: "Austin", + state: "TX", + zipCode: "78730", + propertyType: "house", + bedrooms: 6, + bathrooms: 5, + squareFeet: 5_500, + lotSize: 20_000, + yearBuilt: 2022, + description: + "Architectural masterpiece perched on a hilltop with sweeping Hill Country views. Features include a cantilevered deck, chef's kitchen, primary suite with spa bath, and a separate guest casita.", + features: [ + "Hill Country Views", + "Guest Casita", + "Chef's Kitchen", + "Spa Bathroom", + "Solar Panels", + "EV Charger", + ], + images: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + ], + agent: MOCK_AGENTS[0]!, + neighborhood: "Westlake Hills", + listingDate: "2024-11-10", + status: "for-sale", + }, + { + id: "prop-5", + title: "Urban Loft in East Side", + slug: "urban-loft-east-side", + price: 415_000, + address: "200 East 6th Street, Unit 4B", + city: "Austin", + state: "TX", + zipCode: "78702", + propertyType: "apartment", + bedrooms: 2, + bathrooms: 2, + squareFeet: 1_400, + yearBuilt: 2019, + description: + "Industrial-chic loft with exposed brick, polished concrete floors, 14-foot ceilings, and a private balcony overlooking the East Side arts district. Building includes rooftop pool and co-working lounge.", + features: [ + "Exposed Brick", + "14-Foot Ceilings", + "Rooftop Pool", + "Co-Working Lounge", + "Balcony", + ], + images: [ + "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + ], + agent: MOCK_AGENTS[1]!, + neighborhood: "East Side", + listingDate: "2024-10-28", + status: "pending", + }, + { + id: "prop-6", + title: "Classic Colonial on Maple Lane", + slug: "classic-colonial-maple-lane", + price: 680_000, + address: "45 Maple Lane", + city: "Austin", + state: "TX", + zipCode: "78703", + propertyType: "townhouse", + bedrooms: 4, + bathrooms: 3.5, + squareFeet: 3_200, + lotSize: 8_000, + yearBuilt: 2005, + description: + "Elegant colonial-style townhouse in a tree-lined neighborhood. Formal living and dining rooms, a sun-drenched family room, updated chef's kitchen, and a spacious primary suite. Community pool and tennis courts included.", + features: [ + "Community Pool", + "Tennis Courts", + "Updated Kitchen", + "Fireplace", + "Two-Car Garage", + "Fenced Yard", + ], + images: [ + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + ], + agent: MOCK_AGENTS[2]!, + neighborhood: "Tarrytown", + listingDate: "2024-08-15", + status: "sold", + }, +]; + +/** + * Look up a single property by its URL slug. + * + * @param slug - The URL-friendly slug to search for. + * @returns The matching Property, or undefined if not found. + */ +export function getPropertyBySlug(slug: string): Property | undefined { + return MOCK_PROPERTIES.find((property) => property.slug === slug); +} + +/** + * Filter properties by their listing status. + * + * @param status - The PropertyStatus to filter by. + * @returns An array of properties matching the given status. + */ +export function getPropertiesByStatus(status: PropertyStatus): Property[] { + return MOCK_PROPERTIES.filter((property) => property.status === status); +} + +/** + * Return featured properties (the first 3 listings). + * + * @returns An array of up to 3 featured Property objects. + */ +export function getFeaturedProperties(): Property[] { + return MOCK_PROPERTIES.slice(0, 3); +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/src/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..f0d17a3 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; + +/** + * Application entry point. + * + * Mounts the React root into the #root DOM element. + * The full App component with routing will be added in a later phase. + */ +const rootElement = document.getElementById("root"); + +if (!rootElement) { + throw new Error("Root element not found. Ensure index.html contains
."); +} + +ReactDOM.createRoot(rootElement).render( + +
+

+ Real Estate Website — Coming Soon +

+
+
, +); diff --git a/src/types/models.ts b/src/types/models.ts new file mode 100644 index 0000000..de401c8 --- /dev/null +++ b/src/types/models.ts @@ -0,0 +1,143 @@ +/** + * Domain data models for the real estate website. + * + * All TypeScript interfaces and type aliases used across the application + * are defined here as the single source of truth. + */ + +/** The kind of real estate property. */ +export type PropertyType = "house" | "condo" | "townhouse" | "apartment" | "land"; + +/** Current listing status of a property. */ +export type PropertyStatus = "for-sale" | "pending" | "sold"; + +/** How the user prefers to be contacted. */ +export type PreferredContact = "email" | "phone" | "either"; + +/** Social media links for an agent profile. */ +export interface SocialLinks { + /** LinkedIn profile URL. */ + linkedin?: string; + /** Twitter/X profile URL. */ + twitter?: string; + /** Facebook profile URL. */ + facebook?: string; + /** Instagram profile URL. */ + instagram?: string; +} + +/** A real estate agent. */ +export interface Agent { + /** Unique identifier. */ + id: string; + /** Full display name. */ + name: string; + /** Professional title (e.g., "Senior Real Estate Agent"). */ + title: string; + /** Contact phone number. */ + phone: string; + /** Contact email address. */ + email: string; + /** URL to the agent's headshot photo. */ + photo: string; + /** Short biography. */ + bio: string; + /** List of specialisation areas. */ + specialties: string[]; + /** Number of properties currently managed. */ + propertiesCount: number; + /** Average client rating (0–5). */ + rating: number; + /** Optional social media links. */ + socialLinks: SocialLinks; +} + +/** A property listing. */ +export interface Property { + /** Unique identifier. */ + id: string; + /** Display title / headline. */ + title: string; + /** URL-friendly slug derived from the title. */ + slug: string; + /** Listing price in USD (whole dollars). */ + price: number; + /** Street address. */ + address: string; + /** City name. */ + city: string; + /** State abbreviation. */ + state: string; + /** ZIP / postal code. */ + zipCode: string; + /** Type of property. */ + propertyType: PropertyType; + /** Number of bedrooms. */ + bedrooms: number; + /** Number of bathrooms (supports half-baths as 0.5). */ + bathrooms: number; + /** Interior living area in square feet. */ + squareFeet: number; + /** Total lot size in square feet (optional for condos). */ + lotSize?: number; + /** Year the structure was built. */ + yearBuilt: number; + /** Full-text description of the property. */ + description: string; + /** Notable features / amenities. */ + features: string[]; + /** Ordered list of image URLs (first is the hero image). */ + images: string[]; + /** The listing agent. */ + agent: Agent; + /** Name of the neighborhood this property belongs to. */ + neighborhood: string; + /** ISO-8601 date string of when the listing was published. */ + listingDate: string; + /** Current listing status. */ + status: PropertyStatus; +} + +/** A neighborhood / area guide. */ +export interface Neighborhood { + /** Unique identifier. */ + id: string; + /** Display name. */ + name: string; + /** URL-friendly slug. */ + slug: string; + /** City the neighborhood is in. */ + city: string; + /** State abbreviation. */ + state: string; + /** Descriptive overview of the area. */ + description: string; + /** Hero image URL. */ + image: string; + /** Median / average property price in the area. */ + averagePrice: number; + /** Walk Score® (0–100). */ + walkScore: number; + /** Transit Score® (0–100). */ + transitScore: number; + /** Key neighbourhood highlights / selling points. */ + highlights: string[]; + /** IDs of featured properties in this neighborhood. */ + featuredProperties: string[]; +} + +/** Data submitted through the contact inquiry form. */ +export interface ContactFormData { + /** Full name of the person making the inquiry. */ + name: string; + /** Email address. */ + email: string; + /** Phone number (optional). */ + phone: string; + /** Free-text message body. */ + message: string; + /** Property ID the inquiry is about (if applicable). */ + propertyId?: string; + /** How the user prefers to be reached. */ + preferredContact: PreferredContact; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tests/models.test.ts b/tests/models.test.ts new file mode 100644 index 0000000..9089b1e --- /dev/null +++ b/tests/models.test.ts @@ -0,0 +1,271 @@ +/** + * Tests for TypeScript data models and mock data helpers. + * + * Validates that mock data arrays are well-formed and that helper + * functions return the expected results. + */ + +import { describe, it, expect } from "vitest"; +import type { + Property, + Agent, + Neighborhood, + ContactFormData, + PropertyType, + PropertyStatus, + PreferredContact, +} from "../src/types/models"; +import { + MOCK_PROPERTIES, + getPropertyBySlug, + getPropertiesByStatus, + getFeaturedProperties, +} from "../src/data/mockProperties"; +import { MOCK_AGENTS, getAgentById } from "../src/data/mockAgents"; +import { + MOCK_NEIGHBORHOODS, + getNeighborhoodBySlug, +} from "../src/data/mockNeighborhoods"; + +// --------------------------------------------------------------------------- +// Mock Properties +// --------------------------------------------------------------------------- + +describe("MOCK_PROPERTIES", () => { + it("should contain 6 properties", () => { + expect(MOCK_PROPERTIES).toHaveLength(6); + }); + + it("every property should have required fields", () => { + for (const p of MOCK_PROPERTIES) { + expect(p.id).toBeTruthy(); + expect(p.title).toBeTruthy(); + expect(p.slug).toBeTruthy(); + expect(p.price).toBeGreaterThan(0); + expect(p.address).toBeTruthy(); + expect(p.city).toBeTruthy(); + expect(p.state).toBeTruthy(); + expect(p.zipCode).toBeTruthy(); + expect(p.images.length).toBeGreaterThan(0); + expect(p.agent).toBeDefined(); + expect(p.status).toBeTruthy(); + } + }); + + it("should have unique slugs", () => { + const slugs = MOCK_PROPERTIES.map((p) => p.slug); + expect(new Set(slugs).size).toBe(slugs.length); + }); + + it("should have unique IDs", () => { + const ids = MOCK_PROPERTIES.map((p) => p.id); + expect(new Set(ids).size).toBe(ids.length); + }); +}); + +describe("getPropertyBySlug", () => { + it("should return a property for a valid slug", () => { + const property = getPropertyBySlug("modern-lakefront-estate"); + expect(property).toBeDefined(); + expect(property!.id).toBe("prop-1"); + }); + + it("should return undefined for an invalid slug", () => { + const property = getPropertyBySlug("nonexistent-slug"); + expect(property).toBeUndefined(); + }); +}); + +describe("getPropertiesByStatus", () => { + it("should return only for-sale properties", () => { + const forSale = getPropertiesByStatus("for-sale"); + expect(forSale.length).toBeGreaterThan(0); + for (const p of forSale) { + expect(p.status).toBe("for-sale"); + } + }); + + it("should return pending properties", () => { + const pending = getPropertiesByStatus("pending"); + expect(pending.length).toBeGreaterThan(0); + for (const p of pending) { + expect(p.status).toBe("pending"); + } + }); + + it("should return sold properties", () => { + const sold = getPropertiesByStatus("sold"); + expect(sold.length).toBeGreaterThan(0); + for (const p of sold) { + expect(p.status).toBe("sold"); + } + }); + + it("should return empty array for status with no matches", () => { + // All statuses are covered, but this tests the filter logic + const all = [ + ...getPropertiesByStatus("for-sale"), + ...getPropertiesByStatus("pending"), + ...getPropertiesByStatus("sold"), + ]; + expect(all.length).toBe(MOCK_PROPERTIES.length); + }); +}); + +describe("getFeaturedProperties", () => { + it("should return exactly 3 properties", () => { + const featured = getFeaturedProperties(); + expect(featured).toHaveLength(3); + }); + + it("should return the first 3 properties", () => { + const featured = getFeaturedProperties(); + expect(featured[0]!.id).toBe(MOCK_PROPERTIES[0]!.id); + expect(featured[1]!.id).toBe(MOCK_PROPERTIES[1]!.id); + expect(featured[2]!.id).toBe(MOCK_PROPERTIES[2]!.id); + }); +}); + +// --------------------------------------------------------------------------- +// Mock Agents +// --------------------------------------------------------------------------- + +describe("MOCK_AGENTS", () => { + it("should contain 4 agents", () => { + expect(MOCK_AGENTS).toHaveLength(4); + }); + + it("every agent should have required fields", () => { + for (const a of MOCK_AGENTS) { + expect(a.id).toBeTruthy(); + expect(a.name).toBeTruthy(); + expect(a.title).toBeTruthy(); + expect(a.phone).toBeTruthy(); + expect(a.email).toBeTruthy(); + expect(a.photo).toBeTruthy(); + expect(a.bio).toBeTruthy(); + expect(a.specialties.length).toBeGreaterThan(0); + expect(a.rating).toBeGreaterThanOrEqual(0); + expect(a.rating).toBeLessThanOrEqual(5); + } + }); + + it("should have unique IDs", () => { + const ids = MOCK_AGENTS.map((a) => a.id); + expect(new Set(ids).size).toBe(ids.length); + }); +}); + +describe("getAgentById", () => { + it("should return an agent for a valid ID", () => { + const agent = getAgentById("agent-1"); + expect(agent).toBeDefined(); + expect(agent!.name).toBe("James Mitchell"); + }); + + it("should return undefined for an invalid ID", () => { + const agent = getAgentById("nonexistent-id"); + expect(agent).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// Mock Neighborhoods +// --------------------------------------------------------------------------- + +describe("MOCK_NEIGHBORHOODS", () => { + it("should contain 4 neighborhoods", () => { + expect(MOCK_NEIGHBORHOODS).toHaveLength(4); + }); + + it("every neighborhood should have required fields", () => { + for (const n of MOCK_NEIGHBORHOODS) { + expect(n.id).toBeTruthy(); + expect(n.name).toBeTruthy(); + expect(n.slug).toBeTruthy(); + expect(n.city).toBeTruthy(); + expect(n.state).toBeTruthy(); + expect(n.description).toBeTruthy(); + expect(n.image).toBeTruthy(); + expect(n.averagePrice).toBeGreaterThan(0); + expect(n.walkScore).toBeGreaterThanOrEqual(0); + expect(n.walkScore).toBeLessThanOrEqual(100); + expect(n.transitScore).toBeGreaterThanOrEqual(0); + expect(n.transitScore).toBeLessThanOrEqual(100); + expect(n.highlights.length).toBeGreaterThan(0); + } + }); + + it("should have unique slugs", () => { + const slugs = MOCK_NEIGHBORHOODS.map((n) => n.slug); + expect(new Set(slugs).size).toBe(slugs.length); + }); +}); + +describe("getNeighborhoodBySlug", () => { + it("should return a neighborhood for a valid slug", () => { + const neighborhood = getNeighborhoodBySlug("downtown"); + expect(neighborhood).toBeDefined(); + expect(neighborhood!.name).toBe("Downtown"); + }); + + it("should return undefined for an invalid slug", () => { + const neighborhood = getNeighborhoodBySlug("nonexistent-slug"); + expect(neighborhood).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// Type-level checks (compile-time; runtime assertions for completeness) +// --------------------------------------------------------------------------- + +describe("Type contracts", () => { + it("PropertyType union values should be valid", () => { + const validTypes: PropertyType[] = [ + "house", + "condo", + "townhouse", + "apartment", + "land", + ]; + for (const p of MOCK_PROPERTIES) { + expect(validTypes).toContain(p.propertyType); + } + }); + + it("PropertyStatus union values should be valid", () => { + const validStatuses: PropertyStatus[] = ["for-sale", "pending", "sold"]; + for (const p of MOCK_PROPERTIES) { + expect(validStatuses).toContain(p.status); + } + }); + + it("ContactFormData shape should be constructible", () => { + const form: ContactFormData = { + name: "Test User", + email: "test@example.com", + phone: "(555) 000-0000", + message: "I am interested in this property.", + propertyId: "prop-1", + preferredContact: "email", + }; + expect(form.name).toBe("Test User"); + expect(form.preferredContact).toBe("email"); + }); + + it("ContactFormData should allow optional propertyId", () => { + const form: ContactFormData = { + name: "Test User", + email: "test@example.com", + phone: "", + message: "General inquiry.", + preferredContact: "either", + }; + expect(form.propertyId).toBeUndefined(); + }); + + it("PreferredContact union values should be valid", () => { + const validValues: PreferredContact[] = ["email", "phone", "either"]; + expect(validValues).toHaveLength(3); + }); +}); diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..eca37cc --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting / Strict mode */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src", "tests"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..426eda2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,6 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..b90387b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + ], + server: { + port: 5173, + open: true, + }, + build: { + outDir: "dist", + sourcemap: true, + }, +}); From 7061e3adbfd8e2e3753c6e2651afa60b0146dca1 Mon Sep 17 00:00:00 2001 From: FORGE Date: Mon, 6 Apr 2026 20:11:22 +0000 Subject: [PATCH 02/22] feat: Frontend Project Setup - Entry Files Run: e59d39c7-91f6-466a-9a7c-46014dbfef09 Task: 74aa0702-a1fa-41df-a29d-3764cc9c97ce Agent: builder --- ARCHITECTURE.md | 157 ++++++--------- SETUP.md | 37 +--- index.html | 9 +- src/App.tsx | 25 +++ src/data/mockAgents.ts | 69 +++---- src/data/mockNeighborhoods.ts | 127 ++++++------ src/data/mockProperties.ts | 327 +++++++++++++++++-------------- src/main.tsx | 18 +- src/pages/ContactPage.tsx | 207 +++++++++++++++++++ src/pages/HomePage.tsx | 76 +++++++ src/pages/PropertyDetailPage.tsx | 176 +++++++++++++++++ src/types/models.ts | 128 ++++++------ tests/App.test.tsx | 65 ++++++ tests/mockData.test.ts | 169 ++++++++++++++++ tests/models.test.ts | 303 +++++++--------------------- 15 files changed, 1216 insertions(+), 677 deletions(-) create mode 100644 src/App.tsx create mode 100644 src/pages/ContactPage.tsx create mode 100644 src/pages/HomePage.tsx create mode 100644 src/pages/PropertyDetailPage.tsx create mode 100644 tests/App.test.tsx create mode 100644 tests/mockData.test.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 92100c8..10cd392 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,128 +2,89 @@ ## Overview -A modern real estate listing website built with React, Vite, TypeScript, and Tailwind CSS. The application displays property listings, agent profiles, and neighborhood information with a responsive, professional design. +A modern, responsive real estate website built with React, TypeScript, Vite, and Tailwind CSS. The application showcases property listings, agent profiles, and neighborhood information with a contact system. ## Tech Stack -| Layer | Technology | -| -------------- | --------------------------------- | -| Framework | React 18 | -| Build Tool | Vite 5 | -| Language | TypeScript 5 (strict mode) | -| Styling | Tailwind CSS 4 (via @tailwindcss/vite) | -| Routing | React Router v6 | -| Icons | Lucide React | -| Images | Unsplash source URLs (mock data) | +| Layer | Technology | +| ------------- | -------------------------------- | +| Framework | React 18+ | +| Language | TypeScript 5+ | +| Build Tool | Vite 5+ | +| Routing | React Router v6 (BrowserRouter) | +| Styling | Tailwind CSS 4+ | +| Linting | ESLint, Prettier | +| Data | Mock data (static JSON-like TS) | +| Images | Unsplash source URLs | ## Project Structure ``` -├── ARCHITECTURE.md -├── SETUP.md -├── index.html +├── index.html # HTML entry point +├── ARCHITECTURE.md # This file ├── package.json ├── tsconfig.json -├── tsconfig.app.json ├── vite.config.ts ├── src/ -│ ├── main.tsx # Application entry point -│ ├── App.tsx # Root component with router -│ ├── index.css # Tailwind CSS imports & global styles -│ ├── vite-env.d.ts # Vite client type declarations +│ ├── main.tsx # React DOM render + BrowserRouter +│ ├── App.tsx # Route definitions +│ ├── index.css # Tailwind directives │ ├── types/ -│ │ └── models.ts # TypeScript interfaces & type aliases +│ │ └── models.ts # TypeScript interfaces & type aliases │ ├── data/ -│ │ ├── mockProperties.ts # Mock property listings -│ │ ├── mockAgents.ts # Mock agent profiles -│ │ └── mockNeighborhoods.ts # Mock neighborhood data -│ ├── components/ -│ │ ├── atoms/ # Small, reusable UI primitives -│ │ ├── molecules/ # Composite components -│ │ └── layout/ # Header, Footer, Layout wrappers -│ └── pages/ # Route-level page components -│ ├── HomePage.tsx -│ ├── PropertiesPage.tsx -│ ├── PropertyDetailPage.tsx -│ ├── AgentsPage.tsx -│ ├── NeighborhoodsPage.tsx -│ └── ContactPage.tsx +│ │ ├── mockProperties.ts # Mock property listings +│ │ ├── mockAgents.ts # Mock agent profiles +│ │ └── mockNeighborhoods.ts# Mock neighborhood data +│ ├── pages/ +│ │ ├── HomePage.tsx # Landing page with featured listings +│ │ ├── PropertyDetailPage.tsx # Single property view +│ │ └── ContactPage.tsx # Contact form +│ └── components/ # Reusable UI components (future phases) +│ ├── atoms/ # Buttons, inputs, badges +│ ├── molecules/ # Cards, list items, form groups +│ └── organisms/ # Header, footer, property grid └── tests/ - └── *.test.ts + ├── models.test.ts # Type/data validation tests + ├── mockData.test.ts # Mock data helper tests + └── App.test.tsx # Route rendering tests ``` -## Component Hierarchy - -### Atoms -- **Button** — Reusable button with variant props -- **Badge** — Status/tag badge (e.g., "For Sale", "Pending") -- **PriceTag** — Formatted currency display -- **Input** — Styled form input - -### Molecules -- **PropertyCard** — Thumbnail card for property listings -- **AgentCard** — Agent profile summary card -- **NeighborhoodCard** — Neighborhood overview card -- **SearchBar** — Property search/filter bar -- **ContactForm** — Contact inquiry form -- **ImageGallery** — Property image carousel/gallery - -### Layout -- **Header** — Top navigation bar with logo and links -- **Footer** — Site footer with links and info -- **Layout** — Wraps Header + main content + Footer - -### Pages -- **HomePage** — Hero section, featured properties, neighborhoods -- **PropertiesPage** — Full listings grid with filters -- **PropertyDetailPage** — Single property with gallery, details, agent info -- **AgentsPage** — Team/agent directory -- **NeighborhoodsPage** — Neighborhood guide -- **ContactPage** — Contact form and office info - -## Routing Structure (React Router v6) - -| Path | Component | -| -------------------------- | -------------------- | -| `/` | HomePage | -| `/properties` | PropertiesPage | -| `/properties/:slug` | PropertyDetailPage | -| `/agents` | AgentsPage | -| `/neighborhoods` | NeighborhoodsPage | -| `/contact` | ContactPage | +## Routing + +| Path | Component | Description | +| ----------------- | -------------------- | ------------------------------- | +| `/` | `HomePage` | Featured listings & hero | +| `/property/:id` | `PropertyDetailPage` | Single property detail view | +| `/contact` | `ContactPage` | Contact form | ## Data Models -All TypeScript interfaces are defined in `src/types/models.ts`: +### Property -- **Property** — Full property listing with address, specs, images, agent reference -- **Agent** — Real estate agent profile -- **Neighborhood** — Area/neighborhood information -- **ContactFormData** — Contact form submission payload -- **PropertyType** — Union type: `'house' | 'condo' | 'townhouse' | 'apartment' | 'land'` -- **PropertyStatus** — Union type: `'for-sale' | 'pending' | 'sold'` -- **PreferredContact** — Union type: `'email' | 'phone' | 'either'` +Represents a real estate listing with full details including location, features, pricing, associated agent, and neighborhood. -## Mock Data Strategy +### Agent + +Represents a real estate agent with contact info, bio, specialties, and social links. -During development, all data comes from static mock arrays in `src/data/`. Property and agent images use specific Unsplash photo URLs with `w`, `h`, and `fit=crop` query parameters for consistent sizing. +### Neighborhood -Helper functions are exported alongside the data arrays for common lookups (by slug, by status, by ID, featured items). +Represents a neighborhood area with walkability scores, average prices, and highlights. -## Styling Approach +### ContactFormData + +Captures user-submitted contact/inquiry form data. + +## Mock Data Strategy -- **Tailwind CSS 4** via the `@tailwindcss/vite` plugin (no PostCSS config needed) -- Utility-first classes applied directly in JSX -- Design tokens: - - Primary color: blue-600 / blue-700 - - Accent color: amber-500 - - Neutral palette: slate-50 through slate-900 - - Border radius: rounded-lg (cards), rounded-xl (hero elements) - - Shadows: shadow-md (cards), shadow-lg (modals/overlays) +All data is served from static TypeScript modules in `src/data/`. Images use stable Unsplash photo URLs with explicit width/height/fit parameters to ensure consistent rendering. Helper functions provide filtered and lookup access to the data. -## Build Tooling +## Design Tokens (Tailwind) -- **Vite 5** — Fast HMR, optimized production builds -- **TypeScript 5** — Strict mode enabled in tsconfig -- **ESLint** — Linting (configured separately) -- **Prettier** — Code formatting (configured separately) +- **Primary**: Slate-based neutral palette (`slate-50` through `slate-900`) +- **Accent**: Blue (`blue-600`, `blue-700`) for CTAs and links +- **Success**: Green (`green-600`) for status indicators +- **Warning**: Amber (`amber-500`) for pending states +- **Font**: System font stack via Tailwind defaults +- **Spacing**: Tailwind's default 4px-based scale +- **Breakpoints**: `sm` (640px), `md` (768px), `lg` (1024px), `xl` (1280px) diff --git a/SETUP.md b/SETUP.md index d40ff92..2c9210d 100644 --- a/SETUP.md +++ b/SETUP.md @@ -2,8 +2,8 @@ ## Prerequisites -- Node.js >= 18.0.0 -- npm >= 9.0.0 +- Node.js 18+ +- npm 9+ (or pnpm / yarn) ## Install Dependencies @@ -11,47 +11,26 @@ npm install ``` -This will generate `package-lock.json` and `node_modules/` — both are excluded from version control. - ## Development Server ```bash npm run dev ``` -Opens the app at [http://localhost:5173](http://localhost:5173) with hot module replacement. - ## Run Tests ```bash -npm test -``` - -Or in watch mode: - -```bash -npm run test:watch +npm run test ``` -## Production Build +## Build for Production ```bash npm run build ``` -Output is written to `dist/`. - -## Preview Production Build - -```bash -npm run preview -``` - -## Generated Files (Do Not Commit) - -The following are generated by tooling and must not be hand-written or committed: +## Notes -- `node_modules/` -- `package-lock.json` -- `dist/` -- `.vite/` +- Lock files (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`) are generated by your package manager and should not be hand-edited. +- The `node_modules/` directory is generated by `npm install` and must not be committed. +- Build output (`dist/`) is generated by `npm run build` and must not be committed. diff --git a/index.html b/index.html index 00d010f..d7d781b 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,16 @@ + + + + + + + Real Estate — Find Your Dream Home - +
diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..e620bf6 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Routes, Route } from "react-router-dom"; +import HomePage from "./pages/HomePage"; +import PropertyDetailPage from "./pages/PropertyDetailPage"; +import ContactPage from "./pages/ContactPage"; + +/** + * Root application component. + * + * Defines the top-level route structure for the real estate website. + * Each route maps a URL path to a page-level component. + */ +const App: React.FC = () => { + return ( +
+ + } /> + } /> + } /> + +
+ ); +}; + +export default App; diff --git a/src/data/mockAgents.ts b/src/data/mockAgents.ts index 1572988..e8343d2 100644 --- a/src/data/mockAgents.ts +++ b/src/data/mockAgents.ts @@ -1,23 +1,24 @@ /** - * Mock agent data for development. + * Mock agent data for development and testing. * - * Provides a static array of agent profiles and a lookup helper. + * Provides a static array of Agent objects and a helper function to + * retrieve an agent by ID. */ import type { Agent } from "../types/models"; -/** Sample real estate agents. */ +/** Static list of mock agents. */ export const MOCK_AGENTS: Agent[] = [ { id: "agent-1", name: "James Mitchell", title: "Senior Real Estate Agent", - phone: "(555) 123-4567", - email: "james.mitchell@realestate.com", + phone: "(555) 100-2001", + email: "james.mitchell@realestate.example", photo: "https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&h=400&fit=crop", - bio: "With over 15 years of experience in luxury residential real estate, James has helped hundreds of families find their dream homes. He specializes in waterfront properties and historic estates.", - specialties: ["Luxury Homes", "Waterfront Properties", "Historic Estates"], + bio: "James has over 15 years of experience in luxury residential sales. Known for his deep market knowledge and hands-on approach, he has helped hundreds of families find their perfect home.", + specialties: ["Luxury Homes", "Waterfront Properties", "Investment"], propertiesCount: 24, rating: 4.9, socialLinks: { @@ -28,61 +29,61 @@ export const MOCK_AGENTS: Agent[] = [ { id: "agent-2", name: "Sarah Chen", - title: "Buyer's Specialist", - phone: "(555) 234-5678", - email: "sarah.chen@realestate.com", + title: "Lead Listing Agent", + phone: "(555) 100-2002", + email: "sarah.chen@realestate.example", photo: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=400&fit=crop", - bio: "Sarah is passionate about helping first-time buyers navigate the market with confidence. Her deep knowledge of urban condos and townhouses makes her an invaluable resource.", - specialties: ["First-Time Buyers", "Condos", "Townhouses"], + bio: "Sarah specializes in urban condos and townhouses. Her marketing strategies consistently result in above-asking-price sales and record-breaking close times.", + specialties: ["Condos", "Townhouses", "First-Time Buyers"], propertiesCount: 18, rating: 4.8, socialLinks: { - linkedin: "https://linkedin.com/in/sarahchen", - instagram: "https://instagram.com/sarahchenrealty", + linkedin: "https://linkedin.com/in/sarachen", + instagram: "https://instagram.com/sarachen_realty", }, }, { id: "agent-3", - name: "Michael Torres", - title: "Commercial & Residential Agent", - phone: "(555) 345-6789", - email: "michael.torres@realestate.com", + name: "Michael Rivera", + title: "Buyer's Agent", + phone: "(555) 100-2003", + email: "michael.rivera@realestate.example", photo: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=400&fit=crop", - bio: "Michael brings a unique dual expertise in both commercial and residential markets. His analytical approach and strong negotiation skills consistently deliver exceptional results for his clients.", - specialties: ["Commercial Properties", "Investment Properties", "Negotiation"], - propertiesCount: 31, + bio: "Michael is passionate about helping buyers navigate the competitive market. With a background in finance, he offers unparalleled insight into property valuations and negotiation.", + specialties: ["Buyer Representation", "Relocation", "Negotiation"], + propertiesCount: 12, rating: 4.7, socialLinks: { - linkedin: "https://linkedin.com/in/michaeltorres", - facebook: "https://facebook.com/michaeltorresrealty", + linkedin: "https://linkedin.com/in/michaelrivera", + facebook: "https://facebook.com/michaelrivera.realty", }, }, { id: "agent-4", - name: "Emily Rodriguez", + name: "Emily Nakamura", title: "Luxury Property Specialist", - phone: "(555) 456-7890", - email: "emily.rodriguez@realestate.com", + phone: "(555) 100-2004", + email: "emily.nakamura@realestate.example", photo: "https://images.unsplash.com/photo-1580489944761-15a19d654956?w=400&h=400&fit=crop", - bio: "Emily's eye for design and deep connections in the luxury market set her apart. She works closely with architects and designers to stage and present properties at their absolute best.", - specialties: ["Luxury Homes", "Interior Staging", "New Construction"], + bio: "Emily brings a refined eye for design and architecture to every transaction. Her clientele includes high-net-worth individuals seeking distinctive homes with character.", + specialties: ["Luxury Homes", "Historic Properties", "Architecture"], propertiesCount: 15, - rating: 5.0, + rating: 4.9, socialLinks: { - linkedin: "https://linkedin.com/in/emilyrodriguez", - instagram: "https://instagram.com/emilyrodriguezluxury", + linkedin: "https://linkedin.com/in/emilynakamura", + instagram: "https://instagram.com/emily_luxhomes", }, }, ]; /** - * Look up a single agent by their unique identifier. + * Retrieve a single agent by their unique ID. * - * @param id - The agent ID to search for. - * @returns The matching Agent, or undefined if not found. + * @param id - The agent ID to look up. + * @returns The matching Agent object, or undefined if not found. */ export function getAgentById(id: string): Agent | undefined { return MOCK_AGENTS.find((agent) => agent.id === id); diff --git a/src/data/mockNeighborhoods.ts b/src/data/mockNeighborhoods.ts index eb29056..cc5adb4 100644 --- a/src/data/mockNeighborhoods.ts +++ b/src/data/mockNeighborhoods.ts @@ -1,105 +1,108 @@ /** - * Mock neighborhood data for development. + * Mock neighborhood data for development and testing. * - * Provides a static array of neighborhoods and a lookup helper. + * Provides a static array of Neighborhood objects and a helper + * function to retrieve a neighborhood by slug. */ import type { Neighborhood } from "../types/models"; -/** Sample neighborhoods. */ +/** Static list of mock neighborhoods. */ export const MOCK_NEIGHBORHOODS: Neighborhood[] = [ { id: "neighborhood-1", - name: "Lakewood Hills", - slug: "lakewood-hills", - city: "Austin", - state: "TX", + name: "Downtown Heights", + slug: "downtown-heights", + city: "Metropolis", + state: "CA", description: - "An exclusive lakeside community known for its luxury estates, scenic trails, and top-rated schools. Residents enjoy private lake access, boat docks, and a peaceful retreat minutes from downtown.", + "A vibrant urban core with walkable streets, world-class dining, and a thriving arts scene. Downtown Heights offers the best of city living with modern high-rises and converted lofts.", image: "https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=800&h=600&fit=crop", - averagePrice: 1_100_000, - walkScore: 45, - transitScore: 30, + averagePrice: 750000, + walkScore: 95, + transitScore: 90, highlights: [ - "Private Lake Access", - "Top-Rated Schools", - "Scenic Hiking Trails", - "Gated Communities", + "Walk to restaurants and shops", + "Excellent public transit", + "Vibrant nightlife", + "Farmers market every Saturday", ], - featuredProperties: ["prop-1"], + featuredProperties: ["prop-1", "prop-2"], }, { id: "neighborhood-2", - name: "Downtown", - slug: "downtown", - city: "Austin", - state: "TX", + name: "Oakwood Estates", + slug: "oakwood-estates", + city: "Metropolis", + state: "CA", description: - "The vibrant urban core with world-class dining, live music venues, and stunning skyline views. High-rise condos and lofts put you steps away from everything the city has to offer.", + "Tree-lined streets and spacious lots define this family-friendly suburb. Oakwood Estates is known for its top-rated schools, community parks, and friendly neighbors.", image: - "https://images.unsplash.com/photo-1444723121867-7a241cacace9?w=800&h=600&fit=crop", - averagePrice: 750_000, - walkScore: 95, - transitScore: 85, + "https://images.unsplash.com/photo-1486325212027-8081e485255e?w=800&h=600&fit=crop", + averagePrice: 950000, + walkScore: 65, + transitScore: 45, highlights: [ - "Live Music Capital", - "Walkable Everywhere", - "Rooftop Bars & Restaurants", - "Lady Bird Lake Access", + "Top-rated school district", + "Community pool and playground", + "Low crime rate", + "Annual block party", ], - featuredProperties: ["prop-2"], + featuredProperties: ["prop-3", "prop-4"], }, { id: "neighborhood-3", - name: "South Congress", - slug: "south-congress", - city: "Austin", - state: "TX", + name: "Harbor View", + slug: "harbor-view", + city: "Metropolis", + state: "CA", description: - "Eclectic and colorful, SoCo is famous for its boutique shopping, food trucks, and artistic vibe. Charming bungalows and renovated homes line the tree-shaded streets just south of the river.", + "Perched along the waterfront, Harbor View combines stunning ocean vistas with a laid-back coastal lifestyle. Enjoy morning jogs along the boardwalk and sunset dining at harborside bistros.", image: - "https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?w=800&h=600&fit=crop", - averagePrice: 550_000, - walkScore: 82, - transitScore: 60, + "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=800&h=600&fit=crop", + averagePrice: 1200000, + walkScore: 78, + transitScore: 55, highlights: [ - "Boutique Shopping", - "Food Truck Paradise", - "Art Galleries", - "Historic Architecture", + "Ocean views", + "Boardwalk and marina", + "Fresh seafood restaurants", + "Weekend sailing regattas", ], - featuredProperties: ["prop-3"], + featuredProperties: ["prop-5"], }, { id: "neighborhood-4", - name: "Westlake Hills", - slug: "westlake-hills", - city: "Austin", - state: "TX", + name: "Maplewood Commons", + slug: "maplewood-commons", + city: "Metropolis", + state: "CA", description: - "A prestigious enclave in the Texas Hill Country offering panoramic views, sprawling estates, and acclaimed Eanes ISD schools. The area blends natural beauty with sophisticated luxury living.", + "An up-and-coming neighborhood blending historic charm with modern renovations. Maplewood Commons features artisan coffee shops, indie bookstores, and a growing tech-startup presence.", image: - "https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800&h=600&fit=crop", - averagePrice: 1_500_000, - walkScore: 25, - transitScore: 15, + "https://images.unsplash.com/photo-1460317442991-0ec209397118?w=800&h=600&fit=crop", + averagePrice: 620000, + walkScore: 88, + transitScore: 72, highlights: [ - "Hill Country Views", - "Eanes ISD Schools", - "Nature Preserves", - "Luxury Estates", + "Trendy cafes and boutiques", + "Historic architecture", + "Growing tech hub", + "Dog-friendly parks", ], - featuredProperties: ["prop-4"], + featuredProperties: ["prop-6"], }, ]; /** - * Look up a single neighborhood by its URL slug. + * Retrieve a single neighborhood by its URL-friendly slug. * - * @param slug - The URL-friendly slug to search for. - * @returns The matching Neighborhood, or undefined if not found. + * @param slug - The neighborhood slug to look up. + * @returns The matching Neighborhood object, or undefined if not found. */ -export function getNeighborhoodBySlug(slug: string): Neighborhood | undefined { +export function getNeighborhoodBySlug( + slug: string, +): Neighborhood | undefined { return MOCK_NEIGHBORHOODS.find((n) => n.slug === slug); } diff --git a/src/data/mockProperties.ts b/src/data/mockProperties.ts index 01e2938..7aae2ca 100644 --- a/src/data/mockProperties.ts +++ b/src/data/mockProperties.ts @@ -1,243 +1,268 @@ /** - * Mock property listing data for development. + * Mock property listing data for development and testing. * - * Provides a static array of properties and several lookup / filter helpers. + * Provides a static array of Property objects with Unsplash image URLs + * and helper functions for common queries. */ import type { Property, PropertyStatus } from "../types/models"; import { MOCK_AGENTS } from "./mockAgents"; +import { MOCK_NEIGHBORHOODS } from "./mockNeighborhoods"; -/** Sample property listings. */ +/** Static list of mock property listings. */ export const MOCK_PROPERTIES: Property[] = [ { id: "prop-1", - title: "Modern Lakefront Estate", - slug: "modern-lakefront-estate", - price: 1_250_000, - address: "742 Lakeview Drive", - city: "Austin", - state: "TX", - zipCode: "78701", - propertyType: "house", - bedrooms: 5, - bathrooms: 4.5, - squareFeet: 4_200, - lotSize: 12_000, - yearBuilt: 2021, + title: "Modern Downtown Loft", + slug: "modern-downtown-loft", + price: 685000, + address: "123 Main Street, Unit 4A", + city: "Metropolis", + state: "CA", + zipCode: "90001", + propertyType: "condo", + bedrooms: 2, + bathrooms: 2, + squareFeet: 1450, + lotSize: undefined, + yearBuilt: 2019, description: - "Stunning modern estate overlooking the lake with floor-to-ceiling windows, an open-concept living area, gourmet kitchen with premium appliances, and a resort-style backyard featuring an infinity pool and outdoor kitchen.", + "An open-concept loft in the heart of Downtown Heights featuring floor-to-ceiling windows, polished concrete floors, and designer finishes throughout. The chef's kitchen boasts quartz countertops and premium stainless steel appliances. Building amenities include a rooftop terrace, fitness center, and 24-hour concierge.", features: [ - "Infinity Pool", - "Smart Home System", - "Wine Cellar", - "3-Car Garage", - "Home Theater", - "Waterfront Access", + "Floor-to-ceiling windows", + "Polished concrete floors", + "Quartz countertops", + "Stainless steel appliances", + "In-unit laundry", + "Rooftop terrace access", + "Fitness center", + "1 parking space", ], images: [ - "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", ], - agent: MOCK_AGENTS[0]!, - neighborhood: "Lakewood Hills", - listingDate: "2024-10-15", + agent: MOCK_AGENTS[1], + neighborhood: MOCK_NEIGHBORHOODS[0], + listingDate: "2024-09-15", status: "for-sale", }, { id: "prop-2", - title: "Downtown Luxury Penthouse", - slug: "downtown-luxury-penthouse", - price: 875_000, - address: "100 Congress Ave, Unit PH-1", - city: "Austin", - state: "TX", - zipCode: "78702", + title: "Elegant Penthouse Suite", + slug: "elegant-penthouse-suite", + price: 1250000, + address: "456 Skyline Boulevard, PH1", + city: "Metropolis", + state: "CA", + zipCode: "90002", propertyType: "condo", bedrooms: 3, - bathrooms: 2.5, - squareFeet: 2_800, - yearBuilt: 2023, + bathrooms: 3.5, + squareFeet: 2800, + lotSize: undefined, + yearBuilt: 2021, description: - "Breathtaking penthouse in the heart of downtown with panoramic skyline views, designer finishes throughout, a private rooftop terrace, and access to world-class building amenities.", + "A breathtaking penthouse offering panoramic city views from every room. This residence features a private elevator entry, a wraparound terrace, a gourmet kitchen with a wine fridge, and a spa-like primary suite with heated floors.", features: [ - "Rooftop Terrace", - "Concierge Service", - "Floor-to-Ceiling Windows", - "Fitness Center", - "Valet Parking", + "Panoramic city views", + "Private elevator entry", + "Wraparound terrace", + "Wine fridge", + "Heated bathroom floors", + "Smart home system", + "2 parking spaces", + "Concierge service", ], images: [ "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", ], - agent: MOCK_AGENTS[1]!, - neighborhood: "Downtown", - listingDate: "2024-11-01", + agent: MOCK_AGENTS[0], + neighborhood: MOCK_NEIGHBORHOODS[0], + listingDate: "2024-10-01", status: "for-sale", }, { id: "prop-3", - title: "Charming Craftsman Bungalow", - slug: "charming-craftsman-bungalow", - price: 525_000, - address: "318 Oak Street", - city: "Austin", - state: "TX", - zipCode: "78704", + title: "Charming Oakwood Family Home", + slug: "charming-oakwood-family-home", + price: 875000, + address: "789 Elm Drive", + city: "Metropolis", + state: "CA", + zipCode: "90003", propertyType: "house", - bedrooms: 3, - bathrooms: 2, - squareFeet: 1_850, - lotSize: 6_500, - yearBuilt: 1935, + bedrooms: 4, + bathrooms: 3, + squareFeet: 2600, + lotSize: 0.35, + yearBuilt: 2005, description: - "Beautifully restored 1935 Craftsman bungalow with original hardwood floors, built-in bookshelves, updated kitchen and bathrooms, and a lush private garden. Walking distance to vibrant South Congress shops and restaurants.", + "A beautifully maintained family home on a quiet cul-de-sac in Oakwood Estates. Features include a sun-drenched open-plan living area, a landscaped backyard with a patio, and a two-car garage. Walking distance to award-winning schools and community parks.", features: [ - "Original Hardwood Floors", - "Updated Kitchen", - "Private Garden", - "Front Porch", - "Detached Studio", + "Open-plan living area", + "Landscaped backyard", + "Covered patio", + "Two-car garage", + "Hardwood floors", + "Updated kitchen", + "Walk to schools", + "Community pool access", ], images: [ - "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", - "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", ], - agent: MOCK_AGENTS[3]!, - neighborhood: "South Congress", - listingDate: "2024-09-20", + agent: MOCK_AGENTS[2], + neighborhood: MOCK_NEIGHBORHOODS[1], + listingDate: "2024-08-20", status: "for-sale", }, { id: "prop-4", - title: "Contemporary Hillside Villa", - slug: "contemporary-hillside-villa", - price: 1_750_000, - address: "5500 Mountain Ridge Road", - city: "Austin", - state: "TX", - zipCode: "78730", + title: "Renovated Craftsman Bungalow", + slug: "renovated-craftsman-bungalow", + price: 720000, + address: "321 Maple Lane", + city: "Metropolis", + state: "CA", + zipCode: "90003", propertyType: "house", - bedrooms: 6, - bathrooms: 5, - squareFeet: 5_500, - lotSize: 20_000, - yearBuilt: 2022, + bedrooms: 3, + bathrooms: 2, + squareFeet: 1800, + lotSize: 0.25, + yearBuilt: 1948, description: - "Architectural masterpiece perched on a hilltop with sweeping Hill Country views. Features include a cantilevered deck, chef's kitchen, primary suite with spa bath, and a separate guest casita.", + "A lovingly renovated Craftsman bungalow that blends vintage charm with modern amenities. Original built-in cabinetry and wood beams are complemented by a brand-new kitchen, updated bathrooms, and energy-efficient windows.", features: [ - "Hill Country Views", - "Guest Casita", - "Chef's Kitchen", - "Spa Bathroom", - "Solar Panels", - "EV Charger", + "Original wood beams", + "Built-in cabinetry", + "New kitchen", + "Updated bathrooms", + "Energy-efficient windows", + "Front porch", + "Detached garage", + "Mature fruit trees", ], images: [ - "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", - "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", ], - agent: MOCK_AGENTS[0]!, - neighborhood: "Westlake Hills", - listingDate: "2024-11-10", - status: "for-sale", + agent: MOCK_AGENTS[3], + neighborhood: MOCK_NEIGHBORHOODS[1], + listingDate: "2024-07-10", + status: "pending", }, { id: "prop-5", - title: "Urban Loft in East Side", - slug: "urban-loft-east-side", - price: 415_000, - address: "200 East 6th Street, Unit 4B", - city: "Austin", - state: "TX", - zipCode: "78702", - propertyType: "apartment", - bedrooms: 2, - bathrooms: 2, - squareFeet: 1_400, - yearBuilt: 2019, + title: "Waterfront Luxury Villa", + slug: "waterfront-luxury-villa", + price: 2150000, + address: "10 Harbor Point Road", + city: "Metropolis", + state: "CA", + zipCode: "90004", + propertyType: "house", + bedrooms: 5, + bathrooms: 4.5, + squareFeet: 4200, + lotSize: 0.6, + yearBuilt: 2017, description: - "Industrial-chic loft with exposed brick, polished concrete floors, 14-foot ceilings, and a private balcony overlooking the East Side arts district. Building includes rooftop pool and co-working lounge.", + "A stunning waterfront villa with unobstructed ocean views, an infinity pool, and a private dock. The open-concept great room flows seamlessly to an expansive outdoor living area. The gourmet kitchen features a 12-foot island, Sub-Zero refrigeration, and a Wolf range.", features: [ - "Exposed Brick", - "14-Foot Ceilings", - "Rooftop Pool", - "Co-Working Lounge", - "Balcony", + "Ocean views", + "Infinity pool", + "Private dock", + "Gourmet kitchen", + "12-foot kitchen island", + "Outdoor living area", + "Three-car garage", + "Smart home automation", ], images: [ + "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800&h=600&fit=crop", - "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", - "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", ], - agent: MOCK_AGENTS[1]!, - neighborhood: "East Side", - listingDate: "2024-10-28", - status: "pending", + agent: MOCK_AGENTS[0], + neighborhood: MOCK_NEIGHBORHOODS[2], + listingDate: "2024-11-01", + status: "for-sale", }, { id: "prop-6", - title: "Classic Colonial on Maple Lane", - slug: "classic-colonial-maple-lane", - price: 680_000, - address: "45 Maple Lane", - city: "Austin", - state: "TX", - zipCode: "78703", + title: "Maplewood Modern Townhouse", + slug: "maplewood-modern-townhouse", + price: 540000, + address: "55 Cedar Court", + city: "Metropolis", + state: "CA", + zipCode: "90005", propertyType: "townhouse", - bedrooms: 4, - bathrooms: 3.5, - squareFeet: 3_200, - lotSize: 8_000, - yearBuilt: 2005, + bedrooms: 3, + bathrooms: 2.5, + squareFeet: 1950, + lotSize: 0.1, + yearBuilt: 2022, description: - "Elegant colonial-style townhouse in a tree-lined neighborhood. Formal living and dining rooms, a sun-drenched family room, updated chef's kitchen, and a spacious primary suite. Community pool and tennis courts included.", + "A brand-new townhouse in the heart of Maplewood Commons. This three-story home features a rooftop deck, an attached garage, and an open-plan main level ideal for entertaining. High-end finishes include white-oak flooring, custom tile work, and designer lighting.", features: [ - "Community Pool", - "Tennis Courts", - "Updated Kitchen", - "Fireplace", - "Two-Car Garage", - "Fenced Yard", + "Rooftop deck", + "Attached garage", + "White-oak flooring", + "Custom tile work", + "Designer lighting", + "Open-plan main level", + "Walk to cafes and shops", + "EV charger-ready", ], images: [ - "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", - "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800&h=600&fit=crop", "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&h=600&fit=crop", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800&h=600&fit=crop", ], - agent: MOCK_AGENTS[2]!, - neighborhood: "Tarrytown", - listingDate: "2024-08-15", - status: "sold", + agent: MOCK_AGENTS[3], + neighborhood: MOCK_NEIGHBORHOODS[3], + listingDate: "2024-10-15", + status: "for-sale", }, ]; /** - * Look up a single property by its URL slug. + * Retrieve a single property by its URL-friendly slug. * - * @param slug - The URL-friendly slug to search for. - * @returns The matching Property, or undefined if not found. + * @param slug - The property slug to look up. + * @returns The matching Property object, or undefined if not found. */ export function getPropertyBySlug(slug: string): Property | undefined { - return MOCK_PROPERTIES.find((property) => property.slug === slug); + return MOCK_PROPERTIES.find((p) => p.slug === slug); } /** - * Filter properties by their listing status. + * Filter properties by their sale status. * - * @param status - The PropertyStatus to filter by. + * @param status - The PropertyStatus to filter on. * @returns An array of properties matching the given status. */ export function getPropertiesByStatus(status: PropertyStatus): Property[] { - return MOCK_PROPERTIES.filter((property) => property.status === status); + return MOCK_PROPERTIES.filter((p) => p.status === status); } /** - * Return featured properties (the first 3 listings). + * Return the first three properties as "featured" listings. * * @returns An array of up to 3 featured Property objects. */ diff --git a/src/main.tsx b/src/main.tsx index f0d17a3..b158073 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,25 +1,27 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App"; import "./index.css"; /** * Application entry point. * - * Mounts the React root into the #root DOM element. - * The full App component with routing will be added in a later phase. + * Mounts the React root into the #root DOM element, wrapping the + * application in React.StrictMode and BrowserRouter for client-side routing. */ const rootElement = document.getElementById("root"); if (!rootElement) { - throw new Error("Root element not found. Ensure index.html contains
."); + throw new Error( + "Root element not found. Ensure index.html contains
.", + ); } ReactDOM.createRoot(rootElement).render( -
-

- Real Estate Website — Coming Soon -

-
+ + +
, ); diff --git a/src/pages/ContactPage.tsx b/src/pages/ContactPage.tsx new file mode 100644 index 0000000..0914a5a --- /dev/null +++ b/src/pages/ContactPage.tsx @@ -0,0 +1,207 @@ +import React, { useState } from "react"; +import { Link, useSearchParams } from "react-router-dom"; +import type { ContactFormData, PreferredContact } from "../types/models"; + +/** + * Contact page component. + * + * Renders a contact form that collects user inquiry details. + * Optionally pre-fills a propertyId when linked from a property detail page. + */ +const ContactPage: React.FC = () => { + const [searchParams] = useSearchParams(); + const propertyId = searchParams.get("propertyId") ?? undefined; + + const [formData, setFormData] = useState({ + name: "", + email: "", + phone: "", + message: "", + propertyId, + preferredContact: "email", + }); + + const [submitted, setSubmitted] = useState(false); + + /** + * Handle input field changes and update form state. + */ + const handleChange = ( + e: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ): void => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + /** + * Handle form submission. + */ + const handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + // In a real app this would POST to an API + setSubmitted(true); + }; + + if (submitted) { + return ( +
+
+

+ Thank You! +

+

+ Your message has been sent. We'll get back to you shortly. +

+ + Back to Home + +
+
+ ); + } + + return ( +
+ {/* Navigation */} +
+ + ← Back to Listings + +
+ +
+

Contact Us

+

+ Have a question or want to schedule a viewing? Fill out the form below + and we'll be in touch. +

+ +
+ {/* Name */} +
+ + +
+ + {/* Email */} +
+ + +
+ + {/* Phone */} +
+ + +
+ + {/* Preferred Contact */} +
+ + +
+ + {/* Message */} +
+ +