Skip to content
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

feat: introduce @unkey/ui internal package with a new hook #2661

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

unrenamed
Copy link
Contributor

@unrenamed unrenamed commented Nov 13, 2024

What does this PR do?

Introduces an internal package, @unkey/ui, to the project. The primary purpose of this package is to encapsulate UI components and utilities related to Unkey's functionality, starting with the addition of a new custom React hook.

Key Details:

  • establishes a dedicated UI module for reusable Unkey components, ensuring better structure and reusability across the project.
  • adds a React hook that facilitates copying text to the clipboard by utilizing the Navigator interface and Clipboard API. The timeout is adjustable for enhanced flexibility in different scenarios. Based on this discussion: fix: resolve 16 of 54 lint warnings #2652 (comment).
  • replaces navigator.clipboard usages with the new hook throughout all the web apps.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a custom hook, useCopyToClipboard, for improved clipboard functionality across various components.
    • Enhanced "Copy identifier" functionality with success notifications upon copying.
  • Bug Fixes

    • Streamlined clipboard operations by replacing manual logic with the new hook, reducing complexity and potential errors.
  • Documentation

    • Added README for the @unkey/ui library, detailing installation and usage instructions.
  • Chores

    • Updated package dependencies to include @unkey/ui across relevant applications.

Copy link

changeset-bot bot commented Nov 13, 2024

⚠️ No Changeset found

Latest commit: 26055c5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Nov 13, 2024

@unrenamed is attempting to deploy a commit to the Unkey Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Nov 13, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces a new custom hook, useCopyToClipboard, from the @unkey/ui library to streamline clipboard operations across multiple components. The CopyButton components in various applications replace their previous clipboard logic with this hook, simplifying state management and enhancing modularity. Additionally, the package.json files for both the dashboard and landing applications are updated to include the new dependency. Configuration files for TypeScript and PostCSS are also introduced or modified to support the new UI library.

Changes

File Path Change Summary
apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/menu.tsx Introduced useCopyToClipboard hook for clipboard functionality; modified onClick handler for the "Copy identifier" menu item.
apps/dashboard/components/dashboard/copy-button.tsx Removed manual clipboard handling; integrated useCopyToClipboard hook for state management and clipboard operations.
apps/dashboard/package.json Added dependency @unkey/ui with version "workspace:^".
apps/www/components/copy-button.tsx Replaced clipboard logic with useCopyToClipboard; simplified state management and onClick handler.
apps/www/components/ui/copy-code-button.tsx Refactored to use useCopyToClipboard for clipboard operations; removed manual state management.
apps/www/package.json Added dependency @unkey/ui with version "workspace:^".
internal/tsconfig/react-library.json Updated TypeScript configuration: added "DOM" to lib, changed target to "ES2022", and added noEmit.
internal/ui/README.md Added README for @unkey/ui library with installation instructions.
internal/ui/package.json Created new package.json for @unkey/ui, specifying metadata and dependencies.
internal/ui/postcss.config.js Introduced PostCSS configuration with Tailwind CSS and Autoprefixer plugins.
internal/ui/src/hooks/index.ts Re-exported all exports from use-copy-to-clipboard module.
internal/ui/src/hooks/use-copy-to-clipboard.tsx Added useCopyToClipboard hook for managing clipboard operations and state.
internal/ui/src/index.ts Re-exported all entities from ./hooks.
internal/ui/tsconfig.json Created new TypeScript configuration extending from a base config.
knip.ts Added workspace configuration for "internal/ui" with entry point src/index.ts.
packages/nextjs/package.json Reformatted keywords and files fields for consistency.

Possibly related PRs

Suggested labels

Feature, Documentation, 🕹️ oss.gg, :joystick: 300 points, hacktoberfest

Suggested reviewers

  • mcstepp
  • perkinsjr
  • MichaelUnkey
  • chronark

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

vercel bot commented Nov 13, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
engineering ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 13, 2024 4:12pm

Copy link
Contributor

github-actions bot commented Nov 13, 2024

Thank you for following the naming conventions for pull request titles! 🙏

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (8)
apps/dashboard/components/dashboard/copy-button.tsx (1)

15-15: Consider making the timeout configurable.

