← All case studies

Custom SaaS Platforms

Full, ready-to-run software products built from scratch around one business’s exact workflow, not just an automation, a whole system.

All 6 case studies in this section follow in full.

Contents

Custom SaaS Platforms

Case studies in full

AI-Powered Construction Report SaaS (MeinBüromeister)

Turning construction-site voice notes, photos, and messages into structured reports through an automated AI-powered backend for German trade companies.

Section: Custom SaaS Platforms

Overview

MeinBüromeister is a B2B SaaS platform for German construction and trade companies. It allows workers to submit job updates through WhatsApp instead of preparing every report manually. The backend organizes incoming information, generates structured reports, and asks workers to review them before submission.

The Business Problem

Construction workers often send separate voice notes, photos, and text corrections from job sites. Office staff must identify the correct customer and project, organize materials and working hours, and format the information into professional reports. This creates repetitive administrative work and makes report preparation more time-consuming. The goal was to reduce this manual effort while keeping workers involved in reviewing and approving their reports.

The Solution

An n8n-based backend receives WhatsApp messages and groups related inputs into a single session. OpenAI Whisper transcribes voice recordings, while Claude AI extracts relevant report information into structured data. After the worker reviews and approves the report, the backend stores it in Supabase for delivery to the office dashboard. Supporting workflows automate customer onboarding, subscription management, team access, and error handling.

Project Snapshot

A production backend connecting WhatsApp messaging, AI-powered report extraction, worker approval, report storage, customer onboarding, and subscription lifecycle management.

How the System Works

System Architecture

The backend is organized around an n8n automation layer that sits between WhatsApp, the AI services used for transcription and extraction, and the Supabase/Stripe infrastructure that powers the office dashboard.

Supporting Integrations

Core Automation Workflows

The backend is built around interconnected n8n workflows that automate WhatsApp message handling, AI-powered report generation, customer onboarding, and subscription management. Each workflow handles a specific responsibility and integrates with the relevant APIs and backend services to support the complete reporting lifecycle. Some main automation workflows are shown below.

1. Core Onboarding & Workflow Management

This workflow handles company onboarding, setup-code verification, worker registration, subscription checks, report approval, project management, and notifications through interconnected n8n automation nodes.

Image: Core onboarding and workflow management: company setup, worker registration, subscription checks, report approval, project handling, and notifications across 15 interconnected automation groups.

2. AI-Powered Report Processing Workflow

This workflow processes WhatsApp messages using Whisper and Claude AI to extract structured report data, enrich customer and project details, validate reports, and handle database updates and notifications.

Image: AI-powered report processing: message intake, voice transcription, Claude-based data extraction, report generation, and save/notification handling.

Product Experience

Report inbox

Image: The office inbox organizes incoming reports by date, technician, customer/project, category, and status.

Customers and projects

Image: The project view groups customers and highlights open-report counts and recent work.

WhatsApp work history

Image: Workers review and approve each AI-generated report directly in WhatsApp before it goes to the office.

Image: Once approved, the report is saved, confirmed back to the worker on WhatsApp, and delivered to the office as a PDF.

Core Backend Capabilities

Results & Metrics

The project delivered five production workflows connecting WhatsApp, AI-powered report extraction, Supabase, and Stripe. Across five rounds of end-to-end testing, more than 25 bugs were identified and fixed, with over 30 test scenarios executed per round.

Technical Challenges & Solutions

During development and end-to-end testing, several integration and AI reliability issues required targeted solutions.

1. Stripe Subscription Data Handling

Challenge

The Stripe checkout event did not always contain all the subscription information required by the workflow.

Solution

Retrieved the complete subscription details directly through the Stripe API before continuing with subscription processing.

Outcome

Improved the reliability of subscription data handling.

2. Subscription Plan Mapping

Challenge

Mapping subscription plans based on payment amounts could lead to incorrect plan identification.

Solution

Used Stripe product IDs to identify plans instead of relying on monetary amounts.

Outcome

Made plan identification more consistent across subscription events.

3. AI Hallucination Control

Challenge

Claude AI could occasionally introduce materials or details that were not present in the worker’s input.

Solution

Refined the AI prompt with guardrails to discourage unsupported details and keep extraction grounded in the supplied information.

