Skip to content

Organization Switcher#165

Merged
carhartlewis merged 7 commits into
mainfrom
lewis/pol
Mar 21, 2025
Merged

Organization Switcher#165
carhartlewis merged 7 commits into
mainfrom
lewis/pol

Conversation

@carhartlewis

@carhartlewis carhartlewis commented Mar 21, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Introduced a dedicated modal for organization creation with responsive, real-time updates.
    • Added an organization management menu to facilitate easier switching.
    • Updated user messages for organization transitions in multiple languages.
  • Bug Fixes

    • Improved error handling and user feedback during organization updates.
  • Refactor

    • Streamlined the header and user menu by removing redundant organization navigation options.
    • Enhanced the organization switching flow with an interactive dialog interface.
  • Style

    • Adjusted tooltip layering for improved visual consistency.
  • Chores

    • Improved code readability and maintainability across various components.

- Added `OrgMenu` component for organization switching in the sidebar.
- Introduced `CreateOrgModal` for creating new organizations with form validation.
- Updated `changeOrganizationAction` to revalidate paths after organization change.
- Enhanced `OrganizationSwitcher` to handle organization changes with loading states and user feedback.
- Improved header and sidebar components for better organization management experience.
- Updated translations to include new status messages for organization switching.
@vercel

vercel Bot commented Mar 21, 2025

Copy link
Copy Markdown

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

Name Status Preview Comments Updated (UTC)
app ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 21, 2025 3:06pm
comp-portal ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 21, 2025 3:06pm

@coderabbitai

coderabbitai Bot commented Mar 21, 2025

Copy link
Copy Markdown

Walkthrough

This PR introduces new functionality and UI enhancements related to organization management. The changes add the revalidation of organization pages upon update, incorporate new modal and menu components for creating and selecting organizations, and refactor existing components to improve user interaction. There are also several localization updates and minor code formatting adjustments across various files.

Changes

File(s) Change Summary
apps/app/src/actions/change-organization.ts Added import and call to revalidatePath from next/cache to revalidate the organization path after a successful update.
apps/app/src/components/header.tsx
apps/app/src/components/user-menu.tsx
Removed the OrganizationSwitcher component and related account navigation code respectively, simplifying the UI in these components.
apps/app/src/components/modals/create-org-modal.tsx
apps/app/src/components/org-menu.tsx
apps/app/src/components/organization-switcher.tsx
Introduced new components (CreateOrgModal, OrgMenu) and enhanced OrganizationSwitcher with a dialog-based approach and added support for frameworks.
apps/app/src/components/sidebar.tsx Wrapped the OrgMenu component in a Suspense boundary to handle asynchronous loading states.
apps/app/src/locales/en.ts
apps/app/src/locales/es.ts
apps/app/src/locales/fr.ts
apps/app/src/locales/no.ts
apps/app/src/locales/pt.ts
Added a new key "switch" (with corresponding translations) and adjusted minor syntax to support proper localization messaging.
apps/app/src/components/ui/data-table/DataTable.tsx
packages/ui/src/components/chart-tooltip.tsx
packages/ui/src/components/chart.tsx
Reformatted code for improved readability; adjusted indentation and z-index values in tooltips without functional changes.
packages/ui/src/components/dialog.tsx Added a new optional property hideOverlayClose to the DialogContent component's props, modifying its behavior based on this property.

Sequence Diagram(s)

Organization Switch Flow

sequenceDiagram
    actor User
    participant OS as OrganizationSwitcher (Dialog)
    participant Action as changeOrganizationAction
    participant Cache as revalidatePath
    participant Nav as Navigation

    User->>OS: Select an organization
    OS->>OS: Set loading state and open dialog
    OS->>Action: Trigger organization change action
    Action->>Cache: Call revalidatePath(orgId)
    Cache-->>Action: Path revalidated
    Action-->>OS: Return success
    OS->>Nav: Redirect to new organization page after delay
    Nav-->>User: New organization page displayed
Loading

Organization Creation Flow

sequenceDiagram
    actor User
    participant Modal as CreateOrgModal
    participant Action as createOrganization
    participant Status as RealtimeStatus
    participant Nav as Navigation

    User->>Modal: Open and fill organization form
    Modal->>Action: Submit form with org details
    Action->>Status: Monitor creation status
    Status-->>Modal: Provide real-time updates
    Action-->>Modal: Return new organization details and token
    Modal->>Nav: Redirect user to new organization page
    Nav-->>User: New organization page displayed
Loading

Possibly related PRs

  • SSR Settings + Loading UI #147: Introduces revalidatePath functionality to revalidate paths after updates, which is closely related to the changes in the organization update action.

Poem

