Building a Client-Side Authentication & Progress Tracking System

A comprehensive guide to creating a fully frontend user authentication and progress-tracking system using JavaScript, jQuery, and localStorage.

Introduction

In this guide, you'll learn how to build a fully client-side system that allows users to authenticate, track their progress through educational content, earn rewards, and persist their dataβ€”all without requiring a backend server.

What You'll Build

A complete frontend authentication and progress tracking system that can be used for educational content, tutorials, or any sequence of tasks requiring user progression.

Why It's Useful

This approach allows you to add user-specific features to static websites without the need for server-side code, databases, or user management systems.

πŸ” Use Case Example

A 50-page educational course titled "Future Civilization with selfAI" where users progress through content, earn crypto points (CTC), and save their progress locally.

Project Structure

Let's start by understanding the folder structure for our project. This will help you organize your files and understand how the different components interact.

Folder Structure

  • project-root/
    • assets/
      • css/
        • style.css
      • js/
        • auth.js
        • database.js
        • progress.js
        • rewards.js
        • dashboard.js
        • data-management.js
        • utils.js
      • img/
        • logo.png
        • icons/
    • data/
      • schema.json
      • course-content.json
    • docs/
      • doc1.html
      • doc2.html
      • ...
      • doc50.html
    • backups/
      • [userid].json
    • index.html
    • dashboard.html
    • README.md

File Purposes

  • auth.js: Handles user identification and authentication
  • database.js: Manages localStorage operations and schema
  • progress.js: Tracks and updates user progress through the course
  • rewards.js: Manages the reward system and CTC earnings
  • dashboard.js: Controls the UI for displaying progress and earnings
  • data-management.js: Handles exporting and importing user data
  • utils.js: Common utility functions used across the application
  • schema.json: Initial database structure for localStorage
  • course-content.json: Content metadata for the educational course
  • doc1.html to doc50.html: The educational content pages

Prerequisites

Before we start building our system, let's make sure you have all the necessary tools and knowledge to follow along.

Required Tools

  • A modern code editor (VS Code, Sublime Text, etc.)
  • A modern web browser (Chrome, Firefox, Edge, Safari)
  • Basic understanding of HTML, CSS, and JavaScript
  • Local web server (optional but recommended)

Libraries & Resources

  • jQuery (for DOM manipulation and AJAX)
  • FingerprintJS (for device fingerprinting)
  • FontAwesome (for icons)
  • Knowledge of localStorage API

Local Development Server Options

While you can open HTML files directly in a browser, some features might require a local server. Here are some simple options:

Using Python

python -m http.server

Using Node.js

npx serve

⚠️ Important Note

This system relies on browser localStorage, which has limitations:

  • Generally limited to ~5MB of storage per domain
  • Data is specific to a browser on a specific device
  • Can be cleared by users when clearing browser data

Getting Started

Let's begin by setting up the project structure and initializing our files.

Step 1: Create the Basic Project Structure

First, let's create all the required directories and files according to our project structure:

Terminal
mkdir -p project/assets/{css,js,img}
mkdir -p project/data
mkdir -p project/docs
mkdir -p project/backups
touch project/index.html
touch project/dashboard.html
touch project/assets/css/style.css
touch project/assets/js/{auth.js,database.js,progress.js,rewards.js,dashboard.js,data-management.js,utils.js}
touch project/data/{schema.json,course-content.json}

Step 2: Set Up the HTML Template

Next, let's create a basic HTML template for our main index page:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Future Civilization with selfAI</title>
    <link rel="stylesheet" href="assets/css/style.css">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@fingerprintjs/[email protected]/dist/fp.min.js"></script>
</head>
<body>
    <header>
        <div class="container">
            <h1>Future Civilization with selfAI</h1>
            <div id="user-info">
                <span id="user-id">Not logged in</span>
                <span id="progress-indicator">0/50</span>
                <span id="ctc-earnings">0 CTC</span>
            </div>
        </div>
    </header>

    <main class="container">
        <section id="welcome-section">
            <h2>Welcome to the Course</h2>
            <p>Start your journey through our 50-page course on Future Civilization with selfAI.</p>
            <a href="docs/doc1.html" class="btn" id="start-course">Start Course</a>
        </section>
    </main>

    <footer>
        <div class="container">
            <p>© 2023 Future Civilization with selfAI</p>
        </div>
    </footer>

    <script src="assets/js/utils.js"></script>
    <script src="assets/js/database.js"></script>
    <script src="assets/js/auth.js"></script>
    <script src="assets/js/progress.js"></script>
    <script src="assets/js/rewards.js"></script>
    <script>
        $(document).ready(function() {
            // Initialize the application
            initializeDatabase().then(() => {
                authenticateUser().then(userId => {
                    updateUserInterface();
                });
            });
        });
    </script>
</body>
</html>

Step 3: Basic CSS Styling

Let's add some basic CSS to make our application look good:

assets/css/style.css
:root {
    --primary-color: #4a6da7;
    --secondary-color: #32cd32;
    --bg-color: #f9fafb;
    --text-color: #333;
    --header-bg: #2c3e50;
    --header-text: #ffffff;
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Arial', sans-serif;
    line-height: 1.6;
    color: var(--text-color);
    background-color: var(--bg-color);
}

.container {
    width: 90%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 15px;
}

header {
    background-color: var(--header-bg);
    color: var(--header-text);
    padding: 1rem 0;
    position: sticky;
    top: 0;
    z-index: 100;
}

header .container {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

header h1 {
    font-size: 1.5rem;
}

#user-info {
    display: flex;
    gap: 1rem;
    align-items: center;
}

main {
    padding: 2rem 0;
}

.btn {
    display: inline-block;
    background-color: var(--primary-color);
    color: white;
    padding: 0.5rem 1rem;
    text-decoration: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
}

.btn:hover {
    background-color: #3a5580;
}

footer {
    text-align: center;
    padding: 1rem 0;
    background-color: #eee;
    margin-top: 2rem;
}

/* Progress styling */
#progress-indicator {
    background-color: rgba(255, 255, 255, 0.2);
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
}

/* CTC earnings styling */
#ctc-earnings {
    background-color: var(--secondary-color);
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    color: white;
}

Step 4: Sample Document Page

Let's create a template for our first document page that will be part of our tutorial sequence:

docs/doc1.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chapter 1: Introduction to selfAI | Future Civilization</title>
    <link rel="stylesheet" href="../assets/css/style.css">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
