🔧 Practice Project: FlowMaster Plumbing & Heating

Build a real-world mobile-first website using React, WordPress, and Tailwind CSS

Project Overview: Your Client's Requirements

FlowMaster Plumbing & Heating is a local business that needs a modern, mobile-first website. They currently lose 65% of potential customers because their old site doesn't work on phones. Your mission: build them a lightning-fast, SEO-optimized solution using React + WordPress + Tailwind CSS.

graph TD A[Client Requirements] --> B[Mobile-First Design] A --> C[Emergency Call Button] A --> D[Service Listings] A --> E[Online Booking] A --> F[Customer Reviews] B --> G[Phone Users: 75%] B --> H[Tablet Users: 15%] B --> I[Desktop Users: 10%] style A fill:#e74c3c,stroke:#333,stroke-width:3px style B fill:#3498db,stroke:#333,stroke-width:2px style G fill:#2ecc71,stroke:#333,stroke-width:2px

Business Goals

📱 Mobile Conversions

Increase emergency calls by 40%

📅 Online Bookings

Enable 24/7 appointment scheduling

⭐ Trust Building

Showcase reviews and certifications

🔍 Local SEO

Rank #1 for "plumber near me"

Exercise Requirements

Technical Requirements Checklist

Frontend (React + Tailwind)

☐ Mobile-first responsive design
☐ Sticky emergency call button
☐ Service cards with icons
☐ Review carousel/slider
☐ Contact form with validation
☐ Google Maps integration
☐ Dark mode toggle

Backend (WordPress)

☐ Custom post type: Services
☐ Custom post type: Reviews
☐ ACF for service details
☐ REST API endpoints
☐ Contact form handler
☐ SEO optimization
☐ Cache configuration

Step 1: WordPress Setup

First, let's set up the WordPress backend with custom post types and fields for our plumbing business.

// functions.php - Register Custom Post Types

function flowmaster_register_post_types() {
    // Services Post Type
    register_post_type('services', [
        'labels' => [
            'name' => 'Services',
            'singular_name' => 'Service',
        ],
        'public' => true,
        'show_in_rest' => true,
        'menu_icon' => 'dashicons-hammer',
        'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
        'has_archive' => true,
        'rewrite' => ['slug' => 'services'],
    ]);

    // Reviews Post Type
    register_post_type('reviews', [
        'labels' => [
            'name' => 'Reviews',
            'singular_name' => 'Review',
        ],
        'public' => true,
        'show_in_rest' => true,
        'menu_icon' => 'dashicons-star-filled',
        'supports' => ['title', 'editor', 'custom-fields'],
    ]);
}
add_action('init', 'flowmaster_register_post_types');

Adding REST API Fields

// Add REST API fields for custom data
function flowmaster_add_rest_fields() {
    register_rest_field('services', 'service_details', [
        'get_callback' => function($post) {
            return [
                'price_range' => get_field('price_range', $post['id']),
                'duration' => get_field('duration', $post['id']),
                'emergency' => get_field('emergency_available', $post['id']),
            ];
        },
        'schema' => null,
    ]);
}
add_action('rest_api_init', 'flowmaster_add_rest_fields');

Step 2: React Component Structure

Build mobile-first React components using Tailwind CSS for styling.

Main App Component

// App.js - Main Application Component
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
    const [services, setServices] = useState([]);
    const [reviews, setReviews] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetchData();
    }, []);

    const fetchData = async () => {
        try {
            const [servicesData, reviewsData] = await Promise.all([
                WordPressAPI.getServices(),
                WordPressAPI.getReviews()
            ]);
            setServices(servicesData);
            setReviews(reviewsData);
        } catch (error) {
            console.error('Error fetching data:', error);
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="min-h-screen bg-white">
            <EmergencyBar />
            <MobileNav />
            <main>
                <Hero />
                <Services services={services} loading={loading} />
                <Reviews reviews={reviews} />
                <Contact />
            </main>
            <Footer />
        </div>
    );
}

