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
|