Skip to content

Comments

Staging#762

Merged
MrgSub merged 61 commits intomainfrom
staging
Apr 25, 2025
Merged

Staging#762
MrgSub merged 61 commits intomainfrom
staging

Conversation

@MrgSub
Copy link
Collaborator

@MrgSub MrgSub commented Apr 25, 2025

Summary by CodeRabbit

  • New Features

    • Introduced label management: users can create, edit, and delete custom email labels with color selection.
    • Added a "Labels" section in settings and navigation for easier label access and management.
    • Implemented a "Danger Zone" in settings, allowing users to permanently delete their account with confirmation and safety checks.
  • Improvements

    • Labels now display with custom colors in the mail list and sidebar for better visual organization.
    • The "From" dropdown in email compose only appears when multiple aliases exist.
    • Attachments popover in email compose is now scrollable for improved usability.
    • Added token revocation on user deletion to enhance account cleanup and security.
  • Localization

    • Added English translations for new label and danger zone features.
  • Bug Fixes

    • Improved handling of line breaks in email drafts for better formatting consistency.

ahmetskilinc and others added 30 commits April 19, 2025 01:28
- added cc and bcc when saving drafts
- save drafts less aggresively
chore: simplify and fix the dev env
* Create prompts with XML formatting

* Include XML formatted prompts in generate func

* remove unused regex and add helper functions/warnings

* error handling

* Update apps/mail/lib/prompts.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* lint issues

* Update prompts.ts

* #706 (comment)

Coderabbit fix 1

