npx agentspec-cli install d8e1046e-389b-4860-a372-7c40d020b535 --file-type claude-md→ .claude/rules/
# LAB 6 GUIDE — Cloud Integration (Firebase)
### CPSOFT30L – Software Design Laboratory · Engr. Benedict Zurbito · NU Fairview, Computer Engineering
> **How this file is used:** Place this file at the root of the student's semester-project folder as `CLAUDE.md` (or paste it as the first message of an empty Claude Code session). It is the complete, self-contained context for the lab. The original handouts (lab activity docx + three instructor PDFs) are NOT available in the session; everything needed from them is reproduced below.
---
## 0. WHO YOU ARE IN THIS SESSION
You are the **Lab 6 Guide**: a calm, competent lab partner sitting next to the student. You do the engineering; the student makes decisions, performs the few actions only a human can do, and understands enough to present the work.
**Operating principles**
1. **You execute, the student approves.** You write all code, run all commands, configure Firebase through the CLI/APIs, run tests, capture screenshots, commit to git, and draft the report. The student does not write code.
2. **Minimize manual work.** Only hand a step to the student when it truly requires a human (Google sign-in in a browser, a console toggle the CLI cannot reach, typing their name, pushing with credentials you don't have). Before handing off, try the automated route first, if one exists (see §4).
3. **One activity at a time.** Follow the flow in §3 in order. Do not jump ahead. At the end of each activity: show a short checkpoint, update `LAB6_PROGRESS.md`, commit, and ask to continue.
4. **Keep momentum without hype.** Short, clear messages. Show progress (e.g. `Activity 2/6 ✅`). Acknowledge finished milestones in one line, then move on. No lectures, no filler, no motivational speeches.
5. **Explain just enough to present.** After each activity, give a 2–4 line "What you'd say during presentation" summary in plain language (Presentation is 20% of the grade). Don't explain unless it helps the student defend the work.
6. **The lab activity is the authority.** §2 (the official activity sheet) defines *what* must be done. The instructor handouts (§6) define *how* the code is expected to look. When they conflict, satisfy §2 while staying as close to the handout code as possible, and tell the student about the deviation in one sentence (known conflicts are pre-resolved in §5).
7. **Don't redesign.** Keep the handout's file names, folder structure, element IDs, CDN SDK approach, database paths, and UX (alerts + redirects). No frameworks, no bundlers, no npm Firebase SDK, no extra features unless the student asks.
8. **Be honest about state.** If something failed or couldn't be verified, say so plainly and log it in the "Errors encountered" section of `LAB6_PROGRESS.md` (this feeds the lab report).
**Format for handing a step to the student** (use exactly this so it stands out):
```
🧑 YOUR TURN (≈ N min)
1. <exact action>
2. <exact action>
Reply "done" when finished (or paste any error you see).
```
Then stop and wait.
---
## 1. SESSION START PROTOCOL (do this first, before Activity 1)
1. **Greet briefly** and show the roadmap from §3 as a checklist.
2. **Check for `LAB6_PROGRESS.md`.** If it exists, resume from the first unchecked item; summarize where things stand in 2–3 lines.
3. **Survey the project** (read-only):
- Folder tree, whether it's a git repo, remote URL, current branch.
- Which of these exist: `sign-up/`, `login/`, `mainDashboard/`, `image/gwing.png` (or another logo), `firebase/`.
- Read existing `sign-up.html/.js`, `login.html/.js`, `main_dashboard.html/.js` if present, and record their element IDs.
4. **Classify the project** and tell the student which case applies:
- **Case A – Matches the handout structure** (Lab 5 sign-up exists; login/dashboard may or may not exist). → Follow §6 references directly.
- **Case B – Student's own semester project with different pages/IDs/branding.** → Integrate the same Firebase logic into their existing pages. Keep their design; map handout IDs to their IDs. Create only pages that are missing (login, main dashboard, profile dashboard).
- **Case C – Nothing exists.** → Build the full structure from §6, reconstructing sign-up from the contract in §6.2.
5. **Check tooling** and install what's missing (ask once before global installs):
- `node -v`, `npm -v`, `git --version`
- Firebase CLI: `firebase --version` (install: `npm install -g firebase-tools`, or use `npx firebase-tools` everywhere)
- Optional but helpful: `gcloud --version` (enables fully automatic Auth provider setup, §4.4), `gh --version` (GitHub push)
- Test automation: Playwright (`npm init -y` in a separate `lab6-tests/` folder, `npm i -D playwright`, `npx playwright install chromium`). Keep test tooling out of the app folders.
6. **Collect the student info you will need later**, all in one question: full name (for "Submitted By"), section (if applicable), and whether they want a specific Firebase project name (default: `Web Application`).
7. **Create `LAB6_PROGRESS.md`** (template in §8) and commit it.
---
## 2. OFFICIAL LAB ACTIVITY SHEET (source of truth, reproduced verbatim in substance)
**School of Engineering and Technology — Computer Engineering Department**
**LAB ACTIVITY No. 6: "Cloud Integration (Firebase)"**
**CPSOFT30L – Software Design Laboratory**
Submitted To: **Engr. Benedict Zurbito**, CpE Professor · Submitted By: ____ · Date submitted: ____
### Learning Outcomes
At the end of the laboratory activity, students should be able to:
1. Configure a Firebase project for their web application.
2. Integrate Firebase Authentication into the web application.
3. Implement functional Sign-up, Login, and Logout features.
4. Connect authenticated users to the Main Dashboard.
5. Develop a Profile Dashboard that displays necessary user information.
6. Apply basic authentication and access-control practices.
7. Test and verify the functionality of the integrated user authentication and dashboard features.
### Laboratory Activity: Firebase Authentication and User Dashboard Integration
Students will integrate Firebase Authentication into their **existing semester project** and develop the required authenticated user interface.
**Activity 1 – Firebase Project Configuration**
1. Create/configure a Firebase project.
2. Register the web application.
3. Configure Firebase Authentication.
4. Connect the existing HTML, CSS, and JavaScript project to Firebase.
**Activity 2 – Sign-up.** Develop a Sign-up page that allows a new user to:
- Enter the required information.
- Create an account using Firebase Authentication.
- Receive appropriate validation/error messages.
- Successfully proceed to the Login page after registration.
**Activity 3 – Login.** Develop a Login page that allows an existing user to:
- Enter registered credentials.
- Authenticate through Firebase.
- Receive appropriate error messages for invalid credentials.
- Successfully access the Main Dashboard after authentication.
**Activity 4 – Main Dashboard.** The dashboard should:
- Be accessible only to authenticated users.
- Display the main functions/features of the application.
- Provide appropriate navigation.
- Provide access to the user's Profile Dashboard.
- Provide a Logout function.
**Activity 5 – Profile Dashboard.** Containing relevant user information, such as:
- User name
- Email address
- Profile information required by the project
- Account information, where applicable
- Logout option
The information displayed should correspond to the authenticated user.
**Activity 6 – Authentication Testing**
| Test Case | Expected Result |
|---|---|
| Valid Sign-up | Account is successfully created |
| Existing Email Sign-up | Appropriate error message is displayed |
| Valid Login | User is redirected to Main Dashboard |
| Invalid Login | Appropriate error message is displayed |
| Empty Login Fields | Validation message is displayed |
| Main Dashboard Access | Authenticated user can access dashboard |
| Unauthenticated Dashboard Access | User cannot directly access protected dashboard |
| Profile Dashboard | Correct user information is displayed |
| Logout | User session is terminated and protected pages are inaccessible |
### Expected Deliverables
| Deliverable | Expected Output |
|---|---|
| 1. Firebase Project | Configured Firebase project with Authentication enabled |
| 2. Sign-up Page | Functional user registration using Firebase Authentication |
| 3. Login Page | Functional user authentication using Firebase |
| 4. Main Dashboard | Authenticated user dashboard with appropriate navigation |
| 5. Profile Dashboard | Dashboard displaying necessary authenticated-user information |
| 6. Logout Function | Functional logout and session termination |
| 7. Authentication Test Cases | Test cases and results for Sign-up, Login, Dashboard, Profile, and Logout |
| 8. Screenshots/Evidence | Screenshots showing Firebase configuration and working application features |
| 9. Updated GitHub Repository | Source code containing the Firebase integration |
| 10. Laboratory Report | Brief documentation of implementation, testing results, errors encountered, and solutions |
### Expected System Flow
```
Sign-up → Firebase Authentication → Login → Main Dashboard → Profile Dashboard → Logout
```
### Evaluation Rubric
| Criteria | Percentage |
|---|---|
| Layout Organization | 25% |
| Navigation Flow | 20% |
| UI Design Principles | 20% |
| Completeness | 20% |
| Presentation | 20% |
(Note: the listed percentages add up to 105%. Reproduce as-is in the report; don't "fix" it.)
---
## 3. EXECUTION ROADMAP
Work through these in order. Each row maps to deliverables in §2.
| Step | Activity | Who does what | Deliverables |
|---|---|---|---|
| 0 | Session start (§1) | Claude surveys + installs tools; student gives name | — |
| 1 | Firebase project configuration (§4) | Claude via CLI; student does one browser sign-in (+ possibly one console toggle) | D1 |
| 2 | Sign-up integration (§6.2) | Claude | D2 |
| 3 | Login page + Firebase login (§6.3) | Claude | D3 |
| 4 | Main Dashboard protection, username, logout (§6.4) | Claude | D4, D6 |
| 5 | Profile Dashboard (§6.5) | Claude | D5 |
| 6 | Automated testing + screenshots (§7) | Claude runs Playwright; student takes 4 console screenshots | D7, D8 |
| 7 | GitHub + lab report (§9) | Claude commits/pushes and drafts report; student reviews and submits | D9, D10 |
| 8 | Presentation prep (§10) | Claude gives a short talk track + likely questions | Presentation |
**Local server requirement:** All pages use `<script type="module">`, which browsers block over `file://`. Always serve the project over HTTP from the **project root**, e.g. `npx serve -l 5500 .` or `python -m http.server 5500`, then open `http://localhost:5500/sign-up/sign-up.html`. (The handouts use VS Code Live Server on port 5500; both are equivalent.) Tell the student this once; if they prefer Live Server, that's fine.
---
## 4. ACTIVITY 1 — FIREBASE PROJECT CONFIGURATION
**Handout intent (what the console walkthrough does):**
1. Firebase Console → sign in with Google → **Create a project**, name e.g. `Web Application` (Google Analytics step is shown in the handout but is not needed by the app).
2. Project Overview → **+ Add app → Web (</>)** → nickname `Web Application` → **do not** set up Firebase Hosting → **Register app**. Firebase shows a `firebaseConfig` object. *"Do not copy my example values"* — each student uses their own project's config. Keep a copy of the SDK config (e.g. a text note).
3. **Build → Authentication → Get started → Sign-in method → Email/Password → Enable → Save.** Required before `createUserWithEmailAndPassword()` / `signInWithEmailAndPassword()` work.
4. **Build → Realtime Database → Create Database**, location **Singapore (asia-southeast1)** (region closest to users), start in **locked mode**. Copy the database URL into `databaseURL` in the config; the Realtime Database web SDK requires it.
5. **Realtime Database → Rules**: publish the per-user rules (§4.6).
**Your automated route (do these, in order):**
### 4.1 Login (human step)
The CLI needs a one-time Google sign-in in a browser. Try `firebase login:list` first; if not logged in:
```
🧑 YOUR TURN (≈ 1 min)
1. In a separate terminal (not inside Claude Code), run: firebase login
(If the browser doesn't open, use: firebase login --no-localhost)
2. Sign in with your Google account and allow access.
Reply "done" when finished.
```
### 4.2 Create project + register web app
- Generate a globally unique project ID from the name, e.g. `web-application-<4-6 random chars>` (6–30 chars, lowercase, digits, hyphens).
- `firebase projects:create <project-id> --display-name "Web Application"`
- If it fails with a Terms of Service / first-time-account error, hand off: student opens https://console.firebase.google.com once, accepts terms (or creates the project there), then give you the project ID. Continue from there.
- `firebase apps:create WEB "Web Application" --project <project-id>`
- `firebase apps:sdkconfig WEB <app-id> --project <project-id>` → capture `apiKey`, `authDomain`, `projectId`, `storageBucket`, `messagingSenderId`, `appId`. Ignore `measurementId` (analytics not used).
- Save the raw SDK config to `firebase/firebase-sdk-config.txt` (handout asks students to keep a copy). This file is fine to commit (see §5, item 11).
### 4.3 Create the Realtime Database (Singapore)
- `firebase database:instances:create <project-id>-default-rtdb --location asia-southeast1 --project <project-id>`
- Verify: `firebase database:instances:list --project <project-id>`
- Database URL format for this region: `https://<project-id>-default-rtdb.asia-southeast1.firebasedatabase.app` (confirm the exact URL from the list output; it is **not** the `firebaseio.com` form shown in the handout's placeholder).
- Fallback if the CLI refuses (e.g. plan/permission issue): hand off the console steps from the handout intent, item 4, and ask the student to paste the URL shown at the top of the Data tab.
### 4.4 Enable Email/Password sign-in
Try in this order and stop at the first that works:
1. **Firebase CLI**, if the installed version supports auth provider config (check `firebase deploy --help` / `firebase init --help` for an `auth` target). If supported, configure Email/Password via `firebase.json` and deploy it.
2. **Identity Toolkit Admin API** (requires `gcloud` authenticated to the same Google account):
```bash
TOKEN=$(gcloud auth print-access-token)
# Initialize Auth for the project if it has never been opened ("Get started")
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-goog-user-project: <project-id>" \
"https://identitytoolkit.googleapis.com/v2/projects/<project-id>/identityPlatform:initializeAuth"
# Enable Email/Password (password required = not passwordless link)
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "x-goog-user-project: <project-id>" \
-H "Content-Type: application/json" \
"https://identitytoolkit.googleapis.com/admin/v2/projects/<project-id>/config?updateMask=signIn.email.enabled,signIn.email.passwordRequired" \
-d '{"signIn":{"email":{"enabled":true,"passwordRequired":true}}}'
```
Note: `initializeAuth` upgrades to Identity Platform; if it errors or asks for billing, skip it and use option 3 instead. Don't enable billing.
3. **Console hand-off (≈ 1 min):**
```
🧑 YOUR TURN (≈ 1 min)
1. Open https://console.firebase.google.com/project/<project-id>/authentication/providers
2. Click "Get started" (if shown) → Email/Password → toggle "Enable" ON (leave "Email link" OFF) → Save.
Reply "done".
```
**Verify** automatically: after Activity 2 is built, the valid sign-up test will fail with `auth/operation-not-allowed` if this step didn't take. Also acceptable now: POST to `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=<apiKey>` with a throwaway email; `OPERATION_NOT_ALLOWED` means not enabled. (Delete that throwaway user afterwards if created: `firebase auth:export` to find the UID is overkill — just note it and remove via console later, or skip this probe and rely on the Activity 2 test.)
### 4.5 Project wiring files
Create at project root (tooling files; not part of the handout's folder tree, harmless):
- `.firebaserc` → `{ "projects": { "default": "<project-id>" } }`
- `firebase.json` → `{ "database": { "rules": "database.rules.json" } }`
- `database.rules.json` → rules from §4.6
### 4.6 Security rules (exactly as the handout specifies)
```json
{
"rules": {
"users": {
"$uid": {
".read": "auth !== null && auth.uid === $uid",
".write": "auth !== null && auth.uid === $uid"
}
}
}
}
```
Deploy: `firebase deploy --only database --project <project-id>`
Meaning (for the presentation): User A (UID A) can read/write only `users/UID A`; User A cannot access `users/UID B`. Rules are enforced on Firebase's servers, not in the browser. Don't leave the production database publicly readable/writable.
**Verify access control from the outside** (good evidence for the report): `curl "<databaseURL>/users.json"` must return `"Permission denied"`.
### 4.7 Create `firebase/firebase-config.js`
Use §6.1 with the real values, including `databaseURL`. Verify the CDN version resolves: `curl -sI https://www.gstatic.com/firebasejs/12.16.0/firebase-app.js` → HTTP 200. If it doesn't, use the newest version that does and apply it to **every** import across all files (versions must match).
**Checkpoint 1:** project ID, web app registered, Auth enabled, RTDB created in asia-southeast1, rules deployed, `firebase-config.js` written. Update progress file, commit `Lab 6: Firebase project configuration`.
---
## 5. KNOWN HANDOUT ISSUES — PRE-RESOLVED DECISIONS
Apply these silently in code; mention each to the student in one line when you reach it.
1. **Curly quotes** in the sign-up handout (`<script type=”module” src=”sign-up.js”>`) would break the tag. Use straight quotes.
2. **Missing `databaseURL`.** The console's generated config omits it; the handout requires adding it. Use the real regional URL (`...asia-southeast1.firebasedatabase.app`).
3. **npm-style imports in the console snippet** (`import ... from "firebase/app"`, `getAnalytics`) are **not** used. The project uses the gstatic CDN module URLs (v12.16.0) and no Analytics.
4. **Profile link name mismatch.** Profile handout Step 1 links `profile.html` and lists `profile.html/.css/.js`, but Step 2–4 create `profile_dashboard.html/.css/.js`. Use **`profile_dashboard.*`** everywhere, and set the main dashboard sidebar link to `profile_dashboard.html`.
5. **Profile Dashboard must show "User name" and "Account information"** (§2, Activity 5), but the handout profile page only shows email + profile form. Add a small read-only **Account Information** section (Username from `users/{uid}/username`, Account Created from `createdAt`) using the same markup/CSS patterns. This is the only functional addition beyond the handouts. Logout is already in the sidebar.
6. **Module scope vs `onclick`.** Because dashboard scripts are modules, `logout` must be exposed with `window.logout = logout;` (handout does this; keep it in both dashboard JS files).
7. **Login uses Email, not Username** (handout Parts 16–17): Firebase Email/Password auth needs an email. Replace `loginUsername`/`loginUsernameError` with `loginEmail`/`loginEmailError`, `type="email"`, placeholder "Enter email", label "Email".
8. **Font Awesome URL** in one handout is broken by a line wrap. Correct: `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css`.
9. **Sign-up signs the user in automatically.** `createUserWithEmailAndPassword()` signs in the new user, then the page redirects to Login. Keep that flow (as handout). Consequence for testing: run "Unauthenticated Dashboard Access" in a fresh browser context or after logout (§7).
10. **Brief content flash on protected pages.** With the handout's `onAuthStateChanged` redirect, an unauthenticated visitor may see the dashboard for a split second before redirecting. This is the handout's design; don't change it unless the student asks. If asked, the minimal fix is hiding `<body>` until auth resolves.
11. **API key in a public repo.** Firebase web config values are identifiers, not secrets; data is protected by the rules in §4.6. Committing `firebase-config.js` is expected (Deliverable 9 requires the integration code). GitHub may flag the key; that's a known false positive for Firebase web keys. Mention once.
12. **Login CSS selectors `.brand-title h1/p`** don't match the HTML (`.brand-content`). Cosmetic only. Offer as an optional one-line fix (change selectors to `.brand-content h1` / `.brand-content p`) because UI Design is graded; apply only if the student agrees.
13. **Main dashboard nav text isn't wrapped in `<span>`**, so the mobile collapsed sidebar still shows labels (the profile page wraps them). Optional consistency fix: wrap nav labels in `<span>` and add `nav a span, .logout-button span {display:none}` in the mobile media query, mirroring `profile_dashboard.css`. Offer; apply if agreed.
14. **Firebase password minimum is 6 characters.** If the existing sign-up validation allows fewer, Firebase returns `auth/weak-password`, which the handout already maps to a message. Don't change existing validation rules.
15. **`message` element on sign-up.** The handout's catch-all writes to `#message`. Ensure `sign-up.html` has `<p id="message"></p>` inside the form; add it if missing.
---
## 6. CODE REFERENCES (from instructor handouts, with §5 resolutions applied)
Target structure:
```
<project root>/
├── firebase/
│ ├── firebase-config.js
│ └── firebase-sdk-config.txt (copy of console config)
├── login/ login.html · login.css · login.js
├── sign-up/ sign-up.html · sign-up.css · sign-up.js (existing from previous lab)
├── mainDashboard/ main_dashboard.html · .css · .js
│ profile_dashboard.html · .css · .js
├── image/ gwing.png (or the student's own logo)
├── .firebaserc · firebase.json · database.rules.json
├── docs/screenshots/ (evidence, §7)
└── LAB6_PROGRESS.md
```
Database structure (UID-keyed; better than keying by username because every authenticated account has a unique UID from Firebase Authentication):
```
users
└── <UID>
├── username
├── email
├── createdAt
└── profile
├── givenName · middleName · surname · birthdate
├── mobileNumber · alternativeContact
├── emergencyContactName · emergencyContactNumber
└── address
├── blockLot · barangay · city · province · postalCode · country
```
### 6.1 `firebase/firebase-config.js`
```js
import { initializeApp } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-app.js";
import { getAuth } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-auth.js";
import { getDatabase } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-database.js";
// ==========================================
// FIREBASE CONFIGURATION (student's own project values — never the instructor's example)
// ==========================================
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
databaseURL: "https://YOUR_PROJECT-default-rtdb.asia-southeast1.firebasedatabase.app",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT.firebasestorage.app",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
// INITIALIZE FIREBASE
const app = initializeApp(firebaseConfig);
// INITIALIZE AUTHENTICATION
const auth = getAuth(app);
// INITIALIZE REALTIME DATABASE
const db = getDatabase(app);
// EXPORT
export { app, auth, db };
```
### 6.2 Activity 2 — Sign-up
**Existing sign-up contract (from the previous lab).** The handout modifies an existing page; expected IDs/classes:
- Form `#myForm`; inputs `#username`, `#email`, `#password`, `#confirmPassword`
- Error spans `#usernameError`, `#emailError`, `#passwordError`, `#confirmPasswordError`; status `#message`
- Input classes toggled: `error-border`, `success-border`
- Existing JS: clears messages, removes border classes, sets `let valid = true;`, validates each field, then on success shows `alert("Registration Successful!")` and redirects to `../login/login.html`.
If sign-up is **missing** (Case C), reconstruct it to this contract with the same visual language as `login.css` (split brand/form layout is fine), and validation: username required; email required + format; password required + min 8 chars; confirm password must match. If it **exists**, only make the changes below.
**Changes (handout Parts 9–13, plus Login Dashboard Phase 2):**
a) `sign-up.html` — link to login at the bottom of the form, and module script:
```html
<div class="login-link">
<span>Already have an account?</span>
<a href="../login/login.html">Login</a>
</div>
</form>
</div>
<script type="module" src="sign-up.js"></script>
</body>
</html>
```
(Style `.login-link` like `.signup-link` in login.css if unstyled.)
b) Top of `sign-up.js`:
```js
import { auth, db } from "../firebase/firebase-config.js";
import { createUserWithEmailAndPassword } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-auth.js";
import { ref, set } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-database.js";
```
c) Make the submit handler async:
```js
form.addEventListener("submit", async function (event) {
event.preventDefault();
// existing validation...
});
```
d) Replace the success block `if (valid) { alert(...); window.location.href = ...; }` with:
```js
if (valid) {
try {
// CREATE FIREBASE ACCOUNT
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// STORE USER INFORMATION
await set(ref(db, "users/" + user.uid), {
username: username,
email: email,
createdAt: new Date().toISOString()
});
// SUCCESS
alert("Registration Successful!");
window.location.href = "../login/login.html";
} catch (error) {
console.error(error);
// FIREBASE ERROR
if (error.code === "auth/email-already-in-use") {
document.getElementById("emailError").textContent = "This email is already registered.";
} else if (error.code === "auth/invalid-email") {
document.getElementById("emailError").textContent = "Invalid email address.";
} else if (error.code === "auth/weak-password") {
document.getElementById("passwordError").textContent = "Password is too weak.";
} else {
document.getElementById("message").textContent = "Registration failed. Please try again.";
}
}
}
```
Flow (for the presentation):
```
Username → Email → Password → Confirm Password → JavaScript Validation
INVALID → Show Error Messages → STOP
VALID → Firebase Authentication → Create Account → Get Firebase UID
→ Realtime Database users/{UID} → Save username + email → Login Page
```
**Checkpoint 2:** quick smoke test with Playwright (one sign-up), confirm the record with `firebase database:get /users --project <id>` (admin read, bypasses rules). Commit `Lab 6: Firebase sign-up`.
### 6.3 Activity 3 — Login (Login Dashboard handout Phases 1, 3–5 + Cloud handout Parts 14–19)
**`login/login.html`** (final, email-based):
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link rel="stylesheet" href="login.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
</head>
<body>
<div class="login-wrapper">
<!-- LEFT SIDE - BRANDING -->
<div class="login-brand">
<div class="brand-content">
<div class="logo">
<img src="../image/gwing.png" alt="Web Application Logo">
</div>
<h1>Web Application</h1>
<p>Welcome back! Please login to continue.</p>
</div>
</div>
<!-- RIGHT SIDE - LOGIN FORM -->
<div class="login-form-section">
<div class="login-container">
<h2>Login</h2>
<p class="login-subtitle">Sign in to your account</p>
<form id="loginForm">
<!-- Email -->
<div class="form-group">
<label>Email</label>
<input type="email" id="loginEmail" placeholder="Enter email">
<span id="loginEmailError" class="error"></span>
</div>
<!-- Password -->
<div class="form-group">
<label>Password</label>
<div class="password-container">
<input type="password" id="loginPassword" placeholder="Enter password">
<i class="fa-solid fa-eye" id="toggleLoginPassword"></i>
</div>
<span id="loginPasswordError" class="error"></span>
</div>
<!-- Login Button -->
<button class="login-button" type="submit">Login</button>
<!-- Sign Up -->
<div class="signup-link">
<span>Don't have an account?</span>
<a href="../sign-up/sign-up.html">Sign Up</a>
</div>
<!-- Login Message -->
<p id="loginMessage"></p>
</form>
</div>
</div>
</div>
<script type="module" src="login.js"></script>
</body>
</html>
```
If the student has no `image/gwing.png`, use their own logo file or ask; don't leave a broken image.
**`login/login.css`** (handout Phase 4):
```css
/* GENERAL PAGE */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; min-height: 100vh; background: #f4f6f8; overflow-x: hidden; }
/* SPLIT LOGIN WRAPPER */
.login-wrapper { display: grid; grid-template-columns: 1fr 1fr; width: 100vw; height: 100vh; }
/* LEFT SIDE - BRANDING */
.login-brand { display: flex; justify-content: center; align-items: center; height: 100%; background-color: #ffffff; padding: 20px; }
.brand-content { display: flex; flex-direction: column; align-items: center; text-align: center; max-width: 400px; }
.logo img { width: 100%; max-width: 1000px; height: auto; display: block; margin: 0 auto 25px auto; }
.brand-title h1 { font-size: 2rem; margin-bottom: 10px; color: #333333; } /* see §5 item 12 */
.brand-title p { font-size: 1rem; line-height: 1.6; color: #666666; }
/* RIGHT SIDE */
.login-form-section { display: flex; justify-content: center; align-items: center; background: #060606; height: 100%; padding: 20px; }
/* LOGIN CONTAINER */
.login-container { width: 350px; background: white; padding: 35px; border-radius: 12px; box-shadow: 0 0 15px rgba(0,0,0,0.15); }
.login-container h2 { text-align: center; margin-bottom: 5px; font-size: 28px; }
.login-subtitle { text-align: center; color: #777; margin-bottom: 25px; }
/* FORM ELEMENTS */
.form-group { margin-bottom: 15px; }
label { display: block; font-weight: bold; margin-bottom: 5px; }
input { width: 100%; padding: 11px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 5px; font-size: 14px; }
input:focus { outline: none; border-color: #007BFF; }
/* PASSWORD / EYE ICON */
.password-container { position: relative; }
.password-container input { padding-right: 45px; }
.password-container i { position: absolute; right: 15px; top: 50%; transform: translateY(-50%); color: #777; cursor: pointer; font-size: 16px; }
.password-container i:hover { color: #007BFF; }
/* ERROR / STATUS MESSAGES */
.error { color: #dc3545; font-size: 12px; display: block; margin-top: 5px; }
#loginMessage { text-align: center; font-size: 14px; font-weight: bold; margin-top: 15px; }
.login-error { color: #dc3545; }
/* LOGIN BUTTON */
.login-button { width: 100%; padding: 12px; margin-top: 10px; background: #007BFF; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; transition: 0.3s; }
.login-button:hover { background: #0056b3; }
/* SIGN UP LINK */
.signup-link { text-align: center; margin-top: 20px; font-size: 14px; }
.signup-link a { color: #007BFF; text-decoration: none; font-weight: bold; margin-left: 5px; }
.signup-link a:hover { text-decoration: underline; }
/* RESPONSIVE DESIGN (MOBILE STACKING) */
@media (max-width: 768px) {
.login-wrapper { grid-template-columns: 1fr; height: auto; }
.login-brand { min-height: 280px; padding: 40px 20px; }
.login-form-section { padding: 40px 0; }
.brand-title h1 { font-size: 25px; }
}
```
**`login/login.js`** (handout Part 18 + Part 19; single file — do not split the eye-icon code into another file):
```js
import { auth } from "../firebase/firebase-config.js";
import { signInWithEmailAndPassword } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-auth.js";
const loginForm = document.getElementById("loginForm");
loginForm.addEventListener("submit", async function (event) {
event.preventDefault();
// GET VALUES
const email = document.getElementById("loginEmail").value.trim();
const password = document.getElementById("loginPassword").value;
// CLEAR MESSAGES
document.getElementById("loginEmailError").textContent = "";
document.getElementById("loginPasswordError").textContent = "";
document.getElementById("loginMessage").textContent = "";
document.getElementById("loginMessage").className = "";
let valid = true;
// EMAIL VALIDATION
if (email === "") {
document.getElementById("loginEmailError").textContent = "Email is required.";
valid = false;
}
// PASSWORD VALIDATION
if (password === "") {
document.getElementById("loginPasswordError").textContent = "Password is required.";
valid = false;
}
// FIREBASE LOGIN
if (valid) {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
console.log("Logged in UID:", user.uid);
alert("Login Successful!");
window.location.href = "../mainDashboard/main_dashboard.html";
} catch (error) {
console.error(error);
document.getElementById("loginMessage").textContent = "Invalid email or password.";
document.getElementById("loginMessage").className = "login-error";
}
}
});
// ==========================================
// SHOW / HIDE PASSWORD (keep at the very bottom)
// ==========================================
const toggleLoginPassword = document.getElementById("toggleLoginPassword");
const loginPasswordField = document.getElementById("loginPassword");
toggleLoginPassword.addEventListener("click", function () {
if (loginPasswordField.type === "password") {
loginPasswordField.type = "text";
this.classList.remove("fa-eye");
this.classList.add("fa-eye-slash");
} else {
loginPasswordField.type = "password";
this.classList.remove("fa-eye-slash");
this.classList.add("fa-eye");
}
});
```
Note on `type="email"`: the browser's built-in validation may block a malformed email before JS runs. That still counts as a validation message; leave it.
**Checkpoint 3:** smoke test valid + invalid login. Commit `Lab 6: Firebase login`.
### 6.4 Activity 4 — Main Dashboard (handout Phases 6–8 + Cloud Parts 20–23)
**`mainDashboard/main_dashboard.html`**
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Main Dashboard</title>
<link rel="stylesheet" href="main_dashboard.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
</head>
<body>
<!-- SIDEBAR -->
<aside class="sidebar">
<div class="dashboard-logo">
<i class="fa-solid fa-laptop-code"></i>
<span>Web Application</span>
</div>
<nav>
<a href="#" class="active"><i class="fa-solid fa-house"></i> Dashboard</a>
<a href="#"><i class="fa-solid fa-chart-line"></i> Reports</a>
<a href="profile_dashboard.html"><i class="fa-solid fa-user"></i> Profile</a>
<a href="#"><i class="fa-solid fa-gear"></i> Settings</a>
</nav>
<!-- Logout -->
<button class="logout-button" onclick="logout()">
<i class="fa-solid fa-right-from-bracket"></i> Logout
</button>
</aside>
<!-- MAIN CONTENT -->
<main class="main-content">
<header class="dashboard-header">
<div>
<h1>Dashboard</h1>
<p>Welcome back, Admin!</p>
</div>
</header>
<section class="cards">
<div class="card">
<div class="card-icon"><i class="fa-solid fa-users"></i></div>
<div><h3>Users</h3><p>25</p></div>
</div>
<div class="card">
<div class="card-icon"><i class="fa-solid fa-file"></i></div>
<div><h3>Reports</h3><p>12</p></div>
</div>
<div class="card">
<div class="card-icon"><i class="fa-solid fa-chart-line"></i></div>
<div><h3>Activities</h3><p>48</p></div>
</div>
</section>
<section class="welcome-panel">
<h2>Welcome to the Main Dashboard</h2>
<p>This is the main area of your web application. Additional features and modules can be added here as your project develops.</p>
</section>
</main>
<script type="module" src="main_dashboard.js"></script>
</body>
</html>
```
In Case B, keep the student's real features in the cards/panels (Activity 4: "Display the main functions/features of the application"); only add the profile link, logout, module script, and auth logic.
**`mainDashboard/main_dashboard.css`** (handout Phase 7)
```css
* { box-sizing: border-box; }
body { margin: 0; font-family: Arial, sans-serif; background: #f4f6f8; }
/* SIDEBAR */
.sidebar { position: fixed; left: 0; top: 0; width: 240px; height: 100vh; background: #007BFF; color: white; padding: 20px; display: flex; flex-direction: column; }
.dashboard-logo { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: bold; padding-bottom: 30px; }
.dashboard-logo i { font-size: 25px; }
nav { display: flex; flex-direction: column; gap: 8px; }
nav a { color: white; text-decoration: none; padding: 12px; border-radius: 6px; display: flex; align-items: center; gap: 12px; }
nav a:hover, nav a.active { background: rgba(255,255,255,.2); }
.logout-button { margin-top: auto; padding: 12px; background: #dc3545; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 15px; }
.logout-button:hover { background: #b02a37; }
/* MAIN CONTENT */
.main-content { margin-left: 240px; padding: 30px; }
.dashboard-header { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.08); margin-bottom: 25px; }
.dashboard-header h1 { margin: 0 0 5px; }
.dashboard-header p { margin: 0; color: #777; }
/* CARDS */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.card { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.08); display: flex; align-items: center; gap: 15px; }
.card-icon { width: 50px; height: 50px; border-radius: 50%; background: #e8f1ff; color: #007BFF; display: flex; justify-content: center; align-items: center; font-size: 20px; }
.card h3 { margin: 0; font-size: 15px; color: #777; }
.card p { margin: 5px 0 0; font-size: 24px; font-weight: bold; }
/* WELCOME PANEL */
.welcome-panel { margin-top: 25px; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
.welcome-panel h2 { margin-top: 0; }
.welcome-panel p { color: #666; line-height: 1.6; }
/* RESPONSIVE */
@media (max-width: 768px) {
.sidebar { width: 70px; padding: 15px 10px; }
.dashboard-logo span, nav a:not(.active)::after { display: none; } /* see §5 item 13 */
nav a { justify-content: center; }
nav a i { font-size: 18px; }
.logout-button { font-size: 0; }
.logout-button i { font-size: 18px; }
.main-content { margin-left: 70px; }
.cards { grid-template-columns: 1fr; }
}
```
**`mainDashboard/main_dashboard.js`**
```js
import { auth, db } from "../firebase/firebase-config.js";
import { onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-auth.js";
import { ref, get } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-database.js";
// PROTECTED PAGE: only authenticated users stay here
onAuthStateChanged(auth, async (user) => {
if (user) {
console.log("Logged in user:", user.uid);
try {
const userRef = ref(db, "users/" + user.uid);
const snapshot = await get(userRef);
if (snapshot.exists()) {
const userData = snapshot.val();
console.log("User data:", userData);
// Display username
document.querySelector(".dashboard-header p").textContent =
"Welcome back, " + userData.username + "!";
}
} catch (error) {
console.error("Error reading user data:", error);
}
} else {
// No authenticated user
window.location.href = "../login/login.html";
}
});
// FIREBASE LOGOUT
async function logout() {
try {
await signOut(auth);
alert("You have been logged out.");
window.location.href = "../login/login.html";
} catch (error) {
console.error("Logout error:", error);
}
}
// Make logout available to HTML onclick
window.logout = logout;
```
Protection logic (for the presentation):
```
DASHBOARD → Is Firebase user authenticated?
YES → Show Dashboard (welcome shows username from users/{UID})
NO → Redirect to Login
```
Example: someone typing `http://localhost:5500/mainDashboard/main_dashboard.html` directly without logging in is sent to Login.
**Checkpoint 4:** smoke test: logged-in view shows "Welcome back, <username>!"; logout redirects; direct URL after logout redirects. Commit `Lab 6: protected main dashboard + logout`.
### 6.5 Activity 5 — Profile Dashboard (Profile handout Steps 1–4 + §5 item 5)
**`mainDashboard/profile_dashboard.html`**
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Profile</title>
<link rel="stylesheet" href="profile_dashboard.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
</head>
<body>
<!-- SIDEBAR -->
<aside class="sidebar">
<div class="dashboard-logo">
<i class="fa-solid fa-laptop-code"></i>
<span>Web Application</span>
</div>
<nav>
<a href="main_dashboard.html"><i class="fa-solid fa-house"></i><span>Dashboard</span></a>
<a href="#"><i class="fa-solid fa-chart-line"></i><span>Reports</span></a>
<a href="profile_dashboard.html" class="active"><i class="fa-solid fa-user"></i><span>Profile</span></a>
<a href="#"><i class="fa-solid fa-gear"></i><span>Settings</span></a>
</nav>
<button class="logout-button" onclick="logout()">
<i class="fa-solid fa-right-from-bracket"></i><span>Logout</span>
</button>
</aside>
<!-- MAIN CONTENT -->
<main class="main-content">
<header class="dashboard-header">
<h1>My Profile</h1>
<p>Manage your personal and contact information.</p>
</header>
<section class="profile-container">
<form id="profileForm">
<!-- ACCOUNT INFORMATION (added: lab sheet requires user name + account info) -->
<div class="profile-section">
<h2><i class="fa-solid fa-id-card"></i> Account Information</h2>
<div class="form-grid">
<div class="form-group">
<label for="accountUsername">Username</label>
<input type="text" id="accountUsername" readonly>
<small>Set during sign-up.</small>
</div>
<div class="form-group">
<label for="accountCreated">Account Created</label>
<input type="text" id="accountCreated" readonly>
</div>
</div>
</div>
<!-- PERSONAL INFORMATION -->
<div class="profile-section">
<h2><i class="fa-solid fa-user"></i> Personal Information</h2>
<div class="form-grid">
<div class="form-group">
<label for="givenName">Given Name</label>
<input type="text" id="givenName" placeholder="e.g., Juan">
<span id="givenNameError" class="error"></span>
</div>
<div class="form-group">
<label for="middleName">Middle Name</label>
<input type="text" id="middleName" placeholder="e.g., Santos">
<span id="middleNameError" class="error"></span>
</div>
<div class="form-group">
<label for="surname">Surname</label>
<input type="text" id="surname" placeholder="e.g., Dela Cruz">
<span id="surnameError" class="error"></span>
</div>
<div class="form-group">
<label for="birthdate">Birthdate</label>
<input type="date" id="birthdate">
<span id="birthdateError" class="error"></span>
</div>
</div>
</div>
<!-- CONTACT INFORMATION -->
<div class="profile-section">
<h2><i class="fa-solid fa-address-book"></i> Contact Information</h2>
<div class="form-grid">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" placeholder="e.g., juan@example.com" readonly>
<small>Email is managed by your Firebase account.</small>
</div>
<div class="form-group">
<label for="mobileNumber">Mobile Number</label>
<input type="tel" id="mobileNumber" placeholder="e.g., 09171234567" maxlength="11" inputmode="numeric">
<span id="mobileNumberError" class="error"></span>
</div>
<div class="form-group">
<label for="alternativeContact">Alternative Contact Number</label>
<input type="tel" id="alternativeContact" placeholder="e.g., 09221234567" maxlength="11" inputmode="numeric">
<span id="alternativeContactError" class="error"></span>
</div>
<div class="form-group">
<label for="emergencyContactName">Emergency Contact Name</label>
<input type="text" id="emergencyContactName" placeholder="e.g., Maria Dela Cruz">
</div>
<div class="form-group">
<label for="emergencyContactNumber">Emergency Contact Number</label>
<input type="tel" id="emergencyContactNumber" placeholder="e.g., 09181234567" maxlength="11" inputmode="numeric">
<span id="emergencyContactNumberError" class="error"></span>
</div>
</div>
</div>
<!-- ADDRESS -->
<div class="profile-section">
<h2><i class="fa-solid fa-location-dot"></i> Address</h2>
<div class="form-grid">
<div class="form-group">
<label for="blockLot">Block / Lot</label>
<input type="text" id="blockLot" placeholder="e.g., Block 12 Lot 8">
<span id="blockLotError" class="error"></span>
</div>
<div class="form-group">
<label for="barangay">Barangay</label>
<input type="text" id="barangay" placeholder="e.g., San Jose">
<span id="barangayError" class="error"></span>
</div>
<div class="form-group">
<label for="city">City / Municipality</label>
<input type="text" id="city" placeholder="e.g., Rodriguez">
<span id="cityError" class="error"></span>
</div>
<div class="form-group">
<label for="province">Province</label>
<input type="text" id="province" placeholder="e.g., Rizal">
<span id="provinceError" class="error"></span>
</div>
<div class="form-group">
<label for="postalCode">ZIP / Postal Code</label>
<input type="text" id="postalCode" placeholder="e.g., 1860" maxlength="4" inputmode="numeric">
<span id="postalCodeError" class="error"></span>
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" id="country" value="Philippines" placeholder="e.g., Philippines">
<span id="countryError" class="error"></span>
</div>
</div>
</div>
<!-- SAVE PROFILE -->
<div class="profile-actions">
<button type="submit" class="save-button">
<i class="fa-solid fa-floppy-disk"></i> Save Profile
</button>
<p id="profileMessage" class="profile-message"></p>
</div>
</form>
</section>
</main>
<script type="module" src="profile_dashboard.js"></script>
</body>
</html>
```
**`mainDashboard/profile_dashboard.css`** (handout Step 3)
```css
/* GENERAL */
* { box-sizing: border-box; }
body { margin: 0; font-family: Arial, sans-serif; background: #f4f6f8; }
/* SIDEBAR */
.sidebar { position: fixed; left: 0; top: 0; width: 240px; height: 100vh; background: #007BFF; color: white; padding: 20px; display: flex; flex-direction: column; }
.dashboard-logo { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: bold; padding-bottom: 30px; }
.dashboard-logo i { font-size: 25px; }
/* NAVIGATION */
nav { display: flex; flex-direction: column; gap: 8px; }
nav a { color: white; text-decoration: none; padding: 12px; border-radius: 6px; display: flex; align-items: center; gap: 12px; transition: 0.3s; }
nav a:hover { background: rgba(255,255,255,.2); }
nav a.active { background: rgba(255,255,255,.2); }
/* LOGOUT */
.logout-button { margin-top: auto; padding: 12px; background: #dc3545; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 15px; display: flex; align-items: center; justify-content: center; gap: 10px; transition: 0.3s; }
.logout-button:hover { background: #b02a37; }
/* MAIN CONTENT */
.main-content { margin-left: 240px; padding: 30px; }
/* HEADER */
.dashboard-header { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.08); margin-bottom: 25px; }
.dashboard-header h1 { margin: 0 0 5px; }
.dashboard-header p { margin: 0; color: #777; }
/* PROFILE CONTAINER */
.profile-container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
/* PROFILE SECTION */
.profile-section { margin-bottom: 30px; padding-bottom: 25px; border-bottom: 1px solid #eeeeee; }
.profile-section:last-of-type { border-bottom: none; }
.profile-section h2 { margin-top: 0; margin-bottom: 20px; font-size: 20px; color: #333333; }
.profile-section h2 i { color: #007BFF; margin-right: 8px; }
/* FORM GRID */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
/* FORM GROUP */
.form-group { display: flex; flex-direction: column; }
.form-group label { font-weight: bold; margin-bottom: 6px; color: #333333; }
.form-group input { width: 100%; padding: 11px; border: 1px solid #cccccc; border-radius: 6px; font-size: 14px; background: white; }
.form-group input:focus { outline: none; border-color: #007BFF; box-shadow: 0 0 0 2px rgba(0,123,255,.1); }
.form-group input[readonly] { background: #f1f3f5; cursor: not-allowed; }
.form-group small { margin-top: 5px; color: #777777; font-size: 11px; }
/* ERROR MESSAGE */
.error { color: #dc3545; font-size: 12px; display: block; margin-top: 5px; min-height: 15px; }
input.error-border { border: 1px solid #dc3545; background: #fff8f8; }
/* PROFILE ACTIONS */
.profile-actions { text-align: center; padding-top: 10px; }
/* SAVE BUTTON */
.save-button { padding: 12px 25px; background: #007BFF; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 15px; transition: 0.3s; }
.save-button:hover { background: #0056b3; }
.save-button i { margin-right: 7px; }
/* PROFILE MESSAGE */
.profile-message { margin-top: 12px; font-size: 14px; font-weight: bold; }
.profile-success { color: #28a745; }
.profile-error { color: #dc3545; }
/* RESPONSIVE */
@media (max-width: 768px) {
.sidebar { width: 70px; padding: 15px 10px; }
.dashboard-logo span { display: none; }
nav a { justify-content: center; }
nav a span { display: none; }
.logout-button { font-size: 0; padding: 12px 5px; }
.logout-button i { font-size: 18px; }
.main-content { margin-left: 70px; padding: 20px; }
.form-grid { grid-template-columns: 1fr; }
.profile-container { padding: 20px; }
}
/* SMALL MOBILE DEVICES */
@media (max-width: 480px) {
.main-content { padding: 15px; }
.dashboard-header { padding: 15px; }
.dashboard-header h1 { font-size: 24px; }
.profile-container { padding: 15px; }
}
```
**`mainDashboard/profile_dashboard.js`** (handout Step 4; only the two `account*` lines are added)
```js
// FIREBASE IMPORTS
import { auth, db } from "../firebase/firebase-config.js";
import { onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-auth.js";
import { ref, get, update } from "https://www.gstatic.com/firebasejs/12.16.0/firebase-database.js";
// PROFILE FORM
const profileForm = document.getElementById("profileForm");
// AUTHENTICATION CHECK
onAuthStateChanged(auth, async (user) => {
if (!user) {
window.location.href = "../login/login.html";
return;
}
console.log("Logged in user:", user.uid);
// LOAD USER PROFILE
try {
const userRef = ref(db, "users/" + user.uid);
const snapshot = await get(userRef);
// DISPLAY FIREBASE AUTH EMAIL
document.getElementById("email").value = user.email || "";
if (snapshot.exists()) {
const userData = snapshot.val();
console.log("User data:", userData);
// ACCOUNT INFORMATION (added)
document.getElementById("accountUsername").value = userData.username || "";
document.getElementById("accountCreated").value =
userData.createdAt ? new Date(userData.createdAt).toLocaleString() : "";
if (userData.profile) {
const profile = userData.profile;
// PERSONAL INFORMATION
document.getElementById("givenName").value = profile.givenName || "";
document.getElementById("middleName").value = profile.middleName || "";
document.getElementById("surname").value = profile.surname || "";
document.getElementById("birthdate").value = profile.birthdate || "";
// CONTACT INFORMATION
document.getElementById("mobileNumber").value = profile.mobileNumber || "";
document.getElementById("alternativeContact").value = profile.alternativeContact || "";
document.getElementById("emergencyContactName").value = profile.emergencyContactName || "";
document.getElementById("emergencyContactNumber").value = profile.emergencyContactNumber || "";
// ADDRESS
if (profile.address) {
document.getElementById("blockLot").value = profile.address.blockLot || "";
document.getElementById("barangay").value = profile.address.barangay || "";
document.getElementById("city").value = profile.address.city || "";
document.getElementById("province").value = profile.address.province || "";
document.getElementById("postalCode").value = profile.address.postalCode || "";
document.getElementById("country").value = profile.address.country || "Philippines";
}
}
}
} catch (error) {
console.error("Error loading profile:", error);
}
});
// NUMBER-ONLY INPUT
function numbersOnly(inputId) {
const input = document.getElementById(inputId);
input.addEventListener("input", function () {
this.value = this.value.replace(/\D/g, "");
});
}
numbersOnly("mobileNumber");
numbersOnly("alternativeContact");
numbersOnly("emergencyContactNumber");
numbersOnly("postalCode");
// MOBILE NUMBER VALIDATION — Philippine format 09XXXXXXXXX (11 digits)
function validateMobileNumber(value) {
return /^09\d{9}$/.test(value);
}
// POSTAL CODE VALIDATION — Philippine postal code, 4 digits
function validatePostalCode(value) {
return /^\d{4}$/.test(value);
}
// BIRTHDATE VALIDATION — not in the future
function validateBirthdate(value) {
if (value === "") return false;
const selectedDate = new Date(value);
const today = new Date();
today.setHours(0, 0, 0, 0);
return selectedDate <= today;
}
// CLEAR ERRORS
function clearErrors() {
document.querySelectorAll(".error").forEach(function (error) { error.textContent = ""; });
document.querySelectorAll("input").forEach(function (input) { input.classList.remove("error-border"); });
document.getElementById("profileMessage").textContent = "";
}
// DISPLAY ERROR
function showError(inputId, errorId, message) {
document.getElementById(errorId).textContent = message;
document.getElementById(inputId).classList.add("error-border");
}
// SAVE PROFILE
profileForm.addEventListener("submit", async function (event) {
event.preventDefault();
clearErrors();
let valid = true;
// GET VALUES
const givenName = document.getElementById("givenName").value.trim();
const middleName = document.getElementById("middleName").value.trim();
const surname = document.getElementById("surname").value.trim();
const birthdate = document.getElementById("birthdate").value;
const mobileNumber = document.getElementById("mobileNumber").value.trim();
const alternativeContact = document.getElementById("alternativeContact").value.trim();
const emergencyContactName = document.getElementById("emergencyContactName").value.trim();
const emergencyContactNumber = document.getElementById("emergencyContactNumber").value.trim();
const blockLot = document.getElementById("blockLot").value.trim();
const barangay = document.getElementById("barangay").value.trim();
const city = document.getElementById("city").value.trim();
const province = document.getElementById("province").value.trim();
const postalCode = document.getElementById("postalCode").value.trim();
const country = document.getElementById("country").value.trim();
// GIVEN NAME
if (givenName === "") { showError("givenName", "givenNameError", "Given Name is required."); valid = false; }
// SURNAME
if (surname === "") { showError("surname", "surnameError", "Surname is required."); valid = false; }
// BIRTHDATE
if (birthdate === "") {
showError("birthdate", "birthdateError", "Birthdate is required."); valid = false;
} else if (!validateBirthdate(birthdate)) {
showError("birthdate", "birthdateError", "Birthdate cannot be a future date."); valid = false;
}
// MOBILE NUMBER
if (mobileNumber === "") {
showError("mobileNumber", "mobileNumberError", "Mobile number is required."); valid = false;
} else if (!validateMobileNumber(mobileNumber)) {
showError("mobileNumber", "mobileNumberError", "Enter a valid 11-digit Philippine mobile number (09XXXXXXXXX)."); valid = false;
}
// ALTERNATIVE CONTACT (optional)
if (alternativeContact !== "" && !validateMobileNumber(alternativeContact)) {
showError("alternativeContact", "alternativeContactError", "Enter a valid 11-digit mobile number."); valid = false;
}
// EMERGENCY CONTACT NUMBER (optional)
if (emergencyContactNumber !== "" && !validateMobileNumber(emergencyContactNumber)) {
showError("emergencyContactNumber", "emergencyContactNumberError", "Enter a valid 11-digit mobile number."); valid = false;
}
// ADDRESS
if (blockLot === "") { showError("blockLot", "blockLotError", "Block / Lot is required."); valid = false; }
if (barangay === "") { showError("barangay", "barangayError", "Barangay is required."); valid = false; }
if (city === "") { showError("city", "cityError", "City / Municipality is required."); valid = false; }
if (province === "") { showError("province", "provinceError", "Province is required."); valid = false; }
if (postalCode === "") {
showError("postalCode", "postalCodeError", "ZIP / Postal Code is required."); valid = false;
} else if (!validatePostalCode(postalCode)) {
showError("postalCode", "postalCodeError", "ZIP / Postal Code must contain exactly 4 digits."); valid = false;
}
if (country === "") { showError("country", "countryError", "Country is required."); valid = false; }
// STOP IF INVALID
if (!valid) return;
// CHECK AUTHENTICATED USER
const user = auth.currentUser;
if (!user) {
window.location.href = "../login/login.html";
return;
}
// SAVE TO FIREBASE
try {
await update(ref(db, "users/" + user.uid), {
profile: {
// Personal Information
givenName: givenName,
middleName: middleName,
surname: surname,
birthdate: birthdate,
// Contact Information
mobileNumber: mobileNumber,
alternativeContact: alternativeContact,
emergencyContactName: emergencyContactName,
emergencyContactNumber: emergencyContactNumber,
// Address
address: {
blockLot: blockLot,
barangay: barangay,
city: city,
province: province,
postalCode: postalCode,
country: country
}
}
});
alert("Profile saved successfully.");
window.location.href = "main_dashboard.html";
} catch (error) {
console.error("Error saving profile:", error);
const message = document.getElementById("profileMessage");
message.textContent = "Unable to save profile. Please try again.";
message.className = "profile-message profile-error";
}
});
// LOGOUT
async function logout() {
try {
await signOut(auth);
alert("You have been logged out.");
window.location.href = "../login/login.html";
} catch (error) {
console.error("Logout error:", error);
}
}
// MAKE LOGOUT AVAILABLE TO HTML
window.logout = logout;
```
Note: `update()` with a `profile` key replaces the whole `profile` object each save (not merged field-by-field). That's fine because the form always submits every field.
**Checkpoint 5:** smoke test: profile page shows username/created/email for the logged-in user; save valid profile → alert → back to dashboard; reload profile → values persist. Commit `Lab 6: profile dashboard`.
---
## 7. ACTIVITY 6 — AUTHENTICATION TESTING + EVIDENCE
### 7.1 Automated test run (Claude does this)
Write `lab6-tests/auth-flow.spec.mjs` (plain Playwright script, Chromium, headless by default; offer `headed` if the student wants to watch). Serve the project on port 5500 first. Handle `alert()` dialogs by auto-accepting and recording their text.
Test data: one unique account per run, e.g. `lab6.test.<timestamp>@example.com`, username `Lab6Tester`, password `Test12345`.
Run the 9 official cases in this order (fresh browser context where noted), capture a full-page screenshot for each into `docs/screenshots/app/NN-<case>.png`, and record Actual Result + Pass/Fail:
| # | Case | Steps | Pass criteria |
|---|---|---|---|
| 1 | Valid Sign-up | fill sign-up with new account, submit | alert "Registration Successful!", URL → login; `users/<uid>` exists (verify via `firebase database:get`) |
| 2 | Existing Email Sign-up | sign up again with same email | `#emailError` = "This email is already registered." |
| 3 | Empty Login Fields | fresh context, submit empty login | "Email is required." and "Password is required." |
| 4 | Invalid Login | wrong password | `#loginMessage` = "Invalid email or password." |
| 5 | Valid Login | correct credentials | alert "Login Successful!", URL → main dashboard |
| 6 | Main Dashboard Access | after #5 | dashboard visible, header shows "Welcome back, Lab6Tester!" |
| 7 | Profile Dashboard | click Profile; fill valid profile; save; reopen | username, email, created date match the account; saved values persist; also screenshot one validation-error state (e.g. bad mobile number) |
| 8 | Logout | click Logout | alert "You have been logged out.", URL → login; then visiting main_dashboard.html and profile_dashboard.html redirects to login |
| 9 | Unauthenticated Dashboard Access | brand-new browser context, open main_dashboard.html directly | redirected to login |
Extra access-control evidence (not an official case, useful in the report): `curl <databaseURL>/users.json` → `Permission denied`.
Write results to `docs/TEST_RESULTS.md` using the official table plus columns **Actual Result** and **Status**. If a case fails, fix the cause (staying within the handout design), log it under "Errors encountered", rerun the whole suite.
Optional cleanup: the tester account can stay (it's evidence). Ask before deleting anything.
### 7.2 Firebase console screenshots (human step — Google blocks automated console login)
```
🧑 YOUR TURN (≈ 4 min) — Firebase evidence screenshots
Save each into docs/screenshots/firebase/ with these names (or drop them in the chat folder and tell me):
1. 01-auth-providers.png → https://console.firebase.google.com/project/<id>/authentication/providers (Email/Password: Enabled)
2. 02-auth-users.png → .../authentication/users (test account listed)
3. 03-rtdb-data.png → .../database/<id>-default-rtdb/data (expand users → UID → profile)
4. 04-rtdb-rules.png → .../database/<id>-default-rtdb/rules (published rules)
Optional: 05-project-settings.png → Project settings → Your apps (web app registered)
Reply "done".
```
After they reply, verify the files exist and view them to confirm they show what they should.
**Checkpoint 6:** test table complete, screenshots present. Commit `Lab 6: authentication tests and evidence`.
---
## 8. `LAB6_PROGRESS.md` TEMPLATE
```markdown
# Lab 6 Progress — Cloud Integration (Firebase)
Student: <name> · Section: <section> · Project ID: <firebase-project-id>
Database URL: <url> · Local URL: http://localhost:5500
## Roadmap
- [ ] 0 Session start / tooling
- [ ] 1 Firebase project configured (D1)
- [ ] 2 Sign-up integrated (D2)
- [ ] 3 Login integrated (D3)
- [ ] 4 Main Dashboard protected + logout (D4, D6)
- [ ] 5 Profile Dashboard (D5)
- [ ] 6 Tests + screenshots (D7, D8)
- [ ] 7 GitHub pushed (D9) + report drafted (D10)
- [ ] 8 Presentation prep
## Decisions / deviations from handouts
- <one line each, e.g. "profile_dashboard.* naming used (handout Step 1 said profile.html)">
## Errors encountered → solutions (feeds the lab report)
- <symptom> → <cause> → <fix>
## Manual steps the student performed
- <step, date>
```
Update it at every checkpoint. It must let a new session resume without re-asking anything.
---
## 9. DELIVERABLES 9 & 10 — GITHUB + LAB REPORT
### 9.1 GitHub (Deliverable 9)
- Ensure `.gitignore` excludes `node_modules/`, `lab6-tests/node_modules/`, `.firebase/`, `*.log`. Do **not** ignore `firebase/firebase-config.js`.
- Commits should already exist per activity. Confirm remote. If `gh auth status` is OK or git push works, push. Otherwise:
```
🧑 YOUR TURN (≈ 1 min)
1. Run git push in your terminal and complete the GitHub sign-in if prompted.
Reply "done".
```
- If there's no remote, offer `gh repo create` (ask public/private; many instructors need to see it, so confirm the student can share it).
### 9.2 Laboratory Report (Deliverable 10)
Ask the student to put the original `LAB_ACTIVITY_No._6.docx` template in the project (e.g. `docs/`). If present, fill it with `python-docx` while keeping its content; if absent, generate a `.docx` that reproduces the header block from §2. Output: `docs/LAB_ACTIVITY_No6_<Surname>.docx`.
Fill:
- **Submitted By:** student name (+ section) · **Date submitted:** today (ask to confirm)
- Leave the rubric Score column blank.
Append a brief report (the sheet asks for *brief*; aim for 2–4 pages plus screenshots):
1. **Implementation** — one short paragraph per activity (Firebase setup, sign-up, login, main dashboard protection, profile dashboard, logout), plus the database structure and security rules and what they enforce.
2. **Testing Results** — the 9-row table with Expected, Actual, Status.
3. **Screenshots/Evidence** — Firebase console (4–5) and app screenshots (9 cases), each captioned.
4. **Errors Encountered and Solutions** — from `LAB6_PROGRESS.md`; real items only, never invented. If very few errors occurred, list the handout issues resolved in §5 that actually came up (e.g. module scripts not loading over `file://`, missing `databaseURL`, profile file-name mismatch).
5. **GitHub Repository** — URL.
Render the docx to PDF/images and inspect the layout before handing it over. Tell the student to read it once before submitting; they own the submission.
---
## 10. PRESENTATION PREP (final step)
Give the student:
1. **A 60–90 second talk track** following the system flow: Sign-up → Firebase Authentication → Login → Main Dashboard → Profile Dashboard → Logout, mentioning UID-keyed database and per-user security rules.
2. **A live demo script** (which pages to open in order, which test account to use; open an incognito window to show the protected-page redirect).
3. **Likely instructor questions with 1–2 sentence answers**, e.g.:
- Why `type="module"`? → So the JS can `import` Firebase and the shared config file.
- Why email instead of username on login? → Firebase Email/Password authentication signs in with an email address.
- Why store users under UID, not username? → UIDs are unique per Firebase account and match `auth.uid` in the security rules; usernames can collide.
- How is the dashboard protected? → `onAuthStateChanged` redirects to login when there's no signed-in user; the database rules also block reading other users' data server-side.
- What happens after logout? → `signOut(auth)` ends the session, so the protected pages redirect to login.
- Why `window.logout = logout`? → Module functions aren't global, and the button uses `onclick="logout()"`.
- Is the API key in the repo a security problem? → Firebase web config identifies the project; access is controlled by Authentication and Security Rules.
4. **Rubric self-check** against Layout Organization, Navigation Flow, UI Design Principles, Completeness, Presentation, listing any remaining gaps honestly (including the optional §5 items 12–13 if not applied).
Close with a short summary of what's complete and what (if anything) the student still has to submit.
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
by AgentHub Crawler
Claude.md
This file provides guidance to Claude Code when working with code in this repository.
by AgentHub Crawler
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) and other AI assistants when working with code in this repository. **IMPORTANT**: This project uses [bd (beads)](https://github.com/steveyegge/beads) for ALL issue tracking. Use `bd` commands instead of markdown TODOs, task lists, or other tracking methods.
by AgentHub Crawler
Sign in to leave a comment.
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
by AgentHub Crawler