Skip to content

feat: add request icon page#37

Merged
Abhijit-Jha merged 7 commits intoitshover:masterfrom
krockxz:feat/request-icon-page
Jan 6, 2026
Merged

feat: add request icon page#37
Abhijit-Jha merged 7 commits intoitshover:masterfrom
krockxz:feat/request-icon-page

Conversation

@krockxz
Copy link
Copy Markdown
Contributor

@krockxz krockxz commented Jan 4, 2026

Added a new page at /request to allow users to request new icons via GitHub Issues. Also updated the navbar to include a link to this page.

Summary by CodeRabbit

  • New Features

    • Request icons from the app via a new modal — submit category, references, optional handle, and receive a confirmation link to the created issue.
    • Added a server-side action powering modal submissions.
  • UX

    • New "Request an Icon" button in footer and updated icon list to open the modal.
  • Refactor

    • Container now accepts an optional class name for external styling overrides.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Jan 4, 2026

@krockxz is attempting to deploy a commit to the itshover's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Adds an icon-request feature: a server action to create GitHub issues from form submissions, a client modal to collect requests and show results, footer and icons-list integration to open the modal, and a Container component update to accept an optional className via cn.

Changes

Cohort / File(s) Summary
Container enhancement
components/container.tsx
Added import { cn } from "@/lib/utils";, made className?: string prop optional, and composed classes with cn("mx-auto max-w-7xl", className).
Icon request server action
actions/create-issue.ts
New exported server action createIssue(formData: FormData) that validates/sanitizes inputs, requires GITHUB_ACCESS_TOKEN, posts a GitHub issue, and returns structured success/error payloads.
Client modal component
src/components/request-icon-modal.tsx
New client RequestIconModal component rendering a form, managing loading/success/error state, invoking createIssue, and showing a confirmation with the GitHub issue link.
Footer integration
components/footer.tsx
Imported and mounted RequestIconModal, added isRequestModalOpen state, and added a "Request an Icon" Quick Links button that opens the modal.
Icons list integration
components/icons-list.tsx
Replaced a link with a button that opens RequestIconModal; added isRequestModalOpen state and mounted the modal.
Minor markup cleanup
components/navbar.tsx
Removed a whitespace-only line in mobile navigation markup.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant Modal as RequestIconModal
    participant Server as createIssue()
    participant GitHub as GitHub API

    User->>Browser: Click "Request an Icon"
    Browser->>Modal: Render modal (user fills form)
    User->>Modal: Submit form
    Modal->>Modal: setLoading(true)
    Modal->>Server: call createIssue(formData)
    alt valid & token present
        Server->>GitHub: POST /repos/:owner/:repo/issues (title, body)
        GitHub-->>Server: 201 Created + issue data (html_url)
        Server-->>Modal: { success: true, url }
        Modal->>Modal: setSuccess(url), setLoading(false)
        Modal->>User: show confirmation + link
    else validation/env/api error
        Server-->>Modal: { error }
        Modal->>Modal: setError(msg), setLoading(false)
        Modal->>User: show error message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped a modal, nibbled a form so sweet,
Sent a tiny issue on digital feet,
Containers that flex with a class that sings,
Footers that open the most helpful things,
✨📬

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add request icon page' accurately reflects the main objective of this PR—introducing a request icon modal feature with integration into multiple components.
✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
constants.ts (1)

1-8: Update or remove the "DO NOT CHANGE THIS FILE" comment.

Line 1 contains a comment stating "DO NOT CHANGE THIS FILE", but this PR adds a new field to the LINKS object. Either the comment should be removed/updated, or this change violates an established policy.

🧹 Nitpick comments (1)
components/navbar.tsx (1)

123-123: Remove trailing spaces before closing tags.

Lines 123 and 194 have trailing spaces before the > character (</div > and </nav >), which is inconsistent with the rest of the codebase formatting.

🔎 Proposed fix
-      </div >
+      </div>
-    </nav >
+    </nav>

Also applies to: 194-194

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e21481 and 5690e7d.

📒 Files selected for processing (4)
  • app/request/page.tsx
  • components/container.tsx
  • components/navbar.tsx
  • constants.ts
🧰 Additional context used
🧬 Code graph analysis (2)
app/request/page.tsx (1)
constants.ts (1)
  • LINKS (2-9)
components/container.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (2)
components/container.tsx (1)

2-11: LGTM! Clean implementation of className prop support.

The addition of optional className prop with the cn utility follows React best practices for composable styling. The class merge order ("mx-auto max-w-7xl" before className) allows external overrides while maintaining sensible defaults.

components/navbar.tsx (1)

62-67: LGTM! Navigation items added consistently.

The new /request navigation links are implemented correctly in both desktop and mobile menus, following the existing patterns for styling and behavior.

Also applies to: 149-155

Comment thread app/request/page.tsx Outdated
Copy link
Copy Markdown

@tanmayagrwl tanmayagrwl left a comment

Choose a reason for hiding this comment

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

Small Changes to beautify the page further, good work overall!

Please address these comments

Comment thread app/request/page.tsx Outdated
Comment thread app/request/page.tsx Outdated
Comment thread app/request/page.tsx Outdated
Comment thread components/navbar.tsx Outdated
Comment thread constants.ts Outdated
@Abhijit-Jha
Copy link
Copy Markdown
Member

I think the request form should be kept very minimal and contain only two fields:

  1. Category of icons needed (for example, travel, database, etc.)
  2. Reference links (optional). This is where the requester can paste Figma links or links to existing icon libraries.
    For this flow, a separate page isn’t really needed. We can use a modal instead, and on submission, it should automatically create a GitHub issue so anyone can view it and contribute.

One more suggestion: we can remove the “Request” link from the navbar.
Let me know your thoughts on both of these.

@krockxz
Copy link
Copy Markdown
Contributor Author

krockxz commented Jan 5, 2026