</head>
<body>
    <header>
        <div class="container">
            <h1>Future Civilization with selfAI</h1>
            <div id="user-info">
                <span id="user-id">Not logged in</span>
                <span id="progress-indicator">0/50</span>
                <span id="ctc-earnings">0 CTC</span>
                <a href="../dashboard.html" class="btn btn-small">Dashboard</a>
            </div>
        </div>
    </header>

    <main class="container">
        <article class="document-content">
            <h2>Chapter 1: Introduction to selfAI</h2>
            
            <p>Welcome to the first chapter of our course on Future Civilization with selfAI. In this lesson, you'll learn the fundamental concepts of selfAI and its potential impact on civilization.</p>
            
            <!-- Content would go here -->
            
            <section class="task-completion" id="chapter-tasks">
                <h3>Complete these tasks to continue</h3>
                <form id="task-form">
                    <div class="task-item">
                        <input type="checkbox" id="task1" name="task1">
                        <label for="task1">Read the entire chapter</label>
                    </div>
                    <div class="task-item">
                        <input type="checkbox" id="task2" name="task2">
                        <label for="task2">Watch the introduction video</label>
                    </div>
                    <div class="task-item">
                        <input type="checkbox" id="task3" name="task3">
                        <label for="task3">Share this chapter on social media</label>
                    </div>
                    <button type="submit" class="btn" id="complete-chapter">Mark as Complete</button>
                </form>
            </section>
            
            <div class="navigation-links">
                <a href="../index.html" class="btn btn-secondary">Back to Home</a>
                <a href="doc2.html" class="btn" id="next-chapter" style="display: none;">Next Chapter</a>
            </div>
        </article>
    </main>

    <footer>
        <div class="container">
            <p>© 2023 Future Civilization with selfAI</p>
        </div>
    </footer>

    <script src="../assets/js/utils.js"></script>
    <script src="../assets/js/database.js"></script>
    <script src="../assets/js/auth.js"></script>
    <script src="../assets/js/progress.js"></script>
    <script src="../assets/js/rewards.js"></script>
    <script>
        $(document).ready(function() {
            // Initialize the application
            initializeDatabase().then(() => {
                authenticateUser().then(userId => {
                    updateUserInterface();
                    checkChapterProgress(1);
                });
            });
            
            // Handle task form submission
            $('#task-form').on('submit', function(e) {
                e.preventDefault();
                const allTasksCompleted = $('.task-item input[type="checkbox"]').toArray()
                    .every(checkbox => checkbox.checked);
                
                if (allTasksCompleted) {
                    completeChapter(1).then(() => {
                        $('#next-chapter').show();
                        awardCTC(0.5);
                        updateUserInterface();
                    });
                } else {
                    alert('Please complete all tasks before proceeding.');
                }
            });
        });
    </script>
</body>
</html>

Creating a Local Database

Now, let's set up our local storage database system with a proper schema.

Step 1: Create the Database Schema

First, we'll define a schema for our local database in JSON format:

data/schema.json
{
  "version": "1.0.0",
  "users": {
    "template": {
      "userId": "",
      "fingerprint": "",
      "createdAt": "",
      "lastLogin": "",
      "progress": {
        "currentChapter": 0,
        "completedChapters": [],
        "lastActivity": ""
      },
      "rewards": {
        "totalCTC": 0,
        "transactions": []
      },
      "tasks": {
        "socialShares": 0,
        "youtubeComments": 0,
        "completedTasks": []
      }
    }
  },
  "content": {
    "totalChapters": 50,
    "chapters": {}
  },
  "system": {
    "lastBackup": "",
    "initialized": false
  }
}

Step 2: Create the Database Manager

Next, let's create our database.js file that will handle all localStorage operations:

assets/js/database.js
/**
 * Database Management System using localStorage
 * Handles initialization, CRUD operations, and schema management
 */

// Database constants
const DB_NAME = 'selfAI_course_db';
const SCHEMA_URL = 'data/schema.json';

/**
 * Initialize the database with the schema
 * @returns {Promise} Resolves when database is initialized
 */
function initializeDatabase() {
    return new Promise((resolve, reject) => {
        // Check if the database already exists
        const existingDb = localStorage.getItem(DB_NAME);
        
        if (existingDb) {
            const db = JSON.parse(existingDb);
            // Check if we need to update the schema
            if (db.system && db.system.initialized) {
                console.log('Database already initialized');
                resolve(db);
                return;
            }
        }
        
        // Fetch the schema and initialize the database
        fetch(SCHEMA_URL)
            .then(response => response.json())
            .then(schema => {
                // Initialize the schema
                schema.system.initialized = true;
                schema.system.lastBackup = new Date().toISOString();
                
                // Save to localStorage
                localStorage.setItem(DB_NAME, JSON.stringify(schema));
                console.log('Database initialized with schema');
                resolve(schema);
            })
            .catch(error => {
                console.error('Failed to initialize database:', error);
                reject(error);
            });
    });
}

/**
 * Get the entire database or a specific part
 * @param {string} path - Dot notation path to a specific part of the database (optional)
 * @returns {object} The requested database section
 */
function getDatabase(path = null) {
    const db = JSON.parse(localStorage.getItem(DB_NAME) || '{}');
    
    if (!path) return db;
    
    // Navigate through the object using the path
    return path.split('.').reduce((obj, key) => {
        return obj && obj[key] !== undefined ? obj[key] : null;
    }, db);
}

/**
 * Update a specific part of the database
 * @param {string} path - Dot notation path to update
 * @param {any} value - New value to set
 * @returns {boolean} Success status
 */
function updateDatabase(path, value) {
    const db = getDatabase();
    
    if (!db || typeof db !== 'object') {
        console.error('Database not initialized');
        return false;
    }
    
    // Split the path and traverse the object
    const pathParts = path.split('.');
    const lastKey = pathParts.pop();
    const target = pathParts.reduce((obj, key) => {
        if (obj[key] === undefined) obj[key] = {};
        return obj[key];
    }, db);
    
    // Update the value
    target[lastKey] = value;
    
    // Save back to localStorage
    localStorage.setItem(DB_NAME, JSON.stringify(db));
    return true;
}

/**
 * Create a new user in the database
 * @param {string} userId - Unique identifier for the user
 * @param {string} fingerprint - Browser fingerprint
 * @returns {object} Newly created user object
 */
function createUser(userId, fingerprint) {
    const db = getDatabase();
    const timestamp = new Date().toISOString();
    
    // Clone the user template
    const newUser = JSON.parse(JSON.stringify(db.users.template));
    
    // Fill in user details
    newUser.userId = userId;
    newUser.fingerprint = fingerprint;
    newUser.createdAt = timestamp;
    newUser.lastLogin = timestamp;
    
    // Add to the database
    db.users[userId] = newUser;
    localStorage.setItem(DB_NAME, JSON.stringify(db));
    
    return newUser;
}

/**
 * Get a user from the database
 * @param {string} userId - User ID to find
 * @returns {object|null} User object or null if not found
 */
function getUser(userId) {
    return getDatabase(`users.${userId}`);
}

/**
 * Check if a database backup is needed
 * @param {number} days - Number of days between backups
 * @returns {boolean} True if backup is needed
 */
function isBackupNeeded(days = 3) {
    const db = getDatabase();
    const lastBackup = new Date(db.system.lastBackup);
    const now = new Date();
    
    // Calculate days difference
    const diffTime = Math.abs(now - lastBackup);
    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
    
    return diffDays >= days;
}

