Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@Developing-Gamer
Copy link
Contributor

@Developing-Gamer Developing-Gamer self-assigned this Nov 13, 2025
@vercel
Copy link

vercel bot commented Nov 13, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Nov 13, 2025 5:06am
stack-dashboard Ready Ready Preview Comment Nov 13, 2025 5:06am
stack-demo Ready Ready Preview Comment Nov 13, 2025 5:06am
stack-docs Ready Ready Preview Comment Nov 13, 2025 5:06am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 13, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch api-keys-and-vercel-app-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Nov 13, 2025

Greptile Overview

Greptile Summary

This PR redesigns the Vercel integration page with a modern, interactive UI featuring collapsible step cards, animated progress tracking, and a celebratory confetti effect upon completion.

Key changes:

  • Restructured step data model from flat actions to nested checklist items
  • Added collapsible/expandable step cards with smooth CSS grid animations
  • Implemented animated progress bar with delayed animation for visual polish
  • Added auto-expansion logic to focus users on the next incomplete step
  • Integrated confetti celebration animation when all steps are completed
  • Improved visual hierarchy with updated card styling, badges, and icons
  • Enhanced accessibility with proper keyboard navigation and ARIA attributes

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The changes are purely UI/UX improvements with no breaking changes to functionality. The code follows React best practices with proper cleanup in useEffect hooks, maintains existing API integration patterns, and includes good accessibility support. No security issues or logic errors were identified.
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx 5/5 Complete UI redesign with collapsible steps, progress tracking, confetti animation, and improved UX through auto-expansion and visual feedback

Sequence Diagram

sequenceDiagram
    participant User
    participant PageClient
    participant State
    participant AdminApp
    participant Confetti
    
    User->>PageClient: Load page
    PageClient->>State: Initialize state (keys, expandedStep, etc.)
    PageClient->>State: Calculate progress & nextItem
    
    alt First load or next step changes
        PageClient->>State: Auto-expand next step section
    end
    
    PageClient->>PageClient: Animate progress bar (100ms delay)
    
    User->>PageClient: Click "Generate keys"
    PageClient->>AdminApp: createInternalApiKey()
    AdminApp-->>PageClient: Return keys
    PageClient->>State: setKeys(newKeys)
    PageClient->>State: Recalculate progress
    
    User->>PageClient: Click step header
    PageClient->>State: Toggle expandedStepId
    
    User->>PageClient: Click checklist item
    PageClient->>State: toggleStepCompletion()
    State->>State: Update manuallyCompleted (cascading logic)
    PageClient->>State: Recalculate progress
    
    alt All steps completed
        PageClient->>Confetti: Trigger confetti animation
        loop Every 250ms for 3 seconds
            Confetti->>Confetti: Fire confetti particles
        end
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copilot finished reviewing on behalf of Developing-Gamer November 13, 2025 05:03
Copy link

Copilot AI left a 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 redesigns the Vercel integration page with an improved UI/UX featuring expandable/collapsible step cards, animated progress tracking, and celebratory confetti effects upon completion.

Key Changes

  • Redesigned step cards with expand/collapse functionality and improved visual hierarchy
  • Added animated progress bar with real-time completion tracking
  • Introduced confetti celebration effect when all integration steps are completed

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +42 to +61
const STATUS_META: Record<
StepStatus,
{
cardClass: string,
inactiveIcon: string,
}
> = {
done: {
cardClass: "border-primary/30 bg-background transition-all duration-300 hover:shadow-lg dark:border-primary/40 dark:shadow-primary/5",
inactiveIcon: "text-emerald-500 dark:text-emerald-400",
},
action: {
cardClass: "border-primary/30 bg-background transition-all duration-300 hover:shadow-lg dark:border-primary/40 dark:shadow-primary/5",
inactiveIcon: "text-muted-foreground",
},
blocked: {
cardClass: "border-primary/30 bg-background transition-all duration-300 hover:shadow-lg dark:border-primary/40 dark:shadow-primary/5",
inactiveIcon: "text-muted-foreground",
},
};
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The STATUS_META object defines identical cardClass values for all three status types ("done", "action", and "blocked"). This makes the status field redundant since it doesn't affect the card styling. Either differentiate the card classes based on status, or remove the unused status field from the type definition if the styling is intentionally the same.

If these styles are meant to be different, consider updating them like:

