diff --git a/docker-compose.yaml b/docker-compose.yaml index 428d63a..9bbfbca 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,17 +1,17 @@ version: '3' services: # applications - frontend: - build: - context: frontend - args: - REACT_APP_API_URL: http://api:5001/graphql - restart: always - container_name: frontend - ports: - - '5000:80' - depends_on: - - api + # frontend: + # build: + # context: frontend + # args: + # REACT_APP_API_URL: http://api:5001/graphql + # restart: always + # container_name: frontend + # ports: + # - '5000:80' + # depends_on: + # - api api: build: api restart: always diff --git a/frontend/public/albums.svg b/frontend/public/albums.svg new file mode 100644 index 0000000..0f8c039 --- /dev/null +++ b/frontend/public/albums.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.scss b/frontend/src/App.scss index 19eaf86..abc40ff 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -158,7 +158,6 @@ p { .mdc-image-list--with-text-protection .mdc-image-list__supporting { background: rgba(0, 0, 0, 0.2) !important; - justify-content: center !important; } .mdc-layout-grid { @@ -186,6 +185,10 @@ p { justify-content: center !important; } +.album-list-info { + justify-content: left !important; +} + .mdc-image-list__label, .mdc-text-field__input { font-family: $font; diff --git a/frontend/src/components/Content.js b/frontend/src/components/Content.js index c80896d..4129164 100644 --- a/frontend/src/components/Content.js +++ b/frontend/src/components/Content.js @@ -2,7 +2,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Route, Switch } from 'react-router-dom'; import { DrawerAppContent } from '@rmwc/drawer'; -import { Photo, Photos, Search, Upcoming, Favourites, Trash } from '../pages'; +import { + Photo, + Photos, + Search, + Upcoming, + Favourites, + Trash, + Albums, + Album, +} from '../pages'; import { Explore, People, Places, Things, Entity } from '../pages/explore'; import SideNav from './SideNav'; @@ -23,10 +32,11 @@ const Content = (props) => { + + {/* static */} - diff --git a/frontend/src/components/DeleteAlbumDialog.js b/frontend/src/components/DeleteAlbumDialog.js new file mode 100644 index 0000000..4a05e9d --- /dev/null +++ b/frontend/src/components/DeleteAlbumDialog.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router-dom'; +import { gql, useMutation } from '@apollo/client'; +import { CircularProgress } from '@rmwc/circular-progress'; +import { Dialog, DialogTitle, DialogActions, DialogButton } from '@rmwc/dialog'; +import '@rmwc/dialog/styles'; + +const DELETE_ALBUM = gql` + mutation deleteAlbum($id: String!) { + deleteAlbum(id: $id) + } +`; + +const DeleteAlbumDialog = ({ open, setOpen, albumName, albumId }) => { + const history = useHistory(); + const [deleteAlbum, { data: delData, loading: delLoading, error: delError }] = + useMutation(DELETE_ALBUM); + + const handleDeleteAlbum = (albumId) => { + deleteAlbum({ + variables: { id: albumId }, + }); + }; + + if (delData && delData.deleteAlbum) { + setTimeout(() => { + history.push('/albums'); + }, 2000); + } + + return ( + { + setOpen(false); + }} + > + + Are you sure you want to delete album "{albumName}"? + +
+
+ + {delLoading && }    + {delError && <>Sorry, some error occured!}    + handleDeleteAlbum(albumId)} + style={{ color: '#fff' }} + > + Delete + +    + + Close + + +
+ ); +}; + +DeleteAlbumDialog.propTypes = { + open: PropTypes.bool, + setOpen: PropTypes.func, + albumId: PropTypes.string, + albumName: PropTypes.string, +}; + +export default DeleteAlbumDialog; diff --git a/frontend/src/components/EditAlbum.js b/frontend/src/components/EditAlbum.js new file mode 100644 index 0000000..c3099f6 --- /dev/null +++ b/frontend/src/components/EditAlbum.js @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import { gql, useMutation } from '@apollo/client'; +import PropTypes from 'prop-types'; +import { Icon } from '@rmwc/icon'; +import { Button } from '@rmwc/button'; +import { TextField } from '@rmwc/textfield'; +import { CircularProgress } from '@rmwc/circular-progress'; + +const UPDATE_ALBUM = gql` + mutation updateAlbum($id: String!, $name: String!) { + updateAlbum(id: $id, input: { name: $name }) + } +`; + +const EditAlbum = ({ albumId, albumName }) => { + const [ + updateAlbum, + { loading: updateAlbumNameLoading, error: updateAlbumNameyError }, + ] = useMutation(UPDATE_ALBUM); + + const [showEdit, setShowEdit] = useState(false); + const [updatedAlbumName, setupdatedAlbumName] = useState(albumName); + + const handleEditAlbumName = (albumId, updatedAlbumName) => { + updateAlbum({ variables: { id: albumId, name: updatedAlbumName } }); + setShowEdit(false); + }; + + return ( + <> + {showEdit ? ( + <> +
+ setupdatedAlbumName(e.target.value)} + style={{ height: '36px' }} + /> +     +
+
+
+ + ) : ( + <> +

{updatedAlbumName}

+     + setShowEdit(!showEdit)} + style={{ cursor: 'pointer', color: '#424242' }} + icon={{ icon: 'edit', size: 'small' }} + /> + + )} + + ); +}; + +EditAlbum.propTypes = { + albumId: PropTypes.string, + albumName: PropTypes.string, +}; + +export default EditAlbum; diff --git a/frontend/src/components/index.js b/frontend/src/components/index.js index 39305ae..8a979a0 100644 --- a/frontend/src/components/index.js +++ b/frontend/src/components/index.js @@ -8,6 +8,8 @@ import ExploreEntity from './explore/ExploreEntity'; import PeopleList from './PeopleList'; import FavouriteAction from './FavouriteAction'; import DeleteAction from './DeleteAction'; +import DeleteAlbumDialog from './DeleteAlbumDialog'; +import EditAlbum from './EditAlbum'; export { Content, @@ -20,4 +22,6 @@ export { PeopleList, FavouriteAction, DeleteAction, + DeleteAlbumDialog, + EditAlbum, }; diff --git a/frontend/src/pages/Album.js b/frontend/src/pages/Album.js new file mode 100644 index 0000000..db03658 --- /dev/null +++ b/frontend/src/pages/Album.js @@ -0,0 +1,113 @@ +import React, { useState } from 'react'; +import moment from 'moment'; +import { useHistory } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; +import { gql, useQuery } from '@apollo/client'; +import { Icon } from '@rmwc/icon'; +import { + ImageList, + ImageListImage, + ImageListItem, + ImageListImageAspectContainer, +} from '@rmwc/image-list'; +import { Grid, GridCell } from '@rmwc/grid'; +import '@rmwc/grid/styles'; +import { Loading, Error, DeleteAlbumDialog, EditAlbum } from '../components'; + +const GET_ALBUM = gql` + query getAlbum($id: String!) { + album(id: $id) { + id + name + description + createdAt + mediaItems { + totalCount + nodes { + id + imageUrl + } + } + } + } +`; + +const Album = () => { + let history = useHistory(); + let { id } = useParams(); + const { error, loading, data } = useQuery(GET_ALBUM, { + variables: { id }, + fetchPolicy: 'no-cache', + }); + + if (error) return ; + + const styleFav = { + radius: '4px', + width: '180px', + margin: '0px 6px 8px 6px', + }; + + const [open, setOpen] = useState(false); + + return ( + <> + {loading ? ( + + ) : ( + <> + + +
+ +     + setOpen(true)} + style={{ cursor: 'pointer', color: '#424242' }} + icon={{ icon: 'delete', size: 'small' }} + /> + +
+ + {moment(data.album.createdAt).format('MMMM D, YYYY')} + +
+
+ + + + {data.album.mediaItems.nodes.map((img) => ( + + + history.push(`/photo/${img.id}`)} + /> + + + ))} + + + + + )} + + ); +}; + +export default Album; diff --git a/frontend/src/pages/Albums.js b/frontend/src/pages/Albums.js new file mode 100644 index 0000000..eeb468e --- /dev/null +++ b/frontend/src/pages/Albums.js @@ -0,0 +1,108 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { gql, useQuery } from '@apollo/client'; +import { Grid, GridCell } from '@rmwc/grid'; +import { + ImageList, + ImageListImage, + ImageListItem, + ImageListLabel, + ImageListSupporting, + ImageListImageAspectContainer, +} from '@rmwc/image-list'; +import { Loading, Error } from '../components'; + +const GET_ALBUMS = gql` + query getAlbums { + albums { + totalCount + nodes { + id + name + mediaItems { + totalCount + nodes { + imageUrl + } + } + } + } + } +`; + +const Albums = () => { + const { error: albumsError, data: albumsData } = useQuery(GET_ALBUMS, { + fetchPolicy: 'no-cache', + }); + if (albumsError) return ; + + let history = useHistory(); + const styleFav = { + radius: '4px', + width: '180px', + margin: '0px 6px 8px 6px', + }; + + return ( + <> + {albumsData && albumsData.albums && albumsData.albums.nodes ? ( + <> + {albumsData.albums && albumsData.albums.totalCount === 0 ? ( + <> + + + +
+ +
+
+ You have not created any albums yet! +
+
+ +
+ + ) : ( + <> + + + Albums + + + + + + {albumsData.albums.nodes.map((album) => ( + + + history.push(`/album/${album.id}`)} + /> + + + + {album.name} +
+ + {album.mediaItems?.totalCount} items + +
+
+
+ ))} +
+
+
+ + )} + + ) : ( + + )} + + ); +}; + +export default Albums; diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js index a6b1098..cbaebf3 100644 --- a/frontend/src/pages/index.js +++ b/frontend/src/pages/index.js @@ -4,6 +4,18 @@ import Photo from './Photo'; import Photos from './Photos'; import Search from './Search'; import Sharing from './Sharing'; +import Albums from './Albums'; +import Album from './Album'; import Upcoming from './Upcoming'; -export { Trash, Favourites, Photo, Photos, Search, Sharing, Upcoming }; +export { + Trash, + Favourites, + Photo, + Photos, + Search, + Sharing, + Albums, + Album, + Upcoming, +};