feat: abstracts upload component
This commit is contained in:
173
src/admin/components/forms/field-types/Upload/Input.tsx
Normal file
173
src/admin/components/forms/field-types/Upload/Input.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useModal } from '@faceless-ui/modal';
|
||||
import Button from '../../../elements/Button';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FileDetails from '../../../elements/FileDetails';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
import { UploadField } from '../../../../../fields/config/types';
|
||||
import { Description } from '../../FieldDescription/types';
|
||||
import { FieldTypes } from '..';
|
||||
import AddModal from './Add';
|
||||
import SelectExistingModal from './SelectExisting';
|
||||
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'upload';
|
||||
|
||||
export type UploadInputProps = Omit<UploadField, 'type'> & {
|
||||
showError: boolean
|
||||
errorMessage?: string
|
||||
readOnly?: boolean
|
||||
path?: string
|
||||
required?: boolean
|
||||
value?: string
|
||||
description?: Description
|
||||
onChange?: (e) => void
|
||||
placeholder?: string
|
||||
style?: React.CSSProperties
|
||||
width?: string
|
||||
fieldTypes?: FieldTypes
|
||||
collection?: SanitizedCollectionConfig
|
||||
serverURL?: string
|
||||
api?: string
|
||||
}
|
||||
|
||||
const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
const {
|
||||
path,
|
||||
required,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
description,
|
||||
label,
|
||||
relationTo,
|
||||
fieldTypes,
|
||||
value,
|
||||
onChange,
|
||||
showError,
|
||||
serverURL = 'http://localhost:3000',
|
||||
api = '/api',
|
||||
collection,
|
||||
errorMessage,
|
||||
} = props;
|
||||
|
||||
const { toggle } = useModal();
|
||||
|
||||
const addModalSlug = `${path}-add`;
|
||||
const selectExistingModalSlug = `${path}-select-existing`;
|
||||
|
||||
const [file, setFile] = useState(undefined);
|
||||
const [missingFile, setMissingFile] = useState(false);
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
baseClass,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof value === 'string' && value !== '') {
|
||||
const fetchFile = async () => {
|
||||
const response = await fetch(`${serverURL}${api}/${relationTo}/${value}`);
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
setFile(json);
|
||||
} else {
|
||||
setMissingFile(true);
|
||||
setFile(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFile();
|
||||
} else {
|
||||
setFile(undefined);
|
||||
}
|
||||
}, [
|
||||
value,
|
||||
relationTo,
|
||||
api,
|
||||
serverURL,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={path}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
{collection?.upload && (
|
||||
<React.Fragment>
|
||||
{(file && !missingFile) && (
|
||||
<FileDetails
|
||||
collection={collection}
|
||||
doc={file}
|
||||
handleRemove={() => {
|
||||
onChange(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(!file || missingFile) && (
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
onClick={() => {
|
||||
toggle(addModalSlug);
|
||||
}}
|
||||
>
|
||||
Upload new
|
||||
{' '}
|
||||
{collection.labels.singular}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
onClick={() => {
|
||||
toggle(selectExistingModalSlug);
|
||||
}}
|
||||
>
|
||||
Choose from existing
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<AddModal
|
||||
{...{
|
||||
collection,
|
||||
slug: addModalSlug,
|
||||
fieldTypes,
|
||||
setValue: onChange,
|
||||
}}
|
||||
/>
|
||||
<SelectExistingModal
|
||||
{...{
|
||||
collection,
|
||||
slug: selectExistingModalSlug,
|
||||
setValue: onChange,
|
||||
addModalSlug,
|
||||
}}
|
||||
/>
|
||||
<FieldDescription
|
||||
value={file}
|
||||
description={description}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadInput;
|
||||
@@ -1,27 +1,21 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useModal } from '@faceless-ui/modal';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useConfig } from '@payloadcms/config-provider';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Button from '../../../elements/Button';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { upload } from '../../../../../fields/validations';
|
||||
import FileDetails from '../../../elements/FileDetails';
|
||||
import AddModal from './Add';
|
||||
import SelectExistingModal from './SelectExisting';
|
||||
import { Props } from './types';
|
||||
import UploadInput from './Input';
|
||||
|
||||
import './index.scss';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
|
||||
const baseClass = 'upload';
|
||||
|
||||
const Upload: React.FC<Props> = (props) => {
|
||||
const { toggle } = useModal();
|
||||
const [internalValue, setInternalValue] = useState(undefined);
|
||||
const [missingFile, setMissingFile] = useState(false);
|
||||
const { collections, serverURL, routes: { api } } = useConfig();
|
||||
const {
|
||||
collections,
|
||||
serverURL,
|
||||
routes: {
|
||||
api,
|
||||
},
|
||||
} = useConfig();
|
||||
|
||||
const {
|
||||
path: pathFromProps,
|
||||
@@ -38,15 +32,11 @@ const Upload: React.FC<Props> = (props) => {
|
||||
validate = upload,
|
||||
relationTo,
|
||||
fieldTypes,
|
||||
value: valueFromProps,
|
||||
onChange: onChangeFromProps,
|
||||
} = props;
|
||||
|
||||
const collection = collections.find((coll) => coll.slug === relationTo);
|
||||
|
||||
const path = pathFromProps || name;
|
||||
const addModalSlug = `${path}-add`;
|
||||
const selectExistingModalSlug = `${path}-select-existing`;
|
||||
|
||||
const memoizedValidate = useCallback((value) => {
|
||||
const validationResult = validate(value, { required });
|
||||
@@ -60,132 +50,42 @@ const Upload: React.FC<Props> = (props) => {
|
||||
});
|
||||
|
||||
const {
|
||||
value: valueFromContext,
|
||||
value,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = fieldType;
|
||||
|
||||
const value = valueFromProps || valueFromContext || '';
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
baseClass,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof value === 'string') {
|
||||
const fetchFile = async () => {
|
||||
const response = await fetch(`${serverURL}${api}/${relationTo}/${value}`);
|
||||
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
setInternalValue(json);
|
||||
} else {
|
||||
setInternalValue(undefined);
|
||||
setMissingFile(true);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFile();
|
||||
}
|
||||
const onChange = useCallback((incomingValue) => {
|
||||
const incomingID = incomingValue?.id || incomingValue;
|
||||
setValue(incomingID);
|
||||
}, [
|
||||
value,
|
||||
relationTo,
|
||||
api,
|
||||
serverURL,
|
||||
setValue
|
||||
setValue,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const { id: incomingID } = internalValue || {};
|
||||
if (typeof onChangeFromProps === 'function') {
|
||||
onChangeFromProps(incomingID)
|
||||
} else {
|
||||
setValue(incomingID);
|
||||
}
|
||||
}, [internalValue]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={path}
|
||||
if (collection.upload) {
|
||||
return (
|
||||
<UploadInput
|
||||
value={value as string}
|
||||
onChange={onChange}
|
||||
description={description}
|
||||
label={label}
|
||||
required={required}
|
||||
showError={showError}
|
||||
serverURL={serverURL}
|
||||
api={api}
|
||||
errorMessage={errorMessage}
|
||||
readOnly={readOnly}
|
||||
style={style}
|
||||
width={width}
|
||||
collection={collection}
|
||||
fieldTypes={fieldTypes}
|
||||
name={name}
|
||||
relationTo={relationTo}
|
||||
/>
|
||||
{collection?.upload && (
|
||||
<React.Fragment>
|
||||
{(internalValue && !missingFile) && (
|
||||
<FileDetails
|
||||
collection={collection}
|
||||
doc={internalValue}
|
||||
handleRemove={() => {
|
||||
setInternalValue(undefined);
|
||||
setValue(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(!value || missingFile) && (
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
onClick={() => {
|
||||
toggle(addModalSlug);
|
||||
}}
|
||||
>
|
||||
Upload new
|
||||
{' '}
|
||||
{collection.labels.singular}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
onClick={() => {
|
||||
toggle(selectExistingModalSlug);
|
||||
}}
|
||||
>
|
||||
Choose from existing
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<AddModal {...{
|
||||
collection,
|
||||
slug: addModalSlug,
|
||||
fieldTypes,
|
||||
setValue: (val) => {
|
||||
setMissingFile(false);
|
||||
setInternalValue(val);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<SelectExistingModal {...{
|
||||
collection,
|
||||
slug: selectExistingModalSlug,
|
||||
setValue: (val) => {
|
||||
setMissingFile(false);
|
||||
setInternalValue(val);
|
||||
},
|
||||
addModalSlug,
|
||||
}}
|
||||
/>
|
||||
<FieldDescription
|
||||
value={value}
|
||||
description={description}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
export default withCondition(Upload);
|
||||
|
||||
@@ -113,6 +113,7 @@ const useField = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
sendField(valueToSend);
|
||||
}
|
||||
}, [
|
||||
path,
|
||||
valueToSend,
|
||||
sendField,
|
||||
field,
|
||||
|
||||
Reference in New Issue
Block a user