export default App;

Emergency Bar Component

// components/EmergencyBar.js - Sticky Emergency CTA
import React from 'react';

const EmergencyBar = () => {
    return (
        <div className="sticky top-0 z-50 bg-red-600 text-white">
            <div className="container mx-auto px-4">
                <div className="flex items-center justify-between py-2">
                    <span className="text-sm font-semibold">
                        24/7 Emergency Service
                    </span>
                    <a
                        href="tel:1-800-PLUMBER"
                        className="bg-white text-red-600 px-4 py-1 rounded-full"
                    >
                        Call Now
                    </a>
                </div>
            </div>
        </div>
    );
};

export default EmergencyBar;

Step 3: Mobile-First Service Cards

// components/Services.js - Responsive Service Grid
import React from 'react';

const Services = ({ services, loading }) => {
    if (loading) {
        return (
            <div className="py-12 px-4">
                <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
                    {[1, 2, 3].map(i => (
                        <div key={i} className="animate-pulse">
                            <div className="bg-gray-200 rounded-lg h-48"></div>
                        </div>
                    ))}
                </div>
            </div>
        );
    }

    return (
        <section className="py-12 px-4 bg-gray-50">
            <div className="container mx-auto">
                <h2 className="text-3xl font-bold text-center mb-8">
                    Book Your Service
                </h2>
                
                <form onSubmit={handleSubmit} className="max-w-2xl mx-auto">
                    {/* Urgency Selector */}
                    <div className="grid grid-cols-2 gap-2 mb-4">
                        <button
                            type="button"
                            className={`py-3 px-4 rounded-lg ${
                                formData.urgency === 'emergency'
                                    ? 'bg-red-600 text-white'
                                    : 'bg-gray-100'
                            }`}
                        >
                            🚨 Emergency
                        </button>
                        <button
                            type="button"
                            className={`py-3 px-4 rounded-lg ${
                                formData.urgency === 'regular'
                                    ? 'bg-blue-600 text-white'
                                    : 'bg-gray-100'
                            }`}
                        >
                            📅 Schedule
                        </button>
                    </div>

                    {/* Form fields */}
                    <input
                        type="text"
                        placeholder="Your Name"
                        required
                        className="w-full px-4 py-3 rounded-lg border mb-4"
                    />
                    
                    <input
                        type="tel"
                        placeholder="Phone Number"
                        required
                        className="w-full px-4 py-3 rounded-lg border mb-4"
                    />

                    <button
                        type="submit"
                        className="w-full bg-blue-600 text-white py-3 rounded-lg"
                    >
                        Book Appointment
                    </button>
                </form>
            </div>
        </section>
    );
};

export default Contact;

Step 6: API Service Layer

// services/api.js - WordPress API Integration
class WordPressAPIService {
    constructor() {
        this.baseURL = process.env.REACT_APP_WP_API_URL || 
                       'https://your-site.com/wp-json';
        this.cache = new Map();
    }

    async fetchWithCache(endpoint, options = {}) {
        const cacheKey = `${endpoint}`;
        const cached = this.cache.get(cacheKey);

        if (cached) {
            return cached;
        }

        try {
            const response = await fetch(
                `${this.baseURL}${endpoint}`, 
                options
            );
            const data = await response.json();
            
            this.cache.set(cacheKey, data);
            return data;
        } catch (error) {
            console.error('API Error:', error);
            throw error;
        }
    }

    async getServices() {
        return this.fetchWithCache(
            '/wp/v2/services?per_page=20'
        );
    }

    async getReviews() {
        return this.fetchWithCache(
            '/wp/v2/reviews?per_page=10'
        );
    }
}

export const WordPressAPI = new WordPressAPIService();

Step 7: Tailwind CSS Configuration