Outcome

Improved the consistency of structured report generation.

4. Supabase PATCH Response Handling

Challenge

Some Supabase PATCH operations returned empty responses, creating difficulties for downstream workflow nodes.

Solution

Explicitly referenced upstream node outputs where required instead of relying on the PATCH response to provide all subsequent data.

Outcome

Made downstream data handling more reliable.

5. End-to-End Integration Testing

Challenge

The system depended on multiple connected services, including WhatsApp, AI models, Supabase, and Stripe, making integration failures difficult to isolate.

Solution

Conducted five rounds of end-to-end testing, covering more than 30 scenarios per round, and fixed over 25 identified bugs.

Outcome

Improved integration reliability across the production workflows.

Key Takeaways & Future Development

Key Takeaways

Closing Summary

MeinBüromeister demonstrates how workflow automation, AI-powered information extraction, and integrated SaaS infrastructure can simplify construction reporting. The primary contribution was engineering the backend workflows and integrations that connect field communication with structured reports, subscription management, and the office dashboard.

Page · Plain text

Contract Intake Tool

AI-Powered Contract Validation, Human Review, Automated Google Drive Filing, Folder Organization, Missing-Term Detection & Document Tracking.

Section: Custom SaaS Platforms

Overview

The Contract Intake Tool is a web app that uses Claude AI to extract key terms from supplier contracts, flags missing information for human review, and automatically files approved contracts into the correct Google Drive folders. It also maintains a record of all filing activities..

Business Problem

Supplier contracts such as hotel, venue, and bus services) Were reviewed and filed manually, making the process time-consuming. Important terms like cancellation clauses, fees, and tax rates could be missed, and contracts sometimes ended up in the wrong Google Drive folders, making them difficult to find later for billing or disputes.

Proposed Solution

The solution is a web app that uses Claude AI to extract required terms from supplier contracts (PDFs), allows users to review missing information, and automatically files approved contracts into the correct Google Drive folders. PostgreSQL maintains a record of each filing, such as who filed it, what was filed, where it was filed, and when

How the System Works

Image: Figure: AI-Powered Supplier Contract Processing Workflow

Technology Stack

The application is built using the following technologies:

  • Next.js: Web application framework.
  • TypeScript: Application development.
  • Claude AI: Extracts and checks required contract terms.
  • Google Drive API: Files contracts into the correct folders.
  • PostgreSQL: Stores filing records (who filed it, what was filed, where it was filed, and when).
  • JavaScript PDF Library: Extracts text from uploaded PDF contracts.

Key Features

  1. AI-Powered Term Extraction: Extracts required contract terms using Claude AI.
  2. Missing Term Review: Flags missing information (Not Found) for manual review before filing.
  3. Automated Google Drive Filing: Files contracts into the correct folder based on group name, date, and category.
  4. Filing Activity Log: Records who filed the contract, what was filed, where it was stored, and when.
  5. Category-Based Checks: Checks contract terms according to the selected category (Hotel / Venue / Buses / Other).

Key Safeguards

  1. Human Verification: Contracts with missing terms (Not Found) require manual review before filing.
  2. Controlled Folder Creation: Only the Chapter and Category folders are created automatically; Year, Season, and University folders must already exist in Google Drive.
  3. Filing Audit Log: Each filing records who filed the contract, what was filed, where it was stored, and when.

Closing Summary

The Contract Intake Tool simplifies supplier contract processing by combining AI-powered term extraction, human review, and automated Google Drive filing. It helps reduce manual work, identify missing terms, and maintain a clear record of filing activities.

Page · Plain text

Paid List vs. Rooming List

Compare payment and rooming records, identify discrepancies, and keep group travel organized with confidence through faster, smarter, and more accurate data reconciliation.

Section: Custom SaaS Platforms

Project Overview

Paid List vs. Rooming List is a web app that compares group travel payment records with room assignments exported from Google Sheets. It identifies fully paid travelers assigned to rooms, flags missing or unpaid records, and highlights similar-looking names that may need staff review.

Business Problem

Group travel teams maintain separate Paid List and Rooming List tabs in Google Sheets. As lists change, they can become inconsistent: someone may pay but not be assigned to a room, appear on a rooming list without being listed as paid, or have a slightly different spelling of their name across the two lists. Finding these discrepancies manually in spreadsheets with 100 or more rows is slow and error-prone.

