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/
- css/
- data/
- schema.json
- course-content.json
- docs/
- doc1.html
- doc2.html
- ...
- doc50.html
- backups/
- [userid].json
- index.html
- dashboard.html
- README.md
- assets/
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:
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:
<!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:
: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:
<!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:
{
"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:
/**
* 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:
{
"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:
/**
* 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:
// 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:
/**
* 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).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:
/**
* 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:
/**
* 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:
<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>
// 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:
<!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:
/**
* 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('');
}
};
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 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:
/**
* 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('');
}
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('');
}
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('');
}
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:
/**
* 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:
// 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
-
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.
-
Local Database:
We use localStorage to maintain a structured database with user information, progress data, and transaction history. The schema ensures consistent data structure.
-
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.
-
Reward System:
Users earn CTC for completing chapters and social tasks. Transactions are recorded with timestamps and reasons, providing a transparent history.
-
Dashboard Visualization:
A visual dashboard shows progress, earnings, and transaction history, motivating users to continue their learning journey.
-
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:
- Add offline support with Service Workers
- Implement a more sophisticated user interface with animations
- Create a social component allowing users to share achievements
- Add interactive assessments to test knowledge acquisition
- Implement data visualization for learning patterns
- Explore WebRTC for peer-to-peer functionality
- 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.