I'm a rabbit, hopping with glee,
New modals and menus, so spry and free.
Switching orgs with a gentle tap,
Paths revalidated, no time for a nap.
In a world of code, here's to change anew,
With bugs chased away and features true!
🐇✨ Happy hopping into a seamless view!


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8354b2b and ebd17fa.

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

🪧 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 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.

@vercel vercel Bot temporarily deployed to Preview – comp-portal March 21, 2025 14:48 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (11)
packages/ui/src/components/chart.tsx (1)

182-182: Added z-index to tooltip container

A z-30 class has been added to the tooltip container's className, which will affect the tooltip's stacking order relative to other elements.

Make sure this z-index value (30) is appropriate for your application's overall stacking context. If tooltips need to appear above other high z-index elements, you might need to increase this value.

apps/app/src/components/sidebar.tsx (3)

8-9: Confirm the fallback handling for Suspense.
By importing Suspense and OrgMenu, you're setting up asynchronous rendering in React, but there is no explicit fallback element provided. This may lead to a blank screen if data fetching is delayed. Consider providing a small spinner or skeleton UI for better user feedback.


22-24: Handle potential JSON parsing errors.
When retrieving the MenuConfig cookie, you assume valid JSON. Malformed JSON would cause a runtime error. Wrap JSON.parse in a try/catch for more robust error handling.

 const initialItems = cookieStore.has(Cookies.MenuConfig)
   ? JSON.parse(cookieStore.get(Cookies.MenuConfig)?.value ?? "")
   : null;

+// Potential safety net for malformed cookies:
+let parsedMenuConfig = null;
+try {
+  parsedMenuConfig = cookieStore.has(Cookies.MenuConfig)
+    ? JSON.parse(cookieStore.get(Cookies.MenuConfig)?.value ?? "")
+    : null;
+} catch (e) {
+  console.error("Invalid JSON in MenuConfig cookie:", e);
+}
+const initialItems = parsedMenuConfig;

26-43: Add a fallback for OrgMenu Suspense.
The return block includes <Suspense> without specifying a fallback within the component. Users may see an empty area during the async fetch. Provide a lightweight fallback UI or skeleton to improve overall user experience.

apps/app/src/components/modals/create-org-modal.tsx (1)

240-281: RealtimeStatus component handles asynchronous completion well.
The RealtimeStatus logic elegantly transitions to the organization page when the run is successfully completed. Minor suggestions:

  • Log or expose the error more prominently in the UI if you detect one, so users know the creation has failed.
  • Consider ensuring the final redirect only happens once to avoid potential race conditions if re-render triggers useEffect multiple times.
    Overall, this chunk is appropriately designed for smooth user feedback.
apps/app/src/components/organization-switcher.tsx (6)

4-7: Consolidate new imports.
Imports for Framework, Organization, Dialog, motion, useClickAway, etc., have increased. This is acceptable given new features, but watch out for potential bloat. Remove any imports that are no longer needed to keep the file concise.

Also applies to: 10-18


20-24: OrgItem interface clarity.
Your OrgItem interface provides a simple shape for each org. If you plan to store more metadata, consider a partial or extended type from the original Organization model for consistency.


37-41: Manage state for dialog, active status, and loading.
Using separate states isOpen, isActive, and isLoading clarifies the user flow. Consider grouping them if they share a life cycle. This is optional but can sometimes simplify logic.


43-65: Asynchronous organization change with delayed navigation.

  1. Good handling of success/failure states, including a 1.5-second delay for user experience.
  2. For large user bases or remote connections, consider confirming success before applying a forced delay to avoid possible confusion.
  3. The router.refresh() ensures partial revalidation. Confirm it doesn’t over-fetch or conflict with other revalidation logic.

72-75: Ensure error handling around handleOrganizationChange.
You set isLoading and then trigger execute to change org. If execute fails, you reset loading in onError. Looks fine. Possibly add final success state reset if you want the loading state to end after the final navigation.


83-121: Portal-based loading overlay.
Blocking the UI and blurring the background is a good pattern for switching contexts. However, you might consider giving the user a cancellation path if the switch is misclicked or slow.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6aba99f and d178df9.

⛔ Files ignored due to path filters (1)
  • apps/app/languine.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • apps/app/src/actions/change-organization.ts (1 hunks)
  • apps/app/src/components/header.tsx (1 hunks)
  • apps/app/src/components/modals/create-org-modal.tsx (1 hunks)
  • apps/app/src/components/org-menu.tsx (1 hunks)
  • apps/app/src/components/organization-switcher.tsx (1 hunks)
  • apps/app/src/components/sidebar.tsx (1 hunks)
  • apps/app/src/components/ui/data-table/DataTable.tsx (2 hunks)
  • apps/app/src/components/user-menu.tsx (0 hunks)
  • apps/app/src/locales/en.ts (1 hunks)
  • apps/app/src/locales/es.ts (1 hunks)
  • apps/app/src/locales/fr.ts (1 hunks)
  • apps/app/src/locales/no.ts (1 hunks)
  • apps/app/src/locales/pt.ts (1 hunks)
  • packages/ui/src/components/chart-tooltip.tsx (2 hunks)
  • packages/ui/src/components/chart.tsx (5 hunks)
