Skip to content

Commit

Permalink
ui: set up and use redux in new holdingpen
Browse files Browse the repository at this point in the history
  • Loading branch information
karolina-siemieniuk-morawska committed Jul 19, 2024
1 parent 8a8a92c commit ba0c290
Show file tree
Hide file tree
Showing 15 changed files with 703 additions and 364 deletions.
17 changes: 15 additions & 2 deletions ui/src/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const USER_SET_ORCID_PUSH_SETTING_ERROR =
export const SETTINGS_CHANGE_EMAIL_REQUEST = 'SETTINGS_CHANGE_EMAIL_REQUEST';
export const SETTINGS_CHANGE_EMAIL_ERROR = 'SETTINGS_CHANGE_EMAIL_ERROR';
export const SETTINGS_CHANGE_EMAIL_SUCCESS = 'SETTINGS_CHANGE_EMAIL_SUCCESS';

export const SUBMIT_REQUEST = 'SUBMIT_REQUEST';
export const SUBMIT_ERROR = 'SUBMIT_ERROR';
export const SUBMIT_SUCCESS = 'SUBMIT_SUCCESS';
Expand Down Expand Up @@ -115,7 +115,8 @@ export const JOURNAL_ERROR = 'JOURNAL_ERROR';
export const UI_CLOSE_BANNER = 'UI_CLOSE_BANNER';
export const UI_CHANGE_GUIDE_MODAL_VISIBILITY =
'UI_CHANGE_GUIDE_MODAL_VISIBILITY';
export const UI_SCROLL_VIEWPORT_TO_PREVIOUS_REFERENCE = 'UI_SCROLL_VIEWPORT_TO_PREVIOUS_REFERENCE';
export const UI_SCROLL_VIEWPORT_TO_PREVIOUS_REFERENCE =
'UI_SCROLL_VIEWPORT_TO_PREVIOUS_REFERENCE';

export const CLEAR_STATE = 'CLEAR_STATE';

Expand All @@ -131,3 +132,15 @@ export const LITERATURE_SET_ASSIGN_LITERATURE_ITEM_DRAWER_VISIBILITY =
'LITERATURE_SET_ASSIGN_LITERATURE_ITEM_DRAWER_VISIBILITY';
export const LITERATURE_SET_CURATE_DRAWER_VISIBILITY =
'LITERATURE_SET_CURATE_DRAWER_VISIBILITY';

export const HOLDINGPEN_LOGIN_ERROR = 'HOLDINGPEN_LOGIN_ERROR';
export const HOLDINGPEN_LOGIN_SUCCESS = 'HOLDINGPEN_LOGIN_SUCCESS';
export const HOLDINGPEN_LOGOUT_SUCCESS = 'HOLDINGPEN_LOGOUT_SUCCESS';
export const HOLDINGPEN_SEARCH_REQUEST = 'HOLDINGPEN_SEARCH_REQUEST';
export const HOLDINGPEN_SEARCH_ERROR = 'HOLDINGPEN_SEARCH_ERROR';
export const HOLDINGPEN_SEARCH_SUCCESS = 'HOLDINGPEN_SEARCH_SUCCESS';
export const HOLDINGPEN_SEARCH_QUERY_UPDATE = 'HOLDINGPEN_SEARCH_QUERY_UPDATE';
export const HOLDINGPEN_SEARCH_QUERY_RESET = 'HOLDINGPEN_SEARCH_QUERY_RESET';
export const HOLDINGPEN_AUTHOR_REQUEST = 'HOLDINGPEN_AUTHOR_REQUEST';
export const HOLDINGPEN_AUTHOR_ERROR = 'HOLDINGPEN_AUTHOR_ERROR';
export const HOLDINGPEN_AUTHOR_SUCCESS = 'HOLDINGPEN_AUTHOR_SUCCESS';
227 changes: 227 additions & 0 deletions ui/src/actions/holdingpen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/* eslint-disable no-underscore-dangle */
import { push } from 'connected-react-router';
import { Action, ActionCreator } from 'redux';
import axios from 'axios';

