Created
November 18, 2025 19:48
-
-
Save bryantee/600d73e3ec106c33e784e0cdb591136c to your computer and use it in GitHub Desktop.
FE Interview Coding Exercise Solution
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useEffect, useState } from "react"; | |
| type ActivityRange = "CURRENT_SCHOOL_YEAR" | "LAST_30_DAYS"; | |
| interface OrgEngagementSummary { | |
| teacherMaterialsAccessed: number; | |
| studentMaterialsAccessed: number; | |
| } | |
| interface OrgEngagementMetricsProps { | |
| orgId: string; | |
| } | |
| export function OrgEngagementMetrics({ orgId }: OrgEngagementMetricsProps) { | |
| const [range, setRange] = useState<ActivityRange>("CURRENT_SCHOOL_YEAR"); | |
| const [summary, setSummary] = useState<OrgEngagementSummary | null>(null); | |
| /** | |
| * Setup state for tracking error and loading states | |
| */ | |
| const [error, setError] = useState<string | null>(null); | |
| const [loading, setLoading] = useState<boolean>(false); | |
| useEffect(() => { | |
| /** | |
| * Initialize loading and error states before fetching data | |
| */ | |
| setLoading(true); | |
| setError(null); | |
| /** | |
| * Setup AbortController to cancel fetch if component unmounts or params change | |
| */ | |
| const abortController = new AbortController(); | |
| /** | |
| * Could elect to either swap API call with a library like SWR or React Query for better data fetching management. | |
| * Or refactor into a custom hook that manages loading, error, and data states. | |
| */ | |
| fetchOrgEngagementSummary(orgId, range, abortController.signal) | |
| .then((data) => { | |
| setSummary(data) | |
| }) | |
| .catch(() => { | |
| /** | |
| * Set error state if the fetch fails | |
| */ | |
| setError("Failed to fetch engagement summary."); | |
| }) | |
| .finally(() => { | |
| /** | |
| * Reset loading state after fetch completes | |
| */ | |
| setLoading(false); | |
| }); | |
| return () => { | |
| /** | |
| * Cleanup function to abort fetch on unmount or param change | |
| */ | |
| abortController.abort(); | |
| } | |
| /** | |
| * Add orgId and range to dependency array to refetch data when they change 👇 | |
| */ | |
| }, [orgId, range]); | |
| return ( | |
| <section style={{ padding: 16, border: "1px solid #eee", borderRadius: 8 }}> | |
| <header style={{ marginBottom: 12 }}> | |
| <h2>Engagement Summary</h2> | |
| <div> | |
| Hogwarts School of Witchcraft and Wizardry | |
| </div> | |
| </header> | |
| <div> | |
| <label | |
| htmlFor="activity-range-select" // Associate label with form element using `htmlFor` attribute | |
| style={{ display: "block", marginBottom: 12, fontSize: 13 }} | |
| > | |
| Activity Range | |
| </label> | |
| <select | |
| id="activity-range-select" // Add id to select for accessibility | |
| value={range} | |
| onChange={(e) => setRange(e.target.value as ActivityRange)} | |
| style={{ marginLeft: 8 }} | |
| > | |
| <option value="CURRENT_SCHOOL_YEAR">Current School Year</option> | |
| <option value="LAST_30_DAYS">Last 30 Days</option> | |
| </select> | |
| </div> | |
| /** | |
| * Handles loading and error states in the UI. | |
| * Conditionally renders loading indicator, error message, or summary data. | |
| * | |
| * Optionally, could elect to: | |
| * - Show a toast for errors | |
| * - Disable the select while loading | |
| * - Use a skeleton loader for better UX | |
| */ | |
| {loading && <div>Loading...</div>} | |
| {error && <div style={{ color: 'red' }}>{error}</div>} | |
| {summary && !loading && !error && ( | |
| <div> | |
| <MetricCard | |
| title="Teacher Materials Accessed" | |
| value={summary.teacherMaterialsAccessed} | |
| /> | |
| <MetricCard | |
| title="Student Materials Accessed" | |
| value={summary.studentMaterialsAccessed} | |
| /> | |
| </div> | |
| )} | |
| </section> | |
| ); | |
| } | |
| /** | |
| * Refactor out metric card into its own component for reusability and cleaner code | |
| */ | |
| function MetricCard(props: { title: string; value: number }) { | |
| return ( | |
| <div style={{ marginBottom: 8 }}> | |
| <strong>{props.value}</strong> {props.title} | |
| </div> | |
| ); | |
| } | |
| // Simulated network request function | |
| function fetchOrgEngagementSummary( | |
| orgId: string, | |
| range: ActivityRange, | |
| abortSignal: AbortSignal = new AbortController().signal // Pass the abort signal to the fetch request (if using fetch API) | |
| ): Promise<OrgEngagementSummary> { | |
| return fetch(`/api/orgs/${orgId}/engagement-summary?range=${range}`, { signal: abortSignal }) | |
| .then((response) => { | |
| if (!response.ok) { | |
| throw new Error("Network response was not ok"); | |
| } | |
| return response.json(); | |
| }) | |
| .then((data) => { | |
| return { | |
| teacherMaterialsAccessed: data.teacherMaterialsAccessed, | |
| studentMaterialsAccessed: data.studentMaterialsAccessed, | |
| }; | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment