Customer Portal Guide - luckydeva03/barbershop_app GitHub Wiki

👥 Customer Portal Guide

Panduan lengkap untuk customer dalam menggunakan portal barbershop management system.

🚀 Getting Started

Registration & Login

1. Creating Account

URL: /register

Registration Methods:

  • Email Registration: Traditional email + password
  • Google OAuth: Quick signup with Google account

Required Information:

  • Full Name
  • Email Address
  • Phone Number (optional)
  • Password (if not using Google)

Registration Flow:

1. Visit /register
2. Choose registration method:
   • Fill registration form OR
   • Click "Continue with Google"
3. Verify email (if email registration)
4. Automatic login and redirect to dashboard

2. Login Process

URL: /login

Login Options:

  • Email + Password
  • Google OAuth (if account linked)

Login Flow:

1. Visit /login
2. Enter credentials OR click Google login
3. Successful authentication
4. Redirect to /dashboard

3. Password Reset

URL: /forgot-password

Reset Process:

1. Enter email address
2. Check email for reset link
3. Click link and set new password
4. Login with new credentials

🏠 Dashboard Overview

Main Dashboard (/dashboard)

Welcome Section

  • Personalized greeting with user name
  • Profile photo (Google synced or default avatar)
  • Quick account stats overview

Point Balance Display

<div class="points-card">
    <h3>Your Points Balance</h3>
    <div class="points-display">
        <span class="points-value">1,250</span>
        <span class="points-label">points</span>
    </div>
    <small class="text-muted">Earned this month: +150 pts</small>
</div>

Quick Actions

  • Redeem Code: Quick access to redeem form
  • Browse Stores: Link to store directory
  • View History: Point transaction history
  • Update Profile: Account settings

Recent Activity

  • Last 5 point transactions
  • Recent store visits
  • Latest reviews written

💎 Point System

Understanding Points

How to Earn Points

  1. Welcome Bonus: 50 points upon registration
  2. Redeem Codes: Use promo codes from barbershops
  3. Write Reviews: 10 points per review
  4. Referral Bonus: 25 points for each friend referred
  5. Birthday Bonus: 100 points annually

Point Values

  • Minimum Redeem: 100 points
  • Maximum Daily Redeem: 5 codes
  • Point Expiry: 365 days from last activity

Redeem Code System

How to Redeem (/dashboard - Redeem Section)

Step-by-Step Process:

1. Get code from barbershop (physical/digital)
2. Login to customer portal
3. Navigate to dashboard
4. Find "Redeem Code" section
5. Enter code in input field
6. Click "Redeem" button
7. Confirmation message + points added

Redeem Form:

<form id="redeem-form" method="POST" action="{{ route('points.redeem') }}">
    @csrf
    <div class="redeem-card">
        <h4>Redeem Your Code</h4>
        
        <div class="input-group mb-3">
            <input type="text" 
                   class="form-control" 
                   name="code" 
                   placeholder="Enter your code (e.g., WELCOME50)"
                   style="text-transform: uppercase"
                   maxlength="20"
                   required>
            <button type="submit" class="btn btn-primary">
                Redeem
            </button>
        </div>
        
        <small class="form-text text-muted">
            Codes are not case-sensitive and expire after use
        </small>
    </div>
</form>

Code Validation Rules

  • Format: Alphanumeric only (A-Z, 0-9)
  • Length: 4-20 characters
  • Case: Not case-sensitive
  • Expiry: Checked on redemption
  • Usage Limit: Enforced per code

Error Handling

// Common error messages
const errorMessages = {
    'invalid_code': 'Invalid code. Please check and try again.',
    'expired_code': 'This code has expired.',
    'used_code': 'This code has already been used.',
    'limit_reached': 'Maximum usage limit reached for this code.',
    'rate_limit': 'Too many attempts. Please wait before trying again.',
    'user_limit': 'You have reached your daily redeem limit.'
};

📊 Point History

Transaction History (/dashboard/history)

History Table Features

  • Date & Time: When transaction occurred
  • Type: Earned, Redeemed, Expired, Bonus
  • Points: Amount (+/-)
  • Description: What the transaction was for
  • Balance: Running total after transaction

History Display

