Files
tenderpilot/public/IMPLEMENTATION_GUIDE.md
Peter Foster f969ecae04 feat: visual polish, nav login link, pricing badge fix, cursor fix, button contrast
- Hero mockup: enhanced 3D perspective and shadow
- Testimonials: illustrated SVG avatars
- Growth pricing card: visual prominence (scale, gradient, badge)
- Most Popular badge: repositioned to avoid overlapping heading
- Nav: added Log In link next to Start Free Trial
- Fixed btn-primary text colour on anchor tags (white on blue)
- Fixed cursor: default on all non-interactive elements
- Disabled user-select on non-form content to prevent text caret
2026-02-14 14:17:15 +00:00

16 KiB
Raw Permalink Blame History

TenderRadar Navigation System & Shared Layout

Complete implementation guide for consistent navigation, authentication, and styling across all TenderRadar pages.

📁 File Structure

/var/www/tenderradar/
├── auth.js                  # Shared auth utilities
├── app.css                  # Shared app styles
├── index.html              # Landing page (unchanged)
├── login.html              # Login page
├── signup.html             # Sign up page
├── dashboard.html          # Dashboard page
├── profile.html            # User profile page
├── alerts.html             # Alerts page
├── tenders.html            # Tenders page (optional)
├── styles.css              # Landing page styles (unchanged)
├── script.js               # Landing page script (unchanged)
└── components/
    ├── nav.js              # Navigation component
    └── footer.js           # Footer component

🚀 Quick Start

1. Add Auth Module to All App Pages

Add this to the <head> of every app page (dashboard, profile, alerts, etc.):

<!-- Authentication utilities (must be loaded first) -->
<script src="/auth.js"></script>

Add these before the closing </body> tag on every app page:

<!-- Navigation component -->
<script src="/components/nav.js"></script>

<!-- Footer component -->
<script src="/components/footer.js"></script>

3. Include App Styles

Add this to the <head> of every app page:

<!-- App-specific styles (complements/overrides landing styles) -->
<link rel="stylesheet" href="/app.css">

4. Protect Pages with Auth Check

Add this immediately after loading auth.js in your page JavaScript:

// Require authentication on this page
requireAuth();

📋 Complete Example: dashboard.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard | TenderRadar</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <link rel="icon" href="/favicon.ico" type="image/x-icon">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
    
    <!-- Landing page styles -->
    <link rel="stylesheet" href="/styles.css">
    
    <!-- App-specific styles -->
    <link rel="stylesheet" href="/app.css">
    
    <!-- Authentication utilities (must load first) -->
    <script src="/auth.js"></script>
</head>
<body>
    <!-- Navigation auto-injects here -->
    <!-- Footer auto-injects here -->
    
    <!-- Main content -->
    <main class="app-container">
        <div class="page-header">
            <div>
                <h1 class="page-title">Dashboard</h1>
                <p class="page-subtitle">Welcome back! Here's your tender overview.</p>
            </div>
            <div class="page-actions">
                <button class="btn btn-primary">New Alert</button>
            </div>
        </div>

        <!-- Your dashboard content here -->
        <div class="grid grid-2">
            <!-- Stat cards, charts, tables, etc. -->
        </div>
    </main>

    <!-- Component scripts (auto-initialize) -->
    <script src="/components/nav.js"></script>
    <script src="/components/footer.js"></script>

    <!-- Page-specific script -->
    <script>
        // Require authentication on this page
        requireAuth();

        // Your dashboard logic here
        document.addEventListener('DOMContentLoaded', () => {
            // Initialize dashboard
            loadDashboardData();
        });

        async function loadDashboardData() {
            const response = await fetchWithAuth('/api/dashboard');
            const data = await response.json();
            // Update UI with data
        }
    </script>
</body>
</html>

🔐 Authentication API Reference

getToken()

Retrieves the stored JWT token.

const token = getToken();
if (token) {
    console.log('User is authenticated');
}

setToken(token)

Stores a JWT token in localStorage.

// Typically done after login
setToken(response.token);

clearToken()

Removes the JWT token from localStorage.

clearToken();

isAuthenticated()

Checks if user is currently authenticated.

if (isAuthenticated()) {
    // Show app content
} else {
    // Redirect to login
}

getUserInfo()

Decodes and returns the JWT payload (user info).

const user = getUserInfo();
console.log(user.email);      // User's email
console.log(user.id);         // User ID
console.log(user.iat);        // Issued at
console.log(user.exp);        // Expiration time

requireAuth()

Redirects to login page if not authenticated. Use this in page initialization.

// At top of page script
requireAuth();

logout()

Clears token and redirects to login page.

// Called when user clicks logout button
logout();

fetchWithAuth(url, options)

Wrapper around fetch() that automatically adds Authorization header.

// GET request with auth
const response = await fetchWithAuth('/api/tenders');
const data = await response.json();

// POST request with auth
const response = await fetchWithAuth('/api/profile', {
    method: 'POST',
    body: JSON.stringify({ name: 'John' })
});

