diff --git a/package.json b/package.json index b027272..8e68970 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "chai": "^4.2.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.5", + "framer-motion": "^2.9.4", "lodash": "^4.17.20", "polished": "^3.6.5", "react": "^16.13.1", diff --git a/src/assets/heart.svg b/src/assets/heart.svg new file mode 100644 index 0000000..5628acb --- /dev/null +++ b/src/assets/heart.svg @@ -0,0 +1,53 @@ + + a whole year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/login.svg b/src/assets/login.svg new file mode 100644 index 0000000..a50cc15 --- /dev/null +++ b/src/assets/login.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Button/styles.ts b/src/components/Button/styles.ts index 33e2182..b3d5853 100644 --- a/src/components/Button/styles.ts +++ b/src/components/Button/styles.ts @@ -50,6 +50,22 @@ export const Container = styled.button` } `} + ${props => + props.variant === 'basic' && + css` + border: 0; + color: ${getBackground(props.theme, props.color, Color.Fill)}; + background: transparent; + + &:hover { + background-color: ${`${getBackground( + props.theme, + props.color, + Color.Fill, + )}15`}; + } + `} + ${props => diff --git a/src/components/LoginAlert/index.tsx b/src/components/LoginAlert/index.tsx new file mode 100644 index 0000000..f3808de --- /dev/null +++ b/src/components/LoginAlert/index.tsx @@ -0,0 +1,54 @@ +import React, { useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; + +import Route from 'routes/enums'; + +import { ReactComponent as HeartImage } from 'assets/heart.svg'; +import { useModal } from 'components/Modal/hooks'; +import { Button } from 'components'; +import { + Container, + ImageContainer, + MessageContainer, + Message, + ButtonsContainer, +} from './styles'; + +const LoginAlert: React.FC = () => { + const history = useHistory(); + const { dismissModal } = useModal(); + + const handleClose = useCallback(() => { + dismissModal(); + }, [dismissModal]); + + return ( + + + + + + Entre com sua conta para adicionar aos favoritos. + + + + + + + ); +}; + +export default LoginAlert; diff --git a/src/components/LoginAlert/styles.ts b/src/components/LoginAlert/styles.ts new file mode 100644 index 0000000..a41249b --- /dev/null +++ b/src/components/LoginAlert/styles.ts @@ -0,0 +1,39 @@ +import styled from 'styled-components'; +import { Size } from 'shared/enums'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + padding: ${Size.Default}; + padding-top: 0; + width: min(550px, calc(100vw - 5rem)); +`; + +export const ImageContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + + svg { + width: min(300px, 100%); + height: auto; + } +`; + +export const MessageContainer = styled.div` + padding: ${Size.Large} ${Size.Default}; + text-align: center; +`; + +export const Message = styled.h3``; + +export const ButtonsContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + @media (min-width: 715px) { + flex-direction: row; + } +`; diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 39df8ba..21a59de 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; +import { AnimatePresence } from 'framer-motion'; import { useModal } from 'components/Modal/hooks'; import StyleProps from 'components/Modal/types/StyleProps'; @@ -43,27 +44,27 @@ const Modal: React.FC = ({ handleDismiss(); }, [location]); // eslint-disable-line - if (!modalContent && !children && !show) { - return null; - } - return ( - - - - - {!hideCloseButton && ( - - - - )} + + {(modalContent || children || show) && ( + + + + + {!hideCloseButton && ( + + + + )} - {modalContent || children} - - - + {modalContent || children} + + + + )} + ); }; diff --git a/src/components/Modal/styles.ts b/src/components/Modal/styles.ts index 276f8be..80128ee 100644 --- a/src/components/Modal/styles.ts +++ b/src/components/Modal/styles.ts @@ -1,4 +1,5 @@ import styled, { css } from 'styled-components'; +import { motion } from 'framer-motion'; import { Color, Size } from 'shared/enums'; import StyleProps from 'components/Modal/types/StyleProps'; @@ -17,19 +18,26 @@ export const Container = styled.div` } */ `; -export const Backdrop = styled.div` +export const Backdrop = styled(motion.div).attrs(() => ({ + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, +}))` flex: 1; background-color: rgba(0, 0, 0, 0.4); `; -export const ModalContainer = styled.div` +export const ModalContainer = styled(motion.div).attrs((props: any) => ({ + initial: { y: '-60%', x: '-50%', opacity: 0 }, + animate: { y: props?.center ? '-50%' : 0, x: '-50%', opacity: 1 }, + exit: { y: '-60%', x: '-50%', opacity: 0 }, +}))` position: absolute; top: 100px; left: 50%; transform: translateX(-50%); - z-index: 15; display: flex; @@ -39,12 +47,10 @@ export const ModalContainer = styled.div` max-height: 100%; overflow: hidden; - /* height: 100%; */ - ${props => props.center && css` - top: 50% !important; + top: 50%; transform: translate(-50%, -50%); `} @@ -53,28 +59,14 @@ export const ModalContainer = styled.div` css` height: ${props.height}; `} - - - - - /* @media (max-width: 1280px) { - width: calc(100% - ${Size.Medium} * 2); - } */ - - @media (max-width: 715px) { - /* top: 0; - left: 0; - width: 100%; - height: 100%; */ - } - - `; export const ModalContent = styled.div` display: flex; flex-direction: column; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); + box-shadow: 0 4px 7px 1px rgba(0, 0, 0, 0.3); + margin: 10px; + margin-top: 0; background-color: ${Color.Fill}; border-radius: ${Size.Small}; diff --git a/src/components/index.ts b/src/components/index.ts index 47aeebb..328d33f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -7,6 +7,7 @@ export { default as AspectRatioLoading } from './AspectRatio/Loading'; export { default as Button } from './Button'; export { default as EnvironmentLabel } from './EnvironmentLabel'; export { default as Input } from './Input'; +export { default as LoginAlert } from './LoginAlert'; export { default as Media } from './Media'; export { default as Modal } from './Modal'; diff --git a/src/containers/EntityImage/index.tsx b/src/containers/EntityImage/index.tsx index 585f470..c7ed9a9 100644 --- a/src/containers/EntityImage/index.tsx +++ b/src/containers/EntityImage/index.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; +import { AnimatePresence } from 'framer-motion'; import { BsHeartFill } from 'react-icons/bs'; import { getEntityRoute } from 'shared/helpers'; @@ -8,7 +9,7 @@ import { useModal } from 'components/Modal/hooks'; import { useFavorite } from 'domains/Favorites/hooks'; import { Color } from 'shared/enums'; -import { Media } from 'components'; +import { LoginAlert, Media } from 'components'; import { EntityImageLoading } from 'containers'; import { Container, @@ -45,7 +46,7 @@ const EntityImage: React.FC = ({ const handleFavorite = useCallback(async () => { try { if (!user) { - alert('Entre com sua conta para adicionar aos favoritos.'); + setModalContent({ value: }); return; } @@ -53,7 +54,7 @@ const EntityImage: React.FC = ({ } catch (error) { console.log('handleFavorite -> error', error); } - }, [user, UpdateFavorite, entity.id, entity.mediaType]); + }, [user, setModalContent, UpdateFavorite, entity.id, entity.mediaType]); const handleRedirect = useCallback(() => { if (!disabled) { @@ -96,10 +97,6 @@ const EntityImage: React.FC = ({ } }, [user, favoriteList, entity.id, entity.mediaType]); - if (!entity.featuredImage && !entity.backdrop && !showEmpty) { - return null; - } - if (isLoading) { return ( = ({ } return ( - - {!hideFavoriteButton && ( - - - + + {(entity.featuredImage || entity.backdrop || showEmpty) && ( + + {!hideFavoriteButton && ( + + + + )} + + + + {showInfo && ( + + + {entity.title} + + {!hideSubtitle && ( + {entity?.subtitle} + )} + + )} + + )} - - - - {showInfo && ( - - {entity.title} - {!hideSubtitle && {entity?.subtitle}} - - )} - - + ); }; diff --git a/src/containers/EntityImage/styles.ts b/src/containers/EntityImage/styles.ts index 1dda95f..9bcb356 100644 --- a/src/containers/EntityImage/styles.ts +++ b/src/containers/EntityImage/styles.ts @@ -75,6 +75,8 @@ export const IconButton = styled.button` svg { height: ${Size.Default}; width: ${Size.Default}; + + transition: fill 0.3s; } `; diff --git a/src/containers/EntityImageList/index.tsx b/src/containers/EntityImageList/index.tsx index 5981aae..7036bd6 100644 --- a/src/containers/EntityImageList/index.tsx +++ b/src/containers/EntityImageList/index.tsx @@ -1,4 +1,5 @@ import React, { useCallback, createRef, useState } from 'react'; +import { AnimatePresence } from 'framer-motion'; import { FiChevronLeft, FiChevronRight } from 'react-icons/fi'; @@ -9,6 +10,7 @@ import { Title, ListContainer, ListContent, + EntityImageContainer, PreviousButton, NextButton, MessageContainer, @@ -73,65 +75,68 @@ const EntityImageList: React.FC = ({ }, [itemsContainer, checkButtons]); return ( - - - {title && ( - - {title} - - )} - - {isLoading && ( - - )} - - {!isLoading && data.length > 0 && ( - <> - {showPreviousButton && ( - - - - )} - - - {data.map(entity => ( - - ))} - - - {showNextButton && ( - - - - )} - - )} - - {!isLoading && data.length === 0 && ( - {message} - )} - - + + + + {title && ( + + {title} + + )} + + {isLoading && ( + + )} + + {!isLoading && data.length > 0 && ( + <> + {showPreviousButton && ( + + + + )} + + + {data.map(entity => ( + + + + ))} + + + {showNextButton && ( + + + + )} + + )} + + {!isLoading && data.length === 0 && ( + {message} + )} + + + ); }; diff --git a/src/containers/EntityImageList/styles.ts b/src/containers/EntityImageList/styles.ts index d5bdca5..5d027f1 100644 --- a/src/containers/EntityImageList/styles.ts +++ b/src/containers/EntityImageList/styles.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { motion } from 'framer-motion'; import DefaultProps from 'shared/types'; import { Container as DefaultContainer, Button } from 'components'; @@ -61,7 +62,9 @@ export const ListContainer = styled.div` } `; -export const ListContent = styled.div` +export const ListContent = styled(motion.div).attrs(() => ({ + // layout: true, +}))` display: inline-flex; padding: 0 2px; margin-top: 2px; @@ -92,6 +95,12 @@ export const ListContent = styled.div` } `; +export const EntityImageContainer = styled(motion.div).attrs(() => ({ + initial: { x: 20, opacity: 0 }, + animate: { x: 0, opacity: 1 }, + exit: { x: -20, opacity: 0 }, +}))``; + export const PreviousButton = styled(Button)` opacity: 0; pointer-events: none; diff --git a/src/containers/Filmography/index.tsx b/src/containers/Filmography/index.tsx index 5129868..928f7c0 100644 --- a/src/containers/Filmography/index.tsx +++ b/src/containers/Filmography/index.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; +import { AnimatePresence } from 'framer-motion'; import { getEntityRoute } from 'shared/helpers'; import { Color } from 'shared/enums'; @@ -51,55 +52,57 @@ const Filmography: React.FC = ({ }, [data]); return ( - - - {title && ( - - {title} - - )} + + + + {title && ( + + {title} + + )} - {isLoading && } + {isLoading && } - {!isLoading && data.length > 0 && ( - <> - - {parsedData.map((entity: any) => ( - handleRedirect(entity)} - > - {entity.year} - - {entity.title || entity.name} - - {entity.character && ` como ${entity.character}`} - - {entity.episodeCount && ( - - {entity.episodeCount > 1 - ? ` (${entity.episodeCount} episódios)` - : ` (${entity.episodeCount} episódio)`} - - )} - - ))} - + {!isLoading && data.length > 0 && ( + <> + + {parsedData.map((entity: any) => ( + handleRedirect(entity)} + > + {entity.year} - + {entity.title || entity.name} + + {entity.character && ` como ${entity.character}`} + + {entity.episodeCount && ( + + {entity.episodeCount > 1 + ? ` (${entity.episodeCount} episódios)` + : ` (${entity.episodeCount} episódio)`} + + )} + + ))} + - {parsedData?.length < data?.length && ( - - - - )} - - )} + {parsedData?.length < data?.length && ( + + + + )} + + )} - {!isLoading && data.length === 0 && ( - {message} - )} - - + {!isLoading && data.length === 0 && ( + {message} + )} + + + ); }; diff --git a/src/containers/Filmography/styles.ts b/src/containers/Filmography/styles.ts index ca61ac7..5809e95 100644 --- a/src/containers/Filmography/styles.ts +++ b/src/containers/Filmography/styles.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { motion } from 'framer-motion'; import DefaultProps from 'shared/types'; import { Container as DefaultContainer } from 'components/Layout'; @@ -43,7 +44,11 @@ export const ListContainer = styled.div` transition: 0.3s height; `; -export const ItemContainer = styled.div` +export const ItemContainer = styled(motion.div).attrs(() => ({ + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, +}))` font-size: ${Size.Default}; color: ${Color.Text}; padding: ${Size.Medium} 0; diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index 3c72f28..abc8cfb 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -72,7 +72,7 @@ const Login: React.FC = () => { return ( -
+
@@ -88,7 +88,7 @@ const Login: React.FC = () => { placeholder="Senha" /> {error} - @@ -102,7 +102,7 @@ const Login: React.FC = () => { @@ -103,7 +103,7 @@ const Signup: React.FC = () => {