Skip to content

Commit

Permalink
ui: added login page to new holdinpen
Browse files Browse the repository at this point in the history
  • Loading branch information
karolina-siemieniuk-morawska committed Jul 10, 2024
1 parent 99b2155 commit 364afa4
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 39 deletions.
24 changes: 19 additions & 5 deletions ui/src/common/PrivateRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ import { List } from 'immutable';

import RouteOrRedirect from './components/RouteOrRedirect';
import { isAuthorized } from './authorization';
import { ERROR_401, USER_LOGIN } from './routes';
import { ERROR_401, HOLDINGPEN_LOGIN_NEW, USER_LOGIN } from './routes';

interface PrivateRouteProps extends ComponentPropsWithoutRef<any> {
loggedIn: boolean;
userRoles: List<string>;
authorizedRoles: List<string>;
component?: JSX.Element | string | any;
isHoldinpen?: boolean;
loggedInToHoldinpen?: boolean;
}

function PrivateRoute(props: PrivateRouteProps) {
function PrivateRoute({
isHoldinpen = false,
loggedInToHoldinpen = false,
...props
}: PrivateRouteProps) {
if (props.loggedIn && props.authorizedRoles) {
const isUserAuthorized = isAuthorized(props.userRoles, props.authorizedRoles);
const isUserAuthorized = isAuthorized(
props.userRoles,
props.authorizedRoles
);
return (
<RouteOrRedirect
redirectTo={ERROR_401}
Expand All @@ -25,10 +34,15 @@ function PrivateRoute(props: PrivateRouteProps) {
/>
);
}

const resolveLoggedIn = props.isHoldinpen
? props.loggedInToHoldinpen && props.loggedIn
: props.loggedIn;

return (
<RouteOrRedirect
redirectTo={USER_LOGIN}
condition={props.loggedIn}
redirectTo={props.isHoldingpen ? HOLDINGPEN_LOGIN_NEW : USER_LOGIN}
condition={resolveLoggedIn}
component={props.component}
{...props}
/>
Expand Down
3 changes: 3 additions & 0 deletions ui/src/common/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ export const HOLDINGPEN_DASHBOARD = `${HOLDINGPEN}/dashboard`;
export const HOLDINGPEN_INSPECT = `${HOLDINGPEN}/inspect`;

export const HOLDINGPEN_NEW = '/holdingpen-new';
export const HOLDINGPEN_LOGIN_NEW = `${HOLDINGPEN_NEW}/login`;
export const HOLDINGPEN_DASHBOARD_NEW = `${HOLDINGPEN_NEW}/dashboard`;
export const HOLDINGPEN_SEARCH_NEW = `${HOLDINGPEN_NEW}/search`;
export const BACKOFFICE_LOGIN =
'https://backoffice.dev.inspirebeta.net/api/token/';
export const BACKOFFICE_API =
'https://backoffice.dev.inspirebeta.net/api/workflows';
export const BACKOFFICE_SEARCH_API = `${BACKOFFICE_API}/search`;
Expand Down
14 changes: 12 additions & 2 deletions ui/src/holdingpen-new/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { MemoryRouter, Route } from 'react-router-dom';
import { fromJS } from 'immutable';
import { render } from '@testing-library/react';

import { getStore } from '../../fixtures/store';
import { getStore, getStoreWithState } from '../../fixtures/store';
import Holdingpen from '..';
import DashboardPageContainer from '../containers/DashboardPageContainer/DashboardPageContainer';
import SearchPageContainer from '../containers/SearchPageContainer/SearchPageContainer';
Expand All @@ -14,9 +15,18 @@ import {
} from '../../common/routes';

describe('Holdingpen', () => {
const store = getStoreWithState({
user: fromJS({
loggedIn: true,
data: {
roles: ['cataloger'],
},
}),
});

it('renders initial state', () => {
const { container } = render(
<Provider store={getStore()}>
<Provider store={store}>
<MemoryRouter initialEntries={[HOLDINGPEN_DASHBOARD_NEW]}>
<Holdingpen />
</MemoryRouter>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable no-underscore-dangle */
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Row, Col, Button, Table } from 'antd';
Expand All @@ -18,9 +17,8 @@ import './DetailPageContainer.less';
import Breadcrumbs from '../../components/Breadcrumbs';
import ContentBox from '../../../common/components/ContentBox';
import CollapsableForm from '../../../submissions/common/components/CollapsableForm';
import { BACKOFFICE_API } from '../../../common/routes';
import { authToken } from '../../token';
import LoadingOrChildren from '../../../common/components/LoadingOrChildren';
import { getSearchResults } from '../../utils/utils';

interface AuthorDetailPageContainerProps {
item: any;
Expand Down Expand Up @@ -117,12 +115,6 @@ const columnsAdvisors = [
},
];

const fetchData = async (id: string) => {
const res = await fetch(`${BACKOFFICE_API}/${id}`, authToken);
const data = await res?.json();
return data || { results: [], count: 0 };
};

const AuthorDetailPageContainer: React.FC<
AuthorDetailPageContainerProps
> = () => {
Expand All @@ -131,7 +123,7 @@ const AuthorDetailPageContainer: React.FC<

useEffect(() => {
(async () => {
setResult(await fetchData(id));
setResult(await getSearchResults(id));
})();
}, [id]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';
import { Row, Card, Button, Input } from 'antd';
import { Field, Form, Formik } from 'formik';
import axios from 'axios';

import DocumentHead from '../../../common/components/DocumentHead';
import {
BACKOFFICE_LOGIN,
HOLDINGPEN_DASHBOARD_NEW,
} from '../../../common/routes';
import storage from '../../../common/storage';

const LocalLoginPage = () => {
const onLoginFormSubmit = async (creds: {
username: string | null;
password: string | null;
}) => {
const response = await axios.post(
BACKOFFICE_LOGIN,
{
email: creds.username,
password: creds.password,
},
{
headers: {
'Content-Type': 'application/json',
},
}
);

if (response.status === 200) {
const { access, refresh } = await response.data;
storage.set('holdingpen.token', access);
storage.set('holdingpen.refreshToken', refresh);

window.location.assign(HOLDINGPEN_DASHBOARD_NEW);
}
};

function renderFormInput({
form,
field,
...rest
}: {
form: any;
field: JSX.Element;
rest: any;
}) {
return <Input {...field} {...rest} />;
}

function renderLoginForm() {
return (
<Form>
<Row className="mb3">
<Field
name="username"
type="username"
placeholder="Username"
data-test-id="username"
component={renderFormInput}
/>
</Row>
<Row className="mb3">
<Field
name="password"
type="password"
placeholder="Password"
data-test-id="password"
component={renderFormInput}
/>
</Row>
<Button
className="w-100"
type="primary"
htmlType="submit"
data-test-id="login"
>
Login
</Button>
</Form>
);
}

return (
<>
<DocumentHead title="Login" />
<Row className="h-100" justify="center" align="middle">
<Card bodyStyle={{ textAlign: 'center' }}>
<h1 className="b mb4 f5">
Please log in with your Backoffice account
</h1>
<Formik
onSubmit={(creds: {
username: string | null;
password: string | null;
}) => onLoginFormSubmit(creds)}
initialValues={{ username: null, password: null }}
>
{renderLoginForm}
</Formik>
</Card>
</Row>
</>
);
};

export default LocalLoginPage;
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ import SearchPagination from '../../../common/components/SearchPagination';
import PublicationsSelectAllContainer from '../../../authors/containers/PublicationsSelectAllContainer';
import UnclickableTag from '../../../common/components/UnclickableTag';
import AuthorResultItem from '../../components/AuthorResultItem';
import { authToken } from '../../token';
import { BACKOFFICE_SEARCH_API } from '../../../common/routes';
import { getSearchResults, refreshToken } from '../../utils/utils';

interface SearchPageContainerProps {
data?: any;
Expand All @@ -38,18 +37,9 @@ const SearchPageContainer: React.FC<SearchPageContainerProps> = () => {

resolveLoading();

const getSearchResults = async () => {
const res = await fetch(
`${BACKOFFICE_SEARCH_API}?page=${page}&size=${size}`,
authToken
);
const data = await res?.json();
return data || { results: [], count: 0 };
};

useEffect(() => {
(async () => {
const data = await getSearchResults();
const data = await getSearchResults({ page: 1, size: 10 });
const filteredData = data?.results?.filter(
(result: any) => result?.data?.id
);
Expand All @@ -58,6 +48,8 @@ const SearchPageContainer: React.FC<SearchPageContainerProps> = () => {
})();
}, [page, size]);

refreshToken();

return (
<div
className="__SearchPageContainer__"
Expand Down
29 changes: 24 additions & 5 deletions ui/src/holdingpen-new/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Redirect } from 'react-router-dom';

import DashboardPage from './containers/DashboardPageContainer/DashboardPageContainer';
import DetailPageContainer from './containers/DetailPageContainer/DetailPageContainer';
Expand All @@ -8,40 +8,59 @@ import {
HOLDINGPEN_NEW,
HOLDINGPEN_DASHBOARD_NEW,
HOLDINGPEN_SEARCH_NEW,
HOLDINGPEN_LOGIN_NEW,
} from '../common/routes';
import SafeSwitch from '../common/components/SafeSwitch';
import AuthorDetailPageContainer from './containers/DetailPageContainer/AuthorDetailPageContainer';
import DocumentHead from '../common/components/DocumentHead';
import LoginPageContainer from './containers/LoginPageContainer/LoginPageContainer';
import storage from '../common/storage';
import PrivateRoute from '../common/PrivateRoute';

const META_DESCRIPTION = 'Tool for curators to manage submissions and harvests';
const TITLE = 'Holdingpen';

const Holdingpen = () => {
const loggedIn = !!storage.getSync('holdingpen.token');

return (
<>
<DocumentHead title={TITLE} description={META_DESCRIPTION} />
<div className="w-100" data-testid="holdingpen-new">
<SafeSwitch>
<PrivateRoute
exact
path={HOLDINGPEN_LOGIN_NEW}
component={LoginPageContainer}
/>
<Redirect exact from={HOLDINGPEN_NEW} to={HOLDINGPEN_DASHBOARD_NEW} />
<Route
<PrivateRoute
exact
path={HOLDINGPEN_DASHBOARD_NEW}
component={DashboardPage}
isHoldinpen
loggedInToHoldinpen={loggedIn}
/>
<Route
<PrivateRoute
exact
path={`${HOLDINGPEN_SEARCH_NEW}`}
component={SearchPageContainer}
isHoldinpen
loggedInToHoldinpen={loggedIn}
/>
<Route
<PrivateRoute
exact
path={`${HOLDINGPEN_NEW}/:id`}
component={DetailPageContainer}
isHoldinpen
loggedInToHoldinpen={loggedIn}
/>
<Route
<PrivateRoute
exact
path={`${HOLDINGPEN_NEW}/author/:id`}
component={AuthorDetailPageContainer}
isHoldinpen
loggedInToHoldinpen={loggedIn}
/>
</SafeSwitch>
</div>
Expand Down
5 changes: 0 additions & 5 deletions ui/src/holdingpen-new/token.ts

This file was deleted.

Loading

0 comments on commit 364afa4

Please sign in to comment.