diff --git a/apps/blade/src/app/_components/bad-perms.tsx b/apps/blade/src/app/_components/bad-perms.tsx index 64973c766..1420a5e48 100644 --- a/apps/blade/src/app/_components/bad-perms.tsx +++ b/apps/blade/src/app/_components/bad-perms.tsx @@ -3,6 +3,12 @@ import { ShieldX } from "lucide-react"; import type { PermissionKey } from "@forge/consts"; import { PERMISSION_DATA } from "@forge/consts"; +/** + * Renders an "Access Denied" alert that lists human-readable names for the given permission keys. + * + * @param perms - Array of permission keys to display; unknown or missing keys are ignored + * @returns A JSX element containing an access-denied alert with the permission names joined by ", " + */ export function BadPerms({ perms }: { perms: PermissionKey[] }) { const permNames: string[] = []; perms.forEach((v) => { @@ -22,4 +28,4 @@ export function BadPerms({ perms }: { perms: PermissionKey[] }) {
{permNames.join(", ")}
); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/_components/discord-modal.tsx b/apps/blade/src/app/_components/discord-modal.tsx index 7594fb373..61266def7 100644 --- a/apps/blade/src/app/_components/discord-modal.tsx +++ b/apps/blade/src/app/_components/discord-modal.tsx @@ -13,6 +13,14 @@ import { DialogTitle, } from "@forge/ui/dialog"; +/** + * Render a dialog prompting the user to join the Knight Hacks Discord. + * + * The dialog's open state is initialized from `initialState`. The primary action opens the permanent Discord invite in a new tab; the secondary action closes the dialog. + * + * @param initialState - Whether the dialog is open on initial render + * @returns The rendered dialog element + */ export function TacoTuesday({ initialState }: { initialState: boolean }) { const [open, setOpen] = useState(initialState); return ( @@ -73,4 +81,4 @@ export function TacoTuesday({ initialState }: { initialState: boolean }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/_components/GenderPie.tsx b/apps/blade/src/app/admin/_components/GenderPie.tsx index ef18aeca1..38da5ca13 100644 --- a/apps/blade/src/app/admin/_components/GenderPie.tsx +++ b/apps/blade/src/app/admin/_components/GenderPie.tsx @@ -25,6 +25,12 @@ interface Person { gender?: (typeof FORMS.GENDERS)[number]; } +/** + * Render an interactive donut chart of people grouped by gender with a select control to choose the active slice. + * + * @param people - Array of person objects whose `gender` values will be aggregated for the chart + * @returns A Card element containing a responsive donut chart, tooltip, central count label for the active gender, and a dropdown to change the active slice + */ export default function GenderPie({ people }: { people: Person[] }) { const id = "pie-interactive"; @@ -206,4 +212,4 @@ export default function GenderPie({ people }: { people: Person[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/_components/MajorBarChart.tsx b/apps/blade/src/app/admin/_components/MajorBarChart.tsx index 19031fbcf..95c435c5b 100644 --- a/apps/blade/src/app/admin/_components/MajorBarChart.tsx +++ b/apps/blade/src/app/admin/_components/MajorBarChart.tsx @@ -25,6 +25,17 @@ interface Person { major?: (typeof FORMS.MAJORS)[number]; } +/** + * Render a vertical bar chart showing the top 10 majors by student count. + * + * The component tallies majors from `people`, sorts majors by count descending, + * truncates major names longer than 35 characters for display, and renders a + * vertical bar chart with tooltips and inline count labels. If no major data + * is present, a centered fallback message is shown. + * + * @param people - Array of Person objects to aggregate by `major` + * @returns The rendered Card element containing the majors bar chart or a fallback message + */ export default function MajorBarChart({ people }: { people: Person[] }) { const majorCounts: Record = {}; @@ -121,4 +132,4 @@ export default function MajorBarChart({ people }: { people: Person[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/_components/RaceOrEthnicityPie.tsx b/apps/blade/src/app/admin/_components/RaceOrEthnicityPie.tsx index d07657bc1..89001753f 100644 --- a/apps/blade/src/app/admin/_components/RaceOrEthnicityPie.tsx +++ b/apps/blade/src/app/admin/_components/RaceOrEthnicityPie.tsx @@ -37,6 +37,12 @@ const shortenRaceOrEthnicity = (raceOrEthnicity: string): string => { return replacements[raceOrEthnicity] ?? raceOrEthnicity; }; +/** + * Render a pie chart showing the distribution of people by race or ethnicity with a selectable active segment. + * + * @param people - Array of Person objects whose `raceOrEthnicity` values will be counted and displayed + * @returns The React element that renders an interactive pie chart and a select control for choosing the active segment + */ export default function RaceOrEthnicityPie({ people }: { people: Person[] }) { const id = "pie-interactive"; @@ -227,4 +233,4 @@ export default function RaceOrEthnicityPie({ people }: { people: Person[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/_components/SchoolBarChart.tsx b/apps/blade/src/app/admin/_components/SchoolBarChart.tsx index c61e012dc..ccdcadbba 100644 --- a/apps/blade/src/app/admin/_components/SchoolBarChart.tsx +++ b/apps/blade/src/app/admin/_components/SchoolBarChart.tsx @@ -25,6 +25,14 @@ interface Person { school?: (typeof FORMS.SCHOOLS)[number]; } +/** + * Render a vertical bar chart of the top 10 schools by number of people. + * + * Truncates school names longer than 35 characters for axis labels while preserving full names in tooltips, and displays a centered message when no school data is available. + * + * @param people - Array of Person objects used to compute counts per school. + * @returns The rendered Schools bar chart element. + */ export default function SchoolBarChart({ people }: { people: Person[] }) { const schoolCounts: Record = {}; @@ -120,4 +128,4 @@ export default function SchoolBarChart({ people }: { people: Person[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/_components/SchoolYearPie.tsx b/apps/blade/src/app/admin/_components/SchoolYearPie.tsx index 08f3ab249..b8dc02835 100644 --- a/apps/blade/src/app/admin/_components/SchoolYearPie.tsx +++ b/apps/blade/src/app/admin/_components/SchoolYearPie.tsx @@ -37,6 +37,15 @@ const shortenLevelOfStudy = (levelOfStudy: string): string => { return replacements[levelOfStudy] ?? levelOfStudy; }; +/** + * Render an interactive pie chart that visualizes counts of people grouped by level of study. + * + * The chart includes a selector to choose the active level, highlights the active slice, and + * shows the selected level's count in the chart center. + * + * @param people - Array of Person objects used to compute counts per level of study. + * @returns A card element containing the interactive pie chart and level selector. + */ export default function SchoolYearPie({ people }: { people: Person[] }) { const id = "pie-interactive"; @@ -225,4 +234,4 @@ export default function SchoolYearPie({ people }: { people: Person[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/EventDemographics.tsx b/apps/blade/src/app/admin/club/data/_components/EventDemographics.tsx index 88ea85062..eb677fd75 100644 --- a/apps/blade/src/app/admin/club/data/_components/EventDemographics.tsx +++ b/apps/blade/src/app/admin/club/data/_components/EventDemographics.tsx @@ -19,6 +19,13 @@ import TypePie from "./event-data/TypePie"; import { WeekdayPopularityRadar } from "./event-data/WeekdayPopularityRadar"; import { FORMS } from '@forge/consts'; +/** + * Render interactive event demographics and visualizations with semester filtering and an optional hackathon inclusion toggle. + * + * The component fetches events, derives selectable semester ranges from event dates (plus an "All Semesters" option), and filters events by the selected semester and hackathon inclusion setting. It displays a semester Select control, a checkbox to include/exclude hackathon events, multiple responsive visualizations for the filtered events, and a centered message when no events match the selection. + * + * @returns A React element containing the semester selector, hackathon toggle, and data visualizations (or a "No events found" message when the filtered set is empty). + */ export default function EventDemographics() { const { data: events } = api.event.getEvents.useQuery(); const semestersArr: FORMS.Semester[] = [ @@ -172,4 +179,4 @@ export default function EventDemographics() { )} ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/event-data/AttendancesBarChart.tsx b/apps/blade/src/app/admin/club/data/_components/event-data/AttendancesBarChart.tsx index 70bef30de..7f2cec943 100644 --- a/apps/blade/src/app/admin/club/data/_components/event-data/AttendancesBarChart.tsx +++ b/apps/blade/src/app/admin/club/data/_components/event-data/AttendancesBarChart.tsx @@ -19,6 +19,15 @@ import { } from "@forge/ui/chart"; import { FORMS } from '@forge/consts'; +/** + * Render a vertical bar chart showing average attendances grouped by event tag. + * + * Averages are computed per tag from events where (numAttended + numHackerAttended) is greater than or equal to 5; each tag's average is rounded to the nearest integer. If no tags qualify, a "No attendance data found" message is rendered instead. + * + * @param events - Array of events used to compute average attendances; each event's total attendees is `numAttended + numHackerAttended` and contributes to the tag's average only when that total is >= 5 + * @param className - Optional CSS class applied to the outer Card component + * @returns A Card containing a vertical bar chart of average attendees by event type, or a centered message when no data is available + */ export default function AttendancesBarChart({ events, className, @@ -132,4 +141,4 @@ export default function AttendancesBarChart({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/event-data/PopularityRanking.tsx b/apps/blade/src/app/admin/club/data/_components/event-data/PopularityRanking.tsx index 9a7d0f43a..691090c65 100644 --- a/apps/blade/src/app/admin/club/data/_components/event-data/PopularityRanking.tsx +++ b/apps/blade/src/app/admin/club/data/_components/event-data/PopularityRanking.tsx @@ -5,6 +5,15 @@ import { Button } from "@forge/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@forge/ui/card"; import { FORMS } from '@forge/consts'; +/** + * Render a card titled "Most Popular Events" showing events ranked by total attendance. + * + * Displays up to 10 events that have at least one attendee, showing the top 3 by default. + * If more than 3 events are available, a "Show more" / "Show less" toggle reveals or hides the full list. + * + * @param events - Array of events to rank; each event is expected to include `id`, `name`, `tag`, `numAttended`, and `numHackerAttended` + * @returns A React element containing an ordered list of ranked events with attendance counts and an optional toggle button + */ export default function PopularityRanking({ events, }: { @@ -64,4 +73,4 @@ export default function PopularityRanking({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/event-data/TypePie.tsx b/apps/blade/src/app/admin/club/data/_components/event-data/TypePie.tsx index 17849c9e0..d2e0d687b 100644 --- a/apps/blade/src/app/admin/club/data/_components/event-data/TypePie.tsx +++ b/apps/blade/src/app/admin/club/data/_components/event-data/TypePie.tsx @@ -22,6 +22,16 @@ import { } from "@forge/ui/select"; import { FORMS } from '@forge/consts'; +/** + * Render an interactive pie chart that visualizes the distribution of events grouped by tag. + * + * Aggregates the provided events by their `tag` field, computes counts and percentages for each + * tag, and renders a selectable card containing a pie chart with a center label showing the + * active slice's percentage. + * + * @param events - Array of events whose `tag` values will be counted and displayed + * @returns The JSX element rendering a selectable pie chart summary of event types by tag + */ export default function TypePie({ events }: { events: ReturnEvent[] }) { const id = "pie-interactive"; @@ -195,4 +205,4 @@ export default function TypePie({ events }: { events: ReturnEvent[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/event-data/WeekdayPopularityRadar.tsx b/apps/blade/src/app/admin/club/data/_components/event-data/WeekdayPopularityRadar.tsx index bbd4f53d2..4b4cbef5b 100644 --- a/apps/blade/src/app/admin/club/data/_components/event-data/WeekdayPopularityRadar.tsx +++ b/apps/blade/src/app/admin/club/data/_components/event-data/WeekdayPopularityRadar.tsx @@ -18,6 +18,14 @@ import { } from "@forge/ui/chart"; import { FORMS } from '@forge/consts'; +/** + * Render a card containing a radar chart of average attendance grouped by weekday. + * + * Only events whose combined attendees (numAttended + numHackerAttended) are 5 or more are included; Saturday and Sunday are combined under "Sat/Sun". + * + * @param events - Array of events to aggregate and visualize. + * @returns A React element: a Card containing a radar chart that displays the average attendees per weekday (averages rounded to the nearest integer). + */ export function WeekdayPopularityRadar({ events }: { events: ReturnEvent[] }) { const chartConfig = { attendees: { @@ -159,4 +167,4 @@ export function WeekdayPopularityRadar({ events }: { events: ReturnEvent[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/member-data/GenderPie.tsx b/apps/blade/src/app/admin/club/data/_components/member-data/GenderPie.tsx index 36cfe2675..cbf819231 100644 --- a/apps/blade/src/app/admin/club/data/_components/member-data/GenderPie.tsx +++ b/apps/blade/src/app/admin/club/data/_components/member-data/GenderPie.tsx @@ -22,6 +22,17 @@ import { SelectValue, } from "@forge/ui/select"; +/** + * Render an interactive donut pie chart that visualizes member counts grouped by gender. + * + * The component aggregates the provided members by their `gender`, assigns a consistent color + * palette per gender, and displays a selectable list of genders. Selecting a gender highlights + * its slice and updates the center label to show that gender's member count. If the active + * gender is removed from the data, the component selects the first available gender. + * + * @param members - Array of members to aggregate and visualize by gender + * @returns A Card containing a configurable pie chart, a gender select control, and a center label showing the selected gender's member count + */ export default function SchoolYearPie({ members, }: { @@ -207,4 +218,4 @@ export default function SchoolYearPie({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/member-data/SchoolBarChart.tsx b/apps/blade/src/app/admin/club/data/_components/member-data/SchoolBarChart.tsx index 262065d0d..7802e1186 100644 --- a/apps/blade/src/app/admin/club/data/_components/member-data/SchoolBarChart.tsx +++ b/apps/blade/src/app/admin/club/data/_components/member-data/SchoolBarChart.tsx @@ -25,6 +25,16 @@ interface Member { school?: (typeof FORMS.SCHOOLS)[number]; } +/** + * Render a horizontal bar chart showing the top 10 schools by member count. + * + * The component counts `school` values from the provided `members`, displays the highest-count + * schools (up to 10) as bars, truncates long school names for on-chart labels, and exposes the + * full school name in the tooltip. + * + * @param members - Array of Member objects; entries without a `school` value are ignored. + * @returns A JSX element containing the bar chart or a centered message when no school data exists. + */ export default function SchoolBarChart({ members }: { members: Member[] }) { const schoolCounts: Record = {}; @@ -120,4 +130,4 @@ export default function SchoolBarChart({ members }: { members: Member[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/member-data/ShirtSizePie.tsx b/apps/blade/src/app/admin/club/data/_components/member-data/ShirtSizePie.tsx index b178dc716..2185ceb55 100644 --- a/apps/blade/src/app/admin/club/data/_components/member-data/ShirtSizePie.tsx +++ b/apps/blade/src/app/admin/club/data/_components/member-data/ShirtSizePie.tsx @@ -25,6 +25,14 @@ interface Member { shirtSize?: (typeof FORMS.SHIRT_SIZES)[number]; } +/** + * Renders a selectable donut chart summarizing member shirt sizes and highlights the selected segment. + * + * Aggregates `members` by their `shirtSize`, displays each size as a colored slice, lets the user pick an active size via a dropdown, and shows the active size's member count in the chart center. + * + * @param members - Array of members to aggregate by their `shirtSize` property. + * @returns The Shirt Sizes pie chart component. + */ export default function ShirtSizePie({ members }: { members: Member[] }) { const id = "pie-interactive-shirt"; @@ -208,4 +216,4 @@ export default function ShirtSizePie({ members }: { members: Member[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/data/_components/member-data/YearOfStudyPie.tsx b/apps/blade/src/app/admin/club/data/_components/member-data/YearOfStudyPie.tsx index 5194ee8e4..b0c483bd1 100644 --- a/apps/blade/src/app/admin/club/data/_components/member-data/YearOfStudyPie.tsx +++ b/apps/blade/src/app/admin/club/data/_components/member-data/YearOfStudyPie.tsx @@ -30,6 +30,17 @@ interface YearOfStudyPieProps { members: Member[]; } +/** + * Render an interactive pie chart of members grouped by academic year category. + * + * Calculates each member's category (e.g., "Freshman", "Sophomore", "Junior", "Senior", + * "High School", "Graduate", "Alumni", or "Unknown") from their graduation date and + * level of study, aggregates counts per category, and displays a selectable pie chart + * with the active segment's count shown in the center. + * + * @param members - Array of member records whose `gradDate` and `levelOfStudy` are used to determine categories + * @returns A React element containing a Card with a selectable, colored pie chart and a centered count for the active segment + */ export default function YearOfStudyPie({ members }: YearOfStudyPieProps) { const id = "pie-interactive"; @@ -270,4 +281,4 @@ export default function YearOfStudyPie({ members }: YearOfStudyPieProps) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/events/_components/create-event.tsx b/apps/blade/src/app/admin/club/events/_components/create-event.tsx index 19d74ed47..243340aec 100644 --- a/apps/blade/src/app/admin/club/events/_components/create-event.tsx +++ b/apps/blade/src/app/admin/club/events/_components/create-event.tsx @@ -52,6 +52,14 @@ const minutes = Array.from({ length: 12 }, (_, i) => const amPmOptions = ["AM", "PM"] as const; +/** + * Renders a "Create Event" button and dialog containing a form to create a new event. + * + * The component manages dialog and submission state, validates and composes start/end datetimes + * from separate date and 12-hour time inputs, and calls the event creation mutation. + * + * @returns A React element that provides the trigger button and the create-event dialog form. + */ export function CreateEventButton() { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -618,4 +626,4 @@ export function CreateEventButton() { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/events/_components/update-event.tsx b/apps/blade/src/app/admin/club/events/_components/update-event.tsx index c52bc5bca..4edf83b76 100644 --- a/apps/blade/src/app/admin/club/events/_components/update-event.tsx +++ b/apps/blade/src/app/admin/club/events/_components/update-event.tsx @@ -99,6 +99,16 @@ function parseDateTime(value: string | Date) { }; } +/** + * Render a dialog-based form for updating an existing event. + * + * Renders a button that opens a dialog containing a pre-filled update form for the given event, + * validates date/time inputs, and submits an update mutation. Shows success or error toasts and + * disables controls while the update is in progress. + * + * @param event - The event to edit (pre-fills form fields) + * @returns A React element containing the update-event dialog and trigger button + */ export function UpdateEventButton({ event }: { event: InsertEvent }) { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -669,4 +679,4 @@ export function UpdateEventButton({ event }: { event: InsertEvent }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/members/_components/delete-member.tsx b/apps/blade/src/app/admin/club/members/_components/delete-member.tsx index a7f8d515a..e18540f0f 100644 --- a/apps/blade/src/app/admin/club/members/_components/delete-member.tsx +++ b/apps/blade/src/app/admin/club/members/_components/delete-member.tsx @@ -19,6 +19,17 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; import { USE_CAUTION } from '@forge/consts'; +/** + * Render a destructive button that opens a confirmation dialog to delete a member. + * + * The dialog displays the member's name, requires typing the exact confirmation phrase + * (paste is prevented), and shows a loading state while the deletion is in progress. + * On success a success toast is shown and the dialog resets and closes; on error an error toast is shown. + * The member cache is invalidated after the mutation settles. + * + * @param member - The member to delete; must include `id`, `firstName`, and `lastName` for identification and display. + * @returns The JSX element for the delete button and its confirmation dialog. + */ export default function DeleteMemberButton({ member, }: { @@ -117,4 +128,4 @@ export default function DeleteMemberButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/members/_components/final-dues-dialog.tsx b/apps/blade/src/app/admin/club/members/_components/final-dues-dialog.tsx index f89e1ac2d..de14ae071 100644 --- a/apps/blade/src/app/admin/club/members/_components/final-dues-dialog.tsx +++ b/apps/blade/src/app/admin/club/members/_components/final-dues-dialog.tsx @@ -19,6 +19,16 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; import { CLEAR_DUES_MESSAGE, USE_CAUTION } from '@forge/consts'; +/** + * Render a destructive confirmation dialog and its trigger button used to clear all member dues. + * + * The dialog requires the user to type a specific confirmation message before enabling the destructive action, + * coordinates local loading/open state with external open-state setters, and invokes the mutation that clears dues. + * + * @param setFirstOpen - Setter to update the external "first" open state; cleared when the dialog is cancelled or the action succeeds. + * @param setSecondOpen - Setter to update the external "second" open state; cleared when the dialog is cancelled or the action succeeds. + * @returns The trigger button and modal dialog UI for confirming and executing the "clear all dues" action. + */ export default function FinalDuesDialogButton({ disabled, setFirstOpen, @@ -129,4 +139,4 @@ export default function FinalDuesDialogButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/members/_components/member-profile.tsx b/apps/blade/src/app/admin/club/members/_components/member-profile.tsx index d51f6b5d3..475336b7f 100644 --- a/apps/blade/src/app/admin/club/members/_components/member-profile.tsx +++ b/apps/blade/src/app/admin/club/members/_components/member-profile.tsx @@ -18,6 +18,12 @@ import { import { api } from "~/trpc/react"; import { FORMS, MEMBER_PROFILE_ICON_SIZE } from '@forge/consts'; +/** + * Renders a button that opens a dialog displaying a member's detailed profile. + * + * @param member - The member record whose details will be shown in the dialog. + * @returns A React element containing the profile button and the dialog UI for the provided member. + */ export default function MemberProfileButton({ member, }: { @@ -172,4 +178,4 @@ export default function MemberProfileButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/club/members/_components/update-member.tsx b/apps/blade/src/app/admin/club/members/_components/update-member.tsx index 34565b8e6..fc865819b 100644 --- a/apps/blade/src/app/admin/club/members/_components/update-member.tsx +++ b/apps/blade/src/app/admin/club/members/_components/update-member.tsx @@ -39,6 +39,14 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; import { FORMS } from '@forge/consts'; +/** + * Renders an "Update Member" button that opens a dialog containing a pre-filled form for editing a member's details. + * + * The dialog validates input, submits updates via the member API, shows success/error toasts, and invalidates member caches on completion. + * + * @param member - The member record used to populate the form fields. + * @returns A React element that renders the update button and modal form UI. + */ export default function UpdateMemberButton({ member, }: { @@ -389,4 +397,4 @@ export default function UpdateMemberButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/forms/[slug]/client.tsx b/apps/blade/src/app/admin/forms/[slug]/client.tsx index 1931a7635..e90e3eaad 100644 --- a/apps/blade/src/app/admin/forms/[slug]/client.tsx +++ b/apps/blade/src/app/admin/forms/[slug]/client.tsx @@ -142,6 +142,15 @@ function SortableItem({ ); } +/** + * Render the connections interface for a form: a procedure matcher and a list of saved connections. + * + * @param props.procs - Map of procedure IDs to their metadata used by the matcher. + * @param props.slug - Form slug used when rendering each connection viewer. + * @param props.id - Form ID used to fetch the form's saved connections. + * @param props.formData - Full form definition; the component reads question texts from this object. + * @returns A React element containing a ListMatcher for the form and a ConnectionViewer for each fetched connection. + */ function ConnectionsTab(props: { procs: Record; slug: string; @@ -772,4 +781,4 @@ export function EditorClient({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/forms/[slug]/responses/_components/ResponsePieChart.tsx b/apps/blade/src/app/admin/forms/[slug]/responses/_components/ResponsePieChart.tsx index 92ff50b91..f96cb4c9f 100644 --- a/apps/blade/src/app/admin/forms/[slug]/responses/_components/ResponsePieChart.tsx +++ b/apps/blade/src/app/admin/forms/[slug]/responses/_components/ResponsePieChart.tsx @@ -21,6 +21,17 @@ interface ResponsePieChartProps { }[]; } +/** + * Render a pie chart showing the distribution of answers for a single question. + * + * Normalizes answers before counting: boolean values and the strings `"true"`/`"false"` are shown as + * "Yes"/"No", objects are JSON-stringified, and other primitives are stringified. Displays the + * total response count, a colored pie chart, and a legend with each option's percentage. + * + * @param question - The question text whose answers are visualized + * @param responses - Array of response objects; each item contains a `responseData` map from question text to an answer + * @returns A React element containing the pie chart, legend, and response count + */ export function ResponsePieChart({ question, responses, @@ -213,4 +224,4 @@ export function ResponsePieChart({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/forms/[slug]/responses/page.tsx b/apps/blade/src/app/admin/forms/[slug]/responses/page.tsx index 798f2d239..0798c9c4e 100644 --- a/apps/blade/src/app/admin/forms/[slug]/responses/page.tsx +++ b/apps/blade/src/app/admin/forms/[slug]/responses/page.tsx @@ -18,6 +18,15 @@ export const metadata: Metadata = { description: "View Form Responses", }; +/** + * Render the admin page that displays responses for a form identified by its slug. + * + * This page verifies the current session and permission (redirecting to the sign-in page or root when access is not allowed), attempts to load the form by `slug`, and renders either a "Form Not Found" message or a responses view (tabs for "All Responses" and "Per User") wrapped with HydrateClient. + * + * @param params - Route parameters object + * @param params.slug - The form slug used to fetch the form and its responses (case sensitive) + * @returns The page JSX showing the form responses or a not-found message + */ export default async function FormResponsesPage({ params, }: { @@ -129,4 +138,4 @@ export default async function FormResponsesPage({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/data/_components/HackerCharts.tsx b/apps/blade/src/app/admin/hackathon/data/_components/HackerCharts.tsx index 1a3adf25e..99f5637f5 100644 --- a/apps/blade/src/app/admin/hackathon/data/_components/HackerCharts.tsx +++ b/apps/blade/src/app/admin/hackathon/data/_components/HackerCharts.tsx @@ -17,6 +17,17 @@ import FirstTimeInfo from "./FirstTimeInfo"; import LevelOfStudyPie from "./LevelOfStudyPie"; import ShirtSizePie from "./ShirtSizePie"; +/** + * Render interactive charts and status filters for hackers belonging to a given hackathon. + * + * Fetches hackers and hackathon metadata for the provided hackathonId, provides a status + * ToggleGroup to filter hackers (with an "all" default), and displays a collection of + * summary charts and info components for the filtered set. Shows an empty-state message + * when the hackathon has no hackers or when no hackers match the active filter. + * + * @param hackathonId - The identifier of the hackathon whose hackers and metadata are displayed + * @returns A React element containing the status filter controls and the assembled charts/info components, or appropriate empty-state messages + */ export default function HackerCharts({ hackathonId }: { hackathonId: string }) { const { data: hackers } = api.hacker.getHackers.useQuery(hackathonId); const { data: hackathon } = @@ -116,4 +127,4 @@ export default function HackerCharts({ hackathonId }: { hackathonId: string }) { )} ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/data/_components/LevelOfStudyPie.tsx b/apps/blade/src/app/admin/hackathon/data/_components/LevelOfStudyPie.tsx index de3054feb..c52c80e4d 100644 --- a/apps/blade/src/app/admin/hackathon/data/_components/LevelOfStudyPie.tsx +++ b/apps/blade/src/app/admin/hackathon/data/_components/LevelOfStudyPie.tsx @@ -30,6 +30,17 @@ interface LevelOfStudyPieProps { hackathonDate: Date | string; } +/** + * Render a pie chart showing the distribution of hackers by year of study relative to a hackathon date. + * + * The component groups hackers by computed levels ("Freshman", "Sophomore", "Junior", "Senior", "Graduate", "Alumni", or "Unknown" + * when dates are invalid) based on each hacker's `gradDate` compared to `hackathonDate`, and provides an interactive selector + * to highlight a level and show its count. + * + * @param hackers - Array of hackers; each hacker's `gradDate` is used to determine their level of study. + * @param hackathonDate - Date (or ISO date string) representing the hackathon date used as the reference year. + * @returns The rendered pie chart component displaying counts per level of study. + */ export default function LevelOfStudyPie({ hackers, hackathonDate, @@ -249,4 +260,4 @@ export default function LevelOfStudyPie({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/data/_components/ShirtSizePie.tsx b/apps/blade/src/app/admin/hackathon/data/_components/ShirtSizePie.tsx index aa1991b8f..ff21b3849 100644 --- a/apps/blade/src/app/admin/hackathon/data/_components/ShirtSizePie.tsx +++ b/apps/blade/src/app/admin/hackathon/data/_components/ShirtSizePie.tsx @@ -25,6 +25,14 @@ interface Hacker { shirtSize?: (typeof FORMS.SHIRT_SIZES)[number]; } +/** + * Render an interactive pie chart of shirt sizes among the provided hackers. + * + * Displays a pie where each slice represents the count of hackers for a shirt size, highlights the selected slice, and provides a dropdown to change the active slice. The component recalculates counts from the `hackers` prop and updates the active selection if data changes. + * + * @param hackers - Array of Hacker objects; only entries with a truthy `shirtSize` are counted. + * @returns The card element containing the configured pie chart, select control, and centered count label for the active size. + */ export default function ShirtSizePie({ hackers }: { hackers: Hacker[] }) { const id = "pie-interactive-shirt-hackers"; @@ -211,4 +219,4 @@ export default function ShirtSizePie({ hackers }: { hackers: Hacker[] }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/events/_components/create-event.tsx b/apps/blade/src/app/admin/hackathon/events/_components/create-event.tsx index 89ff35b70..86979ad2e 100644 --- a/apps/blade/src/app/admin/hackathon/events/_components/create-event.tsx +++ b/apps/blade/src/app/admin/hackathon/events/_components/create-event.tsx @@ -52,6 +52,17 @@ const minutes = Array.from({ length: 12 }, (_, i) => const amPmOptions = ["AM", "PM"] as const; +/** + * Render a button that opens a dialog containing a form for creating a new event. + * + * The component manages dialog visibility and submission state, validates the + * entered dates/times and hackathon/dues constraints, shows success/error toasts, + * and submits the final event payload to the backend via the TRPC `createEvent` + * mutation. Form fields include name, tag, optional hackathon, start/end dates + * and times, location, description, and a dues-paying flag. + * + * @returns A React element that provides the "Create Event" trigger and dialog form. + */ export function CreateEventButton() { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -618,4 +629,4 @@ export function CreateEventButton() { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/events/_components/update-event.tsx b/apps/blade/src/app/admin/hackathon/events/_components/update-event.tsx index 9b681457e..b1bfef48d 100644 --- a/apps/blade/src/app/admin/hackathon/events/_components/update-event.tsx +++ b/apps/blade/src/app/admin/hackathon/events/_components/update-event.tsx @@ -100,6 +100,16 @@ function parseDateTime(value: string | Date) { }; } +/** + * Render a button that opens a dialog allowing the user to edit and submit updates for an existing event. + * + * The dialog is prefilled from `event`, validates business rules (e.g., no dues for hackathon events, + * end date must be after start date), converts selected date/time inputs into Date objects, and calls + * the updateEvent mutation. Shows success or error toasts and invalidates cached event data after settlement. + * + * @param event - The existing event object used to populate the update form + * @returns The "Update Event" trigger button and the edit dialog UI + */ export function UpdateEventButton({ event }: { event: InsertEvent }) { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -679,4 +689,4 @@ export function UpdateEventButton({ event }: { event: InsertEvent }) { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/hackers/_components/hacker-profile.tsx b/apps/blade/src/app/admin/hackathon/hackers/_components/hacker-profile.tsx index 6d192939c..823c56bcb 100644 --- a/apps/blade/src/app/admin/hackathon/hackers/_components/hacker-profile.tsx +++ b/apps/blade/src/app/admin/hackathon/hackers/_components/hacker-profile.tsx @@ -21,6 +21,12 @@ import { api } from "~/trpc/react"; import FoodRestrictionsButton from "./food-restrictions"; import { FORMS, MEMBER_PROFILE_ICON_SIZE } from '@forge/consts'; +/** + * Renders a button that opens a dialog showing a detailed hacker profile. + * + * @param hacker - Hacker profile data used to populate the dialog + * @returns A React element containing the profile button and the populated dialog + */ export default function HackerProfileButton({ hacker, }: { @@ -190,4 +196,4 @@ export default function HackerProfileButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/admin/hackathon/hackers/_components/update-hacker.tsx b/apps/blade/src/app/admin/hackathon/hackers/_components/update-hacker.tsx index 402b4bec3..acaeaae18 100644 --- a/apps/blade/src/app/admin/hackathon/hackers/_components/update-hacker.tsx +++ b/apps/blade/src/app/admin/hackathon/hackers/_components/update-hacker.tsx @@ -39,6 +39,15 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; +/** + * Render a button that opens a modal form for editing a hacker's details. + * + * The dialog contains a form prefilled from the provided `hacker`, submits updates + * to the server, shows success or error toasts, and refreshes hacker data after the mutation settles. + * + * @param hacker - Hacker data used to populate form fields and to identify which record to update. + * @returns A React element rendering the update button and the update-modal form. + */ export default function UpdateHackerButton({ hacker, }: { @@ -377,4 +386,4 @@ export default function UpdateHackerButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/dashboard/_components/hackathon-dashboard/hackathon-dashboard.tsx b/apps/blade/src/app/dashboard/_components/hackathon-dashboard/hackathon-dashboard.tsx index 0d756d927..148d4b45a 100644 --- a/apps/blade/src/app/dashboard/_components/hackathon-dashboard/hackathon-dashboard.tsx +++ b/apps/blade/src/app/dashboard/_components/hackathon-dashboard/hackathon-dashboard.tsx @@ -15,6 +15,17 @@ export const metadata: Metadata = { description: "The official Knight Hacks Hacker Dashboard", }; +/** + * Render the hackathon dashboard UI for a given hacker. + * + * Displays a registration prompt when no hacker is provided, a configuration error + * if the hacker's class is missing or invalid, or the full dashboard with + * class-specific styling and sections (profile data, team points, countdown, + * and upcoming events) when valid. + * + * @param hacker - The current hacker profile returned from the server; may be `null`/`undefined` when the user is not registered. + * @returns A React element rendering the appropriate dashboard or informative prompt based on `hacker` and its class information. + */ export default async function HackathonDashboard({ hacker, }: { @@ -149,4 +160,4 @@ export default async function HackathonDashboard({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/dashboard/_components/member-dashboard/event/event-feedback.tsx b/apps/blade/src/app/dashboard/_components/member-dashboard/event/event-feedback.tsx index 33e96158b..e9009e331 100644 --- a/apps/blade/src/app/dashboard/_components/member-dashboard/event/event-feedback.tsx +++ b/apps/blade/src/app/dashboard/_components/member-dashboard/event/event-feedback.tsx @@ -39,6 +39,16 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; +/** + * Renders a modal feedback form for a given event and member. + * + * The form lets the member rate the event (overall, fun, learned), indicate where they heard about the event, opt into seeing similar events, and provide optional additional feedback. If the member already submitted feedback for the event, the trigger button is disabled. Submitting the form sends the feedback to the server, shows success or error toasts, and closes the dialog on success. + * + * @param event - Event data used to populate the form (id and name are required). + * @param member - Member data used to associate feedback (id is required). + * @param size - Size of the trigger button; one of `"md" | "sm" | "lg" | "icon" | null | undefined`. + * @returns The dialog-based feedback form element. + */ export function EventFeedbackForm({ event, member, @@ -351,4 +361,4 @@ export function EventFeedbackForm({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/forms/[formName]/_components/question-response-card.tsx b/apps/blade/src/app/forms/[formName]/_components/question-response-card.tsx index fc031d623..ee849aacc 100644 --- a/apps/blade/src/app/forms/[formName]/_components/question-response-card.tsx +++ b/apps/blade/src/app/forms/[formName]/_components/question-response-card.tsx @@ -339,6 +339,17 @@ function QuestionBody({ } } +/** + * Renders a multiple-choice radio group for a question, optionally allowing an "Other" text entry and reporting selections to the parent. + * + * When `question.optionsConst` is set, options are loaded from the FORMS constants; otherwise `question.options` are used. If `question.allowOther` is true, an "Other" radio option with a text input is shown when selected; the text is capitalized and propagated. Selection changes call `onChange` with the chosen option string, the entered "Other" text, `OTHER_VALUE` while "Other" is selected with no text, or `null` to clear the answer. + * + * @param question - The form question definition (text, options, flags such as `allowOther` and `optionsConst`). + * @param value - Current answer value (an option string, an "Other" string, `OTHER_VALUE`, or `undefined`/`null`). + * @param onChange - Callback invoked with the new value when the selection or "Other" text changes. + * @param disabled - If true, disables interaction with the control. + * @returns The rendered multiple-choice input element. + */ function MultipleChoiceInput({ question, value, @@ -451,6 +462,18 @@ function MultipleChoiceInput({ ); } +/** + * Renders a list of checkbox options for a form question and an optional editable "Other" entry. + * + * Renders checkboxes from `question.options` or from constants via `question.optionsConst`. When `question.allowOther` + * is true an "Other" checkbox and a text input are shown; the input value is capitalized on blur and included in the selected values. + * + * @param question - The form question definition; may provide `options`, `optionsConst`, and `allowOther`. + * @param value - Currently selected values for the question (array of strings). + * @param onChange - Called with the updated selected values when selections change. If "Other" is checked without text, the special marker `"__OTHER__"` is included; otherwise the entered other text is included. + * @param disabled - If true, all inputs are rendered disabled. + * @returns The checkbox group UI as a JSX element. + */ function CheckboxesInput({ question, value, @@ -604,6 +627,17 @@ function CheckboxesInput({ ); } +/** + * Renders a dropdown input for a form question, using a searchable combobox when there are more than 15 options. + * + * The options are taken from `question.optionsConst` (via FORMS.getDropdownOptionsFromConst) when present, otherwise from `question.options`. + * + * @param question - The form question that provides options or a constant reference for options. + * @param value - The currently selected option value, if any. + * @param onChange - Called when the selection changes with the selected option string or `null` when cleared. + * @param disabled - If `true`, the input is disabled. + * @returns The dropdown React element (either a ResponsiveComboBox or a Select). + */ function DropdownInput({ question, value, @@ -891,4 +925,4 @@ function FileUploadInput({ )} ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/hacker/application/_components/hacker-application-form.tsx b/apps/blade/src/app/hacker/application/_components/hacker-application-form.tsx index 27f5fe5ec..2fdc83bfc 100644 --- a/apps/blade/src/app/hacker/application/_components/hacker-application-form.tsx +++ b/apps/blade/src/app/hacker/application/_components/hacker-application-form.tsx @@ -36,6 +36,18 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; +/** + * Render the hacker application form for a specific hackathon. + * + * Prefills fields from a previous application when available, enforces form + * validations (including a minimum age of 18 by the hackathon start date), + * handles resume upload, submits the application, and sends a confirmation email. + * + * @param hackathonId - Identifier of the hackathon used when creating the application record. + * @param hackathonName - Display name of the hackathon shown in titles and emails. + * @param hackathonStartDate - Hackathon start date as an ISO date string; used to validate applicant age. + * @returns The Hacker registration React component ready to be mounted. + */ export function HackerFormPage({ hackathonId, hackathonName, @@ -1061,4 +1073,4 @@ export function HackerFormPage({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/member/application/_components/member-application-form.tsx b/apps/blade/src/app/member/application/_components/member-application-form.tsx index 45fbde29a..5c6a44957 100644 --- a/apps/blade/src/app/member/application/_components/member-application-form.tsx +++ b/apps/blade/src/app/member/application/_components/member-application-form.tsx @@ -43,6 +43,13 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; +/** + * Render the Knight Hacks membership application form. + * + * Renders the membership application UI, validates inputs (including resume and profile picture files), uploads files when provided, submits the final member payload to the API, and displays success or error toasts. On successful submission the user is navigated to the dashboard. + * + * @returns The React element for the membership application form. + */ export function MemberApplicationForm() { const router = useRouter(); const [loading, setLoading] = useState(false); @@ -930,4 +937,4 @@ export function MemberApplicationForm() { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/settings/_components/delete-member-button.tsx b/apps/blade/src/app/settings/_components/delete-member-button.tsx index 3b2c611f0..8e9acc32c 100644 --- a/apps/blade/src/app/settings/_components/delete-member-button.tsx +++ b/apps/blade/src/app/settings/_components/delete-member-button.tsx @@ -20,6 +20,16 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; import { USE_CAUTION } from '@forge/consts'; +/** + * Renders a button that opens a confirmation dialog to delete a member and performs the deletion. + * + * The dialog requires typing the exact confirmation phrase when extra caution is enabled. On successful deletion the component shows a success toast, closes the dialog, and navigates to the application root; on failure it shows an error toast. + * + * @param memberId - The unique identifier of the member to delete. + * @param firstName - The member's first name (unused by the component but available for callers). + * @param lastName - The member's last name (unused by the component but available for callers). + * @returns The DeleteMemberButton React element. + */ export default function DeleteMemberButton({ memberId, }: { @@ -117,4 +127,4 @@ export default function DeleteMemberButton({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/settings/hacker-profile/hacker-profile-form.tsx b/apps/blade/src/app/settings/hacker-profile/hacker-profile-form.tsx index 18ee99531..5e2bc14c9 100644 --- a/apps/blade/src/app/settings/hacker-profile/hacker-profile-form.tsx +++ b/apps/blade/src/app/settings/hacker-profile/hacker-profile-form.tsx @@ -37,6 +37,17 @@ import { api } from "~/trpc/react"; import DeleteHackerButton from "../_components/delete-hacker-button"; import { HackerAppCard } from "../../_components/option-cards"; +/** + * Render an editable hacker profile form for viewing and updating a user's application data. + * + * The form displays and allows editing of personal, demographic, academic, survey, links, + * resume, food allergies, and MLH consent fields; it handles resume upload and persists + * changes to the server while providing UI feedback. + * + * @param data - Initial hacker data used to populate the form (from the server's getHacker). + * @param hackathon - Current hackathon data used for contextual UI (from the server's getCurrentHackathon). + * @returns The rendered form UI for editing and submitting a hacker profile. + */ export function HackerProfileForm({ data, hackathon, @@ -955,4 +966,4 @@ export function HackerProfileForm({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/settings/hacker-profile/page.tsx b/apps/blade/src/app/settings/hacker-profile/page.tsx index 8d4fa37cf..3c2255b46 100644 --- a/apps/blade/src/app/settings/hacker-profile/page.tsx +++ b/apps/blade/src/app/settings/hacker-profile/page.tsx @@ -9,6 +9,15 @@ import { api, HydrateClient } from "~/trpc/server"; import { HackerProfileForm } from "./hacker-profile-form"; import { DISCORD } from '@forge/consts'; +/** + * Render the hacker profile settings page with access control and conditional content. + * + * If the request has no authenticated session, redirects to the root ("/"). + * If the authenticated user has no hacker profile, returns a centered placeholder that prompts the user to join Discord. + * Otherwise returns the hacker profile editor populated with the user's profile and current hackathon data. + * + * @returns The page's JSX: a placeholder UI when the user has no hacker profile; the hacker profile editor populated with existing data otherwise. + */ export default async function SettingsProfilePage() { const session = await auth(); @@ -70,4 +79,4 @@ export default async function SettingsProfilePage() { ); -} +} \ No newline at end of file diff --git a/apps/blade/src/app/settings/member-profile-form.tsx b/apps/blade/src/app/settings/member-profile-form.tsx index 035649472..3d4753daf 100644 --- a/apps/blade/src/app/settings/member-profile-form.tsx +++ b/apps/blade/src/app/settings/member-profile-form.tsx @@ -44,6 +44,16 @@ import { api } from "~/trpc/react"; import { MemberAppCard } from "../_components/option-cards"; import DeleteMemberButton from "./_components/delete-member-button"; +/** + * Render the Knight Hacks member profile form with validated fields, file uploads, and update/delete actions. + * + * Renders a multi-section form populated from the provided member data that lets members edit personal, demographic, + * academic, employment, and profile customization information. Handles client-side validation, resume and profile + * picture uploads, graduation date composition, company "Other" flow, and invokes server mutations to persist changes. + * + * @param data - Initial member data used to populate the form (typically the result of serverCaller.member.getMember). + * @returns The member profile form React element. + */ export function MemberProfileForm({ data, }: { @@ -938,4 +948,4 @@ export function MemberProfileForm({ ); -} +} \ No newline at end of file diff --git a/apps/blade/src/components/admin/forms/question-edit-card.tsx b/apps/blade/src/components/admin/forms/question-edit-card.tsx index e00313ac5..9b198b277 100644 --- a/apps/blade/src/components/admin/forms/question-edit-card.tsx +++ b/apps/blade/src/components/admin/forms/question-edit-card.tsx @@ -82,6 +82,13 @@ const QUESTION_ICONS: Record = { LINK: Link, }; +/** + * Renders an editable card for a single form question with controls to edit the question text, change question type, manage options/scale bounds, toggle required, duplicate, delete, and reorder. + * + * Changing the question type will auto-initialize or clear options and set default min/max for scale/number types; it also calls `onForceSave` when provided. + * + * @returns The React element representing the question edit card configured for the provided `question` and callbacks. + */ export function QuestionEditCard({ question, isActive, @@ -398,6 +405,17 @@ function QuestionBody({ } } +/** + * Render and manage the editable options UI for a choice-style form question. + * + * Renders either a preset-constant selector or a list of editable option inputs (with add/remove, + * paste-to-insert-multiple, and keyboard behaviors), and exposes controls for toggling the + * "Allow Other" option when applicable. + * + * @param question - The question object (including `id`) whose options, type, and related flags drive the UI. + * @param onUpdate - Called with an updated question whenever options, `optionsConst`, or `allowOther` change. + * @returns The React element for the options editor for the provided question. + */ function OptionList({ question, onUpdate, @@ -695,4 +713,4 @@ function LinearScaleEditor({ ); -} +} \ No newline at end of file diff --git a/apps/club/src/app/_components/landing/discover.tsx b/apps/club/src/app/_components/landing/discover.tsx index b2938c99f..588a6761d 100644 --- a/apps/club/src/app/_components/landing/discover.tsx +++ b/apps/club/src/app/_components/landing/discover.tsx @@ -12,6 +12,12 @@ import CoolButton2 from "./assets/coolbutton2"; import NeonTkSVG from "./assets/neon-tk"; import Counter from "./discover-assets/counter"; +/** + * Renders the Discover landing section with animated entrance effects and a member counter. + * + * @param memberCount - Number to display as the active members count in the counter component. + * @returns The React element for the full-screen Discover section, including animations, call-to-action button, and decorative logo. + */ export default function Discover({ memberCount }: { memberCount: number }) { const containerRef = useRef(null); const counterRef = useRef(null); @@ -129,4 +135,4 @@ export default function Discover({ memberCount }: { memberCount: number }) { ); -} +} \ No newline at end of file diff --git a/apps/club/src/app/officers/_components/officers.tsx b/apps/club/src/app/officers/_components/officers.tsx index c48e12bca..3a9475f70 100644 --- a/apps/club/src/app/officers/_components/officers.tsx +++ b/apps/club/src/app/officers/_components/officers.tsx @@ -9,6 +9,11 @@ import { OFFICERS } from "@forge/consts"; import OfficerCard from "./assets/officer-card"; +/** + * Renders a responsive grid of officer cards and attaches scroll-triggered slide-and-fade entrance animations to each card. + * + * @returns The React element for the officers grid. + */ export default function Officers() { gsap.registerPlugin(useGSAP, ScrollTrigger); const officersRef = useRef<(HTMLDivElement | null)[]>([]); @@ -63,4 +68,4 @@ export default function Officers() { ))} ); -} +} \ No newline at end of file diff --git a/packages/api/src/utils.ts b/packages/api/src/utils.ts index 06331bccd..73ffb6aa4 100644 --- a/packages/api/src/utils.ts +++ b/packages/api/src/utils.ts @@ -30,12 +30,24 @@ export const discord = new REST({ version: "10" }).setToken( env.DISCORD_BOT_TOKEN, ); +/** + * Adds a role to a member of the KnightHacks Discord guild. + * + * @param discordUserId - The Discord user ID of the member to modify + * @param roleId - The Discord role ID to add to the member + */ export async function addRoleToMember(discordUserId: string, roleId: string) { await discord.put( Routes.guildMemberRole(DISCORD.KNIGHTHACKS_GUILD, discordUserId, roleId), ); } +/** + * Removes a role from a member in the KnightHacks Discord guild. + * + * @param discordUserId - The Discord user ID of the member to update. + * @param roleId - The ID of the role to remove. + */ export async function removeRoleFromMember( discordUserId: string, roleId: string, @@ -45,6 +57,12 @@ export async function removeRoleFromMember( ); } +/** + * Finds a Discord user ID in the KnightHacks guild matching the provided username query. + * + * @param username - The username or search query to match against guild members + * @returns The Discord user ID of the first matched member as a `string`, or `null` if no match is found + */ export async function resolveDiscordUserId( username: string, ): Promise { @@ -169,6 +187,12 @@ export const isDiscordMember = async (user: Session["user"]) => { } }; +/** + * Check whether a Discord user has the VIP role in the KnightHacks guild. + * + * @param discordUserId - The Discord user ID to check + * @returns `true` if the user has the VIP role in the KnightHacks guild, `false` otherwise. + */ export async function isDiscordVIP(discordUserId: string) { const guildMember = (await discord.get( Routes.guildMember(DISCORD.KNIGHTHACKS_GUILD, discordUserId), @@ -210,6 +234,14 @@ export const sendEmail = async ({ } }; +/** + * Posts an embedded log message to the configured Discord log channel. + * + * @param title - Brief title shown at the top of the embed + * @param message - Main body text of the embed + * @param color - Visual color of the embed; one of `"tk_blue"`, `"blade_purple"`, `"uhoh_red"`, or `"success_green"` + * @param userId - Discord user ID to mention in the embed + */ export async function log({ title, message, @@ -317,6 +349,23 @@ type OptionalSchema = | { success: true; schema: JSONSchema7 } | { success: false; msg: string }; +/** + * Builds a JSON Schema7 validator fragment for a form field from validator options. + * + * Produces a JSON Schema7 object that enforces the field's type, formats, allowed options, + * and min/max constraints. When `optionsConst` is provided it is used to resolve dropdown/choice + * options; otherwise `options` is used. Validation for required selections is applied when `optional` + * is false. + * + * @param optional - Whether the field is optional (omitting required constraints when true) + * @param type - The form field type to generate schema for (e.g., SHORT_ANSWER, CHECKBOXES) + * @param options - Explicit option values for choice/dropdown/checkbox fields + * @param optionsConst - A constant reference to resolve option values instead of `options` + * @param min - Minimum constraint: minLength for strings, minItems for arrays, minimum for numbers + * @param max - Maximum constraint: maxLength for strings, maxItems for arrays, maximum for numbers + * @param allowOther - When true, permits values outside the declared options for choice fields + * @returns `success: true` with `{ schema }` containing the JSONSchema7 fragment on success; `success: false` with `msg` when validator options are invalid (e.g., missing options for choice fields) + */ function createJsonSchemaValidator({ optional, type, @@ -416,6 +465,13 @@ function createJsonSchemaValidator({ return { success: true, schema }; } +/** + * Builds a JSON Schema (Draft-07) that validates responses for the given form. + * + * @param form - The form definition containing an ordered list of questions and validators + * @returns An object with `success: true` and the generated JSON Schema under `schema` when all questions validate; + * otherwise returns the first validation error produced by a question validator (an object with `success: false` and `msg`). + */ export function generateJsonSchema(form: FORMS.FormType): OptionalSchema { const schema: JSONSchema7 = { type: "object", @@ -446,7 +502,16 @@ export function generateJsonSchema(form: FORMS.FormType): OptionalSchema { return { success: true, schema }; } -// Helper to regenerate presigned URLs for media +/** + * Regenerates presigned image and video URLs for each form instruction that references stored media. + * + * For each instruction with an `imageObjectName` or `videoObjectName`, attempts to replace `imageUrl`/`videoUrl` + * with a newly generated presigned URL. If generating a URL fails for an item, the original URL (if any) is left unchanged + * and processing continues for other items. + * + * @param instructions - The form's instructions array to process + * @returns The updated instructions array with regenerated `imageUrl` and `videoUrl` where applicable + */ export async function regenerateMediaUrls( instructions: FORMS.FormType["instructions"], ) { @@ -499,4 +564,4 @@ export function getPermsAsList(perms: string) { } } return list; -} +} \ No newline at end of file diff --git a/packages/consts/src/forms/index.ts b/packages/consts/src/forms/index.ts index 7b59f7a31..ad8781196 100644 --- a/packages/consts/src/forms/index.ts +++ b/packages/consts/src/forms/index.ts @@ -223,6 +223,12 @@ export const EVENT_FEEDBACK_HEARD = [ "From Another Club", ] as const; +/** + * Retrieve dropdown option values for a named constant. + * + * @param constName - The constant name to look up (one of: `LEVELS_OF_STUDY`, `ALLERGIES`, `MAJORS`, `GENDERS`, `RACES_OR_ETHNICITIES`, `COUNTRIES`, `SCHOOLS`, `COMPANIES`, `SHIRT_SIZES`, `EVENT_FEEDBACK_HEARD`, `SHORT_LEVELS_OF_STUDY`, `SHORT_RACES_AND_ETHNICITIES`) + * @returns The array of option strings associated with `constName`, or an empty array if the name is not recognized. + */ export function getDropdownOptionsFromConst( constName: string, ): readonly string[] { @@ -341,4 +347,4 @@ export interface Semester { endDate: Date; } -export const DEVPOST_TEAM_MEMBER_EMAIL_OFFSET = 3; +export const DEVPOST_TEAM_MEMBER_EMAIL_OFFSET = 3; \ No newline at end of file diff --git a/packages/db/scripts/seed_devdb.ts b/packages/db/scripts/seed_devdb.ts index 08ccbd909..aae31605f 100644 --- a/packages/db/scripts/seed_devdb.ts +++ b/packages/db/scripts/seed_devdb.ts @@ -220,6 +220,11 @@ interface DiscordRole { flags: number; } +/** + * Synchronizes production Discord roles that have permissions with the development guild and records ID mappings. + * + * Fetches production roles referenced in the backup database, attempts to match each by name and permissions with an existing role in the dev guild, and creates the role in the dev guild when no match exists. Updates the module-level `roleIdMappings` map with production-to-development role ID pairs. Performs network calls to the Discord API and returns early if the backup database is not initialized. + */ async function syncRoles() { if (!backupDb) return; @@ -285,6 +290,13 @@ interface DiscordGuildScheduledEvent { image?: string | null; } +/** + * Synchronizes scheduled events from the production guild to the development guild and records ID mappings. + * + * For each scheduled event in the production guild, reuses a development event that matches by name and start time + * or creates a new development event with the production event's data. Updates the global `eventIdMappings` + * with the mapping from production event ID to development event ID. Does nothing if the backup database is not initialized. + */ async function syncEvents() { if (!backupDb) return; @@ -436,4 +448,4 @@ async function main() { } } -await main(); +await main(); \ No newline at end of file