fixes issues with hidden fields on frontend and on schema creation

This commit is contained in:
James
2020-05-20 18:20:01 -04:00
parent 8f879602eb
commit 7e42a2ae09
26 changed files with 266 additions and 86 deletions

View File

@@ -26,13 +26,13 @@ ButtonContents.propTypes = {
const Button = (props) => {
const {
className, type, el, to, url, children, onClick, disabled, icon,
className, type, el, to, url, children, onClick, disabled, icon, buttonStyle,
} = props;
const classes = [
baseClass,
className && className,
type && `${baseClass}--${type}`,
buttonStyle && `${baseClass}--${buttonStyle}`,
icon && `${baseClass}--icon`,
disabled && `${baseClass}--disabled`,
].filter(Boolean).join(' ');
@@ -43,6 +43,7 @@ const Button = (props) => {
}
const buttonProps = {
type,
className: classes,
onClick: handleClick,
};
@@ -75,7 +76,7 @@ const Button = (props) => {
default:
return (
<button
type="button"
type="submit"
{...buttonProps}
>
<ButtonContents icon={icon}>
@@ -88,7 +89,8 @@ const Button = (props) => {
Button.defaultProps = {
className: null,
type: 'primary',
type: 'submit',
buttonStyle: 'primary',
el: null,
to: null,
url: null,
@@ -100,7 +102,8 @@ Button.defaultProps = {
Button.propTypes = {
className: PropTypes.string,
type: PropTypes.oneOf(['primary', 'secondary', 'error', undefined]),
type: PropTypes.oneOf(['submit', 'button']),
buttonStyle: PropTypes.oneOf(['primary', 'secondary', 'error', undefined]),
el: PropTypes.oneOf(['link', 'anchor', undefined]),
to: PropTypes.string,
url: PropTypes.string,

View File

@@ -11,7 +11,7 @@
margin-bottom: base(1);
border: 0;
cursor: pointer;
font-weight: 600;
font-weight: normal;
text-decoration: none;
&--primary {

View File

@@ -1,16 +1,31 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useReducer } from 'react';
import PropTypes from 'prop-types';
import Pill from '../Pill';
import Plus from '../../icons/Plus';
import X from '../../icons/X';
import './index.scss';
const baseClass = 'column-selector';
const reducer = (state, { type, column }) => {
if (type === 'enable') {
return [
...state,
column,
];
}
return state.filter(remainingColumn => remainingColumn !== column);
};
const ColumnSelector = (props) => {
const {
handleChange,
fields,
} = props;
const [columns, setColumns] = useState('');
const [columns, dispatchColumns] = useReducer(reducer, []);
useEffect(() => {
if (typeof handleChange === 'function') handleChange(columns);
@@ -18,19 +33,33 @@ const ColumnSelector = (props) => {
return (
<div className={baseClass}>
Columns
{fields && fields.map((field) => {
if (field?.hidden !== true || field?.hidden?.admin !== true) {
const isEnabled = columns.find(column => column === field.name);
return (
<Pill
onClick={() => dispatchColumns({ column: field.name, type: isEnabled ? 'disable' : 'enable' })}
alignIcon="left"
key={field.name}
icon={isEnabled ? <X /> : <Plus />}
pillStyle={isEnabled ? 'dark' : undefined}
className={`${baseClass}__active-column`}
>
{field.label}
</Pill>
);
}
return null;
})}
</div>
);
};
ColumnSelector.defaultProps = {
fieldName: 'id',
fieldLabel: 'ID',
};
ColumnSelector.propTypes = {
fieldName: PropTypes.string,
fieldLabel: PropTypes.string,
fields: PropTypes.arrayOf(
PropTypes.shape({}),
).isRequired,
handleChange: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,11 @@
@import '../../../scss/styles.scss';
.column-selector {
background: $color-background-gray;
padding: base(1) base(1) 0;
.pill {
margin-right: base(1);
margin-bottom: base(1);
}
}

View File

@@ -38,7 +38,9 @@ const ListControls = (props) => {
}, [useAsTitle, fields]);
useEffect(() => {
const newState = {};
const newState = {
columns,
};
if (search) {
newState.where = {
@@ -61,7 +63,7 @@ const ListControls = (props) => {
/>
<Button
className={`${baseClass}__toggle-columns`}
type={visibleDrawer === 'columns' ? 'secondary' : undefined}
buttonStyle={visibleDrawer === 'columns' ? 'secondary' : undefined}
onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : false)}
icon={<Chevron />}
>
@@ -69,7 +71,7 @@ const ListControls = (props) => {
</Button>
<Button
className={`${baseClass}__toggle-where`}
type={visibleDrawer === 'where' ? 'secondary' : undefined}
buttonStyle={visibleDrawer === 'where' ? 'secondary' : undefined}
onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : false)}
icon={<Chevron />}
>
@@ -80,7 +82,10 @@ const ListControls = (props) => {
className={`${baseClass}__columns`}
height={visibleDrawer === 'columns' ? 'auto' : 0}
>
<ColumnSelector handleChange={setColumns} />
<ColumnSelector
fields={fields}
handleChange={setColumns}
/>
</AnimateHeight>
<AnimateHeight
className={`${baseClass}__where`}

View File

@@ -25,6 +25,11 @@
}
}
.column-selector,
.where-builder {
margin-top: base(1);
}
@include mid-break {
.btn {
margin: 0 0 0 base(.5);

View File

@@ -6,39 +6,64 @@ import './index.scss';
const baseClass = 'pill';
const Pill = ({ children, className, to }) => {
const Pill = ({
children, className, to, icon, alignIcon, onClick, pillStyle,
}) => {
const classes = [
baseClass,
`${baseClass}--style-${pillStyle}`,
className && className,
to && `${baseClass}--has-link`,
(to || onClick) && `${baseClass}--has-action`,
icon && `${baseClass}--has-icon`,
icon && `${baseClass}--align-icon-${alignIcon}`,
].filter(Boolean).join(' ');
if (to) {
return (
<Link
to={to}
className={classes}
>
{children}
</Link>
);
}
let RenderedType = 'div';
if (onClick && !to) RenderedType = 'button';
if (to) RenderedType = Link;
return (
<div className={classes}>
<RenderedType
className={classes}
onClick={onClick}
type={RenderedType === 'button' ? 'button' : undefined}
to={to || undefined}
>
{(icon && alignIcon === 'left') && (
<>
{icon}
</>
)}
{children}
</div>
{(icon && alignIcon === 'right') && (
<>
{icon}
</>
)}
</RenderedType>
);
};
Pill.defaultProps = {
children: undefined,
className: '',
to: undefined,
icon: undefined,
alignIcon: 'right',
onClick: undefined,
pillStyle: 'light',
};
Pill.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
to: PropTypes.string,
icon: PropTypes.node,
alignIcon: PropTypes.oneOf(['left', 'right']),
onClick: PropTypes.func,
pillStyle: PropTypes.oneOf(['light', 'dark']),
};
export default Pill;

View File

@@ -1,15 +1,42 @@
@import '../../../scss/styles.scss';
.pill {
font-size: 1rem;
line-height: base(1);
border: 0;
display: inline-flex;
vertical-align: middle;
background: $color-light-gray;
color: $color-dark-gray;
border-radius: $style-radius-s;
padding: 0 base(.25);
padding-left: base(.0875 + .25);
&--has-link {
text-decoration: none;
&:active,
&:focus {
outline: none;
}
&--has-action {
cursor: pointer;
text-decoration: none;
}
&--has-icon {
svg {
display: block;
}
}
&--align-icon-left {
padding-left: base(.125);
}
&--align-icon-right {
padding-right: base(.125);
}
&--style-light {
&:hover {
background: lighten($color-light-gray, 3%);
}
@@ -18,4 +45,21 @@
background: lighten($color-light-gray, 5%);
}
}
&--style-dark {
background: $color-dark-gray;
color: white;
svg {
@include color-svg(white);
}
&:hover {
background: lighten($color-dark-gray, 3%);
}
&:active {
background: lighten($color-dark-gray, 5%);
}
}
}

View File

@@ -11,34 +11,40 @@ const RenderFields = ({
<>
{fieldSchema.map((field, i) => {
const { defaultValue } = field;
let FieldComponent = field?.hidden?.admin ? fieldTypes.hidden : fieldTypes[field.type];
if (customComponents?.[field.name]?.field) {
FieldComponent = customComponents[field.name].field;
}
if (FieldComponent) {
if (field?.hidden !== 'api' && field?.hidden !== true) {
let FieldComponent = field?.hidden === 'admin' ? fieldTypes.hidden : fieldTypes[field.type];
if (customComponents?.[field.name]?.field) {
FieldComponent = customComponents[field.name].field;
}
if (FieldComponent) {
return (
<FieldComponent
fieldTypes={fieldTypes}
key={field.name}
{...field}
validate={field.validate ? value => field.validate(value, field) : undefined}
defaultValue={initialData[field.name] || defaultValue}
/>
);
}
return (
<FieldComponent
fieldTypes={fieldTypes}
key={field.name}
{...field}
validate={field.validate ? value => field.validate(value, field) : undefined}
defaultValue={initialData[field.name] || defaultValue}
/>
<div
className="missing-field"
key={i}
>
No matched field found for
{' '}
&quot;
{field.label}
&quot;
</div>
);
}
return (
<div
className="missing-field"
key={i}
>
No matched field found for
{' '}
&quot;
{field.label}
&quot;
</div>
);
return null;
})}
</>
);

View File

@@ -133,7 +133,7 @@ const Repeater = (props) => {
<div className={`${baseClass}__add-button-wrap`}>
<Button
onClick={() => addRow(rowCount)}
type="secondary"
buttonStyle="secondary"
>
{`Add ${singularLabel}`}
</Button>

View File

@@ -5,6 +5,8 @@ import './index.scss';
const Plus = () => {
return (
<svg
width="25"
height="25"
className="icon icon--plus"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 25 25"
@@ -14,12 +16,14 @@ const Plus = () => {
y1="16.9175"
x2="12.4589"
y2="8.50115"
className="stroke"
/>
<line
x1="8.05164"
y1="12.594"
x2="16.468"
y2="12.594"
className="stroke"
/>
</svg>
);

View File

@@ -1,7 +1,7 @@
@import '../../../scss/styles';
.icon--plus {
path {
.stroke {
stroke: $color-dark-gray;
stroke-width: $style-stroke-width-s;
}

View File

@@ -2,12 +2,12 @@ import React from 'react';
import './index.scss';
const Close = () => {
const X = () => {
return (
<svg
width="25"
height="25"
className="icon icon--close"
className="icon icon--x"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 25 25"
>
@@ -29,4 +29,4 @@ const Close = () => {
);
};
export default Close;
export default X;

View File

@@ -1,6 +1,6 @@
@import '../../../scss/styles';
.icon--close {
.icon--x {
line {
stroke: $color-dark-gray;
stroke-width: $style-stroke-width-s;

View File

@@ -33,7 +33,7 @@ const StayLoggedInModal = (props) => {
Stay logged in
</Button>
<Button
type="secondary"
buttonStyle="secondary"
onClick={() => {
closeAllModals();
history.push(`${admin}/logout`);

View File

@@ -52,7 +52,7 @@ const ForgotPassword = () => {
<br />
<Button
el="link"
type="secondary"
buttonStyle="secondary"
to={admin}
>
Back to Dashboard
@@ -71,7 +71,7 @@ const ForgotPassword = () => {
<br />
<Button
el="link"
type="secondary"
buttonStyle="secondary"
to={`${admin}/login`}
>
Go to login

View File

@@ -51,7 +51,7 @@ const Login = () => {
<br />
<Button
el="link"
type="secondary"
buttonStyle="secondary"
to={admin}
>
Back to Dashboard

View File

@@ -23,7 +23,7 @@ const Logout = () => {
<br />
<Button
el="anchor"
type="secondary"
buttonStyle="secondary"
url={`${admin}/login`}
>
Log back in

View File

@@ -44,7 +44,7 @@ const ResetPassword = () => {
<br />
<Button
el="link"
type="secondary"
buttonStyle="secondary"
to={admin}
>
Back to Dashboard

View File

@@ -8,6 +8,7 @@ import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import Paginator from '../../../elements/Paginator';
import ListControls from '../../../elements/ListControls';
import Pill from '../../../elements/Pill';
import Button from '../../../elements/Button';
import './index.scss';
@@ -21,6 +22,7 @@ const DefaultList = (props) => {
collection: {
slug,
labels: {
singular: singularLabel,
plural: pluralLabel,
},
},
@@ -51,7 +53,7 @@ const DefaultList = (props) => {
<header className={`${baseClass}__header`}>
<h1>{pluralLabel}</h1>
<Pill to={newDocumentURL}>
Add New
Create New
</Pill>
</header>
<ListControls
@@ -73,11 +75,25 @@ const DefaultList = (props) => {
)}
{(!data.docs || data.docs.length === 0) && (
<div className={`${baseClass}__no-results`}>
No
{' '}
{pluralLabel}
{' '}
found
<p>
No
{' '}
{pluralLabel}
{' '}
found. Either no
{' '}
{pluralLabel}
{' '}
exist yet or none match the filters you&apos;ve specified above.
</p>
<Button
el="link"
to={newDocumentURL}
>
Create new
{' '}
{singularLabel}
</Button>
</div>
)}
<Paginator
@@ -98,6 +114,7 @@ const DefaultList = (props) => {
DefaultList.propTypes = {
collection: PropTypes.shape({
labels: PropTypes.shape({
singular: PropTypes.string,
plural: PropTypes.string,
}),
slug: PropTypes.string,

View File

@@ -10,9 +10,10 @@
margin-bottom: $baseline;
display: flex;
align-items: flex-end;
flex-wrap: wrap;
h1 {
margin: 0;
margin: 0 base(.75) 0 0;
}
a {
@@ -20,9 +21,7 @@
}
.pill {
margin-left: base(.75);
position: relative;
top: - base(.25);
margin: base(.5) 0 base(.25);
}
}
@@ -35,7 +34,7 @@
&__header {
.pill {
top: - base(.0625);
margin-bottom: base(.0625);
}
}

View File

@@ -76,7 +76,7 @@ h5 {
}
p {
margin: 0 0 base(.5);
margin: 0 0 $baseline;
}
ul {

View File

@@ -36,7 +36,7 @@ $font-body : 'Suisse Intl';
$color-dark-gray : #333333;
$color-gray : #9A9A9A;
$color-light-gray : #E7E7E7;
$color-light-gray : #DADADA;
$color-background-gray : #F3F3F3;
$color-red : #ff6f76;
$color-yellow : #FDFFA4;

View File

@@ -2,11 +2,10 @@ const { Schema } = require('mongoose');
const formatBaseSchema = (field) => {
return {
hidden: field.hidden || false,
hide: field.hidden === 'api' || field.hidden === true,
localized: field.localized || false,
unique: field.unique || false,
required: (field.required && !field.localized) || false,
required: (field.required && !field.localized && !field.hidden) || false,
default: field.defaultValue || undefined,
};
};