🎨 Navigation Component Features

The NavBar component automatically:

Injects a sticky navbar at the top of the page
Shows different content based on auth state
Displays user email + avatar for authenticated users
Highlights the current active page
Handles logout with token clearing
Mobile-responsive hamburger menu
Responsive user dropdown menu

  • Dashboard/dashboard.html
  • Tenders/tenders.html
  • Alerts/alerts.html
  • Profile/profile.html
  • Login/login.html
  • Sign Up/signup.html

🎨 Styling System

Color Variables

--primary: #1e40af;           /* Deep Blue */
--primary-dark: #1e3a8a;      /* Darker Blue */
--primary-light: #3b82f6;     /* Light Blue */
--accent: #f59e0b;            /* Orange */
--success: #10b981;           /* Green */
--danger: #ef4444;            /* Red */
--warning: #f59e0b;           /* Orange */
--info: #3b82f6;              /* Blue */

Component Classes

Cards

<div class="card">
    <div class="card-header">
        <h2 class="card-title">Tender Details</h2>
    </div>
    <div class="card-content">
        <!-- Content here -->
    </div>
    <div class="card-footer">
        <button class="btn btn-primary">Save</button>
    </div>
</div>

<!-- Variants: card-primary, card-success, card-warning, card-danger -->
<div class="card card-primary">...</div>

Buttons

<!-- Variants -->
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-outline">Outline</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-success">Success</button>

<!-- Sizes -->
<button class="btn btn-sm">Small</button>
<button class="btn btn-primary">Normal</button>
<button class="btn btn-lg">Large</button>

<!-- Full width -->
<button class="btn btn-primary btn-block">Full Width</button>

<!-- With icon -->
<button class="btn btn-primary btn-icon">
    <svg>...</svg>
    Action
</button>

Badges & Tags

<!-- Badges (status indicators) -->
<span class="badge badge-primary">Active</span>
<span class="badge badge-success">Approved</span>
<span class="badge badge-warning">Pending</span>
<span class="badge badge-danger">Rejected</span>

<!-- Tags (with optional close button) -->
<div class="tag">Python <span class="tag-close">×</span></div>

Alerts & Notifications

<!-- Success alert -->
<div class="alert alert-success">
    <div class="alert-icon"></div>
    <div class="alert-content">
        <div class="alert-title">Success!</div>
        <div class="alert-message">Your profile has been updated.</div>
    </div>
    <button class="alert-close">×</button>
</div>

<!-- Error alert -->
<div class="alert alert-error">
    <div class="alert-icon">!</div>
    <div class="alert-content">
        <div class="alert-title">Error</div>
        <div class="alert-message">Something went wrong. Please try again.</div>
    </div>
</div>

Tables

<div class="table-wrapper">
    <table>
        <thead>
            <tr>
                <th>Tender ID</th>
                <th>Title</th>
                <th>Status</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>TR-001</td>
                <td>Ministry Website Redesign</td>
                <td><span class="badge badge-success">Open</span></td>
                <td>
                    <div class="table-actions">
                        <button class="table-action-btn" title="View">👁️</button>
                        <button class="table-action-btn" title="Edit">✏️</button>
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
</div>

Forms

<form>
    <!-- Single field -->
    <div class="form-group">
        <label for="email" class="label-required">Email</label>
        <input type="email" id="email" name="email" placeholder="user@example.com" required>
        <div class="form-hint">We'll never share your email.</div>
    </div>

    <!-- Text area -->
    <div class="form-group">
        <label for="bio">Bio</label>
        <textarea id="bio" name="bio" placeholder="Tell us about yourself..."></textarea>
    </div>

    <!-- Select dropdown -->
    <div class="form-group">
        <label for="sector">Sector</label>
        <select id="sector" name="sector">
            <option value="">Select a sector...</option>
            <option value="it">IT & Software</option>
            <option value="construction">Construction</option>
            <option value="consulting">Consulting</option>
        </select>
    </div>

    <!-- Two-column layout -->
    <div class="form-row form-row-2">
        <div class="form-group">
            <label for="first_name">First Name</label>
            <input type="text" id="first_name" name="first_name">
        </div>
        <div class="form-group">
            <label for="last_name">Last Name</label>
            <input type="text" id="last_name" name="last_name">
        </div>
    </div>

    <!-- Error state -->
    <div class="form-group">
        <label for="password">Password</label>
        <input type="password" id="password" name="password" class="error">
        <div class="form-error">Password must be at least 8 characters.</div>
    </div>

    <!-- Form actions -->
    <button type="submit" class="btn btn-primary btn-block">Save Profile</button>
</form>

Grids & Layouts

<!-- Responsive 2-column grid -->
<div class="grid grid-2">
    <div class="card">Column 1</div>
    <div class="card">Column 2</div>
    <div class="card">Column 3 (wraps to new row)</div>
</div>

