Migrate current organization from in memory to url state

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-26 12:02:51 +01:00
parent 50aeed787f
commit c6cff715d5
21 changed files with 2150 additions and 832 deletions

View File

@@ -1,5 +1,6 @@
import * as React from "react";
import { Suspense, useEffect } from "react";
import { useParams } from "react-router";
import {
BookOpen,
Users,
@@ -31,55 +32,52 @@ import {
} from "@/components/ui/sidebar";
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
import { TeamSwitcher } from "@/components/TeamSwitcher";
import { url } from "inspector";
const staticData = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
navMain: [
{
title: "Frameworks",
url: "/frameworks",
icon: BookOpen,
},
{
title: "Organizations",
icon: Building,
url: "/peoples",
items: [
{
title: "Peoples",
url: "/peoples",
icon: Users,
},
{
title: "Vendors",
url: "/vendors",
icon: ToyBrick,
},
],
},
{
title: "Settings",
url: "/settings",
icon: Settings,
},
],
navSecondary: [
{
title: "Support",
url: "#",
icon: LifeBuoy,
},
{
title: "Feedback",
url: "#",
icon: Send,
},
],
};
function getNavItems(organizationId: string) {
return {
navMain: [
{
title: "Frameworks",
url: `/organizations/${organizationId}/frameworks`,
icon: BookOpen,
},
{
title: "Organizations",
icon: Building,
url: `/organizations/${organizationId}/peoples`,
items: [
{
title: "Peoples",
url: `/organizations/${organizationId}/peoples`,
icon: Users,
},
{
title: "Vendors",
url: `/organizations/${organizationId}/vendors`,
icon: ToyBrick,
},
],
},
{
title: "Settings",
url: `/organizations/${organizationId}/settings`,
icon: Settings,
},
],
navSecondary: [
{
title: "Support",
url: "#",
icon: LifeBuoy,
},
{
title: "Feedback",
url: "#",
icon: Send,
},
],
};
}
const AppSidebarQuery = graphql`
query AppSidebarQuery {
@@ -97,7 +95,9 @@ function AppSidebarContent({
}: React.ComponentProps<typeof Sidebar> & {
queryRef: PreloadedQuery<AppSidebarQueryType>;
}) {
const { organizationId } = useParams();
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
const navItems = getNavItems(organizationId!);
return (
<Sidebar variant="inset" {...props}>
@@ -105,8 +105,8 @@ function AppSidebarContent({
<TeamSwitcher organizations={data.viewer} />
</SidebarHeader>
<SidebarContent>
<NavMain items={staticData.navMain} />
<NavSecondary items={staticData.navSecondary} className="mt-auto" />
<NavMain items={navItems.navMain} />
<NavSecondary items={navItems.navSecondary} className="mt-auto" />
</SidebarContent>
<SidebarFooter>
<NavUser viewer={data.viewer} />
@@ -121,39 +121,37 @@ function AppSidebarFallback(props: React.ComponentProps<typeof Sidebar>) {
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild>
<a href="#">
<div className="size-8 rounded-lg bg-muted animate-pulse" />
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
</span>
<span className="truncate text-xs">Enterprise</span>
</div>
</a>
<SidebarMenuButton size="lg">
<div className="h-8 w-8 rounded-lg bg-sidebar-muted" />
<div className="flex-1 space-y-1">
<div className="h-4 w-3/4 rounded-lg bg-sidebar-muted" />
<div className="h-3 w-1/2 rounded-lg bg-sidebar-muted" />
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<NavMain items={staticData.navMain} />
<NavSecondary items={staticData.navSecondary} className="mt-auto" />
<SidebarMenu>
{Array.from({ length: 3 }).map((_, i) => (
<SidebarMenuItem key={i}>
<SidebarMenuButton>
<div className="h-4 w-4 rounded-lg bg-sidebar-muted" />
<div className="h-4 w-24 rounded-lg bg-sidebar-muted" />
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild>
<a href="#">
<div className="h-8 w-8 rounded-lg bg-muted animate-pulse" />
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
</span>
<span className="truncate text-xs">
<div className="h-3 w-32 animate-pulse rounded bg-muted" />
</span>
</div>
</a>
<SidebarMenuButton size="lg">
<div className="h-8 w-8 rounded-lg bg-sidebar-muted" />
<div className="flex-1 space-y-1">
<div className="h-4 w-3/4 rounded-lg bg-sidebar-muted" />
<div className="h-3 w-1/2 rounded-lg bg-sidebar-muted" />
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>

View File

@@ -9,6 +9,7 @@ import {
Sparkles,
} from "lucide-react";
import { graphql, useFragment } from "react-relay";
import { useNavigate } from "react-router";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
@@ -26,6 +27,7 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import { useAuth } from "@/contexts/AuthContext";
import {
NavUser_viewer$key,
NavUser_viewer$data,
@@ -42,6 +44,13 @@ export const navUserFragment = graphql`
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
const { isMobile } = useSidebar();
const currentUser = useFragment(navUserFragment, viewer);
const { logout } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
await logout();
navigate("/login");
};
return (
<SidebarMenu>
@@ -116,7 +125,7 @@ export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<DropdownMenuItem onClick={handleLogout}>
<LogOut />
Log out
</DropdownMenuItem>

View File

@@ -3,8 +3,7 @@
import { useState, useEffect } from "react";
import { ChevronsUpDown, Plus } from "lucide-react";
import { graphql, useFragment } from "react-relay";
import { useOrganization } from "@/contexts/OrganizationContext";
import { Link } from "react-router";
import { Link, useNavigate, useParams } from "react-router";
import {
DropdownMenu,
@@ -54,20 +53,22 @@ export function TeamSwitcher({
}) {
const { isMobile } = useSidebar();
const data = useFragment(teamSwitcherFragment, organizations);
const { currentOrganization, setCurrentOrganization } = useOrganization();
const navigate = useNavigate();
const { organizationId } = useParams();
const [currentOrganization, setCurrentOrganization] =
useState<Organization | null>(null);
useEffect(() => {
console.log(data.organizations);
if (
data.organizations &&
data.organizations.edges.length > 0 &&
!currentOrganization
) {
// Type assertion to include plan field
setCurrentOrganization(data.organizations.edges[0].node);
if (data.organizations && data.organizations.edges.length > 0) {
// Find the current organization based on the URL parameter
const org = data.organizations.edges.find(
(edge) => edge.node.id === organizationId
);
if (org) {
setCurrentOrganization(org.node);
}
}
}, [data.organizations, currentOrganization, setCurrentOrganization]);
}, [data.organizations, organizationId]);
// If no organizations or data is still loading
if (!currentOrganization) {
@@ -88,6 +89,11 @@ export function TeamSwitcher({
return null;
};
const handleOrganizationSwitch = (org: Organization) => {
// Navigate to the selected organization
navigate(`/organizations/${org.id}`);
};
return (
<SidebarMenu>
<SidebarMenuItem>
@@ -121,7 +127,7 @@ export function TeamSwitcher({
{data.organizations.edges.map((edge, index) => (
<DropdownMenuItem
key={edge.node.id}
onClick={() => setCurrentOrganization(edge.node)}
onClick={() => handleOrganizationSwitch(edge.node)}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-sm border">