Skip to content

Handle server action failure better#243

Merged
elie222 merged 3 commits intomainfrom
server-action-instrumentation
Oct 11, 2024
Merged

Handle server action failure better#243
elie222 merged 3 commits intomainfrom
server-action-instrumentation

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Oct 11, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new action for deactivating API keys, enhancing user control over API access.
    • Added a utility function for managing asynchronous action calls, improving error handling and user feedback.
  • User Interface Changes

    • Refined success message construction for better clarity and user understanding.
  • Bug Fixes

    • Enhanced error handling for license activation, providing more informative feedback to users.
  • Refactor

    • Updated multiple action functions to include instrumentation for improved monitoring and error handling.
  • Chores

    • Streamlined the codebase by removing outdated utility functions related to error handling.

@vercel
Copy link

vercel bot commented Oct 11, 2024

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

Name Status Preview Updated (UTC)
inbox-zero ✅ Ready (Inspect) Visit Preview Oct 11, 2024 8:55pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 11, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces significant changes to the TypeScript codebase, primarily focusing on server action handling. A new action, deactivateApiKeyAction, replaces processHistoryAction, enhancing tracking through withActionInstrumentation. The validation logic is updated to utilize deactivateApiKeyBody.safeParse. Other modifications include adjustments in various components and utility functions to improve error handling and user experience, particularly in email categorization and admin controls. Additionally, many functions are refactored to utilize the instrumentation middleware, enhancing monitoring capabilities across the application.

Changes

File Path Change Summary
apps/web/utils/actions/premium.ts Refactored several premium-related functions to use withActionInstrumentation.
apps/web/utils/actions/admin.ts Refactored adminProcessHistoryAction to use withActionInstrumentation, changing the parameter structure to object destructuring.
apps/web/utils/actions/categorize.ts Modified categorizeAction to return only the category property from the response.
apps/web/utils/actions/error-messages.ts Refactored clearUserErrorMessagesAction to use withActionInstrumentation.
apps/web/utils/actions/group.ts Introduced multiple new group-related action functions, all wrapped with withActionInstrumentation.
apps/web/utils/actions/middleware.ts Updated withActionInstrumentation for better type safety and error handling, introducing a new utility type EnsureObject<T>.
apps/web/utils/actions/mail.ts Refactored Gmail action functions to utilize withActionInstrumentation, removing executeGmailAction.
apps/web/utils/server-action.ts Added new function handleActionCall to manage action execution and error handling.
apps/web/app/(app)/automation/RulesPrompt.tsx Introduced handleActionCall for improved error handling in RulesPromptForm submission.
apps/web/app/(app)/admin/AdminUserControls.tsx Updated adminProcessHistoryAction call to pass an object containing emailAddress instead of a string.
apps/web/app/(landing)/components/page.tsx Wrapped TestErrorButton and TestActionButton in a div for better layout.
apps/web/components/email-list/EmailList.tsx Enhanced error handling in onAiCategorize function to manage categorization errors more effectively.
apps/web/utils/actions/helpers.ts Removed isStatusOk and handleError utility functions.
apps/web/utils/error.ts Updated ActionError and ServerActionResponse type definitions for enhanced flexibility.
apps/web/utils/gmail/filter.ts Modified createAutoArchiveFilter to destructure parameters and added error handling for filter creation.

Possibly related PRs

"In the burrows deep and wide,
Actions change with every tide.
From keys that deactivate,
To buttons that communicate.
With every hop and every dash,
We refine our code in a flash! 🐇✨"


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.

@elie222
Copy link
Owner Author

elie222 commented Oct 11, 2024

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: 30

🧹 Outside diff range and nitpick comments (19)
apps/web/utils/actions/error-messages.ts (3)

8-10: LGTM: Function refactored to use withActionInstrumentation.

The refactoring improves server action handling as per the PR objective. The function name is correctly passed as a string to withActionInstrumentation.

Consider using a constant for the action name to avoid potential typos and improve maintainability:

const ACTION_NAME = "clearUserErrorMessages";
export const clearUserErrorMessagesAction = withActionInstrumentation(
  ACTION_NAME,
  async () => {
    // ...
  }
);

11-15: LGTM: Function body logic remains correct.

The function body correctly handles session checking, error message clearing, and path revalidation.

Consider enhancing error handling by returning a more specific error message:

if (!session?.user) return { error: "User not authenticated" };

Also, consider adding a try-catch block to handle potential errors from clearUserErrorMessages and revalidatePath:

try {
  await clearUserErrorMessages(session.user.id);
  revalidatePath("/(app)", "layout");
  return { success: true };
} catch (error) {
  console.error("Error clearing user error messages:", error);
  return { error: "Failed to clear error messages" };
}

Line range hint 1-16: Summary: Improved server action handling for clearUserErrorMessagesAction

The refactoring of clearUserErrorMessagesAction to use withActionInstrumentation aligns well with the PR objective of improving server action handling. This change likely enhances logging and error tracking capabilities while maintaining the original functionality.

Key improvements:

  1. Added instrumentation to the action
  2. Maintained existing logic for clearing user error messages
  3. Preserved error handling and path revalidation

The changes are well-implemented and improve the overall quality of the codebase. Consider the minor suggestions provided in previous comments for further enhancements.

