Apply your knowledge to build something amazing!
:information_source: 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.
:bulb: 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!
:warning: Common Pitfall DO NOT DELETE the existing files in the template:
- Package files (package.json, package-lock.json)
- Configuration files (svelte.config.js, vite.config.js)
- Any other files you didn't create
ONLY EDIT the necessary files for your implementation.
:white_check_mark: SvelteKit project template loaded successfully
:white_check_mark: You can see the src folder structure
:white_check_mark: 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.
:warning: 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
:information_source: 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(login.svelte
) and a signup page (signup.svelte
) for authentication. Create the following files in the route folder.
:bulb: 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
login.svelte
{/* login.svelte */}
<script>
// Import necessary functions and modules from Firebase and Svelte
import {signInWithEmailAndPassword} from 'firebase/auth';
import {auth} from '../firebase';
import { goto } from '$app/navigation';
import { onDestroy } from 'svelte';
import authStore from '../stores/authStore';
// Declare variables to store user's email and password
let email = "";
let password = "";
let loading = false;
let error = "";
// Function to handle the login process when the form is submitted
async function login(){
loading = true;
error = "";
try {
// Attempt to log in the user using the provided email and password
await signInWithEmailAndPassword(auth, email, password);
} catch(err) {
// If an error occurs during login, show user-friendly error message
error = "Invalid email or password. Please try again.";
console.log(err);
}
loading = false;
}
// Subscribe to the authStore to check if the user is already logged in
const sub = authStore.subscribe(async (info) => {
// If the user is logged in, redirect to the home page
if (info.isLoggedIn) {
await goto('/');
}
});
// Unsubscribe from the authStore when the component is destroyed
onDestroy(() => {
sub();
});
</script>
{/* The following HTML code creates the login form */}
<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">
<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>
\{#if error\}
<div class="alert alert-danger" role="alert">
\{error\}
</div>
\{/if\}
<form on:submit|preventDefault=\{login\}>
{/* Input field for the user's email */}
<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>
{/* Input field for the user's password */}
<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>
{/* Login button to submit the form */}
<button
type="submit"
class="btn btn-primary btn-lg w-100 mb-3"
disabled=\{loading\}
>
\{loading ? 'Signing in...' : 'Sign In'\}
</button>
</form>
</div>
{/* Link to the registration page for new users */}
<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>
signup.svelte
{/* signup.svelte */}
<script>
// Import necessary functions and modules from Firebase and Svelte
import \{createUserWithEmailAndPassword\} from 'firebase/auth';
import \{auth\} from '../firebase';
import \{ goto \} from '$app/navigation';
import authStore from '../stores/authStore';
import \{ onDestroy \} from 'svelte';
// Declare variables to store user's email and password
let email = "";
let password = "";
let confirmPassword = "";
let loading = false;
let error = "";
// Function to handle the registration process when the form is submitted
async function register()\{
if (password !== confirmPassword) \{
error = "Passwords do not match.";
return;
\}
if (password.length < 6) \{
error = "Password must be at least 6 characters long.";
return;
\}
loading = true;
error = "";
try \{
// Attempt to create a new user account with the provided email and password
await createUserWithEmailAndPassword(auth, email, password);
\} catch(err) \{
// If an error occurs during registration, show user-friendly error message
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;
\}
// Subscribe to the authStore to check if the user is already logged in
const sub = authStore.subscribe(async (info) => \{
// If the user is already logged in, redirect to the dashboard
if (info.isLoggedIn) \{
await goto('/');
\}
\});
// Unsubscribe from the authStore when the component is destroyed
onDestroy(() => \{
sub();
\});
</script>
{/* The following HTML code creates the signup form */}
<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">
<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>
\{#if error\}
<div class="alert alert-danger" role="alert">
\{error\}
</div>
\{/if\}
<form on:submit|preventDefault=\{register\}>
{/* Input field for the user's email */}
<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>
{/* Input field for the user's password */}
<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>
{/* Input field for password confirmation */}
<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>
{/* Sign Up button to submit the form */}
<button
type="submit"
class="btn btn-success btn-lg w-100 mb-3"
disabled=\{loading\}
>
\{loading ? 'Creating Account...' : 'Create Account'\}
</button>
</form>
</div>
{/* Link to the login page 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>
:white_check_mark: Login page loads without errors
:white_check_mark: Signup page loads without errors
:white_check_mark: Forms accept user input properly
:white_check_mark: Error messages display when authentication fails
:white_check_mark: Successful login redirects to dashboard
Create a navigation component that will be used across the dashboard:
:bulb: 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>
import \{ signOut \} from 'firebase/auth';
import \{ auth \} from '../firebase';
import authStore from '../stores/authStore';
import \{ goto \} from '$app/navigation';
async function logout() \{
try \{
await signOut(auth);
$authStore.isLoggedIn = false;
$authStore.firebaseControlled = false;
await goto('/login');
\} catch (error) \{
console.error('Logout error:', error);
\}
\}
</script>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow-sm">
<div class="container">
<a class="navbar-brand fw-bold" href="/">
<i class="bi bi-speedometer2 me-2"></i>
My Dashboard
</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<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>
<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>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/profile">Profile Settings</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<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>
:information_source: Info Component Architecture We're building modular, reusable components. This approach makes your code:
Create reusable dashboard card components:
:bulb: 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>
:warning: 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';
import { collection, query, orderBy, limit, onSnapshot } from 'firebase/firestore';
import { db } from '../firebase';
import authStore from '../stores/authStore';
let activities = [];
onMount(() => {
if ($authStore.user) {
const q = query(
collection(db, 'activities'),
orderBy('timestamp', 'desc'),
limit(10)
);
const unsubscribe = onSnapshot(q, (snapshot) => {
activities = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
timestamp: doc.data().timestamp?.toDate()
}));
});
return unsubscribe;
}
});
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>
src/lib/QuickActions.svelte
<script>
import \{ addDoc, collection \} from 'firebase/firestore';
import \{ db \} from '../firebase';
import authStore from '../stores/authStore';
let showModal = false;
let actionTitle = '';
let actionDescription = '';
let loading = false;
async function addQuickAction() \{
if (!actionTitle.trim()) return;
loading = true;
try \{
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\}
:white_check_mark: Dashboard card component renders properly
:white_check_mark: Activity feed shows real-time updates
:white_check_mark: Quick actions modal opens and closes
:white_check_mark: Data successfully saves to Firestore
:white_check_mark: Components are responsive on mobile
:information_source: 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/index.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>
:bulb: 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;
:warning: 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);
:bulb: 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>
:white_check_mark: Dashboard displays user data correctly
:white_check_mark: Authentication redirects work properly
:white_check_mark: Logout functionality clears user session
:white_check_mark: Protected routes prevent unauthorized access
:white_check_mark: Real-time data updates without page refresh
:information_source: 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;
\});
\}
:warning: 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
:bulb: 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
:information_source: 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.
:bulb: 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) https://youtu.be/final-dashboard-review
Code with AI: Enhance your dashboard application.
Prompts:
:white_check_mark: Complete authentication system working
:white_check_mark: All dashboard components rendering properly
:white_check_mark: Real-time data updates functioning
:white_check_mark: Responsive design on all screen sizes
:white_check_mark: No console errors in production build
:white_check_mark: 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!