An internal tool for a group travel company. Each student travel group keeps two separate tabs in Google Sheets, a Paid List (who paid, how much, and their payment status) and a Rooming List (who’s actually assigned to a room). Those two lists can drift apart: someone pays but never gets added to a room, someone’s on the rooming list but never paid, or a name is slightly misspelled between the two. Catching that manually in a 100+ row spreadsheet is slow and error-prone.

Solution

A small web app where a staff member uploads both CSVs (exported straight from the Google Sheet tabs) and gets back three buckets instantly:

Image: The upload screen: a staff member adds the Rooming List and Paid List CSVs exported from the group’s Google Sheet and clicks Compare.

Image: The results summary and the Paid List issues. Red rows are real discrepancies and yellow rows are similar-name warnings. Names, amounts, room and confirmation numbers are blurred for privacy.

Image: The Rooming List issues: people on the rooming list who are not on the paid list. Names and booking numbers are blurred for privacy.

Image: The matched list: people who paid in full and are on the rooming list, with no action needed. Names, amounts and booking numbers are blurred for privacy.

How It Works

Everything runs stateless: nothing is saved to disk; each upload is compare-and-respond.

Tech Stack

Built in TypeScript running on Node.js (a plain Node HTTP server, no framework), with a plain HTML/CSS/vanilla JS frontend. Deployed on Railway.

Page · Plain text

AI Lead Qualification & Sales Routing Agent

An end-to-end system for automated inbound lead scoring, enrichment, and real-time sales routing, powered by GPT-4.1 and n8n.

Section: Custom SaaS Platforms

Image: The lead form with a sample lead and the AI results card: score, HOT category, intent, budget, urgency, ICP match, summary and recommended action.

Why we built this

Most sales teams receive inbound leads from multiple sources (website contact forms, paid ad landing pages, partner referrals, and API integrations) and have no consistent, fast way to decide which ones deserve immediate attention.

The core problems the team was facing:

The goal was to build a fully automated intake-to-routing pipeline: AI scoring that runs in under two seconds, automatic company enrichment from the email domain, tiered alerts that reach the right person at the right time, and a central record of every lead with full analysis history.

What this system does

The system is composed of three interconnected layers: a lead intake API, a backend processing pipeline, and a real-time alerting engine, all orchestrated through n8n and a Node.js Express server.

Lead intake

AI scoring engine

Enrichment layer

Alerting and routing

CRM integration

Benefits

How it works (simplified)

System architecture (simplified)

Image: Architecture: four lead sources feed an Express backend with a five-step pipeline (validate, repeat detection, Clearbit enrichment, GPT-4.1 scoring, store), which routes to Slack alerts, an Airtable CRM and a nurture queue.

Ops-controlled settings (thresholds, routing rules, and CRM field mappings) are managed through environment configuration:

SettingWhere ConfiguredWhat It Controls
OPENAI_API_KEY.env fileEnables AI scoring via GPT-4.1
SLACK_WEBHOOK_URL.env fileDestination channel for HOT lead alerts
AIRTABLE_API_KEY + BASE_ID.env fileCRM destination for all processed leads
CLEARBIT_API_KEY.env fileEnables full company enrichment (optional)
Alert Toggle (per lead)Google Sheets control layerEnable or disable alerts per lead source
Score thresholdsPrompt config in aiService.jsAdjustable scoring bands per business context

Dashboard overview

Lead analysis results panel

The lead form renders a live results card immediately after submission, displaying the AI score, category, intent rating, budget signal, urgency level, ICP match, and a plain-English summary of why the lead scored the way it did, along with a specific recommended next action.

Renders in the browser within ~2 seconds of form submission

Slack alert panel

When a lead is classified as HOT, a Block Kit alert fires to the configured Slack channel. The message includes: lead name and company, email, phone, all five scoring dimensions, the AI-generated summary, recommended action, estimated deal size, and assigned sales team, formatted as a structured card with section dividers.

Delivered to the #sales-alerts channel within seconds of submission

Analytics dashboard