As you continue to refactor other actions to use withActionInstrumentation, ensure consistency across the codebase. This pattern will improve observability and error handling throughout the application.

apps/web/utils/actions/unsubscriber.ts (1)

17-30: Improved formatting and Prisma usage

The restructuring of the prisma.newsletter.upsert call enhances readability without altering the core functionality. The use of connect in the create object is a more idiomatic approach to establishing relationships in Prisma.

For consistency, consider applying the same formatting to the update object:

update: { 
  status: options.status 
},

This minor change would make the structure of all objects in the upsert call uniform.

apps/web/utils/actions/admin.ts (1)

8-36: Good use of instrumentation and improved parameter handling.

The refactoring of adminProcessHistoryAction to use withActionInstrumentation is a positive change. This likely enhances monitoring and logging capabilities for this server action. The use of object destructuring for parameters is also a good practice, making the function more flexible and easier to extend in the future.

Consider improving the error handling to align with the PR objective of "Handle server action failure better". Instead of returning simple objects for errors, you could create a custom error type or use a more structured error response. For example:

type AdminActionError = {
  code: 'NOT_LOGGED_IN' | 'NOT_ADMIN';
  message: string;
};

// In the function:
if (!userId) return { error: { code: 'NOT_LOGGED_IN', message: "Not logged in" } as AdminActionError };
if (!isAdmin(session.user.email)) return { error: { code: 'NOT_ADMIN', message: "Not admin" } as AdminActionError };

This would provide more type safety and allow for more specific error handling on the client side.

apps/web/app/(app)/ErrorMessages.tsx (1)

33-33: Approve the button text change with a minor suggestion.

The change from "Clear" to "I've fixed them" is a good improvement in terms of user experience. It makes the action more personal and implies user responsibility. However, to be more accurate and avoid potential confusion, consider a slight modification.

Consider changing the button text to "I've addressed these issues" or "Mark as resolved". This maintains the personal touch while being more precise about the action being taken.

- I've fixed them
+ I've addressed these issues
apps/web/utils/actions/categorize.ts (1)

60-60: Simplification of return value improves function focus.

The change to return only { category: res?.category } instead of the entire res object is a good improvement. It makes the function's purpose clearer and reduces the potential for misuse of other data that might have been present in the full response.

However, there are a couple of points to consider:

  1. Error handling: Ensure that calling code can still distinguish between successful categorization and errors.
  2. Nullish coalescing: Consider using nullish coalescing to provide a default value if res?.category is undefined.

Consider updating the return statement to handle potential undefined values:

-    return { category: res?.category };
+    return { category: res?.category ?? 'uncategorized' };

This ensures that the category property always has a value, which can help prevent issues in the calling code.

apps/web/utils/actions/user.ts (3)

19-30: LGTM: Refactoring improves instrumentation.

The saveAboutAction function has been successfully refactored to use withActionInstrumentation, which enhances monitoring capabilities. The core logic remains intact, ensuring that the functionality is preserved.

Consider adding a return statement at the end of the function to explicitly indicate a successful operation:

export const saveAboutAction = withActionInstrumentation(
  "saveAbout",
  async (options: SaveAboutBody) => {
    const session = await auth();
    if (!session?.user.email) return { error: "Not logged in" };

    await prisma.user.update({
      where: { email: session.user.email },
      data: { about: options.about },
    });
+   return { success: true };
  },
);

This change would make the function's success case more explicit and consistent with error handling.


32-56: LGTM: Refactoring enhances monitoring while preserving functionality.

The deleteAccountAction function has been successfully refactored to use withActionInstrumentation, improving monitoring capabilities. The core logic, including error handling and multiple asynchronous operations, remains intact.

Consider adding a return statement at the end of the function to explicitly indicate a successful operation:

export const deleteAccountAction = withActionInstrumentation(
  "deleteAccount",
  async () => {
    const session = await auth();
    if (!session?.user.email) return { error: "Not logged in" };

    try {
      await Promise.allSettled([
        // ... existing operations ...
      ]);
    } catch (error) {
      console.error("Error while deleting account: ", error);
      captureException(error, undefined, session.user.email);
    }

    await prisma.user.delete({ where: { email: session.user.email } });
+   return { success: true };
  },
);

This change would make the function's success case more explicit and consistent with error handling.


58-69: LGTM: Refactoring enhances monitoring capabilities.

The completedOnboardingAction function has been successfully refactored to use withActionInstrumentation, improving monitoring capabilities. The core logic remains intact, ensuring that the functionality is preserved.

Consider adding a return statement at the end of the function to explicitly indicate a successful operation:

export const completedOnboardingAction = withActionInstrumentation(
  "completedOnboarding",
  async () => {
    const session = await auth();
    if (!session?.user.id) return { error: "Not logged in" };

    await prisma.user.update({
      where: { id: session.user.id },
      data: { completedOnboarding: true },
    });
+   return { success: true };
  },
);

This change would make the function's success case more explicit and consistent with error handling.

apps/web/utils/error.ts (1)

34-35: Approve changes with suggestions for improvement

The updates to ActionError and ServerActionResponse type definitions enhance flexibility and type safety. However, there's room for further improvement:

  1. For ActionError<E = {}>, consider using a more specific default type or constraint for E.
  2. For ServerActionResponse<T, E = {}>, similarly, use a more specific default type or constraint for E.

