start
This commit is contained in:
20
src/components/layouts/main/__tests__/auth-header.test.tsx
Normal file
20
src/components/layouts/main/__tests__/auth-header.test.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import AuthHeader from '../auth-header';
|
||||
import { mockFailureResponse } from '../../../../mocks/server';
|
||||
import { renderWithClient } from '../../../../utils/test/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
describe('AuthHeader component', () => {
|
||||
test('successful query component', async () => {
|
||||
const result = renderWithClient(<AuthHeader />, MemoryRouter);
|
||||
|
||||
expect(await result.findByText(/تست/i)).toBeInTheDocument();
|
||||
expect(await result.findByText(/خروج/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('failure query component', async () => {
|
||||
mockFailureResponse();
|
||||
const result = renderWithClient(<AuthHeader />, MemoryRouter);
|
||||
|
||||
expect(await result.findByText(/ورود/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
9
src/components/layouts/main/__tests__/footer.test.tsx
Normal file
9
src/components/layouts/main/__tests__/footer.test.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import Footer from '../footer';
|
||||
|
||||
describe('Footer component', () => {
|
||||
test('successful render component', async () => {
|
||||
const result = render(<Footer />);
|
||||
expect(await result.findByTestId('footer')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
16
src/components/layouts/main/__tests__/header.test.tsx
Normal file
16
src/components/layouts/main/__tests__/header.test.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { renderWithClient } from '../../../../utils/test/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import Header from '../header';
|
||||
|
||||
describe('Header component', () => {
|
||||
test('successful render component', async () => {
|
||||
const result = renderWithClient(<Header />, MemoryRouter);
|
||||
expect(await result.findByTestId('header')).toBeInTheDocument();
|
||||
expect(await result.findByTestId('header-nav')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('successful query component', async () => {
|
||||
const result = renderWithClient(<Header />, MemoryRouter);
|
||||
expect(await result.findByText(/Med Moshaver/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
21
src/components/layouts/main/__tests__/main-layout.test.tsx
Normal file
21
src/components/layouts/main/__tests__/main-layout.test.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { renderWithClient } from '../../../../utils/test/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import MainLayout from '../index';
|
||||
import { expectPageTestIds, expectPageTitle } from '../../../../utils/test/expects';
|
||||
|
||||
describe('MainLayout component', () => {
|
||||
test('successful render component', async () => {
|
||||
const title = 'title';
|
||||
const result = renderWithClient(
|
||||
<MainLayout loginRequired={false} title={title} />,
|
||||
MemoryRouter
|
||||
);
|
||||
await expectPageTitle(title);
|
||||
await expectPageTestIds(result);
|
||||
});
|
||||
|
||||
test('successful render loader component', async () => {
|
||||
const result = renderWithClient(<MainLayout loginRequired />, MemoryRouter);
|
||||
expect(await result.findByTestId('loader')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
59
src/components/layouts/main/auth-header.tsx
Normal file
59
src/components/layouts/main/auth-header.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import NavLink from '../../shared/buttons/nav-link';
|
||||
import Button from '../../shared/buttons/button';
|
||||
import ButtonGroup from '../../shared/buttons/button-group';
|
||||
import { useUser } from '../../../hooks/react-query/use-user';
|
||||
import { useLogoutMutation } from '../../../hooks/react-query/use-auth';
|
||||
import { getLoginRedirectURL } from '../../utils/url';
|
||||
|
||||
type AuthHeaderProps = { className?: string };
|
||||
|
||||
function AuthHeader(props: AuthHeaderProps) {
|
||||
const history = useHistory();
|
||||
const { status, data: user } = useUser();
|
||||
const { mutate: logoutUser } = useLogoutMutation();
|
||||
|
||||
if (status === 'error') {
|
||||
return (
|
||||
<ButtonGroup className={props.className}>
|
||||
<NavLink
|
||||
to={getLoginRedirectURL(history.location.pathname, history.location.search)}
|
||||
color='teal'
|
||||
colorWeight={600}
|
||||
content='ورود'
|
||||
className={props.className}
|
||||
/>
|
||||
<NavLink
|
||||
to='/register'
|
||||
color='teal'
|
||||
colorWeight={600}
|
||||
content='ثبت نام'
|
||||
className={props.className}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonGroup className={props.className}>
|
||||
<NavLink
|
||||
to='/home'
|
||||
color='teal'
|
||||
colorWeight={600}
|
||||
content={user?.name || user?.mobile}
|
||||
loading={status === 'loading'}
|
||||
disabled={status === 'loading'}
|
||||
/>
|
||||
<Button
|
||||
content='خروج'
|
||||
color='red'
|
||||
colorWeight={500}
|
||||
onClick={logoutUser}
|
||||
loading={status === 'loading'}
|
||||
disabled={status === 'loading'}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuthHeader;
|
||||
5
src/components/layouts/main/footer.tsx
Normal file
5
src/components/layouts/main/footer.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
function Footer() {
|
||||
return <footer data-testid='footer' />;
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
87
src/components/layouts/main/header.tsx
Normal file
87
src/components/layouts/main/header.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { MenuIcon } from '@heroicons/react/outline';
|
||||
import { XIcon } from '@heroicons/react/solid';
|
||||
import AuthHeader from './auth-header';
|
||||
import NavLink from '../../shared/buttons/nav-link';
|
||||
|
||||
export type HeaderPropsType = { isAuthenticated: boolean };
|
||||
|
||||
function Header({ isAuthenticated }: HeaderPropsType) {
|
||||
const [isHidden, setIsHidden] = useState(true);
|
||||
const iconSize = 34;
|
||||
|
||||
return (
|
||||
<header data-testid='header' className='sticky top-0 z-50'>
|
||||
<nav
|
||||
data-testid='header-nav'
|
||||
className='flex flex-wrap items-center justify-between bg-teal-800 p-2'
|
||||
>
|
||||
<NavLink to='/' color='transparent' wrapperClassName='hidden md:block'>
|
||||
<p className='text-lg font-bold'>Med Moshaver</p>
|
||||
</NavLink>
|
||||
|
||||
<AuthHeader className='mx-2 md:order-last' />
|
||||
|
||||
<div className='flex md:hidden'>
|
||||
<div className='flex md:hidden'>
|
||||
<button id='hamburger' onClick={() => setIsHidden(isHidden => !isHidden)}>
|
||||
<MenuIcon
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
className={classNames(
|
||||
'toggle block',
|
||||
{ hidden: !isHidden },
|
||||
'rounded-md border-2 border-yellow-400 stroke-current p-1 text-yellow-400'
|
||||
)}
|
||||
/>
|
||||
<XIcon
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
className={classNames(
|
||||
'toggle block',
|
||||
{ hidden: isHidden },
|
||||
'rounded-md border-2 border-yellow-400 stroke-current p-1 text-yellow-400'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
'toggle',
|
||||
{ hidden: isHidden },
|
||||
'text-bold mt-5 w-full text-right md:mt-0 md:flex md:w-auto',
|
||||
'border-t-2 border-blue-900 md:border-none'
|
||||
)}
|
||||
>
|
||||
{links
|
||||
.filter(link => !link.auth || isAuthenticated)
|
||||
.map(link => (
|
||||
<div
|
||||
key={link.id}
|
||||
className={`
|
||||
w-100 block rounded-none border-t-2 border-yellow-400
|
||||
px-1 py-1 md:inline-block md:border-none
|
||||
`}
|
||||
>
|
||||
<NavLink to={link.href} textSize='base' color='transparent'>
|
||||
{link.name}
|
||||
</NavLink>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
const links = [
|
||||
{ id: 1, name: 'معرفی سامانه', href: '/', auth: false },
|
||||
{ id: 2, name: 'کد معرف', href: '/home', auth: true },
|
||||
{ id: 3, name: 'مالی', href: '/financial', auth: true },
|
||||
{ id: 4, name: 'شرایط و قوانین', href: '/terms-and-conditions', auth: false }
|
||||
];
|
||||
|
||||
export default Header;
|
||||
75
src/components/layouts/main/index.tsx
Normal file
75
src/components/layouts/main/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { Redirect, RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import Header from './header';
|
||||
import Container from '../../shared/container';
|
||||
import Footer from './footer';
|
||||
import ScrollToTop from '../../shared/buttons/scroll-to-top';
|
||||
import Loader from '../../shared/loader';
|
||||
import { getLoginRedirectURL } from '../../utils/url';
|
||||
import { useUser } from '../../../hooks/react-query/use-user';
|
||||
import { useIntroducer } from '../../../hooks/react-query/use-introducer';
|
||||
import CreateIntroducerForm from '../../shared/create-introducer-form';
|
||||
|
||||
export type MainLayoutProps = RouteComponentProps & {
|
||||
title?: string;
|
||||
loginRequired?: boolean;
|
||||
introducerRequired?: boolean;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
function MainLayout({
|
||||
title,
|
||||
loginRequired = true,
|
||||
introducerRequired = false,
|
||||
location,
|
||||
children
|
||||
}: MainLayoutProps) {
|
||||
const { status: userStatus } = useUser();
|
||||
const { status: introducerStatus } = useIntroducer(
|
||||
introducerRequired && userStatus === 'success'
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (title) document.title = title;
|
||||
}, [title]);
|
||||
|
||||
function renderContent() {
|
||||
if (introducerRequired && introducerStatus === 'error')
|
||||
return (
|
||||
<div className='mx-auto w-full w-full sm:w-4/5 md:w-3/4 lg:w-2/3'>
|
||||
<CreateIntroducerForm />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (
|
||||
(introducerRequired && ['indle', 'loading'].includes(introducerStatus)) ||
|
||||
(loginRequired && userStatus === 'loading')
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
if (loginRequired && userStatus === 'error')
|
||||
return <Redirect to={getLoginRedirectURL(location.pathname, location.search)} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex h-screen flex-col'>
|
||||
<Header isAuthenticated={userStatus === 'success'} />
|
||||
<main data-testid='main' className='flex-1 py-4'>
|
||||
<Container>{renderContent()}</Container>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(MainLayout);
|
||||
Reference in New Issue
Block a user