<div class="history-section">
    <h4>Transaction History</h4>
    
    <div class="table-responsive">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Type</th>
                    <th>Points</th>
                    <th>Description</th>
                    <th>Balance</th>
                </tr>
            </thead>
            <tbody>
                @foreach($history as $transaction)
                <tr>
                    <td>{{ $transaction->created_at->format('M d, Y H:i') }}</td>
                    <td>
                        <span class="badge bg-{{ $transaction->type == 'earned' ? 'success' : 'info' }}">
                            {{ ucfirst($transaction->type) }}
                        </span>
                    </td>
                    <td class="fw-bold {{ $transaction->points > 0 ? 'text-success' : 'text-danger' }}">
                        {{ $transaction->points > 0 ? '+' : '' }}{{ $transaction->points }}
                    </td>
                    <td>{{ $transaction->description }}</td>
                    <td>{{ number_format($transaction->running_balance) }}</td>
                </tr>
                @endforeach
            </tbody>
        </table>
    </div>
</div>

Filter Options

  • Date Range: Last 30 days, 3 months, 6 months, All time
  • Transaction Type: All, Earned, Redeemed, Expired, Bonus
  • Search: Search by description

🏪 Store Directory

Browse Stores (/stores)

Store Listing Features

  • Grid Layout: Responsive card-based design
  • Store Information: Name, description, address, phone
  • Rating Display: Average rating with stars
  • Review Count: Number of customer reviews
  • Location Map: Google Maps integration
  • WhatsApp Booking: Direct booking via WhatsApp

Store Card Component

<div class="store-card">
    <div class="store-image">
        <img src="{{ $store->image_url }}" alt="{{ $store->name }}">
    </div>
    
    <div class="store-info">
        <h5 class="store-name">{{ $store->name }}</h5>
        
        <!-- Rating -->
        <div class="rating-display">
            @for($i = 1; $i <= 5; $i++)
                <i class="fas fa-star {{ $i <= $store->average_rating ? 'text-warning' : 'text-muted' }}"></i>
            @endfor
            <span class="rating-text">{{ $store->average_rating }}/5 ({{ $store->reviews_count }} reviews)</span>
        </div>
        
        <!-- Address -->
        <p class="store-address">
            <i class="fas fa-map-marker-alt"></i>
            {{ $store->address }}
        </p>
        
        <!-- Description -->
        <p class="store-description">
            {{ Str::limit($store->description, 120) }}
        </p>
    </div>
    
    <div class="store-actions">
        <button class="btn btn-outline-primary btn-sm" onclick="viewStoreDetails({{ $store->id }})">
            View Details
        </button>
        
        @if($store->phone)
        <a href="https://wa.me/{{ formatWhatsAppNumber($store->phone) }}?text={{ urlencode('Hi! I would like to book an appointment at ' . $store->name) }}" 
           class="btn btn-success btn-sm" target="_blank">
            <i class="fab fa-whatsapp"></i> Book Now
        </a>
        @endif
    </div>
</div>

Store Detail View (/stores/{id})

Detailed Information

  • Complete Description: Full store details
  • Photo Gallery: Multiple store images
  • Location Map: Interactive Google Maps
  • Contact Information: Phone, address, hours
  • Services Offered: List of available services
  • Reviews Section: All customer reviews

Google Maps Integration

<div class="store-location">
    <h4>Location</h4>
    <div id="store-map" style="height: 300px; width: 100%;"></div>
    
    <script>
    function initStoreMap() {
        const storeLocation = { lat: {{ $store->latitude }}, lng: {{ $store->longitude }} };
        
        const map = new google.maps.Map(document.getElementById('store-map'), {
            zoom: 15,
            center: storeLocation,
        });
        
        const marker = new google.maps.Marker({
            position: storeLocation,
            map: map,
            title: '{{ $store->name }}',
        });
        
        const infoWindow = new google.maps.InfoWindow({
            content: `
                <div>
                    <h6>{{ $store->name }}</h6>
                    <p>{{ $store->address }}</p>
                    <p>Phone: {{ $store->phone }}</p>
                </div>
            `
        });
        
        marker.addListener('click', () => {
            infoWindow.open(map, marker);
        });
    }
    </script>
</div>

⭐ Review System

Writing Reviews

Review Form (/stores/{id}/review)

