Skip to content

Add labels in mail sidebar#358

Merged
elie222 merged 3 commits intomainfrom
mail-labels
Feb 24, 2025
Merged

Add labels in mail sidebar#358
elie222 merged 3 commits intomainfrom
mail-labels

Conversation

@elie222
Copy link
Copy Markdown
Owner

@elie222 elie222 commented Feb 24, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced mail filtering now supports specifying a label identifier for more precise query results.
    • The sidebar navigation has been improved with a toggle to display hidden labels, offering clearer organization.
    • Navigation items now better indicate active states for a more intuitive user experience.
    • Development guidelines have been documented in a new markdown file, providing clarity on code style and component usage.
  • Improvements

    • The useLabels hook now supports an option to include hidden labels, enhancing label management capabilities.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 24, 2025

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

Name Status Preview Updated (UTC)
inbox-zero ❌ Failed (Inspect) Feb 24, 2025 7:39pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 24, 2025

Warning

Rate limit exceeded

@elie222 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 22 minutes and 8 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 91258c4 and 5a9d3e1.

📒 Files selected for processing (2)
  • apps/web/components/SideNav.tsx (5 hunks)
  • apps/web/hooks/useLabels.ts (1 hunks)

Walkthrough

This pull request adds a new development guidelines document (CLAUDE.md) and extends label-related functionality across the app. The mail page now accepts an optional labelId in its search parameters, and the threads API endpoints and validation schema are updated accordingly. Additionally, the sidebar navigation is enhanced with a toggle for hidden labels via state management and hook adjustments. Minor type refinements are made in the navigation and label hooks to support these new features.

Changes

File(s) Change Summary
apps/web/CLAUDE.md New guidelines document outlining build/test commands, code style, component guidelines, and environment variable practices.
apps/web/app/(app)/mail/page.tsx Extended searchParams to include an optional labelId and updated query logic to handle label-based queries.
apps/web/app/api/google/threads/{controller.ts, route.ts, validation.ts} Modified thread retrieval: In the controller, conditionally use labelId; in route, extract labelId from the request; in validation, add an optional labelId property to the schema.
apps/web/components/SideNav.tsx Introduced state (showHiddenLabels) and UI elements to toggle display of hidden labels; integrated updated useLabels hook to fetch both visible and hidden labels.
apps/web/components/SideNavMenu.tsx Added an optional active property to the NavItem type to enhance active state evaluation for sidebar menu items.
apps/web/hooks/useLabels.ts Extended the UserLabel type with optional visibility properties; updated the hook to accept an includeHidden option, separate labels into visible/hidden, and sort them accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant M as Mail Component
    participant R as Threads Route
    participant C as Threads Controller
    participant V as Validation
    participant API as Google Threads API

    U->>M: Initiate request with searchParams (type, labelId)
    M->>R: Send API request with query parameters
    R->>R: Extract labelId and build query object
    R->>C: Pass query to controller
    C->>V: Validate query (including optional labelId)
    V-->>C: Return validated query
    C->>API: Fetch threads using labelId or type
    API-->>C: Return threads data
    C-->>M: Return threads data
    M-->>U: Render threads
Loading
sequenceDiagram
    participant U as User
    participant S as SideNav
    participant H as useLabels Hook

    U->>S: Click toggle for hidden labels
    S->>S: Update showHiddenLabels state
    S->>H: Invoke useLabels({ includeHidden: true/false })
    H->>H: Fetch and sort user labels into visible and hidden
    H-->>S: Return visible and hidden labels
    S-->>U: Update label navigation display
Loading

Poem

I'm a little rabbit, hopping with glee,
New guidelines and labels set the code free.
Toggling hidden treasures with a joyful bound,
In TypeScript meadows, clarity is found.
With each new change, our code garden grows,
Hoppy coding vibes wherever it flows!


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 generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
apps/web/app/(app)/mail/page.tsx (1)

18-18: Consider using zod for runtime type validation.

The searchParams type could benefit from runtime validation to ensure type safety at the API boundary.

+import { z } from "zod";