import { httpErrorToActionPayload } from '../common/utils';
import {
HOLDINGPEN_LOGIN_ERROR,
HOLDINGPEN_LOGIN_SUCCESS,
HOLDINGPEN_LOGOUT_SUCCESS,
HOLDINGPEN_SEARCH_REQUEST,
HOLDINGPEN_SEARCH_ERROR,
HOLDINGPEN_SEARCH_SUCCESS,
HOLDINGPEN_SEARCH_QUERY_UPDATE,
HOLDINGPEN_AUTHOR_ERROR,
HOLDINGPEN_AUTHOR_REQUEST,
HOLDINGPEN_AUTHOR_SUCCESS,
} from './actionTypes';
import {
BACKOFFICE_API,
BACKOFFICE_LOGIN,
BACKOFFICE_SEARCH_API,
HOLDINGPEN_DASHBOARD_NEW,
HOLDINGPEN_LOGIN_NEW,
} from '../common/routes';
import { Credentials } from '../types';
import storage from '../common/storage';
import { notifyLoginError } from '../holdingpen-new/notifications';
import { refreshToken } from '../holdingpen-new/utils/utils';

const httpClient = axios.create();

// Request interceptor for API calls
httpClient.interceptors.request.use(
async (config) => {
const token = storage.getSync('holdingpen.token');

config.headers = {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
};

return config;
},
(error) => Promise.reject(error)
);

// Response interceptor for API calls
httpClient.interceptors.response.use(
(response) => response,
async (error) => {
const {
response: { status },
config,
} = error;

if (status === 403 && !config._retry) {
config._retry = true;
const accessToken = await refreshToken();
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
return httpClient(config);
}

return Promise.reject(error);
}
);

export function holdingpenLoginSuccess() {
return {
type: HOLDINGPEN_LOGIN_SUCCESS,
};
}

function holdingpenLoginError(error: { error: Error }) {
return {
type: HOLDINGPEN_LOGIN_ERROR,
payload: error,
};
}

function holdingpenLogoutSuccess() {
return {
type: HOLDINGPEN_LOGOUT_SUCCESS,
};
}

function searching() {
return {
type: HOLDINGPEN_SEARCH_REQUEST,
};
}

function searchSuccess(data: any) {
return {
type: HOLDINGPEN_SEARCH_SUCCESS,
payload: { data },
};
}

function searchError(errorPayload: { error: Error }) {
return {
type: HOLDINGPEN_SEARCH_ERROR,
payload: { ...errorPayload },
};
}

function fetchingAuthor() {
return {
type: HOLDINGPEN_AUTHOR_REQUEST,
};
}

function fetchAuthorSuccess(data: any) {
return {
type: HOLDINGPEN_AUTHOR_SUCCESS,
payload: { data },
};
}

function fetchAuthorError(errorPayload: { error: Error }) {
return {
type: HOLDINGPEN_AUTHOR_ERROR,
payload: { ...errorPayload },
};
}

function updateQuery(data: any) {
return {
type: HOLDINGPEN_SEARCH_QUERY_UPDATE,
payload: data,
};
}

export function holdingpenLogin(
credentials: Credentials
): (dispatch: ActionCreator<Action>) => Promise<void> {
return async (dispatch) => {
try {
const response = await httpClient.post(
BACKOFFICE_LOGIN,
{
email: credentials.email,
password: credentials.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);

dispatch(holdingpenLoginSuccess());
dispatch(push(HOLDINGPEN_DASHBOARD_NEW));
}
} catch (err) {
const { error } = httpErrorToActionPayload(err);
notifyLoginError(error?.detail);
dispatch(holdingpenLoginError({ error }));
}
};
}

export function holdingpenLogout(): (
dispatch: ActionCreator<Action>
) => Promise<void> {
return async (dispatch) => {
try {
storage.remove('holdingpen.token');
storage.remove('holdingpen.refreshToken');

dispatch(push(HOLDINGPEN_LOGIN_NEW));
dispatch(holdingpenLogoutSuccess());
} catch (error) {
dispatch(holdingpenLogoutSuccess());
}
};
}

export function fetchSearchResults(query: {
page: number;
size: number;
}): (dispatch: ActionCreator<Action>) => Promise<void> {
return async (dispatch) => {
dispatch(searching());

const resolveQuery = `${BACKOFFICE_SEARCH_API}?page=${query.page}&size=${query.size}`;

try {
const response = await httpClient.get(`${resolveQuery}`);
dispatch(searchSuccess(response?.data));
} catch (err) {
const error = httpErrorToActionPayload(err);
dispatch(searchError(error));
}
};
}

export function fetchAuthor(
id: string
): (dispatch: ActionCreator<Action>) => Promise<void> {
return async (dispatch) => {
dispatch(fetchingAuthor());
const resolveQuery = `${BACKOFFICE_API}/${id}`;

try {
const response = await httpClient.get(`${resolveQuery}`);
dispatch(fetchAuthorSuccess(response?.data));
} catch (err) {
const error = httpErrorToActionPayload(err);
dispatch(fetchAuthorError(error));
}
};
}

