✨(frontend) add email validation pages
This commit is contained in:
committed by
Sylvain Boissel
parent
7e1d8111b3
commit
a5111809bb
@@ -0,0 +1,62 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import React from 'react';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { UserReconciliation } from '../components/UserReconciliation';
|
||||
|
||||
describe('UserReconciliation', () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
['active', 'inactive'].forEach((type) => {
|
||||
test(`renders when reconciliation is a ${type} success`, async () => {
|
||||
fetchMock.get(
|
||||
`http://test.jest/api/v1.0/user_reconciliations/${type}/123456/`,
|
||||
{ details: 'Success' },
|
||||
);
|
||||
|
||||
render(
|
||||
<UserReconciliation
|
||||
type={type as 'active' | 'inactive'}
|
||||
reconciliationId="123456"
|
||||
/>,
|
||||
{
|
||||
wrapper: AppWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByText(/Email validated successfully !/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders when reconciliation fails', async () => {
|
||||
fetchMock.get(
|
||||
`http://test.jest/api/v1.0/user_reconciliations/active/invalid-id/`,
|
||||
{
|
||||
throws: new Error('invalid id'),
|
||||
},
|
||||
);
|
||||
|
||||
render(<UserReconciliation type="active" reconciliationId="invalid-id" />, {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls().length).toBe(1);
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByText(/An error occurred during email validation./i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useAuthQuery';
|
||||
export * from './useUserReconciliations';
|
||||
export * from './types';
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
|
||||
type UserReconciliationResponse = {
|
||||
details: string;
|
||||
};
|
||||
|
||||
interface UserReconciliationProps {
|
||||
type: 'active' | 'inactive';
|
||||
reconciliationId?: string;
|
||||
}
|
||||
|
||||
export const userReconciliations = async ({
|
||||
type,
|
||||
reconciliationId,
|
||||
}: UserReconciliationProps): Promise<UserReconciliationResponse> => {
|
||||
const response = await fetchAPI(
|
||||
`user_reconciliations/${type}/${reconciliationId}/`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIError(
|
||||
'Failed to do the user reconciliation',
|
||||
await errorCauses(response),
|
||||
);
|
||||
}
|
||||
|
||||
return response.json() as Promise<UserReconciliationResponse>;
|
||||
};
|
||||
|
||||
export const KEY_USER_RECONCILIATIONS = 'user_reconciliations';
|
||||
|
||||
export function useUserReconciliationsQuery(
|
||||
param: UserReconciliationProps,
|
||||
queryConfig?: UseQueryOptions<
|
||||
UserReconciliationResponse,
|
||||
APIError,
|
||||
UserReconciliationResponse
|
||||
>,
|
||||
) {
|
||||
return useQuery<
|
||||
UserReconciliationResponse,
|
||||
APIError,
|
||||
UserReconciliationResponse
|
||||
>({
|
||||
queryKey: [KEY_USER_RECONCILIATIONS, param],
|
||||
queryFn: () => userReconciliations(param),
|
||||
...queryConfig,
|
||||
});
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,104 @@
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import Image from 'next/image';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import error_img from '@/assets/icons/error-planetes.png';
|
||||
import { Box, Icon, Loading, StyledLink, Text } from '@/components';
|
||||
|
||||
import { useUserReconciliationsQuery } from '../api';
|
||||
import success_gif from '../assets/rocket.gif';
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
interface UserReconciliationProps {
|
||||
type: 'active' | 'inactive';
|
||||
reconciliationId: string;
|
||||
}
|
||||
|
||||
export const UserReconciliation = ({
|
||||
type,
|
||||
reconciliationId,
|
||||
}: UserReconciliationProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: userReconciliations, isError } = useUserReconciliationsQuery({
|
||||
type,
|
||||
reconciliationId,
|
||||
});
|
||||
|
||||
if (!userReconciliations && !isError) {
|
||||
return (
|
||||
<Loading
|
||||
$height="100vh"
|
||||
$width="100vw"
|
||||
$position="absolute"
|
||||
$css="top: 0;"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let render = (
|
||||
<>
|
||||
<Image src={success_gif} alt="" unoptimized priority />
|
||||
<Text
|
||||
as="p"
|
||||
$textAlign="center"
|
||||
$maxWidth="350px"
|
||||
$theme="neutral"
|
||||
$margin="0"
|
||||
>
|
||||
{t('Email validated successfully !')}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
render = (
|
||||
<>
|
||||
<Image
|
||||
src={error_img}
|
||||
alt=""
|
||||
width={300}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
as="p"
|
||||
$textAlign="center"
|
||||
$maxWidth="350px"
|
||||
$theme="neutral"
|
||||
$margin="0"
|
||||
>
|
||||
{t('An error occurred during email validation.')}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box $align="center" $margin="auto" $gap="md" $padding={{ bottom: '2rem' }}>
|
||||
{render}
|
||||
|
||||
<Box $direction="row" $gap="sm">
|
||||
<StyledLink href="/">
|
||||
<StyledButton
|
||||
color="neutral"
|
||||
icon={
|
||||
<Icon
|
||||
iconName="house"
|
||||
variant="symbols-outlined"
|
||||
$withThemeInherited
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t('Home')}
|
||||
</StyledButton>
|
||||
</StyledLink>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './Auth';
|
||||
export * from './ButtonLogin';
|
||||
export * from './UserAvatar';
|
||||
export * from './UserReconciliation';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { UserReconciliation } from '@/features/auth/components/UserReconciliation';
|
||||
import { PageLayout } from '@/layouts';
|
||||
import { NextPageWithLayout } from '@/types/next';
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
query: { id },
|
||||
} = useRouter();
|
||||
|
||||
if (typeof id !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>{`${t('User reconciliation')} - ${t('Docs')}`}</title>
|
||||
<meta
|
||||
property="og:title"
|
||||
content={`${t('User reconciliation')} - ${t('Docs')}`}
|
||||
key="title"
|
||||
/>
|
||||
</Head>
|
||||
<UserReconciliation type="active" reconciliationId={id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Page.getLayout = function getLayout(page: ReactElement) {
|
||||
return <PageLayout withFooter={false}>{page}</PageLayout>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,40 @@
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { UserReconciliation } from '@/features/auth/components/UserReconciliation';
|
||||
import { PageLayout } from '@/layouts';
|
||||
import { NextPageWithLayout } from '@/types/next';
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
query: { id },
|
||||
} = useRouter();
|
||||
|
||||
if (typeof id !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>{`${t('User reconciliation')} - ${t('Docs')}`}</title>
|
||||
<meta
|
||||
property="og:title"
|
||||
content={`${t('User reconciliation')} - ${t('Docs')}`}
|
||||
key="title"
|
||||
/>
|
||||
</Head>
|
||||
<UserReconciliation type="inactive" reconciliationId={id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Page.getLayout = function getLayout(page: ReactElement) {
|
||||
return <PageLayout withFooter={false}>{page}</PageLayout>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -1,4 +1,23 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
import * as dotenv from 'dotenv';
|
||||
import React from 'react';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
dotenv.config({ path: './.env.test', quiet: true });
|
||||
|
||||
vi.mock('next/image', () => ({
|
||||
__esModule: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
default: (props: any) => {
|
||||
const {
|
||||
src,
|
||||
alt = '',
|
||||
unoptimized: _unoptimized,
|
||||
priority: _priority,
|
||||
...rest
|
||||
} = props;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const resolved = typeof src === 'string' ? src : src?.src;
|
||||
return React.createElement('img', { src: resolved, alt, ...rest });
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user