// tailwind.config.js - Tailwind Configuration
module.exports = {
    content: [
        "./src/**/*.{js,jsx,ts,tsx}",
    ],
    theme: {
        extend: {
            colors: {
                primary: {
                    50: '#eff6ff',
                    500: '#3b82f6',
                    600: '#2563eb',
                    700: '#1d4ed8',
                },
                emergency: {
                    500: '#ef4444',
                    600: '#dc2626',
                }
            },
            animation: {
                'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
            }
        },
    },
    plugins: [],
}

Package.json Dependencies

{
    "dependencies": {
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-router-dom": "^6.8.0",
        "axios": "^1.3.0",
        "tailwindcss": "^3.2.0",
        "@heroicons/react": "^2.0.0"
    },
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test"
    }
}

Step 8: Performance Optimization

// utils/performance.js - Performance Optimizations
import { useState, useEffect } from 'react';

// Lazy Loading Images Component
export const LazyImage = ({ src, alt, className, ...props }) => {
    const [imageSrc, setImageSrc] = useState(null);
    const [imageRef, setImageRef] = useState();

    useEffect(() => {
        let observer;
        
        if (imageRef && imageSrc === null) {
            if (IntersectionObserver) {
                observer = new IntersectionObserver(
                    entries => {
                        entries.forEach(entry => {
                            if (entry.isIntersecting) {
                                setImageSrc(src);
                                observer.unobserve(imageRef);
                            }
                        });
                    },
                    { threshold: 0.1, rootMargin: '50px' }
                );
                observer.observe(imageRef);
            } else {
                // Fallback for older browsers
                setImageSrc(src);
            }
        }
        
        return () => {
            if (observer && observer.unobserve) {
                observer.unobserve(imageRef);
            }
        };
    }, [imageRef, imageSrc, src]);

    return (
        <img
            ref={setImageRef}
            src={imageSrc || '/placeholder.webp'}
            alt={alt}
            className={className}
            loading="lazy"
            {...props}
        />
    );
};

// Debounce Function for Search
export const debounce = (func, wait) => {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
};

Step 9: Service Worker for Offline Support

// public/sw.js - Service Worker
const CACHE_NAME = 'flowmaster-v1';
const urlsToCache = [
    '/',
    '/static/css/main.css',
    '/static/js/bundle.js',
    '/offline.html'
];

// Install event - cache resources
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then((cache) => {
                console.log('Opened cache');
                return cache.addAll(urlsToCache);
            })
    );
});

// Fetch event - serve from cache when offline
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((response) => {
                // Cache hit - return response
                if (response) {
                    return response;
                }
                return fetch(event.request);
            })
            .catch(() => {
                // Offline - return offline page
                return caches.match('/offline.html');
            })
    );
});

Complete Solution Architecture

Testing Your Solution

Mobile Performance Checklist

✅ First Paint: Under 1.5 seconds on 3G
✅ Interactive: Under 3 seconds on mobile
✅ Lighthouse Score: 95+ on mobile
✅ Touch Targets: Minimum 44x44 pixels
✅ Text Size: Minimum 16px base font
✅ Viewport: No horizontal scrolling
✅ Images: WebP format with fallbacks
✅ Offline: Service worker caching

SEO Checklist

✅ Meta Tags: Dynamic per page
✅ Schema.org: LocalBusiness markup
✅ Sitemap: Auto-generated XML
✅ Mobile-First: Responsive design
✅ Page Speed: Core Web Vitals pass
✅ Local SEO: Google My Business integration
✅ Content: Service pages indexed
✅ Reviews: Structured data for ratings

Bonus: Advanced Features

SMS Integration

Add Twilio for text notifications:

Appointment reminders
Service updates
Emergency confirmations

Live Chat

Implement real-time support:

WebSocket connection
Operator dashboard
Chatbot for FAQs

Price Calculator

Interactive pricing tool:

Service selection
Instant quotes
Email estimates

Customer Portal

Account management:

