Skip to content

Conversation

BharathxD
Copy link
Contributor

@BharathxD BharathxD commented May 29, 2025

Problem

The import modals for Rebrandly and Short.io had several UX issues:

fix-import-modal-api-validation.mov
  • API key inputs accepted any text without proper validation patterns
  • Invalid API keys showed confusing dual toasts ("Invalid API key" + "Successfully added API key")
  • Continuous polling after errors caused layout shifts and poor UX
  • Generic error messages didn't provide helpful feedback to users

Solution

This PR addresses these issues with comprehensive improvements:

Backend Changes

  • ✅ Added server-side API key validation before storing in Redis
  • ✅ Validate API keys by testing against provider APIs
  • ✅ Return proper error responses with specific messages

Frontend Changes

  • ✅ Added input pattern validation with proper min/max lengths
  • ✅ Implemented error state tracking to prevent continuous loading
  • ✅ Fixed duplicate toast notifications
  • ✅ Show specific error messages from backend validation
  • ✅ Improved loading state logic to reduce layout shifts

Validation Patterns

  • Rebrandly: 32-character hexadecimal pattern [0-9a-f]{32}
  • Short.io: 19-character pattern sk_[A-Za-z0-9]{16}

Testing

  • Invalid API keys now show single, clear error message
  • Valid API keys save successfully without duplicate toasts
  • Input validation prevents submission of malformed keys
  • Reduced layout shifts in loading states

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Summary by CodeRabbit

  • New Features
    • Added real-time validation for Rebrandly and Short.io API keys before saving, ensuring only valid keys are accepted.
  • Enhancements
    • Improved error handling in API key submission forms with more specific error messages.
    • Updated input fields for API keys with stricter validation rules and helpful tooltips to guide users.
    • Refined loading indicators to prevent repeated loading states after errors.

BharathxD and others added 2 commits May 29, 2025 14:19
- Add server-side API key validation for Rebrandly and Short.io
- Implement proper input patterns and length validation
- Fix duplicate toast notifications on validation errors
- Prevent continuous polling after errors to reduce layout shifts
- Show specific error messages from backend validation
Copy link
Contributor

vercel bot commented May 29, 2025

@BharathxD is attempting to deploy a commit to the Dub Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented May 29, 2025

Walkthrough

Validation of API keys for Rebrandly and Short.io is now performed before saving them. This is achieved by sending a verification request to the respective external APIs. UI modals for importing these keys have improved error handling, input validation, and more informative error messages, enhancing the user experience during API key submission.

Changes

Files Change Summary
.../api/workspaces/[idOrSlug]/import/rebrandly/route.ts Added a HEAD request to Rebrandly API to validate API key before saving; throws error if invalid.
.../api/workspaces/[idOrSlug]/import/short/route.ts Added a HEAD request to Short.io API to validate API key before saving; throws error if invalid.
.../ui/modals/import-rebrandly-modal.tsx,
.../import-short-modal.tsx
Improved error state tracking, error message display, and input validation for API key submission forms.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Modal (UI)
    participant API Route
    participant External API

    User->>Modal (UI): Submit API key
    Modal (UI)->>API Route: PUT request with API key
    API Route->>External API: HEAD request to validate key
    External API-->>API Route: Response (OK or error)
    alt Key valid
        API Route->>API Route: Save key in Redis
        API Route-->>Modal (UI): Success response
        Modal (UI)-->>User: Show success
    else Key invalid
        API Route-->>Modal (UI): Error with specific message
        Modal (UI)-->>User: Show error message
    end
Loading

Suggested reviewers

  • TWilson023

Poem

A bunny hops with nimble feet,
API keys checked before they’re complete.
If you mistype, don’t you fret—
The UI now tells you what you get!
With validations strong and clear,
Your imports are safe, so hop and cheer!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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
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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d13af21 and 70d20e6.