Consider refactoring the type definitions as follows:

export type ActionError<E extends Record<string, unknown> = Record<string, never>> = { error: string } & E;
export type ServerActionResponse<T, E extends Record<string, unknown> = Record<string, never>> = ActionError<E> | T;

This change ensures that E is always an object type, providing better type safety while maintaining flexibility.

🧰 Tools
🪛 Biome

[error] 34-34: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 35-35: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

apps/web/app/(app)/automation/RulesPrompt.tsx (1)

99-129: Improved action handling and success message construction.

The refactoring of the onSubmit function brings several improvements:

  1. Use of handleActionCall for better action management.
  2. Consistent setting of isSubmitting state.
  3. More robust success message construction.

However, consider simplifying the error handling:

Consider flattening the promise chain to simplify error handling:

const onSubmit = useCallback(
  async (data: SaveRulesPromptBody) => {
    setIsSubmitting(true);
    
    toast.promise(
      async () => {
        const result = await handleActionCall("saveRulesPromptAction", () =>
          saveRulesPromptAction(data)
        );

        if (isActionError(result)) {
          throw new Error(result.error);
        }

        router.push("/automation?tab=rules");
        mutate();

        return result;
      },
      {
        loading: "Saving rules... This may take a while to process...",
        success: (result) => {
          setIsSubmitting(false);
          const { createdRules, editedRules, removedRules } = result || {};
          const message = [
            createdRules ? `${createdRules} rules created.` : "",
            editedRules ? `${editedRules} rules edited.` : "",
            removedRules ? `${removedRules} rules removed.` : "",
          ]
            .filter(Boolean)
            .join(" ");

          return `Rules saved successfully! ${message}`;
        },
        error: (err) => {
          setIsSubmitting(false);
          return `Error saving rules: ${err.message}`;
        },
      }
    );
  },
  [router, mutate]
);

This approach ensures that setIsSubmitting(false) is called in both success and error cases, and simplifies the promise chain.

apps/web/components/email-list/EmailList.tsx (1)

Line range hint 233-238: Approve new error checks and suggest minor improvement

The new error checks improve the robustness of the onAiCategorize function. They handle both action errors and incomplete requests, enhancing the overall error handling.

Consider removing the unnecessary else clause as both error cases will throw and exit the function. This will make the code more concise:

 if (isActionError(result)) {
   setIsCategorizing((s) => ({ ...s, [thread.id]: false }));
   throw new Error(`There was an error categorizing the email.`);
 } else if (!result) {
   throw new Error("The request did not complete");
-} else {
-  // setCategory(res);
-  refetch();
 }
+// setCategory(res);
+refetch();
🧰 Tools
🪛 Biome

[error] 232-232: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)

apps/web/utils/actions/middleware.ts (1)

2-2: Import statements should follow project conventions.

Ensure that the import order and grouping follow the project's coding standards. Typically, third-party imports come before internal imports.

If your project uses a tool like ESLint or Prettier with import ordering rules, consider reordering the imports:

 import { withServerActionInstrumentation } from "@sentry/nextjs";
+import { ActionError, type ServerActionResponse } from "@/utils/error";
🧰 Tools
🪛 Biome

[error] 1-2: All these imports are only used as types.

Importing the types with import type ensures that they are removed by the transpilers and avoids loading unnecessary modules.
Safe fix: Use import type.

(lint/style/useImportType)

apps/web/utils/actions/mail.ts (1)

23-23: Consider removing obsolete comment

The comment // do not return functions to the client or we'll get an error may no longer be relevant since the current code does not appear to return functions to the client. Removing unnecessary comments helps keep the codebase clean.

apps/web/utils/actions/group.ts (1)

31-38: Log errors for better debugging

In the catch block, the caught error is not being logged or handled beyond returning a generic message. Logging the error can aid in debugging issues in production.

Consider logging the error:

       } catch (error) {
+        console.error('Error creating group:', error);
         return { error: "Error creating group" };
       }
🧰 Tools
🪛 Biome

[error] 35-35: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)

apps/web/utils/actions/premium.ts (3)

Line range hint 4-4: Remove Unused Import withServerActionInstrumentation

The import withServerActionInstrumentation from "@sentry/nextjs" on line 4 is no longer used since you've switched to using withActionInstrumentation. Removing unused imports helps keep the codebase clean.

Apply this diff to remove the unused import:

-import { withServerActionInstrumentation } from "@sentry/nextjs";

18-18: Import Grouping: Organize Imports for Better Readability

Consider grouping imports from the same module together or ordering them alphabetically to improve readability.

You could rearrange the imports as follows:

 import uniq from "lodash/uniq";
+import { withActionInstrumentation } from "@/utils/actions/middleware";
 import { auth } from "@/app/api/auth/[...nextauth]/auth";
 import prisma from "@/utils/prisma";
 import { env } from "@/env";
-import { withActionInstrumentation } from "@/utils/actions/middleware";

42-42: Provide Feedback When Premium User Skips Credit Decrement

At line 42, when a premium user attempts to decrement unsubscribe credits, the function returns silently. Consider providing feedback to inform the user that premium users have unlimited unsubscribe credits.

You could modify the code to return a message:

 if (isUserPremium)