@Abhijit-Jha I strongly agree with this
Also if we remove it from the Navbar, we need a new home for the Request button. Good options include:
The Footer: A subtle "Request an Icon" link
The Icons Gallery Header: A small button next to the search bar or filters
"No Results" State: If a user searches for an icon and finds nothing, show a "Request this icon" button immediately
I prefer the third one
Let me know what do you feel and I can work on the implementation

@Abhijit-Jha
Copy link
Copy Markdown
Member

Both options 1 and 3 LGTM.

Copy link
Copy Markdown
Contributor

@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: 8

Fix all issues with AI Agents 🤖
In @actions/create-issue.ts:
- Around line 5-9: Export the CreateIssueParams interface so other modules can
import it for type-safe calls to createIssue; locate the interface declaration
named CreateIssueParams in actions/create-issue.ts and add an export modifier
(export interface CreateIssueParams ...) so consumers can import the type when
calling createIssue.
- Around line 53-59: In the catch block of the createIssue function in
actions/create-issue.ts, remove the raw console.error("Error creating GitHub
issue:", error) that may leak sensitive data; instead log only non-sensitive,
sanitized information (for example error.message or a truncated stack) or write
the full error to a debug-only logger when NODE_ENV !== 'production' (e.g.,
process.env.NODE_ENV !== 'production' && logger.debug(sanitizedError)); keep the
returned payload generic (success: false, message: "...") and ensure any
token/credential fields are redacted before logging if you must log in
non-production.
- Line 45: The code in actions/create-issue.ts sets labels: ["icon request"]
which doesn't exist in the target repo; replace that label with one of the
existing labels (for example "enhancement" or "good first issue") or add logic
in the issue-creation flow to validate/create labels via the GitHub API before
applying them; update the labels array used in the issue creation call (labels)
or add a preflight that ensures "icon request" is created if you intend to keep
that name.
- Around line 25-37: Validate and sanitize the user inputs before interpolating
them into title/body: trim category, iconName, and references; reject category
if empty or only whitespace; enforce max lengths (category ≤ 50, iconName ≤ 100,
references ≤ 1000) and truncate or return a 400 error if exceeded; escape or
sanitize markdown/HTML-sensitive characters in category, iconName, and
references to prevent injection/formatting (e.g., escape backticks, angle
brackets, and markdown tokens) before building title and body variables so the
constructed title and body use only validated, escaped values.

In @components/request-icon-modal.tsx:
- Around line 36-45: Reset the form state when the modal closes so partial input
doesn't persist: update the modal close flow (where isOpen is toggled via
setIsOpen) to also reset formData, loading, success, and error back to their
initial values (use initialIconName for iconName) or add an effect that watches
isOpen and when it becomes false calls setFormData({...}), setLoading(false),
setSuccess(false), setError(""). Locate the state variables isOpen, setIsOpen,
formData, setFormData, initialIconName and ensure the reset runs on
close/unmount of the RequestIconModal.
- Around line 129-146: The Radix <Select> does not support the native required
attribute so remove the ineffective required prop and implement manual
validation: either add a hidden native input (e.g., <input type="hidden"
name="category" value={formData.category} required />) so the browser enforces
it, or validate in your form submit handler (check formData.category inside your
onSubmit function and prevent submission / set an error state if it is empty),
and show an inline error near the Select; update references to
formData.category, setFormData, and the submit handler accordingly.

In @components/ui/textarea.tsx:
- Around line 5-16: The Textarea component uses the experimental CSS token
"field-sizing-content" which lacks Firefox support; update the component's class
list in function Textarea to provide a safe fallback (e.g., remove or
conditionally replace "field-sizing-content" with a broadly supported sizing
utility or explicit min/max height styles) so non-Chrome browsers still render
correctly, and ensure the unique identifier data-slot="textarea" and prop
spreading {...props} remain unchanged.

In @package.json:
- Around line 21-23: The project uses octokit@^5.0.5 which depends on vulnerable
subpackages; update the octokit entry in package.json to a version that pulls in
patched sub-dependencies (ensure @octokit/endpoint >= 10.1.3,
@octokit/plugin-paginate-rest >= 11.4.1, @octokit/request-error >= 6.1.7 and
@octokit/request >= 9.2.1), then reinstall and rebuild (run npm/yarn install and
your build) and verify with npm audit or yarn audit that the CVEs are resolved;
if you cannot upgrade immediately, add validation/blocking of untrusted
Link/Authorization headers at your application boundary as a temporary
mitigation.
🧹 Nitpick comments (5)
actions/create-issue.ts (2)

39-59: Consider adding rate limiting to prevent abuse.

The server action has no rate limiting or abuse prevention mechanisms. A malicious user could spam the endpoint and create numerous GitHub issues, potentially exhausting API rate limits or cluttering the repository.

Consider implementing:

  • Rate limiting per IP address or user session
  • Captcha or similar verification for anonymous requests
  • Logging and monitoring of request patterns

48-52: Enhance success response with issue URL for better UX.

The success response could include the issue number and URL so users can track their request directly.

🔎 Proposed enhancement
         return {
             success: true,
             data: response.data,
-            message: "Issue created successfully!",
+            message: `Issue #${response.data.number} created successfully!`,
+            issueUrl: response.data.html_url,
         };
components/request-icon-modal.tsx (1)

47-76: Consider centralizing the categories list.

The hardcoded categories array might be reused elsewhere (e.g., for filtering icons, display logic, or validation). Centralizing it as a constant would improve maintainability.

🔎 Proposed refactor: Extract to shared constant

Create a new file src/constants/icon-categories.ts:

export const ICON_CATEGORIES = [
  "Arrow",
  "Brand",
  "Chart",
  "Communication",
  "Database",
  "Design",
  "Development",
  "Device",
  "Ecommerce",
  "Editor",
  "Finance",
  "Game",
  "Health",
  "Map",
  "Math",
  "Media",
  "Nature",
  "Office",
  "Security",
  "Shape",
  "Social",
  "System",
  "Text",
  "Time",
  "Travel",
  "User",
  "Weather",
  "Other",
] as const;