<!-- Fixed 3-column grid -->
<div class="grid grid-cols-3">
    <div class="stat-card">Stat 1</div>
    <div class="stat-card">Stat 2</div>
    <div class="stat-card">Stat 3</div>
</div>

<!-- Dashboard layout with sidebar -->
<div class="app-layout">
    <aside class="app-sidebar">
        <a href="/dashboard.html" class="sidebar-item active">Dashboard</a>
        <a href="/tenders.html" class="sidebar-item">All Tenders</a>
        <a href="/alerts.html" class="sidebar-item">My Alerts</a>
    </aside>
    <div class="app-content">
        <!-- Main content -->
    </div>
</div>

Loading States

<!-- Spinner -->
<div class="spinner"></div>
<div class="spinner spinner-sm"></div>
<div class="spinner spinner-lg"></div>

<!-- Loading message -->
<div class="loading">
    <div class="spinner"></div>
    Loading tenders...
</div>

<!-- Skeleton loading -->
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-text-sm"></div>
<div class="skeleton skeleton-text skeleton-text-lg"></div>

Empty States

<div class="empty-state">
    <div class="empty-state-icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
            <circle cx="12" cy="7" r="4"/>
        </svg>
    </div>
    <h3 class="empty-state-title">No tenders yet</h3>
    <p class="empty-state-desc">Create your first alert to start receiving tender matches.</p>
    <div class="empty-state-actions">
        <button class="btn btn-primary">Create Alert</button>
        <button class="btn btn-secondary">Learn More</button>
    </div>
</div>

📱 Responsive Design

All components are fully responsive with mobile-first design:

  • Desktop: Full navigation with all menu items visible
  • Tablet (768px): Optimized spacing and layouts
  • Mobile (480px): Hamburger menu, single-column layouts, optimized touch targets

Mobile Navigation

On mobile, the navbar automatically switches to a hamburger menu that can be toggled to show/hide navigation items.

Form Inputs on Mobile

All inputs use font-size: 1rem on mobile to prevent iOS auto-zoom.


🔗 Integration with Existing Pages

Landing Page (index.html) - No Changes Needed

The landing page uses styles.css and works independently. No auth required.

Login Page (login.html)

// On successful login, store token:
setToken(response.token);
// Then redirect:
window.location.href = '/dashboard.html';

Sign Up Page (signup.html)

// After successful registration:
setToken(response.token);
// Then redirect:
window.location.href = '/dashboard.html';

Protected Pages (dashboard.html, profile.html, alerts.html)

All must:

  1. Load auth.js first
  2. Load app.css for styling
  3. Load navigation and footer components
  4. Call requireAuth() to protect the page
  5. Use fetchWithAuth() for API calls

🛠️ Utility Classes

Spacing

<!-- Margin top -->
<div class="mt-1 mt-2 mt-3 mt-4 mt-6 mt-8">...</div>

<!-- Margin bottom -->
<div class="mb-1 mb-2 mb-3 mb-4 mb-6 mb-8">...</div>

<!-- Padding -->
<div class="p-1 p-2 p-3 p-4 p-6">...</div>

Text

<div class="text-center">Centered text</div>
<div class="text-right">Right-aligned text</div>
<div class="text-primary">Blue text</div>
<div class="text-success">Green text</div>
<div class="truncate">Text that truncates...</div>
<div class="line-clamp-2">Text limited to 2 lines...</div>

Display

<div class="hidden">Hidden element</div>
<div class="visible">Visible element</div>
<div class="opacity-50">50% opacity</div>
<div class="opacity-75">75% opacity</div>

📋 Checklist for Page Implementation

  • Add <script src="/auth.js"></script> to <head>
  • Add <link rel="stylesheet" href="/app.css"> to <head>
  • Add navigation component script before </body>
  • Add footer component script before </body>
  • Call requireAuth() in page script (for protected pages)
  • Wrap content in <main class="app-container">
  • Use fetchWithAuth() for all API calls
  • Test mobile responsiveness
  • Test logout functionality
  • Verify navigation highlights correct active page

🐛 Troubleshooting

Navigation not showing?

  • Check that auth.js is loaded before nav.js
  • Verify nav.js exists at /components/nav.js
  • Check browser console for errors

Styles not applying?

  • Ensure app.css is loaded after landing styles.css
  • Clear browser cache
  • Check file permissions on server

Auth checks not working?

  • Verify auth.js is loaded first
  • Check localStorage for tenderradar_token
  • Look for JS errors in browser console

API calls failing?

  • Verify JWT token is valid and not expired
  • Use fetchWithAuth() instead of plain fetch()
  • Check server CORS settings if cross-domain

📚 Additional Resources

  • Brand Colors: Primary #1e40af, Accent #f59e0b
  • Font Family: Inter (from Google Fonts)
  • Layout Width: Max 1400px container
  • Shadow System: sm, md, lg, xl variants
  • Responsive Breakpoints: 768px (tablet), 480px (mobile)

Created: 2026-02-14
Last Updated: 2026-02-14