The timeout value (2000ms) could be made configurable through props to allow different components to customize the duration of the "copied" state.

 interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
   value: string;
   src?: string;
+  copyTimeout?: number;
 }

-export function CopyButton({ value, className, src, ...props }: CopyButtonProps) {
-  const [copied, copyToClipboard] = useCopyToClipboard(2000);
+export function CopyButton({ value, className, src, copyTimeout = 2000, ...props }: CopyButtonProps) {
+  const [copied, copyToClipboard] = useCopyToClipboard(copyTimeout);
apps/www/components/copy-button.tsx (1)

12-12: Consider making the timeout configurable via props.

While 2000ms is a reasonable default, different use cases might benefit from custom timeout durations.

Consider this enhancement:

 interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
   value: string;
   src?: string;
+  timeout?: number;
 }

-export function CopyButton({ value, className, src, children, ...props }: CopyButtonProps) {
-  const [copied, copyToClipboard] = useCopyToClipboard(2000);
+export function CopyButton({ value, className, src, children, timeout = 2000, ...props }: CopyButtonProps) {
+  const [copied, copyToClipboard] = useCopyToClipboard(timeout);
knip.ts (1)

40-42: Consider adding test files to entry points.

For consistency with other internal packages like "internal/billing" and "internal/keys", consider including test files in the entry points.

 "internal/ui": {
-  entry: "src/index.ts",
+  entry: ["src/index.ts", "**/*.test.ts"],
 },
internal/ui/src/hooks/use-copy-to-clipboard.tsx (2)

16-36: Enhance error handling and state management

The current implementation could be improved in several ways:

  1. Expose error state to consumers
  2. Add proper type guards for ClipboardItem
  3. Handle specific error types differently

Consider this enhanced implementation:

 export const useCopyToClipboard = (
   timeout = 3000,
-): [boolean, (value: string | ClipboardItem) => Promise<void>] => {
+): [boolean, (value: string | ClipboardItem) => Promise<void>, Error | null] => {
   // ... existing code ...
   const [copied, setCopied] = useState(false);
+  const [error, setError] = useState<Error | null>(null);

   const copyToClipboard = useCallback(
     async (value: string | ClipboardItem) => {
       clearTimer();
+      setError(null);
       try {
         if (typeof value === "string") {
           await navigator.clipboard.writeText(value);
-        } else if (value instanceof ClipboardItem) {
+        } else if (
+          typeof ClipboardItem !== 'undefined' && 
+          value instanceof ClipboardItem
+        ) {
           await navigator.clipboard.write([value]);
+        } else {
+          throw new TypeError('Invalid value type provided to copyToClipboard');
         }
         setCopied(true);

         if (Number.isFinite(timeout) && timeout >= 0) {
           timer.current = setTimeout(() => setCopied(false), timeout);
         }
       } catch (error) {
-        console.error("Failed to copy: ", error);
+        const errorMessage = error instanceof Error ? 
+          error.message : 'Failed to copy to clipboard';
+        setError(new Error(errorMessage));
+        setCopied(false);
       }
     },
     [timeout],
   );

   // ... existing code ...

-  return [copied, copyToClipboard];
+  return [copied, copyToClipboard, error];

43-44: Add JSDoc documentation for better developer experience

Consider adding comprehensive JSDoc documentation to improve the hook's usability.

Add this documentation above the hook:

+/**
+ * React hook for copying text or ClipboardItem to clipboard with timeout functionality
+ * @param timeout - Duration in milliseconds to show the copied state (default: 3000ms)
+ * @returns A tuple containing:
+ *  - copied: Boolean indicating if content was recently copied
+ *  - copyToClipboard: Function to copy content to clipboard
+ *  - error: Error object if the copy operation fails, null otherwise
+ * @example
+ * ```tsx
+ * const [copied, copy, error] = useCopyToClipboard(2000);
+ * return (
+ *   <button onClick={() => copy("Hello World")}>
+ *     {copied ? "Copied!" : "Copy"}
+ *   </button>
+ * );
+ * ```
+ */
 export const useCopyToClipboard = (
apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/menu.tsx (1)

12-12: Consider utilizing the copy state for better UX

The useCopyToClipboard hook likely provides a state value (first destructured value) that could be used to enhance the UI feedback. Consider using it to show loading/success states directly in the menu item.

- const [, copyToClipboard] = useCopyToClipboard();
+ const [copyState, copyToClipboard] = useCopyToClipboard();

Then update the menu item to reflect the state:

 <DropdownMenuItem
+  disabled={copyState.copying}
   onClick={() => {
     copyToClipboard(identifier).then(() =>

Also applies to: 22-22

apps/www/components/ui/copy-code-button.tsx (2)

16-16: Consider adding error handling for clipboard operations.

While the implementation is clean, clipboard operations can fail due to permissions or browser security policies. Consider handling potential errors to provide feedback to users when copying fails.

-      onClick={() => copyToClipboard(props.textToCopy)}
+      onClick={async () => {
+        try {
+          await copyToClipboard(props.textToCopy);
+        } catch (error) {
+          // Consider showing a user-friendly error message
+          console.error('Failed to copy:', error);
+        }
+      }}

3-7: Add JSDoc documentation for the component.

Consider adding JSDoc documentation to describe the component's purpose, props, and usage examples. This would help other developers understand how to use this component effectively.

+/**
+ * A button component that copies text to clipboard with visual feedback.
+ * @param {Object} props
+ * @param {string} props.textToCopy - The text to be copied to clipboard
+ * @param {string} [props.className] - Optional CSS class for styling
+ * @example
+ * ```tsx
+ * <CopyCodeSnippetButton textToCopy="npm install @unkey/ui" />
+ * ```
+ */
 type Props = {
   textToCopy: string;
   className?: string;
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b174401 and c385257.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/menu.tsx (3 hunks)
  • apps/dashboard/components/dashboard/copy-button.tsx (1 hunks)
  • apps/dashboard/package.json (1 hunks)
  • apps/www/components/copy-button.tsx (2 hunks)
  • apps/www/components/ui/copy-code-button.tsx (1 hunks)
  • apps/www/package.json (1 hunks)
  • internal/tsconfig/react-library.json (1 hunks)
  • internal/ui/README.md (1 hunks)
  • internal/ui/package.json (1 hunks)
  • internal/ui/postcss.config.js (1 hunks)
  • internal/ui/src/hooks/index.ts (1 hunks)
  • internal/ui/src/hooks/use-copy-to-clipboard.tsx (1 hunks)
  • internal/ui/src/index.ts (1 hunks)
  • internal/ui/tsconfig.json (1 hunks)
  • knip.ts (1 hunks)
  • packages/nextjs/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • internal/ui/README.md
  • internal/ui/package.json
  • internal/ui/postcss.config.js
  • internal/ui/src/hooks/index.ts
  • internal/ui/src/index.ts
  • internal/ui/tsconfig.json
  • packages/nextjs/package.json
🧰 Additional context used
📓 Learnings (2)
apps/dashboard/components/dashboard/copy-button.tsx (1)
Learnt from: unrenamed
PR: unkeyed/unkey#2652
File: apps/dashboard/components/dashboard/copy-button.tsx:38-38
Timestamp: 2024-11-10T16:45:16.994Z
Learning: The `copyToClipboardWithMeta` function currently has an unused `_meta` parameter. Consider removing it or utilizing it appropriately, especially when introducing the `useCopyToClipboard` hook.
apps/www/components/copy-button.tsx (1)
Learnt from: unrenamed
PR: unkeyed/unkey#2652
File: apps/dashboard/components/dashboard/copy-button.tsx:38-38
Timestamp: 2024-11-10T16:45:16.994Z
Learning: The `copyToClipboardWithMeta` function currently has an unused `_meta` parameter. Consider removing it or utilizing it appropriately, especially when introducing the `useCopyToClipboard` hook.
🔇 Additional comments (16)
internal/tsconfig/react-library.json (3)

6-6: LGTM! DOM lib and JSX configuration are appropriate.

The addition of "DOM" to lib array is essential for clipboard functionality, and "react-jsx" transform is the recommended setting for modern React development.

Also applies to: 9-9


10-10: LGTM! noEmit setting is appropriate for library packages.

The noEmit: true setting is correct for a UI library package as it prevents TypeScript from generating output files, leaving the bundling process to handle the emissions.


8-8: Verify browser compatibility requirements for ES2022.

The target has been updated from "es6" to "ES2022", which is a significant change. While this enables modern JavaScript features, it's important to verify that this aligns with the project's browser support requirements.

✅ Verification successful

Let me try another approach to gather information about browser compatibility requirements.


Let me try one more search to find any browser compatibility related configurations or documentation.


Based on the findings from our searches, I can now provide a final response:

ES2022 target appears safe for this project's context

The change from ES6 to ES2022 appears appropriate for this React library configuration because:

  1. The codebase shows consistent use of modern ECMAScript targets across multiple packages
  2. This is a React library configuration, which typically gets bundled/transpiled by the consuming application
  3. No explicit browser compatibility constraints were found in browserslist configs or documentation
  4. Other TypeScript configurations in the project already use modern targets like "ESNext"

The target will be processed by the consumer's build pipeline, allowing them to apply their own browser compatibility requirements during final bundling.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check browser support requirements and current configurations

# Check for browserslist configuration
echo "Checking browserslist configuration..."
fd -t f -e json package.json --exec jq -r '.browserslist // empty'
fd -t f -e json .browserslistrc --exec cat

# Check for any existing polyfills or transpilation settings
echo "Checking for babel configuration..."
fd -t f -e json -e js babel.config --exec cat
fd -t f -e json .babelrc --exec cat

# Look for any documented browser support requirements
echo "Checking documentation for browser support..."
fd -t f README.md --exec grep -i -A 5 "browser.*support" || true

Length of output: 534


Script:

#!/bin/bash
# Check for any TypeScript configuration files that might define compilation targets
echo "Checking all TypeScript configurations..."
fd -t f -e json tsconfig --exec cat {}

# Look for any dependencies that might indicate browser support requirements
echo "Checking package.json for relevant dependencies..."
fd -t f package.json --exec jq -r '.dependencies, .devDependencies'

# Search for any comments or documentation about browser support in source files
echo "Searching for browser support comments in source..."
rg -i "browser.*(support|compat)" --type ts --type tsx

Length of output: 196851


Script:

#!/bin/bash
# Check for browser targets in package.json and any .browserslistrc files
echo "Checking for browserslist configuration..."
fd -t f -e json package.json --exec jq -r '.browserslist, .engines'

# Look for any documentation about browser support in README or docs
echo "Checking documentation..."
rg -i "browser|es202[0-9]|ecmascript" -t md

# Check for any build tool configurations that might specify browser targets
echo "Checking build configurations..."
fd -t f -e js -e json -e yaml -e yml -g "*config*" --exec grep -l -i "browser\|target\|es202[0-9]\|ecmascript"

Length of output: 2214

apps/dashboard/components/dashboard/copy-button.tsx (3)

3-3: LGTM! Clean import changes.

Good optimization by using type-only import for React and introducing the new hook from @unkey/ui.

Also applies to: 6-6


21-21: LGTM! Clean event handler implementation.

The onClick handler is concise and properly utilizes the hook's functionality.


Line range hint 13-27: Verify CopyButton usage across the codebase.

Since this is a shared component with modified behavior, let's verify its usage to ensure all consumers are compatible with the new implementation.

✅ Verification successful

CopyButton implementation is consistent across usages

The verification shows that:

  • Both implementations (dashboard and www) have identical interfaces and behavior
  • All usages only pass the required value prop and optional className/src
  • No consumers rely on custom clipboard behavior or additional props
  • The component maintains consistent functionality using useCopyToClipboard hook with a 2000ms timeout

The modified behavior is compatible with all current usage patterns in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of CopyButton component to verify compatibility

# Search for CopyButton imports and usages
echo "Searching for CopyButton imports and usages..."
rg -l "CopyButton" --type tsx

# Search for specific prop patterns that might need updates
echo "Checking for any custom clipboard-related props or handlers..."
rg "CopyButton.*onCopy" --type tsx
rg "CopyButton.*clipboard" --type tsx

Length of output: 428


Script:

#!/bin/bash
# Let's try without the file type restriction since tsx isn't recognized
echo "Searching for CopyButton imports and usages..."
rg -l "CopyButton"

echo "\nChecking for specific prop patterns..."
rg "CopyButton.*onCopy"
rg "CopyButton.*clipboard"

# Also search for the component definition to understand its interface
echo "\nSearching for CopyButton component definition..."
rg "interface CopyButtonProps|type CopyButtonProps"

Length of output: 2017


Script:

#!/bin/bash
# Let's examine both CopyButton implementations and their interfaces
echo "Examining CopyButton in dashboard:"
rg -A 10 "interface CopyButtonProps" apps/dashboard/components/dashboard/copy-button.tsx

echo "\nExamining CopyButton in www:"
rg -A 10 "interface CopyButtonProps" apps/www/components/copy-button.tsx

# Let's also check actual usage patterns
echo "\nChecking usage patterns in a few files:"
rg "CopyButton" apps/dashboard/app/new/keys.tsx -A 2
rg "CopyButton" apps/www/components/template/codeblock.tsx -A 2

Length of output: 2036

apps/www/components/copy-button.tsx (2)

3-3: LGTM! Import aligns with the PR objectives.

The addition of useCopyToClipboard from @unkey/ui supports the goal of centralizing UI utilities.


22-22: Verify removal of metadata tracking.

The simplified clipboard operation removes the previous metadata parameter. While this aligns with the previous learning about the unused _meta parameter, let's verify that no analytics or tracking was dependent on this metadata.

knip.ts (1)

40-42: LGTM! Configuration follows established patterns.

The workspace configuration for "internal/ui" follows the same structure as other internal packages, using "src/index.ts" as the entry point.

internal/ui/src/hooks/use-copy-to-clipboard.tsx (2)

9-14: LGTM!

The timer cleanup implementation is correct and follows best practices.


38-41: LGTM!

The cleanup effect is properly implemented and follows React best practices.

apps/www/package.json (2)

30-30: LGTM! Dependency addition looks correct.

The @unkey/ui package is correctly added with workspace:^ version specification, which is appropriate for monorepo internal packages.


30-30: Verify package configuration.

Let's ensure the new UI package is properly configured:

✅ Verification successful

Package configuration is correctly set up

The @unkey/ui package is properly configured in internal/ui with:

  • Correct package name and workspace configuration
  • Required peer dependencies (next, react, react-dom)
  • TypeScript support with proper entry points (src/index.ts)
  • Appropriate dev dependencies and build tools
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify @unkey/ui package configuration

# Check if package.json exists for @unkey/ui
if [ -f "packages/ui/package.json" ]; then
  echo "Checking @unkey/ui package.json..."
  
  # Extract and display peer dependencies
  jq '.peerDependencies // empty' packages/ui/package.json
  
  # Check for TypeScript configuration
  if [ -f "packages/ui/tsconfig.json" ]; then
    echo "TypeScript configuration exists"
    cat packages/ui/tsconfig.json
  fi
  
  # Check for type definitions
  find packages/ui -name "*.d.ts" -o -name "index.ts" -o -name "*.tsx"
else
  echo "Error: @unkey/ui package.json not found"
  exit 1
fi

Length of output: 135


Script:

#!/bin/bash
# Let's first check the actual location of the UI package
fd -t d "ui$" .

# Then check package.json files in any UI-related directories
fd -t f "package.json" . | grep -i "ui"

# List all workspace packages to understand the structure
cat pnpm-workspace.yaml 2>/dev/null || cat package.json

Length of output: 280


Script:

#!/bin/bash
# Let's check the package.json in internal/ui since that seems to be the correct location
cat internal/ui/package.json

# Also check if there are any TypeScript configurations
find internal/ui -name "*.d.ts" -o -name "index.ts" -o -name "*.tsx"

# Check the package name to ensure it matches @unkey/ui
jq '.name' internal/ui/package.json

Length of output: 1236

apps/dashboard/package.json (2)

58-58: LGTM! Dependency addition follows workspace conventions.

The @unkey/ui package is correctly added with the workspace version specifier, consistent with other internal packages.


58-58: Verify package configuration and build impact.

Since this is a new UI package, let's verify its configuration and impact on the build.

✅ Verification successful

The previous attempt failed because we assumed the wrong directory structure. Let's first locate the UI package directory and then analyze its configuration.


UI package configuration is properly set up and used across the workspace

The UI package is well-configured with:

  • Proper peer dependencies for Next.js and React
  • TypeScript and build tooling setup
  • Clear exports through src/index.ts
  • Currently used by dashboard and www apps
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify package configuration and build impact

# Check if package.json exists for the new UI package
if [ -f "packages/ui/package.json" ]; then
  echo "Checking UI package configuration..."
  
  # Check for React peer dependencies
  cat packages/ui/package.json | jq '.peerDependencies'
  
  # Check package size
  cat packages/ui/package.json | jq '.files'
  
  # Check exports configuration
  cat packages/ui/package.json | jq '.exports'
fi

# List all files in the UI package to understand its scope
fd . "packages/ui" -t f

Length of output: 160


Script:

#!/bin/bash
# Let's check if the package is under a different directory structure
fd -t d -d 1 ui

# Once we find the correct location, let's examine its package.json
fd package.json -t f | grep -i ui

Length of output: 78


Script:

#!/bin/bash
# Now that we found the correct path, let's analyze the package configuration
echo "=== UI Package Configuration ==="
cat internal/ui/package.json | jq '.'

echo -e "\n=== UI Package Structure ==="
fd . internal/ui -t f

echo -e "\n=== Workspace Dependencies ==="
# Check other packages that depend on @unkey/ui
fd package.json -t f -x grep -l "@unkey/ui"

Length of output: 1615

apps/www/components/ui/copy-code-button.tsx (1)

1-1: LGTM! Clean implementation using the new UI package.

The migration to useCopyToClipboard hook from @unkey/ui effectively simplifies the component by removing the need for local state management and timeout effects.

Also applies to: 9-9

Comment on lines 3 to 8
export const useCopyToClipboard = (
timeout = 3000,
): [boolean, (value: string | ClipboardItem) => Promise<void>] => {
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const [copied, setCopied] = useState(false);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add browser compatibility check for Clipboard API

The hook should verify browser support for the Clipboard API before attempting to use it. This would prevent runtime errors in unsupported browsers.

Consider adding this check at the beginning of the hook:

 export const useCopyToClipboard = (
   timeout = 3000,
 ): [boolean, (value: string | ClipboardItem) => Promise<void>] => {
+  const isClipboardAvailable = typeof navigator !== 'undefined' && 
+    navigator.clipboard !== undefined;
   const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
-  const [copied, setCopied] = useState(false);
+  const [copied, setCopied] = useState<boolean>(false);
+  const [error, setError] = useState<Error | null>(null);
+
+  if (!isClipboardAvailable) {
+    console.warn('Clipboard API is not supported in this browser');
+  }
📝 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.

Suggested change
export const useCopyToClipboard = (
timeout = 3000,
): [boolean, (value: string | ClipboardItem) => Promise<void>] => {
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const [copied, setCopied] = useState(false);
export const useCopyToClipboard = (
timeout = 3000,
): [boolean, (value: string | ClipboardItem) => Promise<void>] => {
const isClipboardAvailable = typeof navigator !== 'undefined' &&
navigator.clipboard !== undefined;
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const [copied, setCopied] = useState<boolean>(false);
const [error, setError] = useState<Error | null>(null);
if (!isClipboardAvailable) {
console.warn('Clipboard API is not supported in this browser');
}

Comment on lines 43 to 47
copyToClipboard(identifier).then(() =>
toast.success("Copied to clipboard", {
description: identifier,
}),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling and improve user feedback

The current implementation lacks error handling for clipboard operations which could fail due to permissions or other issues. Also, the success message could be more specific.

Apply this improvement:

-copyToClipboard(identifier).then(() =>
-  toast.success("Copied to clipboard", {
-    description: identifier,
-  }),
-);
+copyToClipboard(identifier)
+  .then(() =>
+    toast.success("Identifier copied to clipboard", {
+      description: identifier,
+    })
+  )
+  .catch((error) =>
+    toast.error("Failed to copy to clipboard", {
+      description: error.message,
+    })
+  );
📝 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.

Suggested change
copyToClipboard(identifier).then(() =>
toast.success("Copied to clipboard", {
description: identifier,
}),
);
copyToClipboard(identifier)
.then(() =>
toast.success("Identifier copied to clipboard", {
description: identifier,
})
)
.catch((error) =>
toast.error("Failed to copy to clipboard", {
description: error.message,
})
);

@unrenamed
Copy link
Contributor Author

rn, the @unkey/ui package lacks a TailwindCSS configuration, which would typically be essential for a UI package. To make our Tailwind configuration scalable and consistent across all web applications, a unified base configuration could be created and registered as a standalone internal package, similar to how tsconfig is managed. Any Tailwind configurations, including @unkey/ui, could then extend this base setup.

This update could be implemented in a few ways:

  1. implement now (separate PR): create the base Tailwind configuration and update all relevant packages right away.
  2. delay until @unkey/ui needs Tailwind: postpone adding Tailwind until @unkey/ui evolves to require it, such as when exporting atomic components like buttons or inputs.

- add browser compatibility check for Clipboard API
- propagate copy to clipboard error for higher-level handling
- add error handling and improve user feedback to ratelimits logs menu
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
internal/ui/src/hooks/use-copy-to-clipboard.tsx (3)

11-16: Consider a more concise implementation of clearTimer.

The function can be simplified while maintaining the same functionality.

-  const clearTimer = () => {
-    if (timer.current) {
-      clearTimeout(timer.current);
-      timer.current = null;
-    }
-  };
+  const clearTimer = () => {
+    timer.current && clearTimeout(timer.current);
+    timer.current = null;
+  };

33-40: Optimize timeout handling logic.

The current implementation can be more efficient by avoiding duplicate setTimeout calls.

   const handleTimeout = () => {
+    const timeoutValue = Number.isFinite(timeout) && timeout >= 0
+      ? timeout
+      : DEFAULT_TIMEOUT;
+    
+    if (timeoutValue !== timeout) {
+      console.warn(`Invalid timeout value; defaulting to ${DEFAULT_TIMEOUT}ms`);
+    }
+    
+    timer.current = setTimeout(() => setCopied(false), timeoutValue);
-    if (Number.isFinite(timeout) && timeout >= 0) {
-      timer.current = setTimeout(() => setCopied(false), timeout);
-    } else {
-      console.warn(`Invalid timeout value; defaulting to ${DEFAULT_TIMEOUT}ms`);
-      timer.current = setTimeout(() => setCopied(false), DEFAULT_TIMEOUT);
-    }
   };

42-55: Enhance error handling with more specific error messages.

The current error logging could be more informative for debugging purposes.

       } catch (error) {
-        console.warn("Failed to copy to clipboard. ", error);
+        const errorMessage = error instanceof Error 
+          ? error.message 
+          : 'Unknown error occurred';
+        console.warn(`Failed to copy to clipboard: ${errorMessage}`);
         throw error; // Propagate error for higher-level handling
       }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c385257 and 26055c5.

📒 Files selected for processing (2)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/menu.tsx (3 hunks)
  • internal/ui/src/hooks/use-copy-to-clipboard.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/menu.tsx
🔇 Additional comments (4)
internal/ui/src/hooks/use-copy-to-clipboard.tsx (4)

1-9: LGTM! Well-structured hook signature and state management.

The hook's interface is clear and type-safe, with appropriate default values and state management setup.


18-31: LGTM! Robust clipboard operation implementation.

The implementation includes proper browser compatibility checks and type handling as suggested in the previous review.


57-60: LGTM! Proper cleanup implementation.

The cleanup effect is well-implemented to prevent memory leaks.


62-63: LGTM! Clear and type-safe return value.

The return statement correctly matches the hook's type signature.

"typescript": "^5.5.3"
},
"dependencies": {},
"author": "Nazar Poshtarenko <[email protected]>",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

btw, not sure about this line. let me know if that’s okay or if you prefer it limited to core contributors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and since it's an internal package, one may find this info excessive. I'm ok with refining this file to keep it clean and consistent w/ other packages

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.

1 participant