Created
November 18, 2025 16:26
-
-
Save bryantee/6aae82ead381f1e49d71a91e6b94b7b4 to your computer and use it in GitHub Desktop.
FE Interview Coding Exercise
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
| /** | |
| * CODING EXERCISE: Organization Engagement Report | |
| * | |
| * BUGS: | |
| * 1. The engagement summary does not update when the activity range is changed. | |
| * 2. The select component and label are not properly associated for accessibility. | |
| * | |
| * IMPROVEMENTS: | |
| * 1. How could we handle error states in the UI? | |
| * 2. What about loading states? | |
| * | |
| * REFACTOR OPPORTUNITIES: | |
| * - How could we make the component more modular or reusable? | |
| * - What is another way to manage the network request lifecycle? | |
| * | |
| * BONUS: How would you handle cancelling the fetch request if the component unmounts before it completes? | |
| */ | |
| 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); | |
| useEffect(() => { | |
| fetchOrgEngagementSummary(orgId, range) | |
| .then((data) => { | |
| setSummary(data) | |
| }) | |
| .catch(() => { | |
| }) | |
| .finally(() => { | |
| }); | |
| // What happens if orgId or range changes quickly, or this component unmounts? | |
| }, []); | |
| 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 style={{ display: "block", marginBottom: 12, fontSize: 13 }}> | |
| Activity Range | |
| </label> | |
| <select | |
| 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> | |
| {summary && ( | |
| <div> | |
| <div style={{ marginBottom: 8 }}> | |
| <strong>{summary.teacherMaterialsAccessed}</strong> Teacher Materials Accessed | |
| </div> | |
| <div style={{ marginBottom: 8 }}> | |
| <strong>{summary.studentMaterialsAccessed}</strong> Student Materials Accessed | |
| </div> | |
| </div> | |
| )} | |
| </section> | |
| ); | |
| } | |
| // Simulated network request function | |
| function fetchOrgEngagementSummary( | |
| orgId: string, | |
| range: ActivityRange | |
| ): Promise<OrgEngagementSummary> { | |
| return new Promise((resolve) => { | |
| setTimeout(() => { | |
| resolve({ | |
| teacherMaterialsAccessed: Math.floor(Math.random() * 1000), | |
| studentMaterialsAccessed: Math.floor(Math.random() * 5000), | |
| }); | |
| }, 1000); | |
| }); | |
| } |
Comments are disabled for this gist.