Service history
Invoices & payments
Maintenance schedules
Our Services </h2> {/* Mobile-first grid */} <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> {services.map((service) => ( <ServiceCard key={service.id} service={service} /> ))} </div> </div> </section> ); }; const ServiceCard = ({ service }) => { const { title, excerpt, service_details } = service; return ( <div className="bg-white rounded-xl shadow-lg hover:shadow-xl"> <div className="p-6 bg-gradient-to-r from-blue-500 to-blue-600"> {/* Service icon would go here */} </div> <div className="p-6"> <h3 className="text-xl font-semibold mb-2"> {title.rendered} </h3> <p className="text-gray-600 mb-4"> {excerpt.rendered} </p> <button className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg"> Book Service </button> </div> </div> ); }; export default Services;

Step 4: Customer Reviews Carousel

// components/Reviews.js - Mobile-Optimized Review Slider
import React, { useState, useRef } from 'react';

const Reviews = ({ reviews }) => {
    const [currentIndex, setCurrentIndex] = useState(0);
    const scrollRef = useRef(null);

    const renderStars = (rating) => {
        return Array.from({ length: 5 }).map((_, i) => (
            <span key={i}>{i < rating ? '⭐' : '☆'}</span>
        ));
    };

    return (
        <section className="py-12 bg-white">
            <div className="container mx-auto px-4">
                <h2 className="text-3xl font-bold text-center mb-8">
                    What Our Customers Say
                </h2>
                
                <div ref={scrollRef} className="flex overflow-x-auto snap-x">
                    {reviews.map((review) => (
                        <div key={review.id} className="flex-shrink-0 w-full md:w-96 snap-center p-4">
                            <div className="bg-gray-50 rounded-xl p-6">
                                <div className="flex mb-3">
                                    {renderStars(review.review_meta?.rating || 5)}
                                </div>
                                <p className="text-gray-700 mb-4">
                                    {review.content.rendered}
                                </p>
                                <p className="font-semibold">
                                    {review.review_meta?.customer_name}
                                </p>
                            </div>
                        </div>
                    ))}
                </div>
            </div>
        </section>
    );
};

export default Reviews;

Step 5: Contact Form & Booking System

// components/Contact.js - Mobile-Optimized Contact Form
import React, { useState } from 'react';

const Contact = () => {
    const [formData, setFormData] = useState({
        name: '',
        phone: '',
        email: '',
        service: '',
        urgency: 'regular',
        message: ''
    });

    const handleSubmit = async (e) => {
        e.preventDefault();
        // Handle form submission
        console.log('Form submitted:', formData);
    };

    return (
        <section className="py-12 bg-gray-50">
            <div className="container mx-auto px-4">
                <h2 className="text-3xl font-bold text-center mb-8">

Testing Your Solution

Mobile Performance Checklist

✅ First Paint: Under 1.5 seconds on 3G
✅ Interactive: Under 3 seconds on mobile
✅ Lighthouse Score: 95+ on mobile
✅ Touch Targets: Minimum 44x44 pixels
✅ Text Size: Minimum 16px base font
✅ Viewport: No horizontal scrolling
✅ Images: WebP format with fallbacks
✅ Offline: Service worker caching

SEO Checklist

✅ Meta Tags: Dynamic per page
✅ Schema.org: LocalBusiness markup
✅ Sitemap: Auto-generated XML
✅ Mobile-First: Responsive design
✅ Page Speed: Core Web Vitals pass
✅ Local SEO: Google My Business integration
✅ Content: Service pages indexed
✅ Reviews: Structured data for ratings

Bonus: Advanced Features

SMS Integration

Add Twilio for text notifications:

Appointment reminders
Service updates
Emergency confirmations

Live Chat

Implement real-time support:

WebSocket connection
Operator dashboard
Chatbot for FAQs

Price Calculator

Interactive pricing tool:

Service selection
Instant quotes
Email estimates

Customer Portal

Account management:

Service history
Invoices & payments
Maintenance schedules