Skip to content

Commit

Permalink
Merge pull request #677 from dali-lab/blog-posts-admin-panel
Browse files Browse the repository at this point in the history
Blog posts functionality for admin panel
  • Loading branch information
wu-ciesielska authored Nov 24, 2023
2 parents 68d5678 + 7229df8 commit 1c579e6
Show file tree
Hide file tree
Showing 27 changed files with 975 additions and 108 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ yarn-error.log
.env

.eslintcache
.vscode
121 changes: 121 additions & 0 deletions src/components/input-components/file-input/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useState } from 'react';

import './style.scss';

const FileInput = (props) => {
const {
guideURL, component, onResetFiles, fileFormat = '.csv',
} = props;

const [isUploadingFile, setIsUploadingFile] = useState(false);
const [uploadingFileError, setUploadingFileError] = useState('');
const [successMessage, setSuccessMessage] = useState({});

const clearSuccessMessage = () => setSuccessMessage({});

const clearError = () => {
setUploadingFileError('');
onResetFiles();
setIsUploadingFile(false);
setSuccessMessage({});
};

if (isUploadingFile) {
return (
<div className="uploading-message-container">
<h3>Uploading File...</h3>
</div>
);
}

if (uploadingFileError) {
return (
<div id="uploading-error-container" className="uploading-message-container">
{
guideURL
? <h3>{uploadingFileError} Please read <a href={guideURL} target="_blank" rel="noopener noreferrer">this guide</a> for uploading data.</h3>
: <h3>{uploadingFileError}</h3>
}
<button
type="button"
onClick={clearError}
>Try Again
</button>
</div>
);
}

/**
* @description uploads given file
* @param {Function} uploadFunction function to upload file
* @param {File} file file object
* @param {Function} clearFile function to clear the file
* @param {String} id file id
*/
const uploadFile = async (uploadFunction, file, clearFile, id) => {
setIsUploadingFile(true);

try {
await uploadFunction(file);
clearFile();
setSuccessMessage({ [id]: 'Successfully uploaded file' });
setTimeout(clearSuccessMessage, 1000 * 7);
} catch (err) {
const { data, status } = err?.response || {};

const strippedError = data?.error.toString().replace('Error: ', '');

const badRequest = status === 400;
const badColumnNames = strippedError.includes('missing fields in csv');
const wrongFileFormat = strippedError.includes('Invalid file type');

if (badColumnNames) setUploadingFileError('Incorrect column names. Please upload a different CSV.');
else if (wrongFileFormat) setUploadingFileError('Invalid file type. Only PNG, JPG, and JPEG files are allowed! Please, choose a different file.');
else if (badRequest) setUploadingFileError(`Bad request: ${strippedError}`);
else setUploadingFileError(strippedError || data?.error.toString() || 'We encountered an error. Please try again.');
} finally {
setIsUploadingFile(false);
}
};

return (
<div id={component.id} key={component.id}>
<p>{component.name}</p>
<p id="file-selected">
{component.file ? component.file.name : ''}
</p>
{component.file && component.uploadFile ? (
<button
id="upload-button"
className="custom-file-upload"
type="button"
onClick={() => uploadFile(
component.uploadFile,
component.file,
component.selectFile,
component.id,
)}
>
Upload File
</button>
) : (
<>
{successMessage[component.id] && (
<p id="success-message">{successMessage[component.id]}</p>
)}
<label htmlFor={`file-upload-${component.id}`} className="custom-file-upload">
<input
id={`file-upload-${component.id}`}
type="file"
accept={fileFormat}
onChange={(e) => component.selectFile(e.target.files[0]) && clearSuccessMessage()}
/>
Select File
</label>
</>
)}
</div>
);
};

export default FileInput;
2 changes: 2 additions & 0 deletions src/components/input-components/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import ChoiceInput from './choice-input';
import TextInput from './text-input';
import MultiSelectInput from './multi-select-input';
import FileInput from './file-input';

export {
ChoiceInput,
TextInput,
MultiSelectInput,
FileInput,
};
6 changes: 6 additions & 0 deletions src/screens/admin/component.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useState } from 'react';