The /dashboard page provides a live operations view of all processed leads: total volume, HOT / WARM / COLD breakdown, average score, conversion rate, leads today, leads this week, top sources by volume, and a filterable lead feed. Data is drawn directly from the in-memory store and refreshes on page load.

Accessible at /dashboard: no login required in base configuration

Project evolution & key milestones

The system scope grew significantly from the initial lead scoring proof-of-concept. Key additions and refinements delivered across the project lifecycle:

Core scoring engine

Enrichment layer

Alerting system

Data and integration

Memory layer and analytics

Security and production hardening

Data flow architecture

How data moves through each stage and at what cadence, a key reference for ops teams integrating this system into existing stacks:

StageSourceDestinationTrigger
IntakeForm / API / n8n webhookExpress backendOn submission (real-time)
EnrichmentClearbit API / domain analysisLead record (in memory)Per lead, synchronous
AI ScoringOpenAI GPT-4.1Lead record (in memory)Per lead, synchronous
CRM PushIn-memory lead recordAirtable (18 fields)Per lead, async (non-blocking)
Slack AlertIn-memory lead recordSlack channel (HOT only)Per lead, async (non-blocking)
DashboardIn-memory lead storeBrowser via /dashboardOn page load / manual refresh

Note

The current storage layer uses an in-memory Map for speed and simplicity. For production deployments requiring persistence across restarts or horizontal scaling, this is designed to be swapped for PostgreSQL, Supabase, or MongoDB, the storage service exposes a clean interface with no direct database calls in the route layer.

Lead scoring & classification

The AI scoring engine evaluates every lead across five weighted dimensions and assigns a 0–100 score. Classification thresholds are:

CategoryScore RangeRouting ActionCriteria
HOT80 – 100Immediate Slack alert + rep assignmentClear budget, urgency signal, decision-maker identified, strong ICP match
WARM50 – 79Added to nurture email sequenceSome buying signals, partial ICP match, needs further qualification
COLD0 – 49Archived, no immediate actionNo clear intent, no budget signals, poor ICP fit or generic enquiry

The five scoring dimensions GPT-4.1 evaluates are:

Page · Plain text

LeadFlow AI (Dubai Real Estate Lead Automation)

AI-powered SaaS platform for Dubai real estate brokerages. Capture inbound leads, classify with GPT-4o, auto-reply via WhatsApp, assign to agents, and track follow-ups, all behind Stripe subscription billing.

Section: Custom SaaS Platforms

Image: The LeadFlow AI landing page: “Never miss a hot lead again”.

Why we built this

Dubai’s real estate market moves fast. Leads come in around the clock from buyers, renters, and investors often in multiple languages, often with a specific unit, area, or budget in mind. Brokerages that respond within minutes close deals; those that respond the next day don’t.

The core problems Dubai brokerages were facing:

The goal was to build a turnkey SaaS platform that any Dubai brokerage could subscribe to, embed a lead capture form on their website, and immediately have every inbound enquiry processed, classified, replied to, and assigned, automatically, in any language, within seconds.

What this system does

LeadFlow AI is a multi-tenant SaaS platform that gives Dubai real estate brokerages two things: an automated lead pipeline and a dashboard for managing it. Every brokerage gets its own isolated data environment, its own agent team, and its own Stripe subscription.

Automated lead pipeline

AI engine (GPT-4o)

WhatsApp integration (Meta Cloud API)

Owner dashboard

Agent dashboard

Subscription billing (Stripe)

Follow-up automation

Benefits

How it works (simplified)

System architecture (simplified)

Image: Architecture: inbound leads reach a public API and pass a five-step pipeline (plan limits, GPT-4o classification and reply, round-robin agent assignment, Firestore write, WhatsApp reply), with a Stripe billing layer and a daily follow-up job.

Subscription plans

LeadFlow AI is sold as a monthly SaaS subscription in UAE Dirhams. Both plans include a 7-day free trial before the first charge. Stripe Checkout handles the payment flow; Stripe Billing Portal handles self-service plan changes and cancellations.

PlanPriceLead LimitIncluded Features
BasicAED 149 / month100 leads / monthAI classification, WhatsApp auto-reply, agent assignment, follow-up tracking
ProAED 399 / monthUnlimited leadsEverything in Basic + analytics dashboard, agent performance tracking, priority support

