Skip to content

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

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

[#398] COMPONENTS - Added creation/edit dialog component#421
mchdich wants to merge 8 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 dual modes (Event vs Task) and multi-section form (Details, Room & Requirements, Links & Notes)
    • Status selection with visual indicators, date/time/due management (including all-day), and team/priority controls
    • Link management (add, update, remove), room booking and requirements toggles, visibility controls
    • Keyboard/overlay close, body-scroll locking, and delete for existing issues
    • Public types for issue/event form values and dialog props added

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

Adds a new CreateEditDialog React component (modal) for creating/editing issues in Event or Task modes, plus related types/constants (CreateEditDialogProps, IssueFormValues, EventFormValues, DetailSectionKey, SECTION_TABS, status/priority types) consumed by the dialog.

Changes

Cohort / File(s) Summary
Create/Edit dialog component
apps/blade/src/app/_components/issues/create-edit-dialog.tsx
New modal component: portal overlay, body scroll lock, Escape/overlay close, Event vs Task form modes, tabs for Details/Room & Requirements/Links & Notes, status/team/priority controls, link add/remove/update UI, default form factories, submit and delete handlers, exported as CreateEditDialog.
Issue types & UI constants
packages/consts/src/issue.ts
Adds exported types/interfaces and constants used by the dialog: CreateEditDialogProps, IssueFormValues, EventFormValues, DetailSectionKey, SECTION_TABS, and status/priority related types/constants.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Dialog as CreateEditDialog
    participant Parent as ParentComponent
    participant API as onSubmit/onDelete

    User->>Dialog: open(intent=create|edit, initialValues?)
    Dialog-->>User: render modal (tabs, Event/Task fields, controls)
    User->>Dialog: change fields / toggle isEvent / add/remove links
    Dialog->>Dialog: update internal form state
    User->>Dialog: Submit
    Dialog->>Parent: onSubmit(formValues)
    Parent->>API: persist create/update
    API-->>Parent: success/failure
    Parent-->>Dialog: onClose() or surface errors
    User->>Dialog: Delete (when editing)
    Dialog->>Parent: onDelete(id)
    Parent->>API: delete
    API-->>Parent: success
    Parent-->>Dialog: onClose()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Suggested labels

Blade, Constants, Feature, Major

🚥 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 issue number in brackets [#398], followed by a concise description that accurately reflects the main changes—adding a dialog component for creating/editing issues. The title is 56 characters, well under the 72-character limit.
No Hardcoded Secrets ✅ Passed No hardcoded secrets, API keys, passwords, tokens, or sensitive credentials found in the modified files. Code follows security best practices.
Validated Env Access ✅ Passed The pull request contains no direct process.env usage in either modified file, correctly following the codebase convention to use validated env imports.
No Typescript Escape Hatches ✅ Passed The code contains no TypeScript escape hatches. No instances of any type, @ts-ignore, @ts-expect-error, non-null assertions, or type assertions were found.

✏️ 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.

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 +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.

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: 2

♻️ Duplicate comments (3)
apps/blade/src/app/_components/issues/create-edit-dialog.tsx (3)

854-861: ⚠️ Potential issue | 🟡 Minor

Add an accessible name to the icon-only delete button.

At Line 854, the trash-icon button has no aria-label, so assistive tech lacks context.

Suggested patch
 <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>

As per coding guidelines, apps/blade/** requires accessibility-safe UI patterns (including ARIA coverage for actionable controls).

🤖 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
854 - 861, The trash-icon-only Button (Button with onClick={() =>
handleRemoveLink(link.id)} rendering <Trash2 />) is missing an accessible name;
add an aria-label (or aria-labelledby) to the Button so screen readers have
context (e.g., aria-label={`Delete link`} or a dynamic label like
aria-label={`Delete link ${link.title || link.url || link.id}`}) while keeping
the existing onClick handler and classes; update the Button that wraps <Trash2
className="h-4 w-4" /> to include this aria attribute so the control is
accessible.

938-944: ⚠️ Potential issue | 🟡 Minor

Use a consistent local-time basis for date/time formatting.

Line 939 uses UTC (toISOString) while Line 943 uses local time (toTimeString), which can shift the displayed date near midnight/timezone boundaries.

Suggested patch
 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}`;
 }
In JavaScript, does Date.prototype.toISOString() always output UTC while Date.prototype.toTimeString() outputs local time?
🤖 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
938 - 944, formatDateForInput uses UTC via toISOString() while
formatTimeForInput uses local time via toTimeString(), causing inconsistent
results near timezone boundaries; change both to use the same local-time basis
by deriving date and time from the Date object's local getters (e.g.,
getFullYear, getMonth, getDate for formatDateForInput and getHours, getMinutes
for formatTimeForInput) and zero-pad as needed so both functions consistently
format based on local time; update the implementations of formatDateForInput and
formatTimeForInput accordingly.

68-93: ⚠️ Potential issue | 🟠 Major

Align status/priority option values with @forge/consts enums.

The select options use values outside ISSUE_STATUS/PRIORITY (confirmed, tentative, lowercase priorities), and casts at Line 463 mask the mismatch. This can submit values the backend does not accept.

Suggested patch
-const STATUS_OPTIONS = [
-  { value: "confirmed", label: "Confirmed", caption: "Everything is locked in", dotClass: "bg-emerald-400" },
-  { value: "tentative", label: "Tentative", caption: "Waiting on a few details", dotClass: "bg-amber-400" },
-  { value: "draft", label: "Draft", caption: "Still being scoped", dotClass: "bg-slate-400" },
-  { value: "cancelled", label: "Cancelled", caption: "No longer happening", dotClass: "bg-rose-400" },
-] as const;
+const STATUS_OPTIONS: { value: IssueStatus; label: string; caption: string; dotClass: string }[] = [
+  { value: "BACKLOG", label: "Backlog", caption: "Not started yet", dotClass: "bg-slate-400" },
+  { value: "PLANNING", label: "Planning", caption: "Being scoped", dotClass: "bg-amber-400" },
+  { value: "IN_PROGRESS", label: "In Progress", caption: "Actively underway", dotClass: "bg-sky-400" },
+  { value: "FINISHED", label: "Finished", caption: "Completed", dotClass: "bg-emerald-400" },
+];
@@
-const PRIORITY_OPTIONS = ["High", "Medium", "Low"];
+const PRIORITY_OPTIONS: IssuePriority[] = [...PRIORITY];
@@
-                onValueChange={(value) =>
-                  updateForm("status", value as IssueStatus)
-                }
+                onValueChange={(value) => updateForm("status", value as IssueStatus)}
@@
-                            {PRIORITY_OPTIONS.map((priority) => (
-                              <SelectItem
-                                key={priority}
-                                value={priority.toLowerCase()}
-                              >
+                            {PRIORITY_OPTIONS.map((priority) => (
+                              <SelectItem key={priority} value={priority}>
                                 {priority}
                               </SelectItem>
                             ))}
#!/bin/bash
# Verify enum sources vs local options (read-only).
echo "=== Canonical enums ==="
sed -n '1,40p' packages/consts/src/issue.ts

echo
echo "=== Local options and select value wiring ==="
sed -n '68,120p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx
sed -n '455,705p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

Also applies to: 110-111, 461-464, 677-693

🤖 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 68
- 93, STATUS_OPTIONS (and the Priority options referenced around
create-edit-dialog.tsx) use non-canonical string values and then get force-cast
when submitting; update the option objects so their value fields match the
canonical enums exported from `@forge/consts` (ISSUE_STATUS and PRIORITY) instead
of "confirmed"/"tentative" or lowercase priorities, and update any select wiring
that currently casts values (see the cast near the submit handler and the SELECT
value props around create-edit-dialog.tsx) to rely on the real enum types; in
short, replace the value strings in STATUS_OPTIONS (and the priority options)
with the corresponding ISSUE_STATUS/PRIORITY enum members and remove the unsafe
type casts so submitted payloads use the backend-accepted enum values.
🤖 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`:
- Line 927: The submit button copy currently uses intent to render "Create
Event"/"Update Event" but doesn't respect task mode; update the text generation
in the CreateEditDialog component to also consider the task vs event flag (e.g.,
use isTask, mode, or itemType used in this component) and return "Create
Task"/"Update Task" when in task mode and "Create Event"/"Update Event"
otherwise, keeping the existing intent-based switch for create vs update.

---

Duplicate comments:
In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx`:
- Around line 854-861: The trash-icon-only Button (Button with onClick={() =>
handleRemoveLink(link.id)} rendering <Trash2 />) is missing an accessible name;
add an aria-label (or aria-labelledby) to the Button so screen readers have
context (e.g., aria-label={`Delete link`} or a dynamic label like
aria-label={`Delete link ${link.title || link.url || link.id}`}) while keeping
the existing onClick handler and classes; update the Button that wraps <Trash2
className="h-4 w-4" /> to include this aria attribute so the control is
accessible.
- Around line 938-944: formatDateForInput uses UTC via toISOString() while
formatTimeForInput uses local time via toTimeString(), causing inconsistent
results near timezone boundaries; change both to use the same local-time basis
by deriving date and time from the Date object's local getters (e.g.,
getFullYear, getMonth, getDate for formatDateForInput and getHours, getMinutes
for formatTimeForInput) and zero-pad as needed so both functions consistently
format based on local time; update the implementations of formatDateForInput and
formatTimeForInput accordingly.
- Around line 68-93: STATUS_OPTIONS (and the Priority options referenced around
create-edit-dialog.tsx) use non-canonical string values and then get force-cast
when submitting; update the option objects so their value fields match the
canonical enums exported from `@forge/consts` (ISSUE_STATUS and PRIORITY) instead
of "confirmed"/"tentative" or lowercase priorities, and update any select wiring
that currently casts values (see the cast near the submit handler and the SELECT
value props around create-edit-dialog.tsx) to rely on the real enum types; in
short, replace the value strings in STATUS_OPTIONS (and the priority options)
with the corresponding ISSUE_STATUS/PRIORITY enum members and remove the unsafe
type casts so submitted payloads use the backend-accepted enum values.
🪄 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: dd1ecf33-f938-403d-ae85-54fa8e50ccf6

📥 Commits

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

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

Comment on lines +44 to +57
export interface IssueFormValues {
status: IssueStatus;
name: string;
description: string;
links: LinkItem[];
date: string; // ISO string
priority: IssuePriority;
team: string; // UUID or name
parent?: string;
// UI only fields
isEvent: boolean;
// For event, we store event form values
event?: EventFormValues;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify missing type and key drift inside the dialog file (read-only).
echo "=== IssueFormValues declaration ==="
sed -n '44,90p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== updateForm key usage ==="
rg -nP 'updateForm\("([^"]+)"' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== Direct formValues accesses for UI-only fields ==="
rg -nP 'formValues\.(details|notes|requiresRoom|isHackathonCritical|needsDesignAssets|needsOutreach)' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== LinkItem declaration check ==="
rg -nP '\b(interface|type)\s+LinkItem\b' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

Repository: KnightHacks/forge

Length of output: 2150


🏁 Script executed:

#!/bin/bash
echo "=== LinkItem import/type in file ==="
head -50 apps/blade/src/app/_components/issues/create-edit-dialog.tsx | grep -E "import|LinkItem"

echo
echo "=== Search for needsDesignAssets usage ==="
rg -n 'needsDesignAssets' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== Delete button around line 854-861 ==="
sed -n '854,870p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== Check STATUS enum from consts ==="
rg -A10 'export.*STATUS|export.*enum.*Status' packages/consts/src/issue.ts

echo
echo "=== Check PRIORITY enum from consts ==="
rg -A10 'export.*PRIORITY|export.*enum.*Priority' packages/consts/src/issue.ts

Repository: KnightHacks/forge

Length of output: 1888


🏁 Script executed:

#!/bin/bash
echo "=== Full imports section (first 80 lines) ==="
sed -n '1,80p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== EventFormValues interface (around 113-127) ==="
sed -n '110,135p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== PRIORITY_OPTIONS definition ==="
rg -B2 -A15 'const PRIORITY_OPTIONS' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== Form submission - what gets sent to API ==="
rg -B5 -A10 'onSubmit\?.*values' apps/blade/src/app/_components/issues/create-edit-dialog.tsx | head -40

Repository: KnightHacks/forge

Length of output: 3943


🏁 Script executed:

#!/bin/bash
echo "=== Search for LinkItem definition across codebase ==="
rg -n 'interface LinkItem|type LinkItem' --type ts --type tsx

echo
echo "=== Submit handler and form value processing ==="
sed -n '900,950p' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== How STATUS_OPTIONS is used in form ==="
rg -B3 -A3 'SelectItem.*value.*confirmed' apps/blade/src/app/_components/issues/create-edit-dialog.tsx | head -30

Repository: KnightHacks/forge

Length of output: 1538


🏁 Script executed:

#!/bin/bash
echo "=== Search for LinkItem definition (no type filter) ==="
rg -n 'LinkItem' apps/blade/src/app/_components/issues/create-edit-dialog.tsx | head -5

echo
echo "=== Check if LinkItem is in shared types ==="
find . -path ./node_modules -prune -o -name '*.ts' -o -name '*.tsx' | xargs grep -l 'interface LinkItem\|type LinkItem' 2>/dev/null | head -10

echo
echo "=== Check handleSubmit and formValues usage ==="
rg -B5 -A8 'const handleSubmit|form.handleSubmit' apps/blade/src/app/_components/issues/create-edit-dialog.tsx

echo
echo "=== Check the default/initial formValues structure ==="
rg -B2 -A15 'const getDefaultValues|initialValues' apps/blade/src/app/_components/issues/create-edit-dialog.tsx | head -50

Repository: KnightHacks/forge

Length of output: 2037


Define LinkItem type and sync IssueFormValues with actual form field usage—currently missing required fields causing TypeScript errors and form data loss.

The interface declares links: LinkItem[] without defining LinkItem, and is missing five fields actively bound in the form: requiresRoom, isHackathonCritical, notes, needsDesignAssets, and needsOutreach. Additionally, the form binds to formValues.details but the interface defines description—the submitted data will lack a description field.

Also, fix enum value mismatches: STATUS_OPTIONS values ("confirmed", "tentative", "draft", "cancelled") don't match the imported ISSUE_STATUS constants ("BACKLOG", "PLANNING", "IN_PROGRESS", "FINISHED"), and PRIORITY_OPTIONS (["High", "Medium", "Low"]) don't match PRIORITY (["LOWEST", "LOW", "MEDIUM", "HIGH", "HIGHEST"]). These mismatches will cause API validation failures at runtime.

Finally, the submit button always shows "Create Event" / "Update Event" regardless of isEvent mode, and the delete-link icon button (line 854–861) lacks an accessible name.

Suggested fixes
+ interface LinkItem {
+   id: string;
+   label: string;
+   url: string;
+ }
+ 
  export interface IssueFormValues {
    status: IssueStatus;
    name: string;
    description: string;
    links: LinkItem[];
    date: string; // ISO string
    priority: IssuePriority;
    team: string; // UUID or name
    parent?: string;
+   requiresRoom: boolean;
+   isHackathonCritical: boolean;
+   notes: string;
+   needsDesignAssets: boolean;
+   needsOutreach: boolean;
    // UI only fields
    isEvent: boolean;
    // For event, we store event form values
    event?: EventFormValues;
  }

At form binding (line 716–719):

- value={formValues.details}
+ value={formValues.description}
  onChange={(event) =>
-   updateForm("details", event.target.value)
+   updateForm("description", event.target.value)
  }

For enums, align STATUS_OPTIONS and PRIORITY_OPTIONS with the imported constants rather than hardcoded strings.

Delete button (line 854–861):

  <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"
+   aria-label="Remove link"
    onClick={() => handleRemoveLink(link.id)}
  >

Submit button text (line 927):

  {intent === "edit" 
-   ? "Update Event" 
-   : "Create Event"
+   ? formValues.isEvent ? "Update Event" : "Update Issue"
+   : formValues.isEvent ? "Create Event" : "Create Issue"
  }

className="w-full disabled:opacity-40 sm:w-auto"
disabled={isSubmitDisabled}
>
{intent === "edit" ? "Update Event" : "Create Event"}
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

Use task/event-aware submit copy.

Line 927 always renders event text, which is incorrect in task mode.

Suggested patch
-                {intent === "edit" ? "Update Event" : "Create Event"}
+                {intent === "edit"
+                  ? formValues.isEvent
+                    ? "Update Event"
+                    : "Update Task"
+                  : formValues.isEvent
+                    ? "Create Event"
+                    : "Create Task"}
🤖 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` at line 927,
The submit button copy currently uses intent to render "Create Event"/"Update
Event" but doesn't respect task mode; update the text generation in the
CreateEditDialog component to also consider the task vs event flag (e.g., use
isTask, mode, or itemType used in this component) and return "Create
Task"/"Update Task" when in task mode and "Create Event"/"Update Event"
otherwise, keeping the existing intent-based switch for create vs update.

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: 2

♻️ Duplicate comments (2)
apps/blade/src/app/_components/issues/create-edit-dialog.tsx (1)

534-553: ⚠️ Potential issue | 🟠 Major

Use the enum value as the select value.

formValues.priority stores ISSUE.PRIORITY members like "LOWEST", but each SelectItem publishes priority.toLowerCase(). After the first selection, the state contains a value outside IssuePriority, the current option no longer matches, and any upstream enum validation will fail. Keep the value uppercase and only format the label for display.

Suggested patch
 {ISSUE.PRIORITY.map((priority) => (
-  <SelectItem key={priority} value={priority.toLowerCase()}>
-    {priority}
+  <SelectItem key={priority} value={priority}>
+    {getStatusLabel(priority)}
   </SelectItem>
 ))}
🤖 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
534 - 553, The Select is storing lowercase strings (priority.toLowerCase())
while formValues.priority expects ISSUE.PRIORITY enum members (e.g., "LOWEST"),
causing mismatches; update the SelectItem value to use the enum member
(priority) instead of priority.toLowerCase(), keep the displayed label formatted
(e.g., render priority.toLowerCase() or humanize it inside the SelectItem
children) and ensure updateForm("priority", value) continues to receive the
uppercase enum value so formValues.priority and any upstream IssuePriority
validation remain consistent.
packages/consts/src/issue.ts (1)

39-50: ⚠️ Potential issue | 🔴 Critical

Sync IssueFormValues with what the dialog actually edits.

CreateEditDialog reads/writes details, notes, requiresRoom, isHackathonCritical, needsDesignAssets, and needsOutreach in apps/blade/src/app/_components/issues/create-edit-dialog.tsx (for example Lines 574-576, 594-596, 616-618, and 718-720), but those keys do not exist here. Because updateForm is keyed off this interface, the component will not type-check, and the current details binding also means the dialog never populates the existing description field. Either add the missing state keys here or rename the component bindings to the existing schema before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/consts/src/issue.ts` around lines 39 - 50, The IssueFormValues
interface is missing fields that CreateEditDialog reads/writes (details, notes,
requiresRoom, isHackathonCritical, needsDesignAssets, needsOutreach) causing
type errors and incorrect binding of description; update the IssueFormValues
interface to include these properties with appropriate types (e.g., details:
string, notes: string, requiresRoom: boolean, isHackathonCritical: boolean,
needsDesignAssets: boolean, needsOutreach: boolean and keep existing fields like
description) so that updateForm and CreateEditDialog bindings compile and the
dialog correctly populates description, or alternatively change
CreateEditDialog’s bindings to the existing schema (e.g., use description
instead of details) but be sure to update all references in CreateEditDialog and
any callers (including updateForm) to match the chosen schema.
🤖 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 53-70: The event form currently leaves required fields tag and
description empty because defaultEventForm sets them but the Event branch UI (in
create-edit-dialog.tsx) never renders inputs for them and handleSubmit forwards
formValues unchanged to onSubmit; update the Event branch to either (a) render
controlled inputs for tag and description and validate them before submission
(modify the JSX where event mode is handled to include these fields and their
handlers) or (b) if you intend defaults, populate formValues.tag and
formValues.description with sensible non-empty values just prior to calling
handleSubmit/onSubmit (update the handleSubmit path that prepares the payload),
and add client-side validation in the submit flow to reject empty
tag/description so the required server schema is satisfied (referencing
defaultEventForm, handleSubmit, and onSubmit in this file).

In `@packages/consts/src/issue.ts`:
- Around line 52-65: EventFormValues currently requires discordId and googleId
even though the dialog never edits them and createEvent omits them; update the
form model by making discordId and googleId optional (discordId?: string;
googleId?: string) or removing them from EventFormValues and defaultEventForm
entirely, and adjust any usages (e.g., defaultEventForm and form initialization)
so they don't fabricate empty strings; also ensure the API shape in createEvent
(packages/api/src/routers/event.ts, createEvent handler) remains consistent with
the revised EventFormValues so there is no mismatch between the form type and
the create payload.

---

Duplicate comments:
In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx`:
- Around line 534-553: The Select is storing lowercase strings
(priority.toLowerCase()) while formValues.priority expects ISSUE.PRIORITY enum
members (e.g., "LOWEST"), causing mismatches; update the SelectItem value to use
the enum member (priority) instead of priority.toLowerCase(), keep the displayed
label formatted (e.g., render priority.toLowerCase() or humanize it inside the
SelectItem children) and ensure updateForm("priority", value) continues to
receive the uppercase enum value so formValues.priority and any upstream
IssuePriority validation remain consistent.

In `@packages/consts/src/issue.ts`:
- Around line 39-50: The IssueFormValues interface is missing fields that
CreateEditDialog reads/writes (details, notes, requiresRoom,
isHackathonCritical, needsDesignAssets, needsOutreach) causing type errors and
incorrect binding of description; update the IssueFormValues interface to
include these properties with appropriate types (e.g., details: string, notes:
string, requiresRoom: boolean, isHackathonCritical: boolean, needsDesignAssets:
boolean, needsOutreach: boolean and keep existing fields like description) so
that updateForm and CreateEditDialog bindings compile and the dialog correctly
populates description, or alternatively change CreateEditDialog’s bindings to
the existing schema (e.g., use description instead of details) but be sure to
update all references in CreateEditDialog and any callers (including updateForm)
to match the chosen schema.
🪄 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: 7d55970f-f0d2-4c1d-b439-169511cd1df2

📥 Commits

Reviewing files that changed from the base of the PR and between 11cdf63 and 5229fa8.

📒 Files selected for processing (2)
  • apps/blade/src/app/_components/issues/create-edit-dialog.tsx
  • packages/consts/src/issue.ts

Comment on lines +53 to +70
const defaultEventForm = (): ISSUE.EventFormValues => {
const now = new Date();
const end = new Date(now.getTime() + 60 * 60 * 1000);
return {
discordId: "",
googleId: "",
name: "",
tag: "",
description: "",
startDate: formatDateForInput(now),
startTime: formatTimeForInput(now),
endDate: formatDateForInput(end),
endTime: formatTimeForInput(end),
location: "",
dues_paying: false,
points: undefined,
hackathonId: undefined,
};
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

Event mode never collects a complete event payload.

defaultEventForm initializes tag and description, but the event branch only renders name/date/time/location controls, and handleSubmit forwards formValues unchanged. That means a new event can reach onSubmit with event.tag === "", and event.description is never updated anywhere in this component even though the Event schema/API treat both fields as required in packages/db/src/schemas/knight-hacks.ts:165-189 and packages/api/src/routers/event.ts:168-200. Add event-specific inputs for these fields or populate them before calling onSubmit.

As per coding guidelines apps/blade/**: Form validation on both client and server.

Also applies to: 224-231, 370-459

🤖 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 53
- 70, The event form currently leaves required fields tag and description empty
because defaultEventForm sets them but the Event branch UI (in
create-edit-dialog.tsx) never renders inputs for them and handleSubmit forwards
formValues unchanged to onSubmit; update the Event branch to either (a) render
controlled inputs for tag and description and validate them before submission
(modify the JSX where event mode is handled to include these fields and their
handlers) or (b) if you intend defaults, populate formValues.tag and
formValues.description with sensible non-empty values just prior to calling
handleSubmit/onSubmit (update the handleSubmit path that prepares the payload),
and add client-side validation in the submit flow to reject empty
tag/description so the required server schema is satisfied (referencing
defaultEventForm, handleSubmit, and onSubmit in this file).

Comment on lines +52 to +65
export interface EventFormValues {
discordId: string;
googleId: string;
name: string;
tag: string;
description: string;
startDate: string;
startTime: string;
endDate: string;
endTime: string;
location: string;
dues_paying: boolean;
points?: number;
hackathonId?: 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

Make provider IDs optional or move them out of the form model.

The dialog never edits discordId or googleId, but EventFormValues requires both, so defaultEventForm has to fabricate "" values. That makes new forms start with fake IDs and makes edit flows unable to distinguish “unchanged” from “cleared”. packages/api/src/routers/event.ts:168-200 also omits these fields from createEvent, so they are not part of the create payload shape.

Suggested shape
 export interface EventFormValues {
-  discordId: string;
-  googleId: string;
+  discordId?: string;
+  googleId?: string;
   name: string;
   tag: string;
   description: string;
📝 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
export interface EventFormValues {
discordId: string;
googleId: string;
name: string;
tag: string;
description: string;
startDate: string;
startTime: string;
endDate: string;
endTime: string;
location: string;
dues_paying: boolean;
points?: number;
hackathonId?: string;
export interface EventFormValues {
discordId?: string;
googleId?: string;
name: string;
tag: string;
description: string;
startDate: string;
startTime: string;
endDate: string;
endTime: string;
location: string;
dues_paying: boolean;
points?: number;
hackathonId?: string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/consts/src/issue.ts` around lines 52 - 65, EventFormValues currently
requires discordId and googleId even though the dialog never edits them and
createEvent omits them; update the form model by making discordId and googleId
optional (discordId?: string; googleId?: string) or removing them from
EventFormValues and defaultEventForm entirely, and adjust any usages (e.g.,
defaultEventForm and form initialization) so they don't fabricate empty strings;
also ensure the API shape in createEvent (packages/api/src/routers/event.ts,
createEvent handler) remains consistent with the revised EventFormValues so
there is no mismatch between the form type and the create payload.

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: 1

♻️ Duplicate comments (4)
apps/blade/src/app/_components/issues/create-edit-dialog.tsx (4)

692-699: ⚠️ Potential issue | 🟡 Minor

Add aria-label to the delete link button for accessibility.

The button only contains an icon (Trash2). Screen readers will announce "button" without context.

Fix: Add accessible 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(i)}
+  aria-label="Remove link"
 >
   <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
692 - 699, The delete button that renders the Trash2 icon lacks an accessible
label; update the Button rendered in create-edit-dialog.tsx (the Button with
onClick={() => handleRemoveLink(i)}) to include an aria-label (for example
aria-label={`Remove link ${i + 1}`} or a static "Remove link") so screen readers
receive context about the button's purpose while keeping the existing onClick
and styling.

774-780: ⚠️ Potential issue | 🟡 Minor

Timezone inconsistency between date and time formatting.

formatDateForInput uses toISOString() (UTC), but formatTimeForInput uses toTimeString() (local). Near midnight, this can cause the displayed date to be off by one day.

Example: At 11 PM in UTC-5, toISOString() returns tomorrow's date while toTimeString() returns today's time.

Fix: 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}`;
 }
🤖 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
774 - 780, The date/time formatting is inconsistent: formatDateForInput uses UTC
via toISOString() while formatTimeForInput uses local time via toTimeString(),
which can produce off-by-one-day dates near midnight; update formatDateForInput
to use local date fields (e.g., getFullYear(), getMonth()+1, getDate()) and
zero-pad to produce a "YYYY-MM-DD" string consistent with formatTimeForInput (or
alternatively make both use toLocaleDateString('en-CA') and a local time
formatter) so both functions operate in the same timezone (functions:
formatDateForInput and formatTimeForInput).

758-764: ⚠️ Potential issue | 🟡 Minor

Submit button always shows "Event" text regardless of mode.

Line 763 hardcodes "Event" but should adapt based on formValues.isEvent.

Fix: Use task/event-aware copy
 <Button
   type="submit"
   className="w-full disabled:opacity-40 sm:w-auto"
   disabled={isSubmitDisabled}
 >
-  {intent === "edit" ? "Update Event" : "Create Event"}
+  {intent === "edit"
+    ? formValues.isEvent ? "Update Event" : "Update Task"
+    : formValues.isEvent ? "Create Event" : "Create Task"}
 </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
758 - 764, The submit button currently hardcodes "Event" in its label; update
the Button render (inside create-edit-dialog.tsx) to pick "Task" vs "Event"
based on formValues.isEvent and still respect intent. Replace the current label
expression ({intent === "edit" ? "Update Event" : "Create Event"}) with a
conditional that uses formValues.isEvent to produce "Update Task"/"Create Task"
when false and "Update Event"/"Create Event" when true, referencing the existing
intent and formValues.isEvent variables so the button text adapts to both mode
and type.

71-89: ⚠️ Potential issue | 🟠 Major

Event mode lacks inputs for required tag and description fields.

The defaultEventForm() initializes tag and description to empty strings, but the event UI branch (lines 372-459) doesn't render inputs for these fields. Per EventFormValues, both are required—submitting with empty values will fail server validation.

Why it matters: The API expects non-empty tag and description for events. Users have no way to fill these fields.

Suggested fix: Add inputs for tag and description in event mode

Add these fields inside the event date/time section or in the Details tab when isEvent is true:

+              {formValues.isEvent && (
+                <div className="space-y-4">
+                  <div className="space-y-2">
+                    <Label className="text-sm text-white/70">Event Tag</Label>
+                    <Input
+                      className={cn(baseField, "h-11")}
+                      placeholder="e.g., workshop, social, hackathon"
+                      value={formValues.event?.tag ?? ""}
+                      onChange={(e) => updateEventForm("tag", e.target.value)}
+                    />
+                  </div>
+                  <div className="space-y-2">
+                    <Label className="text-sm text-white/70">Event Description</Label>
+                    <Textarea
+                      className={cn(baseField, "min-h-[100px] resize-none")}
+                      placeholder="Describe the event..."
+                      value={formValues.event?.description ?? ""}
+                      onChange={(e) => updateEventForm("description", e.target.value)}
+                    />
+                  </div>
+                </div>
+              )}
🤖 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 71
- 89, defaultEventForm() sets required EventFormValues.tag and .description to
empty strings but the event UI branch (the isEvent branch in
create-edit-dialog.tsx that renders the event date/time and details) does not
render inputs for these fields; add form inputs for "tag" and "description" to
the event mode UI (either inside the event date/time section or the Details tab
shown when isEvent is true) and wire them to the same form state handlers used
elsewhere (the form field names must match EventFormValues.tag and
EventFormValues.description and use the existing onChange/onBlur or form
register functions so server validation can receive non-empty values on submit).
🧹 Nitpick comments (1)
apps/blade/src/app/_components/issues/create-edit-dialog.tsx (1)

677-680: Consider stable keys for link items.

Using array index as key (line 679) can cause React to misassociate DOM state when items are removed mid-list. Since links are simple strings, you could generate stable IDs when adding items.

Why it matters: If a user deletes link 2 of 3, the input for link 3 might retain link 2's focus/cursor state.

Optional: Use stable IDs
- const createLinkItem = (): string => "";
+ interface LinkEntry {
+   id: string;
+   url: string;
+ }
+ const createLinkItem = (): LinkEntry => ({ id: crypto.randomUUID(), url: "" });

Then update the links array type and map by link.id.

🤖 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
677 - 680, The map over formValues.links is using the array index as key
(key={i}) which can break React reconciliation; change the links items to
objects with a stable id and value (e.g., {id: string, url: string}) when
creating/initializing links, generate the id in the add-link handler (use
nanoid/uuid or a simple incrementer), update all consumers (the form state
initialization, onChange handler, and remove handler) to read/write link.url and
link.id, and then map using key={link.id} instead of the index so inputs keep
stable identity when items are inserted/removed.
🤖 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 547-557: The SelectItem values use priority.toLowerCase(), which
mismatches ISSUE.PRIORITY and formValues.priority (uppercase) and breaks
selection and onValueChange typing; update the SelectItem value to use the
original priority casing (value={priority}) or alternatively normalize values
consistently by converting incoming onValueChange to the enum casing inside the
handler (update the handler used by onValueChange in the create/edit dialog),
ensuring SelectItem, formValues.priority and onValueChange all use the same enum
casing (reference SelectItem, SelectContent, ISSUE.PRIORITY,
formValues.priority, and the onValueChange handler).

---

Duplicate comments:
In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx`:
- Around line 692-699: The delete button that renders the Trash2 icon lacks an
accessible label; update the Button rendered in create-edit-dialog.tsx (the
Button with onClick={() => handleRemoveLink(i)}) to include an aria-label (for
example aria-label={`Remove link ${i + 1}`} or a static "Remove link") so screen
readers receive context about the button's purpose while keeping the existing
onClick and styling.
- Around line 774-780: The date/time formatting is inconsistent:
formatDateForInput uses UTC via toISOString() while formatTimeForInput uses
local time via toTimeString(), which can produce off-by-one-day dates near
midnight; update formatDateForInput to use local date fields (e.g.,
getFullYear(), getMonth()+1, getDate()) and zero-pad to produce a "YYYY-MM-DD"
string consistent with formatTimeForInput (or alternatively make both use
toLocaleDateString('en-CA') and a local time formatter) so both functions
operate in the same timezone (functions: formatDateForInput and
formatTimeForInput).
- Around line 758-764: The submit button currently hardcodes "Event" in its
label; update the Button render (inside create-edit-dialog.tsx) to pick "Task"
vs "Event" based on formValues.isEvent and still respect intent. Replace the
current label expression ({intent === "edit" ? "Update Event" : "Create Event"})
with a conditional that uses formValues.isEvent to produce "Update Task"/"Create
Task" when false and "Update Event"/"Create Event" when true, referencing the
existing intent and formValues.isEvent variables so the button text adapts to
both mode and type.
- Around line 71-89: defaultEventForm() sets required EventFormValues.tag and
.description to empty strings but the event UI branch (the isEvent branch in
create-edit-dialog.tsx that renders the event date/time and details) does not
render inputs for these fields; add form inputs for "tag" and "description" to
the event mode UI (either inside the event date/time section or the Details tab
shown when isEvent is true) and wire them to the same form state handlers used
elsewhere (the form field names must match EventFormValues.tag and
EventFormValues.description and use the existing onChange/onBlur or form
register functions so server validation can receive non-empty values on submit).

---

Nitpick comments:
In `@apps/blade/src/app/_components/issues/create-edit-dialog.tsx`:
- Around line 677-680: The map over formValues.links is using the array index as
key (key={i}) which can break React reconciliation; change the links items to
objects with a stable id and value (e.g., {id: string, url: string}) when
creating/initializing links, generate the id in the add-link handler (use
nanoid/uuid or a simple incrementer), update all consumers (the form state
initialization, onChange handler, and remove handler) to read/write link.url and
link.id, and then map using key={link.id} instead of the index so inputs keep
stable identity when items are inserted/removed.
🪄 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: e745c88b-6e8c-4626-bbda-a945a5324001

📥 Commits

Reviewing files that changed from the base of the PR and between 5229fa8 and 9a83954.

📒 Files selected for processing (2)
  • apps/blade/src/app/_components/issues/create-edit-dialog.tsx
  • packages/consts/src/issue.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/consts/src/issue.ts

Comment on lines +547 to +557
<SelectContent>
{ISSUE.PRIORITY.map((priority) => (
<SelectItem
key={priority}
value={priority.toLowerCase()}
>
{priority}
</SelectItem>
))}
</SelectContent>
</Select>
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

Priority select value casing mismatch will break selection state.

SelectItem values are set to priority.toLowerCase() (line 551), but formValues.priority stores uppercase values from ISSUE.PRIORITY. This causes two problems:

  1. The current selection won't display (values don't match)
  2. onValueChange receives lowercase but the type expects uppercase

Fix: Keep values consistent with the enum:

Proposed fix
 <SelectContent>
   {ISSUE.PRIORITY.map((priority) => (
     <SelectItem
       key={priority}
-      value={priority.toLowerCase()}
+      value={priority}
     >
       {priority}
     </SelectItem>
   ))}
 </SelectContent>
🤖 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
547 - 557, The SelectItem values use priority.toLowerCase(), which mismatches
ISSUE.PRIORITY and formValues.priority (uppercase) and breaks selection and
onValueChange typing; update the SelectItem value to use the original priority
casing (value={priority}) or alternatively normalize values consistently by
converting incoming onValueChange to the enum casing inside the handler (update
the handler used by onValueChange in the create/edit dialog), ensuring
SelectItem, formValues.priority and onValueChange all use the same enum casing
(reference SelectItem, SelectContent, ISSUE.PRIORITY, formValues.priority, and
the onValueChange handler).

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