/**
 * Update the last backup timestamp
 */
function updateBackupTimestamp() {
    updateDatabase('system.lastBackup', new Date().toISOString());
}

Step 3: Course Content JSON

Let's create a JSON file to store metadata about our course chapters:

data/course-content.json
{
  "title": "Future Civilization with selfAI",
  "description": "A 50-chapter course exploring the future of civilization with selfAI",
  "chapters": [
    {
      "id": 1,
      "title": "Introduction to selfAI",
      "file": "doc1.html",
      "requiredTasks": 3,
      "ctcReward": 0.5,
      "socialTask": true
    },
    {
      "id": 2,
      "title": "Historical Context of AI Development",
      "file": "doc2.html",
      "requiredTasks": 2,
      "ctcReward": 0.5,
      "socialTask": false
    },
    {
      "id": 3,
      "title": "Principles of Self-Directed AI",
      "file": "doc3.html",
      "requiredTasks": 3,
      "ctcReward": 0.5,
      "socialTask": true
    },
    
    // Additional chapters would be defined here
    
    {
      "id": 50,
      "title": "Building the Future with selfAI",
      "file": "doc50.html",
      "requiredTasks": 4,
      "ctcReward": 2.0,
      "socialTask": true
    }
  ]
}

πŸ’‘ Tip

You can expand the course-content.json to include more detailed information about each chapter, such as:

  • Detailed descriptions
  • Expected completion time
  • Task details
  • Related resources

User Authentication with FingerprintJS

Let's implement a client-side authentication system using browser fingerprinting to identify unique users without requiring logins.

Step 1: Create the Authentication Module

First, let's create our auth.js file to handle user identification:

assets/js/auth.js
/**
 * User Authentication Module
 * Uses FingerprintJS to generate unique user IDs and manage user sessions
 */

// Cache for the current user
let currentUser = null;

/**
 * Generate a short unique user ID from fingerprint
 * @param {string} fingerprint - The browser fingerprint
 * @returns {string} A shortened unique ID
 */
function generateUserId(fingerprint) {
    // Create a short hash from the fingerprint
    let hash = 0;
    for (let i = 0; i < fingerprint.length; i++) {
        const char = fingerprint.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash;
    }
    
    // Convert to base 36 and take the first 8 characters
    return Math.abs(hash).toString(36).substring(0, 8);
}

/**
 * Authenticate the current user using FingerprintJS
 * @returns {Promise} Promise resolving to the user ID
 */
function authenticateUser() {
    return new Promise((resolve, reject) => {
        // If we already have a user cached, return it
        if (currentUser) {
            resolve(currentUser.userId);
            return;
        }
        
        // Initialize FingerprintJS
        FingerprintJS.load()
            .then(fp => fp.get())
            .then(result => {
                const fingerprint = result.visitorId;
                const userId = generateUserId(fingerprint);
                
                // Check if user exists in the database
                let user = getUser(userId);
                
                if (!user) {
                    // Create new user if not found
                    user = createUser(userId, fingerprint);
                } else {
                    // Update last login time
                    updateDatabase(`users.${userId}.lastLogin`, new Date().toISOString());
                }
                
                // Cache the current user
                currentUser = user;
                
                console.log(`User authenticated: ${userId}`);
                resolve(userId);
            })
            .catch(error => {
                console.error('Authentication failed:', error);
                reject(error);
            });
    });
}

/**
 * Get the current authenticated user
 * @returns {object|null} The current user object or null if not authenticated
 */
function getCurrentUser() {
    return currentUser;
}

/**
 * Update the current user data in the cache and database
 * @param {object} userData - Updated user data
 */
function updateCurrentUser(userData) {
    if (!currentUser || !userData) return;
    
    // Update the cache
    currentUser = { ...currentUser, ...userData };
    
    // Update in the database
    updateDatabase(`users.${currentUser.userId}`, currentUser);
}

/**
 * Update the user interface with current user info
 */
function updateUserInterface() {
    const user = getCurrentUser();
    
    if (!user) return;
    
    // Update user ID display (show only first 4 chars)
    $('#user-id').text(`User: ${user.userId.substring(0, 4)}...`);
    
    // Update progress display
    const completedChapters = user.progress.completedChapters.length;
    const totalChapters = getDatabase('content.totalChapters') || 50;
    $('#progress-indicator').text(`Progress: ${completedChapters}/${totalChapters}`);
    
    // Update CTC earnings display
    $('#ctc-earnings').text(`${user.rewards.totalCTC.toFixed(1)} CTC`);
}

Step 2: Understanding FingerprintJS

FingerprintJS creates a unique identifier for each browser based on various characteristics, allowing us to recognize returning users without requiring them to log in.

How Browser Fingerprinting Works

FingerprintJS analyzes multiple browser attributes to create a unique fingerprint:

  • User agent string
  • Browser plugins
  • Screen resolution
  • Installed fonts
  • Timezone
  • Canvas fingerprinting
  • WebGL fingerprinting
  • Hardware information

⚠️ Privacy Considerations

When using browser fingerprinting:

  • Include a privacy policy explaining what data is collected
  • Only store the generated ID, not the full fingerprint details
  • Give users a way to clear their data
  • Be aware that fingerprints can change (browser updates, settings changes)

Step 3: Testing the Authentication

Here's a simple code snippet to test if our authentication system is working correctly:

Test Authentication
// Add this to your index.html for testing
$(document).ready(function() {
    initializeDatabase().then(() => {
        console.log('Database initialized');
        
        authenticateUser().then(userId => {
            console.log('Authenticated user:', userId);
            
            const user = getCurrentUser();
            console.log('User details:', user);
            
            updateUserInterface();
        }).catch(error => {
            console.error('Authentication error:', error);
        });
    });
});

Progress Tracking Logic

Now, let's implement a progress tracking system that allows users to unlock content as they complete previous sections.

Step 1: Create the Progress Tracking Module

First, let's create our progress.js file:

assets/js/progress.js
/**
 * Progress Tracking Module
 * Manages user progress through the course and unlocking content
 */

/**
 * Check if a chapter is unlocked for the current user
 * @param {number} chapterId - The chapter to check
 * @returns {boolean} True if the chapter is unlocked
 */
function isChapterUnlocked(chapterId) {
    const user = getCurrentUser();
    
    if (!user) return false;
    
    // Chapter 1 is always unlocked
    if (chapterId === 1) return true;
    
    // Check if the previous chapter is completed
    return user.progress.completedChapters.includes(chapterId - 1);
}

/**
 * Mark a chapter as completed for the current user
 * @param {number} chapterId - The chapter to mark as completed
 * @returns {Promise} Promise resolving to success status
 */
function completeChapter(chapterId) {
    return new Promise((resolve, reject) => {
        const user = getCurrentUser();
        
        if (!user) {
            reject(new Error('User not authenticated'));
            return;
        }
        
        // Check if chapter is already completed
        if (user.progress.completedChapters.includes(chapterId)) {
            resolve(true); // Already completed
            return;
        }
        
        // Add to completed chapters if not already there
        const completedChapters = [...user.progress.completedChapters];
        if (!completedChapters.includes(chapterId)) {
            completedChapters.push(chapterId);
        }
        
        // Sort chapters in numerical order
        completedChapters.sort((a, b) => a - b);
        
        // Update user progress
        const updatedProgress = {
            currentChapter: Math.max(user.progress.currentChapter, chapterId),
            completedChapters: completedChapters,
            lastActivity: new Date().toISOString()
        };
        
        // Update the database
        updateDatabase(`users.${user.userId}.progress`, updatedProgress);
        
        // Update the cached user
        user.progress = updatedProgress;
        
        console.log(`Chapter ${chapterId} marked as completed`);
        resolve(true);
    });
}

/**
 * Check the progress status for a specific chapter
 * @param {number} chapterId - The chapter to check
 * @returns {object} Status object with unlocked and completed status
 */
function checkChapterProgress(chapterId) {
    const user = getCurrentUser();
    
    if (!user) return { unlocked: false, completed: false };
    
    const isUnlocked = isChapterUnlocked(chapterId);
    const isCompleted = user.progress.completedChapters.includes(chapterId);
    
    // Update UI based on progress
    if (isCompleted) {
        // Show completed UI
        $('#chapter-tasks').addClass('completed');
        $('#complete-chapter').prop('disabled', true).text('Completed');
        $('#next-chapter').show();
    } else if (!isUnlocked && chapterId > 1) {
        // Redirect to the homepage if trying to access locked content
        alert('This chapter is locked. Please complete the previous chapters first.');
        window.location.href = '../index.html';
    }
    
    return { unlocked: isUnlocked, completed: isCompleted };
}

/**
 * Get the next unlocked chapter for the current user
 * @returns {number} The ID of the next unlocked chapter
 */
function getNextUnlockedChapter() {
    const user = getCurrentUser();
    
    if (!user) return 1; // Default to first chapter
    
    // Find the highest completed chapter
    const completedChapters = user.progress.completedChapters;
    
    if (completedChapters.length === 0) return 1;
    
    // Get the highest completed chapter and return the next one
    const highestCompleted = Math.max(...completedChapters);
    return highestCompleted + 1;
}

/**
 * Redirect user to their next unlocked chapter
 */
function goToNextUnlockedChapter() {
    const nextChapter = getNextUnlockedChapter();
    const totalChapters = getDatabase('content.totalChapters') || 50;
    
    if (nextChapter <= totalChapters) {
        window.location.href = `docs/doc${nextChapter}.html`;
    } else {
        // All chapters completed
        alert('Congratulations! You have completed all chapters.');
    }
}

Step 2: Update Document Pages to Check Progress

Let's add logic to our document pages to check if a user has access to that page:

Document Page Script Section
$(document).ready(function() {
    // Current chapter ID (extracted from the page)
    const currentChapterId = 2; // Example: page doc2.html has ID 2
    
    // Initialize the application
    initializeDatabase().then(() => {
        authenticateUser().then(userId => {
            updateUserInterface();
            
            // Check if this chapter is unlocked
            const progress = checkChapterProgress(currentChapterId);
            
            if (!progress.unlocked) {
                // Redirect to the last unlocked chapter
                goToNextUnlockedChapter();
            }
            
            // If already completed, show the next button
            if (progress.completed) {
                $('#next-chapter').show();
            }
        });
    });
    
    // Handle task form submission
    $('#task-form').on('submit', function(e) {
        e.preventDefault();
        
        // Check if all tasks are completed
        const allTasksCompleted = $('.task-item input[type="checkbox"]').toArray()
            .every(checkbox => checkbox.checked);
        
        if (allTasksCompleted) {
            // Mark chapter as completed
            completeChapter(currentChapterId).then(() => {
                // Show the next button
                $('#next-chapter').show();
                
                // Award CTC tokens
                awardCTC(0.5);
                
                // Update UI
                updateUserInterface();
                
                // Add completion class
                $('#chapter-tasks').addClass('completed');
                $('#complete-chapter').prop('disabled', true).text('Completed');
            });
        } else {
            alert('Please complete all tasks before proceeding.');
        }
    });
});

Step 3: Dynamic Content Access Control

Let's add a function to fetch chapter information and control access:

Utility Function for Chapter Access

			/**
 * Get chapter information and check access
 * @param {number} chapterId - The chapter ID to check
 * @returns {Promise object} Promise resolving to chapter info and access status
 */
function getChapterInfo(chapterId) {
    return new Promise((resolve, reject) => {
        // Fetch course content data
        fetch('../data/course-content.json')
            .then(response => response.json())
            .then(courseData => {
                // Find the chapter
                const chapter = courseData.chapters.find(ch => ch.id === chapterId);
                
                if (!chapter) {
                    reject(new Error(`Chapter ${chapterId} not found`));
                    return;
                }
                
                // Check access permissions
                const user = getCurrentUser();
                const hasAccess = user && isChapterUnlocked(chapterId);
                
                resolve({
                    ...chapter,
                    hasAccess,
                    isCompleted: user?.progress.completedChapters.includes(chapterId) || false
                });
            })
            .catch(error => {
                console.error('Failed to get chapter info:', error);
                reject(error);
            });
    });
}

πŸ’‘ Pro Tip: Persistence Across Sessions

The progress tracking system persists across browser sessions since it's stored in localStorage. This provides a seamless experience where users can close their browser and return later to the same position in the course.

Earning Rewards

Let's implement a system for awarding CTC (Crypto Token Coins) as users complete chapters and perform social tasks.

Step 1: Create the Rewards Module

Let's create our rewards.js file to handle the token economy:

assets/js/rewards.js
/**
 * Rewards Management Module
 * Handles CTC token awards and transaction tracking
 */

/**
 * Award CTC tokens to the current user
 * @param {number} amount - Amount of CTC to award
 * @param {string} reason - Reason for the award
 * @returns {boolean} Success status
 */
function awardCTC(amount, reason = 'Chapter completion') {
    const user = getCurrentUser();
    
    if (!user) {
        console.error('Cannot award CTC: User not authenticated');
        return false;
    }
    
    // Create a new transaction record
    const transaction = {
        id: generateTransactionId(),
        amount: amount,
        reason: reason,
        timestamp: new Date().toISOString()
    };
    
    // Update user's rewards
    const totalCTC = (user.rewards.totalCTC || 0) + amount;
    const transactions = [...user.rewards.transactions, transaction];
    
    // Update the database
    updateDatabase(`users.${user.userId}.rewards`, {
        totalCTC: totalCTC,
        transactions: transactions
    });
    
    // Update cached user
    user.rewards.totalCTC = totalCTC;
    user.rewards.transactions = transactions;
    
    console.log(`Awarded ${amount} CTC to user ${user.userId} for ${reason}`);
    
    // Update the UI
    updateUserInterface();
    
    return true;
}

/**
 * Award CTC for completing a social task
 * @param {string} taskType - Type of social task (youtube, twitter, etc.)
 * @param {string} proof - Proof of completion (link, screenshot, etc.)
 * @returns {boolean} Success status
 */
function awardSocialTaskCTC(taskType, proof) {
    const user = getCurrentUser();
    
    if (!user) return false;
    
    // Define reward amounts for different task types
    const rewards = {
        youtube: 1.0,
        twitter: 0.5,
        facebook: 0.5,
        linkedin: 0.7
    };
    
    // Get the reward amount for this task type
    const amount = rewards[taskType] || 0.5;
    
    // Create a task record
    const task = {
        id: generateTransactionId(),
        type: taskType,
        proof: proof,
        timestamp: new Date().toISOString()
    };
    
    // Update user's tasks
    const tasks = {
        ...user.tasks,
        completedTasks: [...user.tasks.completedTasks, task]
    };
    
    // Increment the specific task counter
    if (taskType === 'youtube') {
        tasks.youtubeComments = (user.tasks.youtubeComments || 0) + 1;
    } else {
        tasks.socialShares = (user.tasks.socialShares || 0) + 1;
    }
    
    // Update the database
    updateDatabase(`users.${user.userId}.tasks`, tasks);
    
    // Update cached user
    user.tasks = tasks;
    
    // Award CTC
    return awardCTC(amount, `Social task: ${taskType}`);
}

/**
 * Generate a unique transaction ID
 * @returns {string} Unique transaction ID
 */
function generateTransactionId() {
    return 'txn_' + Math.random().toString(36).substring(2, 10) + 
           '_' + new Date().getTime().toString(36);
}

/**
 * Get transaction history for the current user
 * @param {number} limit - Maximum number of transactions to return
 * @returns {Array} Transaction history
 */
function getTransactionHistory(limit = 10) {
    const user = getCurrentUser();
    
    if (!user || !user.rewards || !user.rewards.transactions) {
        return [];
    }
    
    // Return most recent transactions first
    return [...user.rewards.transactions]
        .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
        .slice(0, limit);
}

/**
 * Calculate total rewards by category
 * @returns {object} Rewards summary by category
 */
function getRewardsSummary() {
    const user = getCurrentUser();
    
    if (!user || !user.rewards || !user.rewards.transactions) {
        return { total: 0, categories: {} };
    }
    
    // Group transactions by reason and sum amounts
    const categories = user.rewards.transactions.reduce((acc, txn) => {
        const category = txn.reason.split(':')[0].trim();
        acc[category] = (acc[category] || 0) + txn.amount;
        return acc;
    }, {});
    
    return {
        total: user.rewards.totalCTC,
        categories: categories
    };
}

Step 2: Social Task Verification

Let's create a function to verify social tasks through a simple form:

Social Task Verification Form HTML
<div class="social-task-form bg-white p-6 rounded-lg border border-gray-200 mt-6">
    <h3 class="text-xl font-semibold mb-4">Complete a Social Task for Extra CTC</h3>
    
    <form id="social-task-form">
        <div class="mb-4">
            <label for="task-type" class="block mb-2 font-medium">Task Type</label>
            <select id="task-type" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                <option value="youtube">YouTube Comment (1.0 CTC)</option>
                <option value="twitter">Twitter Share (0.5 CTC)</option>
                <option value="facebook">Facebook Share (0.5 CTC)</option>
                <option value="linkedin">LinkedIn Share (0.7 CTC)</option>
            </select>
        </div>
        
        <div class="mb-4">
            <label for="task-proof" class="block mb-2 font-medium">Link to Your Post/Comment</label>
            <input type="url" id="task-proof" placeholder="https://..." required
                   class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
        </div>
        
        <button type="submit" class="btn bg-green-600 text-white px-4 py-2 rounded-md">Submit for Verification</button>
    </form>
    
    <div id="task-result" class="mt-4 hidden"></div>
</div>
Social Task JavaScript
// Handle social task submission
$('#social-task-form').on('submit', function(e) {
    e.preventDefault();
    
    const taskType = $('#task-type').val();
    const taskProof = $('#task-proof').val();
    
    // Basic validation
    if (!taskProof.startsWith('http')) {
        $('#task-result')
            .removeClass('hidden bg-green-100')
            .addClass('bg-red-100 p-3 rounded')
            .html('Please enter a valid URL as proof.');
        return;
    }
    
    // Award CTC for the social task
    const success = awardSocialTaskCTC(taskType, taskProof);
    
    if (success) {
        $('#task-result')
            .removeClass('hidden bg-red-100')
            .addClass('bg-green-100 p-3 rounded')
            .html(`Congratulations! You've earned CTC for your ${taskType} task.`);
        
        // Reset the form
        $('#task-proof').val('');
    } else {
        $('#task-result')
            .removeClass('hidden bg-green-100')
            .addClass('bg-red-100 p-3 rounded')
            .html('Failed to award CTC. Please try again later.');
    }
});

Reward System Guidelines

For a successful token economy in your educational content:

  • Consistent rewards: Set clear rewards for each task type
  • Progressive structure: Consider increasing rewards for later chapters
  • Bonus rewards: Offer extra rewards for completing milestones
  • Social incentives: Encourage sharing with higher rewards
  • Transparency: Always show users what they've earned and why

Displaying Earnings Dashboard

Let's build a dashboard to show users their progress, earned CTC, and transaction history.

Step 1: Create the Dashboard HTML

Let's create our dashboard.html file:

dashboard.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Your Progress Dashboard | Future Civilization with selfAI</title>
    <link rel="stylesheet" href="assets/css/style.css">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js"></script>