The Basic plan lead limit is enforced server-side on every lead submission. When the 100-lead monthly cap is reached, the API returns an HTTP 429 response with a clear upgrade message. Pro plan subscribers have no cap.

Dashboard overview

Owner dashboard

The owner's view is the brokerage’s operational command centre. It shows four headline KPIs (total leads, hot leads, follow-ups today, new leads this week), an agent performance breakdown, and a live feed of the most recent leads. Owners can reassign any lead to a different agent directly from this view.

Role-restricted: visible only to owner accounts

Agent dashboard

The agent's view is designed for daily use. It shows the agent’s assigned lead count, active (non-closed) lead count, and follow-ups currently due, the three numbers that define their working day. Below the KPIs is a list of their most recently assigned leads.

Agents only see leads assigned to them: enforced at the API level

Lead detail page

Each lead has a dedicated detail page showing all contact information, AI classification results (language, intent, temperature), the full WhatsApp message thread, and status controls. Agents can update the lead’s status, set or clear a follow-up date, and send new messages directly from this page.

Message thread includes AI replies, agent messages, and incoming WhatsApp replies in chronological order

Pricing page

Public-facing pricing page displays both plans with feature lists and a “Start Free Trial” call-to-action. Clicking a plan triggers Stripe Checkout in AED, with the 7-day trial clearly communicated before any payment details are entered.

Currency displayed in AED; trial communicated pre-checkout

Lead classification & AI scoring

Every inbound lead is processed by GPT-4o using a structured system prompt that enforces a consistent professional tone and returns strict JSON. The model evaluates four dimensions simultaneously and returns them in a single API call.

TemperatureScoreRouting ActionClassification Criteria
HOTHighImmediate agent assignment + WhatsApp replyMentions specific budget, area, or timeline. Clearly ready to act.
WARMMediumAssigned + follow-up date setShows genuine interest but lacks specifics about budget, area, or timing.
COLDLowStored, lower priority nurtureGeneral inquiry, browsing behaviour, or unclear intent.

The AI prompt explicitly defines the classification rules for each temperature band, prohibits emojis and slang, and instructs the model to respond in the detected language of the lead’s message. The system prompt is fixed (not user-editable) to ensure brand consistency across all brokerages.

Project evolution & key milestones

The platform scope expanded significantly from an initial lead capture proof-of-concept. Key additions and refinements delivered across the build:

AI & classification

WhatsApp integration

Agent assignment

Subscription billing

Security & access control

Follow-up system

Data flow architecture

A reference overview of how data moves through each stage of the system and at what cadence:

StageSourceDestinationTrigger / Cadence
Lead intakeWebsite form / API callExpress backendOn submission (real-time)
AI processingOpenAI GPT-4oFirestore lead recordPer lead, synchronous
Agent assignmentFirestore counters (round-robin)Lead record (assignedAgentId)Per lead, synchronous
WhatsApp replyAI-generated reply textLead's phone (Meta API)Per lead, async
Follow-up checkFirestore leads collectionfollowUpDue flag on each leadDaily at 8:00 AM UAE time
Dashboard refreshFirestore (Realtime queries)Owner / Agent dashboardOn page load / request
Subscription syncStripe webhook eventsFirestore user recordOn Stripe event (real-time)

Note

Firestore is the single source of truth for all lead records, message threads, user profiles, subscription status, and round-robin counters. The Firebase Admin SDK (backend) and Firebase Web SDK (frontend) both read from the same Firestore project, with security rules and server-side middleware providing access control.

API reference

All endpoints are prefixed with /api/. Protected routes require a Firebase ID token in the Authorization: Bearer <token> header. The lead capture endpoint (/api/leads POST) is intentionally public to allow embedding on any brokerage website without authentication.