<form method="POST" action="{{ route('reviews.store', $store) }}">
    @csrf
    
    <div class="review-form">
        <h4>Write a Review</h4>
        
        <!-- Rating Selection -->
        <div class="rating-input mb-3">
            <label class="form-label">Your Rating</label>
            <div class="star-rating">
                @for($i = 1; $i <= 5; $i++)
                    <input type="radio" name="rating" value="{{ $i }}" id="star{{ $i }}" required>
                    <label for="star{{ $i }}" class="star"></label>
                @endfor
            </div>
        </div>
        
        <!-- Comment -->
        <div class="mb-3">
            <label for="comment" class="form-label">Your Review</label>
            <textarea class="form-control" 
                      name="comment" 
                      id="comment" 
                      rows="4" 
                      placeholder="Share your experience..."
                      maxlength="1000"></textarea>
            <div class="form-text">Maximum 1000 characters</div>
        </div>
        
        <button type="submit" class="btn btn-primary">Submit Review</button>
    </div>
</form>

Review Guidelines

  • One Review Per Store: Users can only review each store once
  • Rating Scale: 1-5 stars
  • Comment Length: Maximum 1000 characters
  • Content Policy: No offensive language or spam
  • Edit Permission: Users can edit their own reviews

Viewing Reviews

Review Display

<div class="reviews-section">
    <h4>Customer Reviews ({{ $store->reviews_count }})</h4>
    
    @foreach($reviews as $review)
    <div class="review-item">
        <div class="review-header">
            <div class="reviewer-info">
                <img src="{{ $review->user->profile_photo }}" alt="{{ $review->user->name }}" class="reviewer-avatar">
                <div>
                    <h6 class="reviewer-name">{{ $review->user->name }}</h6>
                    <small class="review-date">{{ $review->created_at->diffForHumans() }}</small>
                </div>
            </div>
            
            <div class="review-rating">
                @for($i = 1; $i <= 5; $i++)
                    <i class="fas fa-star {{ $i <= $review->rating ? 'text-warning' : 'text-muted' }}"></i>
                @endfor
            </div>
        </div>
        
        <div class="review-content">
            <p>{{ $review->comment }}</p>
        </div>
        
        @if(auth()->id() === $review->user_id)
        <div class="review-actions">
            <button class="btn btn-sm btn-outline-primary" onclick="editReview({{ $review->id }})">
                Edit
            </button>
            <button class="btn btn-sm btn-outline-danger" onclick="deleteReview({{ $review->id }})">
                Delete
            </button>
        </div>
        @endif
    </div>
    @endforeach
</div>

👤 Profile Management

Profile Settings (/profile)

Personal Information

<div class="profile-section">
    <h4>Personal Information</h4>
    
    <form method="POST" action="{{ route('profile.update') }}">
        @csrf
        @method('PUT')
        
        <!-- Profile Photo -->
        <div class="photo-section mb-4">
            <div class="current-photo">
                <img src="{{ auth()->user()->profile_photo }}" 
                     alt="Profile Photo" 
                     class="profile-photo-large">
            </div>
            
            @if(auth()->user()->google_id)
                <small class="text-muted">Profile photo synced from Google</small>
            @else
                <input type="file" name="profile_photo" class="form-control mt-2" accept="image/*">
            @endif
        </div>
        
        <!-- Name -->
        <div class="mb-3">
            <label for="name" class="form-label">Full Name</label>
            <input type="text" class="form-control" name="name" value="{{ auth()->user()->name }}" required>
        </div>
        
        <!-- Email -->
        <div class="mb-3">
            <label for="email" class="form-label">Email Address</label>
            <input type="email" class="form-control" name="email" value="{{ auth()->user()->email }}" required>
        </div>
        
        <!-- Phone -->
        <div class="mb-3">
            <label for="phone" class="form-label">Phone Number</label>
            <input type="tel" class="form-control" name="phone" value="{{ auth()->user()->phone }}">
        </div>
        
        <button type="submit" class="btn btn-primary">Update Profile</button>
    </form>
</div>

Account Security

