diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx index c4d41e3ad..588232d3e 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx @@ -1,13 +1,11 @@ import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { useAuth } from '@/auth/hooks/useAuth'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; -import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState'; +import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; import { AppPath } from '@/types/AppPath'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared'; import { useNavigateApp } from '~/hooks/useNavigateApp'; @@ -17,15 +15,9 @@ export const VerifyEffect = () => { const errorMessage = searchParams.get('errorMessage'); const { enqueueSnackBar } = useSnackBar(); - const isLogged = useIsLogged(); const navigate = useNavigateApp(); - - const { getAuthTokensFromLoginToken } = useAuth(); - - const setIsAppWaitingForFreshObjectMetadata = useSetRecoilState( - isAppWaitingForFreshObjectMetadataState, - ); + const { verifyLoginToken } = useVerifyLogin(); useEffect(() => { if (isDefined(errorMessage)) { @@ -36,8 +28,7 @@ export const VerifyEffect = () => { } if (isDefined(loginToken)) { - setIsAppWaitingForFreshObjectMetadata(true); - getAuthTokensFromLoginToken(loginToken); + verifyLoginToken(loginToken); } else if (!isLogged) { navigate(AppPath.SignInUp); } diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts b/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts new file mode 100644 index 000000000..57cd68a2d --- /dev/null +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts @@ -0,0 +1,70 @@ +import { renderHook } from '@testing-library/react'; +import { RecoilRoot } from 'recoil'; + +import { AppPath } from '@/types/AppPath'; +import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; +import { useAuth } from '../useAuth'; +import { useVerifyLogin } from '../useVerifyLogin'; + +jest.mock('../useAuth', () => ({ + useAuth: jest.fn(), +})); + +jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({ + useSnackBar: jest.fn(), +})); + +jest.mock('~/hooks/useNavigateApp', () => ({ + useNavigateApp: jest.fn(), +})); + +const renderHooks = () => { + const { result } = renderHook(() => useVerifyLogin(), { + wrapper: RecoilRoot, + }); + return { result }; +}; + +describe('useVerifyLogin', () => { + const mockGetAuthTokensFromLoginToken = jest.fn(); + const mockEnqueueSnackBar = jest.fn(); + const mockNavigate = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + (useAuth as jest.Mock).mockReturnValue({ + getAuthTokensFromLoginToken: mockGetAuthTokensFromLoginToken, + }); + + (useSnackBar as jest.Mock).mockReturnValue({ + enqueueSnackBar: mockEnqueueSnackBar, + }); + + (useNavigateApp as jest.Mock).mockReturnValue(mockNavigate); + }); + + it('should verify login token', async () => { + const { result } = renderHooks(); + + await result.current.verifyLoginToken('test-token'); + + expect(mockGetAuthTokensFromLoginToken).toHaveBeenCalledWith('test-token'); + }); + + it('should handle verification error', async () => { + const error = new Error('Verification failed'); + mockGetAuthTokensFromLoginToken.mockRejectedValueOnce(error); + + const { result } = renderHooks(); + + await result.current.verifyLoginToken('test-token'); + + expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Authentication failed', { + variant: SnackBarVariant.Error, + }); + expect(mockNavigate).toHaveBeenCalledWith(AppPath.SignInUp); + }); +}); diff --git a/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts b/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts new file mode 100644 index 000000000..096258678 --- /dev/null +++ b/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts @@ -0,0 +1,34 @@ +import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; + +import { useAuth } from '@/auth/hooks/useAuth'; +import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState'; +import { AppPath } from '@/types/AppPath'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useSetRecoilState } from 'recoil'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; + +export const useVerifyLogin = () => { + const { enqueueSnackBar } = useSnackBar(); + const navigate = useNavigateApp(); + const { getAuthTokensFromLoginToken } = useAuth(); + + const setIsAppWaitingForFreshObjectMetadata = useSetRecoilState( + isAppWaitingForFreshObjectMetadataState, + ); + + const verifyLoginToken = async (loginToken: string) => { + try { + setIsAppWaitingForFreshObjectMetadata(true); + await getAuthTokensFromLoginToken(loginToken); + } catch (error) { + enqueueSnackBar('Authentication failed', { + variant: SnackBarVariant.Error, + }); + navigate(AppPath.SignInUp); + } finally { + setIsAppWaitingForFreshObjectMetadata(false); + } + }; + + return { verifyLoginToken }; +}; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx index 7ba095ff9..757c06a9d 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx @@ -27,15 +27,11 @@ import { useUserLookupAdminPanelMutation } from '~/generated/graphql'; import packageJson from '../../../../../package.json'; -const StyledLinkContainer = styled.div` - margin-right: ${({ theme }) => theme.spacing(2)}; - width: 100%; -`; - const StyledContainer = styled.div` align-items: center; display: flex; flex-direction: row; + gap: ${({ theme }) => theme.spacing(2)}; `; const StyledUserInfo = styled.div` @@ -143,16 +139,14 @@ export const SettingsAdminGeneral = () => { /> - - - +