MethodEndpointDescription
POST/api/leadsPublic lead capture: no auth required. Runs AI, assigns agent, sends WhatsApp.
GET/api/leadsList leads (owners see all, agents see assigned only). Filterable by status, temperature, intent.
GET/api/leads/:idSingle lead detail with access control (role-based).
PATCH/api/leads/:idUpdate lead status, follow-up date, or agent assignment.
GET/api/leads/:id/messagesRetrieve full message thread for a lead.
POST/api/leads/:id/messagesAgent sends a message, delivered via WhatsApp if phone is available.
GET/api/dashboard/ownerOwner dashboard: total leads, hot leads, follow-ups today, agent performance.
GET/api/dashboard/agentAgent dashboard: assigned leads, active leads, follow-ups due.
POST/api/stripe/create-checkoutCreate Stripe Checkout session (Basic or Pro plan, AED, 7-day trial).
POST/api/stripe/portalCreate Stripe Billing Portal session for plan management.
POST/api/stripe/webhookReceive Stripe events: checkout.session.completed, subscription.updated/deleted.
GET/api/whatsapp/webhookMeta webhook verification challenge (subscribe mode).
POST/api/whatsapp/webhookReceive incoming WhatsApp messages, match to lead by phone, store in messages collection.
GET/api/healthHealth check: returns server status and timestamp.

Security

LeadFlow AI is built with multi-tenant security as a first-class requirement. Each security layer is implemented independently so that no single point of failure exposes brokerage data.

Security LayerImplementation
Firebase ID Token AuthAll protected routes verify Bearer token via Firebase Admin SDK. Invalid or expired tokens return 401.
Subscription GateMiddleware checks owner subscription status (active / trialing) before allowing access. Agents inherit their owner’s subscription.
Role-Based Access ControlrequireRole(‘owner’ | ‘agent’) middleware enforces role boundaries. Agents can only access their assigned leads.
WhatsApp Signature ValidationIncoming Meta webhooks validated via HMAC-SHA256 using App Secret. Invalid signatures return 401.
Stripe Webhook VerificationStripe events verified using stripe.webhooks.constructEvent with the webhook signing secret.
Helmet HTTP HeadersSets security headers (XSS, clickjacking, content-type sniffing) via the Helmet middleware.
CORS RestrictionRequests accepted only from the configured FRONTEND_URL. Credentials are required for authenticated calls.
Plan Limit EnforcementBasic plan lead cap (100/month) enforced server-side via Firestore count query on every lead submission.

Image: The pricing page: a Basic plan at AED 149 per month and a Pro plan at AED 399 per month, both with a 7-day free trial.

Image: The live demo: a sample enquiry is classified by GPT-4o as a HOT lead, with intent, urgency, language and an AI-generated WhatsApp reply.

Page · Plain text

Estato (Real Estate Management Dashboard)

A full-stack MERN real estate operations platform with CRUD, Google Auth, live charts, and Cloudinary image management, built on the Refine framework with Material UI.

Section: Custom SaaS Platforms

Image: The Estato dashboard: revenue, property, agent and customer tiles, a monthly revenue chart and a property referrals breakdown.

Why we built this

Real estate teams at any scale face the same structural problem: their properties, agents, and performance data live in disconnected places. Listing portals hold property records. Spreadsheets track agent performance. Photos are stored in email threads or local drives. There is no single screen that shows the full picture.

The core problems Estato was designed to solve:

The goal was a unified MERN-stack dashboard where every agent logs in with Google, can create and manage their own listings with photo upload, and managers can see live KPIs, revenue trends, and the full agent roster in one place.

What this system does

Estato is a full-stack MERN application that gives real estate teams a complete operations interface: live analytics, a full property CRUD system, an agent directory, individual profiles, and Google-authenticated access. The Refine framework handles data fetching, routing, and auth integration; Material UI provides the component layer; ApexCharts and custom donut charts deliver the analytics.

Operations dashboard

Property management (full CRUD)

Agent management

Authentication

Light and dark mode

Benefits

How it works (simplified)

System architecture

Image: Architecture: a React 18 and TypeScript browser app built on Refine with Material UI talks over HTTPS to an Express server that uses MongoDB Atlas and Cloudinary.

Page inventory

Estato consists of 9 pages, each a React TypeScript component managed by Refine’s routing and data layer.

