Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Homepage redesign & blog functionality #682

Merged
merged 30 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7989671
feat: add list of blog posts to admin panel
wu-ciesielska Nov 8, 2023
74d98d1
feat: use redux for blog posts
wu-ciesielska Nov 10, 2023
d84b864
feat: create FileInput component using UploadFile component
wu-ciesielska Nov 17, 2023
03dc205
feat: add BlogPostForm component and AddBlogPost form
wu-ciesielska Nov 17, 2023
4c510f8
feat: add edit form for blog posts
wu-ciesielska Nov 21, 2023
37e5814
feat: add error handling to blog post forms
wu-ciesielska Nov 23, 2023
74c941e
feat: add delete modal to blog post list
wu-ciesielska Nov 23, 2023
d5287d3
feat: add post title to delete modal
wu-ciesielska Nov 24, 2023
7229df8
style: adjustments after self-review
wu-ciesielska Nov 24, 2023
1c579e6
Merge pull request #677 from dali-lab/blog-posts-admin-panel
wu-ciesielska Nov 24, 2023
e5d5a10
fix: add api url to image path in blog edit
wu-ciesielska Nov 24, 2023
4fcda56
Merge pull request #678 from dali-lab/blog-posts-admin-panel
wu-ciesielska Nov 24, 2023
ce73d51
feat: create blog page and display blog posts
wu-ciesielska Nov 27, 2023
51183b8
Merge pull request #679 from dali-lab/blog-posts-page
wu-ciesielska Nov 27, 2023
222d282
feat: hero section and navbar redesign
wu-ciesielska Nov 29, 2023
3123a63
refactor: refactor homepage to be more readable and maintainable
wu-ciesielska Nov 30, 2023
95cc1b5
feat: add button component
wu-ciesielska Dec 1, 2023
dc7ded0
feat: add blog post preview to homepage
wu-ciesielska Dec 1, 2023
4b274bb
feat: add predictions map preview to homepage
wu-ciesielska Dec 1, 2023
2876bd1
feat: add video component and draft of historical data to homepage
wu-ciesielska Dec 4, 2023
b06f9d7
refactor: move trapping data charts to separate files
wu-ciesielska Dec 4, 2023
420a039
feat: add spb chart to home page
wu-ciesielska Dec 4, 2023
fc75a37
refactor: move play with model files to screens/home directory
wu-ciesielska Dec 5, 2023
52bb39f
feat: add play with model to homepage
wu-ciesielska Dec 5, 2023
e19c886
feat: add single blog post page
wu-ciesielska Dec 12, 2023
a6be7e4
refactor: improve the homepage
wu-ciesielska Dec 12, 2023
a00b47b
refactor: self review
wu-ciesielska Dec 13, 2023
8f411c3
Merge pull request #680 from dali-lab/homepage-redesign
wu-ciesielska Dec 13, 2023
ff42e6c
fix: fix bug in redirection to how does it work section
wu-ciesielska Dec 19, 2023
564b9d5
Merge pull request #681 from dali-lab/homepage-redesign
wu-ciesielska Dec 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
],
"max-len": [
"error",
200
200,
{
"ignoreStrings": true
}
],
"no-extra-parens": 0,
"no-restricted-syntax": [
Expand Down Expand Up @@ -78,4 +81,4 @@
"sourceType": "module"
}
}
}
}
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
9 changes: 7 additions & 2 deletions src/app/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
Resources,
TrappingData,
Home,
PlayWithModel,
Prediction,
Blog,
SingleBlogPost,
} from '../screens';

