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
4 changes: 3 additions & 1 deletion apps/blade/src/app/admin/forms/[slug]/con-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export function ConnectionViewer({
Form Field
</div>
<div className="font-mono text-sm font-semibold">
{conn.formField || "Not Mapped"}
{conn.customValue
? `Custom: "${conn.customValue}"`
: conn.formField || "Not Mapped"}
</div>
</div>
</div>
Expand Down
74 changes: 65 additions & 9 deletions apps/blade/src/app/admin/forms/[slug]/linker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { useState } from "react";
import { useState } from "react";
import { Loader2 } from "lucide-react";
import { z } from "zod";

Expand All @@ -26,6 +26,7 @@ const matchingSchema = z.object({
z.object({
procField: z.string(),
formField: z.string().optional(),
customValue: z.string().optional(),
}),
),
});
Expand All @@ -44,7 +45,7 @@ export default function ListMatcher({
const [procFields, setProcFields] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [connections, setConnections] = useState<
{ procField: string; formField: string }[]
{ procField: string; formField?: string; customValue?: string }[]
>([]);

const formFields = form.questions;
Expand All @@ -67,16 +68,35 @@ export default function ListMatcher({
setProcSelection(value);
const newProcFields = procs[value].inputSchema;
setProcFields(newProcFields);
setConnections(
newProcFields.map((item) => ({ procField: item, formField: "" })),
);
setConnections(newProcFields.map((item) => ({ procField: item })));
};

const updateConnection = (index: number, value: string) => {
setConnections((prev) => {
const updated = [...prev];
if (!updated[index]) return updated;
updated[index] = { ...updated[index], formField: value };
if (value === "__CUSTOM__") {
updated[index] = {
...updated[index],
formField: undefined,
customValue: updated[index].customValue || "",
};
} else {
updated[index] = {
...updated[index],
formField: value,
customValue: undefined,
};
}
return updated;
});
};

const updateCustomValue = (index: number, value: string) => {
setConnections((prev) => {
const updated = [...prev];
if (!updated[index]) return updated;
updated[index] = { ...updated[index], customValue: value };
return updated;
});
};
Expand All @@ -89,13 +109,35 @@ export default function ListMatcher({
return formFields.filter((item) => !usedItems.includes(item));
};

const isCustomValue = (index: number) => {
const conn = connections[index];
return conn && !conn.formField && conn.customValue !== undefined;
};

const handleSubmit = () => {
setIsLoading(true);

const cleanedConnections = connections.map((conn) => {
const cleaned: {
procField: string;
formField?: string;
customValue?: string;
} = {
procField: conn.procField,
};
if (conn.formField) {
cleaned.formField = conn.formField;
}
if (conn.customValue !== undefined && conn.customValue !== "") {
cleaned.customValue = conn.customValue;
}
return cleaned;
});

const data = {
form: form.id,
proc: procSelection,
connections: connections,
connections: cleanedConnections,
};

try {
Expand Down Expand Up @@ -126,7 +168,7 @@ export default function ListMatcher({
</div>
</div>

{procFields.length > 0 && formFields.length > 0 && (
{procFields.length > 0 && (
<div className="space-y-4 border-t pt-4">
<h3 className="text-lg font-semibold">Connect Items</h3>

Expand All @@ -145,7 +187,11 @@ export default function ListMatcher({
<div className="flex-1 space-y-2">
<Label htmlFor={`form-${index}`}>Form field</Label>
<Select
value={connection.formField}
value={
isCustomValue(index)
? "__CUSTOM__"
: connection.formField || ""
}
onValueChange={(value) => updateConnection(index, value)}
>
<SelectTrigger id={`form-${index}`}>
Expand All @@ -157,8 +203,18 @@ export default function ListMatcher({
{item}
</SelectItem>
))}
<SelectItem value="__CUSTOM__">Input Custom</SelectItem>
</SelectContent>
</Select>
{isCustomValue(index) && (
<Input
id={`custom-${index}`}
placeholder="Enter custom value"
value={connection.customValue || ""}
onChange={(e) => updateCustomValue(index, e.target.value)}
className="mt-2"
/>
)}
</div>
</div>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
ChevronLeft,
ChevronRight,
Expand All @@ -11,6 +12,7 @@ import {
FileSpreadsheet,
FileText,
Loader2,
X,
} from "lucide-react";

import type { FormType } from "@forge/consts/knight-hacks";
Expand All @@ -24,6 +26,7 @@ import { api } from "~/trpc/react";
interface PerUserResponsesViewProps {
formData: FormType;
responses: {
id: string;
submittedAt: Date;
responseData: Record<string, unknown>;
member: {
Expand All @@ -36,6 +39,7 @@ interface PerUserResponsesViewProps {
}

interface GroupedResponse {
id: string;
member: {
firstName: string;
lastName: string;
Expand All @@ -58,6 +62,7 @@ export function PerUserResponsesView({
acc[anonymousKey] = [];
}
acc[anonymousKey].push({
id: response.id,
member: {
firstName: "Anonymous",
lastName: "",
Expand All @@ -75,6 +80,7 @@ export function PerUserResponsesView({
acc[userId] = [];
}
acc[userId].push({
id: response.id,
member: response.member,
submittedAt: response.submittedAt,
responseData: response.responseData,
Expand Down Expand Up @@ -191,10 +197,15 @@ export function PerUserResponsesView({
{currentUserResponses.map((response, responseIndex) => (
<Card key={responseIndex}>
<CardHeader>
<CardTitle>Response #{responseIndex + 1}</CardTitle>
<p className="mt-1 text-sm text-muted-foreground">
Submitted: {new Date(response.submittedAt).toLocaleString()}
</p>
<div className="flex items-start justify-between">
<div>
<CardTitle>Response #{responseIndex + 1}</CardTitle>
<p className="mt-1 text-sm text-muted-foreground">
Submitted: {new Date(response.submittedAt).toLocaleString()}
</p>
</div>
<DeleteResponseButton responseId={response.id} />
</div>
</CardHeader>
<CardContent className="space-y-4">
{formData.questions.map((question, questionIndex) => {
Expand Down Expand Up @@ -241,6 +252,38 @@ export function PerUserResponsesView({
);
}

function DeleteResponseButton({ responseId }: { responseId: string }) {
const router = useRouter();
const utils = api.useUtils();

const deleteResponse = api.forms.deleteResponse.useMutation({
async onSuccess() {
toast.success("Response deleted");
await utils.forms.getResponses.invalidate();
router.refresh();
},
onError() {
toast.error("Failed to delete response");
},
});

return (
<Button
variant="ghost"
size="icon"
onClick={() => deleteResponse.mutate({ id: responseId })}
disabled={deleteResponse.isPending}
className="text-destructive hover:bg-destructive/10 hover:text-destructive"
>
{deleteResponse.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<X className="h-4 w-4" />
)}
</Button>
);
}

function FileUploadDisplay({ objectName }: { objectName: string }) {
const [isDownloading, setIsDownloading] = useState(false);

Expand Down
1 change: 1 addition & 0 deletions apps/blade/src/app/admin/forms/[slug]/responses/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default async function FormResponsesPage({

// type assertion to the correct format
const responses = apiResponses as {
id: string;
submittedAt: Date;
responseData: Record<string, unknown>;
member: {
Expand Down
15 changes: 11 additions & 4 deletions apps/blade/src/app/forms/[formName]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ export default async function FormResponderPage({
const data: Record<string, unknown> = {};
for (const map of con.connections as {
procField: string;
formField: string;
formField?: string;
customValue?: string;
}[]) {
if (map.formField in response)
if (map.customValue !== undefined) {
data[map.procField] = map.customValue;
} else if (map.formField && map.formField in response) {
data[map.procField] = response[map.formField];
}
}

const route = procs[con.proc]?.route.split(".");
Expand All @@ -71,10 +75,13 @@ export default async function FormResponderPage({
color: "success_green",
userId: session.user.discordUserId,
});
} catch {
} catch (error) {
const errorMessage = JSON.stringify(error, null, 2);
await log({
title: `Failed to automatically fire procedure`,
message: `**Failed to fire procedure**\n\`${con.proc}\`\n\nTriggered after **${form.name}** submission from **${session.user.name}**\n\n**Data:**\n\`\`\`json\n${stringify(data)}\`\`\``,
message:
`**Failed to fire procedure**\n\`${con.proc}\`\n\nTriggered after **${form.name}** submission from **${session.user.name}**\n\n**Data:**\n\`\`\`json\n${stringify(data)}\`\`\`` +
`\n\n**Error:**\n\`\`\`json\n${errorMessage}\`\`\``,
color: "uhoh_red",
userId: session.user.discordUserId,
});
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { hackathonRouter } from "./routers/hackathon";
import { hackerRouter } from "./routers/hacker";
import { judgeRouter } from "./routers/judge";
import { memberRouter } from "./routers/member";
import { miscRouter } from "./routers/misc";
import { passkitRouter } from "./routers/passkit";
import { qrRouter } from "./routers/qr";
import { resumeRouter } from "./routers/resume";
Expand All @@ -21,6 +22,7 @@ import { userRouter } from "./routers/user";
import { createTRPCRouter } from "./trpc";

export const appRouter = createTRPCRouter<{
misc: typeof miscRouter;
auth: typeof authRouter;
duesPayment: typeof duesPaymentRouter;
member: typeof memberRouter;
Expand All @@ -42,6 +44,7 @@ export const appRouter = createTRPCRouter<{
forms: typeof formsRouter;
roles: typeof rolesRouter;
}>({
misc: miscRouter,
auth: authRouter,
duesPayment: duesPaymentRouter,
member: memberRouter,
Expand Down
Loading
Loading