<div class="security-section">
    <h4>Account Security</h4>
    
    <!-- Password Change -->
    @if(!auth()->user()->google_id)
    <form method="POST" action="{{ route('password.update') }}">
        @csrf
        @method('PUT')
        
        <div class="mb-3">
            <label for="current_password" class="form-label">Current Password</label>
            <input type="password" class="form-control" name="current_password" required>
        </div>
        
        <div class="mb-3">
            <label for="password" class="form-label">New Password</label>
            <input type="password" class="form-control" name="password" required>
        </div>
        
        <div class="mb-3">
            <label for="password_confirmation" class="form-label">Confirm New Password</label>
            <input type="password" class="form-control" name="password_confirmation" required>
        </div>
        
        <button type="submit" class="btn btn-warning">Change Password</button>
    </form>
    @endif
    
    <!-- Google Account Linking -->
    <div class="google-account mt-4">
        @if(auth()->user()->google_id)
            <div class="alert alert-success">
                <i class="fab fa-google"></i>
                Your Google account is linked
            </div>
            
            <form method="POST" action="{{ route('google.unlink') }}">
                @csrf
                <button type="submit" class="btn btn-outline-danger">
                    Unlink Google Account
                </button>
            </form>
        @else
            <div class="alert alert-info">
                Link your Google account for easier login
            </div>
            
            <a href="{{ route('google.login') }}" class="btn btn-outline-primary">
                <i class="fab fa-google"></i>
                Link Google Account
            </a>
        @endif
    </div>
</div>

📱 Mobile Experience

Responsive Design Features

  • Mobile-First: Optimized for smartphone usage
  • Touch-Friendly: Large buttons and touch targets
  • Fast Loading: Optimized images and assets
  • Offline Capability: Service worker for basic offline functionality

Mobile Navigation

<!-- Mobile hamburger menu -->
<nav class="mobile-nav d-md-none">
    <div class="mobile-nav-header">
        <div class="nav-brand">
            <img src="{{ asset('images/logo.png') }}" alt="Logo">
            {{ config('app.name') }}
        </div>
        <button class="nav-toggle" id="mobile-nav-toggle">
            <span></span>
            <span></span>
            <span></span>
        </button>
    </div>
    
    <div class="mobile-nav-menu" id="mobile-nav-menu">
        <a href="{{ route('dashboard') }}" class="nav-item">
            <i class="fas fa-home"></i>
            Dashboard
        </a>
        <a href="{{ route('stores.index') }}" class="nav-item">
            <i class="fas fa-store"></i>
            Stores
        </a>
        <a href="{{ route('profile.edit') }}" class="nav-item">
            <i class="fas fa-user"></i>
            Profile
        </a>
        <a href="{{ route('logout') }}" class="nav-item">
            <i class="fas fa-sign-out-alt"></i>
            Logout
        </a>
    </div>
</nav>

Progressive Web App Features

// Service Worker for offline capability
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(registration => console.log('SW registered'))
        .catch(error => console.log('SW registration failed'));
}

// Add to homescreen prompt
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
    e.preventDefault();
    deferredPrompt = e;
    
    // Show install button
    const installButton = document.getElementById('install-app');
    installButton.style.display = 'block';
    
    installButton.addEventListener('click', () => {
        deferredPrompt.prompt();
        deferredPrompt.userChoice.then((choiceResult) => {
            deferredPrompt = null;
        });
    });
});

🔔 Notifications

In-App Notifications

  • Point Earnings: When points are added
  • Code Expiry: When codes are about to expire
  • New Features: Updates and announcements
  • Promotional: Special offers and events

Notification Center

<div class="notification-center">
    <div class="notification-header">
        <h4>Notifications</h4>
        <button class="btn btn-sm btn-outline-secondary" onclick="markAllRead()">
            Mark All Read
        </button>
    </div>
    
    <div class="notification-list">
        @foreach($notifications as $notification)
        <div class="notification-item {{ $notification->read_at ? '' : 'unread' }}">
            <div class="notification-icon">
                <i class="fas fa-{{ $notification->icon }}"></i>
            </div>
            <div class="notification-content">
                <h6>{{ $notification->title }}</h6>
                <p>{{ $notification->message }}</p>
                <small class="text-muted">{{ $notification->created_at->diffForHumans() }}</small>
            </div>
        </div>
        @endforeach
    </div>
</div>

🆘 Customer Support

Help & Support

  • FAQ Section: Common questions and answers
  • Contact Form: Direct support ticket submission
  • Live Chat: Real-time support (if available)
  • Email Support: Support email contact

FAQ Categories

  1. Account & Registration
  2. Points & Rewards
  3. Store Booking
  4. Technical Issues
  5. Privacy & Security

Next: Store Management untuk panduan pengelolaan data toko.

⚠️ **GitHub.com Fallback** ⚠️