-  return;
+  return { message: "Premium users have unlimited unsubscribe credits." };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ade389f and a0d3c79.

📒 Files selected for processing (19)
  • .cursorrules (1 hunks)
  • apps/web/app/(app)/ErrorMessages.tsx (1 hunks)
  • apps/web/app/(app)/admin/AdminUserControls.tsx (1 hunks)
  • apps/web/app/(app)/automation/RulesPrompt.tsx (2 hunks)
  • apps/web/app/(landing)/components/page.tsx (1 hunks)
  • apps/web/components/email-list/EmailList.tsx (1 hunks)
  • apps/web/utils/actions/admin.ts (1 hunks)
  • apps/web/utils/actions/categorize.ts (1 hunks)
  • apps/web/utils/actions/error-messages.ts (1 hunks)
  • apps/web/utils/actions/group.ts (1 hunks)
  • apps/web/utils/actions/helpers.ts (0 hunks)
  • apps/web/utils/actions/mail.ts (1 hunks)
  • apps/web/utils/actions/middleware.ts (1 hunks)
  • apps/web/utils/actions/premium.ts (2 hunks)
  • apps/web/utils/actions/unsubscriber.ts (1 hunks)
  • apps/web/utils/actions/user.ts (1 hunks)
  • apps/web/utils/error.ts (1 hunks)
  • apps/web/utils/gmail/filter.ts (2 hunks)
  • apps/web/utils/server-action.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/utils/actions/helpers.ts
🧰 Additional context used
🪛 Biome
apps/web/components/email-list/EmailList.tsx

[error] 233-238: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)

apps/web/utils/actions/group.ts

[error] 35-35: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 47-49: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


[error] 86-86: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 118-118: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 156-156: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 192-192: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 204-204: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 223-223: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)


[error] 237-237: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)

apps/web/utils/actions/middleware.ts

[error] 16-16: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)


[error] 18-18: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 29-29: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 48-48: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

apps/web/utils/actions/premium.ts

[error] 262-262: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

apps/web/utils/actions/user.ts

[error] 75-75: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

apps/web/utils/error.ts

[error] 34-34: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)


[error] 35-35: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

apps/web/utils/gmail/filter.ts

[error] 41-41: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

apps/web/utils/server-action.ts

[error] 3-9: Some named imports are only used as types.

This import is only used as a type.

Importing the types with import type ensures that they are removed by the transpilers and avoids loading unnecessary modules.
Safe fix: Use import type.

(lint/style/useImportType)


[error] 22-22: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

🔇 Additional comments (23)
apps/web/utils/actions/error-messages.ts (1)

6-6: LGTM: Import statement for withActionInstrumentation added.

The import statement for withActionInstrumentation is correctly added and necessary for the refactored function.

apps/web/utils/actions/unsubscriber.ts (2)

6-13: Excellent use of middleware for improved action tracking!

The addition of withActionInstrumentation middleware enhances the monitoring capabilities of the setNewsletterStatusAction. This change aligns well with the PR objective of better handling server actions. The function signature remains unchanged, ensuring backward compatibility.


1-32: Summary: Solid improvements with minor suggestions for enhancement

Overall, the changes to setNewsletterStatusAction are well-implemented and align with the PR objectives. The use of withActionInstrumentation middleware enhances action tracking, which is a significant improvement. The refactoring of the Prisma query improves readability without altering functionality.

While the core changes are solid, there are opportunities for further enhancement:

  1. Improving error handling and type safety as suggested earlier.
  2. Minor formatting adjustments for consistency in the Prisma upsert call.

These suggestions are minor and don't detract from the overall quality of the implemented changes. Great work on improving the server action handling!

apps/web/utils/actions/categorize.ts (2)

60-60: Update TypeScript types and consuming components.

The change in the return value of categorizeAction may require updates in other parts of the codebase.

Please ensure the following:

  1. Update any TypeScript types that define the return type of categorizeAction.
  2. Modify components or functions that consume categorizeAction to handle the new return format.

Run the following script to find TypeScript types related to categorizeAction:

#!/bin/bash
# Description: Find TypeScript types related to categorizeAction

# Search for type definitions that mention categorizeAction
rg --type typescript 'type.*categorizeAction'

Review the results and update any relevant type definitions to match the new return format.


60-60: Verify error handling with the new return format.

While simplifying the return value improves the function's focus, it's crucial to ensure that error cases are still properly handled. The function has multiple points where it returns error objects, but the new return statement doesn't explicitly handle errors.

To ensure that error handling hasn't been compromised, please verify the following:

  1. Check all call sites of categorizeAction to confirm they can still distinguish between successful categorization and errors.
  2. Verify that the calling code doesn't rely on any properties from the res object that are no longer being returned.

Run the following script to find all usages of categorizeAction:

Review the results to ensure that error handling is still appropriate with the new return format.

apps/web/app/(app)/admin/AdminUserControls.tsx (1)

44-46: LGTM! Verify consistency of adminProcessHistoryAction usage.

The change to adminProcessHistoryAction looks good. The function now receives an object with an emailAddress property instead of a plain email string, which can improve type safety and make the function more extensible.

To ensure consistency across the codebase, let's verify the usage of adminProcessHistoryAction:

✅ Verification successful

Consistency Verified for adminProcessHistoryAction Usage

