Skip to content

Commit

Permalink
v1.1.0 - Add Capture Date Input to 'Add Image' Modal (#6)
Browse files Browse the repository at this point in the history
* Add Capture Date Input to 'Add Image' Modal (#5)
* Add capture date picker to 'add image' modal
* Add captureDate to the request
* Add capture date formatter
* Version bump
  • Loading branch information
bjoberg authored Aug 7, 2020
1 parent a217aa0 commit db6bc2e
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 17 deletions.
54 changes: 49 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bo-portfolio",
"version": "1.0.1",
"version": "1.1.0",
"description": "Brett Oberg's portfolio website",
"private": true,
"main": "./build/server.js",
Expand Down Expand Up @@ -28,15 +28,18 @@
]
},
"dependencies": {
"@date-io/date-fns": "^1.3.13",
"@material-ui/core": "^4.8.0",
"@material-ui/icons": "^4.5.1",
"@material-ui/pickers": "^3.2.10",
"axios": "^0.19.0",
"body-parser": "^1.19.0",
"clsx": "^1.0.4",
"compression": "^1.7.4",
"cookie-session": "^1.3.3",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "2.1.1",
"date-fns": "^2.15.0",
"dotenv": "6.2.0",
"express": "^4.17.1",
"express-http-proxy": "^1.6.0",
Expand Down Expand Up @@ -123,4 +126,4 @@
"tslint": "^5.20.0",
"webpack-cli": "^3.3.7"
}
}
}
75 changes: 68 additions & 7 deletions src/app/components/add-image-dialog/add-image-dialog.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import * as dateFns from 'date-fns';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import DateFnsUtils from '@date-io/date-fns';
import {
MuiPickersUtilsProvider,
KeyboardDatePicker,
} from '@material-ui/pickers';
import {
Grid,
Dialog,
Expand All @@ -17,6 +23,7 @@ import {
defaultTitle,
defaultThumbnailUrl,
defaultImageUrl,
defaultCaptureDate,
defaultDescription,
defaultWidth,
defaultHeight,
Expand All @@ -37,6 +44,7 @@ const AddImageDialog = (props) => {
const [title, setTitle] = useState(defaultTitle);
const [thumbnailUrl, setThumbnailUrl] = useState(defaultThumbnailUrl);
const [imageUrl, setImageUrl] = useState(defaultImageUrl);
const [captureDate, setCaptureDate] = useState(defaultCaptureDate);
const [width, setWidth] = useState(defaultWidth);
const [height, setHeight] = useState(defaultHeight);
const [location, setLocation] = useState(defaultLocation);
Expand All @@ -63,6 +71,13 @@ const AddImageDialog = (props) => {
setImageUrl({ ...imageUrl, hasError, helperText });
};

const updateCaptureDate = (e) => {
setCaptureDate({ ...captureDate, value: e });
};
const updateCaptureDateError = (hasError, helperText) => {
setCaptureDate({ ...captureDate, hasError, helperText });
};

const updateLocation = (e) => {
setLocation({ ...location, value: e.target.value });
};
Expand Down Expand Up @@ -167,6 +182,29 @@ const AddImageDialog = (props) => {
return true;
};

/**
* Check to see if the image's capture date value is valid
*/
const isValidCaptureDate = () => {
const input = captureDate.value;

// Capture date is required, so it cannot be empty
if (!isNotEmpty(input)) {
updateCaptureDateError(true, 'Capture date is required');
return false;
}

// Capture date cannot be in the future
const isAfterToday = dateFns.isAfter(captureDate.value, Date.now());
if (isAfterToday) {
updateCaptureDateError(true, 'Capture date cannot be in the future');
return false;
}

updateCaptureDateError(false, '');
return true;
};

/**
* Check to see if the image's location value is valid
*/
Expand Down Expand Up @@ -261,6 +299,7 @@ const AddImageDialog = (props) => {
if (!isValidTitle()) isValid = false;
if (!isValidThumbnailUrl()) isValid = false;
if (!isValidImageUrl()) isValid = false;
if (!isValidCaptureDate()) isValid = false;
if (!isValidLocation()) isValid = false;
if (!isValidWidth()) isValid = false;
if (!isValidHeight()) isValid = false;
Expand All @@ -280,10 +319,12 @@ const AddImageDialog = (props) => {
description: description.value,
thumbnailUrl: thumbnailUrl.value,
imageUrl: imageUrl.value,
captureDate: captureDate.value,
location: location.value,
width: width.value,
height: height.value,
};

await imageService.createImage(image);
resetForm();
handleClose();
Expand Down Expand Up @@ -338,7 +379,7 @@ const AddImageDialog = (props) => {
id="title"
label="Title"
margin="normal"
variant="outlined"
variant="standard"
fullWidth
disabled={formIsLoading}
value={title.value}
Expand All @@ -351,7 +392,7 @@ const AddImageDialog = (props) => {
id="thumbnailUrl"
label="Thumbnail Url"
margin="normal"
variant="outlined"
variant="standard"
fullWidth
disabled={formIsLoading}
value={thumbnailUrl.value}
Expand All @@ -364,7 +405,7 @@ const AddImageDialog = (props) => {
id="imageUrl"
label="Image Url"
margin="normal"
variant="outlined"
variant="standard"
fullWidth
disabled={formIsLoading}
value={imageUrl.value}
Expand All @@ -373,11 +414,31 @@ const AddImageDialog = (props) => {
helperText={imageUrl.helperText}
onChange={e => updateImageUrl(e)}
/>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
id="capture-date"
label="Capture Date"
margin="normal"
variant="inline"
format="MM/dd/yyyy"
disableToolbar
fullWidth
disabled={formIsLoading}
value={captureDate.value}
required={captureDate.isRequired}
error={captureDate.hasError}
helperText={captureDate.helperText}
onChange={updateCaptureDate}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</MuiPickersUtilsProvider>
<TextField
id="location"
label="Location"
margin="normal"
variant="outlined"
variant="standard"
fullWidth
disabled={formIsLoading}
value={location.value}
Expand All @@ -390,7 +451,7 @@ const AddImageDialog = (props) => {
id="width"
label="Width"
margin="normal"
variant="outlined"
variant="standard"
disabled
fullWidth
value={width.value}
Expand All @@ -402,7 +463,7 @@ const AddImageDialog = (props) => {
id="height"
label="Height"
margin="normal"
variant="outlined"
variant="standard"
disabled
fullWidth
value={height.value}
Expand All @@ -415,7 +476,7 @@ const AddImageDialog = (props) => {
id="description"
label="Description"
margin="normal"
variant="outlined"
variant="standard"
fullWidth
disabled={formIsLoading}
rows="4"
Expand Down
8 changes: 8 additions & 0 deletions src/app/components/add-image-dialog/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ const defaultImageUrl = {
value: '',
};

const defaultCaptureDate = {
hasError: false,
isRequired: true,
helperText: '',
value: new Date(),
};

const defaultLocation = {
hasError: false,
isRequired: false,
Expand Down Expand Up @@ -51,6 +58,7 @@ export {
defaultTitle,
defaultThumbnailUrl,
defaultImageUrl,
defaultCaptureDate,
defaultLocation,
defaultWidth,
defaultHeight,
Expand Down
3 changes: 2 additions & 1 deletion src/services/image.service.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';

import HttpMethods from '../models/http-methods';
import { createNewApiError } from './service-helpers';
import { createNewApiError, formatCaptureDate } from './service-helpers';

export default class ImageService {
constructor() {
Expand Down Expand Up @@ -218,6 +218,7 @@ export default class ImageService {
imageUrl: image.imageUrl,
title: image.title,
description: image.description,
captureDate: formatCaptureDate(image.captureDate),
location: image.location,
width: image.width,
height: image.height,
Expand Down
23 changes: 21 additions & 2 deletions src/services/service-helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import ApiError from '../models/api-error.model';

/**
* Format the provided string as a capture date.
*
* @param {string} captureDate date to be formatted
* @returns {string|undefined} if provided string is a valid date, return year-month-day;
* otherwise undefined
*/
const formatCaptureDate = (captureDate) => {
try {
const d = new Date(captureDate);
const year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(d);
const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(d);
const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(d);

return `${year}-${month}-${day}`;
} catch (e) {
return undefined;
}
};

/**
* Create a new ApiError based on input
*
Expand All @@ -21,5 +41,4 @@ const createNewApiError = (error, defaultStatusCode, defaultMessage) => {
return new ApiError(status, message);
};

// eslint-disable-next-line import/prefer-default-export
export { createNewApiError };
export { createNewApiError, formatCaptureDate };

0 comments on commit db6bc2e

Please sign in to comment.