PageFileKey Content
Dashboard (Home)home.tsx4 PieChart KPI tiles, TotalRevenue ApexChart, PropertyReferrals bar chart, Latest Properties card grid (4 items live from API).
All Propertiesall-properties.tsxFull property grid with search by title, filter by 8 property types, sort by price (asc/desc), page size selector, Previous/Next pagination.
Property Detailsproperty-details.tsxFull-width photo, property type + 5-star rating, title, location, price, description. Agent sidebar: avatar, name, property count, location, Google Map, Book Now, Message/Edit + Call/Delete buttons.
Create Propertycreate-property.tsxDelegates to shared Form component. Handles image file selection (FileReader to base64), attaches logged-in user email before submitting to API.
Edit Propertyedit-property.tsxSame shared Form component as Create. Pre-populates existing property data. Allows photo re-upload; photo URL preserved if unchanged.
Agents Listagent.tsxGrid of all users (agents) from /api/v1/users. Each AgentCard shows avatar, name, email, and property count. Clicking navigates to agent profile.
Agent Profileagent-profile.tsxFull agent detail using shared Profile component. Shows avatar, name, email, properties count and full property list.
My Profilemy-profile.tsxLogged-in user’s own profile using the same shared Profile component. Data fetched from /api/v1/users/:userId.
Loginlogin.tsxGoogle Sign-In button using Google Identity Services. On success, JWT decoded, user POSTed to /api/v1/users, token stored in localStorage.

Data models

Two Mongoose schemas define the complete data layer. The bidirectional relationship between Property and User is maintained by the server using session transactions on create and delete operations.

CollectionFieldTypeNotes
PropertytitleString (required)Listing headline
PropertydescriptionString (required)Full description text
PropertypropertyTypeString (required)Apartment, Villa, Farmhouse, Condos, Townhouse, Chalet, Studio, or Duplex
PropertylocationString (required)Free-text location / address
PropertypriceNumber (required)Numeric value (rendered as $price per day in the UI)
PropertyphotoString (required)Cloudinary CDN URL after base64 upload
PropertycreatorObjectId → UserPopulated on read to include full user document
UsernameString (required)Full name from Google profile
UseremailString (required)Google account email; used as unique identifier
UseravatarString (required)Google profile picture URL
UserallProperties[ObjectId] → PropertyBidirectional reference array; maintained via Mongoose transactions

Note

The User schema does not require a password field because authentication is handled entirely by Google OAuth. The email address serves as the unique identifier and is used both for login matching and for associating new properties with the correct creator.

API reference

All endpoints are prefixed with /api/v1/ and served by the Express server on port 8080. No authentication middleware is currently applied server-side; auth is enforced client-side by Refine’s checkAuth hook and the Axios Bearer token interceptor.

MethodEndpointDescription
GET/api/v1/propertiesList all properties. Supports _start, _end, _sort, _order, title_like, propertyType query params. Returns x-total-count header for Refine pagination.
GET/api/v1/properties/:idFetch a single property with creator user populated (name, email, avatar, allProperties).
POST/api/v1/propertiesCreate a new property. Uploads photo to Cloudinary via base64. Uses Mongoose session transaction to atomically create property and push to user.allProperties.
PATCH/api/v1/properties/:idUpdate an existing property. Re-uploads photo to Cloudinary. Falls back to original photo URL if upload fails.
DELETE/api/v1/properties/:idDelete a property. Uses Mongoose session transaction to remove from DB and pull from creator’s allProperties array atomically.
GET/api/v1/usersList all users (agents). Supports _end for limit. Returns full user documents including allProperties references.
POST/api/v1/usersCreate or find a user by email (idempotent). Called on every Google OAuth login to ensure the user record exists in MongoDB.
GET/api/v1/users/:idFetch a single user by MongoDB _id with allProperties populated. Used by agent profile and my profile pages.

Dashboard overview

Home: KPI dashboard

The home page opens with four donut-style KPI tiles: Properties for Sale (live count from the API), Properties for Rent, Total Customers (5,684), and Total Agents (684). Each tile uses a two-segment ApexCharts donut series to visualise the proportion as a ring. Below the tiles, a TotalRevenue area chart spans the full width alongside a PropertyReferrals horizontal bar chart.

Route: / (DashboardPage)

All Properties

The properties page renders the full listing catalogue as a card grid. The toolbar above the grid provides a Sort price button (toggles asc/desc), a title search text field, and a property type dropdown filter. Pagination controls at the bottom include Previous/Next buttons, a current page indicator, and a page-size selector. All filter, sort, and page state is managed by Refine’s useTable hook and translated into API query parameters automatically.

Route: /properties

Property Details