+const searchParamsSchema = z.object({
+  type: z.string().optional(),
+  labelId: z.string().optional(),
+});

 export default function Mail({
   searchParams,
 }: {
-  searchParams: { type?: string; labelId?: string };
+  searchParams: z.infer<typeof searchParamsSchema>;
apps/web/CLAUDE.md (2)

17-17: Fix Next.js spelling.

The official spelling is "Next.js" rather than "NextJS".

-NextJS app router structure with (app) directory
+Next.js app router structure with (app) directory
🧰 Tools
🪛 LanguageTool

[uncategorized] ~17-~17: The official spelling of this programming framework is “Next.js”.
Context: ...se @/ for imports from project root - NextJS app router structure with (app) directo...

(NODE_JS)


27-27: Add missing articles.

Add missing articles "a" and "the" for better readability.

-Ensure responsive design with mobile-first approach
+Ensure responsive design with a mobile-first approach
-Use LoadingContent component for async data:
+Use the LoadingContent component for async data:

Also applies to: 30-30

🧰 Tools
🪛 LanguageTool

[uncategorized] ~27-~27: You might be missing the article “a” here.
Context: ...ailable - Ensure responsive design with mobile-first approach - Follow consistent nami...

(AI_EN_LECTOR_MISSING_DETERMINER_A)

apps/web/components/SideNav.tsx (1)

338-349: Enhance button accessibility.

The toggle button for hidden labels should have an aria-label and aria-expanded attribute.

 <button
   type="button"
   onClick={() => setShowHiddenLabels(!showHiddenLabels)}
+  aria-label="Toggle hidden labels"
+  aria-expanded={showHiddenLabels}
   className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
 >
apps/web/hooks/useLabels.ts (1)

6-12: Consider making visibility properties more type-safe.

The visibility properties could use string literals for better type safety.

 export type UserLabel = {
   id: string;
   name: string;
   type: "user";
-  labelListVisibility?: string;
-  messageListVisibility?: string;
+  labelListVisibility?: "labelShow" | "labelHide" | "labelShowIfUnread";
+  messageListVisibility?: "show" | "hide";
 };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d460191 and 05a4573.

📒 Files selected for processing (8)
  • apps/web/CLAUDE.md (1 hunks)
  • apps/web/app/(app)/mail/page.tsx (1 hunks)
  • apps/web/app/api/google/threads/controller.ts (1 hunks)
  • apps/web/app/api/google/threads/route.ts (1 hunks)
  • apps/web/app/api/google/threads/validation.ts (1 hunks)
  • apps/web/components/SideNav.tsx (5 hunks)
  • apps/web/components/SideNavMenu.tsx (2 hunks)
  • apps/web/hooks/useLabels.ts (1 hunks)
🧰 Additional context used
🪛 LanguageTool
apps/web/CLAUDE.md

[uncategorized] ~17-~17: The official spelling of this programming framework is “Next.js”.
Context: ...se @/ for imports from project root - NextJS app router structure with (app) directo...

(NODE_JS)


[uncategorized] ~27-~27: You might be missing the article “a” here.
Context: ...ailable - Ensure responsive design with mobile-first approach - Follow consistent nami...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~30-~30: You might be missing the article “the” here.
Context: ... dedicated type files when shared - Use LoadingContent component for async data: ```tsx <L...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

🔇 Additional comments (10)
apps/web/components/SideNavMenu.tsx (2)

18-18: LGTM! The new active property enhances control over navigation states.

The optional active property provides explicit control over a nav item's active state, independent of URL matching.


34-34: LGTM! The active state logic is correctly implemented.

The isActive prop now considers both the explicit active property and URL matching, providing flexible control over navigation highlighting.

apps/web/app/api/google/threads/validation.ts (1)

9-9: LGTM! The schema correctly defines the optional label ID property.

The labelId property is properly defined as a nullable string, maintaining consistency with other query parameters and Gmail's API requirements.

apps/web/app/api/google/threads/controller.ts (1)

47-47: LGTM! The label handling logic is well-implemented.

The code correctly prioritizes explicit label IDs while maintaining backward compatibility with type-based label filtering.

apps/web/app/api/google/threads/route.ts (2)

17-17: LGTM! The label ID parameter is correctly extracted.

The code properly extracts the optional label ID from search parameters.


24-24: LGTM! The label ID is properly included in query validation.

The extracted label ID is correctly passed to the validation schema.

apps/web/app/(app)/mail/page.tsx (1)

20-27: LGTM! Clean query parameter handling.

The query construction logic is well-organized and handles the parameters appropriately.

apps/web/CLAUDE.md (1)

1-41: LGTM! Comprehensive development guidelines.

The guidelines provide clear instructions for development practices, component usage, and environment configuration.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~17-~17: The official spelling of this programming framework is “Next.js”.
Context: ...se @/ for imports from project root - NextJS app router structure with (app) directo...

(NODE_JS)


[uncategorized] ~27-~27: You might be missing the article “a” here.
Context: ...ailable - Ensure responsive design with mobile-first approach - Follow consistent nami...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~30-~30: You might be missing the article “the” here.
Context: ... dedicated type files when shared - Use LoadingContent component for async data: ```tsx <L...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

apps/web/components/SideNav.tsx (1)

267-269: LGTM! Well-structured label management.

The implementation of label management is clean and efficient:

  • Good use of useMemo for performance optimization
  • Clear separation of visible and hidden labels
  • Proper URL parameter handling

Also applies to: 272-283, 286-297

apps/web/hooks/useLabels.ts (1)

51-60: LGTM! Efficient label sorting implementation.

The sorting logic is well-implemented:

  • Handles edge cases with null/undefined names
  • Properly orders bracketed labels
  • Uses localeCompare for correct string comparison

Copy link
Copy Markdown
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: 3

🧹 Nitpick comments (3)
apps/web/hooks/useLabels.ts (2)

7-13: Add JSDoc documentation for the UserLabel type.

Consider adding documentation to explain the purpose of labelListVisibility and messageListVisibility properties, their possible values, and their impact on label visibility.

+/**
+ * Represents a user-created Gmail label.
+ * @property labelListVisibility - Controls label visibility in the label list (e.g., 'labelShow', 'labelHide')
+ * @property messageListVisibility - Controls label visibility in the message list
+ */
 export type UserLabel = {
   id: string;
   name: string;
   type: "user";
   labelListVisibility?: string;
   messageListVisibility?: string;
 };

48-57: Add null checks in sortLabels function.

While the function handles empty strings, it should also handle null or undefined values to prevent potential runtime errors.

 function sortLabels(a: UserLabel, b: UserLabel) {
-  const aName = a.name || "";
-  const bName = b.name || "";
+  const aName = a?.name ?? "";
+  const bName = b?.name ?? "";

   // Order words that start with [ at the end
   if (aName.startsWith("[") && !bName.startsWith("[")) return 1;
   if (!aName.startsWith("[") && bName.startsWith("[")) return -1;

   return aName.localeCompare(bName);
 }
apps/web/components/SideNav.tsx (1)

272-283: Optimize label transformation performance.

Consider moving the URL parsing logic outside the memoized function to prevent unnecessary re-parsing on each render.

+const getSearchParams = (path: string) => new URLSearchParams(path.split("?")[1] || "");

 const labelNavItems = useMemo(() => {
-  const searchParams = new URLSearchParams(path.split("?")[1] || "");
+  const searchParams = getSearchParams(path);
   const currentLabelId = searchParams.get("labelId");

   return userLabels.map((label) => ({
     name: label.name,
     icon: TagIcon,
     href: `?type=label&labelId=${encodeURIComponent(label.id)}`,
     active: currentLabelId === label.id,
   }));
 }, [userLabels, path]);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 05a4573 and 91258c4.

📒 Files selected for processing (2)
  • apps/web/components/SideNav.tsx (5 hunks)
  • apps/web/hooks/useLabels.ts (1 hunks)
🔇 Additional comments (1)
apps/web/components/SideNav.tsx (1)

12-13: LGTM!

The new imports are appropriate for the added functionality.

Also applies to: 51-51

Comment on lines +26 to +36
const { userLabels, hiddenUserLabels } = useMemo(() => {
// Always get visible labels
const visible = allUserLabels.filter(isHiddenLabel).sort(sortLabels);

// Only process hidden labels if needed
const hidden = options.includeHidden
? allUserLabels.filter(isHiddenLabel).sort(sortLabels)
: [];

return { userLabels: visible, hiddenUserLabels: hidden };
}, [allUserLabels, options.includeHidden]);
Copy link
Copy Markdown
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 label filtering logic.

The current implementation has a bug where isHiddenLabel is used for both visible and hidden labels, which will result in incorrect filtering. The visible labels should use the opposite condition.

 const { userLabels, hiddenUserLabels } = useMemo(() => {
   // Always get visible labels
-  const visible = allUserLabels.filter(isHiddenLabel).sort(sortLabels);
+  const visible = allUserLabels.filter((label) => !isHiddenLabel(label)).sort(sortLabels);

   // Only process hidden labels if needed
   const hidden = options.includeHidden
     ? allUserLabels.filter(isHiddenLabel).sort(sortLabels)
     : [];

   return { userLabels: visible, hiddenUserLabels: hidden };
 }, [allUserLabels, options.includeHidden]);
📝 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 { userLabels, hiddenUserLabels } = useMemo(() => {
// Always get visible labels
const visible = allUserLabels.filter(isHiddenLabel).sort(sortLabels);
// Only process hidden labels if needed
const hidden = options.includeHidden
? allUserLabels.filter(isHiddenLabel).sort(sortLabels)
: [];
return { userLabels: visible, hiddenUserLabels: hidden };
}, [allUserLabels, options.includeHidden]);
const { userLabels, hiddenUserLabels } = useMemo(() => {
// Always get visible labels
const visible = allUserLabels.filter((label) => !isHiddenLabel(label)).sort(sortLabels);
// Only process hidden labels if needed
const hidden = options.includeHidden
? allUserLabels.filter(isHiddenLabel).sort(sortLabels)
: [];
return { userLabels: visible, hiddenUserLabels: hidden };
}, [allUserLabels, options.includeHidden]);

Comment on lines +326 to +334
<LoadingContent loading={isLoading}>
{userLabels.length > 0 ? (
<SideNavMenu items={labelNavItems} activeHref={path} />
) : (
<div className="px-3 py-2 text-xs text-muted-foreground">
No labels
</div>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for the loading state.

The LoadingContent component should handle error states from the useLabels hook.

-<LoadingContent loading={isLoading}>
+<LoadingContent loading={isLoading} error={error}>
   {userLabels.length > 0 ? (
     <SideNavMenu items={labelNavItems} activeHref={path} />
   ) : (
     <div className="px-3 py-2 text-xs text-muted-foreground">
-      No labels
+      {error ? 'Failed to load labels' : 'No labels'}
     </div>
   )}
📝 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
<LoadingContent loading={isLoading}>
{userLabels.length > 0 ? (
<SideNavMenu items={labelNavItems} activeHref={path} />
) : (
<div className="px-3 py-2 text-xs text-muted-foreground">
No labels
</div>
)}
<LoadingContent loading={isLoading} error={error}>
{userLabels.length > 0 ? (
<SideNavMenu items={labelNavItems} activeHref={path} />
) : (
<div className="px-3 py-2 text-xs text-muted-foreground">
{error ? 'Failed to load labels' : 'No labels'}
</div>
)}

Comment on lines +338 to +349
<button
type="button"
onClick={() => setShowHiddenLabels(!showHiddenLabels)}
className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
>
{showHiddenLabels ? (
<ChevronDownIcon className="mr-1 size-4" />
) : (
<ChevronRightIcon className="mr-1 size-4" />
)}
<span>More</span>
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes to the toggle button.

The toggle button for hidden labels should have proper accessibility attributes for better screen reader support.

 <button
   type="button"
   onClick={() => setShowHiddenLabels(!showHiddenLabels)}
+  aria-expanded={showHiddenLabels}
+  aria-controls="hidden-labels-menu"
   className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
 >
📝 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
<button
type="button"
onClick={() => setShowHiddenLabels(!showHiddenLabels)}
className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
>
{showHiddenLabels ? (
<ChevronDownIcon className="mr-1 size-4" />
) : (
<ChevronRightIcon className="mr-1 size-4" />
)}
<span>More</span>
</button>
<button
type="button"
onClick={() => setShowHiddenLabels(!showHiddenLabels)}
aria-expanded={showHiddenLabels}
aria-controls="hidden-labels-menu"
className="flex w-full items-center px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
>
{showHiddenLabels ? (
<ChevronDownIcon className="mr-1 size-4" />
) : (
<ChevronRightIcon className="mr-1 size-4" />
)}
<span>More</span>
</button>

@elie222
Copy link
Copy Markdown
Owner Author

elie222 commented Feb 24, 2025

Closes: #347

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