Skip to content

[#398] COMPONENTS - Added creation/edit dialog component#421

Open
mchdich wants to merge 2 commits intomainfrom
issue-dialog-component
Open

[#398] COMPONENTS - Added creation/edit dialog component#421
mchdich wants to merge 2 commits intomainfrom
issue-dialog-component

Conversation

@mchdich
Copy link
Contributor

@mchdich mchdich commented Mar 13, 2026

Why

For the Calendar/Task Manager sprint.

What

Issue(s): #398
Dialog to create or edit a new issue.

Test Plan

Merge me twin.

Checklist

  • Database: No schema changes, OR I have contacted the Development Lead to run db:push before merging
  • Environment Variables: No environment variables changed, OR I have contacted the Development Lead to modify them on Coolify BEFORE merging.

Summary by CodeRabbit

  • New Features
    • New dialog for creating and editing issues with multi-section form (Details, Room & Requirements, Links & Notes)
    • Status selection with visual indicators, date/time management with all-day support
    • Link management (add, update, remove), room booking and requirements configuration
    • Visibility controls and delete functionality for existing issues

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

Introduces a new React component for creating and editing event-style issues through a modal dialog. The component provides multi-section form state management (Details, Room & Requirements, Links & Notes) with date-time selection, visibility controls, room booking, and dynamic link management capabilities.

Changes

Cohort / File(s) Summary
Issue Creation/Edit Form
apps/blade/src/app/_components/issues/create-edit-dialog.tsx
New component with comprehensive form for issue creation and editing. Includes sectioned form UI with status selection (with color indicators), date-time fields with all-day mode, team/priority/description fields, visibility and room booking toggles, dynamic link management (add/update/remove), and modal behaviors (overlay close, Escape key, scroll locking). Exports IssueFormValues interface, CreateEditDialogProps interface, and CreateEditDialog component.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Suggested labels

Blade, Feature, Minor

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title starts with the required [#398] issue number, includes a concise description, and is well under the 72-character limit at 56 characters.
No Hardcoded Secrets ✅ Passed File contains no hardcoded secrets, API keys, passwords, tokens, or sensitive credentials. All string values are legitimate UI configuration, form field names, labels, or styling classes.
Validated Env Access ✅ Passed The new CreateEditDialog component contains no direct process.env usage and follows the codebase pattern of keeping environment configuration centralized in env.ts files.
No Typescript Escape Hatches ✅ Passed No TypeScript escape hatches found: no any types, @ts-ignore, @ts-expect-error, or non-null assertions; only legitimate logical NOT operators and standard as type assertions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-dialog-component
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.

Add a configuration file to your project to customize how CodeRabbit runs biome.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
apps/blade/src/app/_components/issues/create-edit-dialog.tsx (3)

706-718: Add accessible labels to dynamic link inputs.

The link label and URL inputs lack associated <Label> elements or aria-label attributes. Screen reader users won't know what these fields are for.

Add aria-labels to link inputs
 <Input
   className={cn(baseField, "h-11")}
   placeholder="Label (e.g., Notion doc)"
+  aria-label={`Link ${formValues.links.indexOf(link) + 1} label`}
   value={link.label}
   onChange={(event) =>
     handleLinkUpdate(
       link.id,
       "label",
       event.target.value,
     )
   }
 />
 <Input
   className={cn(baseField, "h-11 pl-9")}
   placeholder="URL"
   type="url"
+  aria-label={`Link ${formValues.links.indexOf(link) + 1} URL`}
   value={link.url}
   onChange={(event) =>
     handleLinkUpdate(
       link.id,
       "url",
       event.target.value,
     )
   }
 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx` around lines
706 - 718, The dynamic link inputs rendered inside the create-edit dialog are
missing accessible labels; update the Input elements (the ones using
value={link.label} and value={link.url} and onChange calling
handleLinkUpdate(link.id,...)) to include accessible labeling — either wrap each
input with the existing Label component or add descriptive aria-label attributes
(e.g., aria-label="Link label" and aria-label="Link URL") and keep the existing
className={cn(baseField, "h-11")}; ensure the label text or aria-label uniquely
identifies the field when iterating multiple links so screen readers can
distinguish them (you can include the link.id or index in the aria-label if
needed).

300-331: Add aria-labelledby to improve screen reader accessibility.

The dialog has role="dialog" and aria-modal, but screen readers benefit from aria-labelledby pointing to the title. This helps users understand the dialog's purpose when it opens.

Add aria-labelledby
+      const titleId = `${baseId}-dialog-title`;
+      // ... in JSX:
       <div
         aria-modal
         role="dialog"
+        aria-labelledby={titleId}
         className="fixed inset-0 z-50 ..."
         onMouseDown={handleOverlayPointerDown}
       >
         ...
-          <h2 className="text-2xl font-semibold text-white">
+          <h2 id={titleId} className="text-2xl font-semibold text-white">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx` around lines
300 - 331, The dialog container with role="dialog" lacks aria-labelledby; add
aria-labelledby to the outer div returned by createPortal (the element with
role="dialog") and give the title h2 a matching id (e.g.,
id="create-edit-dialog-title") so screen readers announce the dialog title;
update the h2 inside the header (the element rendering "Update the event details
below" / "Enter the event details below") and the createPortal div (the element
with aria-modal and role="dialog") accordingly.

285-288: Consider validating date range before submission.

The form allows endDate/endTime to be before startDate/startTime. Adding client-side validation prevents users from submitting logically invalid date ranges.

Example validation in handleSubmit
 const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
   event.preventDefault();
+  
+  if (!formValues.allDay) {
+    const start = new Date(`${formValues.startDate}T${formValues.startTime}`);
+    const end = new Date(`${formValues.endDate}T${formValues.endTime}`);
+    if (end <= start) {
+      // Handle error - could add error state and display message
+      return;
+    }
+  }
+  
   onSubmit?.(formValues);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx` around lines
285 - 288, The handleSubmit function currently submits formValues without
checking that the end date/time is after the start; update handleSubmit to
validate the date range by constructing comparable Date objects from
formValues.startDate/formValues.startTime and
formValues.endDate/formValues.endTime, and if the end is not after the start
prevent submission (call event.preventDefault() already present), set a
validation error state or invoke existing error handler to show a user-facing
message, and only call onSubmit(formValues) when the date-range check passes;
reference the handleSubmit function, formValues fields (startDate, startTime,
endDate, endTime), and onSubmit when implementing this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx`:
- Around line 737-744: The delete button that renders the Trash2 icon lacks an
accessible name; update the Button element that calls handleRemoveLink(link.id)
to include an aria-label (for example aria-label={`Delete link${link.title ? `:
${link.title}` : ''}`} or a static "Delete link") so screen readers get context
when they encounter the icon-only button.
- Around line 821-827: formatDateForInput currently uses date.toISOString()
(UTC) while formatTimeForInput uses date.toTimeString() (local), causing
timezone drift; change formatDateForInput to produce a local YYYY-MM-DD string
(e.g., via date.getFullYear()/getMonth()+1/getDate with zero-padding or
date.toLocaleDateString('sv')/ 'en-CA') so both formatDateForInput and
formatTimeForInput use the same local time basis and remain consistent across
timezones.
- Around line 31-49: IssueFormValues fields and STATUS_OPTIONS values do not
match the Issue DB enum (ISSUE.ISSUE_STATUS) and other form fields don't map to
the Issue model; before calling createIssue you must transform the form payload
to the DB shape: map form status values (from STATUS_OPTIONS) to the DB enum
values (e.g., "confirmed"/"tentative"/"draft"/"cancelled" -> one of
BACKLOG/PLANNING/IN_PROGRESS/FINISHED), combine startDate+startTime and
endDate+endTime into the single timestamp column (date) or otherwise produce the
expected timestamp(s), convert LinkItem[] into a string[] (text[])
representation, and drop or persist the extra properties (priority,
requiresRoom, needsDesignAssets, needsOutreach, requirements, notes) either by
removing them from the payload sent to createIssue or by adding corresponding DB
columns if intended; update the code path that builds the mutation payload
(where IssueFormValues is serialized for createIssue) to perform these mappings
and validations.

---

Nitpick comments:
In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx`:
- Around line 706-718: The dynamic link inputs rendered inside the create-edit
dialog are missing accessible labels; update the Input elements (the ones using
value={link.label} and value={link.url} and onChange calling
handleLinkUpdate(link.id,...)) to include accessible labeling — either wrap each
input with the existing Label component or add descriptive aria-label attributes
(e.g., aria-label="Link label" and aria-label="Link URL") and keep the existing
className={cn(baseField, "h-11")}; ensure the label text or aria-label uniquely
identifies the field when iterating multiple links so screen readers can
distinguish them (you can include the link.id or index in the aria-label if
needed).
- Around line 300-331: The dialog container with role="dialog" lacks
aria-labelledby; add aria-labelledby to the outer div returned by createPortal
(the element with role="dialog") and give the title h2 a matching id (e.g.,
id="create-edit-dialog-title") so screen readers announce the dialog title;
update the h2 inside the header (the element rendering "Update the event details
below" / "Enter the event details below") and the createPortal div (the element
with aria-modal and role="dialog") accordingly.
- Around line 285-288: The handleSubmit function currently submits formValues
without checking that the end date/time is after the start; update handleSubmit
to validate the date range by constructing comparable Date objects from
formValues.startDate/formValues.startTime and
formValues.endDate/formValues.endTime, and if the end is not after the start
prevent submission (call event.preventDefault() already present), set a
validation error state or invoke existing error handler to show a user-facing
message, and only call onSubmit(formValues) when the date-range check passes;
reference the handleSubmit function, formValues fields (startDate, startTime,
endDate, endTime), and onSubmit when implementing this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 5cc81bde-1b80-4cdc-9cd4-6d1246535d03

📥 Commits

Reviewing files that changed from the base of the PR and between 0194403 and 51b5700.

📒 Files selected for processing (1)
  • apps/blade/src/app/_components/issues/create-edit-dialog.tsx

Comment on lines +31 to +49
export interface IssueFormValues {
title: string;
status: IssueStatus;
startDate: string;
startTime: string;
endDate: string;
endTime: string;
allDay: boolean;
team: string;
priority: string;
isHackathonCritical: boolean;
requiresRoom: boolean;
needsDesignAssets: boolean;
needsOutreach: boolean;
details: string;
requirements: string;
links: LinkItem[];
notes: string;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the Issue schema and tRPC mutation to confirm field expectations
echo "=== Issue table schema ==="
rg -n -A 30 'export const Issue = createTable' packages/db/src/schemas/

echo ""
echo "=== CreateIssueInputSchema ==="
rg -n -A 10 'CreateIssueInputSchema' packages/api/src/routers/issues.ts

echo ""
echo "=== ISSUE_STATUS enum values ==="
rg -n 'ISSUE_STATUS' packages/consts/src/

Repository: KnightHacks/forge

Length of output: 3487


🏁 Script executed:

#!/bin/bash
# Get the IssueSchema definition
echo "=== IssueSchema definition ==="
rg -n -B 5 -A 20 'export const IssueSchema' packages/api/src/

echo ""
echo "=== Full ISSUE_STATUS enum ==="
cat packages/consts/src/issue.ts

echo ""
echo "=== Check for form transformation logic in the dialog component ==="
grep -n -A 5 "onSubmit\|handleSubmit\|mutation" apps/blade/src/app/_components/issues/create-edit-dialog.tsx | head -40

Repository: KnightHacks/forge

Length of output: 1086


🏁 Script executed:

#!/bin/bash
# Search for IssueSchema in different ways
echo "=== Search for IssueSchema ==="
rg -n 'IssueSchema' packages/api/src/routers/issues.ts | head -20

echo ""
echo "=== Check zod schema definitions in issues.ts ==="
rg -n -B 2 -A 15 'const.*Schema.*=' packages/api/src/routers/issues.ts | head -60

echo ""
echo "=== Read the entire STATUS_OPTIONS section from the dialog ==="
sed -n '60,80p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

Repository: KnightHacks/forge

Length of output: 2668


🏁 Script executed:

#!/bin/bash
# Find where IssueSchema is defined
echo "=== IssueSchema import source ==="
rg -n 'export.*IssueSchema\|const IssueSchema' packages/

echo ""
echo "=== Find where CreateEditDialog is used ==="
rg -n 'CreateEditDialog' apps/blade/src --type tsx --type ts | head -20

echo ""
echo "=== Check the parent component that uses CreateEditDialog ==="
# Look for the component that imports and uses CreateEditDialog
rg -B 5 -A 10 'CreateEditDialog' apps/blade/src/app --type tsx | grep -A 10 "onSubmit" | head -30

Repository: KnightHacks/forge

Length of output: 275


🏁 Script executed:

#!/bin/bash
# Find IssueSchema definition without type filters
echo "=== Search for IssueSchema definition ==="
rg -n 'export.*IssueSchema' packages/

echo ""
echo "=== Find CreateEditDialog usage ==="
rg -rn 'CreateEditDialog' apps/blade/src/ 2>/dev/null

echo ""
echo "=== Check what's imported from db schemas in issues.ts ==="
sed -n '1,20p' packages/api/src/routers/issues.ts

Repository: KnightHacks/forge

Length of output: 1160


🏁 Script executed:

#!/bin/bash
# Get IssueSchema from createInsertSchema
echo "=== Read Issue table columns again ==="
sed -n '555,585p' packages/db/src/schemas/knight-hacks.ts

echo ""
echo "=== Check for any status value transformation/mapping ==="
rg -n 'confirmed|tentative|draft|cancelled' packages/api/src/ packages/db/src/

echo ""
echo "=== Check the entire create-edit-dialog file to understand data flow ==="
wc -l apps/blade/src/app/_components/issues/create-edit-dialog.tsx

Repository: KnightHacks/forge

Length of output: 2830


🏁 Script executed:

#!/bin/bash
# Read the full file to understand the issue form
echo "=== First 100 lines of create-edit-dialog.tsx ==="
head -100 apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo ""
echo "=== Lines around form submission (285-320) ==="
sed -n '285,320p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo ""
echo "=== What is issueStatus() in the Issue table? ==="
rg -n -B 2 -A 2 'issueStatus' packages/db/src/schemas/knight-hacks.ts

Repository: KnightHacks/forge

Length of output: 4069


Form's issue status values don't match the database enum.

The form uses STATUS_OPTIONS with values "confirmed", "tentative", "draft", "cancelled", but the Issue table's status column is a PostgreSQL enum restricted to ISSUE.ISSUE_STATUS: ["BACKLOG", "PLANNING", "IN_PROGRESS", "FINISHED"]. Submitting the form's status values to the tRPC mutation will fail validation.

Additionally, several form fields have no corresponding database columns: priority, requiresRoom, needsDesignAssets, needsOutreach, requirements, notes. The form also uses separate date/time fields (startDate, startTime, endDate, endTime), whereas the database has only a single date timestamp column. The links field is typed as LinkItem[] objects but the database expects text[].

Before wiring this form to createIssue, either:

  • Transform form data to match the Issue schema (map status values, flatten/restructure fields)
  • Extend the Issue table if these fields are intentional for your use case
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx` around lines 31
- 49, IssueFormValues fields and STATUS_OPTIONS values do not match the Issue DB
enum (ISSUE.ISSUE_STATUS) and other form fields don't map to the Issue model;
before calling createIssue you must transform the form payload to the DB shape:
map form status values (from STATUS_OPTIONS) to the DB enum values (e.g.,
"confirmed"/"tentative"/"draft"/"cancelled" -> one of
BACKLOG/PLANNING/IN_PROGRESS/FINISHED), combine startDate+startTime and
endDate+endTime into the single timestamp column (date) or otherwise produce the
expected timestamp(s), convert LinkItem[] into a string[] (text[])
representation, and drop or persist the extra properties (priority,
requiresRoom, needsDesignAssets, needsOutreach, requirements, notes) either by
removing them from the payload sent to createIssue or by adding corresponding DB
columns if intended; update the code path that builds the mutation payload
(where IssueFormValues is serialized for createIssue) to perform these mappings
and validations.

Comment on lines +737 to +744
<Button
variant="ghost"
type="button"
className="h-11 w-11 rounded-2xl border border-white/10 bg-transparent text-white/70 hover:border-white/30 hover:text-white"
onClick={() => handleRemoveLink(link.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add aria-label to the delete link button.

The button only contains an icon. Screen readers will announce it as "button" with no context.

Add aria-label
 <Button
   variant="ghost"
   type="button"
   className="h-11 w-11 rounded-2xl border border-white/10 bg-transparent text-white/70 hover:border-white/30 hover:text-white"
   onClick={() => handleRemoveLink(link.id)}
+  aria-label={`Remove link ${link.label || "item"}`}
 >
   <Trash2 className="h-4 w-4" />
 </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="ghost"
type="button"
className="h-11 w-11 rounded-2xl border border-white/10 bg-transparent text-white/70 hover:border-white/30 hover:text-white"
onClick={() => handleRemoveLink(link.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
<Button
variant="ghost"
type="button"
className="h-11 w-11 rounded-2xl border border-white/10 bg-transparent text-white/70 hover:border-white/30 hover:text-white"
onClick={() => handleRemoveLink(link.id)}
aria-label={`Remove link ${link.label || "item"}`}
>
<Trash2 className="h-4 w-4" />
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx` around lines
737 - 744, The delete button that renders the Trash2 icon lacks an accessible
name; update the Button element that calls handleRemoveLink(link.id) to include
an aria-label (for example aria-label={`Delete link${link.title ? `:
${link.title}` : ''}`} or a static "Delete link") so screen readers get context
when they encounter the icon-only button.

Comment on lines +821 to +827
function formatDateForInput(date: Date) {
return date.toISOString().slice(0, 10);
}

function formatTimeForInput(date: Date) {
return date.toTimeString().slice(0, 5);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Timezone inconsistency between date and time formatting.

formatDateForInput uses toISOString() which returns UTC, but formatTimeForInput uses toTimeString() which returns local time. This mismatch can cause the displayed date to be off by one day near midnight in certain timezones.

Use consistent local time formatting
 function formatDateForInput(date: Date) {
-  return date.toISOString().slice(0, 10);
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, "0");
+  const day = String(date.getDate()).padStart(2, "0");
+  return `${year}-${month}-${day}`;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function formatDateForInput(date: Date) {
return date.toISOString().slice(0, 10);
}
function formatTimeForInput(date: Date) {
return date.toTimeString().slice(0, 5);
}
function formatDateForInput(date: Date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
function formatTimeForInput(date: Date) {
return date.toTimeString().slice(0, 5);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx` around lines
821 - 827, formatDateForInput currently uses date.toISOString() (UTC) while
formatTimeForInput uses date.toTimeString() (local), causing timezone drift;
change formatDateForInput to produce a local YYYY-MM-DD string (e.g., via
date.getFullYear()/getMonth()+1/getDate with zero-padding or
date.toLocaleDateString('sv')/ 'en-CA') so both formatDateForInput and
formatTimeForInput use the same local time basis and remain consistent across
timezones.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant