Saturday · 24 May 2025 · Gesamtschule Konradsdorf · 10:00–17:00
+
+
+
+ Ever wanted to unleash your AI skills? Join us for a day of hands-on workshops (practical), thrilling competitions (hackathons), and thought-provoking debates (About future AI)!
+ You will learn about the latest in robotics and AI, and have the chance to showcase your own projects. Even bring your arguments to the table!
+ So what are you waiting for? Come join the event!
+
+
+
+
Event Schedule
+
+
+
+
Time
+
Activity
+
Location
+
+
+
+
10 : 00
Opening Keynote
Aula
+
11 : 00
Autonomous-Bot Race
Sporthalle
+
13 : 00
ML Workshop: "Teach a CNN to lower grade students"
+ Hey, I'm Mohamad Dabbabo, a web‑dev and AI enthusiast on a quest to fuse code with creativity.
+ Lately I've been trying Bootstrap, which is perfect for rapid, responsive builds
+ (and great for this internship’s mini‑projects).
+
+
+
+ HTML
+
+
+ CSS
+
+
+ Python
+
+
+ C#
+
+
+ LaTeX
+
+
+ JavaScript
+
+
+
+
+
+
+
+
+
+
My Portfolio
+
+
+
+
+
+
+
+
Project 1: HTML
+
+ A simple HTML page showcasing a table of personal favorites and a styled user input form using pure HTML.
+
+
+
+
+
+
+
+
+
+
+
+
Project 2: CSS
+
+ A custom-styled event schedule for the Robotics & AI Expo 2025, featuring colored headers and responsive table layout powered by CSS.
+
+
+
+
+
+
+
+
+
+
+
+
Project 3: Advanced CSS
+
+ A dark, AI-themed responsive redesign: hexagon background, grid-based layout, modals, and custom form styling with advanced CSS techniques.
+
+
+
+
+
+
+
+
+
+
+
+
Project 4: Bootstrap
+
+ A playful nod to the power of Bootstrap itself.
+
Saturday · 24 May 2025 · Gesamtschule Konradsdorf · 10:00–17:00
+
+
+
+ Ever wanted to unleash your AI skills? Join us for a day of hands-on workshops (practical), thrilling competitions (hackathons), and thought-provoking debates (About future AI)!
+ You will learn about the latest in robotics and AI, and have the chance to showcase your own projects. Even bring your arguments to the table!
+ So what are you waiting for? Come join the event!
+
+
+
+
Event Schedule
+
+
+
+
Time
+
Activity
+
Location
+
+
+
+
10 : 00
Opening Keynote
Aula
+
11 : 00
Autonomous-Bot Race
Sporthalle
+
13 : 00
ML Workshop: "Teach a CNN to lower grade students"
+ ERROR: Please specify a valid hook challenge
+ (e.g. npm run dev:useState)
+
// use " " for space, had issues with spaces in the past.
+ );
+ }
+}
+
+// export default App; will be used in the index.jsx file
+// to render the App component in the root element
+export default App;
diff --git a/Exercise 7/src/components/FinalChallenge.jsx b/Exercise 7/src/components/FinalChallenge.jsx
new file mode 100644
index 00000000..18f24b59
--- /dev/null
+++ b/Exercise 7/src/components/FinalChallenge.jsx
@@ -0,0 +1,280 @@
+// Import React and several hooks from the React library
+import React, { useState, useEffect, useRef, useContext, useReducer } from 'react';
+// Import AuthContext and AuthProvider from a local file for authentication and theme context
+import { AuthContext, AuthProvider } from './useContextChallenge/AuthContext';
+
+// This is a comment block describing the improvements and features of this component
+/**
+ * Improved FinalChallenge.jsx
+ *
+ * Changes made:
+ * - Moved useContext inside a provider: Wrapped form content with and moved context usage inside to fix the blank screen (user was undefined without provider).
+ * - Extended useReducer to handle form submission (added a 'SUBMIT' action and 'submitted' state) for submission feedback.
+ * - Added conditional rendering of a success message upon form submission.
+ * - Added styling (padding, margins, colors, etc.) to inputs, buttons, and container for a polished, responsive UI.
+ * - Included comments explaining how each React hook is used.
+ */
+
+// Define the initial state for the form, with empty name and email, and not submitted
+const initialFormState = { name: '', email: '', submitted: false, touched: false };
+
+// This function manages how the form state changes based on different actions
+function formReducer(state, action) {
+ // Check the type of action being dispatched; which simply means what action is being performed
+ // and update the state accordingly
+ switch (action.type) {
+ case 'CHANGE':
+ // If the action is 'CHANGE', update the specific field (name or email) and reset 'submitted' to false
+ return { ...state, [action.field]: action.value, submitted: false }; // .field means the field name is dynamic, it can be either name or email
+ case 'RESET':
+ // If the action is 'RESET', return the initial form state (clear all fields and submission status)
+ return initialFormState;
+
+ case 'SUBMIT':
+ // If the action is 'SUBMIT', set 'submitted' to true (keep the current field values)
+ return { ...state, submitted: true };
+
+ // If the user initially started the website, it won't throw an error
+ case 'TOUCH':
+ return { ...state, touched: true };
+ default:
+ // If the action type is not recognized, return the current state unchanged
+ return state;
+ }
+}
+
+// Define a styles object to store CSS styles for the component
+const styles = {
+ container: {
+ // Set the maximum width of the form container
+ maxWidth: '400px',
+ // Center the container horizontally and add top margin
+ margin: '30px auto',
+ // Add padding inside the container
+ padding: '20px',
+ // Round the corners of the container
+ borderRadius: '8px',
+ // Add a subtle box shadow for depth
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
+ // Set a default background color (can be changed by theme)
+ backgroundColor: '#ffffff',
+ // Set a default text color (can be changed by theme)
+ color: '#343a40',
+ },
+ input: {
+ // Make input fields take up the full width of the container
+ width: '100%',
+ // Add padding inside the input fields
+ padding: '10px',
+ // Set the font size for input text
+ fontSize: '16px',
+ // Add space below each input
+ marginBottom: '1rem',
+ // Round the corners of the input fields
+ borderRadius: '5px',
+ // Add a light border around the input fields
+ border: '1px solid #ced4da',
+ // Make the input background transparent so the container's background shows through
+ backgroundColor: 'transparent',
+ // Make the input text color inherit from the container
+ color: 'inherit'
+ },
+ button: {
+ // Add padding inside the buttons
+ padding: '10px 20px',
+ // Set the font size for button text
+ fontSize: '16px',
+ // Remove the default border from buttons
+ border: 'none',
+ // Round the corners of the buttons
+ borderRadius: '5px',
+ // Change the cursor to a pointer when hovering over buttons
+ cursor: 'pointer'
+ },
+ submitButton: {
+ // Set the background color for the submit button (blue)
+ backgroundColor: '#007bff',
+ // Set the text color for the submit button (white)
+ color: '#ffffff'
+ },
+ resetButton: {
+ // Set the background color for the reset button (gray)
+ backgroundColor: '#6c757d',
+ // Set the text color for the reset button (white)
+ color: '#ffffff',
+ // Add space to the left of the reset button
+ marginLeft: '10px'
+ },
+ errorText: {
+ // Add space above the error message
+ marginTop: '10px',
+ // Set the text color for the error message (red)
+ color: '#dc3545',
+ // Set the background color for the error message (light red)
+ backgroundColor: '#f8d7da',
+ // Add padding inside the error message
+ padding: '10px',
+ // Round the corners of the error message box
+ borderRadius: '5px'
+ },
+ successText: {
+ // Add space above the success message
+ marginTop: '10px',
+ // Set the text color for the success message (green)
+ color: '#155724',
+ // Set the background color for the success message (light green)
+ backgroundColor: '#d4edda',
+ // Add padding inside the success message
+ padding: '10px',
+ // Round the corners of the success message box
+ borderRadius: '5px'
+ },
+ validText: {
+ // Add space above the success message
+ marginTop: '10px',
+ // Set the text color for the success message (dark blue)
+ color: '#004085',
+ // Set the background color for the success message (light blue)
+ backgroundColor: '#cce5ff',
+ // Add padding inside the success message
+ padding: '10px',
+ // Round the corners of the success message box
+ borderRadius: '5px'
+ }
+};
+
+// This is the main exported component for the file
+export default function FinalChallenge() {
+ // Wrap the form in the AuthProvider so that any child component can access authentication and theme context
+ return (
+
+ {/* Render the actual form inside the AuthProvider */}
+
+
+ );
+}
+
+// This is the inner component that contains the form and uses several React hooks
+function FinalChallengeForm() {
+ // useContext: Get the current user and theme from the AuthContext
+ const { user, theme } = useContext(AuthContext);
+
+ // useRef: Create a reference to the name input field so we can focus it automatically
+ const inputRef = useRef(null);
+
+ // useReducer: Manage the form's state (name, email, submitted) using the reducer function and initial state
+ const [state, dispatch] = useReducer(formReducer, initialFormState);
+
+ // useState: Track whether the form is valid (used to enable/disable the submit button and show validation messages)
+ const [isValid, setIsValid] = useState(false);
+
+ // useEffect: Whenever the name or email changes, check if the form is valid
+ useEffect(() => {
+ // Check if the name field is not empty (after trimming whitespace)
+ const nameFilled = state.name.trim().length > 0;
+ // Check if the email field matches a basic email pattern
+ const emailValid = /\S+@\S+\.\S+/.test(state.email); // "/\S+@\S+\.\S+/" is a regex pattern for basic email validation
+ // Set isValid to true only if both fields are valid
+ setIsValid(nameFilled && emailValid);
+ }, [state.name, state.email]); // Only run this effect when name or email changes
+
+ // useEffect: When the component first mounts, focus the name input field
+ useEffect(() => {
+ // If the inputRef is attached to an input element, focus it
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, []); // Run this effect only once when the component mounts
+
+ // Adjust the container's background and text color based on the current theme (light or dark)
+ const containerStyle = {
+ ...styles.container,
+ backgroundColor: theme === 'dark' ? '#343a40' : '#ffffff',
+ color: theme === 'dark' ? '#ffffff' : '#343a40'
+ };
+
+ // Render the form UI
+ return (
+ // Apply the container styles to the outer div
+
+ {/* Display the form title */}
+
Final Challenge
+ {/* Show the logged-in user's name from context, or 'Guest' if no user is logged in */}
+
Logged in as: { user ? user.name : 'Guest' }
+
+ {/* Input field for the user's name */}
+ {
+ dispatch({ type: 'CHANGE', field: 'name', value: e.target.value });
+ dispatch({ type: 'TOUCH' });
+ }}
+ // Apply the input styles
+ style={styles.input}
+ />
+ {/* Input field for the user's email */}
+ dispatch({ type: 'CHANGE', field: 'email', value: e.target.value })}
+ // Apply the input styles
+ style={styles.input}
+ />
+
+ {/* Submit button for the form */}
+
+ {/* Reset button to clear the form */}
+
+
+ {/* If the form is invalid and hasn't been submitted, show a validation error message */}
+ {!isValid && state.touched && !state.submitted && (
+
+ Please fill out both fields correctly.
+
+ )}
+
+ {/* If the form has not been submitted, but has valid credentials */}
+ {!state.submitted && state.touched && isValid && (
+
+ Valid Credentials! Feel free to submit the form.
+
+ )}
+
+ {/* If the form has been submitted successfully, show a thank you message with the user's name and email */}
+ {state.submitted && (
+
+ Thank you, {state.name}! We will be in touch at {state.email}.
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/Exercise 7/src/components/UseEffectChallenge.jsx b/Exercise 7/src/components/UseEffectChallenge.jsx
new file mode 100644
index 00000000..16f8b893
--- /dev/null
+++ b/Exercise 7/src/components/UseEffectChallenge.jsx
@@ -0,0 +1,558 @@
+// Import React and two hooks: useState (for state) and useEffect (for side effects)
+import React, { useState, useEffect } from 'react';
+
+// This is a multi-part challenge component to demonstrate useEffect in different scenarios
+
+/**
+ * Challenge: Create a component that demonstrates useEffect in multiple scenarios
+ *
+ * Requirements:
+ * 1. Implement a window resize tracker that updates when the window size changes
+ * 2. Create a countdown timer that runs once when component mounts
+ * 3. Add a data fetching simulation with loading states
+ * 4. Implement a "typing indicator" that appears when the user types and disappears after 1 second of inactivity
+ * 5. Create a cleanup demo to show how useEffect's cleanup function works
+ */
+
+// Define the main functional component
+const UseEffectChallenge = () => {
+ // Create a state variable to store the window's width and height
+ // useState initializes it with the current window size
+ const [windowSize, setWindowSize] = useState({
+ width: window.innerWidth, // current window width
+ height: window.innerHeight, // current window height
+ });
+
+ // State for the countdown timer value (starts at 10)
+ const [countdown, setCountdown] = useState(10);
+ // State to track if the countdown is active or not
+ const [countdownActive, setCountdownActive] = useState(false);
+
+ // State for fetched data (null means no data yet)
+ const [data, setData] = useState(null);
+ // State to show if data is loading
+ const [loading, setLoading] = useState(false);
+ // State to store any error message from fetching
+ const [error, setError] = useState(null);
+ // State to trigger a new fetch (incrementing this value triggers useEffect)
+ const [fetchTrigger, setFetchTrigger] = useState(0);
+
+ // State for the text input value
+ const [inputText, setInputText] = useState('');
+ // State to show/hide the typing indicator
+ const [isTyping, setIsTyping] = useState(false);
+
+ // State to show/hide the cleanup demo section
+ const [showCleanupDemo, setShowCleanupDemo] = useState(true);
+ // State to count how many times the effect has run
+ const [effectCount, setEffectCount] = useState(0);
+
+ // useEffect to track window resizing
+ useEffect(() => {
+ // Define a function to update the windowSize state when the window is resized
+ const handleResize = () => {
+ setWindowSize({
+ width: window.innerWidth, // update width
+ height: window.innerHeight, // update height
+ });
+ };
+
+ // Add the resize event listener when the component mounts
+ window.addEventListener('resize', handleResize);
+
+ // Cleanup function: remove the event listener when the component unmounts
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, []); // Empty array: this effect runs only once when the component mounts
+
+ // useEffect for the countdown timer
+ useEffect(() => {
+ // If countdown is not active, do nothing
+ if (!countdownActive) return;
+
+ // If countdown reaches 0, stop the countdown
+ if (countdown <= 0) {
+ setCountdownActive(false);
+ return;
+ }
+
+ // Set a timeout to decrease the countdown by 1 after 1 second (1000ms)
+ const timerId = setTimeout(() => {
+ setCountdown(countdown - 1);
+ }, 1000);
+
+ // Cleanup: clear the timeout if the effect runs again or component unmounts
+ return () => {
+ clearTimeout(timerId);
+ };
+ }, [countdown, countdownActive]); // Runs whenever countdown or countdownActive changes
+
+ // useEffect for simulating data fetching
+ useEffect(() => {
+ // If fetchTrigger is 0, don't fetch (means user hasn't clicked yet)
+ if (fetchTrigger === 0) return;
+
+ // Set loading state to true, clear previous error and data
+ setLoading(true);
+ setError(null);
+ setData(null);
+
+ // Define an async function to simulate fetching data
+ const fetchData = async () => {
+ try {
+ // Wait 1.5 seconds to simulate network delay
+ await new Promise(resolve => setTimeout(resolve, 1500));
+
+ // Create some mock data with random values
+ const mockData = {
+ id: Math.floor(Math.random() * 1000),
+ title: `Item ${Math.floor(Math.random() * 100)}`,
+ description: `This is a randomly generated description for item ${Math.floor(Math.random() * 100)}`,
+ timestamp: new Date().toISOString(),
+ };
+
+ // Set the data state with the mock data
+ setData(mockData);
+ // Set loading to false since we're done
+ setLoading(false);
+ } catch (err) {
+ // If there's an error, set the error state
+ setError('An error occurred while fetching data');
+ setLoading(false);
+ }
+ };
+
+ // Call the fetchData function
+ fetchData();
+
+ // No cleanup needed for this effect
+ }, [fetchTrigger]); // Runs whenever fetchTrigger changes
+
+ // useEffect for the typing indicator
+ useEffect(() => {
+ // If the input is empty, hide the typing indicator and exit
+ if (inputText.length === 0) {
+ setIsTyping(false);
+ return;
+ }
+
+ // Show the typing indicator
+ setIsTyping(true);
+
+ // Set a timeout to hide the typing indicator after 1 second of inactivity
+ const typingTimer = setTimeout(() => {
+ setIsTyping(false);
+ }, 1000);
+
+ // Cleanup: clear the timeout if the effect runs again or component unmounts
+ return () => {
+ clearTimeout(typingTimer);
+ };
+ }, [inputText]); // Runs whenever inputText changes
+
+ // useEffect for the cleanup demo
+ useEffect(() => {
+ // If the cleanup demo is hidden, do nothing
+ if (!showCleanupDemo) return;
+
+ // Increment the effectCount state by 1
+ setEffectCount(prev => prev + 1);
+
+ // Set up an interval to increment effectCount every 2 seconds
+ const interval = setInterval(() => {
+ setEffectCount(prev => prev + 1);
+ }, 2000);
+
+ // Log to the console when the interval is set up
+ console.log('Effect setup - interval created');
+
+ // Cleanup: clear the interval and log to the console when the effect is cleaned up
+ return () => {
+ clearInterval(interval);
+ console.log('Effect cleanup - interval cleared');
+ };
+ }, [showCleanupDemo]); // Runs whenever showCleanupDemo changes
+
+ // Handler function to start the countdown timer
+ const handleStartCountdown = () => {
+ setCountdown(10); // Reset countdown to 10
+ setCountdownActive(true); // Start the countdown
+ };
+
+ // Handler function to trigger data fetching
+ const handleFetchData = () => {
+ setFetchTrigger(prev => prev + 1); // Increment fetchTrigger to trigger useEffect
+ };
+
+ // Handler function for input changes (typing)
+ const handleInputChange = (e) => {
+ setInputText(e.target.value); // Update inputText state with the new value
+ };
+
+ // Handler to show/hide the cleanup demo and reset the effect count if hiding
+ const toggleCleanupDemo = () => {
+ setShowCleanupDemo(prev => !prev); // Toggle showCleanupDemo state
+ if (showCleanupDemo) {
+ setEffectCount(0); // Reset effectCount if hiding the demo
+ }
+ };
+
+ // The component's rendered UI
+ return (
+ // Main container div with styling
+
+ {/* Title */}
+
useEffect Challenge
+
+ {/* Window Resize Tracker Section */}
+
+
Window Resize Tracker
+
+
+ Width:
+ {windowSize.width}px
+
+
+ Height:
+ {windowSize.height}px
+
+
+
Resize your browser window to see the values update in real-time!
+
+
+ {/* Countdown Timer Section */}
+
+
Countdown Timer
+
+
+ {/* Show "Time's up!" if countdown is 0, otherwise show the countdown value */}
+ {countdown === 0 ? (
+ Time's up!
+ ) : (
+ {countdown}
+ )}
+
+
+
+
+
+ {/* Data Fetching Demo Section */}
+
+
Data Fetching with useEffect
+
+
+
+
+ {/* Show loader if loading */}
+ {loading && }
+
+ {/* Show error message if there is an error */}
+ {error &&
{error}
}
+
+ {/* Show fetched data if available and not loading or error */}
+ {data && !loading && !error && (
+
+
{data.title}
+
{data.description}
+
Fetched at: {data.timestamp}
+
+ )}
+
+ {/* Show message before any fetch */}
+ {!data && !loading && !error && fetchTrigger === 0 && (
+
Click the button to fetch data
+ )}
+
+ {/* Show message if no data after fetch */}
+ {!data && !loading && !error && fetchTrigger > 0 && (
+
No data available
+ )}
+
+
+
+
+ {/* Typing Indicator Section */}
+
+
Typing Indicator
+
+ {/* Text input for typing */}
+
+ {/* Show typing indicator if isTyping is true */}
+ {isTyping && (
+
+
+
+
+ Typing...
+
+ )}
+
+
+ The typing indicator appears when you type and disappears after 1 second of inactivity.
+
+
+
+ {/* Cleanup Demo Section */}
+
+
useEffect Cleanup Demo
+
+ {/* Button to show/hide the cleanup demo */}
+
+
+ {/* Show the cleanup demo if enabled */}
+ {showCleanupDemo && (
+
+
+ This component has an effect that runs every 2 seconds.
+
+
+ Effect has run {effectCount} times.
+
+
+ Check the console for cleanup messages when you hide this component.
+
+
+ )}
+
+
+
+ );
+};
+
+// Define a styles object for inline styling
+const styles = {
+ container: {
+ maxWidth: '800px', // Maximum width of the container
+ margin: '0 auto', // Center the container horizontally
+ padding: '20px', // Padding inside the container
+ backgroundColor: '#f8f9fa', // Light background color
+ borderRadius: '10px', // Rounded corners
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', // Subtle shadow
+ fontFamily: 'Arial, sans-serif', // Font family
+ },
+ title: {
+ textAlign: 'center', // Center the title
+ color: '#343a40', // Dark gray color
+ marginBottom: '30px', // Space below the title
+ fontSize: '28px', // Large font size
+ },
+ section: {
+ backgroundColor: '#ffffff', // White background for sections
+ padding: '20px', // Padding inside the section
+ borderRadius: '8px', // Rounded corners
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)', // Light shadow
+ marginBottom: '25px', // Space below the section
+ },
+ sectionTitle: {
+ color: '#343a40', // Section title color
+ borderBottom: '1px solid #e9ecef', // Bottom border
+ paddingBottom: '10px', // Space below the title
+ marginTop: '0', // No top margin
+ },
+ description: {
+ color: '#6c757d', // Muted text color
+ fontStyle: 'italic', // Italic text
+ margin: '10px 0', // Vertical margin
+ },
+ windowSizeDisplay: {
+ display: 'flex', // Use flexbox
+ justifyContent: 'center', // Center items horizontally
+ gap: '30px', // Space between items
+ padding: '20px', // Padding inside
+ backgroundColor: '#f1f3f5', // Light gray background
+ borderRadius: '8px', // Rounded corners
+ },
+ sizeItem: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack items vertically
+ alignItems: 'center', // Center items horizontally
+ },
+ sizeLabel: {
+ fontSize: '14px', // Small font size
+ color: '#6c757d', // Muted color
+ marginBottom: '5px', // Space below label
+ },
+ sizeValue: {
+ fontSize: '24px', // Large font size
+ fontWeight: 'bold', // Bold text
+ color: '#343a40', // Dark color
+ },
+ countdownContainer: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack items vertically
+ alignItems: 'center', // Center items horizontally
+ gap: '15px', // Space between items
+ },
+ countdownDisplay: {
+ width: '100px', // Width of the countdown circle
+ height: '100px', // Height of the countdown circle
+ display: 'flex', // Use flexbox
+ justifyContent: 'center', // Center content horizontally
+ alignItems: 'center', // Center content vertically
+ backgroundColor: '#343a40', // Dark background
+ borderRadius: '50%', // Make it a circle
+ color: '#ffffff', // White text
+ },
+ countdownValue: {
+ fontSize: '36px', // Large font size
+ fontWeight: 'bold', // Bold text
+ },
+ countdownComplete: {
+ fontSize: '16px', // Medium font size
+ fontWeight: 'bold', // Bold text
+ color: '#ffc107', // Yellow color
+ },
+ button: {
+ padding: '10px 15px', // Padding inside the button
+ fontSize: '16px', // Font size
+ backgroundColor: '#007bff', // Blue background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ transition: 'background-color 0.3s', // Smooth background color transition
+ },
+ fetchContainer: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack items vertically
+ alignItems: 'center', // Center items horizontally
+ gap: '20px', // Space between items
+ },
+ dataDisplay: {
+ width: '100%', // Full width
+ minHeight: '150px', // Minimum height
+ backgroundColor: '#f8f9fa', // Light background
+ borderRadius: '8px', // Rounded corners
+ padding: '15px', // Padding inside
+ position: 'relative', // For positioning loader
+ },
+ loader: {
+ width: '40px', // Loader size
+ height: '40px',
+ margin: '30px auto', // Center loader
+ border: '4px solid #f3f3f3', // Light border
+ borderTop: '4px solid #007bff', // Blue top border for spinner effect
+ borderRadius: '50%', // Make it a circle
+ animation: 'spin 1s linear infinite', // Spin animation
+ },
+ '@keyframes spin': {
+ '0%': { transform: 'rotate(0deg)' }, // Start at 0 degrees
+ '100%': { transform: 'rotate(360deg)' }, // End at 360 degrees
+ },
+ error: {
+ color: '#dc3545', // Red color for errors
+ textAlign: 'center', // Center text
+ padding: '20px', // Padding
+ fontWeight: 'bold', // Bold text
+ },
+ noData: {
+ color: '#6c757d', // Muted color
+ textAlign: 'center', // Center text
+ padding: '20px', // Padding
+ fontStyle: 'italic', // Italic text
+ },
+ dataContent: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack items vertically
+ gap: '10px', // Space between items
+ },
+ dataTitle: {
+ color: '#343a40', // Dark color
+ margin: '0', // No margin
+ fontSize: '20px', // Medium font size
+ },
+ dataDescription: {
+ color: '#495057', // Gray color
+ margin: '0', // No margin
+ },
+ dataTimestamp: {
+ color: '#6c757d', // Muted color
+ fontSize: '12px', // Small font
+ margin: '10px 0 0 0', // Margin above
+ fontStyle: 'italic', // Italic text
+ },
+ typingContainer: {
+ position: 'relative', // For positioning typing indicator
+ marginBottom: '30px', // Space below
+ },
+ input: {
+ width: '100%', // Full width
+ padding: '12px', // Padding inside
+ fontSize: '16px', // Font size
+ borderRadius: '5px', // Rounded corners
+ border: '1px solid #ced4da', // Light border
+ outline: 'none', // No outline
+ transition: 'border-color 0.3s', // Smooth border color transition
+ },
+ typingIndicator: {
+ display: 'flex', // Use flexbox
+ alignItems: 'center', // Center items vertically
+ gap: '5px', // Space between dots and text
+ position: 'absolute', // Position below the input
+ bottom: '-25px', // 25px below the input
+ left: '10px', // 10px from the left
+ },
+ typingDot: {
+ width: '8px', // Dot size
+ height: '8px',
+ backgroundColor: '#007bff', // Blue color
+ borderRadius: '50%', // Make it a circle
+ animation: 'blink 1s infinite', // Blinking animation
+ },
+ typingText: {
+ color: '#007bff', // Blue color
+ fontSize: '14px', // Small font
+ fontStyle: 'italic', // Italic text
+ },
+ cleanupContainer: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack items vertically
+ alignItems: 'center', // Center items horizontally
+ gap: '20px', // Space between items
+ },
+ cleanupDemo: {
+ width: '100%', // Full width
+ backgroundColor: '#e9ecef', // Light gray background
+ borderRadius: '8px', // Rounded corners
+ padding: '15px', // Padding inside
+ marginTop: '10px', // Space above
+ },
+ cleanupText: {
+ color: '#495057', // Gray color
+ margin: '10px 0', // Vertical margin
+ },
+ cleanupCount: {
+ fontWeight: 'bold', // Bold text
+ color: '#007bff', // Blue color
+ },
+};
+
+// Export the component so it can be used in other files
+export default UseEffectChallenge;
\ No newline at end of file
diff --git a/Exercise 7/src/components/UseMemoChallenge.jsx b/Exercise 7/src/components/UseMemoChallenge.jsx
new file mode 100644
index 00000000..7e3a8452
--- /dev/null
+++ b/Exercise 7/src/components/UseMemoChallenge.jsx
@@ -0,0 +1,444 @@
+// Import React and hooks for state and effect management
+import React, { useState } from 'react';
+
+
+
+
+// --- Utility functions ---
+
+function relu(x) { // ReLU activation function
+ return x.map(v => Math.max(0, v));
+}
+function reluDerivative(x) { // Derivative of ReLU
+ return x.map(v => (v > 0 ? 1 : 0));
+}
+function sigmoid(x) { // Sigmoid activation function
+ return x.map(v => 1 / (1 + Math.exp(-v)));
+}
+function sigmoidDerivative(x) { // Derivative of sigmoid
+ return x.map(v => v * (1 - v));
+}
+function matMul(a, b) { // Matrix multiplication
+ return a.map(row =>
+ b[0].map((_, j) =>
+ row.reduce((sum, val, i) => sum + val * b[i][j], 0)
+ )
+ );
+}
+function addBias(mat, bias) { // Add bias vector to each row of matrix
+ return mat.map(row => row.map((v, i) => v + bias[i]));
+}
+function transpose(mat) { // Transpose a matrix
+ return mat[0].map((_, i) => mat.map(row => row[i]));
+}
+function subtract(a, b) { // Element-wise subtraction of matrices
+ return a.map((row, i) => row.map((v, j) => v - b[i][j]));
+}
+function multiply(a, b) { // Element-wise multiplication of matrices
+ return a.map((row, i) => row.map((v, j) => v * b[i][j]));
+}
+function scalarMultiply(mat, scalar) { // Multiply matrix by scalar
+ return mat.map(row => row.map(v => v * scalar));
+}
+function randomMatrix(rows, cols) { // Generate random matrix
+ return Array.from({ length: rows }, () =>
+ Array.from({ length: cols }, () => Math.random() * 2 - 1)
+ );
+}
+function zeroMatrix(rows, cols) { // Generate zero matrix
+ return Array.from({ length: rows }, () =>
+ Array.from({ length: cols }, () => 0)
+ );
+}
+
+// --- Dataset: XOR problem ---
+const DATASET = [
+ { input: [0, 0], target: [0] }, // Input/target pairs for XOR
+ { input: [0, 1], target: [1] },
+ { input: [1, 0], target: [1] },
+ { input: [1, 1], target: [0] }
+];
+
+// --- Main React component ---
+export default function UseMemoChallenge() {
+ // State: weights and biases for each layer
+ const [W1, setW1] = useState(randomMatrix(2, 4)); // Weights for layer 1
+ const [b1, setB1] = useState(Array(4).fill(0)); // Biases for layer 1
+ const [W2, setW2] = useState(randomMatrix(4, 4)); // Weights for layer 2
+ const [b2, setB2] = useState(Array(4).fill(0)); // Biases for layer 2
+ const [W3, setW3] = useState(randomMatrix(4, 4)); // Weights for layer 3
+ const [b3, setB3] = useState(Array(4).fill(0)); // Biases for layer 3
+ const [W4, setW4] = useState(randomMatrix(4, 1)); // Weights for output layer
+ const [b4, setB4] = useState(Array(1).fill(0)); // Biases for output layer
+
+ // State: activations, loss, gradients, and UI step
+ const [activations, setActivations] = useState({}); // Stores activations for all layers
+ const [loss, setLoss] = useState(null); // Stores current loss
+ const [grads, setGrads] = useState({}); // Stores gradients for all layers
+ const [step, setStep] = useState(''); // UI step indicator
+ const [epoch, setEpoch] = useState(0); // Track epoch count
+
+ // --- Forward pass: compute activations and loss ---
+ function forwardPass(weights = { W1, b1, W2, b2, W3, b3, W4, b4 }) {
+ // Prepare input as a matrix (batch size = 4)
+ const X = DATASET.map(d => d.input);
+ // Layer 1: input -> hidden1 (ReLU)
+ const z1 = addBias(matMul(X, weights.W1), weights.b1);
+ const a1 = relu(z1.flat()).reduce((acc, v, i) => {
+ if (i % 4 === 0) acc.push([]);
+ acc[acc.length - 1].push(v);
+ return acc;
+ }, []);
+ // Layer 2: hidden1 -> hidden2 (ReLU)
+ const z2 = addBias(matMul(a1, weights.W2), weights.b2);
+ const a2 = relu(z2.flat()).reduce((acc, v, i) => {
+ if (i % 4 === 0) acc.push([]);
+ acc[acc.length - 1].push(v);
+ return acc;
+ }, []);
+ // Layer 3: hidden2 -> hidden3 (ReLU)
+ const z3 = addBias(matMul(a2, weights.W3), weights.b3);
+ const a3 = relu(z3.flat()).reduce((acc, v, i) => {
+ if (i % 4 === 0) acc.push([]);
+ acc[acc.length - 1].push(v);
+ return acc;
+ }, []);
+ // Output layer: hidden3 -> output (Sigmoid)
+ const z4 = addBias(matMul(a3, weights.W4), weights.b4);
+ const a4 = sigmoid(z4.flat()).map(v => [v]);
+ // Compute loss (mean squared error)
+ const Y = DATASET.map(d => d.target);
+ const mse = a4.reduce((sum, row, i) => sum + Math.pow(row[0] - Y[i][0], 2), 0) / 4;
+ // Store activations and loss in state
+ setActivations({ X, z1, a1, z2, a2, z3, a3, z4, a4, Y });
+ setLoss(mse);
+ return { X, z1, a1, z2, a2, z3, a3, z4, a4, Y, mse };
+ }
+
+ // --- Button handlers ---
+ function handleForwardPass() { // Run forward pass
+ forwardPass();
+ setStep('forward');
+ setGrads({});
+ }
+
+ function handleBackprop() { // Run backpropagation
+ // Use stored activations from last forward pass
+ if (!activations.a4) return;
+ const { X, z1, a1, z2, a2, z3, a3, z4, a4, Y } = activations;
+ // Output layer error
+ const dz4 = a4.map((row, i) => [2 * (row[0] - Y[i][0]) * sigmoidDerivative([row[0]])[0]]);
+ const dW4 = matMul(transpose(a3), dz4);
+ const db4 = dz4.reduce((acc, row) => acc.map((v, i) => v + row[i]), Array(1).fill(0));
+ // Hidden3 layer
+ const da3 = matMul(dz4, transpose(W4));
+ const dz3 = multiply(da3, reluDerivative(z3.flat()).map(v => [v]).reduce((acc, v, i) => {
+ if (i % 4 === 0) acc.push([]);
+ acc[acc.length - 1].push(v[0]);
+ return acc;
+ }, []));
+ const dW3 = matMul(transpose(a2), dz3);
+ const db3 = dz3.reduce((acc, row) => acc.map((v, i) => v + row[i]), Array(4).fill(0));
+ // Hidden2 layer
+ const da2 = matMul(dz3, transpose(W3));
+ const dz2 = multiply(da2, reluDerivative(z2.flat()).map(v => [v]).reduce((acc, v, i) => {
+ if (i % 4 === 0) acc.push([]);
+ acc[acc.length - 1].push(v[0]);
+ return acc;
+ }, []));
+ const dW2 = matMul(transpose(a1), dz2);
+ const db2 = dz2.reduce((acc, row) => acc.map((v, i) => v + row[i]), Array(4).fill(0));
+ // Hidden1 layer
+ const da1 = matMul(dz2, transpose(W2));
+ const dz1 = multiply(da1, reluDerivative(z1.flat()).map(v => [v]).reduce((acc, v, i) => {
+ if (i % 4 === 0) acc.push([]);
+ acc[acc.length - 1].push(v[0]);
+ return acc;
+ }, []));
+ const dW1 = matMul(transpose(X), dz1);
+ const db1 = dz1.reduce((acc, row) => acc.map((v, i) => v + row[i]), Array(4).fill(0));
+ setGrads({ dW1, db1, dW2, db2, dW3, db3, dW4, db4 });
+ setStep('backprop');
+ }
+
+ function handleGradientDescent() { // Run one step of gradient descent
+ if (!grads.dW1) return;
+ const lr = 0.1; // Learning rate
+ // Update weights and biases for each layer
+ const newW1 = W1.map((row, i) => row.map((v, j) => v - lr * grads.dW1[i][j]));
+ const newB1 = b1.map((v, i) => v - lr * grads.db1[i] / 4);
+ const newW2 = W2.map((row, i) => row.map((v, j) => v - lr * grads.dW2[i][j]));
+ const newB2 = b2.map((v, i) => v - lr * grads.db2[i] / 4);
+ const newW3 = W3.map((row, i) => row.map((v, j) => v - lr * grads.dW3[i][j]));
+ const newB3 = b3.map((v, i) => v - lr * grads.db3[i] / 4);
+ const newW4 = W4.map((row, i) => row.map((v, j) => v - lr * grads.dW4[i][j]));
+ const newB4 = b4.map((v, i) => v - lr * grads.db4[i] / 4);
+ setW1(newW1);
+ setB1(newB1);
+ setW2(newW2);
+ setB2(newB2);
+ setW3(newW3);
+ setB3(newB3);
+ setW4(newW4);
+ setB4(newB4);
+ // Immediately run forward pass with new weights to update loss/activations
+ setTimeout(() => {
+ forwardPass({ W1: newW1, b1: newB1, W2: newW2, b2: newB2, W3: newW3, b3: newB3, W4: newW4, b4: newB4 });
+ setStep('descent');
+ }, 0);
+ }
+
+ // --- Epoch handler: run multiple gradient descent steps ---
+ function handleEpoch() {
+ let count = 10; // Number of epochs per click
+ function runEpoch(i) {
+ if (i === 0) {
+ setEpoch(e => e + count);
+ return;
+ }
+ handleBackprop();
+ setTimeout(() => {
+ handleGradientDescent();
+ setTimeout(() => runEpoch(i - 1), 10);
+ }, 10);
+ }
+ runEpoch(count);
+ }
+
+ // --- Helper: pretty print a matrix ---
+ function printMatrix(mat, decimals = 3) {
+ return (
+
+ {/* Inline CSS for button hover and focus */}
+
+ {/* Title */}
+
Neural Network Simulator
+ {/* Subtitle */}
+
+ A simple 3-hidden-layer neural net (ReLU, Sigmoid) trained on XOR.
+
+
+ Each button runs a step. See activations, loss, and weights below.
+ Tip: Hover buttons for explanations.
+
+
+ {/* Neural Network Diagram */}
+
+ {/* Buttons for each process */}
+
+
+
+
+
+
+ {/* Show current loss */}
+ {loss !== null && (
+
+ Loss (MSE):{loss.toFixed(5)}
+ Epoch: {epoch}
+
+ )}
+ {/* Show activations if available */}
+ {activations.a4 && (
+
+ Dataset: XOR (inputs: [0,0], [0,1], [1,0], [1,1])
+ Output: 1 if inputs differ, else 0.
+ Try: Run Forward Pass, then Backpropagation, then Gradient Descent repeatedly.
+ All math and code is manual, no ML libraries used.
+
+
+ );
+}
\ No newline at end of file
diff --git a/Exercise 7/src/components/UseReducerChallenge.jsx b/Exercise 7/src/components/UseReducerChallenge.jsx
new file mode 100644
index 00000000..18951985
--- /dev/null
+++ b/Exercise 7/src/components/UseReducerChallenge.jsx
@@ -0,0 +1,295 @@
+// Import React and the useReducer hook from the React library
+import React, { useReducer } from 'react';
+
+/**
+ * Challenge: Create a shopping cart using useReducer
+ *
+ * Requirements:
+ * 1. Add, remove, update quantity, and clear cart functionality
+ * 2. Use action types & reducer logic
+ * 3. Display cart items, total count & total price
+ * 4. Style it so it actually looks like a mini‐shop
+ */
+
+// Define the initial state for the shopping cart
+const initialState = {
+ items: [], // Start with an empty array of items in the cart
+ totalItems: 0, // Start with zero total items
+ totalPrice: 0, // Start with a total price of zero
+};
+
+// Define action types as constants to avoid typos and make code easier to manage
+const ACTIONS = {
+ ADD_ITEM: 'add_item', // Action for adding an item to the cart
+ REMOVE_ITEM: 'remove_item', // Action for removing an item from the cart
+ UPDATE_QUANTITY: 'update_quantity', // Action for updating the quantity of an item
+ CLEAR_CART: 'clear_cart', // Action for clearing the entire cart
+};
+
+// Create a list of products that can be added to the cart
+const products = [
+ { id: 1, name: 'React Cookbook', price: 29.99 }, // Product 1
+ { id: 2, name: 'JS T-Shirt', price: 19.99 }, // Product 2
+ { id: 3, name: 'Node Sticker Pack', price: 4.99 }, // Product 3
+];
+
+// Define the reducer function that will handle all cart actions
+function cartReducer(state, action) {
+ // Use a switch statement to handle different action types
+ switch (action.type) {
+ case ACTIONS.ADD_ITEM: { // If the action is to add an item
+ const item = action.payload; // Get the item to add from the action payload
+ // Check if the item already exists in the cart
+ const exists = state.items.find(i => i.id === item.id);
+
+ let newItems; // Will hold the new array of items
+ if (exists) {
+ // If the item exists, increase its quantity by 1
+ newItems = state.items.map(i =>
+ i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
+ );
+ } else {
+ // If the item does not exist, add it with quantity 1
+ newItems = [...state.items, { ...item, quantity: 1 }];
+ }
+
+ // Return the new state with updated items, totalItems, and totalPrice
+ return {
+ items: newItems,
+ totalItems: state.totalItems + 1, // Increase total items by 1
+ totalPrice: +(state.totalPrice + item.price).toFixed(2), // Add item's price to total, rounded to 2 decimals
+ };
+ }
+
+ case ACTIONS.REMOVE_ITEM: { // If the action is to remove an item
+ const id = action.payload; // Get the id of the item to remove
+ // Find the item to remove in the cart
+ const toRemove = state.items.find(i => i.id === id);
+ if (!toRemove) return state; // If item not found, return current state
+
+ // Filter out the item to remove from the items array
+ const filtered = state.items.filter(i => i.id !== id);
+ // Return the new state with updated items, totalItems, and totalPrice
+ return {
+ items: filtered,
+ totalItems: state.totalItems - toRemove.quantity, // Subtract the quantity of removed item from total
+ totalPrice: +(
+ state.totalPrice -
+ toRemove.price * toRemove.quantity // Subtract the total price of removed item(s)
+ ).toFixed(2),
+ };
+ }
+
+ case ACTIONS.UPDATE_QUANTITY: { // If the action is to update quantity
+ const { id, quantity } = action.payload; // Get id and new quantity from payload
+ if (quantity < 1) return state; // If quantity is less than 1, do nothing
+
+ // Map through items and update the quantity for the matching item
+ const updated = state.items.map(i =>
+ i.id === id ? { ...i, quantity } : i
+ );
+ // Calculate the new total number of items
+ const totalItems = updated.reduce((sum, i) => sum + i.quantity, 0);
+ // Calculate the new total price
+ const totalPrice = updated
+ .reduce((sum, i) => sum + i.price * i.quantity, 0)
+ .toFixed(2);
+
+ // Return the new state with updated items, totalItems, and totalPrice
+ return {
+ items: updated,
+ totalItems,
+ totalPrice: +totalPrice, // Convert string to number
+ };
+ }
+
+ case ACTIONS.CLEAR_CART: // If the action is to clear the cart
+ return initialState; // Reset state to initial values
+
+ default: // If action type is not recognized
+ return state; // Return current state unchanged
+ }
+}
+
+// Component to display a single product and an "Add" button
+function ProductItem({ product, dispatch }) {
+ // Render a product card with name, price, and add button
+ return (
+
+
{product.name}
{/* Show product name */}
+
${product.price.toFixed(2)}
{/* Show product price with 2 decimals */}
+
+
+ );
+}
+
+// Component to display a single cart item with quantity controls and remove button
+function CartItem({ item, dispatch }) {
+ // Render a cart row with name, quantity controls, price, and remove button
+ return (
+
+
{item.name}
{/* Show item name */}
+
+ {/* Button to decrease quantity */}
+
+ {/* Show current quantity */}
+ {item.quantity}
+ {/* Button to increase quantity */}
+
+
+ {/* Show total price for this item (price * quantity) */}
+
${(item.price * item.quantity).toFixed(2)}
+ {/* Button to remove this item from the cart */}
+
+
+ );
+}
+
+// Main component that brings everything together
+export default function UseReducerChallenge() {
+ // useReducer returns the current state and a dispatch function to send actions
+ const [state, dispatch] = useReducer(cartReducer, initialState);
+
+ // Render the shopping cart UI
+ return (
+
+ {/* Title for the shopping cart */}
+
🛒 useReducer Shopping Cart
+
+ {/* Section to display available products */}
+
+
Products
+ {/* Map through products and render a ProductItem for each */}
+ {products.map((p) => (
+
+ ))}
+
+
+ {/* Section to display the cart */}
+
+ {/* Show cart summary: number of items and total price */}
+
+
+ {/* If cart is empty, show a message. Otherwise, show cart items */}
+ {state.items.length === 0 ? (
+
Your cart is empty
+ ) : (
+ state.items.map((item) => (
+
+ ))
+ )}
+
+ {/* If there are items in the cart, show a "Clear Cart" button */}
+ {state.items.length > 0 && (
+
+ )}
+
+
+ );
+}
+
+// Define styles for the components as a JavaScript object
+const styles = {
+ container: {
+ fontFamily: 'sans-serif', // Use a sans-serif font for the whole app
+ padding: '20px', // Add padding around the container
+ maxWidth: '600px', // Limit the width of the container
+ margin: 'auto', // Center the container horizontally
+ },
+ products: {
+ display: 'flex', // Arrange products in a row
+ gap: '10px', // Add space between product cards
+ marginBottom: '30px', // Add space below the products section
+ flexWrap: 'wrap', // Allow products to wrap to the next line
+ },
+ productItem: {
+ border: '1px solid #ddd', // Light border around each product
+ borderRadius: '5px', // Rounded corners
+ padding: '10px', // Padding inside the product card
+ width: '150px', // Fixed width for each product card
+ textAlign: 'center', // Center text inside the card
+ },
+ addButton: {
+ marginTop: '10px', // Space above the add button
+ cursor: 'pointer', // Show pointer cursor on hover
+ },
+ cart: {
+ borderTop: '2px solid #333', // Dark border above the cart section
+ paddingTop: '20px', // Space above the cart content
+ },
+ cartItem: {
+ display: 'flex', // Arrange cart item content in a row
+ justifyContent: 'space-between', // Space out the content
+ alignItems: 'center', // Vertically center the content
+ borderBottom: '1px solid #eee', // Light border below each cart item
+ padding: '8px 0', // Vertical padding for each cart item
+ },
+ cartName: {
+ flex: 1, // Allow the name to take up available space
+ },
+ qtyButton: {
+ margin: '0 5px', // Space on left and right of quantity buttons
+ cursor: 'pointer', // Show pointer cursor on hover
+ },
+ qty: {
+ minWidth: '20px', // Minimum width for the quantity display
+ textAlign: 'center', // Center the quantity text
+ },
+ removeButton: {
+ cursor: 'pointer', // Show pointer cursor on hover
+ color: 'red', // Red color for the remove button
+ border: 'none', // No border for the button
+ background: 'none', // No background for the button
+ },
+ clearButton: {
+ marginTop: '15px', // Space above the clear button
+ padding: '8px 12px', // Padding inside the button
+ cursor: 'pointer', // Show pointer cursor on hover
+ },
+ empty: {
+ fontStyle: 'italic', // Italic text for empty cart message
+ color: '#777', // Gray color for the message
+ },
+};
\ No newline at end of file
diff --git a/Exercise 7/src/components/UseRefChallenge.jsx b/Exercise 7/src/components/UseRefChallenge.jsx
new file mode 100644
index 00000000..84709ce0
--- /dev/null
+++ b/Exercise 7/src/components/UseRefChallenge.jsx
@@ -0,0 +1,391 @@
+// Import React and some useful hooks from the 'react' library
+import React, { useRef, useState, useEffect } from 'react';
+
+// This is a React functional component called UseRefChallenge
+const UseRefChallenge = () => {
+ // Create a reference to the input element so we can focus it later
+ const inputRef = useRef(null);
+
+ // Create a reference to store the interval ID for the stopwatch timer
+ const timerIdRef = useRef(null);
+
+ // Create a reference to store the previous time value for the stopwatch
+ const prevTimeRef = useRef(0);
+
+ // Create a reference to the box element for detecting clicks outside of it
+ const boxRef = useRef(null);
+
+ // Create a state variable to keep track of the elapsed time in milliseconds
+ const [time, setTime] = useState(0);
+
+ // Create a state variable to know if the stopwatch is running or not
+ const [isRunning, setIsRunning] = useState(false);
+
+ // Create a state variable to count how many times the user has clicked outside the box
+ const [outsideClicks, setOutsideClicks] = useState(0);
+
+ // Create a state variable to store the current value of the input field
+ const [inputValue, setInputValue] = useState('');
+
+ // Create a state variable to store all the values the user has saved from the input
+ const [savedValues, setSavedValues] = useState([]);
+
+ // This useEffect runs once when the component mounts (because of the empty array [])
+ useEffect(() => {
+ // Focus the input element as soon as the component appears on the page
+ inputRef.current.focus();
+ }, []);
+
+ // This useEffect sets up and cleans up the click outside detector
+ useEffect(() => {
+ // This function will be called whenever the user clicks anywhere on the page
+ const handleClickOutside = (event) => {
+ // If the boxRef exists and the clicked element is NOT inside the box
+ if (boxRef.current && !boxRef.current.contains(event.target)) {
+ // Increase the outsideClicks counter by 1
+ setOutsideClicks(prev => prev + 1);
+ }
+ };
+
+ // Add an event listener to the whole document for mouse down events
+ document.addEventListener('mousedown', handleClickOutside);
+
+ // Cleanup function: remove the event listener when the component unmounts
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, []);
+
+ // This function starts the stopwatch timer
+ const startTimer = () => {
+ // Only start if the timer isn't already running
+ if (!isRunning) {
+ // Set the running state to true
+ setIsRunning(true);
+ // Calculate the starting point for the timer (handles resume after pause)
+ prevTimeRef.current = Date.now() - time;
+ // Start a new interval that updates the time every 10 milliseconds
+ timerIdRef.current = setInterval(() => {
+ // Update the time state with the elapsed time
+ setTime(Date.now() - prevTimeRef.current);
+ }, 10);
+ }
+ };
+
+ // This function stops (pauses) the stopwatch timer
+ const stopTimer = () => {
+ // Only stop if the timer is currently running
+ if (isRunning) {
+ // Clear the interval so the timer stops updating
+ clearInterval(timerIdRef.current);
+ // Set the running state to false
+ setIsRunning(false);
+ }
+ };
+
+ // This function resets the stopwatch timer to zero
+ const resetTimer = () => {
+ // Clear the interval in case it's running
+ clearInterval(timerIdRef.current);
+ // Set the running state to false
+ setIsRunning(false);
+ // Reset the time state to zero
+ setTime(0);
+ };
+
+ // This function formats the time in mm:ss:ms for display
+ const formatTime = () => {
+ // Calculate the number of minutes
+ const minutes = Math.floor(time / 60000);
+ // Calculate the number of seconds (after removing minutes)
+ const seconds = Math.floor((time % 60000) / 1000);
+ // Calculate the number of hundredths of a second (after removing seconds)
+ const milliseconds = Math.floor((time % 1000) / 10);
+
+ // Return a string in the format "mm:ss:ms", always two digits each
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}:${milliseconds.toString().padStart(2, '0')}`;
+ };
+
+ // This function updates the inputValue state when the user types in the input
+ const handleInputChange = (e) => {
+ // Set the inputValue state to whatever the user typed
+ setInputValue(e.target.value);
+ };
+
+ // This function saves the current input value to the savedValues array
+ const handleSaveValue = () => {
+ // Only save if the input isn't just empty spaces
+ if (inputValue.trim()) {
+ // Add the current inputValue to the end of the savedValues array
+ setSavedValues(prev => [...prev, inputValue]);
+ // Clear the input field
+ setInputValue('');
+ // Focus the input again so the user can keep typing
+ inputRef.current.focus();
+ }
+ };
+
+ // This is the JSX that defines what the component looks like on the page
+ return (
+ // The main container div with some styling
+
+ {/* The main title of the page */}
+
useRef Challenge
+
+ {/* Section for the auto-focus input */}
+
+ {/* Section title */}
+
Auto-Focus Input
+ {/* Container for the input and save button */}
+
+ {/* The input field, connected to inputRef and inputValue */}
+
+ {/* Button to save the current input value */}
+
+
+
+ {/* If there are any saved values, show them in a list */}
+ {savedValues.length > 0 && (
+
+ {/* Title for the saved values list */}
+
Saved Values:
+ {/* List of saved values */}
+
+ {/* Map over each saved value and display it in a list item */}
+ {savedValues.map((value, index) => (
+
+ {value}
+
+ ))}
+
+
+ )}
+
+
+ {/* Section for the stopwatch */}
+
+ {/* Section title */}
+
Stopwatch using useRef
+ {/* Container for the stopwatch display and buttons */}
+
+ {/* Display the formatted time */}
+
{formatTime()}
+ {/* Group of buttons for controlling the stopwatch */}
+
+ {/* Start button, only enabled if not running */}
+
+ {/* Stop button, only enabled if running */}
+
+ {/* Reset button, always enabled */}
+
+
+
+
+
+ {/* Section for the click outside detector */}
+
+ {/* Section title */}
+
Click Outside Detector
+ {/* Description for the user */}
+
+ Click anywhere outside the box to increase the counter.
+
+ {/* Container for the box and the counter */}
+
+ {/* The box we're tracking clicks outside of */}
+
+
I'm tracking clicks outside me!
+
+ {/* Display the number of outside clicks */}
+
+ Outside Clicks: {outsideClicks}
+
+
+
+
+ );
+};
+
+// This object contains all the styles used in the component
+const styles = {
+ // Style for the main container
+ container: {
+ maxWidth: '800px', // Maximum width of the container
+ margin: '0 auto', // Center the container horizontally
+ padding: '20px', // Padding inside the container
+ backgroundColor: '#f8f9fa', // Light gray background
+ borderRadius: '10px', // Rounded corners
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', // Subtle shadow
+ fontFamily: 'Arial, sans-serif', // Font for all text
+ },
+ // Style for the main title
+ title: {
+ textAlign: 'center', // Center the text
+ color: '#343a40', // Dark gray color
+ marginBottom: '30px', // Space below the title
+ fontSize: '28px', // Large font size
+ },
+ // Style for each section
+ section: {
+ backgroundColor: '#ffffff', // White background
+ padding: '20px', // Padding inside the section
+ borderRadius: '8px', // Rounded corners
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)', // Light shadow
+ marginBottom: '25px', // Space below the section
+ },
+ // Style for section titles
+ sectionTitle: {
+ color: '#343a40', // Dark gray color
+ borderBottom: '1px solid #e9ecef', // Light border below the title
+ paddingBottom: '10px', // Space below the title text
+ marginTop: '0', // No space above the title
+ },
+ // Style for the input and button container
+ inputContainer: {
+ display: 'flex', // Arrange children in a row
+ gap: '10px', // Space between input and button
+ marginBottom: '15px', // Space below the container
+ },
+ // Style for the input field
+ input: {
+ flex: '1', // Take up remaining space
+ padding: '12px', // Padding inside the input
+ fontSize: '16px', // Font size
+ borderRadius: '5px', // Rounded corners
+ border: '1px solid #ced4da', // Light border
+ outline: 'none', // No outline when focused
+ transition: 'border-color 0.3s', // Smooth border color change
+ },
+ // Style for all buttons
+ button: {
+ padding: '10px 15px', // Padding inside the button
+ fontSize: '16px', // Font size
+ backgroundColor: '#007bff', // Blue background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ transition: 'background-color 0.3s', // Smooth color change
+ },
+ // Style for the container of saved values
+ savedValuesContainer: {
+ marginTop: '15px', // Space above the container
+ },
+ // Style for the saved values title
+ savedValuesTitle: {
+ color: '#495057', // Medium gray color
+ margin: '10px 0', // Space above and below the title
+ },
+ // Style for the list of saved values
+ savedValuesList: {
+ backgroundColor: '#f8f9fa', // Light gray background
+ borderRadius: '5px', // Rounded corners
+ padding: '10px', // Padding inside the list
+ margin: '0', // No margin
+ },
+ // Style for each saved value item
+ savedValueItem: {
+ padding: '8px 0', // Space above and below each item
+ borderBottom: '1px solid #e9ecef', // Light border below each item
+ listStyleType: 'none', // No bullet points
+ },
+ // Style for the stopwatch container
+ stopwatchContainer: {
+ display: 'flex', // Arrange children in a column
+ flexDirection: 'column', // Vertical layout
+ alignItems: 'center', // Center horizontally
+ },
+ // Style for the time display
+ timeDisplay: {
+ fontSize: '36px', // Large font size
+ fontFamily: 'monospace', // Monospace font for numbers
+ backgroundColor: '#343a40', // Dark background
+ color: '#ffffff', // White text
+ padding: '15px 30px', // Padding inside the display
+ borderRadius: '8px', // Rounded corners
+ marginBottom: '20px', // Space below the display
+ width: '200px', // Fixed width
+ textAlign: 'center', // Center the text
+ },
+ // Style for the group of stopwatch buttons
+ buttonGroup: {
+ display: 'flex', // Arrange buttons in a row
+ gap: '15px', // Space between buttons
+ },
+ // Style for the description text
+ description: {
+ color: '#6c757d', // Gray color
+ marginBottom: '15px', // Space below the description
+ fontStyle: 'italic', // Italic text
+ },
+ // Style for the detector container
+ detectorContainer: {
+ display: 'flex', // Arrange children in a column
+ flexDirection: 'column', // Vertical layout
+ alignItems: 'center', // Center horizontally
+ gap: '20px', // Space between children
+ },
+ // Style for the box that detects outside clicks
+ box: {
+ width: '250px', // Fixed width
+ height: '150px', // Fixed height
+ backgroundColor: '#007bff', // Blue background
+ color: '#ffffff', // White text
+ display: 'flex', // Center content
+ justifyContent: 'center', // Center horizontally
+ alignItems: 'center', // Center vertically
+ borderRadius: '8px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ transition: 'transform 0.3s, box-shadow 0.3s', // Smooth transitions
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', // Shadow for depth
+ },
+ // Style for the text inside the box
+ boxText: {
+ textAlign: 'center', // Center the text
+ padding: '10px', // Padding inside the text
+ fontSize: '18px', // Font size
+ fontWeight: 'bold', // Bold text
+ },
+ // Style for the outside clicks counter
+ clicksCounter: {
+ fontSize: '18px', // Font size
+ padding: '10px 15px', // Padding inside the counter
+ backgroundColor: '#e9ecef', // Light gray background
+ borderRadius: '5px', // Rounded corners
+ color: '#343a40', // Dark gray text
+ },
+ // Style for the number in the outside clicks counter
+ clicksValue: {
+ fontWeight: 'bold', // Bold number
+ color: '#dc3545', // Red color
+ },
+};
+
+// Export the UseRefChallenge component so it can be used in other files
+export default UseRefChallenge;
\ No newline at end of file
diff --git a/Exercise 7/src/components/UseStateChallenge.jsx b/Exercise 7/src/components/UseStateChallenge.jsx
new file mode 100644
index 00000000..dc19e849
--- /dev/null
+++ b/Exercise 7/src/components/UseStateChallenge.jsx
@@ -0,0 +1,301 @@
+// Import React and the useState hook from the 'react' library
+import React, { useState } from 'react';
+
+/**
+ * Challenge: Create a counter component using useState
+ *
+ * Requirements:
+ * 1. Implement a counter with increment, decrement, and reset functionality
+ * 2. Add a count history feature that tracks previous counter values
+ * 3. Allow setting the counter to a specific value using an input field
+ * 4. Add some styling to make it visually appealing
+ */
+
+// Define a functional React component called UseStateChallenge
+const UseStateChallenge = () => {
+ // Declare a state variable 'count' to store the current counter value, and 'setCount' to update it. Start at 0.
+ const [count, setCount] = useState(0);
+
+ // Declare a state variable 'countHistory' to store an array of previous counter values, and 'setCountHistory' to update it. Start as an empty array.
+ const [countHistory, setCountHistory] = useState([]);
+
+ // Declare a state variable 'inputValue' to store the value from the input field, and 'setInputValue' to update it. Start as an empty string.
+ const [inputValue, setInputValue] = useState('');
+
+ // Define a function to increment the counter by 1
+ const handleIncrement = () => {
+ // Use the previous count value to calculate the new count
+ setCount(prevCount => {
+ // Add 1 to the previous count
+ const newCount = prevCount + 1;
+ // Add the new count to the history array
+ setCountHistory(prevHistory => [...prevHistory, newCount]);
+ // Return the new count so React updates the state
+ return newCount;
+ });
+ };
+
+ // Define a function to decrement the counter by 1
+ const handleDecrement = () => {
+ // Use the previous count value to calculate the new count
+ setCount(prevCount => {
+ // Subtract 1 from the previous count
+ const newCount = prevCount - 1;
+ // Add the new count to the history array
+ setCountHistory(prevHistory => [...prevHistory, newCount]);
+ // Return the new count so React updates the state
+ return newCount;
+ });
+ };
+
+ // Define a function to reset the counter to 0
+ const handleReset = () => {
+ // Set the counter value to 0
+ setCount(0);
+ // Add 0 to the history array
+ setCountHistory(prevHistory => [...prevHistory, 0]);
+ };
+
+ // Define a function to handle changes in the input field
+ const handleInputChange = (e) => {
+ // Update the inputValue state with the new value from the input field
+ setInputValue(e.target.value);
+ };
+
+ // Define a function to set the counter to the value entered in the input field
+ const handleSetCounterValue = () => {
+ // Convert the input value from a string to an integer
+ const parsedValue = parseInt(inputValue, 10);
+ // Check if the parsed value is a valid number (not NaN)
+ if (!isNaN(parsedValue)) {
+ // Set the counter to the parsed value
+ setCount(parsedValue);
+ // Add the new value to the history array
+ setCountHistory(prevHistory => [...prevHistory, parsedValue]);
+ // Clear the input field
+ setInputValue('');
+ }
+ };
+
+ // Define a function to clear the count history
+ const handleClearHistory = () => {
+ // Set the history array to an empty array
+ setCountHistory([]);
+ };
+
+ // The component returns JSX to render the UI
+ return (
+ // Main container div with a class and inline styles
+
+ {/* Title of the counter app */}
+
useState Counter Challenge
+
+ {/* Display the current counter value */}
+
+
{count}
+
+
+ {/* Container for the increment, decrement, and reset buttons */}
+
+ {/* Button to decrement the counter */}
+
+ {/* Button to reset the counter */}
+
+ {/* Button to increment the counter */}
+
+
+
+ {/* Container for the input field and set value button */}
+
+ {/* Input field to enter a specific counter value */}
+
+ {/* Button to set the counter to the input value */}
+
+
+
+ {/* Container for the count history section */}
+
+ {/* Header for the history section, includes title and clear button */}
+
+ {/* Title for the history section */}
+
Count History
+ {/* Button to clear the history */}
+
+
+
+ {/* List of previous counter values */}
+
+ {/* If there is no history, show a message */}
+ {countHistory.length === 0 ? (
+
No history yet
+ ) : (
+ // Otherwise, map over the history array and display each value
+ countHistory.map((value, index) => (
+ // Each history item is shown in a styled div
+
+ {/* Show the index (starting at 1) and the value */}
+ {index + 1}. Changed to {value}
+
+ ))
+ )}
+
+
+
+ );
+};
+
+// Define a 'styles' object to store all the inline styles for the component
+const styles = {
+ // Styles for the main container
+ container: {
+ maxWidth: '600px', // Maximum width of the container
+ margin: '0 auto', // Center the container horizontally
+ padding: '20px', // Padding inside the container
+ backgroundColor: '#f8f9fa', // Light background color
+ borderRadius: '10px', // Rounded corners
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', // Subtle shadow for depth
+ fontFamily: 'Arial, sans-serif', // Font for the text
+ },
+ // Styles for the title
+ title: {
+ textAlign: 'center', // Center the text
+ color: '#343a40', // Dark gray color
+ marginBottom: '20px', // Space below the title
+ },
+ // Styles for the counter display area
+ counterDisplay: {
+ backgroundColor: '#ffffff', // White background
+ padding: '20px', // Padding inside the box
+ borderRadius: '8px', // Rounded corners
+ boxShadow: 'inset 0 2px 4px rgba(0, 0, 0, 0.1)', // Inner shadow for effect
+ marginBottom: '20px', // Space below the counter
+ },
+ // Styles for the counter value
+ counterValue: {
+ textAlign: 'center', // Center the number
+ fontSize: '48px', // Large font size
+ margin: '0', // No margin
+ color: '#007bff', // Blue color
+ },
+ // Styles for the button container
+ buttonContainer: {
+ display: 'flex', // Use flexbox layout
+ justifyContent: 'space-between', // Space buttons evenly
+ marginBottom: '20px', // Space below the buttons
+ },
+ // Styles for the increment, decrement, and reset buttons
+ button: {
+ padding: '10px 20px', // Padding inside the button
+ fontSize: '16px', // Font size
+ backgroundColor: '#007bff', // Blue background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ transition: 'all 0.3s ease', // Smooth transition for hover effects
+ },
+ // Styles for the input and set value button container
+ inputContainer: {
+ display: 'flex', // Use flexbox layout
+ marginBottom: '20px', // Space below the input area
+ gap: '10px', // Space between input and button
+ },
+ // Styles for the input field
+ input: {
+ flex: '1', // Take up remaining space
+ padding: '10px', // Padding inside the input
+ fontSize: '16px', // Font size
+ borderRadius: '5px', // Rounded corners
+ border: '1px solid #ced4da', // Light gray border
+ },
+ // Styles for the set value button
+ setButton: {
+ padding: '10px 15px', // Padding inside the button
+ fontSize: '16px', // Font size
+ backgroundColor: '#28a745', // Green background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ },
+ // Styles for the history container
+ historyContainer: {
+ backgroundColor: '#ffffff', // White background
+ padding: '15px', // Padding inside the box
+ borderRadius: '8px', // Rounded corners
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', // Subtle shadow
+ },
+ // Styles for the history header (title and clear button)
+ historyHeader: {
+ display: 'flex', // Use flexbox layout
+ justifyContent: 'space-between', // Space title and button apart
+ alignItems: 'center', // Vertically center items
+ marginBottom: '10px', // Space below the header
+ },
+ // Styles for the history title
+ historyTitle: {
+ margin: '0', // No margin
+ color: '#343a40', // Dark gray color
+ },
+ // Styles for the clear history button
+ clearButton: {
+ padding: '5px 10px', // Padding inside the button
+ fontSize: '14px', // Font size
+ backgroundColor: '#dc3545', // Red background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ },
+ // Styles for the history list container
+ historyList: {
+ maxHeight: '200px', // Maximum height before scrolling
+ overflowY: 'auto', // Add vertical scroll if needed
+ },
+ // Styles for each history item
+ historyItem: {
+ padding: '8px', // Padding inside the item
+ borderBottom: '1px solid #e9ecef', // Light gray line below each item
+ color: '#495057', // Medium gray text
+ },
+ // Styles for the empty history message
+ emptyHistory: {
+ textAlign: 'center', // Center the text
+ color: '#6c757d', // Light gray color
+ fontStyle: 'italic', // Italic text
+ },
+};
+
+// Export the UseStateChallenge component so it can be used in other files
+export default UseStateChallenge;
\ No newline at end of file
diff --git a/Exercise 7/src/components/useContextChallenge/AuthContext.jsx b/Exercise 7/src/components/useContextChallenge/AuthContext.jsx
new file mode 100644
index 00000000..51c588e4
--- /dev/null
+++ b/Exercise 7/src/components/useContextChallenge/AuthContext.jsx
@@ -0,0 +1,200 @@
+// Import React and some useful hooks and functions from the 'react' library
+import React, { createContext, useState, useEffect } from 'react';
+
+// This is a comment block describing the challenge and requirements for this file
+/**
+ * Challenge: Create an authentication context for user management
+ *
+ * Requirements:
+ * 1. Create a context for authentication state
+ * 2. Implement login/logout functionality
+ * 3. Add user profile and preferences
+ * 4. Create a provider component that will wrap the application
+ */
+
+// Create a new context object for authentication, which will be used to share data across components
+export const AuthContext = createContext();
+
+// Define the AuthProvider component, which will wrap parts of the app that need authentication info
+export const AuthProvider = ({ children }) => {
+ // Create a state variable to track if the user is authenticated, default is false (not logged in)
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ // Create a state variable to store user information, default is null (no user)
+ const [user, setUser] = useState(null);
+ // Create a state variable to show if the app is loading authentication info, default is true (loading)
+ const [loading, setLoading] = useState(true);
+ // Create a state variable to store any error messages, default is null (no error)
+ const [error, setError] = useState(null);
+ // Create a state variable for the user's theme preference, default is 'light'
+ const [theme, setTheme] = useState('light');
+ // Create a state variable for notification settings, default is true (notifications on)
+ const [notifications, setNotifications] = useState(true);
+
+ // useEffect runs code when the component mounts (loads) or updates
+ useEffect(() => {
+ // Define an async function to check if the user is already logged in
+ const checkAuth = async () => {
+ try {
+ // Simulate a delay (like an API call) using setTimeout for 1 second
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Try to get stored user data from the browser's localStorage
+ const storedUser = localStorage.getItem('user');
+ // Try to get stored theme preference from localStorage
+ const storedTheme = localStorage.getItem('theme');
+ // Try to get stored notification setting from localStorage
+ const storedNotifications = localStorage.getItem('notifications');
+
+ // If there is a stored user, update the user state and set authenticated to true
+ if (storedUser) {
+ setUser(JSON.parse(storedUser)); // Parse the user data from JSON
+ setIsAuthenticated(true); // Set authentication to true
+ }
+
+ // If there is a stored theme, update the theme state
+ if (storedTheme) {
+ setTheme(storedTheme);
+ }
+
+ // If there is a stored notification setting, update the notifications state
+ if (storedNotifications !== null) {
+ setNotifications(JSON.parse(storedNotifications)); // Parse the boolean value
+ }
+ } catch (err) {
+ // If there is an error, set the error state with a message
+ setError('Failed to authenticate');
+ // Also log the error to the console for debugging
+ console.error('Auth check error:', err);
+ } finally {
+ // After everything is done (success or error), set loading to false
+ setLoading(false);
+ }
+ };
+
+ // Call the checkAuth function when the component mounts
+ checkAuth();
+ }, []); // The empty array means this runs only once when the component mounts
+
+ // Define a function to log in the user, takes email and password as arguments
+ const login = async (email, password) => {
+ // Set loading to true while logging in
+ setLoading(true);
+ // Clear any previous error messages
+ setError(null);
+
+ try {
+ // Simulate a delay (like an API call) using setTimeout for 1 second
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Check if the email and password match the demo credentials
+ if (email === 'user@example.com' && password === 'password') {
+ // If credentials are correct, create a user object with some info
+ const userData = {
+ id: '1',
+ name: 'Demo User',
+ email: 'user@example.com',
+ avatar: 'https://via.placeholder.com/150',
+ role: 'user',
+ lastLogin: new Date().toISOString(), // Store the current date/time
+ };
+
+ // Update the user state with the new user data
+ setUser(userData);
+ // Set authentication to true
+ setIsAuthenticated(true);
+
+ // Save the user data to localStorage so it persists after refresh
+ localStorage.setItem('user', JSON.stringify(userData));
+
+ // Return an object indicating login was successful
+ return { success: true };
+ } else {
+ // If credentials are wrong, throw an error
+ throw new Error('Invalid credentials');
+ }
+ } catch (err) {
+ // If there is an error, set the error state with the error message
+ setError(err.message || 'Login failed');
+ // Return an object indicating login failed, with the error message
+ return { success: false, error: err.message };
+ } finally {
+ // After everything is done (success or error), set loading to false
+ setLoading(false);
+ }
+ };
+
+ // Define a function to log out the user
+ const logout = () => {
+ // Set authentication to false
+ setIsAuthenticated(false);
+ // Clear the user state (no user)
+ setUser(null);
+ // Remove the user data from localStorage
+ localStorage.removeItem('user');
+ };
+
+ // Define a function to update the user's profile with new data
+ const updateProfile = (updatedData) => {
+ // Create a new user object by merging the current user and the updated data
+ const updatedUser = { ...user, ...updatedData };
+ // Update the user state with the new user object
+ setUser(updatedUser);
+ // Save the updated user data to localStorage
+ localStorage.setItem('user', JSON.stringify(updatedUser));
+ };
+
+ // Define a function to toggle the theme between 'light' and 'dark'
+ const toggleTheme = () => {
+ // If the current theme is 'light', change to 'dark', otherwise change to 'light'
+ const newTheme = theme === 'light' ? 'dark' : 'light';
+ // Update the theme state with the new theme
+ setTheme(newTheme);
+ // Save the new theme to localStorage
+ localStorage.setItem('theme', newTheme);
+ };
+
+
+ // Define a function to toggle notifications on or off
+ const toggleNotifications = () => {
+ // Flip the current notifications value (true becomes false, false becomes true)
+ const newNotificationSetting = !notifications;
+ // Update the notifications state with the new value
+ setNotifications(newNotificationSetting);
+ // Save the new notifications setting to localStorage
+ localStorage.setItem('notifications', JSON.stringify(newNotificationSetting));
+ };
+
+ // Create an object with all the values and functions we want to share in the context
+ const contextValue = {
+ isAuthenticated, // Whether the user is logged in
+ user, // The user object (or null if not logged in)
+ loading, // Whether authentication info is loading
+ error, // Any error messages
+ theme, // The user's theme preference
+ notifications, // Whether notifications are enabled
+ login, // Function to log in
+ logout, // Function to log out
+ updateProfile, // Function to update user profile
+ toggleTheme, // Function to toggle theme
+ toggleNotifications, // Function to toggle notifications
+ };
+
+ // This is the return statement of our AuthProvider component
+ // We're returning a special React component called "AuthContext.Provider"
+return (
+ // AuthContext.Provider is a built-in component from React's Context API
+ // It allows us to share data (like user info or theme settings) with any nested components
+
+
+ {/* This is where we render any child components inside the provider */}
+ {/* These children will now have access to the contextValue via useContext(AuthContext) */}
+ {children}
+
+ {/* Closing tag for the AuthContext.Provider */}
+
+);
+}
+
+// Export the AuthProvider component as the default export from this file
+// This allows other files to import and use the AuthProvider
+export default AuthProvider;
\ No newline at end of file
diff --git a/Exercise 7/src/components/useContextChallenge/UseContext.jsx b/Exercise 7/src/components/useContextChallenge/UseContext.jsx
new file mode 100644
index 00000000..d615cf2c
--- /dev/null
+++ b/Exercise 7/src/components/useContextChallenge/UseContext.jsx
@@ -0,0 +1,559 @@
+// Import React and two hooks: useContext (to use context) and useState (to manage local state)
+import React, { useContext, useState } from 'react';
+// Import AuthContext (the context object) and AuthProvider (the provider component) from AuthContext file
+import { AuthContext, AuthProvider } from './AuthContext';
+
+/**
+ * Challenge: Create a component that uses the AuthContext
+ *
+ * Requirements:
+ * 1. Use the AuthContext to access authentication state and functions
+ * 2. Implement login/logout UI
+ * 3. Display user profile when logged in
+ * 4. Allow changing user preferences (theme, notifications)
+ * 5. Add styling to make it visually appealing
+ */
+
+// Login Form Component
+// This component displays the login form and handles login logic
+const LoginForm = () => {
+ // Get login function, error message, and loading state from AuthContext
+ const { login, error, loading } = useContext(AuthContext);
+ // Create state for email input
+ const [email, setEmail] = useState('');
+ // Create state for password input
+ const [password, setPassword] = useState('');
+ // Create state to show/hide the login hint
+ const [showHint, setShowHint] = useState(false);
+
+ // Function to handle form submission
+ const handleSubmit = async (e) => {
+ e.preventDefault(); // Prevent default form submission behavior (page reload)
+ await login(email, password); // Call login function from context with email and password
+ };
+
+ // Render the login form UI
+ return (
+
+ {/* Title for the login form */}
+
Login
+
+ {/* If there is an error, show it above the form */}
+ {error &&
{error}
}
+
+ {/* The login form */}
+
+
+ {/* Hint section for test credentials */}
+
+ {/* Button to show/hide the hint */}
+
+
+ {/* If showHint is true, display the hint */}
+ {showHint && (
+
+
Use these credentials:
+
Email: user@example.com
+
Password: password
+
+ )}
+
+
+ );
+};
+
+// User Profile Component
+// This component displays the user's profile and allows editing preferences
+const UserProfile = () => {
+ // Get user info, logout function, updateProfile function, theme, theme toggle, notifications, and notification toggle from context
+ const { user, logout, updateProfile, theme, toggleTheme, notifications, toggleNotifications } = useContext(AuthContext);
+ // State to track if the user is editing their name
+ const [isEditing, setIsEditing] = useState(false);
+ // State for the edited name input
+ const [editedName, setEditedName] = useState(user.name);
+
+ // Function to save the edited name
+ const handleSaveProfile = () => {
+ updateProfile({ name: editedName }); // Call updateProfile with new name
+ setIsEditing(false); // Exit editing mode
+ };
+
+ // Render the user profile UI
+ return (
+
+ {/* Profile header with avatar and user info */}
+
+ {/* User avatar */}
+
+
+
+
+ {/* User information section */}
+
+ {/* If editing, show input and save/cancel buttons; otherwise, show name and edit button */}
+ {isEditing ? (
+
+ {/* Display last login time, formatted as a readable string */}
+
Last login: {new Date(user.lastLogin).toLocaleString()}
+
+
+
+ {/* Preferences section for theme and notifications */}
+
+
User Preferences
+
+ {/* Theme toggle */}
+
+ Theme:
+
+
+
+
+
+ {/* Notifications toggle */}
+
+ Notifications:
+
+
+
+
+ {/* Logout button */}
+
+
+ );
+};
+
+// Loading Spinner Component
+// This component displays a loading spinner and message
+const LoadingSpinner = () => {
+ // Render spinner and loading text
+ return (
+
+
+
Loading...
+
+ );
+};
+
+// Main Component
+// This is the main component that wraps everything in the AuthProvider
+const UseContextChallenge = () => {
+ // Render the AuthProvider and the main UI
+ return (
+
+
+ {/* Title for the challenge */}
+
useContext Authentication Challenge
+ {/* Render the AuthConsumer component, which shows login or profile */}
+
+
+
+ );
+};
+
+// Consumer Component
+// This component decides whether to show the login form or user profile
+const AuthConsumer = () => {
+ // Get authentication state, loading state, and theme from context
+ const { isAuthenticated, loading, theme } = useContext(AuthContext);
+
+ // Apply theme styles to the container
+ const containerStyle = {
+ ...styles.authContainer, // Spread base styles
+ backgroundColor: theme === 'light' ? '#ffffff' : '#343a40', // Set background based on theme
+ color: theme === 'light' ? '#343a40' : '#ffffff', // Set text color based on theme
+ };
+
+ // If loading, show the loading spinner
+ if (loading) {
+ return ;
+ }
+
+ // If authenticated, show user profile; otherwise, show login form
+ return (
+
+ {isAuthenticated ? : }
+
+ );
+};
+
+// Styles object for inline styling throughout the components
+const styles = {
+ container: {
+ maxWidth: '800px', // Maximum width of the container
+ margin: '0 auto', // Center the container horizontally
+ padding: '20px', // Padding inside the container
+ backgroundColor: '#f8f9fa', // Light background color
+ borderRadius: '10px', // Rounded corners
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)', // Subtle shadow
+ fontFamily: 'Arial, sans-serif', // Font family
+ },
+ title: {
+ textAlign: 'center', // Center the title
+ color: '#343a40', // Dark text color
+ marginBottom: '30px', // Space below the title
+ fontSize: '28px', // Large font size
+ },
+ authContainer: {
+ padding: '25px', // Padding inside the auth container
+ borderRadius: '8px', // Rounded corners
+ boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)', // Subtle shadow
+ transition: 'all 0.3s ease', // Smooth transition for style changes
+ },
+ loadingContainer: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack children vertically
+ alignItems: 'center', // Center children horizontally
+ justifyContent: 'center', // Center children vertically
+ padding: '40px', // Padding inside the container
+ },
+ spinner: {
+ width: '50px', // Spinner width
+ height: '50px', // Spinner height
+ border: '5px solid #f3f3f3', // Light gray border
+ borderTop: '5px solid #007bff', // Blue top border for spinning effect
+ borderRadius: '50%', // Make it circular
+ animation: 'spin 1s linear infinite', // Spin animation (requires CSS keyframes)
+ },
+ loadingText: {
+ marginTop: '15px', // Space above the text
+ color: '#6c757d', // Gray text color
+ fontSize: '16px', // Medium font size
+ },
+ loginForm: {
+ maxWidth: '400px', // Maximum width of the form
+ margin: '0 auto', // Center the form horizontally
+ },
+ formTitle: {
+ textAlign: 'center', // Center the form title
+ marginBottom: '20px', // Space below the title
+ },
+ form: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack form fields vertically
+ },
+ formGroup: {
+ marginBottom: '15px', // Space below each form group
+ },
+ label: {
+ marginBottom: '5px', // Space below the label
+ display: 'block', // Make label a block element
+ fontSize: '14px', // Small font size
+ },
+ input: {
+ width: '100%', // Input takes full width
+ padding: '10px', // Padding inside the input
+ fontSize: '16px', // Medium font size
+ borderRadius: '5px', // Rounded corners
+ border: '1px solid #ced4da', // Light gray border
+ backgroundColor: 'transparent', // Transparent background
+ },
+ submitButton: {
+ padding: '12px', // Padding inside the button
+ backgroundColor: '#007bff', // Blue background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ fontSize: '16px', // Medium font size
+ cursor: 'pointer', // Pointer cursor on hover
+ marginTop: '10px', // Space above the button
+ },
+ errorMessage: {
+ color: '#dc3545', // Red text color
+ padding: '10px', // Padding inside the error message
+ borderRadius: '5px', // Rounded corners
+ backgroundColor: '#f8d7da', // Light red background
+ marginBottom: '15px', // Space below the error message
+ textAlign: 'center', // Center the text
+ },
+ hintContainer: {
+ marginTop: '20px', // Space above the hint container
+ textAlign: 'center', // Center the content
+ },
+ hintButton: {
+ backgroundColor: 'transparent', // No background
+ border: 'none', // No border
+ color: '#007bff', // Blue text
+ cursor: 'pointer', // Pointer cursor on hover
+ fontSize: '14px', // Small font size
+ textDecoration: 'underline', // Underline the text
+ },
+ hint: {
+ marginTop: '10px', // Space above the hint
+ padding: '10px', // Padding inside the hint
+ backgroundColor: '#e9ecef', // Light gray background
+ borderRadius: '5px', // Rounded corners
+ fontSize: '14px', // Small font size
+ },
+ profileContainer: {
+ display: 'flex', // Use flexbox
+ flexDirection: 'column', // Stack children vertically
+ gap: '20px', // Space between children
+ },
+ profileHeader: {
+ display: 'flex', // Use flexbox
+ alignItems: 'center', // Center items vertically
+ gap: '20px', // Space between avatar and info
+ },
+ avatarContainer: {
+ width: '100px', // Avatar container width
+ height: '100px', // Avatar container height
+ borderRadius: '50%', // Make it circular
+ overflow: 'hidden', // Hide overflow
+ border: '3px solid #007bff', // Blue border
+ },
+ avatar: {
+ width: '100%', // Avatar image takes full width
+ height: '100%', // Avatar image takes full height
+ objectFit: 'cover', // Cover the container
+ },
+ userInfo: {
+ flex: '1', // Take up remaining space
+ },
+ userName: {
+ fontSize: '24px', // Large font size
+ fontWeight: 'bold', // Bold text
+ margin: '0 0 5px 0', // Margin below the name
+ display: 'flex', // Use flexbox
+ alignItems: 'center', // Center items vertically
+ gap: '10px', // Space between name and edit button
+ },
+ userEmail: {
+ fontSize: '16px', // Medium font size
+ color: '#6c757d', // Gray text color
+ margin: '0 0 5px 0', // Margin below the email
+ },
+ userRole: {
+ fontSize: '14px', // Small font size
+ margin: '0 0 5px 0', // Margin below the role
+ },
+ lastLogin: {
+ fontSize: '12px', // Small font size
+ fontStyle: 'italic', // Italic text
+ margin: '0', // No margin
+ },
+ editButton: {
+ padding: '3px 8px', // Padding inside the button
+ fontSize: '12px', // Small font size
+ backgroundColor: '#6c757d', // Gray background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '3px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ marginLeft: '10px', // Space to the left of the button
+ },
+ editNameContainer: {
+ marginBottom: '10px', // Space below the edit container
+ },
+ editNameInput: {
+ width: '100%', // Input takes full width
+ padding: '8px', // Padding inside the input
+ fontSize: '16px', // Medium font size
+ borderRadius: '5px', // Rounded corners
+ border: '1px solid #ced4da', // Light gray border
+ marginBottom: '10px', // Space below the input
+ backgroundColor: 'transparent', // Transparent background
+ },
+ editButtonGroup: {
+ display: 'flex', // Use flexbox
+ gap: '10px', // Space between buttons
+ },
+ saveButton: {
+ padding: '5px 10px', // Padding inside the button
+ backgroundColor: '#28a745', // Green background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ },
+ cancelButton: {
+ padding: '5px 10px', // Padding inside the button
+ backgroundColor: '#dc3545', // Red background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ cursor: 'pointer', // Pointer cursor on hover
+ },
+ preferencesSection: {
+ marginTop: '20px', // Space above the section
+ padding: '15px', // Padding inside the section
+ borderRadius: '5px', // Rounded corners
+ backgroundColor: 'rgba(0, 0, 0, 0.05)', // Light transparent background
+ },
+ preferencesTitle: {
+ margin: '0 0 15px 0', // Margin below the title
+ fontSize: '18px', // Medium font size
+ },
+ preferenceItem: {
+ display: 'flex', // Use flexbox
+ justifyContent: 'space-between', // Space between label and control
+ alignItems: 'center', // Center items vertically
+ marginBottom: '10px', // Space below the item
+ },
+ preferenceLabel: {
+ fontSize: '16px', // Medium font size
+ },
+ themeToggle: {
+ display: 'flex', // Use flexbox
+ alignItems: 'center', // Center items vertically
+ },
+ themeButton: {
+ padding: '8px 12px', // Padding inside the button
+ borderRadius: '20px', // Pill-shaped button
+ border: '1px solid #ced4da', // Light gray border
+ cursor: 'pointer', // Pointer cursor on hover
+ transition: 'all 0.3s ease', // Smooth transition for style changes
+ },
+ switch: {
+ position: 'relative', // Position relative for slider
+ display: 'inline-block', // Inline-block element
+ width: '60px', // Width of the switch
+ height: '34px', // Height of the switch
+ },
+ slider: {
+ position: 'absolute', // Position absolute for slider
+ cursor: 'pointer', // Pointer cursor on hover
+ top: '0', // Top position
+ left: '0', // Left position
+ right: '0', // Right position
+ bottom: '0', // Bottom position
+ backgroundColor: '#ccc', // Gray background
+ borderRadius: '34px', // Rounded slider
+ transition: '0.4s', // Smooth transition
+ '&:before': {
+ position: 'absolute', // Position absolute for the knob
+ content: '""', // Empty content for the knob
+ height: '26px', // Height of the knob
+ width: '26px', // Width of the knob
+ left: '4px', // Left position
+ bottom: '4px', // Bottom position
+ backgroundColor: 'white', // White knob
+ borderRadius: '50%', // Circular knob
+ transition: '0.4s', // Smooth transition
+ },
+ },
+ logoutButton: {
+ padding: '12px', // Padding inside the button
+ backgroundColor: '#dc3545', // Red background
+ color: '#ffffff', // White text
+ border: 'none', // No border
+ borderRadius: '5px', // Rounded corners
+ fontSize: '16px', // Medium font size
+ cursor: 'pointer', // Pointer cursor on hover
+ marginTop: '10px', // Space above the button
+ width: '100%', // Button takes full width
+ },
+};
+
+// Export the main component as the default export
+export default UseContextChallenge;
\ No newline at end of file
diff --git a/Exercise 7/src/index.css b/Exercise 7/src/index.css
new file mode 100644
index 00000000..151514fc
--- /dev/null
+++ b/Exercise 7/src/index.css
@@ -0,0 +1,53 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
diff --git a/Exercise 7/src/index.jsx b/Exercise 7/src/index.jsx
new file mode 100644
index 00000000..8ed7c441
--- /dev/null
+++ b/Exercise 7/src/index.jsx
@@ -0,0 +1,15 @@
+// index.jsx is what allows us to run the App.jsx file
+
+// import React and ReactDOM from the react and react-dom packages
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App.jsx';
+
+// import the App component from the App.jsx file
+// the 'body' equivalent in HTML
+// the 'root' element is where the App component will be rendered
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+);
diff --git a/Exercise 7/vite.config.js b/Exercise 7/vite.config.js
new file mode 100644
index 00000000..a0f4db5a
--- /dev/null
+++ b/Exercise 7/vite.config.js
@@ -0,0 +1,10 @@
+import { defineConfig } from "vite"
+import react from "@vitejs/plugin-react"
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ server: {
+ port: 3000
+ },
+ plugins: [react()]
+})
diff --git a/PDFs/Bootstrap_Cheat-Sheet.pdf b/PDFs/Bootstrap_Cheat-Sheet.pdf
new file mode 100644
index 00000000..dc2fe553
Binary files /dev/null and b/PDFs/Bootstrap_Cheat-Sheet.pdf differ
diff --git a/PDFs/CSS_Attributes_Cheat_Sheet.pdf b/PDFs/CSS_Attributes_Cheat_Sheet.pdf
new file mode 100644
index 00000000..d264504f
Binary files /dev/null and b/PDFs/CSS_Attributes_Cheat_Sheet.pdf differ
diff --git a/PDFs/CSS_Selectors_Cheat-Sheet.pdf b/PDFs/CSS_Selectors_Cheat-Sheet.pdf
new file mode 100644
index 00000000..0a693e3d
Binary files /dev/null and b/PDFs/CSS_Selectors_Cheat-Sheet.pdf differ
diff --git a/PDFs/JS_React_Syntax_Cheat_Sheet.pdf b/PDFs/JS_React_Syntax_Cheat_Sheet.pdf
new file mode 100644
index 00000000..eedfae06
Binary files /dev/null and b/PDFs/JS_React_Syntax_Cheat_Sheet.pdf differ
diff --git a/PDFs/React_Cheat_Sheet.pdf b/PDFs/React_Cheat_Sheet.pdf
new file mode 100644
index 00000000..eb1b6baf
Binary files /dev/null and b/PDFs/React_Cheat_Sheet.pdf differ