import {
AddBlogPost,
AddUser,
BlogPosts,
ChangePassword,
FileUpload,
Login,
Expand Down Expand Up @@ -78,6 +80,10 @@ const Admin = (props) => {
<p>{modelError}</p>
</div>
)}
<div className="blog-container">
<AddBlogPost />
<BlogPosts />
</div>
</div>
<ChangePassword
close={() => setChangePasswordVisible(false)}
Expand Down
14 changes: 14 additions & 0 deletions src/screens/admin/components/add-blog-post/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import BlogPostForm from '../blog-post-form';
import './style.scss';

const AddBlogPost = (props) => {
const { createBlogPost } = props;

return (
<BlogPostForm onSubmit={createBlogPost} formTitle="Create blog post" formType="create" />
);
};

export default AddBlogPost;
18 changes: 18 additions & 0 deletions src/screens/admin/components/add-blog-post/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { connect } from 'react-redux';

import { createBlogPost } from '../../../../state/actions';
import AddBlogPost from './component';

const mapStateToProps = (state) => {
return {};
};

const mapDispatchToProps = (dispatch) => {
return {
createBlogPost: (fields, onSuccess) => {
dispatch(createBlogPost(fields, onSuccess));
},
};
};

export default connect(mapStateToProps, mapDispatchToProps)(AddBlogPost);
Empty file.
127 changes: 127 additions & 0 deletions src/screens/admin/components/blog-post-form/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React, { useState } from 'react';

import { FileInput } from '../../../../components/input-components';

import './style.scss';

const BlogPostForm = (props) => {
const {
onSubmit, formTitle, formValues, formType, error,
} = props;

const [formData, setFormData] = useState(formValues || {
title: '',
body: '',
image: null,
});

const handleInputChange = (ev) => {
return setFormData({
...formData,
[ev.target.name]: ev.target.value,
});
};

const handleFileChange = (file) => {
setFormData({
...formData,
image: file,
});
};

const handleSubmit = async (ev) => {
ev.preventDefault();

const data = new FormData();
data.append('title', formData.title);
data.append('body', formData.body);

if (formData.image) {
data.append('image', formData.image);
}
if (formType === 'create') {
await onSubmit(data, () => setFormData({
title: '',
body: '',
image: null,
}));
} else {
onSubmit(data);
}
};

const uploadImageComponent = {
file: formData.image,
id: `${formType}-blog-post-image`,
name: formType === 'edit'
? 'Change image for your blog post'
: 'Upload image for your blog post',
selectFile: handleFileChange,
};

const resetImage = () => setFormData({
...formData,
image: null,
});

const isButtonDisabled = !formData.title.length || !formData.body.length;

const shouldErrorDisplay = error?.action && error.action.toLowerCase().includes(formType);

return (
<div className="add-blog-post-container">
<div className="blog-posts-form-title">
{formTitle}
</div>
<form>
<label htmlFor="title" className="input-label">
Title
<div className="input-container">
<input
name="title"
type="text"
placeholder="Title"
onChange={handleInputChange}
value={formData.title}
required
/>
</div>
</label>
<div className="image-input-container">
<FileInput
component={uploadImageComponent}
onResetFiles={resetImage}
fileFormat="image/png, image/jpg, image/jpeg, image/pjpeg"
/>
{typeof formData.image === 'string' && (
<div className="image-preview-container">
<img src={formData.image} alt="blog post illustration" />
</div>
)}
</div>
<label htmlFor="body" className="input-label">
Enter blog post
<div className="input-container">
<textarea
name="body"
onChange={handleInputChange}
value={formData.body}
required
/>
</div>
</label>
<button
type="submit"
className={`blog-form-button ${isButtonDisabled ? '' : 'animated-button'}`}
onClick={handleSubmit}
disabled={isButtonDisabled}
>
Submit
</button>
<div className="blog-form-error">{shouldErrorDisplay && error.message}</div>
</form>
</div>
);
};

export default BlogPostForm;
19 changes: 19 additions & 0 deletions src/screens/admin/components/blog-post-form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { connect } from 'react-redux';

import BlogPostForm from './component';

const mapStateToProps = (state) => {
const {
blog: {
error,
},
} = state;

return { error };
};

const mapDispatchToProps = (dispatch) => {
return {};
};

export default connect(mapStateToProps, mapDispatchToProps)(BlogPostForm);
Loading

0 comments on commit 1c579e6

Please sign in to comment.