💤 Files with no reviewable changes (1)
  • apps/app/src/components/user-menu.tsx
🧰 Additional context used
🧬 Code Definitions (5)
apps/app/src/components/header.tsx (3)
apps/app/src/data/getOrganizations.ts (1) (1)
  • getOrganizations (4-28)
apps/app/src/components/mobile-menu.tsx (1) (1)
  • MobileMenu (9-36)
apps/app/src/components/user-menu.tsx (1) (1)
  • UserMenu (15-94)
apps/app/src/components/ui/data-table/DataTable.tsx (3)
apps/app/src/components/ui/data-table/DataTableHeader.tsx (1) (1)
  • DataTableHeader (13-62)
apps/app/src/components/ui/data-table/DataTableSkeleton.tsx (1) (1)
  • DataTableSkeleton (21-79)
apps/app/src/components/ui/data-table/DataTablePagination.tsx (1) (1)
  • DataTablePagination (24-85)
apps/app/src/components/sidebar.tsx (2)
apps/app/src/components/main-menu.tsx (1) (1)
  • MainMenu (180-402)
apps/app/src/components/org-menu.tsx (1) (1)
  • OrgMenu (9-30)
apps/app/src/components/organization-switcher.tsx (2)
apps/app/src/actions/change-organization.ts (1) (1)
  • changeOrganizationAction (8-76)
apps/app/src/components/modals/create-org-modal.tsx (1) (1)
  • CreateOrgModal (37-238)
apps/app/src/components/org-menu.tsx (1)
apps/app/src/components/organization-switcher.tsx (1) (1)
  • OrganizationSwitcher (32-205)
🔇 Additional comments (39)
apps/app/src/locales/en.ts (1)

304-304: LGTM: New localization string for organization switching.

The addition of the "switch" string in the onboarding section is appropriate and consistent with the organization switching functionality described in the PR objectives.

apps/app/src/locales/es.ts (1)

289-290: LGTM: Spanish translation added correctly.

The Spanish translation for the organization switching message has been added appropriately, maintaining consistency with the English version.

apps/app/src/locales/fr.ts (1)

289-290: LGTM: French translation added correctly.

The French translation for the organization switching message has been added appropriately, maintaining consistency with the English and Spanish versions.

apps/app/src/locales/no.ts (1)

289-290: Properly added localization for organization switching.

The addition of the "switch" key with Norwegian translation complements the existing organization-related strings. The comma added to the "creating" entry is necessary for valid object syntax.

apps/app/src/locales/pt.ts (1)

289-290: Properly added localization for organization switching.

The addition of the "switch" key with Portuguese translation complements the existing organization-related strings. The comma added to the "creating" entry is necessary for valid object syntax.

packages/ui/src/components/chart-tooltip.tsx (2)

156-157: Reduced z-index for mobile tooltip consistency.

The z-index has been reduced from z-60 to z-30, ensuring the tooltip doesn't appear above important UI elements like organization menus.


176-177: Standardized z-index for desktop tooltip.

The z-index has been reduced from z-50 to z-30, matching the mobile tooltip's z-index value. This creates consistency in the stacking order across device types.

apps/app/src/actions/change-organization.ts (2)

6-6: Added revalidation import for UI consistency.

Importing the revalidatePath function will allow proper page revalidation after organization changes.


62-62: Improved UX with path revalidation after organization change.

Revalidating the path ensures that all UI components reflect the newly selected organization immediately after switching, preventing stale data and UI inconsistencies.

apps/app/src/components/org-menu.tsx (3)

1-8: Proper imports and organization

The imports are well-organized, bringing in both local auth utilities and UI components needed for the new organization menu functionality.


9-30: Well-implemented server component with proper error handling

The OrgMenu component is well-structured as a server component that:

  1. Fetches user session and organizations
  2. Handles the case when a user doesn't have an organization by redirecting
  3. Renders the OrganizationSwitcher with proper data
  4. Uses Suspense for loading states

32-38: Good use of caching for database queries

Using cache for the database query is an excellent performance optimization that prevents redundant database calls when this component re-renders.

apps/app/src/components/header.tsx (1)

28-57: Header organization improved

The header has been streamlined by removing the OrganizationSwitcher, which has been moved to its dedicated OrgMenu component. This creates a better separation of concerns and improves component organization.

apps/app/src/components/ui/data-table/DataTable.tsx (10)

