Solutions Showcase: part 4 of 4

The complete text of the site in 4 parts. Parts:

Part 1 has the contents list of every section and case study.

Case studies in full

Cutting Churn Before the Ticket Arrives (SaaS)

How Loomstack Inc. consolidated its customer-success stack into a single Airtable workspace, surfacing at-risk accounts 47 days earlier and lifting net revenue retention from 102% to 114%.

Section: Airtable

Executive Summary

A purpose-built customer-success workspace

Loomstack Inc. is a Series B B2B SaaS company providing workflow automation tooling to mid-market and enterprise customers, with roughly $24M ARR across 280 paying accounts. By Q4 2025 the customer success team was operating across five disconnected tools: HubSpot for CRM data, Zendesk for support tickets, Stripe for subscriptions, a homegrown product-usage dashboard, and a sprawl of Google Sheets for everything that didn’t fit elsewhere. Health scoring was a quarterly spreadsheet exercise, and renewal forecasts were assembled by hand the week before each board meeting.

Over a 9-week engagement we replaced this stack’s coordination layer with a single Airtable workspace, a unified accounts table, computed health scores updated nightly, role-specific Interfaces for CSMs and the VP, and an automation suite that triggers playbooks before churn risk turns into a churn ticket. HubSpot, Zendesk and Stripe still own their primary data; Airtable is the operating surface on top.

Outcomes after 90 days in production

At a glance

Before: 5 disconnected tools, quarterly health scoring done in a spreadsheet, renewal forecast assembled by hand, average risk detection lag of 51 days.

After: 1 Airtable workspace as the CS operating layer, health scores recomputed nightly, live forecast on the VP dashboard, average risk detection lag of 4 days.

Background

The client and the problem

About Loomstack

Loomstack sells a workflow automation platform to mid-market and enterprise operations teams across North America and EMEA. Headcount is 180; the customer-facing org is split into 14 CSMs (segmented by Enterprise / Mid-Market), 6 support engineers, and 4 account executives covering expansion. The CS leadership reports to a VP of Customer Success who in turn reports to the COO.

The pain points that triggered the project

Why Airtable

Loomstack evaluated three category-leading customer success platforms and Airtable. The decision came down to four factors: (1) Loomstack already had clean data sitting in HubSpot, Zendesk and Stripe, what they needed wasn’t another database, but a coordination layer on top, (2) the CS ops team wanted to own the system and iterate on it weekly without a vendor implementation queue, (3) the health-score formula needed to be theirs, not a vendor’s black box, and (4) integration with Slack was non-negotiable. Airtable’s linked-record model, sync sources, and native Slack actions made it the right fit.

Approach

How the implementation was run

We delivered the implementation in four phases over nine weeks. A core working group made up of the VP of Customer Success, two senior CSMs (one per segment), the head of CS Ops, and a RevOps analyst met twice a week to validate the data model, review prototypes, and approve each automation before it went live.

PhaseWeeksWhat we didOutput
1. Discovery1–2Shadowed CSMs through 5 customer reviews. Audited HubSpot, Zendesk, Stripe and the usage dashboard. Workshopped the health-score formula with leadership.Process map, data dictionary, agreed health-score definition.
2. Data model & sync3–4Designed 11-table relational schema. Configured one-way syncs from HubSpot (accounts/contacts), Stripe (subscriptions) and Zendesk (tickets).Live Airtable base with synced data; ER diagram.
3. Interfaces & automations5–7Built the CSM Command Center, the Account Detail Workspace and the VP Forecast Room. Designed and tested 9 automations.3 production interfaces; automation library.
4. Pilot, training, go-live8–9Piloted with 4 CSMs for one week, trained all 14 CSMs and the VP team, ran a 3-day shadow-mode period before full cutover.Single-day cutover; 14 / 14 CSMs active on day 1.

Guiding principles

What Was Built

The Accounts table: the heart of the workspace

Every paying customer is a record in the Accounts table. From here, CSMs can see their book of business at a glance, sliced by segment, plan, health band, or renewal window. The grid below shows the production view used by the VP each Monday to triage the team’s at-risk accounts.

Image: Figure 1: The Accounts grid view, grouped by segment and coloured by health band. The footer rolls up total ARR and counts at-risk / critical accounts across the visible records.

Highlights of this view

Two more views the team uses every day

Kanban by health band is the view the VP uses on Monday morning. Cards are stack-ordered by ARR, so the largest at-risk accounts always sit at the top. The colour bar on the left of each card encodes the band; the avatar in the corner is the CSM.

Image: Figure 2: Kanban view of the Accounts table, grouped by health band. Healthy / At risk / Critical / Onboarding / Churned columns make book composition obvious in one glance.

The expanded account record

Clicking any row opens the full account detail. This is the screen a CSM lives in during a customer review, every field a CSM might want is on one page, computed live from the underlying tables. The Health Score, Usage Trend, Open Tickets and Days to Renewal all turn red automatically when they cross their respective thresholds.

Image: Figure 3: The expanded record view for ENT-00142 (Halcyon Robotics). Notice that the timeline at the bottom shows the champion-change event detected on Apr 18 that automatically triggered a save playbook.

The Data Model

Eleven tables, one source of truth

The base is built around Accounts as the central entity. Everything else (contacts, subscriptions, tickets, health scores, renewals, playbooks, NPS responses) links back to an account. This single design decision is what makes the rollups, formulas and automations possible.

Image: Figure 4: The 11-table schema. Accounts is the hub; Subscriptions and Tickets are synced from Stripe and Zendesk respectively; the rest is native to Airtable.

