[#398] COMPONENTS - Added creation/edit dialog component#421
[#398] COMPONENTS - Added creation/edit dialog component#421
Conversation
📝 WalkthroughWalkthroughIntroduces 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment 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 |
There was a problem hiding this comment.
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 oraria-labelattributes. 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: Addaria-labelledbyto improve screen reader accessibility.The dialog has
role="dialog"andaria-modal, but screen readers benefit fromaria-labelledbypointing 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/endTimeto be beforestartDate/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
📒 Files selected for processing (1)
apps/blade/src/app/_components/issues/create-edit-dialog.tsx
| 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; | ||
| } |
There was a problem hiding this comment.
🧩 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 -40Repository: 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.tsxRepository: 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 -30Repository: 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.tsRepository: 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.tsxRepository: 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.tsRepository: 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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| function formatDateForInput(date: Date) { | ||
| return date.toISOString().slice(0, 10); | ||
| } | ||
|
|
||
| function formatTimeForInput(date: Date) { | ||
| return date.toTimeString().slice(0, 5); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
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
db:pushbefore mergingSummary by CodeRabbit