3-11: Improved import formatting

The import statements have been reformatted for better readability with consistent indentation.


20-26: Better dropdown menu imports formatting

The dropdown menu components imports have been reformatted with consistent indentation.


32-38: Consistent interface formatting

The FilterItem interface has been reformatted for better readability with consistent indentation.


40-44: Consistent FilterCategory interface formatting

The FilterCategory interface has been reformatted for better readability with consistent indentation.


46-79: Improved DataTableProps interface formatting

The DataTableProps interface has been reformatted with consistent indentation for better readability while maintaining the same structure and properties.


81-94: Consistent DataTable component parameters formatting

The DataTable function parameters have been reformatted with consistent indentation for better readability.


95-107: Well-formatted state and effect implementations

The state management and debounced search effect implementation are now consistently formatted.


109-124: Improved table configuration formatting

The useReactTable configuration has been reformatted with consistent indentation while maintaining the same functionality.


126-129: Clear pagination display condition

The showPagination variable declaration is now clearly formatted for better readability.


130-320: Improved JSX structure formatting

The JSX structure of the component return value has been reformatted with consistent indentation while maintaining the same layout and functionality.

packages/ui/src/components/chart.tsx (4)

87-93: Improved mapping formatting in ChartStyle

The mapping of colorConfig has been reformatted for better readability while maintaining the same logic.


106-114: Better ChartTooltipContent type definitions

The ChartTooltipContent component's props type definition has been reformatted for improved readability and consistent indentation.


264-267: Improved ChartLegendContent type definitions

The ChartLegendContent component's props type definition has been reformatted for improved readability and consistent indentation.


330-333: Improved condition formatting in getPayloadConfigFromPayload

The condition for checking payload.payload has been reformatted for better readability with consistent indentation.

apps/app/src/components/sidebar.tsx (3)

12-14: Check for null session handling.
You rightly retrieve the session user and organization ID but rely on them without additional null checks. If auth() fails or returns null, user would be undefined. Consider guarding against a null session or handle it gracefully to avoid potential runtime errors.


16-18: Ensure the redirect aligns with product requirements.
Your conditional redirect on missing organizationId is logical. However, verify that redirecting to / is the intended behavior (e.g., for unlinked user sessions).


20-20: Confirm security for cookie usage.
Acquiring cookies from next/headers looks fine. Just verify that any sensitive data is handled correctly. If the cookie can contain PII or private tokens, ensure you sanitize or encrypt them as needed.

apps/app/src/components/modals/create-org-modal.tsx (3)

1-2: Client-side rendering note.
Including "use client"; ensures React client-side features are enabled, which is correct for a modal with interactive form components. No issues found.


3-36: Validate all imports and dependencies.
All imported modules (e.g., @hookform/resolvers/zod, react-hook-form, next/navigation) appear consistent with the intended functionality. Just ensure each dependency is declared in your project’s dependencies and that versions are compatible.


37-239: Review the CreateOrgModal form logic and structure.

  1. The form uses react-hook-form with a zodResolver, which is a recommended pattern for robust validation.
  2. The onSubmit handler calls createOrganization with success and error callbacks; this is good for encapsulating side effects.
  3. You manage runId and publicAccessToken in state to control the RealtimeStatus flow. This is a nice approach for showing progress.
  4. Consider adding more granular error messages for distinct validation failures if needed.
  5. The UI is strongly typed, ensuring that each field is validated according to organizationSchema.
    Overall, the implementation is clear. Just confirm you thoroughly test edge cases (empty fields, missing frameworks, etc.).
apps/app/src/components/organization-switcher.tsx (6)

27-29: New OrganizationSwitcherProps fields.
Including frameworks aligns with the refactored structure where org creation uses the same set of frameworks. No immediate issues.


33-35: Function signature update.
Adding frameworks is consistent with the changed props above. Make sure you also pass and handle them properly wherever OrganizationSwitcher is used.


67-69: UseClickAway for better UX.
Clicking outside to close or deactivate the menu is user-friendly. This is a straightforward integration with useClickAway. Looks good.


77-79: Find the current organization by ID.
This is a straightforward approach. No further comment needed.


81-81: Grouping organizations with the current org first.
Neat trick to sort the current org to the beginning. Just confirm the logic works when currentOrganization is undefined. The filter call might fail if org is undefined.


123-201: Dialog usage for multi-organization view and creation.

  1. Great use of <CreateOrgModal> to add new organizations directly.
  2. Toggling isActive and isOpen is well-structured, though you may unify them under a single mechanism if desired (one for the entire menu state).
  3. The framer-motion animations add a smooth effect but watch for performance on slower devices.

Comment thread apps/app/src/components/header.tsx Outdated
…oved UX

