Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c5086f2
feat(construct): change the header content according to figma and add…
sangayt1997 Jan 23, 2026
eed8068
feat(construct): implementing the getOrganization api in the org-swit…
sangayt1997 Jan 23, 2026
1a166c9
feat(construct): adding the organization switch trigger the grantType…
sangayt1997 Jan 23, 2026
950a5bb
fix(construct): fixed the test failing errors
sangayt1997 Jan 23, 2026
54bb772
refactor(construct): refactor the multi-orgs logics
sangayt1997 Jan 23, 2026
464c444
refactor(construct): sync with dev branch
sangayt1997 Jan 26, 2026
b82a0e9
fix(construct): fixed the accessToken and refresh token of switch org…
sangayt1997 Jan 26, 2026
901373c
fix(construct): fixed the logout and login removed select org issue
sangayt1997 Jan 26, 2026
ebf1ed0
fix(construct): fixed the switch orgs affecting the permission guards
sangayt1997 Jan 26, 2026
3d429f9
fix(construct): fixed the darkmode transition issue due to transition…
sangayt1997 Jan 26, 2026
654b3f4
fix(construct): fixed the border issue in profile image
sangayt1997 Jan 26, 2026
efae6a7
Merge pull request #963 from SELISEdigitalplatforms/feat/multi-orgs
sangayt1997 Jan 26, 2026
bc382be
Merge pull request #966 from SELISEdigitalplatforms/pre-dev
sangayt1997 Jan 26, 2026
d60a263
fix(construct): fixed sonarqube issue
sangayt1997 Jan 26, 2026
24c045e
fix(construct): fixed the inappropriate toast message for forbidden 4…
sangayt1997 Jan 27, 2026
613ab80
Merge pull request #967 from SELISEdigitalplatforms/fix/proper-toast-…
sangayt1997 Jan 27, 2026
d0719ef
Merge pull request #968 from SELISEdigitalplatforms/pre-dev
sangayt1997 Jan 27, 2026
46de913
fix(construct): fixed the roles issue in the updateAccount due to rep…
sangayt1997 Jan 28, 2026
29d8b47
fix(construct): fixed the account activation toast with proper message
sangayt1997 Jan 28, 2026
4196267
Merge pull request #969 from SELISEdigitalplatforms/pre-dev
sangayt1997 Jan 28, 2026
ba9b0d9
fix(construct): fixed aurthorized roles and permission
sangayt1997 Feb 2, 2026
9b95ac4
fix(construct): fixed aurthorized roles and permission
sangayt1997 Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@types/lodash": "^4.17.23",
"@types/node": "^20.19.19",
"@types/papaparse": "^5.3.15",
"@types/react": "^19.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/core/divider/divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export const Divider = ({ text }: DividerProps) => {
return (
<div className="flex items-center gap-4">
<div className="flex-1">
<hr className="h-[2px] bg-gray-200 border-0 rounded" />
<hr className="h-[2px] bg-border border-0 rounded" />
</div>
<div className="text-base font-normal text-low-emphasis">{text}</div>
<div className="flex-1">
<hr className="h-[2px] bg-gray-200 border-0 rounded" />
<hr className="h-[2px] bg-border border-0 rounded" />
</div>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions src/components/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export { Notification } from './notification/component/notification/notification
export { NotificationItem } from './notification/component/notification-item/notification-item';
export * from './notification/hooks/use-notification';
export { ExtensionBanner } from './extension-banner/extension-banner';
export { ThemeSwitcher } from './theme-switcher/theme-switcher';
export { OrgSwitcher } from './org-switcher/org-switcher';
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const NotificationItem = ({ notification }: Readonly<NotificationItemProp

return (
<div className="border-b border-border last:border-b-0">
<div className="group flex items-start gap-3 p-2 hover:bg-muted/50 transition-colors cursor-pointer">
<div className="group flex items-start gap-3 p-2 hover:bg-muted/50 cursor-pointer">
<div className={`w-2 h-2 rounded-full mt-3 ${!isRead && 'bg-secondary'}`} />
<div className="flex w-full justify-between">
<div className="flex flex-col gap-1">
Expand Down
167 changes: 167 additions & 0 deletions src/components/core/org-switcher/org-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { useState, useMemo } from 'react';
import { Building2, ChevronDown, ChevronUp } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui-kit/dropdown-menu';
import { Skeleton } from '@/components/ui-kit/skeleton';
import { useGetAccount } from '@/modules/profile/hooks/use-account';
import { useGetMultiOrgs } from '@/lib/api/hooks/use-multi-orgs';
import { switchOrganization } from '@/modules/auth/services/auth.service';
import { useAuthStore } from '@/state/store/auth';
import { useToast } from '@/hooks/use-toast';
import { HttpError } from '@/lib/https';
import { decodeJWT } from '@/lib/utils/decode-jwt-utils';

const projectKey = import.meta.env.VITE_X_BLOCKS_KEY || '';

export const OrgSwitcher = () => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isSwitching, setIsSwitching] = useState(false);
const { t } = useTranslation();
const { setTokens, accessToken } = useAuthStore();
const { toast } = useToast();

const currentOrgId = useMemo(() => {
if (!accessToken) return null;
const decoded = decodeJWT(accessToken);
return decoded?.org_id ?? null;
}, [accessToken]);

const { data, isLoading } = useGetAccount();
const { data: orgsData, isLoading: isLoadingOrgs } = useGetMultiOrgs({
ProjectKey: projectKey,
Page: 0,
PageSize: 10,
});

const organizations = orgsData?.organizations ?? [];
const enabledOrganizations = organizations.filter((org) => org.isEnable);

const selectedOrg = currentOrgId
? enabledOrganizations.find((org) => org.itemId === currentOrgId)
: enabledOrganizations[0];

const currentOrgRoles = useMemo(() => {
if (!data?.memberships?.length || !currentOrgId) return [];
const membership = data.memberships.find((m) => m.organizationId === currentOrgId);
return membership?.roles ?? [];
}, [data, currentOrgId]);

const translatedRoles = currentOrgRoles
.map((role: string) => {
const roleKey = role.toUpperCase();
return t(roleKey);
})
.join(', ');

const handleOrgSelect = async (orgId: string) => {
if (isSwitching || orgId === currentOrgId) {
return;
}

try {
setIsSwitching(true);
setIsDropdownOpen(false);

const response = await switchOrganization(orgId);

setTokens({
accessToken: response.access_token,
refreshToken: (response.refresh_token || useAuthStore.getState().refreshToken) ?? '',
});

localStorage.setItem('selected-org-id', orgId);

window.location.reload();
} catch (error) {
console.error('Failed to switch organization:', error);
setIsSwitching(false);

let errorTitle = t('FAILED_TO_SWITCH_ORGANIZATION');
let errorDescription = t('SOMETHING_WENT_WRONG');

if (error instanceof HttpError) {
const errorData = error.error;

if (errorData?.error === 'user_inactive_or_not_verified') {
errorTitle = t('ACCESS_DENIED');
errorDescription =
typeof errorData?.error_description === 'string'
? errorData.error_description
: t('USER_NOT_EXIST_IN_ORGANIZATION');
} else if (typeof errorData?.error_description === 'string') {
errorDescription = errorData.error_description;
} else if (typeof errorData?.error === 'string') {
errorDescription = errorData.error;
}
}

toast({
variant: 'destructive',
title: errorTitle,
description: errorDescription,
});
}
};

const isComponentLoading = isLoading || isLoadingOrgs || isSwitching;

return (
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild className="cursor-pointer p-1 rounded-[2px]">
<div className="flex justify-between items-center gap-1 sm:gap-3 cursor-pointer">
<div className="flex items-center">
{isComponentLoading ? (
<Skeleton className="h-8 w-8 rounded-full" />
) : (
<Building2 className="h-5 w-5 text-medium-emphasis" />
)}
</div>
<div className="flex flex-col">
{isComponentLoading ? (
<>
<Skeleton className="w-24 h-4 mb-1" />
<Skeleton className="w-16 h-3" />
</>
) : (
<>
<h2 className="text-xs font-normal text-high-emphasis">
{selectedOrg?.name ?? '_'}
</h2>
<p className="text-[10px] text-low-emphasis capitalize">{translatedRoles}</p>
</>
)}
</div>
{isDropdownOpen ? (
<ChevronUp className="h-5 w-5 text-medium-emphasis" />
) : (
<ChevronDown className="h-5 w-5 text-medium-emphasis" />
)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56 text-medium-emphasis"
align="end"
side="top"
sideOffset={10}
>
{enabledOrganizations.length > 0 ? (
enabledOrganizations.map((org) => (
<DropdownMenuItem key={org.itemId} onClick={() => handleOrgSelect(org.itemId)}>
{org.name}
</DropdownMenuItem>
))
) : (
<DropdownMenuItem>No orgs found</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem disabled>{t('CREATE_NEW')}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
2 changes: 1 addition & 1 deletion src/components/core/phone-input/phone-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const UIPhoneInput = forwardRef<any, UIPhoneInputProps>(
placeholder={placeholder}
defaultCountry={defaultCountry}
className={cn(
'flex h-11 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
'flex h-11 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className
)}
countryCallingCodeEditable={countryCallingCodeEditable}
Expand Down
Loading
Loading