export function searchQueryUpdate(query: {
page: number;
size: number;
}): (dispatch: ActionCreator<Action>) => Promise<void> {
return async (dispatch) => {
dispatch(updateQuery(query));
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Pagination } from 'antd';

import './SearchPagination.less';

const PAGE_SIZE_OPTIONS = ['25', '50', '100', '250'];
const PAGE_SIZE_OPTIONS = ['10', '25', '50', '100', '250'];

const SearchPagination = ({
page,
Expand All @@ -17,7 +17,7 @@ const SearchPagination = ({
total?: number;
pageSize?: number;
onPageChange?: (page: number, pageSize: number) => void;
onSizeChange?: (current: number, size: number) => void;
onSizeChange: (current: number, size: number) => void;
hideSizeChange?: boolean;
}) => {
return (
Expand Down
2 changes: 2 additions & 0 deletions ui/src/fixtures/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { initialState as inspect } from '../reducers/inspect';
import { initialState as exceptions } from '../reducers/exceptions';
import { initialState as ui } from '../reducers/ui';
import { initialState as bibliographyGenerator } from '../reducers/bibliographyGenerator';
import { initialState as holdingpen } from '../reducers/holdingpen';

import { thunkMiddleware } from '../store';
import { initialState as initialRecordState } from '../reducers/recordsFactory';
Expand Down Expand Up @@ -41,6 +42,7 @@ export function getState() {
institutions: initialRecordState,
experiments: initialRecordState,
journals: initialRecordState,
holdingpen,
};
}

Expand Down
40 changes: 22 additions & 18 deletions ui/src/holdingpen-new/components/AuthorResultItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,66 +57,70 @@ const renderWorkflowStatus = (status: string) => {
) : null;
};

const AuthorResultItem = ({ item }: { item: any }) => {
const {
_workflow: workflow,
metadata,
_extra_data: extraData,
} = item?.data || {};
const AuthorResultItem = ({ id, item }: { id: string; item: any }) => {
const workflow = item?.get('_workflow');
const metadata = item?.get('metadata');
const extraData = item?.get('_extra_data');

return (
<div key={item?.id} className="result-item result-item-action mv2">
<div className="result-item result-item-action mv2">
<Row justify="start" wrap={false}>
<Col className="col-pub-select">
<PublicationSelectContainer
claimed={false}
disabled={false}
isOwnProfile={false}
recordId={item?.id}
recordId={Number(id)}
/>
</Col>
<Col className="col-details">
<ResultItem>
<Link
className="result-item-title"
to={`${HOLDINGPEN_NEW}/author/${item?.id}`}
to={`${HOLDINGPEN_NEW}/author/${id}`}
target="_blank"
>
<div className="flex">
<div style={{ marginTop: '-2px' }}>
<UnclickableTag>Author</UnclickableTag>
{extraData?.user_action && (
{extraData?.get('user_action') && (
<UnclickableTag
className={`decission-pill ${resolveDecision(
extraData?.user_action
extraData?.get('user_action')
)?.bg}`}
>
{resolveDecision(extraData?.user_action)?.text}
{resolveDecision(extraData?.get('user_action'))?.text}
</UnclickableTag>
)}
</div>
<span className="dib ml2">{metadata?.name?.value}</span>
<span className="dib ml2">
{metadata?.getIn(['name', 'value'])}
</span>
</div>
</Link>
</ResultItem>
</Col>
<Col className="col-actions">
<Card>{renderWorkflowStatus(workflow?.status)}</Card>
<Card>{renderWorkflowStatus(workflow?.get('status'))}</Card>
</Col>
<Col className="col-info">
<Card>
<p className="waiting">
{new Date(
metadata?.acquisition_source?.datetime
metadata?.getIn(['acquisition_source', 'datetime'])
).toLocaleDateString()}
</p>
<p className="waiting">{metadata?.acquisition_source?.source}</p>
<p className="waiting mb0">{metadata?.acquisition_source?.email}</p>
<p className="waiting">
{metadata?.getIn(['acquisition_source', 'source'])}
</p>
<p className="waiting mb0">
{metadata?.getIn(['acquisition_source', 'email'])}
</p>
</Card>
</Col>
<Col className="col-subject">
<Card>
{metadata?.arxiv_categories?.map((category: string) => (
{metadata?.get('arxiv_categories')?.map((category: string) => (
<div className="mb2" key={category}>
<UnclickableTag color="blue">{category}</UnclickableTag>
</div>
Expand Down
Loading

0 comments on commit ba0c290

Please sign in to comment.