diff --git a/packages/admin-frontend/components/EditBook/EditBookForm.js b/packages/admin-frontend/components/EditBook/EditBookForm.js
index 23e68948a..414b259f3 100644
--- a/packages/admin-frontend/components/EditBook/EditBookForm.js
+++ b/packages/admin-frontend/components/EditBook/EditBookForm.js
@@ -6,7 +6,8 @@ import {
Button,
TextField,
Typography,
- Select
+ Select,
+ Snackbar
} from '@material-ui/core';
import * as React from 'react';
import Link from 'next/link';
@@ -20,14 +21,28 @@ import Container from '../Container';
import Row from '../Row';
import BookCover from './BookCover';
+import { saveFeaturedContent } from '../../lib/fetch';
+import type { FeaturedContent } from '../../types';
+import getConfig from 'next/config';
+
+const {
+ publicRuntimeConfig: { baseUrl }
+} = getConfig();
+
const PUBLISHING_STATUS = ['PUBLISHED', 'FLAGGED', 'UNLISTED'];
const PAGE_ORIENTATIONS = ['PORTRAIT', 'LANDSCAPE'];
type Props = {
book: BookDetails
};
+type State = {
+ snackbarMessage: ?string
+};
-export default class EditBookForm extends React.Component {
+export default class EditBookForm extends React.Component {
+ state = {
+ snackbarMessage: null
+ };
handleSubmit = (content: BookDetails) => {
this.updateBook(content);
};
@@ -43,8 +58,33 @@ export default class EditBookForm extends React.Component {
}
};
+ postNewFeaturedContent = async (content: FeaturedContent) => {
+ const result = await saveFeaturedContent(
+ content,
+ this.props.book.language.code
+ );
+ if (result.isOk) {
+ this.setState({
+ snackbarMessage: `${this.props.book.title} is added to featured content`
+ });
+ }
+ };
+
render() {
const book = this.props.book;
+ const { snackbarMessage } = this.state;
+ const content = {
+ id: 0,
+ title: book.title,
+ description: book.description,
+ language: book.language,
+ //default image (Grace in Space) if book does not have cover image
+ imageUrl:
+ book.coverImage !== undefined
+ ? book.coverImage.url
+ : 'https://res.cloudinary.com/dwqxoowxi/f_auto,q_auto/e7ad2d851664f1485743e157c46f7142',
+ link: `${baseUrl}/${book.language.code}/books/details/${book.id}`
+ };
return (
{' '}
@@ -150,7 +190,6 @@ export default class EditBookForm extends React.Component {
>
Discard changes
-
+
)}
/>
+ this.setState({ snackbarMessage: null })}
+ ContentProps={{
+ 'aria-describedby': 'feature-content-snack-msg'
+ }}
+ message={
+
+ {snackbarMessage}
+
+ }
+ />
);
}
diff --git a/packages/admin-frontend/config.js b/packages/admin-frontend/config.js
index 4f5f8de93..71c68cf1b 100644
--- a/packages/admin-frontend/config.js
+++ b/packages/admin-frontend/config.js
@@ -46,6 +46,19 @@ const imageApiUrl = () => {
}
};
+const baseUrl = () => {
+ switch (GDL_ENVIRONMENT) {
+ case 'local':
+ return 'http://localhost:3000';
+ case 'dev':
+ return 'https://test.digitallibrary.io';
+ case 'prod':
+ return 'https://digitallibrary.io';
+ default:
+ return `https://${GDL_ENVIRONMENT}.digitallibrary.io`;
+ }
+};
+
module.exports = {
serverRuntimeConfig: {
port: process.env.ADMIN_FRONTEND_PORT || 3010
@@ -53,6 +66,7 @@ module.exports = {
publicRuntimeConfig: {
imageApiUrl: imageApiUrl(),
bookApiUrl: bookApiUrl(),
- statisticsUrl: statisticsUrl()
+ statisticsUrl: statisticsUrl(),
+ baseUrl: baseUrl()
}
};
diff --git a/packages/admin-frontend/pages/_app.js b/packages/admin-frontend/pages/_app.js
index 0ff7cee22..8a8d307bf 100644
--- a/packages/admin-frontend/pages/_app.js
+++ b/packages/admin-frontend/pages/_app.js
@@ -51,7 +51,6 @@ class App extends NextApp {
}
const userHasAuthToken = hasAuthToken(ctx.req);
-
const userHasAdminPrivileges = hasClaim(claims.readAdmin, ctx.req);
// If we have response object, set a proper HTTP status code
diff --git a/packages/admin-frontend/pages/admin/featured.js b/packages/admin-frontend/pages/admin/featured.js
index ddb604e37..5d32f8374 100644
--- a/packages/admin-frontend/pages/admin/featured.js
+++ b/packages/admin-frontend/pages/admin/featured.js
@@ -10,13 +10,18 @@ import * as React from 'react';
import {
Select,
Button,
- FormHelperText,
InputLabel,
FormControl,
- TextField,
- Typography
+ Typography,
+ Card,
+ CardContent,
+ CardMedia,
+ CardActions,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle
} from '@material-ui/core';
-import { Form, Field, FormSpy } from 'react-final-form';
import {
fetchLanguages,
fetchFeaturedContent,
@@ -24,22 +29,25 @@ import {
saveFeaturedContent,
deleteFeaturedContent
} from '../../lib/fetch';
-import UploadFileDialog from '../../components/UploadFileDialog';
-import FeaturedImage from '../../components/FeaturedImage';
import Layout from '../../components/Layout';
-import Row from '../../components/Row';
import Container from '../../components/Container';
-import isEmptyString from '../../lib/isEmptyString';
import type { FeaturedContent, Language } from '../../types';
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core';
+import FeaturedEdit from './featuredEdit';
+import FeatureAdd from './featuredAdd';
type Props = {
languages: Array
};
type State = {
- featuredContent: ?FeaturedContent,
+ featuredContentList: Array,
selectedLanguage: string,
- file: ?File
+ file: ?File,
+ openDeleteDialog: boolean,
+ placementOfSelectedContent: number,
+ selectedContent: null | FeaturedContent
};
export default class EditFeaturedContent extends React.Component {
@@ -52,26 +60,26 @@ export default class EditFeaturedContent extends React.Component {
}
state = {
- featuredContent: null,
selectedLanguage: '',
croppedParameters: null,
- file: null
+ file: null,
+ featuredContentList: [],
+ openDeleteDialog: false,
+ selectedContent: null,
+ placementOfSelectedContent: 0
};
getFeaturedContent = async (languageCode: string) => {
const featuredContentRes = await fetchFeaturedContent(languageCode);
- const featuredContent = featuredContentRes.isOk
- ? featuredContentRes.data[0]
- : null;
- if (featuredContent) {
- if (featuredContent.language.code !== languageCode) {
+ if (featuredContentRes.isOk && featuredContentRes.data[0]) {
+ if (featuredContentRes.data[0].language.code !== languageCode) {
this.setState({
- featuredContent: null
+ featuredContentList: []
});
} else {
this.setState({
- featuredContent: featuredContent
+ featuredContentList: featuredContentRes.data
});
}
}
@@ -87,20 +95,29 @@ export default class EditFeaturedContent extends React.Component {
this.state.selectedLanguage
);
if (result.isOk) {
- this.setState(prevState => ({
- featuredContent: { ...prevState.featuredContent, id: result.data.id }
- }));
+ this.setState(() => {
+ const featuredContentList = this.state.featuredContentList.map(item => {
+ if (item.id === content.id) {
+ return content;
+ } else {
+ return item;
+ }
+ });
+ return {
+ featuredContentList
+ };
+ });
}
};
-
- handleSaveButtonClick = (defaultReturned: boolean) => (
- content: FeaturedContent
- ) => {
- defaultReturned
- ? this.postFeaturedContent(content)
- : this.putFeaturedContent(content);
-
- this.setState({ featuredContent: content });
+ postNewFeaturedContent = async (content: FeaturedContent) => {
+ console.log(content);
+ const result = await saveFeaturedContent(
+ content,
+ this.state.selectedLanguage
+ );
+ if (result.isOk) {
+ this.getFeaturedContent(this.state.selectedLanguage);
+ }
};
handleLanguageSelect = (event: SyntheticInputEvent) => {
@@ -114,12 +131,32 @@ export default class EditFeaturedContent extends React.Component {
deleteFeaturedContent = async (id: number) => {
await deleteFeaturedContent(id);
};
+ handleCloseDeleteDialog = () => {
+ this.setState({
+ openDeleteDialog: false,
+ selectedContent: null
+ });
+ };
- handleDelete = () => {
- if (this.state.featuredContent) {
- this.deleteFeaturedContent(this.state.featuredContent.id);
- this.setState({ featuredContent: null });
- }
+ handleOpenDeleteDialog = (content: FeaturedContent, i: number) => {
+ this.setState({
+ openDeleteDialog: true,
+ selectedContent: content,
+ placementOfSelectedContent: i
+ });
+ };
+ handleDelete = (id: number, index: number) => {
+ this.deleteFeaturedContent(this.state.featuredContentList[index].id);
+ this.setState(state => {
+ const featuredContentList = this.state.featuredContentList.filter(
+ item => item.id !== id
+ );
+ return {
+ featuredContentList,
+ openDeleteDialog: false,
+ selectedContent: null
+ };
+ });
};
handleOnUpload = (
@@ -139,18 +176,83 @@ export default class EditFeaturedContent extends React.Component {
file: event.target.files[0]
});
};
+ handleSaveButtonClick = (
+ defaultReturned: boolean,
+ content: FeaturedContent
+ ) => {
+ defaultReturned
+ ? this.postFeaturedContent(content)
+ : this.putFeaturedContent(content);
+
+ this.setState(() => {
+ const featuredContentList = this.state.featuredContentList.map(item => {
+ if (item.id === content.id) {
+ return content;
+ } else {
+ return item;
+ }
+ });
+ return {
+ featuredContentList
+ };
+ });
+ };
+
+ handleSaveButtonClickNew = (
+ defaultReturned: boolean,
+ content: FeaturedContent
+ ) => {
+ this.postNewFeaturedContent(content);
+ };
+
+ getDialog = () => {
+ if (this.state.selectedContent) {
+ const contentId = this.state.selectedContent.id;
+ return (
+
+ );
+ }
+ };
render() {
- const { featuredContent, selectedLanguage } = this.state;
+ const { selectedLanguage, featuredContentList } = this.state;
// If the language of the featured content is different from what we expected to fetch, there is no featured content for that language. A request defaults to english if it does not exist.
let defaultReturned = true;
if (
- featuredContent &&
- featuredContent.language &&
- featuredContent.language.code
+ featuredContentList[0] &&
+ featuredContentList[0].language &&
+ featuredContentList[0].language.code
) {
- defaultReturned = featuredContent.language.code !== selectedLanguage;
+ defaultReturned =
+ featuredContentList[0].language.code !== selectedLanguage;
}
return (
@@ -180,168 +282,83 @@ export default class EditFeaturedContent extends React.Component {
;
-
+ ) : null}
+
+ {featuredContentList.map((content, i) => {
+ return (
+
-
- (
- <>
-
- {meta.error && meta.touched && (
- {meta.error}
- )}
- >
- )}
- />
-
-
- or
-
- this.handleFileChosen(event)}
+
-
- {this.state.file && (
- this.handleOnUpload(url, form.change)}
+
+ {content.title}
+ {content.description}
+
+
+ Edit}
+ featuredContentList={featuredContentList}
+ selectedLanguage={selectedLanguage}
+ i={i}
+ defaultReturned={defaultReturned}
+ handleSaveButtonClick={this.handleSaveButtonClick}
+ handleFileChosen={this.handleFileChosen}
+ handleOnCancel={this.handleOnCancel}
+ file={this.state.file}
+ handleOnUpload={this.handleOnUpload}
/>
- )}
-
-
- // $FlowFixMe
- values.imageUrl ? (
-
- ) : null
- }
- />
-
-
-
-
-
- )}
- />
+
+
+
+ );
+ })}
+ {selectedLanguage !== ''
+ ? FeatureAdd(
+ defaultReturned,
+ this.handleSaveButtonClickNew,
+ this.handleFileChosen,
+ this.handleOnCancel,
+ this.state.file,
+ this.handleOnUpload,
+ this.state.featuredContentList,
+ this.state.selectedLanguage
+ )
+ : null}
+
+ {this.getDialog()}
);
}
}
-function handleValidate(values) {
- const errors = {};
-
- if (isEmptyString(values.title)) {
- errors.title = 'Required';
- }
-
- if (isEmptyString(values.description)) {
- errors.description = 'Required';
- }
-
- const regex = /http(s)?:\/\/.*/;
- if (isEmptyString(values.link) || !values.link.match(regex)) {
- errors.link = 'Must be a valid URL e.g "https://digitallibrary.io"';
- }
-
- if (isEmptyString(values.imageUrl) || !values.imageUrl.match(regex)) {
- errors.imageUrl =
- 'Must be a valid URL image url e.g "https://images.digitallibrary.io/imageId.png';
- }
-
- return errors;
-}
diff --git a/packages/admin-frontend/pages/admin/featuredAdd.js b/packages/admin-frontend/pages/admin/featuredAdd.js
new file mode 100644
index 000000000..250df3b9c
--- /dev/null
+++ b/packages/admin-frontend/pages/admin/featuredAdd.js
@@ -0,0 +1,66 @@
+//@flow
+import { Card } from '@material-ui/core';
+import { Add } from '@material-ui/icons';
+import FeaturedEdit from './featuredEdit';
+/** @jsx jsx */
+import { css, jsx } from '@emotion/core';
+import type { FeaturedContent } from '../../types';
+
+export default function FeatureAdd(
+ defaultReturned: boolean,
+ handleSaveButtonClick: (boolean, FeaturedContent) => void,
+ handleFileChosen: (SyntheticInputEvent) => void,
+ handleOnCancel: () => void,
+ file: ?File,
+ handleOnUpload: (string, (string, any) => void) => void,
+ featuredContentList: Array,
+ selectedLanguage: string
+) {
+ return (
+
+
+
+ }
+ i={featuredContentList.length}
+ featuredContentList={featuredContentList}
+ defaultReturned={defaultReturned}
+ handleSaveButtonClick={handleSaveButtonClick}
+ handleFileChosen={handleFileChosen}
+ handleOnCancel={handleOnCancel}
+ file={file}
+ handleOnUpload={handleOnUpload}
+ selectedLanguage={selectedLanguage}
+ />
+ );
+}
diff --git a/packages/admin-frontend/pages/admin/featuredEdit.js b/packages/admin-frontend/pages/admin/featuredEdit.js
new file mode 100644
index 000000000..75fcd14fd
--- /dev/null
+++ b/packages/admin-frontend/pages/admin/featuredEdit.js
@@ -0,0 +1,235 @@
+//@flow
+import React from 'react';
+import { Button, Dialog, TextField, FormHelperText } from '@material-ui/core';
+import { Form, Field, FormSpy } from 'react-final-form';
+import isEmptyString from '../../lib/isEmptyString';
+import UploadFileDialog from '../../components/UploadFileDialog';
+import Row from '../../components/Row';
+import FeaturedImage from '../../components/FeaturedImage';
+import type { FeaturedContent } from '../../types';
+
+type Props = {
+ i: number,
+ button: any,
+ featuredContentList: Array,
+ selectedLanguage: string,
+ defaultReturned: boolean,
+ handleSaveButtonClick: (boolean, FeaturedContent) => void,
+ handleFileChosen: (SyntheticInputEvent) => void,
+ handleOnCancel: () => void,
+ file: ?File,
+ handleOnUpload: (string, (string, any) => void) => void
+};
+type State = {
+ open: boolean
+};
+function handleValidate(values) {
+ const errors = {};
+
+ if (isEmptyString(values.title)) {
+ errors.title = 'Required';
+ }
+
+ if (isEmptyString(values.description)) {
+ errors.description = 'Required';
+ }
+
+ const regex = /http(s)?:\/\/.*/;
+ if (isEmptyString(values.link) || !values.link.match(regex)) {
+ errors.link = 'Must be a valid URL e.g "https://digitallibrary.io"';
+ }
+
+ if (isEmptyString(values.imageUrl) || !values.imageUrl.match(regex)) {
+ errors.imageUrl =
+ 'Must be a valid URL image url e.g "https://images.digitallibrary.io/imageId.png';
+ }
+
+ return errors;
+}
+
+export default class FeaturedEdit extends React.Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ open: false
+ };
+ }
+
+ handleClickOpen = () => {
+ this.setState({
+ open: true
+ });
+ };
+ handleClose() {
+ this.setState({
+ open: false
+ });
+ }
+
+ handleSaveButtonClick = (defaultReturned: boolean) => (
+ content: FeaturedContent
+ ) => {
+ this.props.handleSaveButtonClick(defaultReturned, content);
+ this.setState({ open: false });
+ };
+ render() {
+ const {
+ button,
+ featuredContentList,
+ selectedLanguage,
+ i,
+ defaultReturned,
+ file
+ } = this.props;
+ return (
+ <>
+ {button}
+
+
+ >
+ );
+ }
+}