Skip to content

Conversation

@BengeeL
Copy link
Member

@BengeeL BengeeL commented May 19, 2025

Summary by CodeRabbit

  • New Features
    • Introduced centralized Search and Subscription services for improved API interactions and error handling in search and subscription workflows.
    • Added a new search results component to display users and content with incremental loading and duplicate filtering.
  • Refactor
    • Replaced direct API calls with service modules across search and subscription features for better maintainability and consistency.
    • Improved error handling and state management in search and subscription components.
    • Renamed components and variables for clarity and consistency (e.g., SearchListContent → SearchListResults).
    • Updated routing and component hierarchy for enhanced navigation and logout behavior.
  • Bug Fixes
    • Enhanced error handling and robustness in search and subscription flows, reducing the likelihood of unhandled errors.
  • Chores
    • Updated Stripe API version for payment processing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 19, 2025

Walkthrough

This update introduces new service abstractions for search and subscription API interactions in the frontend, refactors related components to use these services, and standardizes error handling. The backend search controller and service are refactored for clarity, consistency, and improved error handling. Minor import path and API version updates are included, and some component and method names are renamed for clarity.

Changes

File(s) Change Summary
backend/src/modules/search/controllers/search.controller.ts Refactored search controller methods for explicit parameter extraction, improved error handling, consistent logging, and renamed searchContent to searchContents.
backend/src/modules/search/routes/search.routes.ts Updated route paths to remove trailing slashes and changed handler references to match renamed controller methods.
backend/src/modules/search/services/search.service.ts Improved type safety, consolidated query logic, renamed variables and methods for clarity, standardized logging, and updated return types.
backend/src/modules/subscription/services/stripe.service.ts Updated Stripe API version string.
frontend/src/App.tsx Swapped nesting order of Router and AuthProvider in the component tree.
frontend/src/components/Navbar.tsx Refactored to use new SearchService and SubscriptionService, centralized API calls, improved error handling and input validation, and updated imports.
frontend/src/components/feed/ContentList.tsx
frontend/src/pages/content/ContentView.tsx
Updated import path for normalizeContentDates from services to utils.
frontend/src/components/search/SearchListResults.tsx Added new component to display search results, using SearchService for fetching, with error handling, duplicate filtering, pagination, and lazy loading.
frontend/src/hooks/AuthProvider.tsx Added navigation to login page after logout using useNavigate.
frontend/src/pages/pro/ManageSubscription.tsx
frontend/src/pages/pro/ProDetails.tsx
frontend/src/pages/pro/SubscribePro.tsx
Refactored subscription logic to use SubscriptionService, removed direct axios calls, and standardized error handling.
frontend/src/services/SearchService.ts New file: Added SearchService class with static methods for user and content search, centralized error handling, and pagination support.
frontend/src/services/SubscriptionService.ts New file: Added SubscriptionService class with static methods for subscription session creation, cancellation, and status retrieval, with consistent error handling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Navbar
    participant SearchService
    participant BackendAPI

    User->>Navbar: Enter search query
    Navbar->>SearchService: searchUsers(query, startingPoint)
    SearchService->>BackendAPI: GET /search/users?searchText=...&userStartingPoint=...
    BackendAPI-->>SearchService: { users, nextStartingPoint }
    SearchService-->>Navbar: { users, nextStartingPoint } or Error

    Navbar->>SearchService: searchContents(query)
    SearchService->>BackendAPI: GET /search/contents?searchText=...
    BackendAPI-->>SearchService: { contents, nextStartingPoint }
    SearchService-->>Navbar: { contents, nextStartingPoint } or Error

    Navbar-->>User: Display search results
Loading
sequenceDiagram
    participant User
    participant ProPage
    participant SubscriptionService
    participant BackendAPI

    User->>ProPage: Click "Subscribe" or "Check Status"
    ProPage->>SubscriptionService: getSubscriptionStatus()
    SubscriptionService->>BackendAPI: GET /subscription/status
    BackendAPI-->>SubscriptionService: { status, ... }
    SubscriptionService-->>ProPage: status or Error

    alt User clicks "Subscribe"
        ProPage->>SubscriptionService: createSubscriptionSession()
        SubscriptionService->>BackendAPI: POST /subscription/session
        BackendAPI-->>SubscriptionService: { url }
        SubscriptionService-->>ProPage: url or Error
        ProPage-->>User: Redirect to Stripe Checkout
    end

    alt User clicks "Cancel Subscription"
        ProPage->>SubscriptionService: cancelSubscription()
        SubscriptionService->>BackendAPI: POST /subscription/cancel
        BackendAPI-->>SubscriptionService: { message, willEndOn }
        SubscriptionService-->>ProPage: message or Error
        ProPage-->>User: Show cancellation result
    end