Field-level decisions worth calling out

Interfaces

Three role-based interfaces, one underlying base

Different people on the CS org need different things from the same data. We built three Interfaces on top of the base, each tailored to a role. All three read from and write to the same tables. There is no duplication.

The CSM Command Center

This is the screen a CSM opens when they sit down at their desk. It surfaces the four KPIs that matter to them, the live health distribution of their book, a renewal forecast for the next six months, and a prioritised list of accounts that need attention today, all filtered to their book automatically.

Image: Figure 5: The CSM Command Center, scoped to the logged-in CSM. KPI cards on top; health distribution and renewal forecast charts below; on the right, the accounts that need attention today, sorted by ARR × risk.

Two other interfaces

Automations

Automating the work that catches churn early

Nine automations remove roughly 3,700 manual touchpoints from the team’s month, but more importantly, they catch churn signals while there’s still time to act. None of them replace a CSM’s judgment; they make sure no signal slips through.

Image: Figure 6: The Automations panel. Nine automations, eight currently enabled, with run counts visible per automation. The detail pane on the right shows the trigger and three actions of the renewal-playbook workflow.

The automation catalogue

Each automation is owned by a named person on the CS Ops team, documented in a Notion runbook, and reviewed monthly.

#AutomationTriggerActionsReplaces
1Health score recompute (nightly)Schedule: 2:00 AM, every nightRuns the health-score formula across all accounts; writes a row into Health Scores; updates health_band.Quarterly spreadsheet exercise
2Score drop > 15 pts → Alert CSMHealth Scores.delta_30d < −15Slack DM to CSM + tags account as ‘momentum loss’; logs into the activity timeline.Nothing: drops were missed
3Renewal in 90/60/30 days → PlaybookAccounts.days_to_renewal IN (90, 60, 30)Branches on plan + health to pick playbook (e.g. Enterprise + <80 → exec save). Spawns 5 linked tasks.Manual playbook lookup in Notion
4Trial ending in 7 days → Sales handoffSubscriptions.status = trial AND term_end in 7 daysCreates handoff record; assigns AE; Slack DM to AE with usage summary; books a calendar slot.CSM-to-AE handoff via email
5New ticket P1 → Slack war roomTickets.priority = P1 (synced from Zendesk)Posts to #cs-warroom; tags CSM, AE, support engineer; creates a Linear issue if flagged.Manual Slack post by support
6NPS detractor → Recovery sequenceNPS Responses.score ≤ 6Sets account flag; emails CSM with NPS comment; creates a follow-up task due in 48 hours.NPS responses going unread
7Usage anomaly → Tag accountUsage signal: WAU drop > 25% w/wSets a ‘usage_drop’ tag; surfaces it on CSM dashboard ‘Needs attention’ panel.Caught (sometimes) at quarter-end
8Onboarding day 14/30/60 check-insSchedule: nightly scan of accounts in OnboardingSpawns the right check-in task at each milestone; emails customer with the day-30 product-tour link.Three different CSM templates
9Champion change detected → PlaybookContacts.is_primary changes OR HubSpot role-change signalCreates ‘Champion change’ playbook record; spawns a 5-step exec-meeting save plan.Nothing: usually noticed too late

Worked example: automation #3 in detail

Integrations

How the workspace connects to the SaaS stack

Airtable doesn’t replace HubSpot, Zendesk, Stripe, or the product analytics warehouse. It coordinates them.

SystemDirectionWhat flowsMechanism
HubSpotOne-way (in)Accounts and Contacts sync nightly. Field-level mapping for industry, employee count, lifecycle stage and owner.Airtable HubSpot sync source
StripeOne-way (in)Subscription rows sync every 4 hours. ARR rolls up from subscription records to the account.Stripe → Airtable via Zapier
ZendeskOne-way (in)Tickets sync every 15 minutes. Priority, status, assignee, and time-open are all rollup-able.Native Zendesk sync
SlackOne-way (out)Health drops, P1 tickets, renewal-playbook triggers, NPS detractor alerts.Native Slack action in Automations
Mixpanel / SnowflakeOne-way (in)Daily WAU and feature-adoption metrics per account, used as inputs to the health score.Snowflake → Airtable via reverse-ETL
LinearOne-way (out)P1 tickets that need engineering escalation create a Linear issue with customer context attached.Linear API call from script action
Gmail / OutlookOne-way (out)Customer-facing emails (NPS follow-ups, day-30 onboarding tour) sent from automation actions.Native email actions

Permissions & data handling

Results

Measurable change after 90 days in production

MetricBeforeAfterChange
Net revenue retention (NRR)102%114%+12 pts
Average risk-detection lag51 days4 days−92%
Renewal forecast variance± 22%± 6%−73%
At-risk ARR identified earliern/a$1.2M (Q1)+ saved revenue
CSM time spent on reporting≈ 6 hr / week≈ 45 min / week−87%
Playbook trigger rate≈ 14% of cases100% of cases+ coverage
Automated touchpoints / month03,742 across 8 active flowsn/a
CSM adoption (firm-wide)n/a14 / 14 active on day 1100%

In the words of the team

The thing that changed isn’t really the dashboard: it’s that the team has time. My CSMs aren’t building reports any more, they’re actually talking to customers. And when an account starts going sideways, we know about it the next morning, not three weeks later when the renewal blows up. We saved $1.2M of ARR last quarter that would otherwise have walked out the door.

VP of Customer Success, Loomstack Inc.

What we’d do again, and what we’d change

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

End of part 4 of 4: that is the complete text of the site.