All instances of adminProcessHistoryAction now use the { emailAddress: email } format, ensuring consistency across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining direct email string usage with adminProcessHistoryAction

# Test: Search for adminProcessHistoryAction usage
# Expect: All occurrences should use the new { emailAddress: email } format
rg --type typescript 'adminProcessHistoryAction\s*\(' -A 3

Length of output: 95


Script:

#!/bin/bash
# Description: Check for any remaining direct email string usage with adminProcessHistoryAction

# Test: Search for adminProcessHistoryAction usage in .ts and .tsx files
# Expect: All occurrences should use the new { emailAddress: email } format
rg 'adminProcessHistoryAction\s*\(' --glob '*.ts' --glob '*.tsx' -A 3

Length of output: 445

apps/web/utils/actions/user.ts (1)

13-14: LGTM: Import statements updated correctly.

The import statements have been updated to reflect the changes in the implementation. The removal of ServerActionResponse and the addition of withActionInstrumentation are consistent with the refactoring of the action functions.

apps/web/app/(landing)/components/page.tsx (1)

146-149: Improved layout for error and action test buttons.

The changes enhance the visual arrangement of the TestErrorButton and TestActionButton components by wrapping them in a flex container with a gap. This improvement aligns well with the PR objective of "Handle server action failure better" by making it easier to test and interact with these components.

The modification is clean, consistent with the existing code style, and improves the overall user experience when working with error handling and server actions.

apps/web/app/(app)/automation/RulesPrompt.tsx (2)

32-32: LGTM: New utility import for improved action handling.

The addition of handleActionCall from @/utils/server-action is a good practice for centralizing action handling logic.


Line range hint 1-265: Overall improvement in error handling and user feedback.

The changes in this file align well with the PR objective of handling server action failure better. The introduction of handleActionCall and the refactoring of the onSubmit function contribute to more robust error handling and potentially improved instrumentation.

The modifications to the success message construction enhance the user experience by providing clearer and more concise feedback about the results of the action.

These changes maintain the core functionality of the component while improving its reliability and user interaction.

apps/web/components/email-list/EmailList.tsx (1)

Line range hint 233-238: Summary of changes: Improved error handling in email categorization

The changes in this file focus on enhancing the error handling within the onAiCategorize function. These improvements make the email categorization feature more robust by explicitly handling action errors and incomplete requests. The overall structure and functionality of the EmailList component remain unchanged, maintaining its core purpose while improving its reliability.

🧰 Tools
🪛 Biome

[error] 232-232: Do not use template literals if interpolation and special-character handling are not needed.

Unsafe fix: Replace with string literal

(lint/style/noUnusedTemplateLiteral)

apps/web/utils/gmail/filter.ts (3)

12-12: Added 'await' to handle the promise correctly

Adding await to the gmail.users.settings.filters.create call ensures that the function waits for the promise to resolve before proceeding. This prevents potential issues with unhandled promise rejections.


24-28: Destructured parameters in createAutoArchiveFilter for clarity

Destructuring the parameters directly in the function signature improves readability and makes it explicit which arguments are required.


33-45: Gracefully handle "Filter already exists" error

The addition of the try-catch block ensures that if the filter already exists, the function handles the error gracefully by returning a successful status. This improves the user experience by preventing unnecessary errors.

🧰 Tools
🪛 Biome

[error] 41-41: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

apps/web/utils/actions/middleware.ts (1)

55-59: Ensure consistent error handling and typing in the catch block.

In the catch block, the returned object is being cast to ActionError<Err & { success: false }>, but the structure may not match the expected ServerActionResponse type. Ensure that the error response includes all necessary fields and types.

Consider adjusting the error response to match the expected type:

         } catch (error) {
           // error is already captured by Sentry in `withServerActionInstrumentation`
           return {
             error: "An error occurred",
             success: false,
+            ...(error instanceof Error ? { message: error.message } : {}),
           } as ActionError<Err & { success: false }>;
         }

Do you need assistance in refining the error handling to ensure it aligns with your application's conventions?

apps/web/utils/actions/group.ts (4)

36-36: Remove unnecessary template literal

Line 36 contains a template literal without interpolation.

This issue has been addressed in a previous comment regarding unnecessary template literals.


202-202: ⚠️ Potential issue

Use deleteMany with composite filters

In Prisma, the delete method requires unique fields. Since we're filtering by both id and userId, consider using deleteMany instead.

Apply this diff to fix the issue:

-      await prisma.group.delete({ where: { id, userId: session.user.id } });
+      await prisma.group.deleteMany({ where: { id, userId: session.user.id } });

Ensure that the group deletion works as expected when using deleteMany.


130-135: ⚠️ Potential issue

Check for group ownership by including user ID in the query

Similar to previous comment, including userId in the where clause enhances security and avoids unnecessary checks.

Apply this diff to fix the issue:

       const existingGroup = await prisma.group.findUnique({
-        where: { id: groupId },
+        where: { id: groupId, userId: session.user.id },
         select: { items: { select: { id: true, type: true, value: true } } },
       });
       if (!existingGroup) return { error: "Group not found" };
-      

Likely invalid or redundant comment.


166-171: ⚠️ Potential issue

Include user ID in group retrieval for security

Ensure that only the user's own groups are accessed by adding userId to the query.

