Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,3 @@ jobs:

- name: Build
run: pnpm build

pr-checks:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Validate PR
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const e = [];
if (!pr.labels.length) e.push('Missing label.');
if (!pr.assignees.length) e.push('Missing assignee.');
if (!/^\[#\d+\]/.test(pr.title))
e.push('Title must start with issue number (e.g. [#123] Add feature).');
if (e.length) core.setFailed(e.join(' '));
4 changes: 4 additions & 0 deletions apps/blade/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const config = {
protocol: "https",
hostname: "minio-g0soogg4gs8gwcggw4ococok.knighthacks.org",
},
{
protocol: "https",
hostname: "minio-y44gsgsskc4ko4kkwsg0csoc.135.237.97.107.sslip.io",
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
],
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import { AwardIcon, Check, ChevronsUpDown } from "lucide-react";
import { z } from "zod";

Expand Down Expand Up @@ -38,6 +38,8 @@ export function ManualEntryForm() {
email: string;
} | null>(null);
const [open, setOpen] = useState(false);
const [searchValue, setSearchValue] = useState("");
const commandListRef = useRef<HTMLDivElement>(null);
Comment thread
DVidal1205 marked this conversation as resolved.

// Only show club events (events without hackathonId)
const filteredEvents = events?.filter((v) => !v.hackathonId);
Expand Down Expand Up @@ -93,6 +95,13 @@ export function ManualEntryForm() {
memberEventCheckIn.mutate(data);
};

// Reset scroll to top when search value changes
useEffect(() => {
if (commandListRef.current) {
commandListRef.current.scrollTop = 0;
}
}, [searchValue]);

const renderEventSelect = (filteredEvents: typeof events) => (
<FormField
name="eventId"
Expand All @@ -111,7 +120,6 @@ export function ManualEntryForm() {
<select
{...field}
className="w-full rounded border p-2"
defaultValue=""
onChange={(e) => {
const selectedEventId = e.target.value;
field.onChange(e);
Expand Down Expand Up @@ -172,8 +180,12 @@ export function ManualEntryForm() {
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="Search members by name or email..." />
<CommandList>
<CommandInput
placeholder="Search members by name or email..."
value={searchValue}
onValueChange={setSearchValue}
/>
<CommandList ref={commandListRef}>
<CommandEmpty>No members found.</CommandEmpty>
<CommandGroup>
{members?.map((member) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const UpdateFormSchema = InsertEventSchema.omit({
endHour: z.string(),
endMinute: z.string(),
endAmPm: z.enum(amPmOptions),
points: z.number().optional(),
});

function parseDateTime(value: string | Date) {
Expand Down Expand Up @@ -156,6 +157,7 @@ export function UpdateEventButton({ event }: { event: InsertEvent }) {
endHour: endHour,
endMinute: endMinute,
endAmPm: endAmPm as "AM" | "PM",
points: event.points ?? undefined,
},
});

Expand Down Expand Up @@ -226,6 +228,7 @@ export function UpdateEventButton({ event }: { event: InsertEvent }) {
hackathons?.find((v) => {
return v.id == values.hackathonId;
})?.displayName ?? null,
points: values.points,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,13 @@ export default function UpdateMemberButton({
firstName: z.string().min(1, "Required"),
lastName: z.string().min(1, "Required"),
email: z.string().email("Invalid email").min(1, "Required"),
points: z.string().transform((points) => Number(points)),
points: z.number(),
phoneNumber: z
.string()
.regex(/^\d{10}|\d{3}-\d{3}-\d{4}$|^$/, "Invalid phone number"),
});

const form = useForm({
// @ts-expect-error -- schema uses .transform() so input≠output; ZodType<TIn,TIn> in useForm can't represent this
schema: UpdateMemberSchema,
defaultValues: {
firstName: member.firstName || "",
Expand Down Expand Up @@ -115,14 +114,12 @@ export default function UpdateMemberButton({
onSubmit={form.handleSubmit((values) => {
setIsLoading(true);

const points = Number(values.points);

updateMember.mutate({
id: member.id,
firstName: values.firstName,
lastName: values.lastName,
email: values.email,
points: points,
points: values.points,
dob: values.dob,
phoneNumber: values.phoneNumber,
school: values.school,
Expand Down Expand Up @@ -209,7 +206,15 @@ export default function UpdateMemberButton({
Points
</FormLabel>
<FormControl>
<Input placeholder="1000" {...field} />
<Input
type="number"
placeholder="1000"
value={field.value}
onChange={(e) => {
const val = e.target.value;
field.onChange(val === "" ? 0 : Number(val));
}}
/>
</FormControl>
<FormMessage className="my-auto whitespace-nowrap" />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { Check, ChevronsUpDown, WrenchIcon } from "lucide-react";
import { z } from "zod";

Expand Down Expand Up @@ -42,6 +42,8 @@ export function ManualEntryForm() {
email: string;
} | null>(null);
const [open, setOpen] = useState(false);
const [searchValue, setSearchValue] = useState("");
const commandListRef = useRef<HTMLDivElement>(null);
Comment thread
DVidal1205 marked this conversation as resolved.
const [activeHackathon, setActiveHackathon] = useState<{
id: string;
name: string;
Expand Down Expand Up @@ -135,6 +137,13 @@ export function ManualEntryForm() {
hackerEventCheckIn.mutate(data);
};

// Reset scroll to top when search value changes
useEffect(() => {
if (commandListRef.current) {
commandListRef.current.scrollTop = 0;
}
}, [searchValue]);

const renderEventSelect = (filteredEvents: typeof events) => (
<FormField
name="eventId"
Expand All @@ -153,7 +162,6 @@ export function ManualEntryForm() {
<select
{...field}
className="w-full rounded border p-2"
defaultValue=""
onChange={(e) => {
const selectedEventId = e.target.value;
field.onChange(e);
Expand Down Expand Up @@ -191,7 +199,6 @@ export function ManualEntryForm() {
<select
{...field}
className="w-full rounded border p-2"
defaultValue=""
onChange={(e) => {
const selectedClass = e.target.value;
field.onChange(e);
Expand Down Expand Up @@ -278,8 +285,12 @@ export function ManualEntryForm() {
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="Search hackers by name or email..." />
<CommandList>
<CommandInput
placeholder="Search hackers by name or email..."
value={searchValue}
onValueChange={setSearchValue}
/>
<CommandList ref={commandListRef}>
<CommandEmpty>No hackers found.</CommandEmpty>
<CommandGroup>
{hackers?.map((hacker) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function UpdateEventButton({ event }: { event: InsertEvent }) {
endHour: endHour,
endMinute: endMinute,
endAmPm: endAmPm as "AM" | "PM",
points: EVENTS.EVENT_POINTS[event.tag],
points: event.points ?? EVENTS.EVENT_POINTS[event.tag],
},
});

Expand Down
10 changes: 7 additions & 3 deletions apps/blade/src/app/_components/admin/roles/roleassign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default function RoleAssign() {
) : (
roles.map((v, i) => {
return (
<li className="flex flex-row gap-3">
<li key={v.id} className="flex flex-row gap-3">
<Checkbox
id={"role-f_" + i}
checked={filterRoles[v.id] ?? false}
Expand Down Expand Up @@ -213,7 +213,10 @@ export default function RoleAssign() {
<TableBody className="max-h-[50vh] overflow-y-scroll">
{filteredUsers.map((v, i) => {
return (
<TableRow className={`${i % 2 == 1 && "bg-muted/20"}`}>
<TableRow
key={v.id}
className={`${i % 2 == 1 && "bg-muted/20"}`}
>
<TableCell className="flex flex-row gap-4 text-base font-semibold">
<Checkbox
id={"user_" + i}
Expand Down Expand Up @@ -273,6 +276,7 @@ export default function RoleAssign() {
{v.permissions.map((p) => {
return (
<li
key={p.roleId}
className={`p-1 text-sm text-muted-foreground`}
>
{mappedRoles[p.roleId]?.name ?? ""}
Expand Down Expand Up @@ -319,7 +323,7 @@ export default function RoleAssign() {
) : (
roles.map((v, i) => {
return (
<li className="flex flex-row gap-3">
<li key={v.id} className="flex flex-row gap-3">
<Checkbox
id={"role_" + i}
checked={checkedRoles[v.id] ?? false}
Expand Down
7 changes: 5 additions & 2 deletions apps/blade/src/app/_components/admin/roles/roletable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default function RoleTable() {
{roles.map((v, i) => {
const role = discordRoles?.at(i);
return (
<TableRow id={"role" + i} className="">
<TableRow key={v.id} id={"role" + i} className="">
<TableCell>
<div className="text-base font-medium">{v.name}</div>
</TableCell>
Expand Down Expand Up @@ -149,7 +149,10 @@ export default function RoleTable() {
<ul className="mt-1 max-h-48 list-disc overflow-y-auto px-4">
{permissions.getPermsAsList(v.permissions).map((p) => {
return (
<li className={`p-1 text-sm text-muted-foreground`}>
<li
key={p}
className={`p-1 text-sm text-muted-foreground`}
>
{p}
</li>
);
Expand Down
30 changes: 28 additions & 2 deletions apps/blade/src/app/_components/forms/connection-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,36 @@ export const handleCallbacks = async (

for (const con of connections) {
const data: Record<string, unknown> = {};
const missingFields: string[] = [];
const availableFields = Object.keys(response);

for (const map of con.connections as {
procField: string;
formField?: string;
customValue?: string;
}[]) {
if (map.customValue !== undefined) {
data[map.procField] = map.customValue;
} else if (map.formField && map.formField in response) {
data[map.procField] = response[map.formField];
} else if (map.formField) {
// Try exact match first (handles whitespace differences too)
const trimmedFormField = map.formField.trim();
if (trimmedFormField in response) {
data[map.procField] = response[trimmedFormField];
} else {
// Try case-insensitive match (also trim for whitespace tolerance)
const matchedField = availableFields.find(
(field) =>
field.trim().toLowerCase() === trimmedFormField.toLowerCase(),
);
if (matchedField) {
data[map.procField] = response[matchedField];
} else {
// Field not found - track it for error reporting
missingFields.push(
`${map.procField} (expected form field: "${map.formField}")`,
);
}
}
}
}

Expand All @@ -54,10 +75,15 @@ export const handleCallbacks = async (
});
} catch (error) {
const errorMessage = JSON.stringify(error, null, 2);
const missingFieldsMsg =
missingFields.length > 0
? `\n\n**Missing Form Fields:**\n${missingFields.map((f) => `- ${f}`).join("\n")}\n\n**Available Form Fields:**\n${availableFields.map((f) => `- "${f}"`).join("\n")}`
: "";
await discord.log({
title: `Failed to automatically fire procedure`,
message:
`**Failed to fire procedure**\n\`${con.proc}\`\n\nTriggered after **${name}** submission from **${session.user.name}**\n\n**Data:**\n\`\`\`json\n${stringify(data)}\`\`\`` +
missingFieldsMsg +
`\n\n**Error:**\n\`\`\`json\n${errorMessage}\`\`\``,
color: "uhoh_red",
userId: session.user.discordUserId,
Expand Down
Loading