- Simplified the `OrganizationSwitcher` component by removing unnecessary animations and styling.
- Enhanced `CreateOrgModal` to prevent closing during execution and conditionally render titles and descriptions based on execution state.
- Updated `DialogContent` to support new props for controlling close behavior and overlay interactions.
@vercel vercel Bot temporarily deployed to Preview – comp-portal March 21, 2025 15:01 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
apps/app/src/components/modals/create-org-modal.tsx (4)

49-57: Error handling could be more informative.

While the success handling is comprehensive, the error handling simply shows a generic error message. Consider enhancing the error message to provide more context about what went wrong during organization creation.

onError: (error) => {
-  toast.error(t("common.actions.error"), { duration: 5000 });
+  const errorMessage = error.serverError || t("common.actions.error");
+  toast.error(errorMessage, { duration: 5000 });
},

191-232: Framework selection UI could benefit from a message when no frameworks are available.

The framework selection fieldset should handle the case when no frameworks are available. Consider adding a message when the frameworks array is empty.

<fieldset className="flex flex-col gap-2 select-none">
  <legend className="sr-only">
    {t("frameworks.overview.grid.title")}
  </legend>
+ {frameworks.length === 0 && (
+   <p className="text-sm text-muted-foreground">{t("frameworks.overview.grid.empty")}</p>
+ )}
  {frameworks.map((framework) => (
    // Existing framework items
  ))}
</fieldset>

239-241: Cancel button should reset form state.

Currently, the cancel button only closes the modal. Consider resetting the form when canceling to ensure a clean state if the user reopens the modal.

<Button 
  type="button" 
  variant="outline" 
- onClick={() => onOpenChange(false)} 
+ onClick={() => {
+   form.reset();
+   onOpenChange(false);
+ }} 
  disabled={isExecuting}>
  {t("common.actions.cancel")}
</Button>

259-300: RealtimeStatus component handles completion correctly but lacks error recovery.

The RealtimeStatus component properly redirects upon successful completion, but it could provide a better experience when errors occur. Consider adding a retry button or providing more detailed error information.

function RealtimeStatus({
  runId,
  publicAccessToken,
  newOrganization,
}: {
  runId: string;
  publicAccessToken: string;
  newOrganization: Pick<Organization, "id" | "name"> | null;
}) {
  const t = useI18n();
  const router = useRouter();
+ const [hasError, setHasError] = useState(false);

  const { run, error } = useRealtimeRun(runId, {
    accessToken: publicAccessToken,
    onComplete: (run, error) => {
      if (error) {
+       setHasError(true);
        toast.error(t("common.actions.error"), { duration: 5000 });
        return;
      }
    },
  });

  useEffect(() => {
    if (run?.status === "COMPLETED" && newOrganization) {
      router.push(`/${newOrganization.id}`);
    }
  }, [run, router, newOrganization]);

  return (
    <div className="flex flex-col justify-center space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-300">
      <div className="flex flex-col gap-2 justify-center">
        <LogoSpinner />
        <h2 className="text-xl font-semibold text-center tracking-tight">
          {t("onboarding.trigger.title")}
        </h2>
        <p className="text-center text-sm text-muted-foreground">
          {t("onboarding.trigger.creating")}
        </p>
+       {hasError && (
+         <Button
+           onClick={() => window.location.reload()}
+           className="mt-4"
+           variant="outline"
+           size="sm"
+         >
+           {t("common.actions.retry")}
+         </Button>
+       )}
      </div>
    </div>
  );
}
apps/app/src/components/organization-switcher.tsx (2)

20-24: OrgItem interface could use more descriptive naming.

The OrgItem interface represents an organization with minimal properties, but its name doesn't clearly indicate that. Consider renaming it to something more descriptive like MinimalOrganization.

