Skip to content

🧹 Refactor CardGeneratorModal#125

Closed
is0692vs wants to merge 2 commits intomainfrom
jules-2358708667884152100-3f73c316
Closed

🧹 Refactor CardGeneratorModal#125
is0692vs wants to merge 2 commits intomainfrom
jules-2358708667884152100-3f73c316

Conversation

@is0692vs
Copy link
Copy Markdown
Contributor

@is0692vs is0692vs commented Apr 3, 2026

🧹 [Refactor CardGeneratorModal]

🎯 What:
Refactored the large and monolithic CardGeneratorModal.tsx file into smaller, more manageable hooks and sub-components.

💡 Why:
The original CardGeneratorModal.tsx file was over 460 lines long, handling everything from layout and display options state management, to html-to-image blob/png conversion, clipboard copying, downloading, and rendering numerous complex UI sections (headers, tabs, settings grids, actions). This refactoring improves the codebase's readability, maintainability, and separation of concerns by breaking it down into distinct, logical modules. It makes individual pieces easier to test, reuse, and debug.

Changes made:

  • Hooks created:
    • useCardImageGenerator: Encapsulates the logic for html-to-image rendering, blob copying to clipboard, PNG downloading, and related state (previewUrl, isGenerating, copyStatus).
    • useCardSettings: Encapsulates the logic for reading/writing the user's CardLayout and CardDisplayOptions preferences to/from localStorage, as well as toggling visibility.
  • Components extracted:
    • ModalHeader: The simple header section of the modal.
    • ModalSettingsTab: The complex grid containing all the checkboxes for toggling blocks and display options.
    • ModalActions: The action buttons for "Copy Image" and "Download PNG".

✅ Verification:

  • Ran the full test suite (npm run test -- --run) and verified that all 218 tests passed successfully.
  • Verified there are no new linting errors (npm run lint).
  • Checked type definitions with npx tsc --noEmit.

✨ Result:
The CardGeneratorModal.tsx file has been significantly reduced in size and complexity (from ~460 lines down to ~180 lines), delegating specific responsibilities to well-named, dedicated hooks and sub-components. The core functionality remains identical.


PR created automatically by Jules for task 2358708667884152100 started by @is0692vs

Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 3, 2026

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

Project Deployment Actions Updated (UTC)
github-user-summary Ready Ready Preview, Comment Apr 12, 2026 4:33pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Warning

Rate limit exceeded

@is0692vs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 23 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 23 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 543757b7-30e1-4cf7-af87-04fc2d33b3ea

📥 Commits

Reviewing files that changed from the base of the PR and between dd37d9e and 2d8838f.

📒 Files selected for processing (7)
  • pr_description.md
  • src/components/CardGeneratorModal.tsx
  • src/components/CardGeneratorModalParts/ModalActions.tsx
  • src/components/CardGeneratorModalParts/ModalHeader.tsx
  • src/components/CardGeneratorModalParts/ModalSettingsTab.tsx
  • src/hooks/useCardImageGenerator.ts
  • src/hooks/useCardSettings.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jules-2358708667884152100-3f73c316

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.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Refactor CardGeneratorModal into hooks and sub-components

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Refactored CardGeneratorModal.tsx from ~460 lines to ~180 lines
• Extracted image generation logic into useCardImageGenerator hook
• Extracted settings management into useCardSettings hook
• Created three sub-components: ModalHeader, ModalSettingsTab, ModalActions
• Improved code organization, maintainability, and separation of concerns
Diagram
flowchart LR
  A["CardGeneratorModal<br/>~460 lines"] -->|extract| B["useCardImageGenerator<br/>hook"]
  A -->|extract| C["useCardSettings<br/>hook"]
  A -->|extract| D["ModalHeader<br/>component"]
  A -->|extract| E["ModalSettingsTab<br/>component"]
  A -->|extract| F["ModalActions<br/>component"]
  B --> G["CardGeneratorModal<br/>~180 lines"]
  C --> G
  D --> G
  E --> G
  F --> G
Loading

Grey Divider

File Changes

1. src/hooks/useCardImageGenerator.ts ✨ Enhancement +122/-0

Image generation and export logic hook

• New hook encapsulating image generation logic using html-to-image library
• Manages state for isGenerating, previewUrl, and copyStatus
• Implements handleDownload and handleCopy functions for image export
• Handles font loading and cleanup with proper cancellation logic

src/hooks/useCardImageGenerator.ts


2. src/hooks/useCardSettings.ts ✨ Enhancement +73/-0

Card settings and layout management hook

• New hook managing card layout and display options state
• Handles localStorage persistence for user preferences
• Provides methods to toggle block visibility and display options
• Implements hydration pattern to sync with localStorage on mount

