Apply your knowledge to build something amazing!
ℹ️ Project Overview Difficulty Level: Advanced
Estimated Time: 8-10 hours
Skills Practiced:
This is the final capstone project where you'll build a comprehensive dashboard application combining all the skills learned throughout the course. The dashboard will include Firebase authentication, Firestore database integration, real-time data, and a modern user interface.
graph TB
A[Project Start] --> B[Phase 1: Setup]
B --> C[Phase 2: Authentication]
C --> D[Phase 3: Components]
D --> E[Phase 4: Dashboard]
E --> F[Phase 5: Advanced Features]
F --> G[Project Complete]
B --> B1[SvelteKit Setup]
B --> B2[Firebase Config]
C --> C1[Login/Signup Pages]
C --> C2[Navigation Bar]
C --> C3[Auth Store]
D --> D1[Dashboard Cards]
D --> D2[Activity Feed]
D --> D3[Quick Actions]
E --> E1[Main Dashboard]
E --> E2[Real-time Data]
E --> E3[Stats Display]
F --> F1[Notifications]
F --> F2[Data Export]
F --> F3[Dark Mode]
style A fill:#e1f5fe
style G fill:#c8e6c9
style C fill:#fff3e0
style E fill:#f3e5f5
Before we can connect Firebase to our SvelteKit project we have to create a new SvelteKit project.
💡 Best Practice Take a moment to understand the SvelteKit folder structure before diving in. This will save you time when navigating and organizing your code!
Quick Start (Choose One):
StackBlitz (Online): 🚀 Open in StackBlitz
GitHub (Local):
git clone https://github.com/academic-telebort/Web-3-Final-Project-Updated.git
cd Web-3-Final-Project-Updated
./setup.sh
Local ZIP: Download project-06-dashboard.zip
, extract, then run ./setup.sh
💡 Tip The
setup.sh
script will guide you through Firebase configuration and setup
🔥 Firebase Setup Required
- Create Firebase project at https://console.firebase.google.com/
- Enable Email/Password authentication
- Create Firestore database
- Copy
.env.example
to.env
and add your credentialsSee Templates/TESTING.md for detailed Firebase setup guide
💡 Understanding the Template Approach The template provides minimal scaffolding (~15%):
- ✅ Basic SvelteKit 2.0 project structure
- ✅ Firebase configuration placeholders
- ✅ Basic authentication store setup
- ✅ Bootstrap 5 styling framework
You will build (~85%):
- 🔨 Complete authentication flow
- 🔨 Dashboard components and logic
- 🔨 Firestore database integration
- 🔨 All user-facing features
DO NOT DELETE the existing files in the template
ONLY EDIT the necessary files in src/
✅ SvelteKit project template loaded successfully
✅ You can see the src folder structure
✅ Project runs without errors in your browser
Students would need to complete the steps in Chapter 10 - Introduction to Firebase (10.3 How to setup for Firebase) before continuing Project Part 2.
⚠️ Firebase Setup Requirements Before proceeding, ensure you have:
- Created a Firebase project
- Enabled Authentication (Email/Password)
- Enabled Firestore Database
- Obtained your Firebase configuration object
- Set up security rules for development
ℹ️ Info Phase Overview In this phase, you'll implement the authentication system - the foundation of any secure web application. Take your time here as authentication errors can be frustrating to debug later!
In order to complete this project, we would require Firebase Authentication and Firebase Database.
Students would need to complete the steps in:
Then the authentication and database are ready to use.
After creating firebase authentication, we are moving on to creating a login page and a signup page for authentication.
ℹ️ SvelteKit 2.0 File Structure In SvelteKit 2.0, pages use the
+page.svelte
naming convention:
- Create folder:
src/routes/login/
- Create file:
src/routes/login/+page.svelte
- Create folder:
src/routes/signup/
- Create file:
src/routes/signup/+page.svelte
🔍 Discovery Challenge: Authentication Flow
Before implementing, research and answer:
- Firebase Auth Methods: What Firebase function signs in existing users? (Hint: Check Chapter 11.3)
- Error Handling: How should you handle failed login attempts?
- User Feedback: What loading states improve user experience?
- Navigation: When should authenticated users be redirected?
Expected Skills: Firebase Auth API, Svelte stores, error handling, navigation
💡 Code Organization Notice how we separate concerns in this component:
- Imports - All dependencies at the top
- State variables - Reactive variables for form management
- Functions - Business logic separated from markup
- Lifecycle - Cleanup in onDestroy to prevent memory leaks
src/routes/login/+page.svelte
{/* src/routes/login/+page.svelte */}
<script>
// TODO: Import Firebase authentication functions
// Research: What Firebase function handles email/password login?
import {signInWithEmailAndPassword} from 'firebase/auth';
import {auth} from '../../firebase';
import { goto } from '$app/navigation';
import { onDestroy } from 'svelte';
import authStore from '../../stores/authStore';
// TODO: Set up form state variables
// Challenge: What states do you need for email, password, loading, and errors?
let email = "";
let password = "";
let loading = false;
let error = "";
// TODO: Implement login function
// Research: How do you call Firebase signInWithEmailAndPassword?
// Hint: Use try/catch for error handling
async function login(){
loading = true;
error = "";
try {
// TODO: Call Firebase auth function with email and password
await signInWithEmailAndPassword(auth, email, password);
} catch(err) {
// TODO: Set user-friendly error message
error = "Invalid email or password. Please try again.";
console.log(err);
}
loading = false;
}
// TODO: Subscribe to authStore for auto-redirect
// Challenge: When should logged-in users be redirected to dashboard?
const sub = authStore.subscribe(async (info) => {
if (info.isLoggedIn) {
await goto('/');
}
});
// TODO: Cleanup subscription on component destroy
// Research: Why is cleanup important? What happens without it?
onDestroy(() => {
sub();
});
</script>
{/* TODO: Create login form UI */}
{/* Research: Bootstrap 5 card components and form styling */}
<div class="container d-flex justify-content-center align-items-center min-vh-100">
<div class="card shadow-lg" style={{'width':'28rem'}}>
<div class="card-body p-5">
{/* TODO: Add heading and description */}
<div class="text-center mb-4">
<h2 class="card-title fw-bold">Welcome Back</h2>
<p class="card-text text-muted">Please log-in to continue to your dashboard</p>
</div>
{/* TODO: Display error messages conditionally */}
{/* Challenge: How do you show errors only when they exist? */}
{#if error}
<div class="alert alert-danger" role="alert">
{error}
</div>
{/if}
{/* TODO: Create form with submit handler */}
{/* Research: What does on:submit|preventDefault do? */}
<form on:submit|preventDefault={login}>
{/* TODO: Email input field */}
{/* Challenge: How do you bind input value to variable? */}
<div class="mb-3">
<label for="emailInput" class="form-label">Email address</label>
<input
bind:value={email}
type="email"
class="form-control form-control-lg"
id="emailInput"
placeholder="Enter your email"
required
/>
</div>
{/* TODO: Password input field */}
<div class="mb-3">
<label for="passwordInput" class="form-label">Password</label>
<input
bind:value={password}
type="password"
class="form-control form-control-lg"
id="passwordInput"
placeholder="Enter your password"
required
/>
</div>
<div class="form-text mb-3">
Your password must be at least 6 characters long.
</div>
{/* TODO: Submit button with loading state */}
{/* Challenge: How do you show different text while loading? */}
<button
type="submit"
class="btn btn-primary btn-lg w-100 mb-3"
disabled={loading}
>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
</div>
{/* TODO: Add link to signup page */}
<div class="card-footer text-center bg-light">
<small class="text-muted">
First time? <a href="/signup" class="text-decoration-none">Create an account</a>
</small>
</div>
</div>
</div>
🔍 Discovery Challenge: User Registration
Before implementing, research and answer:
- Firebase Registration: What Firebase function creates new user accounts?
- Validation Logic: How should you validate password confirmation matches?
- Firebase Error Codes: What specific error codes should you handle? (auth/email-already-in-use, auth/weak-password)
- Security: What minimum password requirements should you enforce?
Expected Skills: Firebase Auth API, form validation, error code handling
src/routes/signup/+page.svelte
{/* src/routes/signup/+page.svelte */}
<script>
// TODO: Import Firebase user creation function
// Research: How is creating a user different from signing in?
import {createUserWithEmailAndPassword} from 'firebase/auth';
import {auth} from '../../firebase';
import { goto } from '$app/navigation';
import authStore from '../../stores/authStore';
import { onDestroy } from 'svelte';
// TODO: Set up form state variables
// Challenge: Why do we need confirmPassword in addition to password?
let email = "";
let password = "";
let confirmPassword = "";
let loading = false;
let error = "";
// TODO: Implement registration function with validation
// Research: What validations should run before calling Firebase?
async function register(){
// TODO: Check if passwords match
if (password !== confirmPassword) {
error = "Passwords do not match.";
return;
}
// TODO: Validate password length
// Challenge: Why is 6 characters the minimum?
if (password.length < 6) {
error = "Password must be at least 6 characters long.";
return;
}
loading = true;
error = "";
try {
// TODO: Call Firebase createUserWithEmailAndPassword
await createUserWithEmailAndPassword(auth, email, password);
} catch(err) {
// TODO: Handle specific Firebase error codes
// Research: What other error codes might occur?
if (err.code === 'auth/email-already-in-use') {
error = "An account with this email already exists.";
} else if (err.code === 'auth/weak-password') {
error = "Password is too weak. Please choose a stronger password.";
} else {
error = "Failed to create account. Please try again.";
}
console.log(err);
}
loading = false;
}
// TODO: Auto-redirect if already logged in
const sub = authStore.subscribe(async (info) => {
if (info.isLoggedIn) {
await goto('/');
}
});
// TODO: Cleanup on destroy
onDestroy(() => {
sub();
});
</script>
{/* TODO: Create signup form UI */}
<div class="container d-flex justify-content-center align-items-center min-vh-100">
<div class="card shadow-lg" style={{'width':'28rem'}}>
<div class="card-body p-5">
{/* TODO: Add welcoming header */}
<div class="text-center mb-4">
<h2 class="card-title fw-bold">Create Account</h2>
<p class="card-text text-muted">Join us and start tracking your progress</p>
</div>
{/* TODO: Display error alerts */}
{#if error}
<div class="alert alert-danger" role="alert">
{error}
</div>
{/if}
{/* TODO: Create registration form */}
<form on:submit|preventDefault={register}>
{/* TODO: Email input */}
<div class="mb-3">
<label for="emailInput" class="form-label">Email address</label>
<input
bind:value={email}
type="email"
class="form-control form-control-lg"
id="emailInput"
placeholder="Enter your email"
required
/>
</div>
{/* TODO: Password input */}
<div class="mb-3">
<label for="passwordInput" class="form-label">Password</label>
<input
bind:value={password}
type="password"
class="form-control form-control-lg"
id="passwordInput"
placeholder="Create a password"
required
/>
</div>
{/* TODO: Password confirmation input */}
{/* Challenge: Why is password confirmation important for UX? */}
<div class="mb-3">
<label for="confirmPasswordInput" class="form-label">Confirm Password</label>
<input
bind:value={confirmPassword}
type="password"
class="form-control form-control-lg"
id="confirmPasswordInput"
placeholder="Confirm your password"
required
/>
</div>
<div class="form-text mb-3">
Your password must be at least 6 characters long.
</div>
{/* TODO: Submit button with loading state */}
<button
type="submit"
class="btn btn-success btn-lg w-100 mb-3"
disabled={loading}
>
{loading ? 'Creating Account...' : 'Create Account'}
</button>
</form>
</div>
{/* TODO: Add link for existing users */}
<div class="card-footer text-center bg-light">
<small class="text-muted">
Already have an account? <a href="/login" class="text-decoration-none">Sign in here</a>
</small>
</div>
</div>
</div>
✅ Login page loads without errors
✅ Signup page loads without errors
✅ Forms accept user input properly
✅ Error messages display when authentication fails
✅ Successful login redirects to dashboard
Create a navigation component that will be used across the dashboard:
🔍 Discovery Challenge: Navigation Component
Before implementing, research and answer:
- Firebase SignOut: What Firebase function logs users out? How does it differ from signIn?
- Store Management: Why update both isLoggedIn and firebaseControlled on logout?
- Component Location: Why is this in
src/lib/
instead ofsrc/routes/
?- Reactive Syntax: What does
$authStore
syntax mean in Svelte?Expected Skills: Firebase Auth logout, Svelte stores ($syntax), component organization
💡 Component Reusability The Navbar component will be used on every page. Keep it modular and avoid page-specific logic to ensure maximum reusability.
src/lib/Navbar.svelte
<script>
// TODO: Import Firebase signOut function
// Research: What's the difference between signOut and signInWithEmailAndPassword?
import { signOut } from 'firebase/auth';
import { auth } from '../firebase';
import authStore from '../stores/authStore';
import { goto } from '$app/navigation';
// TODO: Implement logout function
// Challenge: Why do we need to update authStore manually after signOut?
async function logout() {
try {
// TODO: Call Firebase signOut
await signOut(auth);
// TODO: Reset authStore state
$authStore.isLoggedIn = false;
$authStore.firebaseControlled = false;
// TODO: Redirect to login page
await goto('/login');
} catch (error) {
console.error('Logout error:', error);
}
}
</script>
{/* TODO: Create responsive navigation bar */}
{/* Research: Bootstrap 5 navbar components and responsive design */}
<nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow-sm">
<div class="container">
{/* TODO: Add brand/logo with icon */}
<a class="navbar-brand fw-bold" href="/">
<i class="bi bi-speedometer2 me-2"></i>
My Dashboard
</a>
{/* TODO: Add mobile menu toggle */}
{/* Challenge: How does Bootstrap collapse work? */}
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
>
<span class="navbar-toggler-icon"></span>
</button>
{/* TODO: Create collapsible navigation */}
<div class="collapse navbar-collapse" id="navbarNav">
{/* TODO: Add main navigation links */}
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">
<i class="bi bi-house me-1"></i>
Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/analytics">
<i class="bi bi-graph-up me-1"></i>
Analytics
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profile">
<i class="bi bi-person me-1"></i>
Profile
</a>
</li>
</ul>
{/* TODO: Add user dropdown menu */}
{/* Challenge: How do you display reactive store values in HTML? */}
<div class="navbar-nav">
<div class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
>
<i class="bi bi-person-circle me-1"></i>
\{$authStore.user?.email || 'User'\}
</a>
{/* TODO: Create dropdown menu items */}
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/profile">Profile Settings</a></li>
<li><hr class="dropdown-divider"></li>
<li>
{/* TODO: Add logout button with click handler */}
<button class="dropdown-item" on:click={logout}>
<i class="bi bi-box-arrow-right me-1"></i>
Logout
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</nav>
ℹ️ Info Component Architecture We're building modular, reusable components. This approach makes your code:
Create reusable dashboard card components:
💡 Design Pattern Dashboard cards follow the "Single Responsibility Principle" - each card displays one type of metric or information. This makes them highly reusable across different dashboard views.
src/lib/DashboardCard.svelte
<script>
export let title = "";
export let value = "";
export let icon = "";
export let color = "primary";
export let trend = null; // \{ direction: 'up'|'down', percentage: number \}
</script>
<div class="card h-100 shadow-sm border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="card-subtitle mb-2 text-muted">{title}</h6>
<h2 class="card-title mb-0 text-{color}">{value}</h2>
{#if trend}
<small class="text-{trend.direction === 'up' ? 'success' : 'danger'}">
<i class="bi bi-arrow-{trend.direction} me-1"></i>
{trend.percentage}% from last month
</small>
{/if}
</div>
{#if icon}
<div class="text-{color} opacity-75">
<i class="bi bi-{icon} fs-1"></i>
</div>
{/if}
</div>
</div>
</div>
🔍 Discovery Challenge: Real-time Data Subscription
Before implementing, research and answer:
- Firestore Queries: How do you build a query with orderBy and limit?
- Real-time Updates: What's the difference between
getDocs()
andonSnapshot()
?- Memory Management: Why must you return the unsubscribe function from onMount?
- Timestamp Conversion: How do Firestore Timestamps differ from JavaScript Dates?
Expected Skills: Firestore queries, real-time listeners, lifecycle management, timestamp handling
⚠️ Real-time Listener This component uses Firestore's
onSnapshot
for real-time updates. Remember to:
- Always unsubscribe when the component unmounts
- Handle cases where the user might not be authenticated
- Consider pagination for large activity lists
src/lib/ActivityFeed.svelte
<script>
import { onMount } from 'svelte';
// TODO: Import Firestore query functions
// Research: What does each function do? (collection, query, orderBy, limit, onSnapshot)
import { collection, query, orderBy, limit, onSnapshot } from 'firebase/firestore';
import { db } from '../firebase';
import authStore from '../stores/authStore';
let activities = [];
onMount(() => {
// TODO: Check if user is authenticated before querying
if ($authStore.user) {
// TODO: Build Firestore query
// Challenge: Why order by 'timestamp' desc? What does limit(10) do?
const q = query(
collection(db, 'activities'),
orderBy('timestamp', 'desc'),
limit(10)
);
// TODO: Set up real-time listener
// Research: How is onSnapshot different from a one-time read?
const unsubscribe = onSnapshot(q, (snapshot) => {
// TODO: Transform snapshot documents to array
// Challenge: Why use doc.id and ...doc.data()?
activities = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
timestamp: doc.data().timestamp?.toDate()
}));
});
// TODO: Return cleanup function
// Challenge: What happens if you forget this?
return unsubscribe;
}
});
// TODO: Implement relative time formatter
// Research: How do you calculate time differences in JavaScript?
function formatTime(timestamp) {
if (!timestamp) return '';
const now = new Date();
const diff = now - timestamp;
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
return `${days}d ago`;
}
</script>
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-transparent">
<h5 class="card-title mb-0">
<i class="bi bi-activity me-2"></i>
Recent Activity
</h5>
</div>
<div class="card-body p-0">
{#if activities.length > 0}
<div class="list-group list-group-flush">
{#each activities as activity}
<div class="list-group-item border-0">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h6 class="mb-1">{activity.title}</h6>
<p class="mb-1 text-muted small">{activity.description}</p>
</div>
<small class="text-muted">{formatTime(activity.timestamp)}</small>
</div>
</div>
{/each}
</div>
{:else}
<div class="text-center py-4">
<i class="bi bi-inbox fs-1 text-muted"></i>
<p class="text-muted mt-2">No recent activity</p>
</div>
{/if}
</div>
</div>
🔍 Discovery Challenge: Writing to Firestore
Before implementing, research and answer:
- Firestore Writes: What's the difference between
addDoc()
andsetDoc()
?- Data Structure: Why include userId in each document?
- Timestamp Storage: Should you use JavaScript Date or Firestore serverTimestamp()?
- Input Validation: What validations should you perform before writing to database?
Expected Skills: Firestore writes, data modeling, input validation, modal UI
src/lib/QuickActions.svelte
<script>
// TODO: Import Firestore write functions
// Research: When to use addDoc vs setDoc?
import { addDoc, collection } from 'firebase/firestore';
import { db } from '../firebase';
import authStore from '../stores/authStore';
// TODO: Set up component state
let showModal = false;
let actionTitle = '';
let actionDescription = '';
let loading = false;
// TODO: Implement function to add activity to Firestore
// Challenge: Why validate input before database write?
async function addQuickAction() {
// TODO: Validate input
if (!actionTitle.trim()) return;
loading = true;
try {
// TODO: Write document to Firestore
// Research: What data should you store in each activity document?
await addDoc(collection(db, 'activities'), {
userId: $authStore.user.uid,
title: actionTitle,
description: actionDescription,
timestamp: new Date(),
type: 'quick_action'
});
actionTitle = '';
actionDescription = '';
showModal = false;
} catch (error) {
console.error('Error adding activity:', error);
}
loading = false;
}
</script>
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-transparent">
<h5 class="card-title mb-0">
<i class="bi bi-lightning me-2"></i>
Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button
class="btn btn-outline-primary"
on:click={() => showModal = true}
>
<i class="bi bi-plus-circle me-2"></i>
Add Activity
</button>
<button class="btn btn-outline-success">
<i class="bi bi-file-earmark-plus me-2"></i>
Create Report
</button>
<button class="btn btn-outline-info">
<i class="bi bi-gear me-2"></i>
Settings
</button>
</div>
</div>
</div>
{/* Modal for adding activity */}
{#if showModal}
<div class="modal fade show d-block" tabindex="-1" style={{'backgroundColor':'rgba(0,0,0,0.5)'}}>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Activity</h5>
<button
type="button"
class="btn-close"
on:click={() => showModal = false}
></button>
</div>
<div class="modal-body">
<form on:submit|preventDefault={addQuickAction}>
<div class="mb-3">
<label for="actionTitle" class="form-label">Title</label>
<input
type="text"
class="form-control"
id="actionTitle"
bind:value={actionTitle}
required
/>
</div>
<div class="mb-3">
<label for="actionDescription" class="form-label">Description</label>
<textarea
class="form-control"
id="actionDescription"
rows="3"
bind:value={actionDescription}
></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
on:click={() => showModal = false}
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
on:click={addQuickAction}
disabled={loading}
>
{loading ? 'Adding...' : 'Add Activity'}
</button>
</div>
</div>
</div>
</div>
{/if}
✅ Dashboard card component renders properly
✅ Activity feed shows real-time updates
✅ Quick actions modal opens and closes
✅ Data successfully saves to Firestore
✅ Components are responsive on mobile
ℹ️ Info Dashboard Integration This is where everything comes together! The main dashboard integrates all components and manages the overall application state. Take time to understand how data flows between components.
src/routes/+page.svelte
<script>
import { onMount, onDestroy } from 'svelte';
import { goto } from '$app/navigation';
import { collection, query, where, getDocs } from 'firebase/firestore';
import { db } from '../firebase';
import authStore from '../stores/authStore';
import Navbar from '$lib/Navbar.svelte';
import DashboardCard from '$lib/DashboardCard.svelte';
import ActivityFeed from '$lib/ActivityFeed.svelte';
import QuickActions from '$lib/QuickActions.svelte';
let stats = {
totalActivities: 0,
thisMonth: 0,
completedTasks: 0,
activeProjects: 3
};
let loading = true;
// Authentication guard
const sub = authStore.subscribe(async (info) => {
if (!info.isLoggedIn && info.firebaseControlled) {
await goto('/login');
}
});
onMount(async () => {
if ($authStore.user) {
await loadDashboardData();
}
loading = false;
});
async function loadDashboardData() {
try {
// Load user activities
const activitiesQuery = query(
collection(db, 'activities'),
where('userId', '==', $authStore.user.uid)
);
const activitiesSnapshot = await getDocs(activitiesQuery);
stats.totalActivities = activitiesSnapshot.size;
// Calculate this month's activities
const thisMonth = new Date();
thisMonth.setDate(1);
thisMonth.setHours(0, 0, 0, 0);
stats.thisMonth = activitiesSnapshot.docs.filter(doc => {
const timestamp = doc.data().timestamp?.toDate();
return timestamp && timestamp >= thisMonth;
}).length;
// Mock data for other stats
stats.completedTasks = Math.floor(stats.totalActivities * 0.7);
} catch (error) {
console.error('Error loading dashboard data:', error);
}
}
onDestroy(() => {
sub();
});
</script>
<svelte:head>
<title>My Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
</svelte:head>
<Navbar />
<main class="container-fluid py-4">
{#if loading}
<div class="d-flex justify-content-center align-items-center" style={{'height':'50vh'}}>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
{:else}
{/* Welcome Section */}
<div class="row mb-4">
<div class="col-12">
<div class="bg-gradient bg-primary text-white rounded-3 p-4">
<h1 class="display-6 fw-bold mb-2">
Welcome back, {$authStore.user?.email?.split('@')[0] || 'User'}! 👋
</h1>
<p class="lead mb-0">Here's what's happening with your projects today.</p>
</div>
</div>
</div>
{/* Stats Cards */}
<div class="row g-4 mb-4">
<div class="col-md-3">
<DashboardCard
title="Total Activities"
value={stats.totalActivities.toString()}
icon="activity"
color="primary"
trend={{ direction: 'up', percentage: 12 }}
/>
</div>
<div class="col-md-3">
<DashboardCard
title="This Month"
value={stats.thisMonth.toString()}
icon="calendar-month"
color="success"
trend={{ direction: 'up', percentage: 8 }}
/>
</div>
<div class="col-md-3">
<DashboardCard
title="Completed Tasks"
value={stats.completedTasks.toString()}
icon="check-circle"
color="info"
trend={{ direction: 'down', percentage: 3 }}
/>
</div>
<div class="col-md-3">
<DashboardCard
title="Active Projects"
value={stats.activeProjects.toString()}
icon="folder"
color="warning"
/>
</div>
</div>
{/* Main Content */}
<div class="row g-4">
{/* Activity Feed */}
<div class="col-lg-8">
<ActivityFeed />
</div>
{/* Quick Actions */}
<div class="col-lg-4">
<QuickActions />
</div>
</div>
{/* Additional Charts Section */}
<div class="row g-4 mt-4">
<div class="col-12">
<div class="card shadow-sm border-0">
<div class="card-header bg-transparent">
<h5 class="card-title mb-0">
<i class="bi bi-graph-up me-2"></i>
Performance Overview
</h5>
</div>
<div class="card-body">
<div class="text-center py-5">
<i class="bi bi-bar-chart fs-1 text-muted"></i>
<p class="text-muted mt-2">Charts and analytics coming soon...</p>
</div>
</div>
</div>
</div>
</div>
{/if}
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<style>
.bg-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
transition: transform 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-2px);
}
main {
background-color: #f8f9fa;
min-height: calc(100vh - 76px);
\}
</style>
💡 State Management The authentication store is the single source of truth for user authentication state. Using Svelte stores ensures all components stay synchronized with the authentication status.
Create the authentication store to manage user state:
src/stores/authStore.js
import { writable } from 'svelte/store';
const authStore = writable({
isLoggedIn: false,
firebaseControlled: false,
user: null
});
export default authStore;
⚠️ Security Alert Never commit your Firebase configuration to a public repository! Use environment variables for production deployments.
Set up your Firebase configuration:
src/firebase.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
// Your Firebase configuration
apiKey: "your-api-key",
authDomain: "your-auth-domain",
projectId: "your-project-id",
storageBucket: "your-storage-bucket",
messagingSenderId: "your-messaging-sender-id",
appId: "your-app-id"
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
💡 Authentication Flow The layout component acts as a guard, checking authentication state before rendering protected pages. This centralized approach prevents unauthorized access across your entire application.
Create a layout file to handle authentication state:
src/routes/+layout.svelte
<script>
import { onMount } from 'svelte';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../firebase';
import authStore from '../stores/authStore';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
let currentPath;
page.subscribe((p) => {
currentPath = p.url.pathname;
});
onMount(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
$authStore.isLoggedIn = true;
$authStore.firebaseControlled = true;
$authStore.user = user;
// Redirect to dashboard if on auth pages
if (currentPath === '/login' || currentPath === '/signup') {
goto('/');
}
} else {
$authStore.isLoggedIn = false;
$authStore.firebaseControlled = true;
$authStore.user = null;
// Redirect to login if on protected pages
if (currentPath === '/') {
goto('/login');
}
}
});
return unsubscribe;
});
</script>
<main>
<slot />
</main>
✅ Dashboard displays user data correctly
✅ Authentication redirects work properly
✅ Logout functionality clears user session
✅ Protected routes prevent unauthorized access
✅ Real-time data updates without page refresh
ℹ️ Info Level Up Your Dashboard These advanced features will make your dashboard stand out! They demonstrate mastery of real-time data, user experience, and modern web development practices.
Add real-time notifications using Firestore:
// In your dashboard component
import { onSnapshot, collection, query, where, orderBy, limit } from 'firebase/firestore';
let notifications = [];
onMount(() => {
if ($authStore.user) {
const q = query(
collection(db, 'notifications'),
where('userId', '==', $authStore.user.uid),
where('read', '==', false),
orderBy('timestamp', 'desc'),
limit(5)
);
const unsubscribe = onSnapshot(q, (snapshot) => {
notifications = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
});
return unsubscribe;
}
});
Add functionality to export dashboard data:
function exportData() {
const data = {
activities: activities,
stats: stats,
exportDate: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `dashboard-data-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}
Add dark mode support:
import { writable } from 'svelte/store';
export const darkMode = writable(false);
// In your component
function toggleDarkMode() {
darkMode.update(mode => {
const newMode = !mode;
document.documentElement.setAttribute('data-bs-theme', newMode ? 'dark' : 'light');
localStorage.setItem('darkMode', newMode.toString());
return newMode;
});
}
⚠️ Common Issues & Solutions Authentication Errors:
- "User not found" -> Check if email is registered
- "Wrong password" -> Verify password is at least 6 characters
- "Network error" -> Check Firebase configuration and internet connection
Firestore Errors:
- "Permission denied" -> Update security rules for development
- "Document not found" -> Ensure collection names match exactly
- "Quota exceeded" -> Check Firebase usage limits
Component Errors:
- "Cannot read property of undefined" -> Add null checks for user data
- "Store not updating" -> Ensure proper subscription cleanup
- "Navigation not working" -> Check route file locations
npm run build
npm install -g firebase-tools
firebase login
firebase init hosting
firebase deploy
💡 Deployment Checklist Before deploying:
- Update Firebase security rules for production
- Set environment variables properly
- Test all authentication flows
- Verify responsive design on mobile
- Check console for any errors
ℹ️ Info Ready for More? Once you've completed the basic dashboard, try these advanced challenges to further develop your skills!
Add a complete user profile system:
Integrate Chart.js or D3.js:
Implement enhanced security features:
Add collaborative features:
Convert to a PWA:
This final project demonstrates:
The dashboard serves as a comprehensive example of modern web application development using SvelteKit and Firebase, incorporating all the concepts learned throughout the course.
💡 Final Advice Building this dashboard is a significant achievement! Take pride in creating a full-stack application with authentication, real-time data, and a polished UI. This project demonstrates skills that are highly valued in the industry. Keep iterating and adding features - the best way to learn is by building!
Final Project Dashboard (Code Review)
/* Note: No YouTube video found in git history for this project. Video needs to be recorded. */Video coming soon
Code with AI: Enhance your dashboard application.
Prompts:
✅ Complete authentication system working
✅ All dashboard components rendering properly
✅ Real-time data updates functioning
✅ Responsive design on all screen sizes
✅ No console errors in production build
✅ Successfully deployed to hosting platform
When you have completed your project, submit it using the link below:
Make sure to test your webpage before submitting to ensure all required elements are working properly!