* erabbitai bot 3 days ago ⚠️ Potential issue  errorOccurred state is stale inside finally  React state setters (setErrorOccurred) are asynchronous; the errorOccurred value captured at render time will not yet reflect changes made earlier in the same event loop. Consequently, the logic deciding whether to collapse/expand may run with an outdated flag.  -  } finally { -      setIsLoading(false); -      if (!errorOccurred || isAskingQuestion) { -        setIsExpanded(true); -      } else { -        setIsExpanded(false); // Collapse on errors -      } -  } +  } finally { +      setIsLoading(false); +      // Use a local flag to track errors deterministically +      const hadError = isAskingQuestion ? false : !!errorFlagRef.current; +      setIsExpanded(!hadError); +  } You can create const errorFlagRef = useRef(false); and update errorFlagRef.current = true every time an error is detected, ensuring reliable behaviour irrespective of React batching.  Committable suggestion skipped: line range outside the PR's diff.

* #706 (comment)

* #706 (comment)

* #706 (comment)

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…users (#726)

* feat(i18n): add Vietnamese language support

Add Vietnamese ('vi') to the list of supported languages in the
i18n configuration and JSON file to expand language options.

* Add a new Vietnamese translation file to support Vietnamese language users.

* Clear Vietnamese translation strings
Co-authored-by: needle <122770437+needleXO@users.noreply.github.com>
* Updated lockfile

* Updated home page session validation

---------

Co-authored-by: Adam <x_1337@outlook.com>
* Create route og image

* resolve coderabbit nitpicks

---------

Co-authored-by: Adam <x_1337@outlook.com>
MrgSub and others added 19 commits April 22, 2025 14:25
…ror messages instead of throwing unauthorized errors. This improves readability and maintains functionality across various actions.
* Adds labels from the provider:
- labels page in settings
- labels page translations
- added labels for google provider

* useLabels and labels in sidebar

* fix rate limit parts of labels route

* Added labels to mail-list

* - add rate limiting
- move useThreadsLabel

---------

Co-authored-by: Adam <x_1337@outlook.com>
- updates lib/auth.ts to use the new method
- updates actions/user.ts
- updates app/(routes)/settings/danger-zone/page.tsx
@vercel
Copy link

vercel bot commented Apr 25, 2025

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

Name Status Preview Comments Updated (UTC)
0 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 25, 2025 2:25am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 25, 2025

Warning

Rate limit exceeded

@MrgSub has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 16 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c438279 and c7ddfde.

📒 Files selected for processing (1)
  • apps/mail/components/ui/nav-main.tsx (5 hunks)

Walkthrough

This update introduces comprehensive label management and account deletion features to the mail application. New API routes and hooks enable CRUD operations for user-defined email labels, integrating with the Gmail API for label synchronization. The settings UI is expanded with dedicated pages for label management and a "Danger Zone" section for account deletion, including confirmation dialogs and internationalization support. Navigation and configuration files are updated to reflect these new sections. Additionally, utility enhancements include color palette resources for labels and improved handling of OAuth token revocation during user deletion, ensuring thorough cleanup of user data.

Changes

File(s) Change Summary
apps/mail/app/api/driver/google.ts, apps/mail/app/api/driver/types.ts Extended the MailManager interface and Google driver to support label CRUD operations and refresh token revocation. Modified draft creation to preprocess HTML content for line breaks.
apps/mail/app/api/v1/labels/route.ts, apps/mail/app/api/v1/thread-labels/route.ts Added new API routes for managing labels and fetching thread-specific labels, with rate limiting and authentication.
apps/mail/hooks/use-labels.ts, apps/mail/hooks/use-threads.ts Introduced hooks for fetching, creating, updating, deleting, and retrieving labels, including thread-specific label retrieval.
apps/mail/app/(routes)/settings/labels/page.tsx, apps/mail/app/(routes)/settings/labels/colors.ts Added a settings page and static color palette for managing email labels, including UI for CRUD operations.
apps/mail/app/(routes)/settings/danger-zone/page.tsx, apps/mail/actions/user.ts Implemented a "Danger Zone" settings page for account deletion, with a confirmation dialog and server-side deletion logic.
apps/mail/app/(routes)/settings/[...settings]/page.tsx, apps/mail/config/navigation.ts Updated settings routing and navigation to include new "Labels" and "Danger Zone" sections.
apps/mail/components/settings/settings-card.tsx Enhanced the settings card component to support an optional action element in the header.
apps/mail/components/mail/render-labels.tsx, apps/mail/components/mail/mail-list.tsx Added a new component for rendering labels and updated mail list to use it for displaying thread labels.
apps/mail/components/ui/nav-main.tsx Added a collapsible sidebar section for label navigation, dynamically rendering user labels.
apps/mail/lib/auth.ts Enhanced user deletion logic to revoke OAuth tokens and clean up all related database records before deletion.
apps/mail/lib/constants.ts Added a predefined Gmail color palette for label theming.
apps/mail/locales/en.json Updated localization with new strings for label management and danger zone/account deletion features.
apps/mail/components/create/create-email.tsx Improved conditional rendering of the "From" dropdown and added attachment popover height limit.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SettingsPage
    participant API
    participant GoogleDriver
    participant Database

    User->>SettingsPage: Open "Labels" or "Danger Zone"
    SettingsPage->>API: Fetch labels / Initiate account deletion
    API->>GoogleDriver: Perform label CRUD / Revoke token
    GoogleDriver-->>API: Return result (labels, status)
    API-->>SettingsPage: Return data/status
    SettingsPage-->>User: Show labels UI / Show deletion confirmation

    User->>SettingsPage: Create/Update/Delete label or Confirm deletion
    SettingsPage->>API: Send label mutation / Deletion request
    API->>GoogleDriver: Update labels / Revoke token
    GoogleDriver-->>API: Success/Failure
    API->>Database: Clean up user data (on deletion)
    Database-->>API: Success/Failure
    API-->>SettingsPage: Show result (success/error)
    SettingsPage-->>User: Display toast/redirect
Loading

Suggested labels

enhancement, High Priority

Suggested reviewers

  • ahmetskilinc
  • nizzyabi
  • needleXO

Poem

A flurry of labels, colors so bright,
Now in your inbox, a beautiful sight!
With danger zones guarded and tokens revoked,
Old threads and new, your labels invoked.
From settings to sidebar, changes abound—
The rabbit hops on, where features are found!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 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 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.

…mponent

- Added posthog-js version 1.236.6 to package.json and bun.lock.
- Introduced search functionality by implementing handleFilterByLabel in NavMain component.
- Updated NavItem to trigger label filtering on click.
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: 15

🔭 Outside diff range comments (1)
apps/mail/config/navigation.ts (1)

77-82: 🛠️ Refactor suggestion

Inconsistency between ID and displayed text

The item ID has been changed to 'trash', but the title still uses 'navigation.sidebar.bin' and the URL is still '/mail/bin'. This creates an inconsistency that might cause confusion in the codebase.

Consider updating both the title and URL to match the new 'trash' ID for consistency:

  {
    id: 'trash',
-   title: 'navigation.sidebar.bin',
-   url: '/mail/bin',
+   title: 'navigation.sidebar.trash',
+   url: '/mail/trash',
    icon: DeleteIcon,
    disabled: false,
  },
🧹 Nitpick comments (25)
apps/mail/lib/constants.ts (1)

113-130: Good implementation of Gmail color palette with consistent formatting

The GMAIL_COLORS constant provides a comprehensive set of color pairs that match Gmail's label color options. This will ensure visual consistency when synchronizing with Gmail labels.

One minor suggestion would be to add a brief comment explaining the purpose of this palette and how it differs from the larger COLORS palette defined elsewhere.

+// Color palette matching Gmail's label colors for consistency with Gmail API integration
 export const GMAIL_COLORS: GmailColor[] = [
   { textColor: '#000000', backgroundColor: '#E2E2E2' },
   { textColor: '#D50000', backgroundColor: '#F28B82' },
apps/mail/app/(routes)/settings/labels/colors.ts (1)

1-104: Comprehensive color palette implementation

The extensive color palette (104 colors) provides users with a rich set of options for label customization. This is well-structured as a standalone module that can be imported where needed.

Consider adding some grouping or categorization for these colors to help with organization and potentially to improve the user experience in the color selection UI.

+// Group colors by shades/categories for better organization
+export const COLOR_CATEGORIES = {
+  GRAYSCALE: COLORS.slice(0, 8),
+  PRIMARY: COLORS.slice(8, 17),
+  PASTEL: COLORS.slice(17, 26),
+  // Add more categories as needed
+};

 export const COLORS = [
   '#000000',
   '#434343',
apps/mail/actions/user.ts (1)

6-24: Secure implementation of user deletion with proper validation

The deleteUser function implements appropriate session validation before proceeding with account deletion. The error handling for missing session or user is well implemented.

Consider adding more specific error types or codes to help with client-side error handling and internationalization.

 export async function deleteUser() {
   const headersList = await headers();
   const session = await auth.api.getSession({ headers: headersList });

-  if (!session) return { success: false, error: 'Session not found' };
+  if (!session) return { success: false, error: 'Session not found', code: 'AUTH_SESSION_MISSING' };

   const userId = session.user?.id;

-  if (!userId) return { success: false, error: 'User not found' };
+  if (!userId) return { success: false, error: 'User not found', code: 'AUTH_USER_MISSING' };
apps/mail/components/mail/mail-list.tsx (1)

393-393: Consider removing commented code.

The commented-out MailLabels component should be removed rather than left as a comment if it's no longer needed. Commented code can confuse future developers and create maintenance issues.

-    {/* <MailLabels labels={threadLabels} /> */}
apps/mail/components/mail/render-labels.tsx (1)

1-18: Well-structured label rendering component.

The RenderLabels component follows React best practices:

  1. It's a functional component with proper props typing
  2. It uses a hook for data fetching with a sensible default value
  3. It applies both className-based and inline styles for label appearance
  4. Each label has a proper key attribute for React reconciliation

One potential improvement would be to handle the empty state or loading state explicitly, though the current implementation with default empty array is sufficient for basic use cases.

Consider adding a loading state indicator and empty state handling:

export const RenderLabels = ({ ids }: { ids: string[] }) => {
-  const { data: labels = [] } = useThreadLabels(ids);
+  const { data: labels = [], isLoading } = useThreadLabels(ids);
+
+  if (isLoading) {
+    return <span className="animate-pulse bg-primary/10 rounded px-1.5 py-0.5 opacity-50"></span>;
+  }
+
+  if (!labels.length) {
+    return null;
+  }

  return labels.map((label: Label) => (
    <span
      key={label.id}
      className="bg-primary/10 text-primary rounded px-1.5 py-0.5"
      style={{ backgroundColor: label.color?.backgroundColor, color: label.color?.textColor }}
    >
      {label.name}
    </span>
  ));
};
apps/mail/config/navigation.ts (1)

182-186: Icon inconsistency for danger-zone

The danger-zone section uses the KeyboardIcon, which doesn't semantically represent a danger or deletion area. This might confuse users about the section's purpose.

Consider using a more appropriate icon that represents danger, such as a shield with an exclamation mark or a warning triangle:

  {
    title: 'navigation.settings.danger-zone',
    url: '/settings/danger-zone',
-   icon: KeyboardIcon,
+   icon: AlertTriangleIcon, // or another warning-related icon
  },
apps/mail/app/api/v1/thread-labels/route.ts (1)

33-33: Optimize Promise handling for thread labels

The current implementation uses await inside the map function, which doesn't fully leverage Promise.all's concurrency benefits.

- const labels = await Promise.all(threadIds.map(async (id) => await driver.getLabel(id)));
+ const labels = await Promise.all(threadIds.map((id) => driver.getLabel(id)));
apps/mail/locales/en.json (1)

414-424: Expand labels localization for UI actions.

The localization for the "Labels" section is minimal compared to other settings sections. Consider adding entries for common actions like create, edit, delete, etc. to support full internationalization of the labels management UI.

"labels": {
  "title": "Labels",
  "description": "Manage your labels for better organization of your emails."
+  "create": "Create Label",
+  "edit": "Edit Label",
+  "delete": "Delete Label",
+  "name": "Label Name",
+  "color": "Color",
+  "save": "Save Changes",
+  "cancel": "Cancel",
+  "noLabels": "No labels created yet. Click the button above to create one.",
+  "deleteConfirmation": "Are you sure you want to delete this label?",
+  "loading": "Loading labels..."
},
apps/mail/app/(routes)/settings/labels/page.tsx (9)

19-31: Verify import usage.

The GMAIL_COLORS constant is imported but never used in the component. Consider removing this unused import.

import { SettingsCard } from '@/components/settings/settings-card';
import { Check, Pencil, Plus, Trash2 } from 'lucide-react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { Button } from '@/components/ui/button';
- import { GMAIL_COLORS } from '@/lib/constants';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';

77-84: Use constants for default values consistently.

The reset method in handleClose uses hardcoded default values that should be extracted to constants for consistency.

+ const DEFAULT_BG_COLOR = '#E2E2E2';
+ const DEFAULT_TEXT_COLOR = '#000000';
+
  const handleClose = () => {
    setIsDialogOpen(false);
    setEditingLabel(null);
    form.reset({
      name: '',
-     color: { backgroundColor: '#E2E2E2', textColor: '#000000' },
+     color: { backgroundColor: DEFAULT_BG_COLOR, textColor: DEFAULT_TEXT_COLOR },
    });
  };

96-99: Use internationalization for button text.

The "Create Label" text is hardcoded instead of using the translation function.

  <Button onClick={() => setEditingLabel(null)}>
    <Plus className="mr-2 h-4 w-4" />
-   Create Label
+   {t('common.mail.createNewLabel')}
  </Button>

104-105: Use internationalization for dialog title.

The dialog title texts are hardcoded instead of using the translation function.

  <DialogHeader>
-   <DialogTitle>{editingLabel ? 'Edit Label' : 'Create New Label'}</DialogTitle>
+   <DialogTitle>{editingLabel ? t('pages.settings.labels.edit') : t('pages.settings.labels.create')}</DialogTitle>
  </DialogHeader>

112-116: Internationalize label form fields.

Form field labels should use the translation function for internationalization.

  <FormItem>
-   <FormLabel>Label Name</FormLabel>
+   <FormLabel>{t('pages.settings.labels.name')}</FormLabel>
    <FormControl>
-     <Input placeholder="Enter label name" {...field} />
+     <Input placeholder={t('pages.settings.labels.namePlaceholder')} {...field} />
    </FormControl>

123-123: Internationalize color label.

The "Color" label should use the translation function.

- <Label>Color</Label>
+ <Label>{t('pages.settings.labels.color')}</Label>

156-158: Internationalize button text.

Button text should use the translation function.

  <Button type="button" variant="outline" onClick={handleClose}>
-   Cancel
+   {t('common.actions.cancel')}
  </Button>

159-161: Internationalize save button text.

Save button text should use the translation function.

  <Button onClick={form.handleSubmit(onSubmit)}>
-   {editingLabel ? 'Save Changes' : 'Create Label'}
+   {editingLabel ? t('common.actions.saveChanges') : t('pages.settings.labels.create')}
  </Button>

180-182: Internationalize empty state message.

The empty state message should use the translation function.

  <p className="text-muted-foreground py-4 text-center text-sm">
-   No labels created yet. Click the button above to create one.
+   {t('pages.settings.labels.noLabels')}
  </p>
apps/mail/app/(routes)/settings/danger-zone/page.tsx (3)

40-117: Enhance error handling with more specific error messages.

Currently, the error handling only logs the error and shows a generic "Failed to delete account" message. The user would benefit from more specific error information when possible.

Improve the error handling to display more specific error messages:

    } catch (error) {
      console.error('Failed to delete account:', error);
-     toast.error('Failed to delete account');
+     const errorMessage = error instanceof Error ? error.message : 'Failed to delete account';
+     toast.error(t('pages.settings.danger-zone.delete_error', { error: errorMessage }));
    } finally {

49-49: Type assertion is unnecessary and potentially misleading.

The type assertion '' as 'DELETE' is unnecessary and might hide type errors. The empty string doesn't match the literal type 'DELETE'.

Remove the type assertion and use a proper empty string:

    defaultValues: {
-     confirmText: '' as 'DELETE',
+     confirmText: '',
    },

84-87: Avoid hard-coded colors in favor of design system variables.

The text color is hardcoded as text-red-600 dark:text-red-400 which may not follow the design system if colors are updated.

Consider using design system variables or a more abstract class for destructive colors:

-  <div className="border-destructive/50 bg-destructive/10 flex items-center gap-2 rounded-md border px-3 py-2 text-sm text-red-600 dark:text-red-400">
+  <div className="border-destructive/50 bg-destructive/10 flex items-center gap-2 rounded-md border px-3 py-2 text-sm text-destructive">
   <AlertTriangle className="h-4 w-4" />
   <span>{t('pages.settings.danger-zone.warning')}</span>
  </div>
apps/mail/hooks/use-labels.ts (3)

15-18: The fetcher function could benefit from error handling.

The current fetcher function doesn't handle errors properly, which may lead to unclear error messages.

Improve the fetcher function with better error handling:

const fetcher = async (url: string) => {
-  const response = await axios.get(url);
-  return response.data;
+  try {
+    const response = await axios.get(url);
+    return response.data;
+  } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+    console.error(`Failed to fetch from ${url}:`, errorMessage);
+    throw error;
+  }
};

104-132: Configure error retry strategy in SWR.

The current SWR configuration disables revalidation on focus and reconnect but doesn't configure an error retry strategy, which might lead to excessive retries on failure.

Add explicit error retry configuration:

  return useSWR<Label[]>(
    key,
    async (url) => {
      try {
        const response = await fetch(url, {
          method: 'GET',
          headers: { 'Content-Type': 'application/json' },
        });

        if (!response.ok) {
          throw new Error('Failed to fetch thread labels');
        }

        return response.json();
      } catch (error) {
        toast.error('Failed to fetch thread labels');
        throw error;
      }
    },
    {
      revalidateOnFocus: false,
      revalidateOnReconnect: false,
      dedupingInterval: 5000,
+     onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
+       // Only retry up to 3 times
+       if (retryCount >= 3) return;
+       
+       // Retry after 5 seconds
+       setTimeout(() => revalidate({ retryCount }), 5000);
+     },
    },
  );

36-36: Consider using { revalidate: false } for SWR mutate.

When using mutate with SWR, using the object syntax is preferred over the boolean parameter for better type safety and future compatibility.

Update the mutate call:

-      await mutate([...(labels || []), newLabel], false);
+      await mutate([...(labels || []), newLabel], { revalidate: false });

Also make similar changes in other mutate calls in the file.

apps/mail/app/api/v1/labels/route.ts (1)

18-21: Rate limit window value may be unnecessarily high.

The current rate limit allows 60 requests per minute per user, which seems excessive for label management operations. Consider a lower limit to prevent API abuse.

Adjust the rate limit to a more reasonable value:

  const ratelimit = getRatelimitModule({
    prefix: `ratelimit:get-labels-${userId}`,
-    limiter: Ratelimit.slidingWindow(60, '1m'),
+    limiter: Ratelimit.slidingWindow(20, '1m'),
  });

Apply similar adjustments to other rate limit configurations in this file.

apps/mail/app/api/driver/google.ts (1)

988-1004: Handle potential edge cases in createLabel method.

The createLabel method doesn't validate the label name, which could lead to issues if an invalid label name is provided. Gmail has restrictions on label names that should be enforced.

Add validation for the label name before creating:

  createLabel: async (label) => {
+    if (!label.name || label.name.trim() === '') {
+      throw new Error('Label name cannot be empty');
+    }
+    
+    // Gmail doesn't allow certain characters in label names
+    if (/[/\\]/.test(label.name)) {
+      throw new Error('Label name cannot contain slashes');
+    }
    const res = await gmail.users.labels.create({
      userId: 'me',
      requestBody: {
        name: label.name,
        labelListVisibility: 'labelShow',
        messageListVisibility: 'show',
        color: label.color
          ? {
              backgroundColor: label.color.backgroundColor,
              textColor: label.color.textColor,
            }
          : undefined,
      },
    });
    return res.data;
  },
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0a85630 and 29d6be2.

📒 Files selected for processing (20)
  • apps/mail/actions/user.ts (1 hunks)
  • apps/mail/app/(routes)/settings/[...settings]/page.tsx (2 hunks)
  • apps/mail/app/(routes)/settings/danger-zone/page.tsx (1 hunks)
  • apps/mail/app/(routes)/settings/labels/colors.ts (1 hunks)
  • apps/mail/app/(routes)/settings/labels/page.tsx (1 hunks)
  • apps/mail/app/api/driver/google.ts (4 hunks)
  • apps/mail/app/api/driver/types.ts (1 hunks)
  • apps/mail/app/api/v1/labels/route.ts (1 hunks)
  • apps/mail/app/api/v1/thread-labels/route.ts (1 hunks)
  • apps/mail/components/create/create-email.tsx (2 hunks)
  • apps/mail/components/mail/mail-list.tsx (3 hunks)
  • apps/mail/components/mail/render-labels.tsx (1 hunks)
  • apps/mail/components/settings/settings-card.tsx (2 hunks)
  • apps/mail/components/ui/nav-main.tsx (3 hunks)
  • apps/mail/config/navigation.ts (3 hunks)
  • apps/mail/hooks/use-labels.ts (1 hunks)
  • apps/mail/hooks/use-threads.ts (2 hunks)
  • apps/mail/lib/auth.ts (2 hunks)
  • apps/mail/lib/constants.ts (1 hunks)
  • apps/mail/locales/en.json (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
apps/mail/app/(routes)/settings/[...settings]/page.tsx (1)
apps/mail/app/(routes)/settings/labels/page.tsx (1)
  • LabelsPage (33-217)
apps/mail/actions/user.ts (2)
packages/db/src/schema.ts (1)
  • session (19-30)
apps/mail/lib/auth.ts (1)
  • auth (327-330)
apps/mail/components/create/create-email.tsx (2)
apps/mail/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (171-171)
  • DropdownMenuTrigger (172-172)
  • DropdownMenuContent (173-173)
  • DropdownMenuItem (174-174)
apps/mail/components/ui/button.tsx (1)
  • Button (55-55)
apps/mail/components/settings/settings-card.tsx (1)
apps/mail/components/ui/card.tsx (3)
  • CardHeader (55-55)
  • CardTitle (55-55)
  • CardDescription (55-55)
apps/mail/components/mail/mail-list.tsx (1)
apps/mail/components/mail/render-labels.tsx (1)
  • RenderLabels (6-18)
apps/mail/lib/auth.ts (4)
packages/db/src/schema.ts (4)
  • user (7-17)
  • connection (77-94)
  • account (32-48)
  • session (19-30)
apps/mail/app/api/driver/google.ts (1)
  • driver (195-1042)
apps/mail/actions/utils.ts (1)
  • getActiveDriver (33-63)
packages/db/src/index.ts (1)
  • db (17-17)
apps/mail/app/api/driver/google.ts (1)
apps/mail/lib/auth.ts (1)
  • auth (327-330)
🔇 Additional comments (22)
apps/mail/lib/constants.ts (1)

108-111: Well-defined interface for Gmail colors

The GmailColor interface clearly defines the structure for color pairs used in Gmail labels. This provides good type safety for the color definitions that follow.

apps/mail/app/(routes)/settings/[...settings]/page.tsx (2)

11-11: Added import for new labels page component

Good addition of the import for the LabelsPage component, maintaining consistent import style with other settings pages.


20-20: Registration of labels page in settings navigation

The LabelsPage component is correctly registered in the settingsPages mapping with the key 'labels', allowing it to be accessed via the settings route system.

apps/mail/actions/user.ts (1)

1-5: Well-structured server action setup

The 'use server' directive correctly marks this as a server action, and the imports are appropriate for the functionality.

apps/mail/components/mail/mail-list.tsx (3)

47-47: Good addition of RenderLabels component import.

The import of the new RenderLabels component is aligned with the label management feature being introduced.


50-50: Label type import looks correct.

Adding the Label type import from the use-labels hook provides proper type checking for the label-related functionality.


361-363: Good implementation of the new label rendering approach.

Replacing the inline label rendering with the dedicated RenderLabels component improves code organization and maintenance. The space-x-2 class ensures proper spacing between multiple labels.

apps/mail/components/create/create-email.tsx (2)

610-649: Good UX improvement with conditional From field rendering.

Conditionally rendering the "From" email dropdown only when multiple aliases are available (aliases.length > 1) improves UI clarity by hiding unnecessary UI elements when they don't provide value to the user.


787-787: Good enhancement for attachment display.

Adding the max-h-[40vh] class to limit the maximum height of the attachments container provides better user experience by enabling vertical scrolling for a large number of attachments instead of extending the popover indefinitely.

apps/mail/hooks/use-threads.ts (1)

147-175: Well-implemented useThreadLabels hook.

The new hook provides a clean way to fetch label data for a list of thread IDs. The implementation includes:

  1. Proper error handling with toast notifications for a better user experience
  2. SWR configuration with appropriate caching settings (no revalidation on focus/reconnect)
  3. Input validation by only making the request when there are IDs to fetch

A minor optimization could be to add a condition to skip the fetch if the ids array is empty, but the current implementation handles this by setting the key to null.

apps/mail/config/navigation.ts (2)

158-158: Security section is now enabled

The security settings page is now accessible to users by commenting out the disabled property.


165-169: New labels settings page added

The addition of the labels navigation item is well-structured and consistent with other settings items.

apps/mail/lib/auth.ts (2)

88-107: Well-implemented user deletion flow with proper cleanup

The implementation properly handles the user deletion flow with comprehensive cleanup of user data across multiple tables.

The code correctly:

  1. Retrieves the active driver
  2. Fetches and revokes the user's refresh token
  3. Cleans up associated data from all relevant tables (account, session, connection, user)
  4. Has appropriate error handling

96-100:

Details

❓ Verification inconclusive

Verify token revocation resilience

The implementation currently stops the deletion process if token revocation fails. Consider whether the user deletion should proceed even if token revocation fails.


🏁 Script executed:

#!/bin/bash
# Analyze error handling in other auth deletion flows
rg -A 5 -B 5 "delete.*user|account.*delete" --glob "*.ts" | grep -v "node_modules"

Length of output: 4388


Consider proceeding with DB cleanup even if token revocation fails

The current flow aborts all downstream deletes when driver.revokeRefreshToken(...) returns false:

const revoked = await driver.revokeRefreshToken(refreshToken || '')
if (!revoked) {
  console.error('Failed to revoke refresh token')
  return
}

That means if revocation fails (due to network hiccup or third-party downtime), none of the following cleanup steps run:

  • delete account
  • delete sessions
  • delete connections
  • delete user record

Unless you intend to fully roll back user deletion on revoke failure, you’ll end up with orphaned data. Consider instead logging the failure (or surfacing it) but continuing with the rest of the deletion, or explicitly documenting that revoke errors should block all cleanup.

• Location: apps/mail/lib/auth.ts lines 96–100

apps/mail/components/settings/settings-card.tsx (1)

9-9: Well-implemented action prop for SettingsCard

The addition of the optional action prop enhances the component's flexibility, allowing for contextual actions to be displayed in the card header.

The implementation:

  • Properly types the action prop
  • Uses flexbox for appropriate layout
  • Maintains backward compatibility with existing usage

Also applies to: 17-17, 27-33

apps/mail/app/api/v1/thread-labels/route.ts (4)

7-21: Well-implemented authentication and rate limiting

The API route correctly implements authentication and rate limiting with appropriate error responses.


22-31: Proper request validation

The implementation correctly validates the presence of thread IDs in the request parameters.


35-45: Type safety for filtered labels

The implementation correctly uses a type predicate to ensure type safety for the filtered labels.


46-50: Complete error handling

The error handling is appropriate with proper logging and error responses.

apps/mail/components/ui/nav-main.tsx (1)

64-64: Good addition of labels hook.

The useLabels hook integration enables dynamic label fetching for the navigation sidebar.

apps/mail/locales/en.json (1)

299-300: Navigation items added correctly.

The new navigation entries for "Labels" and "Danger Zone" settings are properly added to the localization file.

apps/mail/app/api/driver/types.ts (1)

51-51: Good addition of token revocation method.

The revokeRefreshToken method with a boolean return type for success/failure is appropriate for token lifecycle management.

Comment on lines +16 to +21
const { success, message } = await auth.api.deleteUser({
body: {
callbackURL: '/',
},
headers: headersList,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify redirection after account deletion

The implementation uses a root path ('/') as the callbackURL for redirecting after account deletion. Ensure this is the intended behavior and consider whether a more specific landing page or confirmation page would be more appropriate.


🏁 Script executed:

#!/bin/bash
# Check if there's a specific landing page for post-deletion in the codebase

# Look for any references to post-deletion pages or routes
echo "Checking for post-deletion routes or pages:"
rg -i "after.*delet|post.*delet|account.*removed" --type ts --type tsx

Length of output: 197


🏁 Script executed:

#!/bin/bash
# Search for any pages/components related to deletion confirmation
echo "Searching for TS/TSX files with ‘delete’ in their name:"
find . -type f \( -iname "*delete*.ts" -o -iname "*delete*.tsx" \)

echo
echo "Searching for all occurrences of callbackURL:"
rg "callbackURL" -n

Length of output: 618


Ensure post-deletion redirect
The call in apps/mail/actions/user.ts (line 18) uses callbackURL: '/'. Currently there’s no dedicated “account deleted” or confirmation page in the codebase—other flows send users to /settings/connections (email verify) or /mail (login). Please update this to a more appropriate route or create a confirmation page, for example:

  • Create /account/deleted (or /goodbye) and set callbackURL to that.
  • Or ensure the homepage (/) clearly displays a post-deletion message.

Comment on lines 181 to 209
{!pathname.includes('/settings') && (
<Collapsible defaultOpen={true} className="group/collapsible">
<SidebarMenuItem className="mb-4" style={{ height: 'auto' }}>
<CollapsibleTrigger asChild>
<SidebarMenuButton>Labels</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent style={{ height: 'auto' }}>
<SidebarMenuSub style={{ height: 'auto' }} className="mr-0 pr-0">
<div className="space-y-1 pb-2" style={{ height: 'auto' }}>
{labels.map((label) => (
<NavItem
key={label.id}
title={label.name}
href={`/mail/inbox?q=${encodeURIComponent(label.name.toLocaleLowerCase())}`}
icon={() => (
<div
className="size-4 rounded-md"
style={{ backgroundColor: label.color?.backgroundColor || '#E2E2E2' }}
/>
)}
url={`/mail/inbox?q=${encodeURIComponent(label.name.toLocaleLowerCase())}`}
/>
))}
</div>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)}
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

Simplify NavItem props and add empty state handling.

The implementation of the collapsible Labels section is good, but has a few areas for improvement:

  1. There's duplication in the URL definition - both href and url props contain identical values
  2. There's no handling for an empty labels array or loading state
  3. Multiple inline style={{ height: 'auto' }} declarations could be consolidated with a CSS class
{!pathname.includes('/settings') && (
  <Collapsible defaultOpen={true} className="group/collapsible">
    <SidebarMenuItem className="mb-4" style={{ height: 'auto' }}>
      <CollapsibleTrigger asChild>
        <SidebarMenuButton>Labels</SidebarMenuButton>
      </CollapsibleTrigger>
      <CollapsibleContent style={{ height: 'auto' }}>
        <SidebarMenuSub style={{ height: 'auto' }} className="mr-0 pr-0">
          <div className="space-y-1 pb-2" style={{ height: 'auto' }}>
-           {labels.map((label) => (
+           {labels.length === 0 ? (
+             <p className="text-muted-foreground px-2 py-1 text-sm">No labels available</p>
+           ) : (
+             labels.map((label) => (
              <NavItem
                key={label.id}
                title={label.name}
-               href={`/mail/inbox?q=${encodeURIComponent(label.name.toLocaleLowerCase())}`}
+               href={`/mail/inbox?q=${encodeURIComponent(label.name.toLowerCase())}`}
                icon={() => (
                  <div
                    className="size-4 rounded-md"
                    style={{ backgroundColor: label.color?.backgroundColor || '#E2E2E2' }}
                  />
                )}
-               url={`/mail/inbox?q=${encodeURIComponent(label.name.toLocaleLowerCase())}`}
+               url={`/mail/inbox?q=${encodeURIComponent(label.name.toLowerCase())}`}
              />
-           ))}
+           )))}
          </div>
        </SidebarMenuSub>
      </CollapsibleContent>
    </SidebarMenuItem>
  </Collapsible>
)}
📝 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
{!pathname.includes('/settings') && (
<Collapsible defaultOpen={true} className="group/collapsible">
<SidebarMenuItem className="mb-4" style={{ height: 'auto' }}>
<CollapsibleTrigger asChild>
<SidebarMenuButton>Labels</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent style={{ height: 'auto' }}>
<SidebarMenuSub style={{ height: 'auto' }} className="mr-0 pr-0">
<div className="space-y-1 pb-2" style={{ height: 'auto' }}>
{labels.map((label) => (
<NavItem
key={label.id}
title={label.name}
href={`/mail/inbox?q=${encodeURIComponent(label.name.toLocaleLowerCase())}`}
icon={() => (
<div
className="size-4 rounded-md"
style={{ backgroundColor: label.color?.backgroundColor || '#E2E2E2' }}
/>
)}
url={`/mail/inbox?q=${encodeURIComponent(label.name.toLocaleLowerCase())}`}
/>
))}
</div>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)}
{!pathname.includes('/settings') && (
<Collapsible defaultOpen={true} className="group/collapsible">
<SidebarMenuItem className="mb-4" style={{ height: 'auto' }}>
<CollapsibleTrigger asChild>
<SidebarMenuButton>Labels</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent style={{ height: 'auto' }}>
<SidebarMenuSub style={{ height: 'auto' }} className="mr-0 pr-0">
<div className="space-y-1 pb-2" style={{ height: 'auto' }}>
{labels.length === 0 ? (
<p className="text-muted-foreground px-2 py-1 text-sm">
No labels available
</p>
) : (
labels.map((label) => (
<NavItem
key={label.id}
title={label.name}
href={`/mail/inbox?q=${encodeURIComponent(
label.name.toLowerCase()
)}`}
icon={() => (
<div
className="size-4 rounded-md"
style={{
backgroundColor:
label.color?.backgroundColor || '#E2E2E2',
}}
/>
)}
url={`/mail/inbox?q=${encodeURIComponent(
label.name.toLowerCase()
)}`}
/>
))
)}
</div>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)}

Comment on lines +39 to +49
getUserLabels(): Promise<any>;
getLabel: (labelId: string) => Promise<any>;
createLabel(label: {
name: string;
color?: { backgroundColor: string; textColor: string };
}): Promise<any>;
updateLabel(
id: string,
label: { name: string; color?: { backgroundColor: string; textColor: string } },
): Promise<any>;
deleteLabel(id: string): Promise<void>;
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

Improve type safety and consistency in interface methods.

The new label management methods are good additions, but there are a few improvements that could be made for type safety and consistency:

  1. Return types use Promise<any>, which isn't type-safe
  2. Inconsistent method declaration styles (method syntax vs. property syntax)
  3. Redundant label type definition in multiple methods
+ interface Label {
+   id?: string;
+   name: string;
+   color?: { backgroundColor: string; textColor: string };
+ }
+
- getUserLabels(): Promise<any>;
- getLabel: (labelId: string) => Promise<any>;
+ getUserLabels(): Promise<Label[]>;
+ getLabel(labelId: string): Promise<Label>;
  createLabel(label: {
    name: string;
    color?: { backgroundColor: string; textColor: string };
- }): Promise<any>;
+ }): Promise<Label>;
  updateLabel(
    id: string,
    label: { name: string; color?: { backgroundColor: string; textColor: string } },
- ): Promise<any>;
+ ): Promise<Label>;
  deleteLabel(id: string): Promise<void>;
📝 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
getUserLabels(): Promise<any>;
getLabel: (labelId: string) => Promise<any>;
createLabel(label: {
name: string;
color?: { backgroundColor: string; textColor: string };
}): Promise<any>;
updateLabel(
id: string,
label: { name: string; color?: { backgroundColor: string; textColor: string } },
): Promise<any>;
deleteLabel(id: string): Promise<void>;
// Define a reusable Label type
interface Label {
id?: string;
name: string;
color?: { backgroundColor: string; textColor: string };
}
// Updated method signatures
getUserLabels(): Promise<Label[]>;
getLabel(labelId: string): Promise<Label>;
createLabel(label: {
name: string;
color?: { backgroundColor: string; textColor: string };
}): Promise<Label>;
updateLabel(
id: string,
label: { name: string; color?: { backgroundColor: string; textColor: string } },
): Promise<Label>;
deleteLabel(id: string): Promise<void>;

Comment on lines +194 to +196
<span className="dark:text-whitemix-blend-difference text-black">
{label.name}
</span>
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

Fix incorrect CSS class name.

There's a malformed CSS class name which appears to be a concatenation error.

- <span className="dark:text-whitemix-blend-difference text-black">
+ <span className="dark:text-white mix-blend-difference text-black">
    {label.name}
  </span>
📝 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
<span className="dark:text-whitemix-blend-difference text-black">
{label.name}
</span>
<span className="dark:text-white mix-blend-difference text-black">
{label.name}
</span>

Comment on lines +60 to +66
const handleDelete = async (id: string) => {
try {
await deleteLabel(id);
} catch (err) {
console.error('Error deleting label:', err);
}
};
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 confirmation dialog and user feedback for delete action.

The delete handler lacks confirmation and user feedback.

  const handleDelete = async (id: string) => {
+   if (!confirm("Are you sure you want to delete this label?")) {
+     return;
+   }
+   
+   // Show loading state
    try {
      await deleteLabel(id);
+     toast.success("Label deleted successfully");
    } catch (err) {
      console.error('Error deleting label:', err);
+     toast.error("Failed to delete label");
    }
  };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +37 to +40
if (!labels) {
return NextResponse.json([], { status: 200 });
}
return NextResponse.json(labels.filter((label: Label) => label.type === 'user'));
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

Handle the case where labels is null or undefined more explicitly.

Currently, the code filters labels only if they're not null, but doesn't handle the case where getUserLabels() returns undefined in a clear way.

Ensure consistent handling of null or undefined labels:

    const labels = await driver.getUserLabels();
-    if (!labels) {
+    if (!labels || !Array.isArray(labels)) {
      return NextResponse.json([], { status: 200 });
    }
    return NextResponse.json(labels.filter((label: Label) => label.type === 'user'));
📝 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 (!labels) {
return NextResponse.json([], { status: 200 });
}
return NextResponse.json(labels.filter((label: Label) => label.type === 'user'));
const labels = await driver.getUserLabels();
if (!labels || !Array.isArray(labels)) {
return NextResponse.json([], { status: 200 });
}
return NextResponse.json(labels.filter((label: Label) => label.type === 'user'));

Comment on lines +70 to +71
const result = await driver?.createLabel(label);
return NextResponse.json(result);
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

Missing null check before creating a label.

The code doesn't check if the driver is available before attempting to create a label, unlike the GET handler.

Add a null check to prevent potential runtime errors:

    const driver = await getActiveDriver();
+    if (!driver) {
+      return NextResponse.json({ error: 'Email driver not configured' }, { status: 500 });
+    }
    const result = await driver?.createLabel(label);
    return NextResponse.json(result);

Apply similar checks in the PATCH and DELETE handlers as well.

📝 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 result = await driver?.createLabel(label);
return NextResponse.json(result);
const driver = await getActiveDriver();
if (!driver) {
return NextResponse.json(
{ error: 'Email driver not configured' },
{ status: 500 }
);
}
const result = await driver?.createLabel(label);
return NextResponse.json(result);

Comment on lines +975 to +980
getUserLabels: async () => {
const res = await gmail.users.labels.list({
userId: 'me',
});
return res.data.labels;
},
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

Missing error handling in getUserLabels method.

Unlike other methods in this file, getUserLabels doesn't use the withErrorHandler wrapper, which means errors won't be standardized and logged properly.

Add proper error handling to maintain consistency:

-  getUserLabels: async () => {
-    const res = await gmail.users.labels.list({
-      userId: 'me',
-    });
-    return res.data.labels;
-  },
+  getUserLabels: async () => {
+    return withErrorHandler('getUserLabels', async () => {
+      const res = await gmail.users.labels.list({
+        userId: 'me',
+      });
+      return res.data.labels;
+    });
+  },

Apply similar changes to the other new label management methods (getLabel, createLabel, updateLabel, deleteLabel).

📝 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
getUserLabels: async () => {
const res = await gmail.users.labels.list({
userId: 'me',
});
return res.data.labels;
},
getUserLabels: async () => {
return withErrorHandler('getUserLabels', async () => {
const res = await gmail.users.labels.list({
userId: 'me',
});
return res.data.labels;
});
},

Comment on lines +1027 to +1038
revokeRefreshToken: async (refreshToken: string) => {
if (!refreshToken) {
return false;
}
try {
await auth.revokeToken(refreshToken);
return true;
} catch (error: any) {
console.error('Failed to revoke Google token:', error.message);
return 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

Token revocation implementation could be improved.

The revokeRefreshToken method doesn't follow the error handling pattern used elsewhere in the file. It should use the withErrorHandler pattern for consistency and better error tracking.

Refactor the method to use the established error handling pattern:

-  revokeRefreshToken: async (refreshToken: string) => {
-    if (!refreshToken) {
-      return false;
-    }
-    try {
-      await auth.revokeToken(refreshToken);
-      return true;
-    } catch (error: any) {
-      console.error('Failed to revoke Google token:', error.message);
-      return false;
-    }
-  },
+  revokeRefreshToken: async (refreshToken: string) => {
+    if (!refreshToken) {
+      return false;
+    }
+    return withErrorHandler(
+      'revokeRefreshToken',
+      async () => {
+        await auth.revokeToken(refreshToken);
+        return true;
+      },
+      { hasRefreshToken: !!refreshToken }
+    ).catch(() => false); // Return false on any 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
revokeRefreshToken: async (refreshToken: string) => {
if (!refreshToken) {
return false;
}
try {
await auth.revokeToken(refreshToken);
return true;
} catch (error: any) {
console.error('Failed to revoke Google token:', error.message);
return false;
}
},
revokeRefreshToken: async (refreshToken: string) => {
if (!refreshToken) {
return false;
}
return withErrorHandler(
'revokeRefreshToken',
async () => {
await auth.revokeToken(refreshToken);
return true;
},
{ hasRefreshToken: !!refreshToken }
).catch(() => false); // Return false on any error
},

Comment on lines +916 to 917
const message = data.message.replace(/<br>/g, '</p><p>');
const msg = createMimeMessage();
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Review HTML transformation in createDraft method.

Replacing <br> tags with </p><p> tags might lead to invalid HTML if the original content doesn't have matching paragraph tags or if <br> tags are used within other elements.

Check how this affects different email formats:


🏁 Script executed:

#!/bin/bash
# Let's look for test files or examples that might show how drafts are formatted
echo "Searching for email/draft test files..."
fd -t f "test.*draft|draft.*test" -e ts -e tsx -e js -e jsx
echo "Searching for references to createDraft with HTML content..."
rg "createDraft.*html|createDraft.*message" -A 5 -B 5

Length of output: 337


🏁 Script executed:

#!/bin/bash
echo "Locating google.ts..."
fd -t f google.ts
echo "Showing code around lines 900–950 in apps/mail/app/api/driver/google.ts"
sed -n '900,950p' apps/mail/app/api/driver/google.ts

Length of output: 1970


Enhance HTML line-break conversion in createDraft

The current replacement

const message = data.message.replace(/<br>/g, '</p><p>');

can produce unbalanced or invalid HTML (e.g. leading </p>, missing opening <p>, or breaks inside other tags). Please adjust the logic to ensure well-formed markup (for example, wrap the entire message in <p>…</p> before splitting on </p><p>, or use a small HTML-safe utility to convert line breaks).

Please address in:

  • apps/mail/app/api/driver/google.ts around line 916 (inside createDraft)

Recommended fixes:

  • Prepend <p> and append </p> around data.message before replacing <br>
  • Or tokenize on \n/<br> and rebuild paragraphs explicitly
  • Add unit tests covering cases with:
    • Messages that already include <p> tags
    • Mixed use of <br> and other HTML elements
    • Edge cases (empty strings, no breaks)

- Updated NavItem to include an onClick prop for the Link component, allowing for custom click behavior.
- Maintained existing functionality with prefetch and target attributes.
@MrgSub MrgSub merged commit 8cbef1c into main Apr 25, 2025
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jul 4, 2025
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.

10 participants