</head>
<body>
    <header>
        <div class="container">
            <h1>Future Civilization with selfAI</h1>
            <div id="user-info">
                <span id="user-id">Not logged in</span>
                <span id="progress-indicator">0/50</span>
                <span id="ctc-earnings">0 CTC</span>
                <a href="index.html" class="btn btn-small">Home</a>
            </div>
        </div>
    </header>

    <main>
        <div class="container">
            <h2 class="dashboard-title">Your Progress Dashboard</h2>
            
            <div class="dashboard-grid">
                <div class="dashboard-card">
                    <h3>Progress Summary</h3>
                    <div class="progress-container">
                        <div class="progress-bar-wrapper">
                            <div class="progress-bar-fill" id="progress-bar"></div>
                        </div>
                        <div class="progress-text" id="progress-text">0/50 chapters completed</div>
                    </div>
                    <div class="progress-actions">
                        <button id="continue-btn" class="btn">Continue Learning</button>
                    </div>
                </div>
                
                <div class="dashboard-card">
                    <h3>CTC Earnings</h3>
                    <div class="earnings-summary">
                        <div class="total-earnings" id="total-ctc">0 CTC</div>
                        <canvas id="earnings-chart" width="400" height="200"></canvas>
                    </div>
                </div>
                
                <div class="dashboard-card transactions-card">
                    <h3>Recent Transactions</h3>
                    <div class="transactions-list" id="transactions-list">
                        <div class="transaction-item placeholder">No transactions yet</div>
                    </div>
                </div>
                
                <div class="dashboard-card">
                    <h3>Data Management</h3>
                    <div class="data-management-options">
                        <button id="export-data-btn" class="btn btn-secondary">Export Your Data</button>
                        <button id="import-data-btn" class="btn btn-secondary">Import Data</button>
                    </div>
                    <div id="data-management-result" class="mt-4"></div>
                </div>
            </div>
        </div>
    </main>

    <footer>
        <div class="container">
            <p>© 2023 Future Civilization with selfAI</p>
        </div>
    </footer>

    <script src="assets/js/utils.js"></script>
    <script src="assets/js/database.js"></script>
    <script src="assets/js/auth.js"></script>
    <script src="assets/js/progress.js"></script>
    <script src="assets/js/rewards.js"></script>
    <script src="assets/js/dashboard.js"></script>
    <script src="assets/js/data-management.js"></script>
</body>
</html>

Step 2: Create the Dashboard JavaScript

Now, let's create our dashboard.js file:

assets/js/dashboard.js
/**
 * Dashboard Management Module
 * Handles dashboard UI and visualization
 */

/**
 * Initialize the dashboard
 */
function initializeDashboard() {
    const user = getCurrentUser();
    
    if (!user) {
        console.error('Cannot initialize dashboard: User not authenticated');
        return;
    }
    
    // Update user information
    updateUserInterface();
    
    // Update progress visualization
    updateProgressVisualization();
    
    // Update earnings visualization
    updateEarningsVisualization();
    
    // Update transactions list
    updateTransactionsList();
    
    // Set up event listeners
    setupDashboardEventListeners();
}

/**
 * Update the progress visualization
 */
function updateProgressVisualization() {
    const user = getCurrentUser();
    
    if (!user) return;
    
    // Get progress data
    const completedChapters = user.progress.completedChapters.length;
    const totalChapters = getDatabase('content.totalChapters') || 50;
    const progressPercentage = (completedChapters / totalChapters) * 100;
    
    // Update progress bar
    $('#progress-bar').css('width', `${progressPercentage}%`);
    
    // Update progress text
    $('#progress-text').text(`${completedChapters}/${totalChapters} chapters completed`);
}

/**
 * Update the earnings visualization with Chart.js
 */
function updateEarningsVisualization() {
    const user = getCurrentUser();
    
    if (!user) return;
    
    // Update total earnings
    $('#total-ctc').text(`${user.rewards.totalCTC.toFixed(1)} CTC`);
    
    // Get rewards summary
    const summary = getRewardsSummary();
    
    // Create data for the chart
    const categories = Object.keys(summary.categories);
    const amounts = categories.map(cat => summary.categories[cat]);
    
    // Create a chart
    const ctx = document.getElementById('earnings-chart').getContext('2d');
    new Chart(ctx, {
        type: 'pie',
        data: {
            labels: categories,
            datasets: [{
                label: 'CTC Earnings',
                data: amounts,
                backgroundColor: [
                    'rgba(75, 192, 192, 0.7)',
                    'rgba(54, 162, 235, 0.7)',
                    'rgba(153, 102, 255, 0.7)',
                    'rgba(255, 205, 86, 0.7)',
                    'rgba(255, 99, 132, 0.7)',
                ],
                borderWidth: 1
            }]
        },
        options: {
            responsive: true,
            plugins: {
                legend: {
                    position: 'bottom',
                }
            }
        }
    });
}

/**
 * Update the transactions list
 */
