Conversation
|
You've used up your 5 PR reviews for this month under the Korbit Starter Plan. You'll get 5 more reviews on November 11th, 2025 or you can upgrade to Pro for unlimited PR reviews and enhanced features in your Korbit Console. |
WalkthroughThis PR restructures the app layout by migrating the blog page from Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
portfolio | 111aa1b | Commit Preview URL Branch Preview URL |
Nov 07 2025, 12:41 PM |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (7)
components/ui/button.tsx (1)
39-58: Forward refs throughButtonRight now
Buttonis a plain function component, so any consumer passing a ref (for focus, measurement, etc.) getsnull, unlike a native<button>. Wrap it inReact.forwardRefand forward the ref through to preserve accessibility ergonomics.-function Button({ - className, - variant, - size, - asChild = false, - ...props -}: React.ComponentProps<"button"> & - VariantProps<typeof buttonVariants> & { - asChild?: boolean; - }) { - const Comp = asChild ? Slot : "button"; - - return ( - <Comp - data-slot="button" - className={cn(buttonVariants({ variant, size, className }))} - {...props} - /> - ); -} - -export { Button, buttonVariants }; +const Button = React.forwardRef< + HTMLButtonElement, + React.ComponentPropsWithoutRef<"button"> & + VariantProps<typeof buttonVariants> & { + asChild?: boolean; + } +>(({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + + return ( + <Comp + ref={ref} + data-slot="button" + className={cn(buttonVariants({ variant, size, className }))} + {...props} + /> + ); +}); +Button.displayName = "Button"; + +export { Button, buttonVariants };components/globe.tsx (1)
23-23: Clean up redundant Tailwind classes.The className has a double space and includes both
dark:invertandinvert-0, which is redundant since the default is no invert.Apply this diff:
- <div className="relative w-[400px] max-w-5xl mx-auto aspect-square dark:invert invert-0 saturate-100 brightness-100"> + <div className="relative w-[400px] max-w-5xl mx-auto aspect-square dark:invert saturate-100 brightness-100">app/(portfolio)/page.tsx (3)
2-2: Remove unused import.
TextEffectis imported but never used in this component.Apply this diff:
import { Globe } from "@/components/globe"; -import { TextEffect } from "@/components/ui/text-effect";
4-4: Remove unnecessaryasynckeyword.The function is marked
asyncbut contains noawaitstatements or asynchronous operations.Apply this diff:
-export default async function IndexPage() { +export default function IndexPage() {
9-12: Replace placeholder content.The lorem ipsum text should be replaced with actual portfolio content.
Would you like me to open an issue to track replacing this placeholder content?
components/ui/globe.tsx (2)
147-150: Optimize duplicate point filtering.The current implementation uses
findIndexinsidefilter, resulting in O(n²) complexity. For the current dataset (~80 points), this is acceptable, but a Set-based approach would be more efficient and scale better.Apply this diff for O(n) complexity:
- const uniquePoints = points.filter( - (v, i, a) => - a.findIndex((v2) => v2.lat === v.lat && v2.lng === v.lng) === i - ); + const seen = new Set<string>(); + const uniquePoints = points.filter((v) => { + const key = `${v.lat},${v.lng}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + });
227-227: Consider removing non-null assertion.While the guard on Line 210 ensures
globeRef.currentexists, using the non-null assertion operator is slightly risky. A more defensive approach would check explicitly.Apply this diff:
- globeRef.current!.ringsData(ringsData); + if (globeRef.current) { + globeRef.current.ringsData(ringsData); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (11)
app/(pages)/page.tsx(0 hunks)app/(portfolio)/blog/page.tsx(1 hunks)app/(portfolio)/page.tsx(1 hunks)components/globe.tsx(1 hunks)components/shared/navbar.tsx(1 hunks)components/ui/button.tsx(1 hunks)components/ui/card-nav.tsx(3 hunks)components/ui/globe.tsx(1 hunks)components/ui/in-view.tsx(1 hunks)components/ui/text-effect.tsx(1 hunks)package.json(1 hunks)
💤 Files with no reviewable changes (1)
- app/(pages)/page.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
app/(portfolio)/blog/page.tsx (3)
app/(portfolio)/page.tsx (1)
IndexPage(4-16)sanity/client.ts (1)
client(3-8)sanity/sanity.types.ts (1)
Post(16-55)
components/ui/card-nav.tsx (1)
components/shared/mode-toggle.tsx (1)
ModeToggle(15-114)
components/ui/button.tsx (1)
lib/utils.ts (1)
cn(4-6)
components/globe.tsx (1)
components/ui/globe.tsx (2)
World(254-291)Globe(63-234)
app/(portfolio)/page.tsx (2)
components/globe.tsx (1)
Globe(21-27)components/ui/globe.tsx (1)
Globe(63-234)
components/ui/globe.tsx (1)
components/globe.tsx (1)
Globe(21-27)
components/ui/text-effect.tsx (1)
lib/utils.ts (1)
cn(4-6)
components/shared/navbar.tsx (1)
components/ui/in-view.tsx (1)
InView(28-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: deploy
- GitHub Check: Workers Builds: portfolio
🔇 Additional comments (9)
components/globe.tsx (2)
5-11: LGTM!The Loader component provides a clean fallback UI during the dynamic import phase.
13-19: LGTM!The dynamic import with
ssr: falseis appropriate for a WebGL-based component that requires browser APIs.components/ui/globe.tsx (7)
1-20: LGTM!The Three.js module setup, type augmentation, and constant definitions are well-structured.
22-58: LGTM!The type definitions for
Position,GlobeConfig, andWorldPropsare comprehensive and correctly structured.
86-92: LGTM!The initialization effect correctly creates the globe instance once on mount with appropriate guards.
95-115: Material update logic is correct.The effect properly updates the globe material when properties change. The dependency array will work optimally once
defaultPropsis memoized as suggested above.
239-249: LGTM!The WebGL renderer configuration properly updates when size changes, with correct dependencies.
254-291: LGTM!The
Worldwrapper correctly sets up the Three.js Canvas with appropriate scene, camera, lighting, and controls configuration. TheOrbitControlssettings provide good UX with rotation enabled but pan/zoom disabled.
296-317: LGTM!The utility functions
hexToRgbandgenRandomNumbersare well-implemented. The hex conversion handles both shorthand and full notation, and the random number generator ensures uniqueness.
| <h2 className="text-xl font-semibold">{post.title}</h2> | ||
| <p>{new Date(post.publishedAt ?? "").toLocaleDateString()}</p> | ||
| </Link> |
There was a problem hiding this comment.
Handle missing publishedAt to prevent runtime crash
Line 22 currently does new Date(post.publishedAt ?? ""), which throws RangeError: Invalid time value whenever a post lacks a publishedAt timestamp (e.g., drafts). That tanks the whole blog index render. Guard the conversion and fall back to a safe date before formatting.
-const POSTS_QUERY = `*[_type == "post" && defined(slug.current)]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt}`;
+const POSTS_QUERY = `*[_type == "post" && defined(slug.current)]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt, _createdAt}`;
...
- <p>{new Date(post.publishedAt ?? "").toLocaleDateString()}</p>
+ <p>
+ {(post.publishedAt ?? post._createdAt)
+ ? new Date(post.publishedAt ?? post._createdAt).toLocaleDateString()
+ : "Unpublished"}
+ </p>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/(portfolio)/blog/page.tsx around lines 21–23, the code calls new
Date(post.publishedAt ?? "") which throws a RangeError when publishedAt is
missing; change this to guard the conversion and render a safe fallback: check
if post.publishedAt is present and a valid date before calling new
Date(...).toLocaleDateString(), otherwise render a safe placeholder (e.g.,
"Unpublished" or an explicit fallback date string like "—") so rendering never
attempts to format an invalid date.
| arcLength: 0.9, | ||
| rings: 1, | ||
| maxRings: 3, | ||
| initialPosition: { lat: 22.3193, lng: 114.1694 }, |
There was a problem hiding this comment.
🧩 Analysis chain
Unused configuration property.
The initialPosition property is defined but not consumed by the World component. The globe always starts at its default orientation.
Run the following script to verify that initialPosition is not used:
If confirmed unused, either remove this property or implement initial camera positioning in the World component.
🏁 Script executed:
#!/bin/bash
# Search for usage of initialPosition in globe-related files
rg -n 'initialPosition' --type=ts --type=tsx -C3Length of output: 27
🏁 Script executed:
#!/bin/bash
# Search for initialPosition usage - corrected syntax
rg -n 'initialPosition' --type=ts -C3Length of output: 669
🏁 Script executed:
#!/bin/bash
# Search for initialPosition being accessed or used (not just defined)
rg -n '\.initialPosition|initialPosition\s*[,}]|initialPosition\s*:' --type=ts -C2Length of output: 242
Remove unused initialPosition property or implement camera positioning.
The initialPosition property is defined in globeConfig but never accessed. Search confirms no usage of .initialPosition anywhere in the codebase. Either remove this unused configuration or implement initial camera positioning in the World component to consume it.
🤖 Prompt for AI Agents
components/globe.tsx around line 48: the globeConfig includes initialPosition: {
lat: 22.3193, lng: 114.1694 } which is unused; either remove this property from
the config or wire it into the World component to position the camera on mount.
If removing: delete the initialPosition key from globeConfig. If implementing:
read globeConfig.initialPosition in World, convert lat/lng to the globe/camera
coordinate system used by the globe library, and set the camera position (or
call the globe API to point/animate the camera) during the component mount or
initialization so the globe starts focused on that lat/lng. Ensure removal or
usage is reflected in types/interfaces to avoid unused property lint warnings.
| endLat: -22.9068, | ||
| endLng: -43.1729, | ||
| arcAlt: 0.1, | ||
| color: colors[Math.floor(Math.random() * (colors.length - 1))], |
There was a problem hiding this comment.
Off-by-one error in color selection.
The expression Math.floor(Math.random() * (colors.length - 1)) will never select the last color in the array. With colors.length = 3, this produces only indices 0 or 1, meaning "#bfdbfe" is never used.
Apply this diff to fix the color selection:
- color: colors[Math.floor(Math.random() * (colors.length - 1))],
+ color: colors[Math.floor(Math.random() * colors.length)],This change should be applied to all arc definitions throughout the sampleArcs array.
Also applies to: 73-73, 82-82, 91-91, 100-100, 109-109, 118-118, 127-127, 136-136, 145-145, 154-154, 163-163, 172-172, 181-181, 190-190, 199-199, 208-208, 217-217, 226-226, 235-235, 244-244, 253-253, 262-262, 271-271, 280-280, 289-289, 298-298, 307-307, 316-316, 325-325, 334-334, 343-343, 352-352, 361-361, 370-370, 379-379, 388-388, 397-397, 406-406, 415-415
🤖 Prompt for AI Agents
In components/globe.tsx around line 64 (and similarly at lines
73,82,91,100,109,118,127,136,145,154,163,172,181,190,199,208,217,226,235,244,253,262,271,280,289,298,307,316,325,334,343,352,361,370,379,388,397,406,415)
the random color index uses Math.floor(Math.random() * (colors.length - 1))
which never selects the last color; replace that expression with
Math.floor(Math.random() * colors.length) for each arc definition so the full
range 0..colors.length-1 can be selected, updating every occurrence listed.
| const defaultProps = { | ||
| pointSize: 1, | ||
| atmosphereColor: "#ffffff", | ||
| showAtmosphere: true, | ||
| atmosphereAltitude: 0.1, | ||
| polygonColor: "rgba(255,255,255,0.7)", | ||
| globeColor: "#1d072e", | ||
| emissive: "#000000", | ||
| emissiveIntensity: 0.1, | ||
| shininess: 0.9, | ||
| arcTime: 2000, | ||
| arcLength: 0.9, | ||
| rings: 1, | ||
| maxRings: 3, | ||
| ...globeConfig, | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Memoize defaultProps to prevent unnecessary re-renders.
The defaultProps object is recreated on every render, causing the useEffect hooks that depend on its properties to re-run unnecessarily. This impacts performance, especially for the material updates and data visualization effects.
Apply this diff to memoize the configuration:
+ const defaultProps = useMemo(() => ({
- const defaultProps = {
pointSize: 1,
atmosphereColor: "#ffffff",
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: "rgba(255,255,255,0.7)",
globeColor: "#1d072e",
emissive: "#000000",
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
...globeConfig,
- };
+ }), [globeConfig]);Don't forget to add the import:
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const defaultProps = { | |
| pointSize: 1, | |
| atmosphereColor: "#ffffff", | |
| showAtmosphere: true, | |
| atmosphereAltitude: 0.1, | |
| polygonColor: "rgba(255,255,255,0.7)", | |
| globeColor: "#1d072e", | |
| emissive: "#000000", | |
| emissiveIntensity: 0.1, | |
| shininess: 0.9, | |
| arcTime: 2000, | |
| arcLength: 0.9, | |
| rings: 1, | |
| maxRings: 3, | |
| ...globeConfig, | |
| }; | |
| const defaultProps = useMemo(() => ({ | |
| pointSize: 1, | |
| atmosphereColor: "#ffffff", | |
| showAtmosphere: true, | |
| atmosphereAltitude: 0.1, | |
| polygonColor: "rgba(255,255,255,0.7)", | |
| globeColor: "#1d072e", | |
| emissive: "#000000", | |
| emissiveIntensity: 0.1, | |
| shininess: 0.9, | |
| arcTime: 2000, | |
| arcLength: 0.9, | |
| rings: 1, | |
| maxRings: 3, | |
| ...globeConfig, | |
| }), [globeConfig]); |
🤖 Prompt for AI Agents
In components/ui/globe.tsx around lines 68 to 83, defaultProps is recreated on
every render causing dependent useEffect hooks to re-run; wrap the defaultProps
construction in React.useMemo (e.g. const defaultProps = useMemo(() => ({
...defaults, ...globeConfig }), [globeConfig])) and update any useEffect
dependency arrays to depend on the memoized defaultProps or specific properties
instead of reconstructing values; also add the import for useMemo from react at
the top of the file.
Summary by CodeRabbit
Release Notes
New Features
UI & Components