The detail page is a two-column layout. The left column shows the full-width property photo (546px height), property type, a 5-star rating row, title, location pin with text, price in blue, and a description block. The right sidebar contains the agent card (Google avatar, name, agent role, location, property count) followed by action buttons that are context-aware: Message + Call for listings the current user does not own; Edit + Delete for their own listings. A Google Maps screenshot and a Book Now button fill the remaining sidebar space.

Route: /properties/show/:id

Create / Edit Property

The shared Form component renders the same layout for both Create and Edit. Fields: Title, Property Type (dropdown: Apartment, Villa, Farmhouse, Condos, Townhouse, Chalet, Studio, Duplex), Description, Location, Price, and Photo (file input). On Edit, all fields are pre-populated from the existing record. The photo preview renders below the file input once a file is selected. Form submission is handled by Refine’s useForm hook via @pankod/refine-react-hook-form.

Route: /properties/create | /properties/edit/:id

Agents List

The agents page uses useList against /api/v1/users to fetch all registered users. Each AgentCard displays the agent’s Google avatar, full name, email address, and total number of properties they have listed. Clicking a card navigates to that agent’s full profile.

Route: /agents

Agent Profile / My Profile

Both the agent profile and my profile use the same shared Profile component. It fetches the user by ID from /api/v1/users/:id with allProperties populated, and renders the agent’s full detail view, including their complete property portfolio. My Profile loads the currently authenticated user’s own data using their userId from localStorage.

Route: /agents/show/:id | /my-profile

Technology stack

The complete dependency surface for both client and server packages:

LayerPackage / VersionRole
UI Framework@pankod/refine-core ^3.103Data fetching (useList, useShow, useDelete, useGetIdentity), auth provider, routing
Component Library@pankod/refine-mui ^3.63Material UI integration for Refine: layout, notifications, theme provider
Forms@pankod/refine-react-hook-formreact-hook-form integration for create/edit property forms with Refine’s onFinish lifecycle
Routing@pankod/refine-react-router-v6React Router v6 adapter; all navigation uses useNavigate, useParams
Data Provider@pankod/refine-simple-restMaps Refine resource operations to REST calls against the Express API
Chartsapexcharts + react-apexcharts ^1.4TotalRevenue (area chart) and PropertyReferrals (horizontal bar chart)
Charts (KPI)Custom PieChart component4 donut-style KPI tiles on the dashboard (Properties for Sale/Rent, Customers, Agents)
HTTPaxios ^0.28All API calls; Axios interceptor attaches Bearer JWT token to every outbound request
Loading Statereact-loader-spinner ^5.4Consistent loading indicator shown across all async pages via the shared Loader component
Icons@mui/icons-material ^5.15Star, Place, Phone, ChatBubble, Edit, Delete, Add, VillaOutlined, PeopleAltOutlined
Serverexpress ^4.18REST API, CORS, JSON body parsing (50 MB limit for base64 photo uploads)
ODMmongoose ^6.9Schemas, validation, populate, and session-based transactions
Mediacloudinary ^1.33Receives base64-encoded image from client, stores and returns CDN URL
Dev Servernodemon ^2.0Auto-restarts Express server on file changes during development

Project evolution & key engineering decisions

Refine the data and the auth layer

Mongoose session transactions for data integrity

Cloudinary for photo management

Idempotent user creation

Shared Form and Profile components

Property type standardisation

Project structure

Image: The project folder structure of the client and the server.

Setup & environment

Required environment variables to run Estato locally:

VariableLocationDescription
REACT_APP_GOOGLE_CLIENT_IDclient/.envGoogle Cloud Console OAuth 2.0 Client ID
REACT_APP_GOOGLE_CLIENT_SECRETclient/.envGoogle Cloud Console OAuth 2.0 Client Secret
REACT_APP_SERVER_URLclient/.envBackend base URL e.g. http://localhost:8080/api/v1
MONGODB_URLserver/.envMongoDB Atlas connection string
CLOUDINARY_CLOUD_NAMEserver/.envCloudinary account cloud name
CLOUDINARY_API_KEYserver/.envCloudinary API key
CLOUDINARY_API_SECRETserver/.envCloudinary API secret

Image: The Agents page, with agent cards showing listings, revenue and experience.

Page · Plain text