Apply this diff to fix the issue:

       const existingGroup = await prisma.group.findUnique({
-        where: { id: groupId },
+        where: { id: groupId, userId: session.user.id },
         select: { items: { select: { id: true, type: true, value: true } } },
       });
       if (!existingGroup) return { error: "Group not found" };

Likely invalid or redundant comment.

apps/web/utils/actions/premium.ts (4)

49-50: Handle Potential Null Values When Creating Premium for User

At lines 49-50, when calling createPremiumForUser(session.user.id), ensure that the function handles cases where the premium creation might fail and returns a valid premium object.


211-225: Check for Undefined Values in upgradeToPremium Call

In lines 211-225, when calling upgradeToPremium, ensure all required parameters are provided and handle any potential undefined values, especially those from lemonSqueezyLicense.data?.meta.


273-292: Confirm Email Accounts Access Quantity Matches Tier Limitations

At lines 273-292, when setting emailAccountsAccess, ensure that the value aligns with the limitations of the selected PremiumTier. This prevents users from exceeding their allowed email accounts.


Line range hint 20-326: Consistent Application of Action Instrumentation

Wrapping actions with withActionInstrumentation enhances monitoring. Ensure that all server actions in the codebase are consistently wrapped to maintain uniform instrumentation and observability.

🧰 Tools
🪛 Biome

[error] 262-262: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

Comment on lines +14 to +15
const session = await auth();
if (!session?.user.email) return { error: "Not logged in" };
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

Consider enhancing error handling and type safety

While the authentication check is maintained, which is good for security, we could further improve the error handling and type safety. Consider the following suggestions:

  1. Use a more specific error type instead of a generic string.
  2. Implement a custom error class for authentication failures.
  3. Use TypeScript's discriminated unions for better type checking of the return value.

Here's an example of how you could implement these improvements:

type AuthError = { kind: 'AuthError'; message: string };
type Success = { kind: 'Success'; data: any };
type ActionResult = AuthError | Success;

// ... (in the function body)
if (!session?.user.email) {
  return { kind: 'AuthError', message: 'Not logged in' };
}

// ... (at the end of the function)
return { kind: 'Success', data: result };

This approach would provide more robust error handling and improve type safety throughout the application.

Comment on lines +71 to +86
export const saveOnboardingAnswersAction = withActionInstrumentation(
"saveOnboardingAnswers",
async (onboardingAnswers: {
surveyId?: string;
questions: any;
answers: Record<string, string>;
}) => {
const session = await auth();
if (!session?.user.id) return { error: "Not logged in" };

await prisma.user.update({
where: { id: session.user.id },
data: { onboardingAnswers },
});
}
await prisma.user.update({
where: { id: session.user.id },
data: { onboardingAnswers },
});
},
);
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

LGTM with suggestions: Refactoring improves monitoring, but type safety can be enhanced.

The saveOnboardingAnswersAction function has been successfully refactored to use withActionInstrumentation, improving monitoring capabilities. The core logic remains intact, ensuring that the functionality is preserved.

The questions parameter is typed as any, which reduces type safety. Consider defining a more specific type for this parameter to improve code quality and catch potential errors at compile-time.

Consider the following improvements:

  1. Define a more specific type for the questions parameter:
type OnboardingQuestion = {
  id: string;
  text: string;
  // Add other relevant properties
};

export const saveOnboardingAnswersAction = withActionInstrumentation(
  "saveOnboardingAnswers",
  async (onboardingAnswers: {
    surveyId?: string;
    questions: OnboardingQuestion[];
    answers: Record<string, string>;
  }) => {
    // ... existing code ...
  },
);
  1. Add a return statement to explicitly indicate a successful operation:
export const saveOnboardingAnswersAction = withActionInstrumentation(
  "saveOnboardingAnswers",
  async (onboardingAnswers: {
    surveyId?: string;
    questions: OnboardingQuestion[];
    answers: Record<string, string>;
  }) => {
    const session = await auth();
    if (!session?.user.id) return { error: "Not logged in" };

    await prisma.user.update({
      where: { id: session.user.id },
      data: { onboardingAnswers },
    });
+   return { success: true };
  },
);

These changes would improve type safety and make the function's success case more explicit.

🧰 Tools
🪛 Biome

[error] 75-75: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

result = await actionFn();
} catch (error) {
captureException(error, { extra: { actionName } });
return { error: String(error) } as ActionError<E>;
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

Use error.message instead of String(error) for clearer error messages

Using String(error) might include unnecessary information like the error name and stack trace. Using error.message provides a cleaner and more user-friendly error description.

Apply this diff to improve the error message:

-return { error: String(error) } as ActionError<E>;
+return { error: error instanceof Error ? error.message : String(error) } as ActionError<E>;
📝 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
return { error: String(error) } as ActionError<E>;
return { error: error instanceof Error ? error.message : String(error) } as ActionError<E>;

}
}