import {
Expand Down Expand Up @@ -58,6 +59,7 @@ const App = (props) => {
getPredictions,
getAvailableStates,
getSparseData,
getAllBlogPosts,
} = props;

const [isMobile, setIsMobile] = useState(window.innerWidth < MIN_WIDTH_THRESHOLD);
Expand Down Expand Up @@ -86,12 +88,14 @@ const App = (props) => {
getAggregateYearData();
getAggregateStateData();
getAggregateLocationData();
getAllBlogPosts();
getSparseData();
getPredictions();
}, [
getAggregateLocationData,
getAggregateStateData,
getAggregateYearData,
getAllBlogPosts,
getPredictions,
getSparseData,
loginUserFromStorage,
Expand Down Expand Up @@ -120,9 +124,10 @@ const App = (props) => {
<Route exact path={ROUTES.HOME} component={Home} />
<Route path={ROUTES.ABOUT} component={About} />
<Route path={ROUTES.ADMIN} component={Admin} />
<Route path={`${ROUTES.BLOG}/:id`} component={SingleBlogPost} />
<Route path={ROUTES.BLOG} component={Blog} />
<Route path={ROUTES.RESOURCES} component={Resources} />
<Route path={ROUTES.TRAPPING_DATA} component={TrappingData} />
<Route path={ROUTES.PLAY_WITH_MODEL} component={PlayWithModel} />
<Route path={ROUTES.PREDICTIONS} component={Prediction} />
{Object.entries(RESOURCE_ROUTES).map(([TYPE, ROUTE]) => (
<Route
Expand Down
4 changes: 4 additions & 0 deletions src/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getAggregateStateData,
getAggregateLocationData,
getAvailableStates,
getAllBlogPosts,
getSparseData,
getPredictions,
getUserFromStorage,
Expand Down Expand Up @@ -44,6 +45,9 @@ const mapDispatchToProps = (dispatch) => {
getAvailableStates: (overrideFilter) => {
dispatch(getAvailableStates(overrideFilter));
},
getAllBlogPosts: () => {
dispatch(getAllBlogPosts());
},
getSparseData: (overrideFilter) => {
dispatch(getSparseData(overrideFilter));
},
Expand Down
13 changes: 13 additions & 0 deletions src/components/button/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import './style.scss';

const Button = (props) => {
const {
onClick, buttonStyle = 'primary', children, className = '',
} = props;

return <button type="button" onClick={onClick} className={`button animated-button button button-${buttonStyle} ${className}`}>{children}</button>;
};

export default Button;
3 changes: 3 additions & 0 deletions src/components/button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Button from './component';

export default Button;
18 changes: 18 additions & 0 deletions src/components/button/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.button {
padding: 10px;
border-radius: 6px;
font-size: 18px;
font-weight: 500;
text-decoration: none;

&-primary {
color: #ffffff;
background-color: #424755;
}

&-secondary {
background-color: white;
border: 1px solid #424755 !important;
color: #424755
}
}
33 changes: 23 additions & 10 deletions src/components/header/component.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';

import './style.scss';
import { Link, useLocation, useHistory } from 'react-router-dom';

import { ROUTES } from '../../constants';
import DownloadData from '../download-data';

import pineBeetleImage from '../../assets/icons/black-beetle-logo.png';

const Header = (_props) => {
import './style.scss';

const Header = () => {
const routes = {
[ROUTES.PREDICTIONS]: 'Predictions',
[ROUTES.TRAPPING_DATA]: 'Historical Data',
[ROUTES.PLAY_WITH_MODEL]: 'Play With Model',
[ROUTES.RESOURCES]: 'Resources',
[ROUTES.ABOUT]: 'About',
};

const urlPath = useLocation().pathname;
const location = useLocation();
const history = useHistory();
const urlPath = location.pathname;

const scrollToUrl = '?scrollTo=howItWorks';

const handleHowItWorksButtonClick = () => {
// handle situation when user wants to go back to how does it work section, after already clicking on the button
if (location.pathname === ROUTES.HOME && location.search === scrollToUrl) {
history.push(ROUTES.HOME);
setTimeout(() => {
history.push(`/${scrollToUrl}`);
}, 0);
} else {
history.push(`/${scrollToUrl}`);
}
};

return (
<div id="header">
Expand All @@ -34,14 +46,15 @@ const Header = (_props) => {
<div id="nav-button-area">
<div id="nav-buttons">
<div id="button-container">
<button type="button" onClick={handleHowItWorksButtonClick} className="nav-button inactive-nav">How does it work?</button>
{Object.entries(routes).map(([key, value]) => (
<Link to={key} key={key} className={`${value === 'About' ? 'nav-button-short' : 'nav-button'} ${(urlPath === key) ? 'active-nav' : 'inactive-nav'}`}>
{value}
</Link>
))}
</div>
<div id="download-button-area">
<DownloadData />
{urlPath === ROUTES.TRAPPING_DATA && <DownloadData />}
</div>
</div>
</div>
Expand Down
8 changes: 5 additions & 3 deletions src/components/header/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
justify-content: space-around;
align-items: center;
background-color: #FBFBFD;
max-width: 1200px;
margin: auto;

a {
text-decoration: none;
Expand All @@ -30,7 +32,6 @@
align-items: center;

img {
margin-left: 35px;
margin-top: 13px;
width: 26px;
height: 32px;
Expand All @@ -43,7 +44,7 @@

#nav-button-area {
margin: auto;
margin-right: 100px;
margin-right: 0;
}

#nav-buttons {
Expand Down Expand Up @@ -71,7 +72,6 @@

#download-button-area {
display: flex;
background-color: white;
padding: 5px;
justify-content: center;
align-content: center;
Expand All @@ -88,6 +88,8 @@
line-height: 24px;
letter-spacing: 0em;
text-align: center;
border: none;
background: none;

-o-transition: 0.3s;
-ms-transition: 0.3s;
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Button from './button';
import DownloadData from './download-data';
import Footer from './footer';
import Header from './header';
Expand All @@ -6,6 +7,7 @@ import MobileOverlay from './mobile-overlay';
import ScrollToTop from './scroll-to-top';

export {
Button,
DownloadData,
Footer,
Header,
Expand Down
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,
};
1 change: 1 addition & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const CHART_MODES = {
const ROUTES = {
ABOUT: '/about',
ADMIN: '/admin',
BLOG: '/blog',
HOME: '/',
RESOURCES: '/resources',
PLAY_WITH_MODEL: '/play-with-model',
Expand Down
Loading
Loading