35 |
36 |
37 |
76 |
--------------------------------------------------------------------------------
/src/firebase/journeys.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Journeys Configuration for Code4U
3 | *
4 | * This file provides client-side functionality for journeyStore.js
5 | * to fetch and manage journey data from Firestore.
6 | */
7 |
8 | import { db, auth } from './index.js'
9 | import { collection, doc, getDocs, getDoc, query, where } from 'firebase/firestore'
10 |
11 | /**
12 | * Fetch available journeys based on user role
13 | * @param {boolean} includeUnpublished - Whether to include unpublished journeys (for admin UI)
14 | * @returns {Array} Array of journey objects
15 | */
16 | export async function fetchJourneys(includeUnpublished = false) {
17 | try {
18 | let journeysSnapshot;
19 | const currentUser = auth.currentUser;
20 |
21 | if (includeUnpublished && currentUser) {
22 | // For admin views - fetch all journeys
23 | journeysSnapshot = await getDocs(collection(db, 'journeys'));
24 | } else {
25 | // For public views - fetch only published journeys
26 | const journeysQuery = query(collection(db, 'journeys'), where('isPublished', '==', true));
27 | journeysSnapshot = await getDocs(journeysQuery);
28 | }
29 |
30 | return journeysSnapshot.docs.map(doc => ({
31 | id: doc.id,
32 | ...doc.data()
33 | })).sort((a, b) => a.order - b.order);
34 | } catch (error) {
35 | console.error('Error fetching journeys:', error);
36 | return [];
37 | }
38 | }
39 |
40 | /**
41 | * Fetch levels for a specific journey
42 | * @param {string} journeyId - ID of the journey
43 | * @returns {Array} Array of level objects sorted by number
44 | */
45 | export async function fetchJourneyLevels(journeyId) {
46 | try {
47 | const journey = await getDoc(doc(db, 'journeys', journeyId));
48 |
49 | if (!journey.exists()) {
50 | throw new Error(`Journey with ID ${journeyId} not found`);
51 | }
52 |
53 | const journeyData = journey.data();
54 |
55 | // If no levels in the journey, return empty array
56 | if (!journeyData.levelIds || !Array.isArray(journeyData.levelIds) || journeyData.levelIds.length === 0) {
57 | return [];
58 | }
59 |
60 | // Get all levels for this journey
61 | const levelsPromises = journeyData.levelIds.map(levelId =>
62 | getDoc(doc(db, 'levels', levelId))
63 | );
64 |
65 | const levelDocs = await Promise.all(levelsPromises);
66 |
67 | // Map to array of level objects
68 | const levels = levelDocs
69 | .filter(doc => doc.exists())
70 | .map(doc => ({
71 | id: doc.id,
72 | ...doc.data()
73 | }));
74 |
75 | // Sort levels by their number property
76 | return levels.sort((a, b) => a.number - b.number);
77 | } catch (error) {
78 | console.error(`Error fetching levels for journey ${journeyId}:`, error);
79 | return [];
80 | }
81 | }
82 |
83 | // Export all client-side journey functions
84 | export default {
85 | fetchJourneys,
86 | fetchJourneyLevels
87 | };
88 |
--------------------------------------------------------------------------------
/src/views/ActivitiesView.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
{{ pageTitle }}
22 |
{{ pageDescription }}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Activity Stats
32 |
33 |
34 |
🏆
35 |
Level Completions
36 |
See who's advancing
37 |
38 |
39 |
🎖️
40 |
Badges Earned
41 |
Recent achievements
42 |
43 |
44 |
👋
45 |
New Members
46 |
Welcome them!
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Community Guidelines
55 |
56 | At Code4U, we believe in celebrating everyone's learning journey!
57 | This activity feed showcases real-time achievements from our community of coders.
58 |
59 |
60 |
Celebrate others' achievements with positive encouragement
61 |
Everyone learns at their own pace - respect each journey
62 |
Use the activity feed as motivation for your own learning
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/firebase/analytics-utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Firebase Analytics utilities for Code4U
3 | * This file contains functions for tracking user interactions and educational metrics
4 | */
5 |
6 | import { logAnalyticsEvent } from './index';
7 |
8 | // User engagement events
9 | export const trackLogin = (method) => {
10 | logAnalyticsEvent('login', { method });
11 | };
12 |
13 | export const trackSignUp = (method) => {
14 | logAnalyticsEvent('sign_up', { method });
15 | };
16 |
17 | export const trackProfileUpdate = (fields) => {
18 | logAnalyticsEvent('profile_update', { fields });
19 | };
20 |
21 | // Learning path events
22 | export const trackPathSelected = (path) => {
23 | logAnalyticsEvent('learning_path_selected', { path });
24 | };
25 |
26 | // Level interactions
27 | export const trackLevelStarted = (levelId, levelNumber, category) => {
28 | logAnalyticsEvent('level_started', {
29 | level_id: levelId,
30 | level_number: levelNumber,
31 | category
32 | });
33 | };
34 |
35 | export const trackLevelCompleted = (levelId, levelNumber, category, timeSpentSeconds) => {
36 | logAnalyticsEvent('level_completed', {
37 | level_id: levelId,
38 | level_number: levelNumber,
39 | category,
40 | time_spent_seconds: timeSpentSeconds
41 | });
42 | };
43 |
44 | export const trackCodeSubmitted = (levelId, success) => {
45 | logAnalyticsEvent('code_submitted', {
46 | level_id: levelId,
47 | success
48 | });
49 | };
50 |
51 | export const trackHintViewed = (levelId, hintNumber) => {
52 | logAnalyticsEvent('hint_viewed', {
53 | level_id: levelId,
54 | hint_number: hintNumber
55 | });
56 | };
57 |
58 | // Badge and achievement events
59 | export const trackBadgeEarned = (badgeId, badgeName) => {
60 | logAnalyticsEvent('badge_earned', {
61 | badge_id: badgeId,
62 | badge_name: badgeName
63 | });
64 | };
65 |
66 | // Content interaction events
67 | export const trackContentView = (contentType, contentId) => {
68 | logAnalyticsEvent('content_view', {
69 | content_type: contentType,
70 | content_id: contentId
71 | });
72 | };
73 |
74 | // Page view tracking
75 | export const trackPageView = (pageName) => {
76 | logAnalyticsEvent('page_view', {
77 | page_name: pageName,
78 | page_location: window.location.href,
79 | page_path: window.location.pathname
80 | });
81 | };
82 |
83 | // Legal document events
84 | export const trackLegalDocumentView = (documentType) => {
85 | logAnalyticsEvent('legal_document_view', {
86 | document_type: documentType
87 | });
88 | };
89 |
90 | // Feature usage events
91 | export const trackFeatureUsed = (featureName) => {
92 | logAnalyticsEvent('feature_used', {
93 | feature_name: featureName
94 | });
95 | };
96 |
97 | // Error tracking
98 | export const trackError = (errorCode, errorMessage, context) => {
99 | logAnalyticsEvent('app_error', {
100 | error_code: errorCode,
101 | error_message: errorMessage,
102 | context
103 | });
104 | };
105 |
106 | // Custom conversion events
107 | export const trackMilestoneReached = (milestoneName) => {
108 | logAnalyticsEvent('milestone_reached', {
109 | milestone_name: milestoneName
110 | });
111 | };
112 |
--------------------------------------------------------------------------------
/src/components/TheWelcome.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Documentation
18 |
19 | Vue’s
20 | official documentation
21 | provides you with all information you need to get started.
22 |
23 |
24 |
25 |
26 |
27 |
28 | Tooling
29 |
30 | This project is served and bundled with
31 | Vite. The
32 | recommended IDE setup is
33 | VSCode
34 | +
35 | Vue - Official. If
36 | you need to test your components and web pages, check out
37 | Vitest
38 | and
39 | Cypress
40 | /
41 | Playwright.
42 |
43 |
44 |
45 | More instructions are available in
46 | README.md.
48 |
49 |
50 |
51 |
52 |
53 |
54 | Ecosystem
55 |
56 | Get official tools and libraries for your project:
57 | Pinia,
58 | Vue Router,
59 | Vue Test Utils, and
60 | Vue Dev Tools. If
61 | you need more resources, we suggest paying
62 | Awesome Vue
63 | a visit.
64 |
65 |
66 |
67 |
68 |
69 |
70 | Community
71 |
72 | Got stuck? Ask your question on
73 | Vue Land
74 | (our official Discord server), or
75 | StackOverflow. You should also follow the official
78 | @vuejs.org
79 | Bluesky account or the
80 | @vuejs
81 | X account for latest news in the Vue world.
82 |
83 |
84 |
85 |
86 |
87 |
88 | Support Vue
89 |
90 | As an independent project, Vue relies on community backing for its sustainability. You can help
91 | us by
92 | becoming a sponsor.
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/firebase/nodejs/import-level-data.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Firebase level data importer for Code4U
3 | * This script imports level data from JSON files in the /docs directory into Firestore
4 | */
5 |
6 | import { db } from './firebase-node.js'
7 | import { doc, writeBatch, getDoc } from 'firebase/firestore'
8 | import fs from 'fs'
9 | import path from 'path'
10 | import { fileURLToPath } from 'url'
11 |
12 | // Get the current file's directory
13 | const __filename = fileURLToPath(import.meta.url)
14 | const __dirname = path.dirname(__filename)
15 | const rootDir = path.resolve(__dirname, '../../../')
16 |
17 | // Path to JSON files
18 | const docsDir = path.join(rootDir, 'docs')
19 |
20 | // Files to import
21 | const levelFiles = [
22 | 'JavaScript-Levels.json',
23 | 'Python-Levels.json',
24 | 'CSS-Levels.json'
25 | ]
26 |
27 | /**
28 | * Import levels from JSON files to Firestore
29 | */
30 | async function importLevelsFromJson() {
31 | console.log('Starting level import process...')
32 |
33 | // Counter for statistics
34 | let totalLevels = 0
35 | let importedLevels = 0
36 | let skippedLevels = 0
37 | let failedLevels = 0
38 |
39 | try {
40 | // Process each JSON file
41 | for (const file of levelFiles) {
42 | const filePath = path.join(docsDir, file)
43 |
44 | // Check if file exists
45 | if (!fs.existsSync(filePath)) {
46 | console.log(`File not found: ${file}. Skipping.`)
47 | continue
48 | }
49 |
50 | // Read and parse the JSON file
51 | console.log(`\nReading ${file}...`)
52 | const jsonData = fs.readFileSync(filePath, 'utf8')
53 | const levels = JSON.parse(jsonData)
54 |
55 | totalLevels += levels.length
56 | console.log(`Found ${levels.length} levels in ${file}`)
57 |
58 | // Use batched writes for more efficient imports
59 | // Firestore allows up to 500 operations per batch
60 | const batchSize = 450
61 | let batchCount = 0
62 |
63 | for (let i = 0; i < levels.length; i += batchSize) {
64 | const batch = writeBatch(db)
65 | const currentBatch = levels.slice(i, i + batchSize)
66 | batchCount++
67 |
68 | console.log(`Processing batch ${batchCount} (${currentBatch.length} levels)`)
69 |
70 | for (const level of currentBatch) {
71 | // Check if the level already exists
72 | const levelDocRef = doc(db, 'levels', level.id)
73 | const levelDoc = await getDoc(levelDocRef)
74 |
75 | if (levelDoc.exists()) {
76 | console.log(`Level ${level.id} already exists. Skipping.`)
77 | skippedLevels++
78 | continue
79 | }
80 |
81 | // Add the level to the batch
82 | batch.set(levelDocRef, {
83 | ...level,
84 | createdAt: new Date(),
85 | isPublished: true // Default to published
86 | })
87 |
88 | importedLevels++
89 | }
90 |
91 | // Commit the batch
92 | await batch.commit()
93 | console.log(`Batch ${batchCount} committed successfully`)
94 | }
95 | }
96 |
97 | console.log('\n=== Import Summary ===')
98 | console.log(`Total levels found: ${totalLevels}`)
99 | console.log(`Levels imported: ${importedLevels}`)
100 | console.log(`Levels skipped (already exist): ${skippedLevels}`)
101 | console.log(`Failed imports: ${failedLevels}`)
102 | console.log('Import process complete!')
103 |
104 | } catch (error) {
105 | console.error('Error importing levels:', error)
106 | process.exit(1)
107 | }
108 | }
109 |
110 | // Run the import function
111 | importLevelsFromJson().then(() => {
112 | console.log('Script execution completed')
113 | process.exit(0)
114 | }).catch(error => {
115 | console.error('Script execution failed:', error)
116 | process.exit(1)
117 | })
118 |
--------------------------------------------------------------------------------
/src/components/CodePreview.vue:
--------------------------------------------------------------------------------
1 |
98 |
99 |
100 |
101 |
102 | {{ previewError }}
103 |
104 |
110 |
111 |
112 |
113 |
142 |
--------------------------------------------------------------------------------
/src/components/AdminBreadcrumbs.vue:
--------------------------------------------------------------------------------
1 |
2 |
68 |
69 |
70 |
128 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
102 |
--------------------------------------------------------------------------------
/src/router/adminGuard.js:
--------------------------------------------------------------------------------
1 | import { auth, db } from '../firebase'
2 | import { onAuthStateChanged } from 'firebase/auth'
3 | import { doc, getDoc } from 'firebase/firestore'
4 |
5 | // Keep track of auth state initialization
6 | let authInitialized = false
7 | let currentUser = null
8 |
9 | // Initialize auth state tracking once
10 | onAuthStateChanged(auth, (user) => {
11 | currentUser = user
12 | authInitialized = true
13 | console.log('Auth state changed in adminGuard:', user ? 'User logged in' : 'No user')
14 | })
15 |
16 | /**
17 | * Navigation guard to check if a user has admin role
18 | * This guard will redirect non-admin users to the home page
19 | */
20 | export const requireAdmin = (to, from, next) => {
21 | console.log('Admin guard running, auth initialized:', authInitialized)
22 |
23 | // If auth isn't initialized yet, wait briefly before checking
24 | if (!authInitialized) {
25 | console.log('Auth not initialized, waiting...')
26 | // Create a function to wait for auth initialization
27 | const waitForAuthInit = () => {
28 | if (authInitialized) {
29 | checkAdminStatus(currentUser, to, next)
30 | } else {
31 | // Still not initialized, wait a bit longer
32 | setTimeout(waitForAuthInit, 50)
33 | }
34 | }
35 | // Start waiting
36 | waitForAuthInit()
37 | return
38 | }
39 |
40 | // Auth is already initialized, check admin status
41 | checkAdminStatus(currentUser, to, next)
42 | }
43 |
44 | /**
45 | * Navigation guard to check if a user has creator or admin role
46 | * This guard will redirect regular users to the home page
47 | */
48 | export const requireCreatorOrAdmin = (to, from, next) => {
49 | console.log('Creator guard running, auth initialized:', authInitialized)
50 |
51 | // If auth isn't initialized yet, wait briefly before checking
52 | if (!authInitialized) {
53 | console.log('Auth not initialized, waiting...')
54 | // Create a function to wait for auth initialization
55 | const waitForAuthInit = () => {
56 | if (authInitialized) {
57 | checkCreatorOrAdminStatus(currentUser, to, next)
58 | } else {
59 | // Still not initialized, wait a bit longer
60 | setTimeout(waitForAuthInit, 50)
61 | }
62 | }
63 | // Start waiting
64 | waitForAuthInit()
65 | return
66 | }
67 |
68 | // Auth is already initialized, check creator status
69 | checkCreatorOrAdminStatus(currentUser, to, next)
70 | }
71 |
72 | /**
73 | * Helper function to check if user has admin role
74 | */
75 | async function checkAdminStatus(user, to, next) {
76 | // First check if the user is authenticated
77 | if (!user) {
78 | console.log('No authenticated user, redirecting to login')
79 | next({ name: 'login', query: { redirect: to.fullPath } })
80 | return
81 | }
82 |
83 | try {
84 | console.log('Checking admin status for user:', user.uid)
85 | // Get the user document and check for admin role
86 | const userDoc = await getDoc(doc(db, 'users', user.uid))
87 |
88 | if (userDoc.exists() && userDoc.data().role === 'admin') {
89 | // User is an admin, allow access
90 | console.log('User is admin, allowing access')
91 | next()
92 | } else {
93 | // User is not an admin, redirect to home
94 | console.warn('Non-admin user attempted to access admin page')
95 | next({ name: 'home' })
96 | }
97 | } catch (error) {
98 | console.error('Error checking admin status:', error)
99 | next({ name: 'home' })
100 | }
101 | }
102 |
103 | /**
104 | * Helper function to check if user has creator or admin role
105 | */
106 | async function checkCreatorOrAdminStatus(user, to, next) {
107 | // First check if the user is authenticated
108 | if (!user) {
109 | console.log('No authenticated user, redirecting to login')
110 | next({ name: 'login', query: { redirect: to.fullPath } })
111 | return
112 | }
113 |
114 | try {
115 | console.log('Checking creator/admin status for user:', user.uid)
116 | // Get the user document and check for creator or admin role
117 | const userDoc = await getDoc(doc(db, 'users', user.uid))
118 |
119 | if (userDoc.exists() && (userDoc.data().role === 'admin' || userDoc.data().role === 'creator')) {
120 | // User is a creator or admin, allow access
121 | console.log('User is creator or admin, allowing access')
122 | next()
123 | } else {
124 | // User is not a creator or admin, redirect to home
125 | console.warn('Non-creator/admin user attempted to access creator page')
126 | next({ name: 'home' })
127 | }
128 | } catch (error) {
129 | console.error('Error checking creator/admin status:', error)
130 | next({ name: 'home' })
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
114 |
--------------------------------------------------------------------------------
/docs/LEVEL.md:
--------------------------------------------------------------------------------
1 | # Level Data Definition
2 |
3 | This document defines the structure of the data used to represent a Level consisting of multiple tasks.
4 |
5 | ---
6 |
7 | ## Level Structure
8 |
9 | - The curriculum is an **array** of **Level** objects.
10 | - Each **Level** contains metadata about the learning module and an array of **Tasks**.
11 | - Tasks represent interactive coding exercises within a level.
12 |
13 | ---
14 |
15 | ## Level Object
16 |
17 | Each **Level** object has the following fields:
18 |
19 | | Field Name | Type | Description |
20 | |----------------------|----------------------------|----------------------------------------------------------------------------------------------------|
21 | | `id` | `string` | Unique identifier for the level (e.g., `"level-11"`) |
22 | | `number` | `number` | Numeric sequence/order of the level |
23 | | `title` | `string` | The title of the level/module |
24 | | `description` | `string` | A brief summary describing the purpose and content of the level |
25 | | `category` | `string` | The subject category this level belongs to (e.g., `"JavaScript"`) |
26 | | `difficulty` | `string` | Difficulty rating (e.g., `"Beginner"`, `"Intermediate"`, `"Advanced"`) |
27 | | `pointsToEarn` | `number` | Number of points awarded for completing this level |
28 | | `estimatedTime` | `string` | Approximate time to complete the level (e.g., `"45 minutes"`) |
29 | | `prerequisites` | `string[]` | Array of prerequisite topics or prior levels the learner should know before starting this level |
30 | | `learningObjectives` | `string[]` | Array of learning goals that the learner should achieve by completing the level |
31 | | `realWorldApplications` | `string[]` | Examples of practical uses or applications of the concepts taught in the level |
32 | | `references` | `{ title: string, url: string }[]` | Array of reference resources, each with a title and URL for further study |
33 | | `tags` | `string[]` | Keywords or tags for categorizing and searching levels |
34 | | `tasks` | `Task[]` | Array of tasks (interactive coding exercises) contained in the level |
35 |
36 | ---
37 |
38 | ## Task Object
39 |
40 | Each **Task** object within a level includes:
41 |
42 | | Field Name | Type | Description |
43 | |--------------------|------------------|-------------------------------------------------------------------------------------------------|
44 | | `id` | `string` | Unique identifier for the task within the level (e.g., `"task1"`) |
45 | | `title` | `string` | Title of the coding exercise |
46 | | `description` | `string` | Detailed instructions describing what the learner needs to do |
47 | | `initialCode` | `string` | Starter code provided to the learner as a base for their solution |
48 | | `solution` | `string` | Partial code or hint representing the expected solution or key part of it |
49 | | `expectedOutput` | `string` | Message or output expected upon successful completion of the task |
50 | | `errorHint` | `string` | Helpful hints or suggestions to guide learners if their solution is incorrect |
51 |
52 | ---
53 |
54 | ## Example: Level and Task (Simplified)
55 |
56 | ```json
57 | {
58 | "id": "level-11",
59 | "number": 11,
60 | "title": "JavaScript Basics: Start Coding!",
61 | "description": "Begin your journey into programming! Learn the building blocks of JavaScript including variables, data types, and simple operations.",
62 | "category": "JavaScript",
63 | "difficulty": "Beginner",
64 | "pointsToEarn": 300,
65 | "estimatedTime": "45 minutes",
66 | "prerequisites": ["HTML Fundamentals"],
67 | "learningObjectives": [
68 | "Understand what JavaScript is and why it's important",
69 | "Learn about variables and different data types",
70 | "Use basic operators to perform calculations"
71 | ],
72 | "realWorldApplications": [
73 | "Creating interactive elements on your website",
74 | "Building simple calculators and tools",
75 | "Making decisions in your programs"
76 | ],
77 | "references": [
78 | {
79 | "title": "MDN Web Docs: JavaScript First Steps",
80 | "url": "https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps"
81 | },
82 | {
83 | "title": "W3Schools JavaScript Tutorial",
84 | "url": "https://www.w3schools.com/js/js_intro.asp"
85 | }
86 | ],
87 | "tags": ["JavaScript", "Programming", "Variables", "Data Types"],
88 | "tasks": [
89 | {
90 | "id": "task1",
91 | "title": "Create your first variables",
92 | "description": "Variables are like containers that store information in your program. Create variables for name, age, and isStudent using the appropriate data types (string, number, and boolean).",
93 | "initialCode": "// Create your variables below\n// Example: let color = \"blue\";\n\n// Your code here:\n",
94 | "solution": "let name",
95 | "expectedOutput": "Variables created successfully!",
96 | "errorHint": "You need to declare variables using let or const. Try: let name = \"Alex\"; let age = 14; let isStudent = true;"
97 | }
98 | ]
99 | }
100 |
--------------------------------------------------------------------------------
/src/firebase/firestore.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 | service cloud.firestore {
3 | match /databases/{database}/documents {
4 | // Helper functions
5 | function isSignedIn() {
6 | return request.auth != null;
7 | }
8 |
9 | function isOwner(userId) {
10 | return isSignedIn() && request.auth.uid == userId;
11 | }
12 |
13 | // Helper function to check if user is an admin
14 | function isAdmin() {
15 | return isSignedIn() &&
16 | exists(/databases/$(database)/documents/users/$(request.auth.uid)) &&
17 | get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
18 | }
19 |
20 | // Helper function to check if user is a creator
21 | function isCreator() {
22 | return isSignedIn() &&
23 | exists(/databases/$(database)/documents/users/$(request.auth.uid)) &&
24 | get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'creator';
25 | }
26 |
27 | // Helper function to check if user is an admin or creator
28 | function isAdminOrCreator() {
29 | return isAdmin() || isCreator();
30 | }
31 |
32 | // Helper function to check if user is the creator of a document
33 | function isDocumentCreator(docData) {
34 | return isSignedIn() && docData.createdBy == request.auth.uid;
35 | }
36 |
37 | // User profiles - public read access for leaderboard functionality
38 | match /users/{userId} {
39 | // Allow public read access for all users to support leaderboard
40 | allow read: if true;
41 |
42 | // But only allow users to create/update their own data
43 | allow create: if isSignedIn() && request.auth.uid == userId;
44 | allow update: if isSignedIn() && (
45 | // Normal users can only update specific fields
46 | (request.auth.uid == userId &&
47 | request.resource.data.diff(resource.data).affectedKeys()
48 | .hasOnly(['displayName', 'photoURL', 'level', 'points', 'badges', 'completedLevels', 'lastLogin', 'isFirstLogin'])) ||
49 | // Admins can update any user
50 | isAdmin()
51 | );
52 | allow delete: if false;
53 | }
54 |
55 | // Level data - filtered by isPublished for regular users, admins and creators can modify, only admins can delete
56 | match /levels/{levelId} {
57 | // Allow everyone (including unauthenticated users) to read published levels
58 | allow read: if request.auth == null || resource.data.isPublished == true || isAdmin() ||
59 | (isCreator() && resource.data.createdBy == request.auth.uid);
60 | allow create: if isAdminOrCreator(); // Admins and creators can create levels
61 | allow update: if isAdmin() || (isCreator() && resource.data.createdBy == request.auth.uid); // Admins can edit any level, creators can only edit their own
62 | allow delete: if isAdmin(); // Only admins can delete levels
63 | }
64 |
65 | // User activities - public read access for activity feed, restricted write
66 | match /user_activities/{activityId} {
67 | allow read: if true; // Public read access for activity feed
68 | allow create: if isSignedIn() && request.resource.data.userId == request.auth.uid;
69 | allow update, delete: if isAdmin(); // Allow admins to update or delete activities
70 | }
71 |
72 | // Badges - read-only for all authenticated users
73 | match /badges/{badgeId} {
74 | allow read: if true;
75 | allow create: if isAdminOrCreator(); // Admins and creators can create badges
76 | allow update: if isAdmin() || (isCreator() && resource.data.createdBy == request.auth.uid); // Admins can edit any badge, creators can only edit their own
77 | allow delete: if isAdmin(); // Only admins can delete badges
78 | }
79 |
80 | // Journeys - read filtered by isPublished, admins and creators can write, only admins can delete
81 | match /journeys/{journeyId} {
82 | // Allow everyone (including unauthenticated users) to read published journeys
83 | allow read: if request.auth == null || resource.data.isPublished == true || isAdmin() ||
84 | (isCreator() && resource.data.createdBy == request.auth.uid);
85 | allow create: if isAdminOrCreator(); // Admins and creators can create journeys
86 | allow update: if isAdmin() || (isCreator() && resource.data.createdBy == request.auth.uid); // Admins can edit any journey, creators can only edit their own
87 | allow delete: if isAdmin(); // Only admins can delete journeys
88 | }
89 |
90 | // Leaderboard data - public read access
91 | match /leaderboard/{entryId} {
92 | allow read: if true; // Allow everyone to read leaderboard data
93 | allow write: if isAdmin(); // Allow admins to update leaderboard
94 | }
95 |
96 | // Feedback collection - allow users to submit and read their own feedback, admins can read all
97 | match /feedback/{feedbackId} {
98 | allow read: if isSignedIn() && (
99 | resource.data.userId == request.auth.uid || // User can read their own feedback
100 | isAdmin() // Admins can read all feedback
101 | );
102 | allow create: if true; // Allow both signed-in and anonymous feedback
103 | allow update: if isAdmin(); // Admins can update feedback status
104 | allow delete: if isAdmin(); // Admins can delete feedback if needed
105 | }
106 |
107 | // Feedback comments - allow admins to manage, users to read their own
108 | match /feedback_comments/{commentId} {
109 | allow read: if isSignedIn() && (
110 | exists(/databases/$(database)/documents/feedback/$(resource.data.feedbackId)) &&
111 | (
112 | get(/databases/$(database)/documents/feedback/$(resource.data.feedbackId)).data.userId == request.auth.uid || // User can read comments on their feedback
113 | isAdmin() // Admins can read all comments
114 | )
115 | );
116 | allow create: if isSignedIn() && (
117 | request.resource.data.authorId == request.auth.uid || // User creating comment with their ID
118 | isAdmin() // Admins can create comments
119 | );
120 | allow update, delete: if isAdmin(); // Only admins can update or delete comments
121 | }
122 |
123 | // Legal content - public read access, admin-only write access
124 | match /legal_content/{documentId} {
125 | allow read: if true; // Allow everyone to read legal documents
126 | allow write: if isAdmin(); // Allow admins to update legal content
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/views/LoginView.vue:
--------------------------------------------------------------------------------
1 |
96 |
97 |
98 |
99 |
100 |
Welcome to Code4U!
101 |
102 |
103 |
104 | {{ errorMessage }}
105 |
106 |
107 |
108 |
109 |
110 |
111 |
119 |
120 |
121 |
125 |
126 |
127 |
128 |
129 |
130 |
150 |
151 |
152 | By signing in, you'll create an account if you don't already have one.
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/CREATOR.md:
--------------------------------------------------------------------------------
1 | # Code4U Creator Documentation
2 |
3 | This guide is designed for content creators developing educational material for the Code4U platform.
4 |
5 | ## Table of Contents
6 |
7 | 1. [Introduction](#introduction)
8 | 2. [Creator Role Access](#creator-role-access)
9 | 3. [Content Structure](#content-structure)
10 | 4. [Solution Checking](#solution-checking)
11 | 5. [Creating Effective Levels](#creating-effective-levels)
12 | 6. [Journey Organization](#journey-organization)
13 | 7. [Badge Creation](#badge-creation)
14 | 8. [Content Management Workflow](#content-management-workflow)
15 |
16 | ## Introduction
17 |
18 | Code4U is an interactive educational web application designed to teach middle and high school students web development fundamentals through a gamified learning experience. As a creator, you'll be developing content that engages students while teaching them valuable coding skills in HTML, CSS, and JavaScript.
19 |
20 | ## Creator Role Access
21 |
22 | As a creator, you have the following permissions:
23 |
24 | - Create and edit your own journeys, levels, and badges
25 | - Manage the visibility of your content through the `isPublished` field
26 | - View analytics for your created content
27 | - Organize your content into meaningful learning paths
28 |
29 | Note that creators cannot permanently delete content - instead, use the `isPublished` toggle to hide content that should not be publicly visible.
30 |
31 | ## Content Structure
32 |
33 | The platform organizes content into a hierarchy:
34 |
35 | ```
36 | Journeys
37 | └── Levels
38 | └── Tasks
39 | ```
40 |
41 | - **Journeys**: Collections of related levels forming a complete learning path
42 | - **Levels**: Individual coding challenges with specific learning objectives
43 | - **Tasks**: Step-by-step exercises within a level that build toward the learning objective
44 |
45 | ## Solution Checking
46 |
47 | ### How Solutions are Verified
48 |
49 | Code4U uses pattern matching to verify student solutions. Each task has a `solution` field containing a string that must be present in the student's code to be considered correct.
50 |
51 | ```javascript
52 | // From gameStore.js
53 | function runCode(code) {
54 | if (!currentLevel.value || !currentLevel.value.tasks) return false
55 |
56 | const task = currentLevel.value.tasks[currentTask.value]
57 | userCode.value = code
58 |
59 | // Check if the solution pattern exists in the student's code
60 | if (code.includes(task.solution)) {
61 | codeOutput.value = task.expectedOutput
62 | return true
63 | } else {
64 | codeOutput.value = 'Error: ' + task.errorHint
65 | return false
66 | }
67 | }
68 | ```
69 |
70 | ### Task Structure
71 |
72 | When creating a task, include the following fields:
73 |
74 | | Field | Description | Example |
75 | |-------|-------------|---------|
76 | | `id` | Unique identifier for the task | `"task1"` |
77 | | `title` | Short, descriptive title | `"Create a simple if statement"` |
78 | | `description` | Clear instructions for the student | `"Write a program that checks if a user is old enough (13 or older)."` |
79 | | `initialCode` | Starter code for the student | `"let age = 12;\nlet message = \"\";\n// Your code here:"` |
80 | | `solution` | Pattern to check for in student's answer | `"if (age >= 13)"` |
81 | | `expectedOutput` | Success message when correct | `"Conditional statement created successfully!"` |
82 | | `errorHint` | Helpful hint when incorrect | `"Use an if/else statement to check if age is greater than or equal to 13."` |
83 |
84 | ### Solution Pattern Best Practices
85 |
86 | 1. **Be specific but flexible**: Choose a solution pattern that must be present in a correct answer but allows for variations in implementation
87 |
88 | 2. **Focus on key concepts**: Your solution pattern should verify the student understands the core concept being taught
89 |
90 | 3. **Consider edge cases**: Think about different ways students might correctly solve the problem
91 |
92 | 4. **Multiple solutions**: If there are multiple valid approaches, consider using several solution patterns and checking if any match
93 |
94 | 5. **Avoid overly strict checking**: Don't require exact whitespace or formatting matches
95 |
96 | ## Creating Effective Levels
97 |
98 | ### Level Structure
99 |
100 | Each level includes:
101 |
102 | - Core metadata (ID, number, title, description)
103 | - Category (HTML, CSS, or JavaScript)
104 | - Difficulty rating
105 | - Points awarded on completion
106 | - Learning objectives
107 | - Real-world applications
108 | - Reference materials
109 | - Tags for searchability
110 | - A series of tasks that build toward the learning objective
111 |
112 | ### Best Practices
113 |
114 | 1. **Clear progression**: Design levels that build on previous knowledge
115 | 2. **Engaging context**: Provide real-world scenarios for applying coding concepts
116 | 3. **Scaffolded learning**: Start with guided examples and gradually reduce support
117 | 4. **Clear success criteria**: Make it obvious what students need to accomplish
118 | 5. **Helpful feedback**: Provide specific guidance when students make mistakes
119 |
120 | ## Journey Organization
121 |
122 | Journeys are collections of levels that form a complete learning path. When creating a journey:
123 |
124 | 1. Group related levels that build toward a cohesive skill set
125 | 2. Provide a clear description of what students will learn
126 | 3. Set appropriate difficulty and prerequisites
127 | 4. Include varied content to maintain engagement
128 | 5. Culminate with a project or challenge that integrates all skills
129 |
130 | ## Badge Creation
131 |
132 | Badges are awarded for achievements within the platform. As a creator, you can define badges that are awarded for:
133 |
134 | 1. Completing specific levels
135 | 2. Finishing entire categories (HTML, CSS, JavaScript)
136 | 3. Achieving special milestones
137 |
138 | Each badge needs:
139 | - A unique identifier
140 | - A descriptive name
141 | - Category classification
142 | - Icon representation
143 | - Requirements for earning
144 |
145 | ## Content Management Workflow
146 |
147 | ### Creating New Content
148 |
149 | 1. **Plan your content**: Define learning objectives, tasks, and progression
150 | 2. **Draft in the admin interface**: Create your journeys, levels, and tasks
151 | 3. **Test thoroughly**: Verify all solutions work as expected
152 | 4. **Review and polish**: Refine descriptions, hints, and examples
153 | 5. **Publish when ready**: Toggle the `isPublished` field to make content available
154 |
155 | ### Updating Existing Content
156 |
157 | 1. Only published content is visible to regular users
158 | 2. You can toggle publication status at any time
159 | 3. Make content changes and test thoroughly before republishing
160 |
161 | ### Collaboration Considerations
162 |
163 | 1. While you can only edit your own content, you can view others' published content for inspiration
164 | 2. Organize your content with clear naming conventions
165 | 3. Use tags effectively to help users discover your content
166 |
167 | ---
168 |
169 | By following these guidelines, you'll create engaging, effective learning experiences that help students develop their coding skills within the Code4U platform.
170 |
--------------------------------------------------------------------------------
/src/firebase/storage-utils.js:
--------------------------------------------------------------------------------
1 | import { storage } from './index.js';
2 | import { ref as storageRef, uploadBytes, getDownloadURL, deleteObject } from 'firebase/storage';
3 |
4 | /**
5 | * Resize an image to a maximum width/height while maintaining aspect ratio
6 | * @param {File|Blob} file - The image file to resize
7 | * @param {number} maxSize - The maximum width or height in pixels
8 | * @returns {Promise} - A promise that resolves with the resized image blob
9 | */
10 | const resizeImage = (file, maxSize = 256) => {
11 | return new Promise((resolve, reject) => {
12 | // Create a FileReader to read the image
13 | const reader = new FileReader();
14 | reader.onload = (event) => {
15 | // Create an image element to load the file
16 | const img = new Image();
17 | img.onload = () => {
18 | // Calculate new dimensions while maintaining aspect ratio
19 | let width = img.width;
20 | let height = img.height;
21 |
22 | if (width > height) {
23 | if (width > maxSize) {
24 | height = Math.round(height * (maxSize / width));
25 | width = maxSize;
26 | }
27 | } else {
28 | if (height > maxSize) {
29 | width = Math.round(width * (maxSize / height));
30 | height = maxSize;
31 | }
32 | }
33 |
34 | // Create a canvas to draw the resized image
35 | const canvas = document.createElement('canvas');
36 | canvas.width = width;
37 | canvas.height = height;
38 |
39 | // Draw the resized image on the canvas
40 | const ctx = canvas.getContext('2d');
41 | ctx.drawImage(img, 0, 0, width, height);
42 |
43 | // Convert the canvas to a Blob
44 | canvas.toBlob((blob) => {
45 | resolve(blob);
46 | }, file.type || 'image/jpeg', 0.85); // 0.85 quality is a good balance
47 | };
48 | img.onerror = () => {
49 | reject(new Error('Failed to load image for resizing'));
50 | };
51 | img.src = event.target.result;
52 | };
53 | reader.onerror = () => {
54 | reject(new Error('Failed to read file for image resizing'));
55 | };
56 | reader.readAsDataURL(file);
57 | });
58 | };
59 |
60 | /**
61 | * Upload a profile picture to Firebase Storage
62 | * @param {string} userId - The user ID to use for the storage path
63 | * @param {File|Blob} file - The file to upload
64 | * @returns {Promise} - The download URL of the uploaded image
65 | */
66 | export const uploadProfilePicture = async (userId, file) => {
67 | try {
68 | // Resize the image to 256px
69 | let fileToUpload;
70 |
71 | // Only resize if we're in a browser environment
72 | if (typeof document !== 'undefined') {
73 | try {
74 | fileToUpload = await resizeImage(file, 256);
75 | console.log('Image resized successfully to 256px');
76 | } catch (resizeError) {
77 | console.warn('Failed to resize image, using original:', resizeError);
78 | fileToUpload = file; // Fallback to original if resizing fails
79 | }
80 | } else {
81 | fileToUpload = file; // Use original in non-browser environments
82 | }
83 |
84 | // Create a reference to the file in Firebase Storage
85 | const profilePicRef = storageRef(storage, `profile-pictures/${userId}/profile-image`);
86 |
87 | // Upload the file
88 | const snapshot = await uploadBytes(profilePicRef, fileToUpload);
89 |
90 | // Get and return the download URL
91 | const downloadURL = await getDownloadURL(snapshot.ref);
92 | return downloadURL;
93 | } catch (error) {
94 | console.error('Error uploading profile picture:', error);
95 |
96 | // If this is a CORS error or any storage error, return a temporary URL
97 | // that works as a placeholder until the proper configuration is set up
98 | if (error.code === 'storage/unauthorized' || error.message?.includes('CORS')) {
99 | console.warn('CORS issue detected. Using local placeholder image.');
100 |
101 | // Create a data URL from the file (works in the browser without CORS issues)
102 | return new Promise((resolve) => {
103 | const reader = new FileReader();
104 | reader.onloadend = () => resolve(reader.result);
105 | reader.readAsDataURL(file);
106 | });
107 | }
108 |
109 | throw error;
110 | }
111 | };
112 |
113 | /**
114 | * Upload a profile picture from an external URL to Firebase Storage
115 | * @param {string} userId - The user ID to use for the storage path
116 | * @param {string} imageUrl - The URL of the image to download and upload
117 | * @returns {Promise} - The download URL of the uploaded image
118 | */
119 | export const uploadProfilePictureFromUrl = async (userId, imageUrl) => {
120 | try {
121 | // For Google profile pictures, we should use a proxy or modify the URL
122 | // to avoid CORS issues. Google profile pictures typically come from
123 | // lh3.googleusercontent.com or similar domains
124 |
125 | // Option 1: Just return the original URL to bypass CORS issues
126 | // We can't upload the image directly due to CORS, so we'll just use the original URL
127 | return imageUrl;
128 |
129 | // Note: In a production environment, you would typically:
130 | // 1. Use a server-side proxy to download the image and then upload it to Firebase
131 | // 2. Or use a CORS proxy service (though this has security implications)
132 | // 3. Or set up proper CORS headers on your Firebase Storage bucket
133 |
134 | /* Commented out the problematic code:
135 | // Fetch the image from the URL
136 | const response = await fetch(imageUrl);
137 | if (!response.ok) {
138 | throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
139 | }
140 |
141 | // Convert response to blob
142 | const imageBlob = await response.blob();
143 |
144 | // Upload the blob to Firebase Storage
145 | return await uploadProfilePicture(userId, imageBlob);
146 | */
147 | } catch (error) {
148 | console.error('Error uploading profile picture from URL:', error);
149 | throw error;
150 | }
151 | };
152 |
153 | /**
154 | * Delete a profile picture from Firebase Storage
155 | * @param {string} userId - The user ID to delete the picture for
156 | * @returns {Promise}
157 | */
158 | export const deleteProfilePicture = async (userId) => {
159 | try {
160 | // Create a reference to the file in Firebase Storage
161 | const profilePicRef = storageRef(storage, `profile-pictures/${userId}/profile-image`);
162 |
163 | // Delete the file
164 | await deleteObject(profilePicRef);
165 | } catch (error) {
166 | console.error('Error deleting profile picture:', error);
167 |
168 | // If this is a CORS error or an unauthorized error, don't throw the error
169 | // This allows the rest of the profile update to continue even if storage access fails
170 | if (error.code === 'storage/unauthorized' || error.code === 'storage/object-not-found' || error.message?.includes('CORS')) {
171 | console.warn('CORS issue or file not found. Continuing with profile update.');
172 | return;
173 | }
174 |
175 | throw error;
176 | }
177 | };
178 |
--------------------------------------------------------------------------------
/src/views/JourneyDetailView.vue:
--------------------------------------------------------------------------------
1 |
78 |
79 |
80 |
The journey you're looking for doesn't exist or has been removed.
184 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/src/firebase/legal-utils.js:
--------------------------------------------------------------------------------
1 | import { db } from './index'
2 | import { doc, getDoc } from 'firebase/firestore'
3 |
4 | // Collection name for legal documents
5 | const LEGAL_COLLECTION = 'legal_content'
6 |
7 | // Default content for Terms of Service - copied here to avoid circular imports
8 | const DEFAULT_TERMS = {
9 | title: 'Terms of Service',
10 | content: [
11 | {
12 | heading: 'Acceptance of Terms',
13 | text: 'By accessing and using the Code4U platform, you agree to be bound by these Terms of Service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site.'
14 | },
15 | {
16 | heading: 'Use License',
17 | text: 'Permission is granted to temporarily use the Code4U platform for personal, educational, and non-commercial purposes only. This is the grant of a license, not a transfer of title, and under this license you may not: modify or copy the materials except as required for normal platform usage; use the materials for any commercial purpose; attempt to decompile or reverse engineer any software; remove any copyright or other proprietary notations; transfer the materials to another person or "mirror" the materials on any other server.'
18 | },
19 | {
20 | heading: 'User Accounts',
21 | text: 'To access certain features of the platform, you must create an account. You are responsible for maintaining the confidentiality of your account information and for all activities that occur under your account. You agree to immediately notify Code4U of any unauthorized use of your account or any other breach of security.'
22 | },
23 | {
24 | heading: 'User Content',
25 | text: 'Any code or content you submit, post, or display on or through Code4U is your responsibility. You retain ownership of your content, but grant Code4U a worldwide, royalty-free license to use, copy, reproduce, process, adapt, modify, publish, transmit, display, and distribute such content for educational and platform improvement purposes.'
26 | },
27 | {
28 | heading: 'Disclaimer',
29 | text: 'The materials on the Code4U platform are provided on an \'as is\' basis. Code4U makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.'
30 | },
31 | {
32 | heading: 'Limitations',
33 | text: 'In no event shall Code4U or its suppliers be liable for any damages arising out of the use or inability to use the materials on the platform, even if Code4U or an authorized representative has been notified orally or in writing of the possibility of such damage.'
34 | },
35 | {
36 | heading: 'Governing Law',
37 | text: 'These terms and conditions are governed by and construed in accordance with local laws, and you irrevocably submit to the exclusive jurisdiction of the courts in that location.'
38 | },
39 | {
40 | heading: 'Changes to Terms',
41 | text: 'Code4U reserves the right, at its sole discretion, to modify or replace these Terms at any time. It is your responsibility to check these Terms periodically for changes. Your continued use of the platform following the posting of any changes constitutes acceptance of those changes.'
42 | }
43 | ],
44 | lastUpdated: new Date().toISOString().split('T')[0]
45 | }
46 |
47 | // Default content for Privacy Policy - copied here to avoid circular imports
48 | const DEFAULT_PRIVACY = {
49 | title: 'Privacy Policy',
50 | content: [
51 | {
52 | heading: 'Introduction',
53 | text: 'At Code4U, we take your privacy seriously. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our platform.'
54 | },
55 | {
56 | heading: 'Information We Collect',
57 | text: 'We may collect information about you in various ways, including: Personal Data (name, email address, and profile picture obtained through Google authentication), Usage Data (information on how you interact with our platform, including completed levels, code solutions, and achievement records), and Technical Data (IP address, browser type, device information, and cookies to improve your experience).'
58 | },
59 | {
60 | heading: 'How We Use Your Information',
61 | text: 'We may use the information we collect about you for various purposes: to provide and maintain our platform, to personalize your experience, to improve our platform, to track your progress and achievements, to communicate with you, and to ensure the security of our platform.'
62 | },
63 | {
64 | heading: 'Storage and Protection',
65 | text: 'Your data is stored securely in Firebase, including Firebase Authentication, Firestore, and Firebase Storage. We implement measures designed to protect your information from unauthorized access, alteration, disclosure, or destruction.'
66 | },
67 | {
68 | heading: 'Data Sharing',
69 | text: 'We do not sell, trade, or otherwise transfer your personal information to third parties without your consent, except as described in this Privacy Policy or as required by law.'
70 | },
71 | {
72 | heading: 'Third-Party Services',
73 | text: 'We use Google services for authentication. Google may collect information as governed by their privacy policy. We encourage you to review Google\'s privacy practices.'
74 | },
75 | {
76 | heading: 'Your Rights',
77 | text: 'You have the right to access, update, or delete your personal information. You can manage your profile through the platform\'s profile settings or contact us for assistance.'
78 | },
79 | {
80 | heading: 'Children\'s Privacy',
81 | text: 'Our platform is not intended for children under 13 years of age. We do not knowingly collect personal information from children under 13.'
82 | },
83 | {
84 | heading: 'Changes to This Privacy Policy',
85 | text: 'We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date.'
86 | },
87 | {
88 | heading: 'Contact Us',
89 | text: 'If you have any questions about this Privacy Policy, please contact us at privacy@Code4U.example.com.'
90 | }
91 | ],
92 | lastUpdated: new Date().toISOString().split('T')[0]
93 | }
94 |
95 | /**
96 | * Fetch legal document content from Firestore
97 | * @param {string} documentId - 'terms_of_service' or 'privacy_policy'
98 | * @returns {Promise