- 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
622 lines
16 KiB
Markdown
622 lines
16 KiB
Markdown
# 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.):
|
||
|
||
```html
|
||
<!-- Authentication utilities (must be loaded first) -->
|
||
<script src="/auth.js"></script>
|
||
```
|
||
|
||
### 2. Add Navigation & Footer Components
|
||
|
||
Add these before the closing `</body>` tag on every app page:
|
||
|
||
```html
|
||
<!-- 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:
|
||
|
||
```html
|
||
<!-- 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:
|
||
|
||
```javascript
|
||
// Require authentication on this page
|
||
requireAuth();
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 Complete Example: dashboard.html
|
||
|
||
```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.
|
||
|
||
```javascript
|
||
const token = getToken();
|
||
if (token) {
|
||
console.log('User is authenticated');
|
||
}
|
||
```
|
||
|
||
### `setToken(token)`
|
||
Stores a JWT token in localStorage.
|
||
|
||
```javascript
|
||
// Typically done after login
|
||
setToken(response.token);
|
||
```
|
||
|
||
### `clearToken()`
|
||
Removes the JWT token from localStorage.
|
||
|
||
```javascript
|
||
clearToken();
|
||
```
|
||
|
||
### `isAuthenticated()`
|
||
Checks if user is currently authenticated.
|
||
|
||
```javascript
|
||
if (isAuthenticated()) {
|
||
// Show app content
|
||
} else {
|
||
// Redirect to login
|
||
}
|
||
```
|
||
|
||
### `getUserInfo()`
|
||
Decodes and returns the JWT payload (user info).
|
||
|
||
```javascript
|
||
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.
|
||
|
||
```javascript
|
||
// At top of page script
|
||
requireAuth();
|
||
```
|
||
|
||
### `logout()`
|
||
Clears token and redirects to login page.
|
||
|
||
```javascript
|
||
// Called when user clicks logout button
|
||
logout();
|
||
```
|
||
|
||
### `fetchWithAuth(url, options)`
|
||
Wrapper around fetch() that automatically adds Authorization header.
|
||
|
||
```javascript
|
||
// 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
|
||
|
||
### Navigation Links (Authenticated)
|
||
|
||
- **Dashboard** → `/dashboard.html`
|
||
- **Tenders** → `/tenders.html`
|
||
- **Alerts** → `/alerts.html`
|
||
- **Profile** → `/profile.html`
|
||
|
||
### Navigation Links (Unauthenticated)
|
||
|
||
- **Login** → `/login.html`
|
||
- **Sign Up** → `/signup.html`
|
||
|
||
---
|
||
|
||
## 🎨 Styling System
|
||
|
||
### Color Variables
|
||
|
||
```css
|
||
--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
|
||
```html
|
||
<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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<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
|
||
```html
|
||
<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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<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)
|
||
```javascript
|
||
// On successful login, store token:
|
||
setToken(response.token);
|
||
// Then redirect:
|
||
window.location.href = '/dashboard.html';
|
||
```
|
||
|
||
### Sign Up Page (signup.html)
|
||
```javascript
|
||
// 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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<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
|
||
```html
|
||
<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
|