function updateTransactionsList() {
    const transactions = getTransactionHistory();
    
    // Clear the list
    $('#transactions-list').empty();
    
    if (transactions.length === 0) {
        $('#transactions-list').html('
No transactions yet
'); return; } // Add transaction items transactions.forEach(txn => { const date = new Date(txn.timestamp).toLocaleDateString(); const time = new Date(txn.timestamp).toLocaleTimeString(); const txnHtml = `
${txn.reason}
${date} at ${time}
+${txn.amount.toFixed(1)} CTC
`; $('#transactions-list').append(txnHtml); }); } /** * Set up event listeners for the dashboard */ function setupDashboardEventListeners() { // Continue button - go to next unlocked chapter $('#continue-btn').on('click', function() { goToNextUnlockedChapter(); }); // Export data button $('#export-data-btn').on('click', function() { exportUserData(); }); // Import data button $('#import-data-btn').on('click', function() { // Show file import dialog const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = e => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = event => { try { const data = JSON.parse(event.target.result); importUserData(data); } catch (error) { $('#data-management-result') .html('
Invalid data file
'); } }; reader.readAsText(file); }; input.click(); }); } // Initialize the dashboard when the document is ready $(document).ready(function() { initializeDatabase().then(() => { authenticateUser().then(() => { initializeDashboard(); }); }); });

Step 3: Dashboard CSS

Let's add some specific CSS for the dashboard in our style.css file:

Dashboard CSS (add to style.css)
/* Dashboard Styles */
.dashboard-title {
    text-align: center;
    margin-bottom: 2rem;
    font-size: 2rem;
    color: var(--primary-color);
}

.dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1.5rem;
    margin-bottom: 2rem;
}

.dashboard-card {
    background-color: white;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    padding: 1.5rem;
}

.dashboard-card h3 {
    margin-top: 0;
    margin-bottom: 1rem;
    color: var(--primary-color);
    font-size: 1.2rem;
    border-bottom: 1px solid #eee;
    padding-bottom: 0.5rem;
}

/* Progress Bar */
.progress-container {
    margin-bottom: 1.5rem;
}

.progress-bar-wrapper {
    height: 20px;
    background-color: #f0f0f0;
    border-radius: 10px;
    overflow: hidden;
    margin-bottom: 0.5rem;
}

.progress-bar-fill {
    height: 100%;
    background-color: var(--secondary-color);
    width: 0%;
    transition: width 0.5s ease;
}

.progress-text {
    text-align: center;
    font-size: 0.9rem;
    color: #666;
}

.progress-actions {
    display: flex;
    justify-content: center;
}

/* Earnings Summary */
.earnings-summary {
    text-align: center;
}

.total-earnings {
    font-size: 2rem;
    font-weight: bold;
    color: var(--secondary-color);
    margin-bottom: 1rem;
}

/* Transactions List */
.transactions-card {
    grid-column: span 2;
}

.transactions-list {
    max-height: 300px;
    overflow-y: auto;
}

.transaction-item {
    display: flex;
    justify-content: space-between;
    padding: 0.8rem 0;
    border-bottom: 1px solid #eee;
}

.transaction-item:last-child {
    border-bottom: none;
}

.transaction-reason {
    font-weight: bold;
}

.transaction-date {
    color: #666;
    font-size: 0.9rem;
}

.transaction-amount {
    color: var(--secondary-color);
    font-weight: bold;
}

.placeholder {
    color: #999;
    text-align: center;
    font-style: italic;
}

/* Data Management */
.data-management-options {
    display: flex;
    gap: 1rem;
    justify-content: center;
}

.btn-secondary {
    background-color: #6c757d;
}

.btn-secondary:hover {
    background-color: #5a6268;
}

.error-message {
    color: #dc3545;
    padding: 0.5rem;
    background-color: #f8d7da;
    border-radius: 4px;
    margin-top: 0.5rem;
}

.success-message {
    color: #28a745;
    padding: 0.5rem;
    background-color: #d4edda;
    border-radius: 4px;
    margin-top: 0.5rem;
}

/* Responsive adjustments */
@media (max-width: 768px) {
    .transactions-card {
        grid-column: span 1;
    }
    
    .data-management-options {
        flex-direction: column;
    }
    
    .data-management-options .btn {
        margin-bottom: 0.5rem;
    }
}

Exporting & Restoring Data

Let's implement functionality to export and import user data, ensuring users can backup and restore their progress.

Step 1: Create the Data Management Module

First, let's create our data-management.js file:

assets/js/data-management.js
/**
 * Data Management Module
 * Handles exporting, importing, and backing up user data
 */

/**
 * Export the current user's data to a JSON file
 * @returns {boolean} Success status
 */
function exportUserData() {
    const user = getCurrentUser();
    
    if (!user) {
        console.error('Cannot export data: User not authenticated');
        return false;
    }
    
    // Create a clone of the user object for export
    const exportData = JSON.parse(JSON.stringify(user));
    
    // Add export metadata
    exportData.exportMeta = {
        version: getDatabase('version'),
        exportDate: new Date().toISOString(),
        totalChapters: getDatabase('content.totalChapters') || 50
    };
    
    // Convert to JSON string
    const dataStr = JSON.stringify(exportData, null, 2);
    
    // Create a download link
    const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
    const exportFileName = `selfAI_data_${user.userId}.json`;
    
    const linkElement = document.createElement('a');
    linkElement.setAttribute('href', dataUri);
    linkElement.setAttribute('download', exportFileName);
    linkElement.style.display = 'none';
    
    // Add to document, click it, and remove it
    document.body.appendChild(linkElement);
    linkElement.click();
    document.body.removeChild(linkElement);
    
    // Show success message on dashboard if available
    if ($('#data-management-result').length) {
        $('#data-management-result').html('
Data exported successfully!
'); } return true; } /** * Import user data from a JSON object * @param {object} importData - The data to import * @returns {boolean} Success status */ function importUserData(importData) { // Validate the import data if (!importData || !importData.userId || !importData.progress) { console.error('Invalid import data'); return false; } // Get the current user const currentUser = getCurrentUser(); if (!currentUser) { console.error('Cannot import data: User not authenticated'); return false; } // Ensure the import data matches the current user if (importData.userId !== currentUser.userId) { if ($('#data-management-result').length) { $('#data-management-result').html('
Cannot import data: User ID mismatch
'); } return false; } // Update the database with imported data updateDatabase(`users.${currentUser.userId}`, importData); // Update the current user cache Object.assign(currentUser, importData); // Refresh the UI updateUserInterface(); // If on dashboard, refresh dashboard visualizations if (typeof initializeDashboard === 'function') { initializeDashboard(); } // Show success message on dashboard if available if ($('#data-management-result').length) { $('#data-management-result').html('
Data imported successfully!
'); } return true; } /** * Perform an automatic backup if needed * Should be called on application initialization */ function checkAutomaticBackup() { // Check if backup is needed if (isBackupNeeded(3)) { // 3 days between backups const user = getCurrentUser(); if (user) { // Export the data const exportData = JSON.parse(JSON.stringify(user)); // Add export metadata exportData.exportMeta = { version: getDatabase('version'), exportDate: new Date().toISOString(), autoBackup: true, totalChapters: getDatabase('content.totalChapters') || 50 }; // Save in localStorage as backup localStorage.setItem(`${DB_NAME}_backup_${user.userId}`, JSON.stringify(exportData)); // Update the backup timestamp updateBackupTimestamp(); console.log('Automatic backup completed'); } } } /** * Restore the most recent automatic backup * @returns {boolean} Success status */ function restoreAutomaticBackup() { const user = getCurrentUser(); if (!user) { console.error('Cannot restore backup: User not authenticated'); return false; } // Try to get the backup const backupKey = `${DB_NAME}_backup_${user.userId}`; const backupData = localStorage.getItem(backupKey); if (!backupData) { console.error('No backup found for this user'); return false; } try { const importData = JSON.parse(backupData); return importUserData(importData); } catch (error) { console.error('Failed to parse backup data:', error); return false; } } // Check for automatic backup on page load $(document).ready(function() { authenticateUser().then(() => { checkAutomaticBackup(); }); });

Step 2: Create the Utils.js Module

Let's create a utility module with helper functions:

assets/js/utils.js
/**
 * Utility Functions
 * Common helper functions used across the application
 */

/**
 * Format a date string to a readable format
 * @param {string} dateString - ISO date string
 * @returns {string} Formatted date
 */
function formatDate(dateString) {
    const date = new Date(dateString);
    return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
}

/**
 * Format a number to 1 decimal place with CTC suffix
 * @param {number} amount - The amount to format
 * @returns {string} Formatted amount
 */
function formatCTC(amount) {
    return amount.toFixed(1) + ' CTC';
}

/**
 * Get URL parameter by name
 * @param {string} name - Parameter name
 * @returns {string|null} Parameter value
 */
function getUrlParam(name) {
    const urlParams = new URLSearchParams(window.location.search);
    return urlParams.get(name);
}

/**
 * Get current chapter ID from URL or filename
 * @returns {number} Current chapter ID
 */
function getCurrentChapterId() {
    // Try to get from URL parameter
    const paramId = getUrlParam('chapter');
    if (paramId && !isNaN(parseInt(paramId))) {
        return parseInt(paramId);
    }
    
    // Try to extract from filename (e.g., doc5.html -> 5)
    const pathMatch = window.location.pathname.match(/doc(\d+)\.html$/);
    if (pathMatch && pathMatch[1]) {
        return parseInt(pathMatch[1]);
    }
    
    // Default to chapter 1
    return 1;
}

/**
 * Show notification
 * @param {string} message - The message to show
 * @param {string} type - Type of notification (success, error, info)
 * @param {number} duration - Duration in milliseconds
 */
function showNotification(message, type = 'info', duration = 3000) {
    // Check if notification container exists
    let container = document.getElementById('notification-container');
    
    // Create container if it doesn't exist
    if (!container) {
        container = document.createElement('div');
        container.id = 'notification-container';
        container.style.position = 'fixed';
        container.style.top = '20px';
        container.style.right = '20px';
        container.style.zIndex = '1000';
        document.body.appendChild(container);
    }
    
    // Create notification element
    const notification = document.createElement('div');
    notification.className = `notification notification-${type}`;
    notification.innerHTML = message;
    
    // Set notification styles
    notification.style.backgroundColor = 
        type === 'success' ? '#d4edda' : 
        type === 'error' ? '#f8d7da' : '#cce5ff';
    notification.style.color = 
        type === 'success' ? '#155724' : 
        type === 'error' ? '#721c24' : '#004085';
    notification.style.padding = '10px 15px';
    notification.style.marginBottom = '10px';
    notification.style.borderRadius = '4px';
    notification.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
    notification.style.transition = 'opacity 0.3s';
    
    // Add to container
    container.appendChild(notification);
    
    // Remove after duration
    setTimeout(() => {
        notification.style.opacity = '0';
        setTimeout(() => {
            container.removeChild(notification);
        }, 300);
    }, duration);
}

/**
 * Debounce function to limit frequency of calls
 * @param {Function} func - Function to debounce
 * @param {number} wait - Wait time in milliseconds
 * @returns {Function} Debounced function
 */
function debounce(func, wait = 300) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

Step 3: Integrating Automatic Backups

Let's add code to index.html and other pages to enable automatic backup checks:

Automatic Backup Integration (add to script section)
// Add to the initialization code in index.html and other pages
$(document).ready(function() {
    initializeDatabase().then(() => {
        authenticateUser().then(userId => {
            updateUserInterface();
            
            // Check for automatic backup
            checkAutomaticBackup();
            
            // Additional page initialization...
        });
    });
});

πŸ’‘ Advanced Backup Strategy

For a more sophisticated backup approach, consider:

  • Automatically store backups in a public directory using AJAX (requires a simple server endpoint)
  • Implement versioning by including timestamps in backup filenames
  • Add a backup browser page where users can see and restore from their backup history
  • Use IndexedDB instead of localStorage for larger data storage capabilities

Complete Example

Here's a summary of how all the components work together in our client-side authentication and progress tracking system:

System Architecture Overview

  1. User Identification:

    FingerprintJS generates a unique browser fingerprint, which we convert to a user ID. This allows us to recognize returning users without requiring login credentials.

  2. Local Database:

    We use localStorage to maintain a structured database with user information, progress data, and transaction history. The schema ensures consistent data structure.

  3. Progress Management:

    As users complete chapters and tasks, we update their progress in the local database. Each chapter can only be accessed if the previous one is completed.

  4. Reward System:

    Users earn CTC for completing chapters and social tasks. Transactions are recorded with timestamps and reasons, providing a transparent history.

  5. Dashboard Visualization:

    A visual dashboard shows progress, earnings, and transaction history, motivating users to continue their learning journey.

  6. Data Persistence:

    Automatic backups ensure data isn't lost, and users can manually export/import their data for added security.

User Flow Diagram

User enters site
    ↓
Fingerprint identified β†’ [New user] β†’ Create user record
    ↓                     [Existing user] β†’ Retrieve data
Initialize local DB
    ↓
Home page loads
    ↓
User clicks "Start Course"
    ↓
Chapter 1 opens (always unlocked)
    ↓
User completes chapter tasks
    ↓
Chapter marked as completed
    ↓
User earns CTC reward
    ↓
Next chapter unlocked
    ↓
User continues through chapters
    ↓
Social tasks complete β†’ Extra CTC awarded
    ↓
Dashboard shows progress and earnings
    ↓
Automatic backup every 3 days
    ↓
User can export/import data manually

Key Implementation Points

Unique Features

  • No server or database required
  • Persistent user identification across sessions
  • Secure local storage of user data
  • Progressive content unlocking
  • Token reward economy
  • Social engagement integration
  • Data portability with import/export

Potential Enhancements

  • Move to IndexedDB for larger storage capacity
  • Add optional cloud sync functionality
  • Implement offline capabilities with Service Workers
  • Add achievement badges for milestones
  • Create a leaderboard using localStorage across domains
  • Add interactive quizzes to verify learning
  • Implement advanced analytics tracking

Troubleshooting

Here are solutions to common issues you might encounter while implementing this system:

Common Issues and Solutions

Data Not Persisting Across Sessions

  • Ensure localStorage is available in the browser
  • Check for errors in the browser console
  • Verify the database initialization is completing
  • Ensure you're using the same domain across sessions (localStorage is domain-specific)

User Not Being Recognized

  • Check if FingerprintJS is loading correctly
  • Verify that cookies are enabled in the browser
  • Ensure the user hasn't cleared their browser data
  • Try adding a fallback identification method (such as a device ID)

Chapters Not Unlocking Properly

  • Verify that chapter completion is being recorded correctly
  • Check the progress tracking logic for errors
  • Ensure the chapter IDs are consistent across files
  • Add console logs to track the completion process

localStorage Quota Exceeded

  • Optimize data storage by removing unnecessary information
  • Implement data pruning for older, less important data
  • Consider moving to IndexedDB for larger storage capacity
  • Add error handling for storage quota issues

Import/Export Not Working

  • Check that the export file has the correct format
  • Verify the user ID in the import file matches the current user
  • Look for JSON parsing errors in the console
  • Ensure the browser supports the File API

πŸ’‘ Debugging Tips

Use these techniques to track down issues:

  • Add console.log() statements at key points in your code
  • Use the browser's Application tab to inspect localStorage content
  • Create a debug mode that displays more detailed information
  • Test in different browsers to identify browser-specific issues
  • Implement error logging to track issues users encounter

Conclusion

Congratulations! You've now learned how to build a complete client-side user authentication and progress tracking system for educational content.

What You've Learned

  • How to use browser fingerprinting to identify users without requiring login
  • How to create and maintain a structured database in localStorage
  • Techniques for tracking user progress through educational content
  • How to implement a token reward system for completed tasks
  • Methods for data persistence and backup in client-side applications
  • Creating a dashboard to visualize progress and achievements
  • Building a complete educational platform without a backend

This approach provides a powerful way to create interactive, personalized experiences for users on static websites. By leveraging client-side technologies, you can deliver sophisticated functionality without the complexity of server management or user account systems.

Next Steps

To further enhance your system, consider exploring these advanced topics:

  1. Add offline support with Service Workers
  2. Implement a more sophisticated user interface with animations
  3. Create a social component allowing users to share achievements
  4. Add interactive assessments to test knowledge acquisition
  5. Implement data visualization for learning patterns
  6. Explore WebRTC for peer-to-peer functionality
  7. Integrate with blockchain technologies for verified credentials

The techniques you've learned in this guide provide a foundation for creating more advanced client-side applications. By mastering these concepts, you're well-equipped to build sophisticated, user-friendly experiences without the overhead of server infrastructure.