src/hooks/useCardSettings.ts


3. src/components/CardGeneratorModal.tsx ✨ Enhancement +48/-295

Main modal component refactored and simplified

• Reduced from ~460 lines to ~180 lines through extraction
• Removed image generation and settings management logic (moved to hooks)
• Replaced inline UI sections with imported sub-components
• Simplified component to focus on modal orchestration and layout
• Removed unused imports and dependencies

src/components/CardGeneratorModal.tsx


View more (4)
4. src/components/CardGeneratorModalParts/ModalHeader.tsx ✨ Enhancement +34/-0

Modal header UI component

• New sub-component extracted from main modal
• Renders modal title and close button
• Accepts onClose callback prop for close functionality

src/components/CardGeneratorModalParts/ModalHeader.tsx


5. src/components/CardGeneratorModalParts/ModalSettingsTab.tsx ✨ Enhancement +55/-0

Modal settings tab UI component

• New sub-component extracted from main modal
• Renders settings grid with block visibility and display option checkboxes
• Accepts configuration arrays and toggle callbacks as props
• Maintains responsive grid layout with Tailwind CSS

src/components/CardGeneratorModalParts/ModalSettingsTab.tsx


6. src/components/CardGeneratorModalParts/ModalActions.tsx ✨ Enhancement +87/-0

Modal action buttons UI component

• New sub-component extracted from main modal
• Renders action buttons for copying image and downloading PNG
• Displays dynamic copy status feedback (idle/copied/error)
• Accepts handler functions and state as props

src/components/CardGeneratorModalParts/ModalActions.tsx


7. pr_description.md Miscellaneous +0/-11

Removed outdated PR description file

• File deleted as part of cleanup

pr_description.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 3, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Remediation recommended

1. Error context dropped 🐞 Bug ✧ Quality
Description
useCardImageGenerator logs generic messages on image generation/clipboard failures without logging
the thrown error, making these failures hard to diagnose in production. This is a regression because
logger.error supports variadic args but the error object is discarded.
Code

src/hooks/useCardImageGenerator.ts[R26-38]

