Merge branch 'master' of github.com:trouble/payload
This commit is contained in:
@@ -15,6 +15,7 @@ import Edit from './views/collections/Edit';
|
||||
import EditGlobal from './views/globals/Edit';
|
||||
import { requests } from '../api';
|
||||
import customComponents from './custom-components';
|
||||
import RedirectToLogin from './utilities/RedirectToLogin';
|
||||
|
||||
const Routes = () => {
|
||||
const [initialized, setInitialized] = useState(null);
|
||||
@@ -151,7 +152,7 @@ const Routes = () => {
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
return <Redirect to={`${match.url}/login`} />;
|
||||
return <RedirectToLogin />;
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
|
||||
@@ -9,14 +9,26 @@ const cookies = new Cookies();
|
||||
const Context = createContext({});
|
||||
|
||||
const UserProvider = ({ children }) => {
|
||||
const cookieToken = cookies.get('token');
|
||||
const [token, setToken] = useState('');
|
||||
const [user, setUser] = useState(cookieToken ? jwtDecode(cookieToken) : null);
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const cookieToken = cookies.get('token');
|
||||
if (cookieToken) {
|
||||
const decoded = jwtDecode(cookieToken);
|
||||
if (decoded.exp > Date.now() / 1000) {
|
||||
setUser(decoded);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
setUser(jwtDecode(token));
|
||||
cookies.set('token', token, { path: '/' });
|
||||
const decoded = jwtDecode(token);
|
||||
if (decoded.exp > Date.now() / 1000) {
|
||||
setUser(decoded);
|
||||
cookies.set('token', token, { path: '/' });
|
||||
}
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
|
||||
@@ -123,8 +123,6 @@ const Form = (props) => {
|
||||
baseClass,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
// console.log(fields);
|
||||
|
||||
return (
|
||||
<form
|
||||
noValidate
|
||||
|
||||
@@ -55,7 +55,9 @@ function fieldReducer(state, action) {
|
||||
}
|
||||
|
||||
case 'ADD_ROW': {
|
||||
const { rowIndex, name, fields } = action;
|
||||
const {
|
||||
rowIndex, name, fields, blockType,
|
||||
} = action;
|
||||
const { rowsFromState, remainingState } = splitRowsFromState(state, name);
|
||||
|
||||
// Get names of sub fields
|
||||
@@ -70,6 +72,8 @@ function fieldReducer(state, action) {
|
||||
};
|
||||
}, {});
|
||||
|
||||
if (blockType) subFields.blockType = blockType;
|
||||
|
||||
// Add new object containing subfield names to rowsFromState array
|
||||
rowsFromState.splice(rowIndex + 1, 0, subFields);
|
||||
|
||||
@@ -92,7 +96,7 @@ function fieldReducer(state, action) {
|
||||
|
||||
return {
|
||||
...remainingState,
|
||||
...(flatten({ [name]: rowsFromState }, { maxDepth: 3 })),
|
||||
...(flatten({ [name]: rowsFromState }, { filters: flattenFilters })),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { asModal } from '@trbl/react-modal';
|
||||
|
||||
const baseClass = 'flexible-add-row-modal';
|
||||
|
||||
const AddRowModal = (props) => {
|
||||
const {
|
||||
addRow,
|
||||
blocks,
|
||||
rowIndexBeingAdded,
|
||||
closeAllModals,
|
||||
} = props;
|
||||
|
||||
const handleAddRow = (blockType) => {
|
||||
addRow(rowIndexBeingAdded, blockType);
|
||||
closeAllModals();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<ul>
|
||||
{blocks.map((block, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<button
|
||||
onClick={() => handleAddRow(block.slug)}
|
||||
type="button"
|
||||
>
|
||||
{block.labels.singular}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AddRowModal.defaultProps = {
|
||||
rowIndexBeingAdded: null,
|
||||
};
|
||||
|
||||
AddRowModal.propTypes = {
|
||||
addRow: PropTypes.func.isRequired,
|
||||
closeAllModals: PropTypes.func.isRequired,
|
||||
blocks: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
labels: PropTypes.shape({
|
||||
singular: PropTypes.string,
|
||||
}),
|
||||
previewImage: PropTypes.string,
|
||||
slug: PropTypes.string,
|
||||
}),
|
||||
).isRequired,
|
||||
rowIndexBeingAdded: PropTypes.number,
|
||||
};
|
||||
|
||||
export default asModal(AddRowModal);
|
||||
@@ -0,0 +1,125 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import RenderFields from '../../../RenderFields';
|
||||
import IconButton from '../../../../controls/IconButton';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'flexible-row';
|
||||
|
||||
const FlexibleRow = (props) => {
|
||||
const {
|
||||
addRow,
|
||||
removeRow,
|
||||
rowIndex,
|
||||
parentName,
|
||||
block,
|
||||
defaultValue,
|
||||
dispatchCollapsibleStates,
|
||||
collapsibleStates,
|
||||
} = props;
|
||||
|
||||
const handleCollapseClick = () => {
|
||||
dispatchCollapsibleStates({
|
||||
type: 'UPDATE_COLLAPSIBLE_STATUS',
|
||||
collapsibleIndex: rowIndex,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={`row-${rowIndex}`}
|
||||
index={rowIndex}
|
||||
>
|
||||
{(providedDrag) => {
|
||||
return (
|
||||
<div
|
||||
ref={providedDrag.innerRef}
|
||||
className={baseClass}
|
||||
{...providedDrag.draggableProps}
|
||||
>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<div
|
||||
{...providedDrag.dragHandleProps}
|
||||
className={`${baseClass}__header__drag-handle`}
|
||||
/>
|
||||
|
||||
<div className={`${baseClass}__header__row-index`}>
|
||||
{`${block.labels.singular} ${rowIndex + 1 > 9 ? rowIndex + 1 : `0${rowIndex + 1}`}`}
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__header__controls`}>
|
||||
|
||||
<IconButton
|
||||
iconName="crosshair"
|
||||
onClick={addRow}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
iconName="crossOut"
|
||||
onClick={removeRow}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={`${baseClass}__collapse__icon ${baseClass}__collapse__icon--${collapsibleStates[rowIndex] ? 'open' : 'closed'}`}
|
||||
iconName="arrow"
|
||||
onClick={handleCollapseClick}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimateHeight
|
||||
className={`${baseClass}__content`}
|
||||
height={collapsibleStates[rowIndex] ? 'auto' : 0}
|
||||
duration={0}
|
||||
>
|
||||
<RenderFields
|
||||
key={rowIndex}
|
||||
fields={block.fields.map((field) => {
|
||||
const fieldName = `${parentName}.${rowIndex}.${field.name}`;
|
||||
return ({
|
||||
...field,
|
||||
name: fieldName,
|
||||
defaultValue: defaultValue?.[field.name],
|
||||
});
|
||||
})}
|
||||
/>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
FlexibleRow.defaultProps = {
|
||||
defaultValue: null,
|
||||
collapsibleStates: [],
|
||||
};
|
||||
|
||||
FlexibleRow.propTypes = {
|
||||
block: PropTypes.shape({
|
||||
labels: PropTypes.shape({
|
||||
singular: PropTypes.string,
|
||||
}),
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
slug: PropTypes.string,
|
||||
}).isRequired,
|
||||
addRow: PropTypes.func.isRequired,
|
||||
removeRow: PropTypes.func.isRequired,
|
||||
rowIndex: PropTypes.number.isRequired,
|
||||
parentName: PropTypes.string.isRequired,
|
||||
fieldState: PropTypes.shape({}).isRequired,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]),
|
||||
dispatchCollapsibleStates: PropTypes.func.isRequired,
|
||||
collapsibleStates: PropTypes.arrayOf(PropTypes.bool),
|
||||
};
|
||||
|
||||
export default FlexibleRow;
|
||||
@@ -0,0 +1,90 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
|
||||
.flexible-row {
|
||||
background: $light-gray;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
margin-top: base(.5);
|
||||
|
||||
&:hover {
|
||||
@include shadow-sm;
|
||||
}
|
||||
|
||||
&__collapse__icon {
|
||||
&--open {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&--closed {
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: base(.75) base(1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&__drag-handle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// elements above the drag handle
|
||||
&__controls,
|
||||
&__header__row-index {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__row-index {
|
||||
font-family: $font-body;
|
||||
font-size: base(.5);
|
||||
}
|
||||
|
||||
&__heading {
|
||||
font-family: $font-body;
|
||||
margin: 0;
|
||||
font-size: base(.65);
|
||||
}
|
||||
|
||||
&__controls {
|
||||
margin-left: auto;
|
||||
|
||||
.btn {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.icon-button--crossOut,
|
||||
.icon-button--crosshair {
|
||||
margin-right: base(.25);
|
||||
}
|
||||
|
||||
.icon-button--crosshair {
|
||||
border-color: $primary;
|
||||
@include color-svg($primary);
|
||||
|
||||
&:hover {
|
||||
background: lighten($primary, 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
box-shadow: inset 0px 1px 0px white;
|
||||
|
||||
> div {
|
||||
padding: base(.75) base(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
165
src/client/components/forms/field-types/Flexible/index.js
Normal file
165
src/client/components/forms/field-types/Flexible/index.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import React, {
|
||||
useContext, useEffect, useReducer, useState, Fragment,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
import { ModalContext } from '@trbl/react-modal';
|
||||
|
||||
import FormContext from '../../Form/Context';
|
||||
import Section from '../../../layout/Section';
|
||||
import FlexibleRow from './FlexibleRow'; // eslint-disable-line import/no-cycle
|
||||
import AddRowModal from './AddRowModal';
|
||||
import collapsibleReducer from './reducer';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'field-type flexible';
|
||||
|
||||
const Flexible = (props) => {
|
||||
const {
|
||||
label,
|
||||
name,
|
||||
blocks,
|
||||
defaultValue,
|
||||
} = props;
|
||||
|
||||
const { toggle: toggleModal, closeAll: closeAllModals } = useContext(ModalContext);
|
||||
const [rowIndexBeingAdded, setRowIndexBeingAdded] = useState(null);
|
||||
const [rowCount, setRowCount] = useState(0);
|
||||
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
|
||||
const formContext = useContext(FormContext);
|
||||
const modalSlug = `flexible-${name}`;
|
||||
|
||||
const { fields: fieldState, dispatchFields } = formContext;
|
||||
|
||||
const addRow = (rowIndex, blockType) => {
|
||||
const blockToAdd = blocks.find(block => block.slug === blockType);
|
||||
|
||||
dispatchFields({
|
||||
type: 'ADD_ROW', rowIndex, name, fields: blockToAdd.fields, blockType,
|
||||
});
|
||||
|
||||
dispatchCollapsibleStates({
|
||||
type: 'ADD_COLLAPSIBLE', collapsibleIndex: rowIndex,
|
||||
});
|
||||
|
||||
setRowCount(rowCount + 1);
|
||||
};
|
||||
|
||||
const removeRow = (rowIndex) => {
|
||||
dispatchFields({
|
||||
type: 'REMOVE_ROW', rowIndex, name,
|
||||
});
|
||||
|
||||
dispatchCollapsibleStates({
|
||||
type: 'REMOVE_COLLAPSIBLE',
|
||||
collapsibleIndex: rowIndex,
|
||||
});
|
||||
|
||||
setRowCount(rowCount - 1);
|
||||
};
|
||||
|
||||
const moveRow = (moveFromIndex, moveToIndex) => {
|
||||
dispatchFields({
|
||||
type: 'MOVE_ROW', moveFromIndex, moveToIndex, name,
|
||||
});
|
||||
|
||||
dispatchCollapsibleStates({
|
||||
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setRowCount(defaultValue.length);
|
||||
|
||||
dispatchCollapsibleStates({
|
||||
type: 'SET_ALL_COLLAPSIBLES',
|
||||
payload: Array.from(Array(defaultValue.length).keys()).reduce(acc => ([...acc, true]), []), // sets all collapsibles to open on first load
|
||||
});
|
||||
}, [defaultValue]);
|
||||
|
||||
const openAddRowModal = (rowIndex) => {
|
||||
setRowIndexBeingAdded(rowIndex);
|
||||
toggleModal(modalSlug);
|
||||
};
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
if (!result.destination) return;
|
||||
const sourceIndex = result.source.index;
|
||||
const destinationIndex = result.destination.index;
|
||||
moveRow(sourceIndex, destinationIndex);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className={baseClass}>
|
||||
<Section
|
||||
heading={label}
|
||||
className="flexible"
|
||||
rowCount={rowCount}
|
||||
addRow={() => openAddRowModal(0)}
|
||||
useAddRowButton
|
||||
>
|
||||
<Droppable droppableId="flexible-drop">
|
||||
{provided => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{rowCount !== 0
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
const blockType = fieldState[`${name}.${rowIndex}.blockType`];
|
||||
const blockToRender = blocks.find(block => block.slug === blockType);
|
||||
|
||||
return (
|
||||
<FlexibleRow
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
addRow={() => openAddRowModal(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
block={blockToRender}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</Section>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
<AddRowModal
|
||||
closeAllModals={closeAllModals}
|
||||
addRow={addRow}
|
||||
rowIndexBeingAdded={rowIndexBeingAdded}
|
||||
slug={modalSlug}
|
||||
blocks={blocks}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
Flexible.defaultProps = {
|
||||
label: '',
|
||||
defaultValue: [],
|
||||
};
|
||||
|
||||
Flexible.propTypes = {
|
||||
defaultValue: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
),
|
||||
blocks: PropTypes.arrayOf(
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Flexible;
|
||||
@@ -0,0 +1,3 @@
|
||||
.field-type.flexible {
|
||||
background: white;
|
||||
}
|
||||
35
src/client/components/forms/field-types/Flexible/reducer.js
Normal file
35
src/client/components/forms/field-types/Flexible/reducer.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const collapsibleReducer = (currentState, action) => {
|
||||
const {
|
||||
type, collapsibleIndex, moveToIndex, payload,
|
||||
} = action;
|
||||
|
||||
const stateCopy = [...currentState];
|
||||
const movingCollapsibleState = stateCopy[collapsibleIndex];
|
||||
|
||||
switch (type) {
|
||||
case 'SET_ALL_COLLAPSIBLES':
|
||||
return payload;
|
||||
|
||||
case 'ADD_COLLAPSIBLE':
|
||||
stateCopy.splice(collapsibleIndex + 1, 0, true);
|
||||
return stateCopy;
|
||||
|
||||
case 'REMOVE_COLLAPSIBLE':
|
||||
stateCopy.splice(collapsibleIndex, 1);
|
||||
return stateCopy;
|
||||
|
||||
case 'UPDATE_COLLAPSIBLE_STATUS':
|
||||
stateCopy[collapsibleIndex] = !movingCollapsibleState;
|
||||
return stateCopy;
|
||||
|
||||
case 'MOVE_COLLAPSIBLE':
|
||||
stateCopy.splice(collapsibleIndex, 1);
|
||||
stateCopy.splice(moveToIndex, 0, movingCollapsibleState);
|
||||
return stateCopy;
|
||||
|
||||
default:
|
||||
return currentState;
|
||||
}
|
||||
};
|
||||
|
||||
export default collapsibleReducer;
|
||||
@@ -101,7 +101,6 @@ RepeaterRow.propTypes = {
|
||||
rowIndex: PropTypes.number.isRequired,
|
||||
parentName: PropTypes.string.isRequired,
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
fieldState: PropTypes.shape({}).isRequired,
|
||||
rowCount: PropTypes.number,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]),
|
||||
dispatchCollapsibleStates: PropTypes.func.isRequired,
|
||||
|
||||
@@ -94,23 +94,22 @@ const Repeater = (props) => {
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{rowCount !== 0
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<RepeaterRow
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
addRow={() => addRow(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
fields={fields}
|
||||
rowCount={rowCount}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<RepeaterRow
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
addRow={() => addRow(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fields={fields}
|
||||
rowCount={rowCount}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import text from './Text';
|
||||
import relationship from './Relationship';
|
||||
import password from './Password';
|
||||
import repeater from './Repeater';
|
||||
import flexible from './Flexible';
|
||||
import textarea from './Textarea';
|
||||
import select from './Select';
|
||||
import number from './Number';
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
text,
|
||||
relationship,
|
||||
// upload,
|
||||
flexible,
|
||||
number,
|
||||
password,
|
||||
repeater,
|
||||
|
||||
@@ -26,7 +26,7 @@ const useFieldType = (options) => {
|
||||
}, [name, required, dispatchFields, validate]);
|
||||
|
||||
useEffect(() => {
|
||||
sendField(defaultValue);
|
||||
if (defaultValue != null) sendField(defaultValue);
|
||||
}, [defaultValue, sendField]);
|
||||
|
||||
const valid = formContext.fields[name] ? formContext.fields[name].valid : true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ModalProvider, ModalContainer } from '@trbl/react-modal';
|
||||
import Loading from './views/Loading';
|
||||
import { SearchParamsProvider } from './utilities/SearchParams';
|
||||
import { LocaleProvider } from './utilities/Locale';
|
||||
@@ -14,15 +15,21 @@ const Index = () => {
|
||||
return (
|
||||
<UserProvider>
|
||||
<Router>
|
||||
<StatusListProvider>
|
||||
<SearchParamsProvider>
|
||||
<LocaleProvider>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Routes />
|
||||
</Suspense>
|
||||
</LocaleProvider>
|
||||
</SearchParamsProvider>
|
||||
</StatusListProvider>
|
||||
<ModalProvider
|
||||
classPrefix="payload"
|
||||
transTime={0}
|
||||
>
|
||||
<StatusListProvider>
|
||||
<SearchParamsProvider>
|
||||
<LocaleProvider>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Routes />
|
||||
</Suspense>
|
||||
</LocaleProvider>
|
||||
</SearchParamsProvider>
|
||||
</StatusListProvider>
|
||||
<ModalContainer />
|
||||
</ModalProvider>
|
||||
</Router>
|
||||
</UserProvider>
|
||||
);
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
///////////////////////////////////////////////////////
|
||||
// Takes a modal component and
|
||||
// a slug to match against a 'modal' URL param
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { withRouter } from 'react-router';
|
||||
import queryString from 'qs';
|
||||
import Close from '../../graphics/Close';
|
||||
import Button from '../../controls/Button';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const asModal = (PassedComponent, modalSlug) => {
|
||||
|
||||
class AsModal extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
el: null
|
||||
}
|
||||
}
|
||||
|
||||
bindEsc = event => {
|
||||
if (event.keyCode === 27) {
|
||||
const params = { ...this.props.searchParams };
|
||||
delete params.modal;
|
||||
|
||||
this.props.history.push({
|
||||
search: queryString.stringify(params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
isOpen = () => {
|
||||
|
||||
// Slug can come from either a HOC or from a prop
|
||||
const slug = this.props.modalSlug ? this.props.modalSlug : modalSlug;
|
||||
|
||||
if (this.props.searchParams.modal === slug) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.bindEsc, false);
|
||||
|
||||
if (this.isOpen()) {
|
||||
this.setState({ open: true })
|
||||
}
|
||||
|
||||
// Slug can come from either a HOC or from a prop
|
||||
const slug = this.props.modalSlug ? this.props.modalSlug : modalSlug;
|
||||
|
||||
this.setState({
|
||||
el: document.querySelector(`#${slug}`)
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.bindEsc, false);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
|
||||
let open = this.isOpen();
|
||||
|
||||
if (open !== prevState.open && open) {
|
||||
this.setState({ open: true })
|
||||
} else if (open !== prevState.open) {
|
||||
this.setState({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
// Slug can come from either a HOC or from a prop
|
||||
const slug = this.props.modalSlug ? this.props.modalSlug : modalSlug;
|
||||
const modalDomNode = document.getElementById('portal');
|
||||
|
||||
return createPortal(
|
||||
<div className={`modal${this.state.open ? ' open' : ''}`}>
|
||||
<Button el="link" type="icon" className="close" to={{ search: '' }}>
|
||||
<Close />
|
||||
</Button>
|
||||
<PassedComponent id={slug} {...this.props} isOpen={this.state.open} />
|
||||
</div>,
|
||||
modalDomNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return withRouter(connect(mapStateToProps)(AsModal));
|
||||
}
|
||||
|
||||
export default asModal;
|
||||
@@ -1,21 +0,0 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.modal {
|
||||
transform: translateZ(0);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
height: 100vh;
|
||||
background-color: rgba(white, .96);
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
23
src/client/components/utilities/RedirectToLogin/index.js
Normal file
23
src/client/components/utilities/RedirectToLogin/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Redirect,
|
||||
} from 'react-router-dom';
|
||||
import { useStatusList } from '../../modules/Status';
|
||||
import config from '../../../config/sanitizedClientConfig';
|
||||
|
||||
const RedirectToLogin = () => {
|
||||
const { addStatus } = useStatusList();
|
||||
|
||||
useEffect(() => {
|
||||
addStatus({
|
||||
message: 'You need to log in to be able to do that.',
|
||||
type: 'error',
|
||||
});
|
||||
}, [addStatus]);
|
||||
|
||||
return (
|
||||
<Redirect to={`${config.routes.admin}/login`} />
|
||||
);
|
||||
};
|
||||
|
||||
export default RedirectToLogin;
|
||||
Reference in New Issue
Block a user