Loading

Poem

In fields of code where searches bloom,
New services now clear the room.
With errors caught and routes aligned,
The rabbits hop—refactored, kind!
Subscriptions flow with gentle ease,
And search results arrive to please.
🐇✨ Hooray for tidy code!

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 242c25b and bbf31eb.

📒 Files selected for processing (1)
  • frontend/src/components/search/SearchListResults.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/search/SearchListResults.tsx

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.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
frontend/src/pages/pro/ManageSubscription.tsx (1)

27-45: 🛠️ Refactor suggestion

fetchSubscriptionStatus missing try / finally leaves loading spinner forever on unhandled throw.

SubscriptionService.getSubscriptionStatus should swallow Axios errors and return Error, but a regression could re-throw.
Guard against that and always clear loading.

-  const subscriptionStatus = await SubscriptionService.getSubscriptionStatus(
-    forceRefresh
-  );
-
-  setLoading(false);
+  try {
+    const subscriptionStatus =
+      await SubscriptionService.getSubscriptionStatus(forceRefresh);
+    if (subscriptionStatus instanceof Error) {
+      setError(subscriptionStatus.message);
+    } else {
+      if (subscriptionStatus.status === "canceled") {
+        setCancelSuccess(false);
+      }
+      setSubscription(subscriptionStatus);
+    }
+  } catch (err) {
+    setError("Unexpected error while fetching subscription status.");
+  } finally {
+    setLoading(false);
+  }
🧹 Nitpick comments (11)
frontend/src/components/search/SearchListContent.tsx (2)

69-73: Avoid blocking alerts in async code paths.

Using alert("Already Fetching!") will interrupt the user’s flow, feels jarring on web, and is not accessible.
Prefer a non-blocking notification (toast / inline message) or silent return.

-  alert("Already Fetching!");
+  // TODO: surface this information via a toast or inline message

74-113: Shared fetching flag can block parallel user & content requests.

fetchUserData guards itself with fetching, but fetchContentData sets the same flag to true without the guard.
If fetchUserData is running, fetchContentData starts, but subsequent user-initiated “fetch more” clicks will bail out because fetching is still true.
Consider a per-endpoint flag (fetchingUsers, fetchingContent) or just removing the guard since back-to-back clicks are already throttled by the disabled button.

frontend/src/components/Navbar.tsx (2)

121-125: Run searches concurrently for better UX.

The user & content searches are independent; firing them sequentially doubles the perceived latency.
A small refactor with Promise.all keeps the same error-handling style but halves waiting time.

-  const userSearchResults = await SearchService.searchUsers(trimmedQuery);
-  const contentSearchResults = await SearchService.searchContents(trimmedQuery);
+  const [userSearchResults, contentSearchResults] = await Promise.all([
+    SearchService.searchUsers(trimmedQuery),
+    SearchService.searchContents(trimmedQuery),
+  ]);

127-141: Reduce duplicated error-handling boilerplate.

Both result blocks follow the pattern “if instanceof Error → log, else → setState”.
Wrapping this in a helper would make the function shorter and easier to reason about.

No code diff provided to avoid over-refactor noise.

frontend/src/pages/pro/ManageSubscription.tsx (2)

48-52: Early-exit guard duplicates navigation logic.

handleCancelSubscription copies the auth check present in the parent route guard (useEffect).
If the user somehow gets here unauthenticated, the UI already redirects.
Consider removing the duplication or extracting a shared helper so the behaviour can’t drift.


64-66: currentSubscription may hold stale data after async await.

Between storing currentSubscription and using it inside the setSubscription state updater, another state update could have fired, making the snapshot obsolete.
Capture it inside the functional update instead:

- const currentSubscription = subscription ? { ...subscription } : null;
-
- setSubscription((prev) => {
-   const base = prev ?? currentSubscription;
+ setSubscription((prev) => {
+   const base = prev ?? (subscription ? { ...subscription } : null);
frontend/src/services/SearchService.ts (3)

6-50: Refactor class to use standalone functions

The class implementation is clear and functions well, but the static analysis tool correctly points out that this class only contains static methods. This pattern can be simplified.

Consider refactoring to use standalone functions instead of a class with static methods:

-export class SearchService {
+/**
+ * searchUsers(query: string) -> Promise<User[] | Error>
+ *
+ * @description
+ * Searches for users based on the provided query string.
+ *
+ * @param query - The search query string.
+ * @returns A promise that resolves to an array of User objects or an Error.
+ */
+export async function searchUsers(
+  searchText: string,
+  userStartingPoint: string | null = null
+): Promise<{ users: User[]; nextStartingPoint: string } | Error> {
+  try {
+    const response = await axios.get(`${apiURL}/search/users`, {
+      params: { searchText, userStartingPoint },
+    });
+
+    return response.data;
+  } catch (error) {
+    if (error instanceof Error) {
+      return new Error("Failed to fetch user data: " + error.message);
+    }
+    return new Error("Failed to fetch user data: Unknown error");
+  }
+}

+export async function searchContents(
+  searchText: string
+): Promise<{ contents: Content[]; nextStartingPoint: string } | Error> {
+  try {
+    const response = await axios.get(`${apiURL}/search/contents`, {
+      params: { searchText },
+    });
+
+    return response.data;
+  } catch (error) {
+    if (error instanceof Error) {
+      return new Error("Failed to fetch content data: " + error.message);
+    }
+    return new Error("Failed to fetch content data: Unknown error");
+  }
+}
-  /**
-   * searchUsers(query: string) -> Promise<User[] | Error>
-   *
-   * @description
-   * Searches for users based on the provided query string.
-   *
-   * @param query - The search query string.
-   * @returns A promise that resolves to an array of User objects or an Error.
-   */
-  static async searchUsers(
-    searchText: string,
-    userStartingPoint: string | null = null
-  ): Promise<{ users: User[]; nextStartingPoint: string } | Error> {
-    try {
-      const response = await axios.get(`${apiURL}/search/users`, {
-        params: { searchText, userStartingPoint },
-      });
-
-      return response.data;
-    } catch (error) {
-      if (error instanceof Error) {
-        return new Error("Failed to fetch user data: " + error.message);
-      }
-      return new Error("Failed to fetch user data: Unknown error");
-    }
-  }
-
-  static async searchContents(
-    searchText: string
-  ): Promise<{ contents: Content[]; nextStartingPoint: string } | Error> {
-    try {
-      const response = await axios.get(`${apiURL}/search/contents`, {
-        params: { searchText },
-      });
-
-      return response.data;
-    } catch (error) {
-      if (error instanceof Error) {
-        return new Error("Failed to fetch content data: " + error.message);
-      }
-      return new Error("Failed to fetch content data: Unknown error");
-    }
-  }
-}
🧰 Tools
🪛 Biome (1.9.4)

[error] 6-50: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


16-32: Consider using the axios.isAxiosError type guard

For more precise error handling, use axios's built-in type guard for Axios errors.

  static async searchUsers(
    searchText: string,
    userStartingPoint: string | null = null
  ): Promise<{ users: User[]; nextStartingPoint: string } | Error> {
    try {
      const response = await axios.get(`${apiURL}/search/users`, {
        params: { searchText, userStartingPoint },
      });

      return response.data;
    } catch (error: unknown) {
-      if (error instanceof Error) {
+      if (axios.isAxiosError(error)) {
+        return new Error(
+          `Failed to fetch user data: ${
+            error.response?.data?.message || error.message
+          }`
+        );
-        return new Error("Failed to fetch user data: " + error.message);
      }
      return new Error("Failed to fetch user data: Unknown error");
    }
  }

34-49: Add missing JSDoc comment

The searchContents method is missing JSDoc documentation unlike the searchUsers method.

Add JSDoc documentation for consistency:

+  /**
+   * searchContents(query: string) -> Promise<Content[] | Error>
+   *
+   * @description
+   * Searches for content based on the provided query string.
+   *
+   * @param searchText - The search query string.
+   * @returns A promise that resolves to an array of Content objects or an Error.
+   */
  static async searchContents(
    searchText: string
  ): Promise<{ contents: Content[]; nextStartingPoint: string } | Error> {
    try {
      const response = await axios.get(`${apiURL}/search/contents`, {
        params: { searchText },
      });

      return response.data;
    } catch (error) {
      if (error instanceof Error) {
        return new Error("Failed to fetch content data: " + error.message);
      }
      return new Error("Failed to fetch content data: Unknown error");
    }
  }
frontend/src/services/SubscriptionService.ts (2)

5-104: Refactor class to use standalone functions

The static analysis tool correctly points out that this class only contains static methods. This pattern can be simplified.

Consider refactoring to use standalone functions instead of a class with static methods:

-export class SubscriptionService {
+/**
+ * Creates a subscription session for the user.
+ *
+ * @returns A promise that resolves to the response containing the session URL.
+ * @returns {Promise<{url: string}>} The checkout session URL on success
+ * @returns {Error} Error object with message if the request fails
+ */
+export async function createSubscriptionSession(): Promise<{ url: string } | Error> {
+  try {
+    const response = await axios.post(
+      `${apiURL}/subscription/create-checkout-session`,
+      {},
+      {
+        withCredentials: true,
+      }
+    );
+
+    return response.data;
+  } catch (error: unknown) {
+    if (axios.isAxiosError(error)) {
+      return new Error(
+        `Failed to create subscription session: ${
+          error.response?.data?.message || error.message
+        }`
+      );
+    }
+    return new Error("Failed to create subscription session: Unknown error");
+  }
+}

+/**
+ * Cancels the user's subscription.
+ *
+ * @returns A promise that resolves to a response containing the cancellation message and end date.
+ * @returns {Promise<{message: string, willEndOn: Date}>} The cancellation message and end date on success
+ * @returns {Error} Error object with message if the request fails
+ */
+export async function cancelSubscription(): Promise<
+  | {
+      message: string;
+      willEndOn: Date;
+    }
+  | Error
+> {
+  try {
+    const response = await axios.post(
+      `${apiURL}/subscription/cancel`,
+      {},
+      {
+        withCredentials: true,
+      }
+    );
+
+    return response.data;
+  } catch (error: unknown) {
+    if (axios.isAxiosError(error)) {
+      return new Error(
+        `Failed to cancel subscription: ${
+          error.response?.data?.message || error.message
+        }`
+      );
+    }
+    return new Error("Failed to cancel subscription: Unknown error");
+  }
+}

+/**
+ * Retrieves the subscription status for the user.
+ *
+ * @param forceRefresh - Whether to force a refresh of the subscription status.
+ * @returns A promise that resolves to the subscription status or an error.
+ * @returns {Promise<SubscriptionStatus | Error>} The subscription status on success
+ * @returns {Error} Error object with message if the request fails
+ */
+export async function getSubscriptionStatus(
+  forceRefresh: boolean = false
+): Promise<SubscriptionStatus | Error> {
+  try {
+    const url = forceRefresh
+      ? `${apiURL}/subscription/status?forceRefresh=true&t=${new Date().getTime()}`
+      : `${apiURL}/subscription/status`;
+
+    const response = await axios.get(url, {
+      withCredentials: true,
+    });
+
+    return response.data;
+  } catch (error: unknown) {
+    if (axios.isAxiosError(error)) {
+      return new Error(
+        `Failed to get subscription status: ${
+          error.response?.data?.message || error.message
+        }`
+      );
+    }
+    return new Error("Failed to get subscription status: Unknown error");
+  }
+}
-  /**
-   * Creates a subscription session for the user.
-   *
-   * @returns A promise that resolves to the response containing the session URL.
-   * @returns {Promise<{url: string}>} The checkout session URL on success
-   * @returns {Error} Error object with message if the request fails
-   */
-  static async createSubscriptionSession(): Promise<{ url: string } | Error> {
-    try {
-      const response = await axios.post(
-        `${apiURL}/subscription/create-checkout-session`,
-        {},
-        {
-          withCredentials: true,
-        }
-      );
-
-      return response.data;
-    } catch (error: unknown) {
-      if (axios.isAxiosError(error)) {
-        return new Error(
-          `Failed to create subscription session: ${
-            error.response?.data?.message || error.message
-          }`
-        );
-      }
-      return new Error("Failed to create subscription session: Unknown error");
-    }
-  }
-
-  /**
-   * Cancels the user's subscription.
-   *
-   * @returns A promise that resolves to a response containing the cancellation message and end date.
-   * @returns {Promise<{message: string, willEndOn: Date}>} The cancellation message and end date on success
-   * @returns {Error} Error object with message if the request fails
-   */
-  static async cancelSubscription(): Promise<
-    | {
-        message: string;
-        willEndOn: Date;
-      }
-    | Error
-  > {
-    try {
-      const response = await axios.post(
-        `${apiURL}/subscription/cancel`,
-        {},
-        {
-          withCredentials: true,
-        }
-      );
-
-      return response.data;
-    } catch (error: unknown) {
-      if (axios.isAxiosError(error)) {
-        return new Error(
-          `Failed to cancel subscription: ${
-            error.response?.data?.message || error.message
-          }`
-        );
-      }
-      return new Error("Failed to cancel subscription: Unknown error");
-    }
-  }
-
-  /**
-   * Retrieves the subscription status for the user.
-   *
-   * @param forceRefresh - Whether to force a refresh of the subscription status.
-   * @returns A promise that resolves to the subscription status or an error.
-   * @returns {Promise<SubscriptionStatus | Error>} The subscription status on success
-   * @returns {Error} Error object with message if the request fails
-   */
-  static async getSubscriptionStatus(
-    forceRefresh: boolean = false
-  ): Promise<SubscriptionStatus | Error> {
-    try {
-      const url = forceRefresh
-        ? `${apiURL}/subscription/status?forceRefresh=true&t=${new Date().getTime()}`
-        : `${apiURL}/subscription/status`;
-
-      const response = await axios.get(url, {
-        withCredentials: true,
-      });
-
-      return response.data;
-    } catch (error: unknown) {
-      if (axios.isAxiosError(error)) {
-        return new Error(
-          `Failed to get subscription status: ${
-            error.response?.data?.message || error.message
-          }`
-        );
-      }
-      return new Error("Failed to get subscription status: Unknown error");
-    }
-  }
-}
🧰 Tools
🪛 Biome (1.9.4)

[error] 5-104: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


84-87: Improve URL construction with URLSearchParams

The manual string concatenation for URL parameters could be more robust using URLSearchParams.

  try {
-      const url = forceRefresh
-        ? `${apiURL}/subscription/status?forceRefresh=true&t=${new Date().getTime()}`
-        : `${apiURL}/subscription/status`;
+      const url = new URL(`${apiURL}/subscription/status`);
+      if (forceRefresh) {
+        url.searchParams.append('forceRefresh', 'true');
+        url.searchParams.append('t', new Date().getTime().toString());
+      }

      const response = await axios.get(url, {
        withCredentials: true,
      });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0a55780 and 291a657.

📒 Files selected for processing (14)
  • backend/src/modules/search/controllers/search.controller.ts (1 hunks)
  • backend/src/modules/search/routes/search.routes.ts (1 hunks)
  • backend/src/modules/search/services/search.service.ts (4 hunks)
  • frontend/src/App.tsx (2 hunks)
  • frontend/src/components/Navbar.tsx (3 hunks)
  • frontend/src/components/feed/ContentList.tsx (1 hunks)
  • frontend/src/components/search/SearchListContent.tsx (4 hunks)
  • frontend/src/hooks/AuthProvider.tsx (2 hunks)
  • frontend/src/pages/content/ContentView.tsx (1 hunks)
  • frontend/src/pages/pro/ManageSubscription.tsx (4 hunks)
  • frontend/src/pages/pro/ProDetails.tsx (2 hunks)
  • frontend/src/pages/pro/SubscribePro.tsx (3 hunks)
  • frontend/src/services/SearchService.ts (1 hunks)
  • frontend/src/services/SubscriptionService.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
backend/src/modules/search/routes/search.routes.ts (1)
backend/src/modules/search/controllers/search.controller.ts (1)
  • SearchController (6-63)
frontend/src/App.tsx (1)
frontend/src/hooks/AuthProvider.tsx (1)
  • AuthProvider (10-95)
frontend/src/pages/pro/ProDetails.tsx (1)
frontend/src/services/SubscriptionService.ts (1)
  • SubscriptionService (5-104)
frontend/src/pages/pro/ManageSubscription.tsx (1)
frontend/src/services/SubscriptionService.ts (1)
  • SubscriptionService (5-104)
backend/src/modules/search/services/search.service.ts (3)
backend/src/shared/utils/logger.ts (1)
  • logger (14-36)
backend/src/shared/config/firebase.config.ts (1)
  • db (31-31)
backend/src/modules/user/models/user.model.ts (1)
  • User (1-33)
frontend/src/components/Navbar.tsx (1)
frontend/src/services/SubscriptionService.ts (1)
  • SubscriptionService (5-104)
frontend/src/services/SubscriptionService.ts (2)
frontend/src/scripts/api.ts (1)
  • apiURL (8-8)
frontend/src/models/SubscriptionStatus.ts (1)
  • SubscriptionStatus (1-7)
frontend/src/services/SearchService.ts (1)
frontend/src/scripts/api.ts (1)
  • apiURL (8-8)
🪛 Biome (1.9.4)
backend/src/modules/search/services/search.service.ts

[error] 99-99: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

frontend/src/services/SubscriptionService.ts

[error] 5-104: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

frontend/src/services/SearchService.ts

[error] 6-50: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

🔇 Additional comments (20)
frontend/src/pages/content/ContentView.tsx (1)

24-24: Properly restructured helper function import path.

The import path for normalizeContentDates has been correctly updated from "../../services/contentHelper" to "../../utils/contentHelper", which aligns with best practices of keeping helper functions in a utils directory rather than services.

frontend/src/components/feed/ContentList.tsx (1)

6-6: Consistent import path update for helper function.

This change properly aligns with the same import path update made in ContentView.tsx, ensuring consistency across the codebase by moving helper functions from services to utils.

frontend/src/hooks/AuthProvider.tsx (2)

8-8: Added navigation capability to AuthProvider.

The useNavigate hook has been properly imported and initialized to enable programmatic navigation within the authentication flow.

Also applies to: 16-17


31-31: Improved logout UX with automatic redirection.

Adding navigation to the login page after successful logout improves the user experience by ensuring users are directed to an appropriate page after ending their session.

backend/src/modules/search/routes/search.routes.ts (1)

6-7: Standardized API endpoint URLs and aligned handler names.

The changes correctly:

  1. Remove trailing slashes from route paths for consistency
  2. Update the handler for the content endpoint to use searchContents which matches the controller method name

These improvements ensure better maintainability and alignment between routes and controller methods.

frontend/src/App.tsx (1)

58-59: Restructured component hierarchy for proper hook usage.

The order of Router and AuthProvider components has been swapped to support the use of navigation hooks within AuthProvider. This change is necessary because AuthProvider now uses useNavigate for handling logout redirects, which requires it to be a child of the Router component.

Also applies to: 106-107

frontend/src/pages/pro/ProDetails.tsx (2)

5-5: Added SubscriptionService import to centralize API interactions.

Good refactoring choice to replace direct API calls with a dedicated service abstraction.


22-31: Improved error handling with SubscriptionService implementation.

The refactoring properly uses the new service layer for subscription status checks and implements a cleaner error handling pattern by checking if the returned value is an Error instance rather than using try-catch blocks.

frontend/src/pages/pro/SubscribePro.tsx (3)

4-4: Added SubscriptionService import to centralize API interactions.

Good refactoring choice to replace direct API calls with a dedicated service abstraction.


38-56: Improved subscription status check with service abstraction.

The refactored code properly delegates the API interaction to the SubscriptionService and handles error cases appropriately. The type checking against Error instances provides a clear control flow.


67-76: Enhanced subscription session creation with service abstraction.

The implementation correctly uses the SubscriptionService for creating a subscription session and handles errors appropriately. The error handling pattern is consistent with other refactored components.

backend/src/modules/search/controllers/search.controller.ts (2)

7-35: Improved documentation and error handling for searchUsers.

The addition of detailed JSDoc comments and explicit type handling for query parameters enhances code readability and type safety. The error handling now distinguishes between Error instances and other error types, providing more detailed logging.


37-62: Renamed searchContent to searchContents for consistency.

The method has been appropriately renamed to follow consistent naming conventions (plural for collections). The implementation includes improved error handling with detailed logging and proper type handling for query parameters.

backend/src/modules/search/services/search.service.ts (7)

19-21: LGTM: Improved environment variable handling

The explicit type casting of environment variables to string enhances type safety.

Also applies to: 24-29


53-66: Improved code organization and query construction

The refactored query construction with a base query and conditional startAfter is cleaner and avoids code duplication.


70-79: Better type safety and structured return object

Adding explicit typing for users as User[] and returning a structured object improves type safety and API consistency.


93-96: Efficient early return pattern

Adding an early return for empty search text is a good optimization that avoids unnecessary processing.


127-130: Improved type safety with type guard

The use of a type guard in the filter function ensures type safety by properly narrowing the type.


132-135: Consistent return structure

The structured return with contents and nextStartingPoint aligns with frontend expectations for easier integration.


137-138: Enhanced error logging

The improved error logging provides better traceability for debugging search failures.

Comment on lines 147 to 151
if (uniqueContents && uniqueContents.length > 0) {
setContentReturned((prev) => [...prev, ...uniqueContents]);
} else {
setUserDisabled(true); // Disable the button if no unique users are found
}
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

Wrong state variable updated – breaks “fetch more” logic for content.

Inside fetchContentData the code disables the “fetch more” button by calling setUserDisabled(true) – that’s the flag that controls USER pagination, not CONTENT pagination. As a result, the user button may be disabled unexpectedly while the content button is never disabled.

-      setUserDisabled(true); // Disable the button if no unique users are found
+      // Disable the content "Fetch more" button (add a dedicated piece of state)
+      // setContentDisabled(true);

Consider introducing a separate contentDisabled state or re-using numberOfContentsToDisplay to decide when to hide the button.

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

🤖 Prompt for AI Agents
In frontend/src/components/search/SearchListContent.tsx around lines 147 to 151,
the code incorrectly calls setUserDisabled(true) to disable the "fetch more"
button for content pagination, which controls user pagination instead. To fix
this, introduce a separate state variable like contentDisabled or use
numberOfContentsToDisplay to manage the content fetch button's disabled state,
and update that state here instead of setUserDisabled.

Comment on lines 24 to 31
const [usersReturned, setUsersReturned] = useState<User[]>([]);
const [userDisabled, setUserDisabled] = useState(true);
const [contentReturned, setContentReturned] = useState<Content[]>([]);

const [userStartingPoint, setUserStartingPoint] = useState<string | null>(
null
);

const [contentReturned, setContentReturned] = useState<Content[]>([]);
// const [contentStartingPoint, setContentStartingPoint] = useState<
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

Reset pagination & internal state when the query changes.

usersReturned, contentReturned, userStartingPoint, and numberOfContentsToDisplay keep their old values when the user types a brand-new query.
This means the very first backend request for a new term will start from the previous userStartingPoint, skipping early results and potentially showing stale data.

useEffect(() => {
+  // When the query itself changes, clear all accumulated state
+  setUsersReturned([]);
+  setContentReturned([]);
+  setUserStartingPoint(null);
+  setNumberOfContentsToDisplay(5);

Adding the reset in the same useEffect (before the fetch calls) makes the component behave like a fresh search every time.

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

🤖 Prompt for AI Agents
In frontend/src/components/search/SearchListContent.tsx around lines 24 to 31,
the state variables usersReturned, contentReturned, userStartingPoint, and
numberOfContentsToDisplay are not reset when the search query changes, causing
stale or skipped results. To fix this, add a reset for these state variables at
the start of the useEffect that triggers on query changes, setting usersReturned
and contentReturned to empty arrays, userStartingPoint to null, and
numberOfContentsToDisplay to its initial value, ensuring each new query starts
fresh.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
frontend/src/components/search/SearchListResults.tsx (1)

192-200: ⚠️ Potential issue

“Fetch more Content” button never appears for service-fetched data

The visibility check relies on the prop contentSearchResults, but after the first request all content lives in the local contentReturned state.
When the component is mounted without pre-loaded props, the button is never rendered, preventing pagination.

-{contentSearchResults &&
-  contentSearchResults.length > numberOfContentsToDisplay && (
+{contentReturned.length > numberOfContentsToDisplay && (

Aligning the condition with contentReturned (or introducing a contentDisabled flag similar to users) restores the expected behaviour.

🧹 Nitpick comments (2)
frontend/src/components/search/SearchListResults.tsx (2)

69-73: Avoid blocking UI with alert for concurrent fetch protection

Using alert("Already Fetching!") freezes the tab and provides a jarring UX. Simply return silently or show a non-blocking toast/snackbar.

-  if (fetching) {
-    alert("Already Fetching!");
-    return;
-  }
+  if (fetching) return; // or display a non-modal notification

This keeps the interface responsive while still preventing duplicate requests.


37-38: Single fetching flag serialises user & content requests

Because fetching is shared, calling fetchUserData and fetchContentData back-to-back means the second call is blocked until the first finishes.
For slow networks this delays data arrival unnecessarily.

Introduce independent flags, e.g. fetchingUsers and fetchingContents, or track ongoing promises per resource so both requests can run concurrently without race conditions.

Also applies to: 69-75, 122-129

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 291a657 and 33fe87e.

📒 Files selected for processing (4)
  • backend/src/modules/search/routes/search.routes.ts (1 hunks)
  • backend/src/modules/subscription/services/stripe.service.ts (1 hunks)
  • frontend/src/components/Navbar.tsx (3 hunks)
  • frontend/src/components/search/SearchListResults.tsx (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/src/modules/subscription/services/stripe.service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/src/modules/search/routes/search.routes.ts
  • frontend/src/components/Navbar.tsx

Comment on lines 44 to 56
useEffect(() => {
if (userSearchResults) {
setUsersReturned(userSearchResults);
} else if (param) {
} else if (query) {
fetchUserData();
}

if (contentSearchResults) {
setContentReturned(contentSearchResults);
} else if (param) {
} else if (query) {
fetchContentData();
}
}, [userSearchResults, contentSearchResults, param]);
}, [userSearchResults, contentSearchResults, query]);
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

State is not reset when the query string changes

When query changes, the previous usersReturned, contentReturned, and pagination pointers persist, mixing results from different searches.

useEffect(() => {
+  // Reset state on query change
+  setUsersReturned([]);
+  setContentReturned([]);
+  setUserStartingPoint(null);
+  setUserDisabled(true);
+
   if (userSearchResults) {
     …

Resetting prevents stale data leakage and ensures correct pagination for each distinct query.

📝 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
useEffect(() => {
if (userSearchResults) {
setUsersReturned(userSearchResults);
} else if (param) {
} else if (query) {
fetchUserData();
}
if (contentSearchResults) {
setContentReturned(contentSearchResults);
} else if (param) {
} else if (query) {
fetchContentData();
}
}, [userSearchResults, contentSearchResults, param]);
}, [userSearchResults, contentSearchResults, query]);
useEffect(() => {
// Reset state on query change
setUsersReturned([]);
setContentReturned([]);
setUserStartingPoint(null);
setUserDisabled(true);
if (userSearchResults) {
setUsersReturned(userSearchResults);
} else if (query) {
fetchUserData();
}
if (contentSearchResults) {
setContentReturned(contentSearchResults);
} else if (query) {
fetchContentData();
}
}, [userSearchResults, contentSearchResults, query]);
🤖 Prompt for AI Agents
In frontend/src/components/search/SearchListResults.tsx around lines 44 to 56,
the state variables usersReturned, contentReturned, and pagination pointers are
not reset when the query string changes, causing stale data from previous
searches to persist. To fix this, add logic inside the useEffect hook to reset
usersReturned, contentReturned, and pagination states to their initial empty or
default values whenever the query changes before fetching new data. This ensures
fresh results and correct pagination for each new query.

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

♻️ Duplicate comments (1)
frontend/src/components/search/SearchListResults.tsx (1)

42-55: State is still not reset when query changes – stale results leak between searches
This issue was flagged in a previous review and remains unresolved.
Before fetching for a new query, clear usersReturned, contentReturned, pagination pointers, and disable flags to avoid mixing results from different queries.

🧹 Nitpick comments (3)
frontend/src/components/search/SearchListResults.tsx (3)

164-169: Use stable keys instead of array index

React keys should be stable across re-renders to prevent unnecessary mounting/unmounting.
Use user.uid (already available) instead of the array index.

-          usersReturned.map((user: User, index) => (
-            <div key={index} className='searchItem'>
+          usersReturned.map((user: User) => (
+            <div key={user.uid} className='searchItem'>

180-184: Same key issue for content items

-          contentReturned.map((content: Content, index) => (
-            <div key={index} className='searchItem'>
+          contentReturned.map((content: Content) => (
+            <div key={content.uid} className='searchItem'>

65-152: Consider extracting shared fetch logic to reduce duplication and race conditions

Both fetchUserData and fetchContentData share ~90 % identical logic (fetch → dedupe → update state → toggle disabled flag).
Duplicated asynchronous code makes maintenance harder and increases the chance of subtle drift.

Suggestion:

  1. Create a generic helper that accepts:
    • fetchFn (SearchService method)
    • existingItems
    • setItems / setStartingPoint / setDisabled
  2. Debounce or abort in-flight requests when query changes to avoid race conditions (e.g., AbortController or a local cancelled flag).

This keeps the component lean and makes future behaviour changes (e.g., new pagination scheme) one-liner updates.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 33fe87e and 242c25b.

📒 Files selected for processing (2)
  • frontend/src/components/search/SearchListResults.tsx (1 hunks)
  • frontend/src/services/SearchService.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/services/SearchService.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/components/search/SearchListResults.tsx (2)
frontend/src/services/SearchService.ts (1)
  • SearchService (6-51)
frontend/src/components/search/ContentSearchResult.tsx (1)
  • ContentSearchResult (4-29)

Copy link
Contributor

@Hi-kue Hi-kue left a comment

Choose a reason for hiding this comment

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

LGTM (from previous review) & GTM 👍

@BengeeL BengeeL merged commit 86f94a5 into dev May 20, 2025
1 check passed
@BengeeL BengeeL deleted the SUMMA-19-Rebase branch May 20, 2025 20:50
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.

3 participants