mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-22 22:35:45 +00:00
Merge branch 'development' into feat/FE/US-335/production-data-report
This commit is contained in:
@@ -1,56 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import useSWRImmutable from 'swr/immutable';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { useAuth } from '@/services/hooks/useAuth';
|
||||
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general';
|
||||
import { AxiosError } from 'axios';
|
||||
import { redirectToSSO } from '@/lib/auth-helper';
|
||||
|
||||
interface RequireAuthProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const RequireAuth = ({ children }: RequireAuthProps) => {
|
||||
const router = useRouter();
|
||||
const { setUser, setIsLoadingUser } = useAuth();
|
||||
const { user, setUser, setIsLoadingUser } = useAuth();
|
||||
|
||||
const {
|
||||
data: userResponse,
|
||||
isLoading: isLoadingUserResponse,
|
||||
error: userErrorResponse,
|
||||
} = useSWRImmutable<
|
||||
} = useSWR<
|
||||
GetMeResponse & { ok?: boolean },
|
||||
AxiosError<BaseApiResponse>,
|
||||
SWRHttpKey
|
||||
>('/sso/userinfo', httpClientFetcher, {
|
||||
shouldRetryOnError: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshInterval: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoadingUser(isLoadingUserResponse);
|
||||
}, [isLoadingUserResponse, setIsLoadingUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isResponseSuccess(userResponse)) {
|
||||
setUser(userResponse.data);
|
||||
} else if (
|
||||
isResponseError(userErrorResponse?.response?.data) &&
|
||||
typeof window !== 'undefined'
|
||||
) {
|
||||
router.replace(
|
||||
`${process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string}?redirect_url=${window.location.href}`
|
||||
);
|
||||
}
|
||||
}, [userResponse, userErrorResponse, setIsLoadingUser, setUser]);
|
||||
}, [userResponse, setUser]);
|
||||
|
||||
if (isLoadingUserResponse && !userResponse && !userErrorResponse) {
|
||||
// Explicitly handle 401 redirect from the component level
|
||||
useEffect(() => {
|
||||
if (
|
||||
isResponseError(userResponse) &&
|
||||
userErrorResponse?.response?.status === 401
|
||||
) {
|
||||
// Clear cache to prevent stale data from rendering children
|
||||
// mutate('/sso/userinfo', undefined, { revalidate: false }); // Optional: if using global mutate
|
||||
setUser(undefined);
|
||||
redirectToSSO();
|
||||
}
|
||||
}, [userErrorResponse, setUser, userResponse]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoadingUser(isLoadingUserResponse);
|
||||
}, [isLoadingUserResponse]);
|
||||
|
||||
if (
|
||||
(isLoadingUserResponse && !userResponse && !userErrorResponse) ||
|
||||
(!userResponse && !userErrorResponse)
|
||||
) {
|
||||
return (
|
||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
<span className='loading loading-spinner loading-xl' />
|
||||
@@ -58,7 +63,25 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
return <>{isResponseSuccess(userResponse) && children}</>;
|
||||
if (userErrorResponse) {
|
||||
return (
|
||||
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
|
||||
<h2 className='text-2xl font-bold text-error'>Authentication Failed</h2>
|
||||
<p className='text-gray-600'>
|
||||
Please try refreshing the page or contact support if the problem
|
||||
persists.
|
||||
</p>
|
||||
<button
|
||||
className='btn btn-primary'
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{isResponseSuccess(userResponse) && user && children}</>;
|
||||
};
|
||||
|
||||
export default RequireAuth;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
'use client';
|
||||
|
||||
import { Icon } from '@iconify/react';
|
||||
import Link from 'next/link';
|
||||
import { ReactNode } from 'react';
|
||||
import { cn } from '@/lib/helper';
|
||||
|
||||
export interface DrawerHeaderProps {
|
||||
// Left side props
|
||||
leftIcon?: string;
|
||||
leftIconSize?: number;
|
||||
leftIconHref?: string;
|
||||
leftIconOnClick?: () => void;
|
||||
leftIconClassName?: string;
|
||||
|
||||
// Subtitle/label props
|
||||
subtitle?: string | ReactNode;
|
||||
subtitleClassName?: string;
|
||||
|
||||
// Right side actions (children)
|
||||
children?: ReactNode;
|
||||
|
||||
// Container props
|
||||
className?: string;
|
||||
showDivider?: boolean;
|
||||
}
|
||||
|
||||
const DrawerHeader = ({
|
||||
leftIcon = 'mdi:close',
|
||||
leftIconSize = 24,
|
||||
leftIconHref,
|
||||
leftIconOnClick,
|
||||
leftIconClassName,
|
||||
subtitle,
|
||||
subtitleClassName,
|
||||
children,
|
||||
className,
|
||||
showDivider = true,
|
||||
}: DrawerHeaderProps) => {
|
||||
const renderLeftIcon = () => {
|
||||
const iconElement = (
|
||||
<Icon
|
||||
icon={leftIcon}
|
||||
width={leftIconSize}
|
||||
height={leftIconSize}
|
||||
className={cn('cursor-pointer', leftIconClassName)}
|
||||
/>
|
||||
);
|
||||
|
||||
if (leftIconHref) {
|
||||
return (
|
||||
<Link href={leftIconHref} className='hover:text-gray-400'>
|
||||
{iconElement}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (leftIconOnClick) {
|
||||
return (
|
||||
<button
|
||||
onClick={leftIconOnClick}
|
||||
className='hover:text-gray-400 bg-transparent border-none p-0'
|
||||
>
|
||||
{iconElement}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return iconElement;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-row justify-between items-center px-4 pt-4',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Left Side */}
|
||||
<div className='flex flex-row h-full gap-2 items-center'>
|
||||
{renderLeftIcon()}
|
||||
|
||||
{showDivider && subtitle && (
|
||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
||||
)}
|
||||
|
||||
{subtitle && (
|
||||
<div className={cn('text-sm text-neutral', subtitleClassName)}>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side Actions */}
|
||||
{children && (
|
||||
<div className='flex flex-row gap-3 justify-end items-center'>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawerHeader;
|
||||
Reference in New Issue
Block a user