+  const generateImage = useCallback(async () => {
+    if (!cardRef.current) return null;
+    try {
+      const dataUrl = await toPng(cardRef.current, {
+        cacheBust: true,
+        pixelRatio: 1,
+        backgroundColor: "#0d1117",
+      });
+      return dataUrl;
+    } catch {
+      logger.error("Failed to generate image");
+      return null;
+    }
Evidence
The hook catches exceptions but does not capture/log the error, even though the logger supports
passing additional arguments (e.g., an Error) for debugging context.

src/hooks/useCardImageGenerator.ts[26-38]
src/hooks/useCardImageGenerator.ts[93-111]
src/lib/logger.ts[7-24]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`useCardImageGenerator` swallows exceptions and calls `logger.error()` without including the caught error. This removes the most useful debugging context (stack/message) when png generation or clipboard copy fails.

### Issue Context
`logger.error` supports variadic args, so we can safely pass the error object.

### Fix Focus Areas
- src/hooks/useCardImageGenerator.ts[26-38]
- src/hooks/useCardImageGenerator.ts[93-111]

### Suggested change
- Change `catch { ... }` to `catch (err) { logger.error("...", err); ... }` for both image generation and copy paths.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Async handler typed void 🐞 Bug ⚙ Maintainability
Description
ModalActions types handleCopy as () => void even though the passed implementation is async,
which hides the Promise-returning contract and can bypass lint rules meant to prevent misused
promises. This can mask future changes where errors propagate and become unhandled.
Code

src/components/CardGeneratorModalParts/ModalActions.tsx[R3-8]

+interface ModalActionsProps {
+  previewUrl: string | null;
+  copyStatus: "idle" | "copied" | "error";
+  handleCopy: () => void;
+  handleDownload: () => void;
+}
Evidence
The component prop types claim handleCopy is synchronous, but useCardImageGenerator defines it
as an async callback. This mismatch reduces type accuracy at the component boundary.

src/components/CardGeneratorModalParts/ModalActions.tsx[3-8]
src/hooks/useCardImageGenerator.ts[93-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ModalActionsProps.handleCopy` is typed as `() => void`, but the actual handler provided is async (`Promise<void>`). This hides the async nature and can defeat linting that prevents misused Promises.

### Issue Context
React will ignore returned Promises from event handlers; if you want to keep the handler async, it’s best to reflect that in types and optionally use `void` when calling it.

### Fix Focus Areas
- src/components/CardGeneratorModalParts/ModalActions.tsx[3-8]
- src/components/CardGeneratorModalParts/ModalActions.tsx[18-22]
- src/hooks/useCardImageGenerator.ts[93-112]

### Suggested change
- Update prop types to `handleCopy: () => Promise<void> | void` (and same for `handleDownload` if you want consistency), OR
- Keep prop types sync and wrap calls: `onClick={() => { void handleCopy(); }}` to make intent explicit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

3. Unknown settings types 🐞 Bug ⚙ Maintainability
Description
useCardImageGenerator declares layout and displayOptions as unknown, reducing type safety and
obscuring what the hook expects. This makes the hook easier to misuse and harder to refactor safely,
even though proper types exist in the codebase.
Code

src/hooks/useCardImageGenerator.ts[R7-13]

+interface UseCardImageGeneratorProps {
+  cardRef: RefObject<HTMLDivElement | null>;
+  isOpen: boolean;
+  layout: unknown;
+  displayOptions: unknown;
+  username: string;
+}
Evidence
The hook API uses unknown for values that are clearly modeled in the domain (CardLayout,
CardDisplayOptions) and are passed from useCardSettings/modal state; these types are defined in
src/lib/types.ts.

src/hooks/useCardImageGenerator.ts[7-13]
src/hooks/useCardImageGenerator.ts[79-83]
src/lib/types.ts[123-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`useCardImageGenerator` types `layout` and `displayOptions` as `unknown`. This weakens static checks and makes the hook contract unclear.

### Issue Context
The repo already defines `CardLayout` and `CardDisplayOptions` in `src/lib/types.ts`, and those are the values being passed in.

### Fix Focus Areas
- src/hooks/useCardImageGenerator.ts[7-13]
- src/lib/types.ts[123-143]

### Suggested change
- Import `CardLayout` and `CardDisplayOptions` types and update the prop interface accordingly.
- (Optional) If the hook truly only needs these for dependency tracking, add a short comment explaining that intent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the CardGeneratorModal component by extracting its state management and image generation logic into custom hooks (useCardSettings and useCardImageGenerator) and modularizing the UI into smaller sub-components. The review feedback focuses on the new useCardImageGenerator hook, identifying several instances where error objects are swallowed in catch blocks and should be logged for better debugging. Additionally, there is a recommendation to improve type safety by replacing unknown types with specific interfaces for layout and display options.

Comment on lines +35 to +38
} catch {
logger.error("Failed to generate image");
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The error object is being swallowed in this catch block. Please log the error object to aid in debugging. This was handled correctly in the original code before refactoring.

Suggested change
} catch {
logger.error("Failed to generate image");
return null;
}
} catch (err) {
logger.error("Failed to generate image", err);
return null;
}

Comment on lines +53 to +57
} catch {
if (!isCancelled) {
setPreviewUrl(null);
}
} finally {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This catch block swallows potential errors (e.g., from document.fonts.ready). Please log the error to help with debugging.

        } catch (err) {
          logger.error("Image generation process failed", err);
          if (!isCancelled) {
            setPreviewUrl(null);
          }
        }

Comment on lines +108 to +111
} catch {
logger.error("Failed to copy");
setCopyStatus("error");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The error object is being swallowed in this catch block. Please log the error object to aid in debugging. This was handled correctly in the original code before refactoring.

Suggested change
} catch {
logger.error("Failed to copy");
setCopyStatus("error");
}
} catch (err) {
logger.error("Failed to copy", err);
setCopyStatus("error");
}

Comment on lines +5 to +13
import { logger } from "@/lib/logger";

interface UseCardImageGeneratorProps {
cardRef: RefObject<HTMLDivElement | null>;
isOpen: boolean;
layout: unknown;
displayOptions: unknown;
username: string;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For better type safety and code clarity, please use specific types for layout and displayOptions instead of unknown.

Suggested change
import { logger } from "@/lib/logger";
interface UseCardImageGeneratorProps {
cardRef: RefObject<HTMLDivElement | null>;
isOpen: boolean;
layout: unknown;
displayOptions: unknown;
username: string;
}
import { logger } from "@/lib/logger";
import type { CardLayout, CardDisplayOptions } from "@/lib/types";
interface UseCardImageGeneratorProps {
cardRef: RefObject<HTMLDivElement | null>;
isOpen: boolean;
layout: CardLayout;
displayOptions: CardDisplayOptions;
username: string;
}

@is0692vs
Copy link
Copy Markdown
Contributor Author

Closing this stale PR: branch now conflicts with main and still has unresolved review feedback. Please re-open as a fresh PR rebased on current main if we still want this refactor.

@is0692vs is0692vs closed this Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant