feat: add request icon page#37
Conversation
|
@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. |
📝 WalkthroughWalkthroughAdds 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 Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
app/request/page.tsxcomponents/container.tsxcomponents/navbar.tsxconstants.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
classNameprop with thecnutility follows React best practices for composable styling. The class merge order ("mx-auto max-w-7xl"beforeclassName) allows external overrides while maintaining sensible defaults.components/navbar.tsx (1)
62-67: LGTM! Navigation items added consistently.The new
/requestnavigation links are implemented correctly in both desktop and mobile menus, following the existing patterns for styling and behavior.Also applies to: 149-155
tanmayagrwl
left a comment
There was a problem hiding this comment.
Small Changes to beautify the page further, good work overall!
Please address these comments
|
I think the request form should be kept very minimal and contain only two fields:
One more suggestion: we can remove the “Request” link from the navbar. |
|
@Abhijit-Jha I strongly agree with this |
|
Both options 1 and 3 LGTM. |
There was a problem hiding this comment.
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 redundantdata-slotprop on DialogPortal.Line 56 passes
data-slot="dialog-portal"to the<DialogPortal>component, butDialogPortal(lines 19-23) already sets this attribute internally. This is redundant and the prop will be ignored sinceDialogPortalspreads{...props}after setting its owndata-slot.🔎 Proposed fix
-<DialogPortal data-slot="dialog-portal"> +<DialogPortal> <DialogOverlay />components/icons-list.tsx (1)
132-142: Addtype="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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (10)
actions/create-issue.tscomponents/icons-list.tsxcomponents/navbar.tsxcomponents/request-icon-modal.tsxcomponents/ui/dialog.tsxcomponents/ui/input.tsxcomponents/ui/label.tsxcomponents/ui/select.tsxcomponents/ui/textarea.tsxpackage.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 ifinitialIconNameprop changes.The
initialIconNameprop 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
useEffectto 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
cnanddata-slotattributes. TheshowCloseButtonprop 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
initialIconNamefor a seamless UX. This aligns well with the PR objectives to use a modal instead of a separate page.Also applies to: 134-141
| 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: "", | ||
| }); |
There was a problem hiding this comment.
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.
| 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} | ||
| /> | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧩 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:
- 1: https://caniuse.com/mdn-css_properties_field-sizing_content?utm_source=openai
- 2: https://caniuse.com/mdn-css_properties_field-sizing_content?utm_source=openai
- 3: https://caniuse.com/mdn-css_properties_field-sizing_content?utm_source=openai
- 4: https://developer.mozilla.org/en-US/docs/Web/CSS/field-sizing?utm_source=openai
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.
|
@Abhijit-Jha I want to refactor the Request page into a simple Modal (Category + Link) that auto-creates a GitHub issue |
a3e0bcf to
466d664
Compare
There was a problem hiding this comment.
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.errorstatements 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
handlecould 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 (whenloadingis 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
actions/create-issue.tscomponents/footer.tsxcomponents/navbar.tsxcomponents/request-icon-modal.tsxpackage.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. Therequiredattribute 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/issuesis 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 thepublic_reposcope (or the broaderreposcope for both public and private repositories).
There was a problem hiding this comment.
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
useEffectresetsloading,success, anderrorstate, 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.
useRefis 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
requiredattribute on the<select>should enforce category selection, adding an explicit check inhandleSubmitprovides 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
📒 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 therequiredattribute resolves the previous concern about validation not working. The combination ofrequired,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.
There was a problem hiding this comment.
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
categoryis 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
categoryvalues- 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
trimmedCategoryand 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_urlexists without validation. If the GitHub API response format changes or is unexpected, this could result in returningundefinedin 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
📒 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_TOKENenvironment 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.
e329e22 to
842701a
Compare
There was a problem hiding this comment.
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
Escapeto 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
useEffectresets 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.
useRefis 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
📒 Files selected for processing (4)
actions/create-issue.tscomponents/footer.tsxcomponents/navbar.tsxcomponents/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
requiredattribute 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.
| <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> |
There was a problem hiding this comment.
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.
| height="24" | ||
| viewBox="0 0 24 24" | ||
| fill="none" | ||
| stroke="currentColor" |
There was a problem hiding this comment.
Here we can use inhouse X (cross) icon
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Sure, still working on the alignment for the modal
will update when done
There was a problem hiding this comment.
lgtm, can you post ss or videos of the UI?
There was a problem hiding this comment.
Screencast.from.2026-01-05.21-27-54.mp4
There was a problem hiding this comment.
Updated
Also fixed the formatting
There was a problem hiding this comment.
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:
- The close button (line 82-88) needs
aria-label="Close modal"for screen reader users- The modal container should have
role="dialog",aria-modal="true", andaria-labelledbyreferencing the heading- 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
📒 Files selected for processing (3)
actions/create-issue.tscomponents/icons-list.tsxcomponents/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
useEffectappropriately 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
finallyblock 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. ThenoValidateattribute disables browser validation UI, but server-side validation increateIssuehandles 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.
There was a problem hiding this comment.
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
categoryis 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
📒 Files selected for processing (2)
actions/create-issue.tscomponents/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.
|
Cool, thanks for this @krockxz |
|
Hey @krockxz, I created a fine-grained personal access token, but it’s not working for me. Which token did you use? |

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
UX
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.