export async function handleActionCall<T, E = {}>(
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

Avoid using {} as a type; specify a more precise default type

Using {} as a type means "any non-nullable value," which is discouraged. It can lead to unintended type matches and obscure type errors. Consider specifying a more precise default type for E, such as Record<string, unknown> or another appropriate interface.

Apply this diff to fix the issue:

-export async function handleActionCall<T, E = {}>(
+export async function handleActionCall<T, E = Record<string, unknown>>(
📝 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 async function handleActionCall<T, E = {}>(
export async function handleActionCall<T, E = Record<string, unknown>>(
🧰 Tools
🪛 Biome

[error] 22-22: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

Comment on lines +4 to +9
import {
ActionError,
type ServerActionResponse,
captureException,
isActionError,
} from "@/utils/error";
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

Optimize type-only imports by using import type

Some of the imported entities ActionError and ServerActionResponse are only used as types. To improve build performance and ensure that these imports are erased during transpilation, consider using import type for type-only imports.

Apply this diff to adjust the imports:

-import {
-  ActionError,
-  type ServerActionResponse,
-  captureException,
-  isActionError,
-} from "@/utils/error";
+import { captureException, isActionError } from "@/utils/error";
+import type { ActionError, ServerActionResponse } from "@/utils/error";
📝 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
import {
ActionError,
type ServerActionResponse,
captureException,
isActionError,
} from "@/utils/error";
import { captureException, isActionError } from "@/utils/error";
import type { ActionError, ServerActionResponse } from "@/utils/error";
🧰 Tools
🪛 Biome

[error] 3-9: Some named imports are only used as types.

This import is only used as a type.

Importing the types with import type ensures that they are removed by the transpilers and avoids loading unnecessary modules.
Safe fix: Use import type.

(lint/style/useImportType)

);
if (!subscription) return { error: "Subscription not found" };
lemonSqueezySubscriptionId = Number.parseInt(subscription.id);
const attributes = subscription.attributes as any;
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

Avoid Using any Type for subscription.attributes

Using as any on line 262 disables type checking and can introduce bugs. Define a proper type for subscription.attributes to ensure type safety.

Consider defining an interface for SubscriptionAttributes and using it:

interface SubscriptionAttributes {
  order_id: string;
  product_id: string;
  variant_id: string;
  first_subscription_item: { id?: string };
  // Include other relevant fields
}

const attributes = subscription.attributes as SubscriptionAttributes;
🧰 Tools
🪛 Biome

[error] 262-262: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

Comment on lines +20 to +75
export const decrementUnsubscribeCreditAction = withActionInstrumentation(
"decrementUnsubscribeCredit",
async () => {
const session = await auth();
if (!session?.user.email) return { error: "Not logged in" };

const user = await prisma.user.findUnique({
where: { email: session.user.email },
select: {
premium: {
select: {
id: true,
unsubscribeCredits: true,
unsubscribeMonth: true,
lemonSqueezyRenewsAt: true,
},
},
},
},
});
});

if (!user) return { error: "User not found" };
if (!user) return { error: "User not found" };

const isUserPremium = isPremium(user.premium?.lemonSqueezyRenewsAt || null);
if (isUserPremium) return;
const isUserPremium = isPremium(user.premium?.lemonSqueezyRenewsAt || null);
if (isUserPremium) return;

const currentMonth = new Date().getMonth() + 1;
const currentMonth = new Date().getMonth() + 1;

// create premium row for user if it doesn't already exist
const premium = user.premium || (await createPremiumForUser(session.user.id));
// create premium row for user if it doesn't already exist
const premium =
user.premium || (await createPremiumForUser(session.user.id));

if (
!premium?.unsubscribeMonth ||
premium?.unsubscribeMonth !== currentMonth
) {
// reset the monthly credits
await prisma.premium.update({
where: { id: premium.id },
data: {
// reset and use a credit
unsubscribeCredits: env.NEXT_PUBLIC_FREE_UNSUBSCRIBE_CREDITS - 1,
unsubscribeMonth: currentMonth,
},
});
} else {
if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0) return;
if (
!premium?.unsubscribeMonth ||
premium?.unsubscribeMonth !== currentMonth
) {
// reset the monthly credits
await prisma.premium.update({
where: { id: premium.id },
data: {
// reset and use a credit
unsubscribeCredits: env.NEXT_PUBLIC_FREE_UNSUBSCRIBE_CREDITS - 1,
unsubscribeMonth: currentMonth,
},
});
} else {
if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0)
return;

// decrement the monthly credits
await prisma.premium.update({
where: { id: premium.id },
data: { unsubscribeCredits: { decrement: 1 } },
});
}
}
// decrement the monthly credits
await prisma.premium.update({
where: { id: premium.id },
data: { unsubscribeCredits: { decrement: 1 } },
});
}
},
);
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

Ensure Consistent Error Handling in decrementUnsubscribeCreditAction

While wrapping the decrementUnsubscribeCreditAction with withActionInstrumentation, ensure that all code paths return a consistent response type. Currently, some paths return an object with an error key, while others return nothing.

Consider updating the function to always return a standardized response, such as:

type ServerActionResponse = { error?: string; message?: string };

...

if (!session?.user.email) return { error: "Not logged in" };
...
return { message: "Credit decremented successfully." };

Comment on lines 229 to 304
export const changePremiumStatusAction = withActionInstrumentation(
"changePremiumStatus",
async (options: ChangePremiumStatusOptions) => {
const session = await auth();
if (!session?.user.email) return { error: "Not logged in" };
if (!isAdmin(session.user.email)) return { error: "Not admin" };

const userToUpgrade = await prisma.user.findUnique({
where: { email: options.email },
select: { id: true, premiumId: true },
});
} else if (userToUpgrade) {
if (userToUpgrade.premiumId) {
await cancelPremium({
premiumId: userToUpgrade.premiumId,
lemonSqueezyEndsAt: new Date(),
});
} else {
return { error: "User not premium." };
}
}
}