-interface OrgItem {
+interface MinimalOrganization {
  id: string;
  name?: string;
  logo_url?: string;
}

85-114: Portal implementation for loading state is effective but has potential issues.

The portal implementation for showing a loading overlay during organization switching is effective, but the global styles that manipulate the body and #__next elements could potentially conflict with other UI components or cause unexpected side effects.

Consider using a more isolated approach for the overlay:

- <style jsx global>{`
-   body {
-     overflow: hidden !important;
-   }
-
-   #__next > *:not(.org-switch-overlay) {
-     pointer-events: none !important;
-     filter: blur(8px) !important;
-     opacity: 0.6 !important;
-   }
- `}</style>
+ <div className="fixed inset-0 z-[999999] pointer-events-auto">
+   <div className="w-full h-full bg-background/80 backdrop-blur-sm" />
+   {/* Rest of overlay content */}
+ </div>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d178df9 and 8354b2b.

📒 Files selected for processing (3)
  • apps/app/src/components/modals/create-org-modal.tsx (1 hunks)
  • apps/app/src/components/organization-switcher.tsx (1 hunks)
  • packages/ui/src/components/dialog.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
apps/app/src/components/modals/create-org-modal.tsx (1)
packages/ui/src/components/dialog.tsx (5) (5)
  • DialogContent (136-136)
  • DialogHeader (137-137)
  • DialogTitle (139-139)
  • DialogDescription (140-140)
  • DialogFooter (138-138)
🔇 Additional comments (13)
packages/ui/src/components/dialog.tsx (2)

32-34: Appropriate property addition for enhanced dialog control.

The new hideOverlayClose property allows for preventing dialogs from closing when users click outside the modal area. This is a useful addition for scenarios where accidental dismissal could lead to data loss or interrupt important processes.


35-35: Implementation correctly prevents overlay dismissal.

The implementation properly leverages the onPointerDownOutside event handler to conditionally prevent the default closing behavior when hideOverlayClose is true. This provides the expected functionality while maintaining the default behavior when the property is not specified.

Also applies to: 40-40

apps/app/src/components/modals/create-org-modal.tsx (5)

32-35: Clean props interface design.

The props interface is well-structured and clearly defines the component's requirements. The onOpenChange callback allows parent components to control the modal's visibility, while the frameworks array provides necessary data for the form.


41-47: State management approach for tracking creation progress.

The component uses appropriate state variables to track the organization creation process. Using useState for runId and publicAccessToken, and useRef for the newly created organization is a good approach to maintain this information across renders.


74-75: Logic ensures modal stays open during execution.

The handleOpenChange function properly prevents the dialog from closing when the organization creation process is executing. This ensures users don't accidentally cancel an in-progress operation.


83-83: Appropriate use of hideOverlayClose property.

The component correctly uses the new hideOverlayClose property when the creation process is executing. This prevents users from accidentally closing the modal by clicking outside it during critical operations.


103-111: Effective use of conditional rendering for process status.

The component conditionally renders the RealtimeStatus component when a run is in progress, which provides good user feedback during the asynchronous operation.

apps/app/src/components/organization-switcher.tsx (6)

4-19: Comprehensive import structure supports new UI functionality.

The updated imports properly include all the necessary dependencies for the enhanced organization switcher functionality, including the new modal components, animations, and Portal functionality.


26-30: Props interface appropriately updated to include frameworks.

The OrganizationSwitcherProps interface has been properly updated to include the frameworks property that will be passed to the create organization modal.


37-42: Well-designed state management for UI interactions.

The component uses appropriate state variables to track the modal's open state, the active state for the organization list, and loading state during organization changes.


72-75: Change organization handling is straightforward and effective.

The organization change handler correctly sets the loading state before executing the action, providing a good user experience during the transition.


81-81: Smart organization sorting improves UX.

Sorting the organizations to show the current one first, followed by others, creates a more intuitive user experience.


145-161: CreateOrgModal integration is well implemented.

The integration of the CreateOrgModal component is well done, using the Dialog component to control its visibility and passing the necessary frameworks data.

Comment on lines +43 to +65
const { execute } = useAction(changeOrganizationAction, {
onSuccess: (result) => {
if (result.data?.success) {
// Add a 2 second delay before navigation
setTimeout(() => {
const currentPath = window.location.pathname;
const currentPathParts = currentPath.split('/');
// Replace the organization ID part with the new one
if (currentPathParts.length > 1) {
currentPathParts[1] = result.data?.data?.id || '';
router.push(currentPathParts.join('/'));
} else {
router.push(`/${result.data?.data?.id}`);
}
router.refresh();
}, 1500);
}
},
onError: (error) => {
console.error(error);
setIsLoading(false);
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Delayed navigation could cause confusion without visual feedback.

The implementation adds a 1.5-second delay before navigation when switching organizations. While there is a loading state, consider adding a more explicit message or progress indicator to inform users about the navigation delay.


🏁 Script executed:

#!/bin/bash
# Description: Check if there's proper loading state UI feedback for delayed navigation

# Check for loading indicators or messages during organization switching
rg -A 5 "switching" --glob "*.tsx" --glob "*.ts"

Length of output: 48


Add explicit progress indicator during delayed navigation

  • The current implementation uses a 1.5‑second delay after successfully switching organizations before navigating, but there’s no clear visual feedback (e.g., a message or spinner) during this interval.
  • It’s recommended to enhance the user experience by displaying a progress indicator or an explicit message during the delay, ensuring users understand that the organization switch is in progress.
  • Affected File: apps/app/src/components/organization-switcher.tsx (lines 43–65)

Comment on lines +116 to +195
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<motion.div ref={ref as React.RefObject<HTMLDivElement>} layout className="w-[32px] h-[32px] relative">
{[
...sortedOrganizations.filter(Boolean).map(org => ({
org: {
id: org?.id || "",
name: org?.name || "",
} as OrgItem
})),
{ org: { id: "add" } as OrgItem }
].map(({ org }, index) => (
<motion.div
key={org.id}
className={cn(
"w-[32px] h-[32px] left-0 overflow-hidden absolute",
isLoading && "opacity-50 pointer-events-none"
)}
style={{ zIndex: -index }}
initial={{
scale: `${100 - index * 16}%`,
y: index * 5,
}}
{...(isActive && {
animate: {
y: -(32 + 10) * index,
scale: "100%",
},
})}
>
{org.id === "add" ? (
<>
<Button
className="w-[32px] h-[32px]"
size="icon"
variant="outline"
onClick={() => {
onOpenChange(true);
setActive(false);
}}
disabled={isLoading}
>
<Icons.Add />
</Button>

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="flex items-center gap-2">
<Building2 className="h-4 w-4" />
<span className="hidden md:inline-block">
{currentOrganization?.name}
</span>
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-52">
{organizations.map((org) => (
<DropdownMenuItem
key={org.id}
onClick={() => handleOrganizationChange(org.id)}
className="flex items-center gap-2 cursor-pointer"
>
<span>{org.name}</span>
</DropdownMenuItem>
))}
<DropdownMenuItem
className="flex items-center gap-2 cursor-pointer"
onClick={() => {
router.push("/setup");
}}
>
<Plus className="h-4 w-4" />
<span>Create new org</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
<CreateOrgModal onOpenChange={onOpenChange} frameworks={frameworks} />
</>
) : (
<Avatar
className={cn(
"w-[32px] h-[32px] rounded-none border border-[#DCDAD2] dark:border-[#2C2C2C] cursor-pointer",
isLoading && "cursor-not-allowed"
)}
onClick={() => {
if (isLoading) return;
if (index === 0) {
setActive(true);
} else {
handleOrganizationChange(org.id);
}
}}
>
<AvatarImageNext
src={org?.logo_url || ""}
alt={org?.name ?? ""}
width={20}
height={20}
quality={100}
/>
<AvatarFallback className="rounded-none w-[32px] h-[32px]">
<span className="text-xs">
{org?.name?.charAt(0)?.toUpperCase()}
{org?.name?.charAt(1)?.toUpperCase()}
</span>
</AvatarFallback>
</Avatar>
)}
</motion.div>
))}
</motion.div>
</Dialog>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Animation approach may cause performance issues on lower-end devices.

The implementation uses complex animations with framer-motion for the organization switching UI. While visually appealing, these animations might cause performance issues on lower-end devices, especially with multiple organization avatars.

Consider adding a check to verify if the user has reduced motion preferences:


🌐 Web query:

What's the best way to detect and respect reduced motion preferences in React applications?

💡 Result:

To detect and respect reduced motion preferences in React applications, developers can implement several strategies ranging from CSS media queries to React-specific hooks and animation library integrations:

Core Detection Methods

CSS Media Queries
Use prefers-reduced-motion to conditionally apply styles:

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

This globally disables animations for users with system-level reduced motion enabled [2][6].

JavaScript Detection
Use window.matchMedia to access the preference programmatically:

const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
const prefersReducedMotion = motionQuery.matches;

This enables dynamic animation adjustments in JavaScript logic [4][16].


React-Specific Solutions

Custom Hook Implementation
Create a reusable hook for motion preferences:

import { useEffect, useState } from 'react';

const useReducedMotion = () => {
  const [prefersReduced, setPrefersReduced] = useState(true);

  useEffect(() => {
    const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
    setPrefersReduced(mediaQuery.matches);
    
    const listener = (e) => setPrefersReduced(e.matches);
    mediaQuery.addEventListener('change', listener);
    return () => mediaQuery.removeEventListener('change', listener);
  }, []);

  return prefersReduced;
};

Handles SSR compatibility by initializing state conservatively [5][12].

Animation Library Integration
With Framer Motion:

import { motion, useReducedMotion } from 'framer-motion';

const AnimatedComponent = () => {
  const shouldReduce = useReducedMotion();
  
  return (
    <motion.div 
      animate={{ x: shouldReduce ? 0 : 100 }}
      transition={{ duration: shouldReduce ? 0 : 1 }}
    />
  );
};

The built-in useReducedMotion hook automatically responds to preference changes [14].


Advanced Techniques

User-Controlled Overrides
Implement a toggle that persists in localStorage:

const MotionToggle = () => {
  const [reduced, setReduced] = useLocalStorage('reducedMotion', false);
  
  useEffect(() => {
    document.documentElement.dataset.reducedMotion = reduced;
  }, [reduced]);

  return (
    <label>
      <input 
        type="checkbox" 
        checked={reduced}
        onChange={(e) => setReduced(e.target.checked)}
      />
      Reduce Motion
    </label>
  );
};

Combines system preferences with user choice [3][17].

Performance Optimization
Conditionally load animation libraries:

useEffect(() => {
  if (!prefersReducedMotion) {
    import('gsap').then(({ gsap }) => {
      // Initialize animations
    });
  }
}, [prefersReducedMotion]);

Reduces bundle size for users who won't use animations [4].


Platform-Specific Notes

React Native
Use built-in APIs:

import { AccessibilityInfo } from 'react-native';

const [reduceMotion, setReduceMotion] = useState(false);

useEffect(() => {
  AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
}, []);

Handles platform-level motion settings [7].

SSR Considerations
For Next.js/Gatsby, use safe initialization:

const [prefersReduced, setPrefersReduced] = useState(true);

useEffect(() => {
  setPrefersReduced(window.matchMedia('(prefers-reduced-motion: reduce)').matches);
}, []);

Avoids hydration mismatches [5].

By combining system preference detection, user-controlled toggles, and library integrations, React applications can provide accessible motion experiences while maintaining engaging interactions for users without motion sensitivities.

Citations:


Performance Concern: Implement Reduced Motion Handling

The current animation implementation using Framer Motion may lead to performance issues on lower-end devices or for users with motion sensitivities. To address this, please consider detecting and respecting reduced motion preferences. For example, you could use Framer Motion’s built-in useReducedMotion hook (or apply CSS media queries with prefers-reduced-motion) to conditionally disable or simplify animations. One potential approach:

  • Integrate Reduced Motion Detection:

    • Import and use the useReducedMotion hook from Framer Motion.
    • Modify animation properties conditionally to either disable or reduce animation effects when reduced motion is preferred.
  • Example Code Update:

    import { motion, useReducedMotion } from 'framer-motion';
    
    const OrganizationSwitcher = (props) => {
      const shouldReduceMotion = useReducedMotion();
      
      return (
        <Dialog open={isOpen} onOpenChange={onOpenChange}>
          <motion.div
            ref={ref as React.RefObject<HTMLDivElement>}
            layout
            className="w-[32px] h-[32px] relative"
          >
            {[
              ...sortedOrganizations.filter(Boolean).map(org => ({
                org: {
                  id: org?.id || "",
                  name: org?.name || "",
                } as OrgItem
              })),
              { org: { id: "add" } as OrgItem }
            ].map(({ org }, index) => (
              <motion.div
                key={org.id}
                className={cn(
                  "w-[32px] h-[32px] left-0 overflow-hidden absolute",
                  isLoading && "opacity-50 pointer-events-none"
                )}
                style={{ zIndex: -index }}
                initial={{
                  scale: shouldReduceMotion ? "100%" : `${100 - index * 16}%`,
                  y: shouldReduceMotion ? 0 : index * 5,
                }}
                {...(!shouldReduceMotion && {
                  animate: {
                    y: -(32 + 10) * index,
                    scale: "100%",
                  },
                })}
                transition={{ duration: shouldReduceMotion ? 0 : 0.5 }}
              >
                {org.id === "add" ? (
                  <>
                    <Button
                      className="w-[32px] h-[32px]"
                      size="icon"
                      variant="outline"
                      onClick={() => {
                        onOpenChange(true);
                        setActive(false);
                      }}
                      disabled={isLoading}
                    >
                      <Icons.Add />
                    </Button>
    
                    <CreateOrgModal onOpenChange={onOpenChange} frameworks={frameworks} />
                  </>
                ) : (
                  <Avatar
                    className={cn(
                      "w-[32px] h-[32px] rounded-none border border-[#DCDAD2] dark:border-[#2C2C2C] cursor-pointer",
                      isLoading && "cursor-not-allowed"
                    )}
                    onClick={() => {
                      if (isLoading) return;
                      if (index === 0) {
                        setActive(true);
                      } else {
                        handleOrganizationChange(org.id);
                      }
                    }}
                  >
                    <AvatarImageNext
                      src={org?.logo_url || ""}
                      alt={org?.name ?? ""}
                      width={20}
                      height={20}
                      quality={100}
                    />
                    <AvatarFallback className="rounded-none w-[32px] h-[32px]">
                      <span className="text-xs">
                        {org?.name?.charAt(0)?.toUpperCase()}
                        {org?.name?.charAt(1)?.toUpperCase()}
                      </span>
                    </AvatarFallback>
                  </Avatar>
                )}
              </motion.div>
            ))}
          </motion.div>
        </Dialog>
      );
    };

This update will help ensure that users who have enabled reduced motion preferences experience a smoother and more accessible UI. Please verify that this approach works as expected across target devices.

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.

2 participants