-
Notifications
You must be signed in to change notification settings - Fork 2
dev #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
dev #32
Conversation
hanaeatsplanes
commented
Jan 25, 2026
- fix TS71007
- uh
- grrr
- ship ship ship
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a "ship project" feature that allows users to mark projects as shipped when they have at least one devlog. It also includes several bug fixes and improvements including favicon path corrections, prop renaming for consistency, and a new shop redirect page.
Changes:
- Added ship project functionality with UI button and API integration
- Fixed favicon paths to include leading slashes
- Renamed
onCanceltoonCancelActionin EditProject component - Created new shop redirect page with loading and error states
- Modified devlogs API to convert empty descriptions to null
- Updated hackatime projects date filter
- Added Content-Type header to ship API route
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/dashboard/ProjectDetailsClient.tsx | Added ship project button with state management, error handling, and devlogCount prop |
| src/components/dashboard/EditProject.tsx | Renamed onCancel prop to onCancelAction and added React import for type annotations |
| src/components/Meta.tsx | Fixed favicon paths by adding leading slashes |
| src/app/dashboard/shop/page.tsx | New shop redirect page with Suspense boundary and error handling |
| src/app/dashboard/projects/[projectId]/page.tsx | Passed devlogCount to ProjectDetailsClient component |
| src/app/api/shop/form-url/route.ts | New API route for fetching shop form URL from environment variables |
| src/app/api/projects/[projectId]/ship/route.ts | Reordered imports and added Content-Type header; renamed unused parameter |
| src/app/api/hackatime/projects/route.ts | Updated start_date filter from 2025-12-01 to 2025-12-21 |
| src/app/api/dashboard/devlogs/route.ts | Added type annotation and empty string to null conversion for description field |
| try { | ||
| const res = await fetch(`/api/projects/${project.project_id}/ship`, { | ||
| method: "POST", | ||
| credentials: "include", |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The credentials: "include" option on line 41 is unnecessary for same-origin requests in Next.js. The cookies are automatically sent with same-origin requests. This option is typically only needed for cross-origin requests with credentials. Consider removing it for clarity.
| credentials: "include", |
| const { formUrl } = await res.json(); | ||
| const redirectUrl = `${formUrl}?item=${encodeURIComponent(item!)}`; | ||
| window.location.href = redirectUrl; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The redirectUrl constructed on line 32 should be validated to ensure the formUrl from the API is a trusted URL before redirecting. While the backend should control this, client-side validation would add defense in depth. Consider validating that formUrl is a known trusted domain before performing the redirect.
| if (data.description === "") { | ||
| data.description = null; | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mutation of the parsed data on lines 16-18 occurs after type assertion but without proper type checking. Since the type annotation on line 10 doesn't provide runtime safety, the check data.description === "" could fail if description is undefined or missing. Consider restructuring this to avoid mutation and ensure type safety, for example: body: JSON.stringify({ ...data, description: data.description === "" ? null : data.description })
|
|
||
| if (!res.ok) { | ||
| const data = await res.json(); | ||
| setError(data.error || "Failed to load shop configuration"); |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error response on line 26 uses a generic "Failed to load shop configuration" message without including the backend's error detail. Consider extracting and including the error from the backend response to provide more context: setError(data.error || data.detail || "Failed to load shop configuration")
| setError(data.error || "Failed to load shop configuration"); | |
| setError((data && (data.error || data.detail)) || "Failed to load shop configuration"); |
| </button> | ||
| </div> | ||
| {shipError && ( | ||
| <p className="text-red-600 text-sm mt-2">{shipError}</p> |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message displayed on lines 105-107 lacks accessibility attributes. Consider adding role="alert" and aria-live="polite" to the error container so screen readers announce the error when it appears.
| <p className="text-red-600 text-sm mt-2">{shipError}</p> | |
| <p | |
| className="text-red-600 text-sm mt-2" | |
| role="alert" | |
| aria-live="polite" | |
| > | |
| {shipError} | |
| </p> |
| } | ||
|
|
||
| let data: unknown; | ||
| let data: { project_id: string; content: string; media_url?: string; description?: string | null }; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type annotation on line 10 is unsafe because req.json() returns any, which doesn't guarantee the data will match this type at runtime. Consider adding runtime validation using a library like Zod, or keeping the type as unknown and performing explicit type checking before using the data properties.
| {shipError && ( | ||
| <p className="text-red-600 text-sm mt-2">{shipError}</p> | ||
| )} |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message (lines 105-107) is placed as a third child in the flex container with justify-between (line 69). This will position the error horizontally alongside the project info and buttons, which is likely not the intended layout. Consider moving the error message outside this flex container (after line 108) or restructuring the layout to display the error below the buttons on a new line.
| onCancelAction, | ||
| }: { | ||
| projectId: number; | ||
| initialData: ProjectData; | ||
| onCancel: () => void; | ||
| onCancelAction: () => void; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prop name "onCancelAction" is unconventional. Standard React naming convention for callback props is "on[EventName]" without the "Action" suffix (e.g., "onCancel", "onClick"). While custom component props can have any name, following the conventional pattern makes the codebase more intuitive. Consider using just "onCancel" instead.
| } | ||
|
|
||
| const { formUrl } = await res.json(); | ||
| const redirectUrl = `${formUrl}?item=${encodeURIComponent(item!)}`; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non-null assertion operator (!) on line 32 is unnecessary because the item parameter is already checked for null/undefined on line 14, and the function returns early if it's falsy. You can safely use item without the assertion: ${formUrl}?item=${encodeURIComponent(item)}
| const redirectUrl = `${formUrl}?item=${encodeURIComponent(item!)}`; | |
| const redirectUrl = `${formUrl}?item=${encodeURIComponent(item)}`; |