export async function claimPremiumAdminAction(): Promise<ServerActionResponse> {
const session = await auth();
if (!session?.user.id) return { error: "Not logged in" };
if (!userToUpgrade) return { error: "User not found" };

const ONE_MONTH = 1000 * 60 * 60 * 24 * 30;

let lemonSqueezySubscriptionId: number | null = null;
let lemonSqueezySubscriptionItemId: number | null = null;
let lemonSqueezyOrderId: number | null = null;
let lemonSqueezyProductId: number | null = null;
let lemonSqueezyVariantId: number | null = null;

if (options.upgrade) {
if (options.lemonSqueezyCustomerId) {
const lemonCustomer = await getLemonCustomer(
options.lemonSqueezyCustomerId.toString(),
);
if (!lemonCustomer.data) return { error: "Lemon customer not found" };
const subscription = lemonCustomer.data.included?.find(
(i) => i.type === "subscriptions",
);
if (!subscription) return { error: "Subscription not found" };
lemonSqueezySubscriptionId = Number.parseInt(subscription.id);
const attributes = subscription.attributes as any;
lemonSqueezyOrderId = Number.parseInt(attributes.order_id);
lemonSqueezyProductId = Number.parseInt(attributes.product_id);
lemonSqueezyVariantId = Number.parseInt(attributes.variant_id);
lemonSqueezySubscriptionItemId = attributes.first_subscription_item.id
? Number.parseInt(attributes.first_subscription_item.id)
: null;
}

const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { premium: { select: { id: true, admins: true } } },
});
await upgradeToPremium({
userId: userToUpgrade.id,
tier: options.period,
lemonSqueezyCustomerId: options.lemonSqueezyCustomerId || null,
lemonSqueezySubscriptionId,
lemonSqueezySubscriptionItemId,
lemonSqueezyOrderId,
lemonSqueezyProductId,
lemonSqueezyVariantId,
lemonSqueezyRenewsAt:
options.period === PremiumTier.PRO_ANNUALLY ||
options.period === PremiumTier.BUSINESS_ANNUALLY ||
options.period === PremiumTier.BASIC_ANNUALLY
? new Date(+new Date() + ONE_MONTH * 12)
: options.period === PremiumTier.PRO_MONTHLY ||
options.period === PremiumTier.BUSINESS_MONTHLY ||
options.period === PremiumTier.BASIC_MONTHLY ||
options.period === PremiumTier.COPILOT_MONTHLY
? new Date(+new Date() + ONE_MONTH)
: null,
emailAccountsAccess: options.emailAccountsAccess,
});
} else if (userToUpgrade) {
if (userToUpgrade.premiumId) {
await cancelPremium({
premiumId: userToUpgrade.premiumId,
lemonSqueezyEndsAt: new Date(),
});
} else {
return { error: "User not premium." };
}
}
},
);
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

Validate options.period Before Using in Date Calculations

When calculating lemonSqueezyRenewsAt based on options.period, ensure that options.period is a valid PremiumTier. Invalid values could lead to incorrect renewal dates.

Consider adding validation for options.period:

if (!Object.values(PremiumTier).includes(options.period)) {
  return { error: "Invalid premium tier specified." };
}
🧰 Tools
🪛 Biome

[error] 262-262: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

Comment on lines +252 to +269
if (options.lemonSqueezyCustomerId) {
const lemonCustomer = await getLemonCustomer(
options.lemonSqueezyCustomerId.toString(),
);
if (!lemonCustomer.data) return { error: "Lemon customer not found" };
const subscription = lemonCustomer.data.included?.find(
(i) => i.type === "subscriptions",
);
if (!subscription) return { error: "Subscription not found" };
lemonSqueezySubscriptionId = Number.parseInt(subscription.id);
const attributes = subscription.attributes as any;
lemonSqueezyOrderId = Number.parseInt(attributes.order_id);
lemonSqueezyProductId = Number.parseInt(attributes.product_id);
lemonSqueezyVariantId = Number.parseInt(attributes.variant_id);
lemonSqueezySubscriptionItemId = attributes.first_subscription_item.id
? Number.parseInt(attributes.first_subscription_item.id)
: null;
}
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

Strongly Type Lemon Squeezy API Responses

In lines 252-269, avoid using loosely typed or any-typed variables when handling API responses. Define interfaces for the Lemon Squeezy API responses to improve type safety and code clarity.

Define interfaces for the expected response structures and update the code accordingly.

🧰 Tools
🪛 Biome

[error] 262-262: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)

Comment on lines +65 to +66
if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0)
return;
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

Handle Case When No Unsubscribe Credits Remain

At lines 65-66, if the user has no unsubscribe credits left, the function returns without any feedback. It's better to inform the user that they've exhausted their credits.

Modify the code to provide an error message:

 if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0)
-  return;
+  return { error: "No unsubscribe credits remaining for this month." };
📝 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
if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0)
return;
if (!premium?.unsubscribeCredits || premium.unsubscribeCredits <= 0)
return { error: "No unsubscribe credits remaining for this month." };

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