export type IconCategory = typeof ICON_CATEGORIES[number];

Then import and use:

+import { ICON_CATEGORIES } from "@/constants/icon-categories";

-const categories = [ ... ];
-
 ...
 <SelectContent>
-  {categories.map((cat) => (
+  {ICON_CATEGORIES.map((cat) => (
components/ui/dialog.tsx (1)

56-56: Remove redundant data-slot prop on DialogPortal.

Line 56 passes data-slot="dialog-portal" to the <DialogPortal> component, but DialogPortal (lines 19-23) already sets this attribute internally. This is redundant and the prop will be ignored since DialogPortal spreads {...props} after setting its own data-slot.

🔎 Proposed fix
-<DialogPortal data-slot="dialog-portal">
+<DialogPortal>
   <DialogOverlay />
components/icons-list.tsx (1)

132-142: Add type="button" to prevent unintended form submission.

Although the button isn't currently inside a form, explicitly setting type="button" is a best practice that prevents unexpected behavior if the component is later placed within a form context.

🔎 Proposed fix
 <button
+  type="button"
   className="text-primary hover:underline hover:cursor-pointer"
 >
   Request this icon
 </button>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 466d664 and a3e0bcf.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • actions/create-issue.ts
  • components/icons-list.tsx
  • components/navbar.tsx
  • components/request-icon-modal.tsx
  • components/ui/dialog.tsx
  • components/ui/input.tsx
  • components/ui/label.tsx
  • components/ui/select.tsx
  • components/ui/textarea.tsx
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/navbar.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
components/ui/textarea.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
components/icons-list.tsx (1)
components/request-icon-modal.tsx (1)
  • RequestIconModal (32-192)
components/ui/label.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
components/ui/input.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
components/ui/select.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
components/ui/dialog.tsx (1)
lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (6)
components/ui/label.tsx (1)

1-24: LGTM!

The Label component follows Radix UI best practices with proper client directive, ref forwarding, and accessibility features. The implementation is clean and consistent with the UI component patterns in this PR.

components/ui/input.tsx (1)

5-19: LGTM!

The Input component implementation is clean and comprehensive. It properly handles various states (focus, invalid, disabled, file) with well-organized Tailwind classes and follows the same pattern as other UI primitives in this PR.

components/request-icon-modal.tsx (1)

41-43: Icon name won't update if initialIconName prop changes.

The initialIconName prop is only used during the initial state setup. If the parent component changes this prop (e.g., user searches for a different icon), the form won't reflect the new value.

Based on the usage in components/icons-list.tsx, the modal is recreated on each render, so this might not be an issue in practice. However, if the modal instance is reused (e.g., lifted to a higher component), this could cause stale data.

Consider using a useEffect to sync with prop changes:

useEffect(() => {
  setFormData(prev => ({ ...prev, iconName: initialIconName }));
}, [initialIconName]);

Do you want the icon name to update dynamically, or is the current behavior (set once) intentional?

components/ui/dialog.tsx (1)

1-141: LGTM! Well-structured Dialog component.

The Dialog wrapper provides a clean, composable API around Radix UI primitives with consistent styling via cn and data-slot attributes. The showCloseButton prop offers useful control over the close button presence.

components/ui/select.tsx (1)

1-188: LGTM! Comprehensive Select component.

The Select wrapper provides a full-featured API around Radix UI primitives with size variants, scroll buttons, and consistent styling. The structure mirrors the Dialog component patterns, ensuring maintainability.

components/icons-list.tsx (1)

10-10: LGTM! Clean integration of request modal.

The no-results state now provides an inline path to request missing icons, passing the search query as initialIconName for a seamless UX. This aligns well with the PR objectives to use a modal instead of a separate page.

Also applies to: 134-141

Comment thread actions/create-issue.ts Outdated
Comment thread actions/create-issue.ts Outdated
Comment thread actions/create-issue.ts Outdated
Comment thread actions/create-issue.ts Outdated
Comment thread components/request-icon-modal.tsx Outdated
Comment on lines +36 to +45
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState("");

const [formData, setFormData] = useState({
category: "",
iconName: initialIconName,
references: "",
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Form state persists when modal is closed without submitting.

When a user opens the modal, partially fills the form, then closes it without submitting, the form data persists. Reopening the modal shows the previous incomplete state, which may confuse users.

🔎 Proposed fix: Reset form on modal close
 return (
-  <Dialog open={isOpen} onOpenChange={setIsOpen}>
+  <Dialog open={isOpen} onOpenChange={(open) => {
+    setIsOpen(open);
+    if (!open) {
+      // Reset form when closing
+      setFormData({ category: "", iconName: initialIconName, references: "" });
+      setError("");
+      setSuccess(false);
+    }
+  }}>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @components/request-icon-modal.tsx around lines 36-45, Reset the form state
when the modal closes so partial input doesn't persist: update the modal close
flow (where isOpen is toggled via setIsOpen) to also reset formData, loading,
success, and error back to their initial values (use initialIconName for
iconName) or add an effect that watches isOpen and when it becomes false calls
setFormData({...}), setLoading(false), setSuccess(false), setError(""). Locate
the state variables isOpen, setIsOpen, formData, setFormData, initialIconName
and ensure the reset runs on close/unmount of the RequestIconModal.

Comment thread components/request-icon-modal.tsx Outdated
Comment thread components/ui/textarea.tsx Outdated
Comment on lines +5 to +16
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props}
/>
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

CSS field-sizing-content property browser support

💡 Result:

Summary — current browser support (as of Jan 5, 2026):

  • Supported: Chromium-based browsers — Chrome (since 123), Edge (since 123), Opera (recent versions). (caniuse.com)
  • Supported in Safari / iOS Safari in recent WebKit builds (see WebKit/Can I Use for exact Safari milestone). (caniuse.com)
  • Not supported: Firefox (no support as of now). (caniuse.com)
  • Notes: The property (field-sizing: content) is experimental; check MDN and Can I Use before using in production and provide fallbacks. (developer.mozilla.org)

If you want, I can list exact minimum browser versions or provide CSS fallbacks/snippets.

Citations:


Verify browser support for field-sizing-content — no Firefox support.

The field-sizing-content CSS property is experimental and has incomplete browser support. It's supported in Chrome (v123+), Edge (v123+), Opera, and recent Safari, but not supported in Firefox. Verify this property aligns with your target browsers, or consider providing a fallback for wider compatibility.

🤖 Prompt for AI Agents
In @components/ui/textarea.tsx around lines 5-16, The Textarea component uses
the experimental CSS token "field-sizing-content" which lacks Firefox support;
update the component's class list in function Textarea to provide a safe
fallback (e.g., remove or conditionally replace "field-sizing-content" with a
broadly supported sizing utility or explicit min/max height styles) so
non-Chrome browsers still render correctly, and ensure the unique identifier
data-slot="textarea" and prop spreading {...props} remain unchanged.

Comment thread package.json Outdated
@krockxz
Copy link
Copy Markdown
Contributor Author

krockxz commented Jan 5, 2026

@Abhijit-Jha I want to refactor the Request page into a simple Modal (Category + Link) that auto-creates a GitHub issue
Since this uses a server API (Bot) to create the issue, we lose the user's identity.
My suggestion: We add an optional GitHub Handle field to the form so we can still credit them without forcing a full OAuth login
Does that sound like a good middle ground?

@krockxz krockxz force-pushed the feat/request-icon-page branch from a3e0bcf to 466d664 Compare January 5, 2026 10:15
Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (3)
actions/create-issue.ts (2)

4-6: Add input validation and sanitization to prevent abuse.

The form inputs are extracted but not validated for length or content. This could allow:

  • Extremely long submissions that create oversized GitHub issues
  • Markdown injection in category, links, or handle fields
  • Empty or whitespace-only categories (line 8-10 only checks falsy, not trimmed content)
🔎 Recommended validation logic
 export async function createIssue(formData: FormData) {
-    const category = formData.get("category") as string;
-    const links = formData.get("links") as string;
-    const handle = formData.get("handle") as string;
+    const category = (formData.get("category") as string)?.trim();
+    const links = (formData.get("links") as string)?.trim();
+    const handle = (formData.get("handle") as string)?.trim();
 
     if (!category) {
         return { error: "Category is required" };
     }
+
+    // Enforce length limits
+    if (category.length > 50) {
+        return { error: "Category must be 50 characters or less" };
+    }
+    
+    if (links && links.length > 1000) {
+        return { error: "Reference links must be 1000 characters or less" };
+    }
+    
+    if (handle && handle.length > 39) {
+        return { error: "GitHub handle must be 39 characters or less" };
+    }

14-14: Avoid logging sensitive details in production.

The console.error statements may expose sensitive information (tokens, internal error details) in production logs or monitoring systems.

🔎 Proposed fix
     const token = process.env.GITHUB_ACCESS_TOKEN;
     if (!token) {
-        console.error("Missing GITHUB_ACCESS_TOKEN");
+        // Log without exposing config details in production
+        if (process.env.NODE_ENV !== 'production') {
+            console.error("Missing GITHUB_ACCESS_TOKEN");
+        }
         return { error: "Server configuration error" };
     }
         if (!response.ok) {
             const errorData = await response.json();
-            console.error("GitHub API Error:", errorData);
+            console.error("GitHub API Error:", response.status);
             return { error: "Failed to create issue on GitHub" };
         }
     } catch (err) {
-        console.error("Server Action Error:", err);
+        console.error("Server Action Error:", err instanceof Error ? err.message : "Unknown error");
         return { error: "Internal server error" };
     }

Also applies to: 40-40, 47-47

components/request-icon-modal.tsx (1)

21-27: Form fields are not reset when modal closes.

The effect resets success/error/loading state when the modal opens, but the form fields themselves (category, links, handle) retain their values when the modal is closed and reopened. This may confuse users who partially fill the form, close it, then reopen it to see their previous input still there.

🔎 Add form reset on close
     // Reset state when modal opens
     useEffect(() => {
         if (isOpen) {
             setSuccess(null);
             setError(null);
             setLoading(false);
+        } else {
+            // Reset form fields when modal closes
+            const form = document.querySelector('form');
+            form?.reset();
         }
     }, [isOpen]);

Alternatively, use a ref to the form and reset it in the effect:

+    const formRef = useRef<HTMLFormElement>(null);
+
     // Reset state when modal opens
     useEffect(() => {
         if (isOpen) {
             setSuccess(null);
             setError(null);
             setLoading(false);
+        } else {
+            formRef.current?.reset();
         }
     }, [isOpen]);

Then add the ref to the form:

-    <form onSubmit={handleSubmit} className="space-y-4">
+    <form ref={formRef} onSubmit={handleSubmit} className="space-y-4">
🧹 Nitpick comments (3)
actions/create-issue.ts (1)

18-25: Consider escaping user input to prevent markdown formatting issues.

While GitHub's issue body is markdown-safe in most contexts, user-provided content like handle could contain markdown syntax that breaks formatting or creates misleading issues (e.g., **Bold** or [Link](url)).

Consider sanitizing or escaping markdown special characters in user inputs, or wrapping user content in code blocks:

🔎 Alternative approach using code blocks
     const title = `Request: [${category}] Icon`;
     const body = `
 **Category:** ${category}
 **Reference Links:**
-${links || "None provided"}
+\`\`\`
+${links || "None provided"}
+\`\`\`
 
-**Requested by:** ${handle || "Anonymous"}
+**Requested by:** \`${handle || "Anonymous"}\`
   `.trim();
components/request-icon-modal.tsx (2)

148-162: Add validation hint for GitHub handle format.

The GitHub handle input is optional but could benefit from a validation hint or pattern to guide users. GitHub usernames have specific rules (alphanumeric, hyphens, max 39 chars, cannot start/end with hyphen).

🔎 Add client-side pattern validation
                                                <input
                                                    id="handle"
                                                    name="handle"
                                                    type="text"
                                                    placeholder="username"
+                                                    pattern="[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?"
+                                                    title="GitHub username (alphanumeric and hyphens, 1-39 characters)"
+                                                    maxLength={39}
                                                    className="flex h-10 w-full rounded-xl border bg-background pl-8 pr-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
                                                />

56-62: Consider preventing backdrop click during form submission.

The backdrop's onClick={onClose} (line 60) allows users to close the modal while a submission is in progress (when loading is true). This could lead to confusion if the request completes after the modal closes.

🔎 Prevent close during submission
                    <motion.div
                        initial={{ opacity: 0 }}
                        animate={{ opacity: 1 }}
                        exit={{ opacity: 0 }}
-                        onClick={onClose}
+                        onClick={() => !loading && onClose()}
                        className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm"
                    />

Also disable the close button during submission:

                                    <button
                                        onClick={onClose}
+                                        disabled={loading}
-                                        className="text-muted-foreground hover:text-foreground transition-colors p-1"
+                                        className="text-muted-foreground hover:text-foreground transition-colors p-1 disabled:opacity-50 disabled:cursor-not-allowed"
                                    >
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3e0bcf and 9359ac6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • actions/create-issue.ts
  • components/footer.tsx
  • components/navbar.tsx
  • components/request-icon-modal.tsx
  • package.json
💤 Files with no reviewable changes (1)
  • components/navbar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (2)
components/footer.tsx (1)
components/request-icon-modal.tsx (1)
  • RequestIconModal (15-198)
components/request-icon-modal.tsx (1)
actions/create-issue.ts (1)
  • createIssue (3-50)
🔇 Additional comments (4)
components/footer.tsx (1)

27-27: LGTM! Clean integration of the request modal.

The modal integration is well-implemented:

  • State management is straightforward and appropriate
  • The trigger button is properly placed in Quick Links
  • Modal rendering with proper props for controlled visibility
  • Consistent styling with existing footer links

Also applies to: 81-81, 114-119, 215-215

components/request-icon-modal.tsx (2)

120-133: The native <select> required attribute should work correctly.

Unlike the past review comment which mentioned Radix UI's Select component, this implementation uses a native HTML <select> element. The required attribute on line 123 will properly enforce validation through the browser's native form validation.

Note: The past review comment about Radix Select validation is not applicable to the current code, which uses a native <select> element.


96-113: Success state UI is well-implemented.

The success view provides clear feedback to users with:

  • Visual confirmation (checkmark icon in colored circle)
  • Clear messaging
  • Direct link to view the created GitHub issue
  • Close button to dismiss
actions/create-issue.ts (1)

28-36: The GitHub API endpoint and repository configuration are correct.

The endpoint /repos/itshover/itshover/issues is valid—the repository exists as a public repository with issues enabled. The Bearer token authentication method is appropriate for the GitHub REST API. Ensure the personal access token used at runtime has at least the public_repo scope (or the broader repo scope for both public and private repositories).

Copy link
Copy Markdown
Contributor

@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

Fix all issues with AI Agents 🤖
In @components/request-icon-modal.tsx:
- Around line 54-61: The backdrop's motion.div and the modal close button
currently call onClose unconditionally; modify both to guard against the loading
state (e.g., if (loading) return) before invoking onClose, and also reflect this
in the UI by disabling the close button and preventing backdrop clicks while
loading (add disabled attribute / aria-disabled on the close button and
conditional class to ignore pointer events on the backdrop) so the modal cannot
be closed during form submission; update the handlers referenced (the motion.div
onClick and the close button's onClick) to check loading and only call onClose
when loading is false.
♻️ Duplicate comments (1)
components/request-icon-modal.tsx (1)

20-26: Form fields still persist when modal is reopened.

The useEffect resets loading, success, and error state, but does not reset the actual form fields (category, links, handle). Because these are uncontrolled inputs, their DOM values persist when the modal is closed and reopened, showing stale data to users.

This issue was previously flagged but remains unresolved.

🔎 Proposed fix: Add form ref and reset on open
-import { useState, useRef, useEffect } from "react";
+import { useState, useEffect, useRef } from "react";

 export default function RequestIconModal({ isOpen, onClose }: RequestIconModalProps) {
+    const formRef = useRef<HTMLFormElement>(null);
     const [loading, setLoading] = useState(false);
     const [success, setSuccess] = useState<string | null>(null);
     const [error, setError] = useState<string | null>(null);

     useEffect(() => {
         if (isOpen) {
             setSuccess(null);
             setError(null);
             setLoading(false);
+            formRef.current?.reset();
         }
     }, [isOpen]);

Then add the ref to the form element:

-                                    <form onSubmit={handleSubmit} className="space-y-4">
+                                    <form ref={formRef} onSubmit={handleSubmit} className="space-y-4">
🧹 Nitpick comments (2)
components/request-icon-modal.tsx (2)

3-3: Remove unused import.

useRef is imported but never used in this component.

🔎 Proposed fix
-import { useState, useRef, useEffect } from "react";
+import { useState, useEffect } from "react";

28-48: Consider manual category validation before submission.

While the native required attribute on the <select> should enforce category selection, adding an explicit check in handleSubmit provides a clearer user experience and guards against edge cases where browser validation might not trigger.

🔎 Proposed enhancement
 const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
+    
+    const formData = new FormData(e.currentTarget);
+    if (!formData.get("category")) {
+        setError("Please select a category.");
+        return;
+    }
+    
     setLoading(true);
     setError(null);

-    const formData = new FormData(e.currentTarget);
-
     try {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9359ac6 and d02ea4f.

📒 Files selected for processing (1)
  • components/request-icon-modal.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/request-icon-modal.tsx (1)
actions/create-issue.ts (1)
  • createIssue (3-50)
🔇 Additional comments (1)
components/request-icon-modal.tsx (1)

117-131: Native select validation is correctly implemented.

The switch from a custom Radix Select to a native HTML <select> element with the required attribute resolves the previous concern about validation not working. The combination of required, defaultValue="", and a disabled placeholder option is a standard pattern that ensures category selection before submission.

The past review comment regarding Radix Select validation is now obsolete.

Comment thread components/request-icon-modal.tsx Outdated
Comment thread components/request-icon-modal.tsx Outdated
Copy link
Copy Markdown
Contributor

@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

Fix all issues with AI Agents 🤖
In @actions/create-issue.ts:
- Around line 17-24: Sanitize or escape user inputs before interpolating into
the issue title/body: ensure the variables category, links, and handle used to
build title and body are cleaned (escape markdown-sensitive characters like [,
], `, <, > and strip or replace newlines) or wrap each value in a fenced code
block to prevent markdown injection; update the code that computes title and
body (the constants title and body) to use a helper sanitize/escape function
(e.g., escapeMarkdown(input) or wrapInCodeBlock(input)) and apply it to
category, links, and handle so injected formatting cannot break the template or
introduce unintended markdown.
♻️ Duplicate comments (1)
actions/create-issue.ts (1)

3-10: Add comprehensive input validation to prevent malicious input.

The current validation only checks if category is truthy, which allows whitespace-only values (e.g., " ") and does not enforce any length limits. This was flagged in previous reviews but remains unaddressed. Malicious users could submit extremely long strings or craft markdown injection attacks.

Implement validation to:

  • Trim and reject empty/whitespace-only category values
  • Enforce maximum lengths (e.g., category ≤ 50 chars, links ≤ 1000 chars, handle ≤ 50 chars)
  • Optionally sanitize markdown-sensitive characters to prevent formatting manipulation
🔎 Proposed validation
 export async function createIssue(formData: FormData) {
     const category = formData.get("category") as string;
     const links = formData.get("links") as string;
     const handle = formData.get("handle") as string;
 
-    if (!category) {
+    const trimmedCategory = category?.trim();
+    if (!trimmedCategory || trimmedCategory.length === 0) {
         return { error: "Category is required" };
     }
+
+    if (trimmedCategory.length > 50) {
+        return { error: "Category must be 50 characters or less" };
+    }
+
+    if (links && links.length > 1000) {
+        return { error: "Reference links must be 1000 characters or less" };
+    }
+
+    if (handle && handle.length > 50) {
+        return { error: "Handle must be 50 characters or less" };
+    }

Then use trimmedCategory and trimmed versions of other fields in the issue body.

🧹 Nitpick comments (1)
actions/create-issue.ts (1)

37-42: Validate the GitHub API response structure.

The code assumes issue.html_url exists without validation. If the GitHub API response format changes or is unexpected, this could result in returning undefined in the success response.

Add a check to ensure the expected field is present:

🔎 Proposed validation
     const issue = await response.json();
+    
+    if (!issue?.html_url) {
+        return { error: "Invalid response from GitHub API" };
+    }
+    
     return { success: true, url: issue.html_url };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d02ea4f and e329e22.

📒 Files selected for processing (1)
  • actions/create-issue.ts
🔇 Additional comments (3)
actions/create-issue.ts (3)

12-15: LGTM: Proper environment variable validation.

The token validation correctly checks for the required GITHUB_ACCESS_TOKEN environment variable and returns a generic error message without exposing sensitive configuration details.


26-35: LGTM: Correct GitHub API usage.

The API call is properly structured with correct headers (Authorization, Content-Type, API version) and uses the appropriate endpoint. The removal of the problematic label assignment from earlier versions is a good improvement.


43-45: LGTM: Secure error handling.

The generic error message and absence of detailed logging prevent exposure of sensitive information (tokens, stack traces) in production. This is a good improvement over earlier versions.

Comment thread actions/create-issue.ts Outdated
@krockxz krockxz force-pushed the feat/request-icon-page branch from e329e22 to 842701a Compare January 5, 2026 10:52
Copy link
Copy Markdown
Contributor

@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

Fix all issues with AI Agents 🤖
In @components/request-icon-modal.tsx:
- Around line 75-94: The close button lacks an accessible name for screen
readers; update the button element that uses onClick={() => !loading &&
onClose()} and disabled={loading} to include an accessible label (e.g., add
aria-label="Close" or aria-label="Close dialog") and optionally a title
attribute so the SVG-only button is announced correctly by assistive
technologies.
♻️ Duplicate comments (3)
components/request-icon-modal.tsx (3)

50-199: Add Escape key handler for keyboard accessibility.

Users expect pressing Escape to close modals, but this behavior is not implemented. This affects keyboard navigation and is a common accessibility requirement.

Based on learnings, this issue was previously flagged and remains unresolved.

🔎 Recommended fix
     useEffect(() => {
         if (isOpen) {
             setSuccess(null);
             setError(null);
             setLoading(false);
         }
     }, [isOpen]);

+    useEffect(() => {
+        const handleEscape = (e: KeyboardEvent) => {
+            if (e.key === "Escape" && isOpen && !loading) {
+                onClose();
+            }
+        };
+        
+        if (isOpen) {
+            window.addEventListener("keydown", handleEscape);
+        }
+        
+        return () => window.removeEventListener("keydown", handleEscape);
+    }, [isOpen, loading, onClose]);
+
     const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {

66-70: Add ARIA attributes for screen reader accessibility.

The modal container is missing essential ARIA attributes that help assistive technologies understand the dialog structure.

🔎 Proposed fix
                         <motion.div
                             initial={{ scale: 0.95, opacity: 0, y: 20 }}
                             animate={{ scale: 1, opacity: 1, y: 0 }}
                             exit={{ scale: 0.95, opacity: 0, y: 20 }}
+                            role="dialog"
+                            aria-modal="true"
+                            aria-labelledby="modal-title"
                             className="bg-background w-full max-w-md rounded-2xl border shadow-2xl pointer-events-auto overflow-hidden"
                         >

And update the heading to reference the ID:

-                                    <h2 className="text-xl font-semibold">Request an Icon</h2>
+                                    <h2 id="modal-title" className="text-xl font-semibold">Request an Icon</h2>

20-26: Form inputs persist across modal open/close cycles.

The useEffect resets state variables (success, error, loading) when the modal opens, but the actual form inputs remain uncontrolled and retain their values. If a user partially fills the form, closes the modal, and reopens it, the old input values will still be present.

Based on learnings, this issue was previously flagged and remains unresolved.

🔎 Recommended fix: Add form ref and reset
+import { useState, useEffect, useRef } from "react";
+
 export default function RequestIconModal({ isOpen, onClose }: RequestIconModalProps) {
     const [loading, setLoading] = useState(false);
     const [success, setSuccess] = useState<string | null>(null);
     const [error, setError] = useState<string | null>(null);
+    const formRef = useRef<HTMLFormElement>(null);

     useEffect(() => {
         if (isOpen) {
             setSuccess(null);
             setError(null);
             setLoading(false);
+            formRef.current?.reset();
         }
     }, [isOpen]);

Then add the ref to the form element:

-                                    <form onSubmit={handleSubmit} className="space-y-4">
+                                    <form ref={formRef} onSubmit={handleSubmit} className="space-y-4">
🧹 Nitpick comments (1)
components/request-icon-modal.tsx (1)

3-3: Remove unused import.

useRef is imported but never used in this component.

🔎 Proposed fix
-import { useState, useRef, useEffect } from "react";
+import { useState, useEffect } from "react";
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e329e22 and 842701a.

📒 Files selected for processing (4)
  • actions/create-issue.ts
  • components/footer.tsx
  • components/navbar.tsx
  • components/request-icon-modal.tsx
💤 Files with no reviewable changes (1)
  • components/navbar.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • actions/create-issue.ts
  • components/footer.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/request-icon-modal.tsx (2)
actions/create-issue.ts (1)
  • createIssue (3-46)
lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (4)
components/request-icon-modal.tsx (4)

28-48: LGTM!

The submit handler properly prevents default submission, manages loading state, handles both success and error cases from the server action, and includes appropriate error handling with try-catch.


97-114: LGTM!

The success state provides clear feedback with a well-designed confirmation screen and a secure external link to the created GitHub issue.


116-164: LGTM!

The form uses native HTML elements with proper labeling and validation. The category field correctly uses the required attribute on a native <select>, and the GitHub handle input has a nice UX touch with the @ prefix.


166-190: LGTM!

The error display provides clear visual feedback, and the submit button properly manages its state with a loading spinner and disabled attribute during submission.

Comment thread components/request-icon-modal.tsx Outdated
Comment on lines +75 to +94
<button
onClick={() => !loading && onClose()}
disabled={loading}
className="text-muted-foreground hover:text-foreground transition-colors p-1 disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
Copy link
Copy Markdown
Contributor

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 close button for screen reader users.

The close button contains only an SVG icon without visible text, so screen reader users won't know its purpose.

🔎 Proposed fix
                                     <button
+                                        aria-label="Close modal"
                                         onClick={() => !loading && onClose()}
                                         disabled={loading}
                                         className="text-muted-foreground hover:text-foreground transition-colors p-1 disabled:opacity-50 disabled:cursor-not-allowed"
                                     >
🤖 Prompt for AI Agents
In @components/request-icon-modal.tsx around lines 75-94, The close button lacks
an accessible name for screen readers; update the button element that uses
onClick={() => !loading && onClose()} and disabled={loading} to include an
accessible label (e.g., add aria-label="Close" or aria-label="Close dialog") and
optionally a title attribute so the SVG-only button is announced correctly by
assistive technologies.

Comment thread components/request-icon-modal.tsx Outdated
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here we can use inhouse X (cross) icon

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, wherever an SVG is used, please try to replace it with our in-house icons.
If an in-house icon isn’t available, use the closest matching icon for now and add a // TODO: comment so we can add the proper icon later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, still working on the alignment for the modal
will update when done

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@Abhijit-Jha updated, take a look

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm, can you post ss or videos of the UI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Screencast.from.2026-01-05.21-27-54.mp4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice, can you add it here as well? Also, please fix the formatting by running npm run format and verify everything using npm run check and npm run build.
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated
Also fixed the formatting

Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (1)
components/request-icon-modal.tsx (1)

72-89: Add accessibility attributes to the modal and close button.

The modal is missing key accessibility features flagged in previous reviews:

  1. The close button (line 82-88) needs aria-label="Close modal" for screen reader users
  2. The modal container should have role="dialog", aria-modal="true", and aria-labelledby referencing the heading
  3. Consider adding an Escape key handler to dismiss the modal
🔎 Proposed accessibility improvements
             <motion.div
               initial={{ scale: 0.95, opacity: 0, y: 20 }}
               animate={{ scale: 1, opacity: 1, y: 0 }}
               exit={{ scale: 0.95, opacity: 0, y: 20 }}
+              role="dialog"
+              aria-modal="true"
+              aria-labelledby="request-modal-title"
               className="bg-background pointer-events-auto max-h-[85vh] w-full max-w-md overflow-y-auto rounded-2xl border shadow-2xl"
             >
               <div className="p-5">
                 <div className="mb-4 flex items-center justify-between">
-                  <h2 className="text-xl font-semibold">Request an Icon</h2>
+                  <h2 id="request-modal-title" className="text-xl font-semibold">Request an Icon</h2>
                   <button
+                    aria-label="Close modal"
                     onClick={() => !loading && onClose()}
                     disabled={loading}
🧹 Nitpick comments (1)
components/request-icon-modal.tsx (1)

192-211: Consider extracting the loading spinner to an in-house icon.

Per the project convention discussed in previous comments, inline SVGs should be replaced with in-house icons where possible. If a spinner icon isn't available yet, consider adding a // TODO: comment.

🔎 Suggested change
                         {loading ? (
                           <div className="flex items-center gap-2">
+                            {/* TODO: Replace with in-house spinner icon when available */}
                             <svg
                               className="mr-3 -ml-1 h-4 w-4 animate-spin text-white"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d83b1ee and 3ea92ed.

📒 Files selected for processing (3)
  • actions/create-issue.ts
  • components/icons-list.tsx
  • components/request-icon-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • actions/create-issue.ts
🧰 Additional context used
🧬 Code graph analysis (1)
components/request-icon-modal.tsx (2)
actions/create-issue.ts (1)
  • createIssue (3-61)
lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (9)
components/icons-list.tsx (3)

9-9: LGTM!

The import and state setup for controlling the modal visibility are clean and follow React conventions.

Also applies to: 14-14


134-139: LGTM!

The button correctly triggers the modal and the inline styling matches the surrounding text context. The implementation aligns with the PR discussion to show "Request this icon" in the "No Results" state.


145-148: LGTM!

The modal integration follows the controlled component pattern correctly, and the placement at the end of the component tree is appropriate.

components/request-icon-modal.tsx (6)

1-17: LGTM!

Clean imports using in-house icons as requested in the previous review discussion. The interface is properly typed.


19-33: LGTM!

The state management is clean. Since the modal content unmounts when closed (rendered conditionally on line 59), form field values naturally reset. The useEffect appropriately clears the submission state (success, error, loading) when reopening.


35-55: LGTM!

The submit handler properly manages loading state, handles both success and error responses from the server action, and includes a catch block for unexpected failures. The finally block ensures loading state is always reset.


91-113: LGTM!

The success state provides clear feedback with a link to the created GitHub issue. The external link correctly includes rel="noopener noreferrer" for security.


115-142: LGTM!

Good switch to native <select> which addresses the previous Radix Select validation concern. The noValidate attribute disables browser validation UI, but server-side validation in createIssue handles missing category, so this works correctly.


144-175: LGTM!

The textarea and GitHub handle input are well-structured. The @ prefix decoration on the handle field is a nice UX touch that clarifies the expected format.

Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (1)
actions/create-issue.ts (1)

8-10: Add whitespace trimming to prevent empty category submission.

The validation only checks if category is truthy, but doesn't trim whitespace. A user could submit a category consisting entirely of spaces (e.g., " ") which would pass validation and create a malformed issue title.

🔎 Proposed fix
 export async function createIssue(formData: FormData) {
   const category = formData.get("category") as string;
   const links = formData.get("links") as string;
   const handle = formData.get("handle") as string;
 
-  if (!category) {
+  if (!category || !category.trim()) {
     return { error: "Category is required" };
   }
🧹 Nitpick comments (2)
actions/create-issue.ts (2)

3-10: Consider adding length validation for better UX.

While GitHub likely enforces its own limits, explicit length validation would provide better user feedback. Past reviews suggested limits such as category ≤ 50 characters and links/references ≤ 1000 characters.

🔎 Proposed validation
 export async function createIssue(formData: FormData) {
   const category = formData.get("category") as string;
   const links = formData.get("links") as string;
   const handle = formData.get("handle") as string;
 
   if (!category || !category.trim()) {
     return { error: "Category is required" };
   }
+
+  if (category.length > 50) {
+    return { error: "Category must be 50 characters or less" };
+  }
+
+  if (links && links.length > 1000) {
+    return { error: "Reference links must be 1000 characters or less" };
+  }
+
+  if (handle && handle.length > 50) {
+    return { error: "GitHub handle must be 50 characters or less" };
+  }

3-3: Consider adding an explicit return type.

Adding an explicit return type would improve type safety and make the function's contract clearer to consumers.

🔎 Proposed type annotation
+type CreateIssueResult =
+  | { success: true; url: string }
+  | { error: string };
+
-export async function createIssue(formData: FormData) {
+export async function createIssue(formData: FormData): Promise<CreateIssueResult> {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ea92ed and 738aa10.

📒 Files selected for processing (2)
  • actions/create-issue.ts
  • components/footer.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/footer.tsx (1)
components/request-icon-modal.tsx (1)
  • RequestIconModal (19-231)
🔇 Additional comments (6)
components/footer.tsx (4)

27-27: LGTM!

The import is correctly used to render the modal component at line 215.


81-81: LGTM!

Modal state management is properly implemented using React hooks.


114-119: LGTM!

The button correctly triggers the modal and maintains proper accessibility with text-left alignment. The implementation aligns with the PR objectives where footer placement was confirmed as acceptable.


215-218: LGTM!

The modal is properly integrated with correct props and callback handling.

actions/create-issue.ts (2)

17-26: LGTM!

The sanitization function appropriately escapes markdown-sensitive characters and strips newlines from single-line fields to prevent format injection. The implementation addresses the markdown injection concerns from past reviews.


41-63: LGTM!

The GitHub API integration properly handles authentication, error cases, and returns appropriate success/error responses. The generic error messages avoid leaking sensitive details.

@Abhijit-Jha
Copy link
Copy Markdown
Member

Cool, thanks for this @krockxz

@Abhijit-Jha Abhijit-Jha merged commit cb3e641 into itshover:master Jan 6, 2026
2 of 3 checks passed
@Abhijit-Jha
Copy link
Copy Markdown
Member

Hey @krockxz, I created a fine-grained personal access token, but it’s not working for me. Which token did you use?

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.

3 participants