Pages & Routes¶
AppArt Agent uses Next.js 14 App Router for file-based routing. All pages are under the [locale] dynamic segment (/fr/... or /en/...) for internationalization.
Route Structure¶
/[locale] # Landing page (/fr, /en)
├── /auth
│ ├── /login # Login form
│ └── /register # Registration form
├── /dashboard # Main dashboard (protected)
└── /properties
├── /new # Create property (protected)
└── /[id] # Property detail (protected)
├── /documents # Document management
├── /photos # Photo management
├── /redesign-studio # Photo redesign
└── /price-analyst # AI-powered price analysis with comparable sales, market trends, and projections
Pages¶
Landing Page (/[locale])¶
File: src/app/[locale]/page.tsx
Public landing page with:
- Hero section
- Feature highlights
- Call-to-action buttons
Authentication¶
Login (/[locale]/auth/login)¶
File: src/app/[locale]/auth/login/page.tsx
- Email/password form (via Better Auth)
- Optional Google OAuth sign-in
- Session cookie set on success
- Redirect to dashboard on success
Register (/[locale]/auth/register)¶
File: src/app/[locale]/auth/register/page.tsx
- Registration form (via Better Auth)
- Email validation
- Auto-login after registration
Dashboard (/[locale]/dashboard)¶
File: src/app/[locale]/dashboard/page.tsx
Protected route showing:
- Property list with synthesis previews (risk level, annual costs)
- Document count and redesign count per property
- Quick stats
- Recent activity
- Create property button
The dashboard fetches from /api/properties/with-synthesis to display enriched property cards with AI analysis summaries alongside basic property information.
Properties¶
New Property (/[locale]/properties/new)¶
File: src/app/[locale]/properties/new/page.tsx
Property creation form:
- Address input (address, postal code, city, department)
- Price and surface area
- Property type selection (apartment/house)
- Rooms, floor, building floors, building year
Property Detail (/[locale]/properties/[id])¶
File: src/app/[locale]/properties/[id]/page.tsx
Property overview with:
- Property information with inline editing (address, postal code, city, department, type, price, surface area, rooms, floor, building floors, building year)
- AI Analysis Preview showing synthesis summary, risk level badge (color-coded), key findings, and recommendations (expandable)
- Design Overview displaying promoted redesigns with before/after toggle
- Price analysis results
- Market trend chart
- Navigation to sub-pages
// Property editing state
const [editingProperty, setEditingProperty] = useState(false);
const [editForm, setEditForm] = useState<PropertyUpdate>({});
// Synthesis data loaded separately
const [synthesis, setSynthesis] = useState<SynthesisData | null>(null);
// Promoted redesigns for design overview
const [designPhotos, setDesignPhotos] = useState<PhotoWithRedesign[]>([]);
Documents (/[locale]/properties/[id]/documents)¶
File: src/app/[locale]/properties/[id]/documents/page.tsx
Comprehensive document management with multi-phase processing:
- Multi-phase upload tracking: Step indicators showing Upload, Analysis, and Synthesis phases
- Bulk upload dropzone with file progress
- Document list with expandable category cards and per-document details
- Multi-select with floating action bar for bulk operations (bulk delete)
- Document renaming (inline, preserves file extension)
- Synthesis dashboard with:
- Risk level and confidence score
- Annual cost breakdown (expandable table with inline editing)
- One-time cost breakdown (expandable table with inline editing)
- Tantiemes management (lot/total tantiemes input, auto-calculated share percentage)
- Cross-document themes with evolution tracking
- Buyer action items with priority/urgency indicators
- Risk factors list
- Synthesis regeneration button
- User overrides preserved across regenerations (cost adjustments, tantiemes)
// Multi-phase upload tracking
const [uploadPhase, setUploadPhase] = useState<'upload' | 'analysis' | 'synthesis'>('upload');
const [uploadProgress, setUploadProgress] = useState(0);
// Synthesis data with full breakdown
interface FullSynthesisData {
annual_cost_breakdown: Record<string, { amount: number; source?: string; note?: string }>;
one_time_cost_breakdown: Array<{
description: string; amount: number; year: number;
cost_type: string; payment_status: string; source: string;
}>;
cross_document_themes: Array<{
theme: string; documents_involved: string[];
evolution: string; current_status: string;
}>;
buyer_action_items: Array<{
priority: number; action: string;
urgency: string; estimated_cost: number;
}>;
risk_factors: string[];
tantiemes_info: {
lot_tantiemes: number; total_tantiemes: number;
share_percentage: number;
};
confidence_score: number;
confidence_reasoning: string;
}
Photos (/[locale]/properties/[id]/photos)¶
File: src/app/[locale]/properties/[id]/photos/page.tsx
Photo management:
- Photo upload
- Gallery view
- Room type tagging
- Promote/demote redesigns: Select a favorite redesign to feature on the property overview
- Promoted badge: Visual indicator showing which redesign is currently promoted
- Link to redesign studio
Redesign Studio (/[locale]/properties/[id]/redesign-studio)¶
File: src/app/[locale]/properties/[id]/redesign-studio/page.tsx
AI-powered photo redesign:
- Photo selection
- Style chooser
- Preference configuration
- Before/after comparison
- Redesign history
// Redesign request
const requestRedesign = async (photoId: number, style: string) => {
const result = await api.post(`/photos/${photoId}/redesign`, {
style,
preferences: selectedPreferences
});
setRedesignId(result.redesign_id);
pollRedesignStatus(result.redesign_id);
};
Price Analyst (/[locale]/properties/[id]/price-analyst)¶
File: src/app/[locale]/properties/[id]/price-analyst/page.tsx
AI-powered price analysis page displaying comprehensive market insights:
- PriceAnalysisSummary: Summary card showing estimated value, deviation from market average, and recommendation
- PriceMetricsGrid: Grid of key price metrics (asking price, market average, deviation percentage)
- ComparableSalesTable: Displays comparable DVF sales with outlier highlighting and exclusion toggles to refine analysis
- TrendProjectionCard: Shows trend-based price projection with confidence indicator
- MarketTrendChart: SVG chart showing price trends over time with neighboring address data
Protected Routes¶
Routes under /[locale]/dashboard and /[locale]/properties require authentication.
ProtectedRoute Component¶
// src/components/ProtectedRoute.tsx
"use client";
import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push('/auth/login');
}
}, [user, loading, router]);
if (loading) {
return <LoadingSpinner />;
}
if (!user) {
return null;
}
return <>{children}</>;
}
Usage in Layout¶
// src/app/[locale]/dashboard/layout.tsx
import { ProtectedRoute } from '@/components/ProtectedRoute';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ProtectedRoute>
<Header />
<main className="container mx-auto px-4 py-8">
{children}
</main>
</ProtectedRoute>
);
}
Route Navigation¶
Use locale-aware navigation from @/i18n/navigation instead of standard Next.js imports. This automatically includes the current locale in URLs.
Link Component¶
import { Link } from '@/i18n/navigation';
<Link
href={`/properties/${property.id}`}
className="text-primary-600 hover:text-primary-800"
>
View Property
</Link>
// Renders: /fr/properties/123 or /en/properties/123
Programmatic Navigation¶
import { useRouter } from '@/i18n/navigation';
const router = useRouter();
// Navigate (locale is automatically included)
router.push('/dashboard');
// Switch locale without losing current path
router.replace(pathname, { locale: 'en' });
// Back
router.back();
Loading States¶
Each route can have a loading.tsx file:
// src/app/[locale]/properties/[id]/loading.tsx
export default function Loading() {
return (
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
<div className="h-64 bg-gray-200 rounded" />
</div>
);
}
Error Handling¶
Each route can have an error.tsx file:
// src/app/[locale]/properties/[id]/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="text-center py-8">
<h2 className="text-xl font-semibold text-danger-600">
Something went wrong
</h2>
<p className="text-gray-600 mt-2">{error.message}</p>
<button
onClick={reset}
className="mt-4 px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700"
>
Try again
</button>
</div>
);
}
Metadata¶
Dynamic metadata for SEO:
// src/app/[locale]/properties/[id]/page.tsx
import { Metadata } from 'next';
export async function generateMetadata({
params
}: {
params: { id: string }
}): Promise<Metadata> {
const property = await fetchProperty(params.id);
return {
title: `${property.address} | AppArt Agent`,
description: `Property analysis for ${property.address}, ${property.city}`,
};
}