📒 Files selected for processing (4)
  • apps/web/app/api/workspaces/[idOrSlug]/import/rebrandly/route.ts (1 hunks)
  • apps/web/app/api/workspaces/[idOrSlug]/import/short/route.ts (1 hunks)
  • apps/web/ui/modals/import-rebrandly-modal.tsx (6 hunks)
  • apps/web/ui/modals/import-short-modal.tsx (7 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (8)
apps/web/ui/modals/import-rebrandly-modal.tsx (4)

41-41: LGTM: Error state tracking improves UX.

Good addition of error state tracking to prevent continuous loading indicators after errors.


95-96: LGTM: Improved loading logic prevents UI flicker.

The conditional loading logic effectively prevents repeated loading states after errors, improving the user experience.


239-241: LGTM: Enhanced error message extraction.

Good improvement to extract specific error messages from the API response instead of showing generic error messages.


268-271: LGTM: Proper input validation constraints.

The validation constraints (32-character hexadecimal pattern) align perfectly with Rebrandly's API key format and the backend validation logic.

apps/web/ui/modals/import-short-modal.tsx (4)

42-42: LGTM: Consistent error state tracking.

The error state tracking implementation is consistent with the Rebrandly modal and improves the user experience.


90-91: LGTM: Improved loading state management.

The loading logic correctly prevents repeated loading indicators after errors, consistent with the Rebrandly implementation.


249-250: LGTM: Enhanced error messaging.

Good improvement to display specific error messages from the backend validation instead of generic error toasts.


278-281: LGTM: Accurate input validation for Short.io format.

The validation constraints correctly enforce the Short.io API key format (sk_ followed by 16 alphanumeric characters) and align with the backend validation.

Comment on lines +82 to +97
const isValidApiKeyResponse = await fetch(
"https://api.rebrandly.com/v1/account",
{
method: "HEAD",
headers: {
"Content-Type": "application/json",
apikey: apiKey,
},
},
);
if (!isValidApiKeyResponse.ok) {
throw new DubApiError({
code: "bad_request",
message: "Invalid Rebrandly API key",
});
}
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 error handling for network failures and timeouts.

The API key validation logic is correct, but it lacks proper error handling for network failures, timeouts, or API service unavailability.

Consider wrapping the fetch in a try-catch block and adding a timeout:

-  const isValidApiKeyResponse = await fetch(
-    "https://api.rebrandly.com/v1/account",
-    {
-      method: "HEAD",
-      headers: {
-        "Content-Type": "application/json",
-        apikey: apiKey,
-      },
-    },
-  );
-  if (!isValidApiKeyResponse.ok) {
-    throw new DubApiError({
-      code: "bad_request",
-      message: "Invalid Rebrandly API key",
-    });
-  }
+  try {
+    const isValidApiKeyResponse = await fetch(
+      "https://api.rebrandly.com/v1/account",
+      {
+        method: "HEAD",
+        headers: {
+          "Content-Type": "application/json",
+          apikey: apiKey,
+        },
+        signal: AbortSignal.timeout(10000), // 10 second timeout
+      },
+    );
+    if (!isValidApiKeyResponse.ok) {
+      throw new DubApiError({
+        code: "bad_request",
+        message: "Invalid Rebrandly API key",
+      });
+    }
+  } catch (error) {
+    if (error instanceof DubApiError) {
+      throw error;
+    }
+    throw new DubApiError({
+      code: "bad_request",
+      message: "Unable to validate Rebrandly API key. Please try again.",
+    });
+  }
📝 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
const isValidApiKeyResponse = await fetch(
"https://api.rebrandly.com/v1/account",
{
method: "HEAD",
headers: {
"Content-Type": "application/json",
apikey: apiKey,
},
},
);
if (!isValidApiKeyResponse.ok) {
throw new DubApiError({
code: "bad_request",
message: "Invalid Rebrandly API key",
});
}
try {
const isValidApiKeyResponse = await fetch(
"https://api.rebrandly.com/v1/account",
{
method: "HEAD",
headers: {
"Content-Type": "application/json",
apikey: apiKey,
},
signal: AbortSignal.timeout(10000), // 10 second timeout
},
);
if (!isValidApiKeyResponse.ok) {
throw new DubApiError({
code: "bad_request",
message: "Invalid Rebrandly API key",
});
}
} catch (error) {
if (error instanceof DubApiError) {
throw error;
}
throw new DubApiError({
code: "bad_request",
message: "Unable to validate Rebrandly API key. Please try again.",
});
}
🤖 Prompt for AI Agents
In apps/web/app/api/workspaces/[idOrSlug]/import/rebrandly/route.ts around lines
82 to 97, the fetch call validating the Rebrandly API key lacks error handling
for network failures, timeouts, or service unavailability. Wrap the fetch call
in a try-catch block to catch exceptions from network errors, and implement a
timeout mechanism to abort the request if it takes too long. In the catch block,
throw a DubApiError with an appropriate message indicating the network or
timeout issue.

Comment on lines +69 to +84
const isValidApiKeyResponse = await fetch(
"https://api.short.io/api/domains?limit=1&offset=0",
{
method: "HEAD",
headers: {
"Content-Type": "application/json",
Authorization: apiKey,
},
},
);
if (!isValidApiKeyResponse.ok) {
throw new DubApiError({
code: "bad_request",
message: "Invalid Short.io API key",
});
}
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 consistent error handling for network failures.

The validation logic mirrors the Rebrandly implementation but has the same network error handling limitations. Consider applying the same timeout and error handling improvements suggested for the Rebrandly route.

Apply similar error handling as recommended for Rebrandly:

-  const isValidApiKeyResponse = await fetch(
-    "https://api.short.io/api/domains?limit=1&offset=0",
-    {
-      method: "HEAD",
-      headers: {
-        "Content-Type": "application/json",
-        Authorization: apiKey,
-      },
-    },
-  );
-  if (!isValidApiKeyResponse.ok) {
-    throw new DubApiError({
-      code: "bad_request",
-      message: "Invalid Short.io API key",
-    });
-  }
+  try {
+    const isValidApiKeyResponse = await fetch(
+      "https://api.short.io/api/domains?limit=1&offset=0",
+      {
+        method: "HEAD",
+        headers: {
+          "Content-Type": "application/json",
+          Authorization: apiKey,
+        },
+        signal: AbortSignal.timeout(10000), // 10 second timeout
+      },
+    );
+    if (!isValidApiKeyResponse.ok) {
+      throw new DubApiError({
+        code: "bad_request",
+        message: "Invalid Short.io API key",
+      });
+    }
+  } catch (error) {
+    if (error instanceof DubApiError) {
+      throw error;
+    }
+    throw new DubApiError({
+      code: "bad_request",
+      message: "Unable to validate Short.io API key. Please try again.",
+    });
+  }
📝 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
const isValidApiKeyResponse = await fetch(
"https://api.short.io/api/domains?limit=1&offset=0",
{
method: "HEAD",
headers: {
"Content-Type": "application/json",
Authorization: apiKey,
},
},
);
if (!isValidApiKeyResponse.ok) {
throw new DubApiError({
code: "bad_request",
message: "Invalid Short.io API key",
});
}
try {
const isValidApiKeyResponse = await fetch(
"https://api.short.io/api/domains?limit=1&offset=0",
{
method: "HEAD",
headers: {
"Content-Type": "application/json",
Authorization: apiKey,
},
signal: AbortSignal.timeout(10000), // 10 second timeout
},
);
if (!isValidApiKeyResponse.ok) {
throw new DubApiError({
code: "bad_request",
message: "Invalid Short.io API key",
});
}
} catch (error) {
if (error instanceof DubApiError) {
throw error;
}
throw new DubApiError({
code: "bad_request",
message: "Unable to validate Short.io API key. Please try again.",
});
}
🤖 Prompt for AI Agents
In apps/web/app/api/workspaces/[idOrSlug]/import/short/route.ts around lines 69
to 84, the fetch call validating the Short.io API key lacks consistent error
handling for network failures and timeouts. To fix this, wrap the fetch call in
a try-catch block to handle exceptions, implement a timeout mechanism to abort
the request if it takes too long, and throw a meaningful error if the network
request fails or times out, similar to the approach used in the Rebrandly route.

@steven-tey steven-tey force-pushed the main branch 2 times, most recently from ea17ddf to f7d87c3 Compare August 24, 2025 20:37
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