const STATUS_META: Record<StepStatus, { cardClass: string, inactiveIcon: string }> = {
  done: {
    cardClass: "border-emerald-500/30 bg-emerald-500/5 transition-all duration-300 hover:shadow-lg dark:border-emerald-500/40",
    inactiveIcon: "text-emerald-500 dark:text-emerald-400",
  },
  action: {
    cardClass: "border-primary/30 bg-background transition-all duration-300 hover:shadow-lg dark:border-primary/40 dark:shadow-primary/5",
    inactiveIcon: "text-muted-foreground",
  },
  blocked: {
    cardClass: "border-muted/30 bg-muted/5 transition-all duration-300 dark:border-muted/40",
    inactiveIcon: "text-muted-foreground",
  },
};

Copilot uses AI. Check for mistakes.
setExpandedStepId((current) => (current === stepId ? null : stepId));
};

const handleItemClick = (stepId: StepId, itemId: string) => {
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The handleItemClick function is always passed to StepCard but only uses the itemId parameter without using it. It immediately calls toggleStepCompletion(stepId) regardless of which specific item was clicked. This could be confusing since the function signature suggests per-item behavior but actually performs per-step behavior.

Consider either:

  1. Renaming to clarify it's a step-level action: onStepItemClick
  2. Or removing the itemId parameter if it's not needed:
const handleItemClick = (stepId: StepId) => {
  toggleStepCompletion(stepId);
};

And update the call site to: onItemClick={() => handleItemClick(step.id)}

Suggested change
const handleItemClick = (stepId: StepId, itemId: string) => {
const handleItemClick = (stepId: StepId) => {

Copilot uses AI. Check for mistakes.
Comment on lines +550 to +557
<CardDescription
className={cn(
"text-sm transition-opacity duration-300 ease-in-out",
props.isExpanded ? "opacity-100" : "opacity-0"
)}
>
{props.step.subtitle}
</CardDescription>
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The CardDescription has conditional opacity that transitions between opacity-0 when collapsed and opacity-100 when expanded. However, when collapsed (opacity-0), the subtitle text is still rendered in the DOM and takes up space, which could cause layout issues. The text becomes invisible but still occupies vertical space.

Consider using display: none or removing it from the DOM when collapsed:

{props.isExpanded && (
  <CardDescription className="text-sm">
    {props.step.subtitle}
  </CardDescription>
)}

Or use a height transition instead of just opacity if you want the animation effect.

Suggested change
<CardDescription
className={cn(
"text-sm transition-opacity duration-300 ease-in-out",
props.isExpanded ? "opacity-100" : "opacity-0"
)}
>
{props.step.subtitle}
</CardDescription>
{props.isExpanded && (
<CardDescription className="text-sm">
{props.step.subtitle}
</CardDescription>
)}

Copilot uses AI. Check for mistakes.
Comment on lines +559 to +573
<button
type="button"
onClick={(e) => {
e.stopPropagation();
props.onToggle();
}}
className="flex shrink-0 items-center justify-center rounded-md p-1.5 transition-colors hover:bg-accent"
aria-label={props.isExpanded ? "Collapse section" : "Expand section"}
>
{props.isExpanded ? (
<ChevronUp className="h-5 w-5 text-muted-foreground" />
) : (
<ChevronDown className="h-5 w-5 text-muted-foreground" />
)}
</button>
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

[nitpick] The chevron toggle button in the CardHeader has both onClick handlers - one on the button itself (line 561-563) and one on the parent CardHeader (line 526). The button's handler calls e.stopPropagation() to prevent the parent's handler from firing, but this creates redundancy since both handlers call the same props.onToggle() function.

The e.stopPropagation() is unnecessary here. Consider simplifying by removing the button's onClick handler and letting it bubble to the parent:

<button
  type="button"
  className="flex shrink-0 items-center justify-center rounded-md p-1.5 transition-colors hover:bg-accent"
  aria-label={props.isExpanded ? "Collapse section" : "Expand section"}
>

Copilot uses AI. Check for mistakes.
Comment on lines +603 to +605
<CardFooter className="flex justify-end">
{getActionButton()}
</CardFooter>
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The action button is displayed in the CardFooter for all steps, but it's only relevant for the "project" and "keys" steps. For other steps (env-vars, deploy, verify), getActionButton() returns null, which still renders an empty CardFooter taking up space.

Consider conditionally rendering the CardFooter only when there's an action button:

{getActionButton() && (
  <CardFooter className="flex justify-end">
    {getActionButton()}
  </CardFooter>
)}

Or cache the button result to avoid calling the function twice:

const actionButton = getActionButton();
// ...
{actionButton && (
  <CardFooter className="flex justify-end">
    {actionButton}
  </CardFooter>
)}

Copilot uses AI. Check for mistakes.
Comment on lines +596 to +601
{props.error && (
<Alert variant="destructive">
<AlertTitle>Could not generate keys</AlertTitle>
<AlertDescription>{props.error}</AlertDescription>
</Alert>
)}
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The error alert is rendered inside the step's content area but only applies to the "keys" step. However, the error prop is passed to all StepCard instances (line 397), causing the error to potentially display in steps where it's not relevant.

Consider only passing the error prop when rendering the "keys" step:

<StepCard
  step={step}
  isExpanded={isExpanded}
  onToggle={() => handleStepToggle(step.id)}
  onGenerateKeys={step.id === "keys" && !keys ? handleGenerateKeys : undefined}
  isGenerating={isGenerating}
  error={step.id === "keys" ? error : null}
  onItemClick={(itemId) => handleItemClick(step.id, itemId)}
/>

Copilot uses AI. Check for mistakes.
Comment on lines +278 to +280
function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min;
}
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

[nitpick] The randomInRange function is defined inside the effect callback on line 278-280. Since this function doesn't depend on any variables from the effect scope and is only used within the interval callback, it would be more efficient and clearer to define it outside the effect or even outside the component to avoid recreating it on every completion.

Consider moving it outside the effect:

function randomInRange(min: number, max: number) {
  return Math.random() * (max - min) + min;
}

// Trigger confetti when all tasks are completed
useEffect(() => {
  // ... rest of the effect
}, [vercelProgress.completed, vercelProgress.total]);

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +309
useEffect(() => {
const allCompleted = vercelProgress.completed === vercelProgress.total && vercelProgress.total > 0;
const prevAllCompleted = prevAllCompletedRef.current;

// Only trigger confetti when completion changes from false to true
if (prevAllCompleted !== undefined && !prevAllCompleted && allCompleted) {
// Create a confetti effect dropping from the top
const duration = 3000;
const animationEnd = Date.now() + duration;
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 9999 };

function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min;
}

const interval = setInterval(() => {
const timeLeft = animationEnd - Date.now();

if (timeLeft <= 0) {
clearInterval(interval);
return;
}

const particleCount = 50 * (timeLeft / duration);
const result = confetti.default({
...defaults,
particleCount,
origin: { x: randomInRange(0.1, 0.9), y: 0 },
});
if (result) {
runAsynchronously(result, { noErrorLogging: true });
}
}, 250);

// Cleanup interval on unmount or when completion changes
return () => {
clearInterval(interval);
};
}

// Update the ref to track the current completion state
prevAllCompletedRef.current = allCompleted;
}, [vercelProgress.completed, vercelProgress.total]);
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The confetti effect interval is not properly cleaned up when the component unmounts before all tasks are completed. The cleanup function on line 302-304 only runs when the effect dependencies change or when allCompleted becomes true. If the component unmounts while the confetti is running (e.g., user navigates away), the interval will continue running, potentially causing memory leaks.

Consider storing the interval in a ref and cleaning it up in the effect's cleanup function unconditionally:

useEffect(() => {
  const allCompleted = vercelProgress.completed === vercelProgress.total && vercelProgress.total > 0;
  const prevAllCompleted = prevAllCompletedRef.current;

  if (prevAllCompleted !== undefined && !prevAllCompleted && allCompleted) {
    const duration = 3000;
    const animationEnd = Date.now() + duration;
    const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 9999 };

    function randomInRange(min: number, max: number) {
      return Math.random() * (max - min) + min;
    }

    const interval = setInterval(() => {
      const timeLeft = animationEnd - Date.now();

      if (timeLeft <= 0) {
        clearInterval(interval);
        return;
      }

      const particleCount = 50 * (timeLeft / duration);
      const result = confetti.default({
        ...defaults,
        particleCount,
        origin: { x: randomInRange(0.1, 0.9), y: 0 },
      });
      if (result) {
        runAsynchronously(result, { noErrorLogging: true });
      }
    }, 250);

    return () => clearInterval(interval);
  }

  prevAllCompletedRef.current = allCompleted;
}, [vercelProgress.completed, vercelProgress.total]);

Copilot uses AI. Check for mistakes.
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.

2 participants