Module 4: Build Sprint 3

Understanding Your Third Ticket

Learn how to approach your third ticket in the Labs project and understand the development workflow.

Third Ticket Details

View your third ticket details and requirements on GitHub:

Third Ticket Documentation

Approaching Your Third Feature

Learn how to implement a React client application that integrates with your backend API.

Implementation Checklist

  • Review and select 3 pages from the provided wireframes
  • Set up React project structure and routing
  • Implement authentication flow with JWT
  • Create reusable components for common UI elements
  • Integrate with backend API endpoints
  • Implement responsive design
  • Add error handling and loading states

React Project Structure

// Example project structure
src/
├── components/
│   ├── common/
│   │   ├── Button.jsx
│   │   ├── Card.jsx
│   │   └── Navbar.jsx
│   ├── auth/
│   │   ├── LoginForm.jsx
│   │   └── ProtectedRoute.jsx
│   └── pages/
│       ├── Home.jsx
│       ├── Dashboard.jsx
│       └── AssignmentView.jsx
├── services/
│   ├── api.js
│   └── auth.js
├── context/
│   └── AuthContext.jsx
└── utils/
    └── helpers.js

Authentication Context Example

// Example of authentication context setup
import { createContext, useContext, useState, useEffect } from 'react';
import api from '../services/api';

const AuthContext = createContext();

export function AuthProvider({ children }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);

    const login = async (credentials) => {
        try {
            const response = await api.post('/api/auth/login', credentials);
            const { token } = response.data;
            localStorage.setItem('token', token);
            
            // Decode token and set user
            const decodedUser = decodeToken(token);
            setUser(decodedUser);
            return { success: true };
        } catch (error) {
            return { 
                success: false, 
                message: error.response?.data?.message || 'Login failed' 
            };
        }
    };

    const logout = () => {
        localStorage.removeItem('token');
        setUser(null);
    };

    // Check if user is authenticated on initial load
    useEffect(() => {
        const checkAuth = async () => {
            const token = localStorage.getItem('token');
            if (token) {
                try {
                    // Verify token is still valid
                    const response = await api.get('/api/auth/me');
                    setUser(response.data);
                } catch (error) {
                    localStorage.removeItem('token');
                }
            }
            setLoading(false);
        };
        
        checkAuth();
    }, []);

    const value = {
        user,
        loading,
        login,
        logout,
        isAuthenticated: !!user
    };

    return (
        <AuthContext.Provider value={value}>
            {children}
        </AuthContext.Provider>
    );
}

export function useAuth() {
    return useContext(AuthContext);
}

API Integration

Learn how to integrate your React frontend with your Spring Boot backend API.

API Service Example

// Example of API service setup
import axios from 'axios';

const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080/api';

const api = axios.create({
    baseURL: API_URL,
    headers: {
        'Content-Type': 'application/json'
    }
});

// Add request interceptor to include auth token
api.interceptors.request.use(
    (config) => {
        const token = localStorage.getItem('token');
        if (token) {
            config.headers['Authorization'] = `Bearer ${token}`;
        }
        return config;
    },
    (error) => Promise.reject(error)
);

// Add response interceptor to handle common errors
api.interceptors.response.use(
    (response) => response,
    (error) => {
        if (error.response && error.response.status === 401) {
            // Unauthorized - token expired or invalid
            localStorage.removeItem('token');
            window.location.href = '/login';
        }
        return Promise.reject(error);
    }
);

export default api;

React Component Example

// Example of a component that fetches and displays data
import { useState, useEffect } from 'react';
import api from '../services/api';
import { useAuth } from '../context/AuthContext';

function Dashboard() {
    const [assignments, setAssignments] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const { user } = useAuth();

    useEffect(() => {
        const fetchAssignments = async () => {
            try {
                const response = await api.get('/assignments');
                setAssignments(response.data);
                setLoading(false);
            } catch (err) {
                setError('Failed to load assignments');
                setLoading(false);
            }
        };

        fetchAssignments();
    }, []);

    if (loading) return <div>Loading assignments...</div>;
    if (error) return <div>{error}</div>;

    return (
        <div className="dashboard">
            <h1>Welcome, {user.name}!</h1>
            <h2>Your Assignments</h2>
            
            <div className="assignment-list">
                {assignments.length === 0 ? (
                    <p>No assignments found.</p>
                ) : (
                    assignments.map(assignment => (
                        <div key={assignment.id} className="assignment-card">
                            <h3>{assignment.title}</h3>
                            <p>{assignment.description}</p>
                            <span className="status">{assignment.status}</span>
                            <button onClick={() => navigate(`/assignments/${assignment.id}`)}>
                                View Details
                            </button>
                        </div>
                    ))
                )}
            </div>
        </div>
    );
}