├── .prettierrc ├── .github ├── logo.png ├── preview1.png ├── preview2.png ├── preview3.png ├── preview4.png ├── preview5.png ├── preview6.png ├── preview7.png └── preview8.gif ├── src ├── assets │ ├── logo.png │ ├── splash.png │ ├── icon-bg.png │ ├── icons │ │ ├── tv.png │ │ ├── drone.png │ │ ├── logo.png │ │ ├── laptop.png │ │ ├── logo@2x.png │ │ ├── logo@3x.png │ │ ├── smartphone.png │ │ ├── videogames.png │ │ └── online-store.png │ ├── images │ │ ├── not-found.png │ │ ├── empty-cart.png │ │ └── empty-favorite.png │ ├── fonts │ │ ├── Roboto-Bold.ttf │ │ └── Roboto-Regular.ttf │ └── animations │ │ ├── loading.json │ │ └── checkout.json ├── store │ ├── modules │ │ ├── favorite │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── rootSaga.js │ │ ├── profile │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── rootReducer.js │ │ └── cart │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── saga.js │ └── index.js ├── services │ └── api.js ├── util │ └── format.js ├── styles │ └── colors.js ├── components │ ├── Header │ │ ├── styles.js │ │ └── index.js │ ├── TabIcon │ │ └── index.js │ ├── Discount │ │ ├── index.js │ │ └── styles.js │ ├── BuyButton │ │ ├── index.js │ │ └── styles.js │ ├── Input │ │ ├── index.js │ │ └── styles.js │ ├── InputSearch │ │ ├── styles.js │ │ └── index.js │ ├── TabStateIcon │ │ ├── styles.js │ │ └── index.js │ ├── Rating │ │ ├── styles.js │ │ └── index.js │ ├── Carousel │ │ ├── styles.js │ │ └── index.js │ ├── ProfileButton │ │ ├── index.js │ │ └── styles.js │ ├── CustomText │ │ └── index.js │ ├── ProductItem │ │ ├── styles.js │ │ └── index.js │ ├── ProductCart │ │ ├── styles.js │ │ └── index.js │ └── Product │ │ ├── styles.js │ │ └── index.js ├── App.js ├── config │ └── ReactotronConfig.js ├── pages │ ├── Favorite │ │ ├── styles.js │ │ └── index.js │ ├── SignIn │ │ ├── styles.js │ │ └── index.js │ ├── Profile │ │ ├── styles.js │ │ └── index.js │ ├── Deals │ │ ├── styles.js │ │ └── index.js │ ├── Home │ │ ├── styles.js │ │ └── index.js │ └── Cart │ │ ├── styles.js │ │ └── index.js └── routes │ └── index.js ├── jsconfig.json ├── .expo-shared └── assets.json ├── .editorconfig ├── .gitignore ├── babel.config.js ├── app.json ├── .eslintrc.js ├── package.json ├── README.md └── db.json /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/logo.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /.github/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview1.png -------------------------------------------------------------------------------- /.github/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview2.png -------------------------------------------------------------------------------- /.github/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview3.png -------------------------------------------------------------------------------- /.github/preview4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview4.png -------------------------------------------------------------------------------- /.github/preview5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview5.png -------------------------------------------------------------------------------- /.github/preview6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview6.png -------------------------------------------------------------------------------- /.github/preview7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview7.png -------------------------------------------------------------------------------- /.github/preview8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/.github/preview8.gif -------------------------------------------------------------------------------- /src/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/splash.png -------------------------------------------------------------------------------- /src/assets/icon-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icon-bg.png -------------------------------------------------------------------------------- /src/assets/icons/tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/tv.png -------------------------------------------------------------------------------- /src/assets/icons/drone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/drone.png -------------------------------------------------------------------------------- /src/assets/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/logo.png -------------------------------------------------------------------------------- /src/assets/icons/laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/laptop.png -------------------------------------------------------------------------------- /src/assets/icons/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/logo@2x.png -------------------------------------------------------------------------------- /src/assets/icons/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/logo@3x.png -------------------------------------------------------------------------------- /src/assets/icons/smartphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/smartphone.png -------------------------------------------------------------------------------- /src/assets/icons/videogames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/videogames.png -------------------------------------------------------------------------------- /src/assets/images/not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/images/not-found.png -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /src/assets/icons/online-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/icons/online-store.png -------------------------------------------------------------------------------- /src/assets/images/empty-cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/images/empty-cart.png -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/assets/images/empty-favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfzaguiar/e-commerce-app/HEAD/src/assets/images/empty-favorite.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "paths": { 5 | "~/*": ["*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/store/modules/favorite/actions.js: -------------------------------------------------------------------------------- 1 | export function toggleFavorite(product) { 2 | return { 3 | type: '@favorite/TOGGLE_FAVORITE', 4 | product, 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /src/store/modules/rootSaga.js: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects'; 2 | 3 | import cart from './cart/saga'; 4 | 5 | export default function* rootSaga() { 6 | return yield all([cart]); 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const api = axios.create({ 4 | baseURL: 'http://10.0.3.2:3000', 5 | // baseURL: 'http://3810d6bb.ngrok.io', 6 | }); 7 | 8 | export default api; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /src/util/format.js: -------------------------------------------------------------------------------- 1 | import 'intl'; 2 | import 'intl/locale-data/jsonp/pt-BR'; 3 | 4 | export const { format: formatPrice } = new Intl.NumberFormat('pt-BR', { 5 | style: 'currency', 6 | currency: 'BRL', 7 | }); 8 | -------------------------------------------------------------------------------- /src/store/modules/profile/actions.js: -------------------------------------------------------------------------------- 1 | export function SignIn(user) { 2 | return { 3 | type: '@profile/SIGN_IN', 4 | payload: user, 5 | }; 6 | } 7 | 8 | export function SignOut() { 9 | return { 10 | type: '@profile/SIGN_OUT', 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/store/modules/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import cart from './cart/reducer'; 4 | import favorite from './favorite/reducer'; 5 | import profile from './profile/reducer'; 6 | 7 | export default combineReducers({ cart, favorite, profile }); 8 | -------------------------------------------------------------------------------- /src/styles/colors.js: -------------------------------------------------------------------------------- 1 | const colors = { 2 | white: '#fff', 3 | grey: '#828282', 4 | darkgrey: '#4f4f4f', 5 | 6 | primary: 'tomato', 7 | background: '#f3f5f5', 8 | discount: 'rgba(255, 0, 0, 0.6)', 9 | 10 | text: '#333333', 11 | }; 12 | 13 | export default colors; 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | [ 7 | 'babel-plugin-root-import', 8 | { 9 | rootPathSuffix: 'src', 10 | }, 11 | ], 12 | ], 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Header/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex-direction: row; 5 | background: tomato; 6 | align-items: center; 7 | padding: 8px 15px; 8 | `; 9 | 10 | export const Logo = styled.Image.attrs({ 11 | resizeMode: 'center', 12 | })` 13 | height: 40px; 14 | width: 75px; 15 | margin-right: 10px; 16 | `; 17 | -------------------------------------------------------------------------------- /src/components/TabIcon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FontAwesome } from '@expo/vector-icons/'; 4 | 5 | export default function TabIcon({ name, tintColor }) { 6 | return ; 7 | } 8 | 9 | TabIcon.propTypes = { 10 | name: PropTypes.string.isRequired, 11 | tintColor: PropTypes.string.isRequired, 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/Discount/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Container, DiscountText } from './styles'; 5 | 6 | export default function Discount({ children }) { 7 | return ( 8 | 9 | {Number(100 - 100 * children)}% off 10 | 11 | ); 12 | } 13 | 14 | Discount.propTypes = { 15 | children: PropTypes.number.isRequired, 16 | }; 17 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { registerRootComponent } from 'expo'; 4 | 5 | import './config/ReactotronConfig'; 6 | import Routes from '~/routes'; 7 | import store from '~/store'; 8 | 9 | function App() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default registerRootComponent(App); 20 | -------------------------------------------------------------------------------- /src/components/BuyButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Button, Text, Icon } from './styles'; 5 | 6 | export default function BuyButton({ children, ...rest }) { 7 | return ( 8 | 12 | ); 13 | } 14 | 15 | BuyButton.propTypes = { 16 | children: PropTypes.string.isRequired, 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/Input/index.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | import { FontAwesome } from '@expo/vector-icons/'; 3 | 4 | import { Container, TInput } from './styles'; 5 | 6 | function Input({ icon, ...rest }, ref) { 7 | return ( 8 | 9 | 10 | {icon && ( 11 | 12 | )} 13 | 14 | ); 15 | } 16 | 17 | export default forwardRef(Input); 18 | -------------------------------------------------------------------------------- /src/components/Input/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | margin-top: 5px; 5 | padding: 0 15px; 6 | height: 50px; 7 | background: rgba(0, 0, 0, 0.6); 8 | flex-direction: row; 9 | align-items: center; 10 | border-radius: 4px; 11 | `; 12 | 13 | export const TInput = styled.TextInput.attrs({ 14 | placeholderTextColor: 'rgba(255,255,255, 0.8)', 15 | })` 16 | flex: 1; 17 | font-size: 15px; 18 | margin-left: 10px; 19 | color: #fff; 20 | `; 21 | -------------------------------------------------------------------------------- /src/components/InputSearch/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex: 1; 5 | padding: 0 15px; 6 | height: 35px; 7 | background: rgba(0, 0, 0, 0.6); 8 | flex-direction: row; 9 | align-items: center; 10 | border-radius: 4px; 11 | `; 12 | 13 | export const TInput = styled.TextInput.attrs({ 14 | placeholderTextColor: 'rgba(255,255,255, 0.8)', 15 | })` 16 | flex: 1; 17 | font-size: 15px; 18 | margin-left: 10px; 19 | color: #fff; 20 | `; 21 | -------------------------------------------------------------------------------- /src/config/ReactotronConfig.js: -------------------------------------------------------------------------------- 1 | import Reactotron from 'reactotron-react-native'; 2 | import { reactotronRedux } from 'reactotron-redux'; 3 | import reactotronSaga from 'reactotron-redux-saga'; 4 | import { AsyncStorage } from 'react-native'; 5 | 6 | if (__DEV__) { 7 | const tron = Reactotron.configure() 8 | .useReactNative() 9 | .use(reactotronRedux()) 10 | .use(reactotronSaga()) 11 | .setAsyncStorageHandler(AsyncStorage) 12 | .connect(); 13 | 14 | console.tron = tron; 15 | 16 | tron.clear(); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/TabStateIcon/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View``; 4 | 5 | export const Circle = styled.View` 6 | position: absolute; 7 | height: 16px; 8 | width: 16px; 9 | border-radius: 8px; 10 | background: rgba(255, 0, 0, 0.6); 11 | justify-content: center; 12 | align-items: center; 13 | top: -5px; 14 | left: 15px; 15 | `; 16 | 17 | export const Number = styled.Text` 18 | color: #fff; 19 | font-weight: bold; 20 | font-size: 10px; 21 | `; 22 | -------------------------------------------------------------------------------- /src/components/Discount/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import CustomText from '~/components/CustomText'; 3 | import colors from '~/styles/colors'; 4 | 5 | export const Container = styled.View` 6 | position: absolute; 7 | padding: 3px; 8 | background: ${colors.discount}; 9 | max-width: 50px; 10 | align-items: center; 11 | justify-content: center; 12 | align-self: flex-end; 13 | `; 14 | 15 | export const DiscountText = styled(CustomText)` 16 | font-family: 'roboto-bold'; 17 | font-size: 12px; 18 | color: #fff; 19 | `; 20 | -------------------------------------------------------------------------------- /src/pages/Favorite/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { Dimensions } from 'react-native'; 3 | import colors from '~/styles/colors'; 4 | 5 | export const Container = styled.View` 6 | flex: 1; 7 | padding: 5px; 8 | background: ${colors.background}; 9 | `; 10 | 11 | export const Wrapper = styled.View` 12 | flex: 1; 13 | align-items: center; 14 | justify-content: center; 15 | `; 16 | 17 | export const EmptyFavorite = styled.Image.attrs({ 18 | resizeMode: 'contain', 19 | })` 20 | width: ${Dimensions.get('window').width * 1}; 21 | `; 22 | -------------------------------------------------------------------------------- /src/components/Rating/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { FontAwesome } from '@expo/vector-icons/'; 3 | import colors from '~/styles/colors'; 4 | 5 | export const Container = styled.View``; 6 | 7 | export const StarContainer = styled.View` 8 | flex-direction: row; 9 | `; 10 | 11 | export const Label = styled.Text` 12 | font-size: ${props => props.fontSize && `${props.fontSize}px`}; 13 | color: ${props => props.color || colors.grey}; 14 | margin: 0 0 2px 5px; 15 | `; 16 | 17 | export const StarIcon = styled(FontAwesome)` 18 | margin: 0 3px; 19 | `; 20 | -------------------------------------------------------------------------------- /src/store/modules/profile/reducer.js: -------------------------------------------------------------------------------- 1 | import produce from 'immer'; 2 | 3 | const INITIAL_STATE = { 4 | email: null, 5 | name: null, 6 | }; 7 | 8 | export default function profile(state = INITIAL_STATE, action) { 9 | return produce(state, draft => { 10 | switch (action.type) { 11 | case '@profile/SIGN_IN': 12 | draft.email = action.payload.email; 13 | draft.name = action.payload.name; 14 | break; 15 | 16 | case '@profile/SIGN_OUT': 17 | draft.email = null; 18 | draft.name = null; 19 | break; 20 | default: 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/store/modules/favorite/reducer.js: -------------------------------------------------------------------------------- 1 | import produce from 'immer'; 2 | 3 | export default function favorite(state = [], action) { 4 | switch (action.type) { 5 | case '@favorite/TOGGLE_FAVORITE': 6 | return produce(state, draft => { 7 | const { product } = action; 8 | 9 | const productIndex = draft.findIndex(p => p.id === product.id); 10 | 11 | if (productIndex >= 0) { 12 | draft.splice(productIndex, 1); 13 | } else { 14 | draft.push({ ...product, favorite: true }); 15 | } 16 | }); 17 | default: 18 | return state; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Carousel/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex: 1; 5 | flex-direction: column; 6 | height: 280px; 7 | align-items: center; 8 | justify-content: center; 9 | background: #fff; 10 | `; 11 | 12 | export const ContainerImage = styled.View` 13 | flex: 1; 14 | background: transparent; 15 | justify-content: center; 16 | align-items: center; 17 | padding: 5px; 18 | `; 19 | 20 | export const ProductImage = styled.Image.attrs({ 21 | resizeMode: 'contain', 22 | aspectRatio: 1, 23 | })` 24 | width: 100%; 25 | height: 100%; 26 | `; 27 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | 4 | import rootReducer from './modules/rootReducer'; 5 | import rootSaga from './modules/rootSaga'; 6 | 7 | const sagaMonitor = __DEV__ ? console.tron.createSagaMonitor() : null; 8 | const sagaMiddleware = createSagaMiddleware({ 9 | sagaMonitor, 10 | }); 11 | 12 | const enhancer = __DEV__ 13 | ? compose(console.tron.createEnhancer(), applyMiddleware(sagaMiddleware)) 14 | : applyMiddleware(sagaMiddleware); 15 | 16 | const store = createStore(rootReducer, enhancer); 17 | 18 | sagaMiddleware.run(rootSaga); 19 | 20 | export default store; 21 | -------------------------------------------------------------------------------- /src/components/BuyButton/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { FontAwesome } from '@expo/vector-icons/'; 3 | import colors from '~/styles/colors'; 4 | 5 | export const Button = styled.TouchableOpacity` 6 | flex-direction: row; 7 | align-self: stretch; 8 | justify-content: center; 9 | align-items: center; 10 | background: tomato; 11 | border-radius: 4px; 12 | `; 13 | 14 | export const Text = styled.Text` 15 | font-weight: bold; 16 | color: ${colors.white}; 17 | font-size: 20px; 18 | `; 19 | 20 | export const Icon = styled(FontAwesome).attrs({ 21 | size: 22, 22 | })` 23 | color: ${colors.white}; 24 | margin-right: 20px; 25 | `; 26 | -------------------------------------------------------------------------------- /src/components/ProfileButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Container, Icon, Wrapper, Title } from './styles'; 5 | 6 | export default function ProfileButton({ name, children, margin = 0 }) { 7 | return ( 8 | 9 | 10 | 11 | {children} 12 | 13 | 14 | ); 15 | } 16 | 17 | ProfileButton.propTypes = { 18 | name: PropTypes.string.isRequired, 19 | children: PropTypes.string.isRequired, 20 | margin: PropTypes.number, 21 | }; 22 | 23 | ProfileButton.defaultProps = { 24 | margin: 0, 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/CustomText/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Text } from 'react-native'; 3 | import * as Font from 'expo-font'; 4 | 5 | export default function CustomText({ children, ...rest }) { 6 | const [fontLoad, setFontLoad] = useState(false); 7 | 8 | useEffect(() => { 9 | async function handleFont() { 10 | await Font.loadAsync({ 11 | 'roboto-regular': require('~/assets/fonts/Roboto-Regular.ttf'), 12 | 'roboto-bold': require('~/assets/fonts/Roboto-Bold.ttf'), 13 | }); 14 | setFontLoad(true); 15 | } 16 | handleFont(); 17 | }, []); 18 | 19 | return fontLoad && {children}; 20 | } 21 | -------------------------------------------------------------------------------- /src/store/modules/cart/actions.js: -------------------------------------------------------------------------------- 1 | export function addToCartRequest(id) { 2 | return { 3 | type: '@cart/ADD_REQUEST', 4 | id, 5 | }; 6 | } 7 | 8 | export function addToCartSuccess(product) { 9 | return { 10 | type: '@cart/ADD_SUCCESS', 11 | product, 12 | }; 13 | } 14 | 15 | export function removeFromCart(id) { 16 | return { 17 | type: '@cart/REMOVE', 18 | id, 19 | }; 20 | } 21 | 22 | export function updateAmountRequest(id, amount) { 23 | return { 24 | type: '@cart/UPDATE_AMOUNT_REQUEST', 25 | id, 26 | amount, 27 | }; 28 | } 29 | 30 | export function updateAmountSuccess(id, amount) { 31 | return { 32 | type: '@cart/UPDATE_AMOUNT_SUCCESS', 33 | id, 34 | amount, 35 | }; 36 | } 37 | 38 | export function checkoutRequest() { 39 | return { 40 | type: '@cart/CHECKOUT', 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Container, Logo } from './styles'; 5 | import InputSearch from '~/components/InputSearch'; 6 | import logo from '~/assets/icons/logo.png'; 7 | 8 | export default function Header({ handleSearchSubmit }) { 9 | const [search, setSearch] = useState(''); 10 | 11 | return ( 12 | 13 | 14 | handleSearchSubmit(search)} 21 | search={search} 22 | setSearch={setSearch} 23 | /> 24 | 25 | ); 26 | } 27 | 28 | Header.propTypes = { 29 | handleSearchSubmit: PropTypes.func.isRequired, 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/InputSearch/index.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | import { RectButton } from 'react-native-gesture-handler'; 3 | import { FontAwesome } from '@expo/vector-icons/'; 4 | 5 | import { Container, TInput } from './styles'; 6 | 7 | function InputSearch( 8 | { handleClearValue, setSearch, search, icon, ...rest }, 9 | ref 10 | ) { 11 | return ( 12 | 13 | 14 | {search.length > 0 ? ( 15 | { 17 | setSearch(''); 18 | }} 19 | > 20 | 21 | 22 | ) : ( 23 | 24 | )} 25 | 26 | ); 27 | } 28 | 29 | export default forwardRef(InputSearch); 30 | -------------------------------------------------------------------------------- /src/components/ProfileButton/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { FontAwesome } from '@expo/vector-icons/'; 3 | import CustomText from '~/components/CustomText'; 4 | import colors from '~/styles/colors'; 5 | 6 | export const Container = styled.TouchableOpacity` 7 | padding: 20px; 8 | background: ${colors.white}; 9 | margin: ${props => (props.margin !== 0 ? `${props.margin}px 0` : `1px 0px`)}; 10 | flex-direction: row; 11 | align-items: center; 12 | justify-content: space-between; 13 | `; 14 | 15 | export const Title = styled(CustomText)` 16 | font-family: 'roboto-regular'; 17 | font-size: 15px; 18 | color: ${colors.grey}; 19 | `; 20 | 21 | export const Icon = styled(FontAwesome).attrs({ 22 | color: colors.grey, 23 | size: 22, 24 | })` 25 | width: 25px; 26 | `; 27 | 28 | export const Wrapper = styled.View` 29 | flex: 1; 30 | align-items: flex-start; 31 | margin-left: 20px; 32 | `; 33 | -------------------------------------------------------------------------------- /src/store/modules/cart/reducer.js: -------------------------------------------------------------------------------- 1 | import produce, { replaced } from 'immer'; 2 | 3 | export default function cart(state = [], action) { 4 | return produce(state, draft => { 5 | switch (action.type) { 6 | case '@cart/ADD_SUCCESS': { 7 | const { product } = action; 8 | draft.push(product); 9 | break; 10 | } 11 | case '@cart/REMOVE': { 12 | const productIndex = draft.findIndex(p => p.id === action.id); 13 | if (productIndex >= 0) { 14 | draft.splice(productIndex, 1); 15 | } 16 | break; 17 | } 18 | case '@cart/UPDATE_AMOUNT_SUCCESS': { 19 | const productIndex = draft.findIndex(p => p.id === action.id); 20 | if (productIndex >= 0) { 21 | draft[productIndex].amount = Number(action.amount); 22 | } 23 | break; 24 | } 25 | case '@cart/CHECKOUT': { 26 | console.tron.warn('entrou'); 27 | draft.splice([]); 28 | break; 29 | } 30 | default: 31 | return state; 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "entryPoint": "./src/App.js", 4 | "name": "Tech Store", 5 | "description": "Tech Store e-commerce", 6 | "slug": "tstore", 7 | "privacy": "public", 8 | "sdkVersion": "35.0.0", 9 | "platforms": ["ios", "android", "web"], 10 | "version": "1.0.0", 11 | "androidStatusBar": { 12 | "backgroundColor": "#FF6347", 13 | "barStyle": "light-content" 14 | }, 15 | "icon": "./src/assets/splash.png", 16 | "android": { 17 | "adaptiveIcon": { 18 | "backgroundColor": "#FF6347", 19 | "foregroundImage": "./src/assets/logo.png", 20 | "backgroundImage": "./src/assets/icon-bg.png" 21 | } 22 | }, 23 | "orientation": "portrait", 24 | "splash": { 25 | "image": "./src/assets/splash.png", 26 | "resizeMode": "contain", 27 | "backgroundColor": "#FF6347" 28 | }, 29 | "updates": { 30 | "fallbackToCacheTimeout": 0 31 | }, 32 | "assetBundlePatterns": ["**/*"], 33 | "ios": { 34 | "supportsTablet": true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/TabStateIcon/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { useSelector } from 'react-redux'; 4 | import { FontAwesome } from '@expo/vector-icons/'; 5 | 6 | import { Container, Circle, Number } from './styles'; 7 | 8 | export default function TabIcon({ name, tintColor }) { 9 | const cartSize = useSelector(state => state.cart.length); 10 | const favoriteSize = useSelector(state => state.favorite.length); 11 | 12 | useEffect(() => {}, [cartSize]); 13 | 14 | return ( 15 | 16 | 17 | {name === 'shopping-cart' 18 | ? cartSize > 0 && ( 19 | 20 | {cartSize} 21 | 22 | ) 23 | : favoriteSize > 0 && ( 24 | 25 | {favoriteSize} 26 | 27 | )} 28 | 29 | ); 30 | } 31 | 32 | TabIcon.propTypes = { 33 | name: PropTypes.string.isRequired, 34 | tintColor: PropTypes.string.isRequired, 35 | }; 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | }, 5 | extends: ['plugin:react/recommended', 'airbnb', 'prettier', 'prettier/react'], 6 | globals: { 7 | Atomics: 'readonly', 8 | SharedArrayBuffer: 'readonly', 9 | __DEV__: 'readonly', 10 | }, 11 | parser: 'babel-eslint', 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | plugins: ['react', 'prettier', 'react-hooks'], 20 | rules: { 21 | 'prettier/prettier': 'error', 22 | 'react-hooks/rules-of-hooks': 'error', 23 | 'react-hooks/exhaustive-deps': 'warn', 24 | 'react/jsx-filename-extension': [ 25 | 'warn', 26 | { 27 | extensions: ['.jsx', 'js'], 28 | }, 29 | ], 30 | 'react/jsx-props-no-spreading': 'off', 31 | 'import/prefer-default-export': 'off', 32 | 'no-param-reassign': 'off', 33 | 'no-console': ['error', { allow: ['tron'] }], 34 | }, 35 | settings: { 36 | 'import/resolver': { 37 | 'babel-plugin-root-import': { 38 | rootPathSuffix: 'src', 39 | }, 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/pages/SignIn/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import CustomText from '~/components/CustomText'; 3 | 4 | import colors from '~/styles/colors'; 5 | 6 | export const Container = styled.View` 7 | flex: 1; 8 | align-items: center; 9 | justify-content: center; 10 | padding: 15px; 11 | background: ${colors.primary}; 12 | `; 13 | 14 | export const Wrapper = styled.View` 15 | margin: 10px 0; 16 | align-items: flex-start; 17 | `; 18 | 19 | export const CustomImage = styled.Image.attrs({ 20 | resizeMode: 'contain', 21 | })` 22 | height: 100px; 23 | margin-bottom: 20px; 24 | `; 25 | 26 | export const Label = styled(CustomText)` 27 | font-family: 'roboto-bold'; 28 | font-size: 16; 29 | color: ${colors.white}; 30 | `; 31 | 32 | export const LoginButton = styled.TouchableOpacity` 33 | height: 50px; 34 | align-items: center; 35 | justify-content: center; 36 | align-self: stretch; 37 | background: rgba(255, 255, 255, 0.9); 38 | margin-top: 20px; 39 | border-radius: 4px; 40 | `; 41 | 42 | export const ButtonText = styled(CustomText)` 43 | font-family: 'roboto-bold'; 44 | font-size: 20; 45 | color: ${colors.primary}; 46 | `; 47 | -------------------------------------------------------------------------------- /src/pages/Profile/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import CustomText from '~/components/CustomText'; 3 | import colors from '~/styles/colors'; 4 | 5 | export const Container = styled.ScrollView.attrs({ 6 | showVerticalScroll: false, 7 | })` 8 | flex: 1; 9 | `; 10 | 11 | export const Name = styled(CustomText)` 12 | font-family: 'roboto-regular'; 13 | color: black; 14 | font-size: 18px; 15 | `; 16 | 17 | export const Email = styled(CustomText)` 18 | font-family: 'roboto-regular'; 19 | color: ${colors.grey}; 20 | font-size: 15px; 21 | `; 22 | 23 | export const ProfileAvatar = styled.Image` 24 | height: 40px; 25 | width: 40px; 26 | border-radius: 20px; 27 | `; 28 | 29 | export const HeaderProfile = styled.View` 30 | flex-direction: row; 31 | align-items: center; 32 | padding: 20px; 33 | background: ${colors.white}; 34 | `; 35 | 36 | export const ProfileInfo = styled.View` 37 | flex-direction: column; 38 | margin-left: 20px; 39 | `; 40 | 41 | export const SingOutButton = styled.TouchableOpacity` 42 | flex-direction: row; 43 | align-items: center; 44 | justify-content: center; 45 | background: ${colors.white}; 46 | padding: 20px; 47 | `; 48 | 49 | export const SignOutText = styled(CustomText)` 50 | font-family: 'roboto-regular'; 51 | color: rgba(255, 0, 0, 0.6); 52 | font-size: 20px; 53 | `; 54 | -------------------------------------------------------------------------------- /src/components/Rating/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Container, StarContainer, Label, StarIcon } from './styles'; 5 | 6 | export default function Rating({ 7 | size = 20, 8 | fontSize = 12, 9 | defaultRating = 0, 10 | defaultNumRating = 0, 11 | TextColor, 12 | ...rest 13 | }) { 14 | const defaultValue = defaultRating; 15 | const arrayStars = [1, 2, 3, 4, 5]; 16 | 17 | return ( 18 | 19 | 22 | 23 | {arrayStars.map((item, index) => ( 24 | 30 | ))} 31 | 32 | 33 | ); 34 | } 35 | 36 | Rating.propTypes = { 37 | size: PropTypes.number, 38 | fontSize: PropTypes.number, 39 | defaultRating: PropTypes.number, 40 | defaultNumRating: PropTypes.number, 41 | TextColor: PropTypes.string, 42 | }; 43 | 44 | Rating.defaultProps = { 45 | size: 20, 46 | fontSize: 12, 47 | defaultRating: 0, 48 | defaultNumRating: 0, 49 | TextColor: null, 50 | }; 51 | -------------------------------------------------------------------------------- /src/pages/Favorite/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StatusBar, Text, FlatList } from 'react-native'; 4 | import { useSelector } from 'react-redux'; 5 | 6 | import { Container, Wrapper, EmptyFavorite } from './styles'; 7 | import emptyFavorite from '~/assets/images/empty-favorite.png'; 8 | import TabStateIcon from '~/components/TabStateIcon'; 9 | import ProductItem from '~/components/ProductItem'; 10 | import colors from '~/styles/colors'; 11 | 12 | export default function Favorite({ navigation }) { 13 | const FavoriteData = useSelector(state => state.favorite); 14 | 15 | return ( 16 | 17 | 18 | {FavoriteData.length > 0 ? ( 19 | String(item.id)} 23 | renderItem={({ item }) => ( 24 | 25 | )} 26 | /> 27 | ) : ( 28 | 29 | 30 | 31 | )} 32 | 33 | ); 34 | } 35 | 36 | Favorite.navigationOptions = { 37 | tabBarColor: colors.primary, 38 | tabBarLabel: Favoritos, 39 | tabBarIcon: props => , 40 | }; 41 | 42 | Favorite.propTypes = { 43 | navigation: PropTypes.shape({ 44 | navigate: PropTypes.func.isRequired, 45 | }).isRequired, 46 | }; 47 | -------------------------------------------------------------------------------- /src/pages/Deals/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { LinearGradient } from 'expo-linear-gradient'; 3 | import { Dimensions } from 'react-native'; 4 | import LottieView from 'lottie-react-native'; 5 | import CustomText from '~/components/CustomText'; 6 | 7 | export const ContainerGradient = styled(LinearGradient).attrs({ 8 | colors: ['#ff6347', '#a40720', '#1c1919'], 9 | })` 10 | flex: 1; 11 | `; 12 | 13 | export const Container = styled.ScrollView.attrs({ 14 | showsVerticalScrollIndicator: false, 15 | })``; 16 | 17 | export const HeaderImage = styled.Image.attrs({ 18 | resizeMode: 'contain', 19 | })` 20 | flex: 1; 21 | height: ${Dimensions.get('window').width * 0.25}; 22 | width: ${Dimensions.get('window').width * 1}; 23 | `; 24 | 25 | export const Title = styled(CustomText)` 26 | font-family: 'roboto-bold'; 27 | font-size: 18px; 28 | text-align: center; 29 | color: #fff; 30 | margin-bottom: 5px; 31 | `; 32 | 33 | export const WrapperCount = styled.View` 34 | align-self: stretch; 35 | margin: 10px; 36 | `; 37 | 38 | export const HeaderCountDown = styled.View` 39 | flex-direction: column; 40 | align-items: center; 41 | justify-content: center; 42 | background: rgba(0, 0, 0, 0.5); 43 | border-radius: 4px; 44 | `; 45 | 46 | export const WrapperAnimation = styled.View` 47 | align-self: center; 48 | justify-content: center; 49 | `; 50 | 51 | export const LoadingAnimation = styled(LottieView).attrs({ 52 | resizeMode: 'contain', 53 | })` 54 | height: 80px; 55 | width: 80px; 56 | margin-top: 35px; 57 | `; 58 | -------------------------------------------------------------------------------- /src/pages/Home/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import CustomText from '~/components/CustomText'; 3 | import colors from '~/styles/colors'; 4 | import { ActivityIndicator, Dimensions } from 'react-native'; 5 | 6 | export const Container = styled.View` 7 | flex: 1; 8 | background: ${colors.background}; 9 | `; 10 | 11 | export const DepartmentContainer = styled.ScrollView.attrs({ 12 | horizontal: true, 13 | showsHorizontalScrollIndicator: false, 14 | })` 15 | flex-direction: row; 16 | align-self: center; 17 | max-height: 100px; 18 | margin: 0 5px; 19 | `; 20 | 21 | export const DepartmentItem = styled.View` 22 | flex-direction: column; 23 | margin: 0 5px; 24 | align-items: center; 25 | justify-content: center; 26 | `; 27 | 28 | export const DepartmentLogo = styled.TouchableOpacity` 29 | width: 60px; 30 | height: 60px; 31 | border-radius: 30px; 32 | background: ${colors.primary}; 33 | border-width: 3px; 34 | border-color: ${colors.white}; 35 | align-items: center; 36 | justify-content: center; 37 | `; 38 | 39 | export const DepartmentImage = styled.Image.attrs({ 40 | resizeMode: 'center', 41 | aspectRatio: 3 / 4, 42 | })` 43 | flex: 1; 44 | `; 45 | 46 | export const DepartmentText = styled(CustomText)` 47 | font-family: 'roboto-bold'; 48 | font-size: 11px; 49 | color: ${colors.text}; 50 | `; 51 | 52 | export const SalesContainer = styled.View` 53 | flex: 1; 54 | margin-bottom: 5px; 55 | `; 56 | 57 | export const SpinnerLoading = styled(ActivityIndicator).attrs({ 58 | color: 'tomato', 59 | alignSelf: 'center', 60 | marginTop: 20, 61 | size: 'large', 62 | })``; 63 | 64 | export const Wrapper = styled.View` 65 | flex: 1; 66 | align-items: center; 67 | justify-content: center; 68 | `; 69 | 70 | export const EmptyImage = styled.Image.attrs({ 71 | resizeMode: 'contain', 72 | })` 73 | width: ${Dimensions.get('window').width * 1}; 74 | `; 75 | -------------------------------------------------------------------------------- /src/components/Carousel/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Carousel, { Pagination } from 'react-native-snap-carousel'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import { Dimensions } from 'react-native'; 6 | 7 | import { Container, ContainerImage, ProductImage } from './styles'; 8 | 9 | export default function MyCarousel({ data, dataSize }) { 10 | const [entries, setEntries] = useState(1); 11 | const [activeSlide, setActiveSlide] = useState(0); 12 | const [fdata, setFdata] = useState([]); 13 | 14 | useEffect(() => { 15 | setFdata(data); 16 | setEntries(dataSize); 17 | }, [dataSize, setFdata, data]); 18 | 19 | const renderItem = ({ item }) => { 20 | return ( 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | return ( 28 | 29 | setActiveSlide(index)} 33 | sliderWidth={Dimensions.get('window').height} 34 | itemWidth={Dimensions.get('window').height * 0.5} 35 | /> 36 | 55 | 56 | ); 57 | } 58 | 59 | MyCarousel.propTypes = { 60 | data: PropTypes.arrayOf(PropTypes.string).isRequired, 61 | dataSize: PropTypes.number.isRequired, 62 | item: PropTypes.string, //eslint-disable-line 63 | }; 64 | 65 | MyCarousel.defaultProps = { 66 | item: null, 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "server": "json-server-relationship --watch db.json" 10 | }, 11 | "dependencies": { 12 | "@expo/vector-icons": "^10.0.6", 13 | "axios": "^0.19.0", 14 | "expo": "^35.0.0", 15 | "expo-font": "~7.0.0", 16 | "expo-linear-gradient": "~7.0.0", 17 | "immer": "^5.0.1", 18 | "intl": "^1.2.5", 19 | "json-server-relationship": "^0.14.5", 20 | "lottie-react-native": "^3.3.2", 21 | "prop-types": "^15.7.2", 22 | "react": "16.8.3", 23 | "react-dom": "16.8.3", 24 | "react-native": "https://github.com/expo/react-native/archive/sdk-35.0.0.tar.gz", 25 | "react-native-countdown-component": "^2.6.0", 26 | "react-native-gesture-handler": "~1.3.0", 27 | "react-native-paper": "^3.2.1", 28 | "react-native-root-toast": "^3.2.0", 29 | "react-native-snap-carousel": "^3.8.4", 30 | "react-native-web": "^0.11.7", 31 | "react-navigation": "^4.0.10", 32 | "react-navigation-material-bottom-tabs": "^2.1.5", 33 | "react-redux": "^7.1.3", 34 | "reactotron-react-js": "^3.3.7", 35 | "reactotron-react-native": "^4.0.2", 36 | "reactotron-redux": "^3.1.2", 37 | "reactotron-redux-saga": "^4.2.3", 38 | "redux": "^4.0.4", 39 | "redux-saga": "^1.1.3", 40 | "styled-components": "^4.4.1" 41 | }, 42 | "devDependencies": { 43 | "babel-eslint": "^10.0.3", 44 | "babel-plugin-root-import": "^6.4.1", 45 | "babel-preset-expo": "^7.1.0", 46 | "eslint": "^6.7.0", 47 | "eslint-config-airbnb": "^18.0.1", 48 | "eslint-config-prettier": "^6.7.0", 49 | "eslint-import-resolver-babel-plugin-root-import": "^1.1.1", 50 | "eslint-plugin-import": "^2.18.2", 51 | "eslint-plugin-jsx-a11y": "^6.2.3", 52 | "eslint-plugin-prettier": "^3.1.1", 53 | "eslint-plugin-react": "^7.16.0", 54 | "eslint-plugin-react-hooks": "^1.7.0", 55 | "prettier": "^1.19.1" 56 | }, 57 | "private": true 58 | } 59 | -------------------------------------------------------------------------------- /src/pages/Cart/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { Dimensions } from 'react-native'; 3 | import { RectButton } from 'react-native-gesture-handler'; 4 | import LottieView from 'lottie-react-native'; 5 | import CustomText from '~/components/CustomText'; 6 | import colors from '~/styles/colors'; 7 | 8 | export const Container = styled.View` 9 | flex: 1; 10 | padding: 5px; 11 | background: ${colors.background}; 12 | `; 13 | 14 | export const TotalWrapper = styled.View` 15 | flex-direction: row; 16 | align-items: baseline; 17 | justify-content: flex-end; 18 | line-height: 0; 19 | margin: 5px 0; 20 | padding: 5px; 21 | background: ${colors.white}; 22 | border-radius: 4px; 23 | `; 24 | 25 | export const TotalText = styled(CustomText)` 26 | font-family: 'roboto-bold'; 27 | font-size: 16px; 28 | color: ${colors.primary}; 29 | `; 30 | 31 | export const TotalPrice = styled(CustomText)` 32 | font-family: 'roboto-bold'; 33 | font-size: 28px; 34 | color: ${colors.text}; 35 | `; 36 | 37 | export const FinishButton = styled(RectButton)` 38 | background: ${colors.primary}; 39 | border-radius: 4px; 40 | padding: 12px 20px; 41 | align-items: center; 42 | justify-content: center; 43 | `; 44 | 45 | export const Wrapper = styled.View` 46 | flex: 1; 47 | align-items: center; 48 | justify-content: center; 49 | `; 50 | 51 | export const EmptyCartImage = styled.Image.attrs({ 52 | resizeMode: 'contain', 53 | })` 54 | width: ${Dimensions.get('window').width * 1}; 55 | `; 56 | 57 | export const BuyButton = styled(RectButton)` 58 | background: ${colors.primary}; 59 | border-radius: 4px; 60 | padding: 12px 20px; 61 | align-self: center; 62 | align-items: center; 63 | justify-content: center; 64 | width: 200px; 65 | margin-top: 20px; 66 | `; 67 | 68 | export const ButtonText = styled(CustomText)` 69 | font-family: 'roboto-bold'; 70 | font-size: 18px; 71 | color: ${colors.white}; 72 | `; 73 | 74 | export const WrapperAnimation = styled.View` 75 | align-items: center; 76 | justify-content: center; 77 | flex: 1; 78 | `; 79 | 80 | export const CheckoutAnimation = styled(LottieView).attrs({ 81 | resizeMode: 'contain', 82 | })` 83 | height: 150px; 84 | width: 150px; 85 | `; 86 | -------------------------------------------------------------------------------- /src/components/ProductItem/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import { RectButton } from 'react-native-gesture-handler'; 3 | import CustomText from '~/components/CustomText'; 4 | import BuyButton from '~/components/BuyButton'; 5 | 6 | import colors from '~/styles/colors'; 7 | 8 | export const ProductItem = styled.View` 9 | height: 150px; 10 | width: 100%; 11 | background-color: rgba(255, 255, 255, 1); 12 | margin-top: 5px; 13 | flex-direction: row; 14 | padding: 5px; 15 | `; 16 | 17 | export const LeftContent = styled.View` 18 | flex: 1; 19 | `; 20 | 21 | export const ProductImage = styled.Image.attrs({ 22 | resizeMode: 'contain', 23 | aspectRatio: 1, 24 | })``; 25 | 26 | export const RightContent = styled.View` 27 | flex: 2; 28 | border-left-width: 1px; 29 | border-color: rgba(0, 0, 0, 0.1); 30 | margin-left: 4px; 31 | padding: 0 5px 5px 5px; 32 | justify-content: space-between; 33 | `; 34 | 35 | export const Title = styled(CustomText).attrs({ 36 | numberOfLines: 2, 37 | })` 38 | font-family: 'roboto-bold'; 39 | font-size: 14px; 40 | color: ${colors.text}; 41 | `; 42 | 43 | export const Price = styled(CustomText)` 44 | font-family: 'roboto-bold'; 45 | font-size: 22px; 46 | color: ${colors.primary}; 47 | letter-spacing: 1; 48 | `; 49 | 50 | export const PriceInfo = styled(CustomText)` 51 | font-family: 'roboto-regular'; 52 | font-size: 18px; 53 | color: ${colors.text}; 54 | `; 55 | 56 | export const PriceContainer = styled.View` 57 | flex-direction: row; 58 | align-self: stretch; 59 | align-items: center; 60 | justify-content: center; 61 | `; 62 | 63 | export const ProductActions = styled.View` 64 | flex-direction: row; 65 | justify-content: flex-end; 66 | `; 67 | 68 | export const AddButton = styled(BuyButton)` 69 | height: 50px; 70 | `; 71 | 72 | export const FavoriteButton = styled(RectButton)` 73 | height: 26px; 74 | width: 26px; 75 | border-radius: 13px; 76 | background: rgba(255, 255, 255, 0.8); 77 | position: absolute; 78 | align-items: center; 79 | justify-content: center; 80 | align-self: flex-start; 81 | top: -3px; 82 | left: 5px; 83 | `; 84 | 85 | export const RatingWrapper = styled.View` 86 | background: rgba(255, 255, 255, 0.8); 87 | align-items: center; 88 | top: -18; 89 | `; 90 | -------------------------------------------------------------------------------- /src/store/modules/cart/saga.js: -------------------------------------------------------------------------------- 1 | import { call, select, put, all, takeLatest } from 'redux-saga/effects'; 2 | import Toast from 'react-native-root-toast'; 3 | 4 | import api from '~/services/api'; 5 | 6 | import { addToCartSuccess, updateAmountSuccess } from './actions'; 7 | 8 | function* addToCart({ id }) { 9 | const productExists = yield select(state => 10 | state.cart.find(p => p.id === id) 11 | ); 12 | 13 | const stock = yield call(api.get, `/stock/${id}`); 14 | const stockAmount = stock.data.amount; 15 | const currentAmount = productExists ? productExists.amount : 0; 16 | const amount = currentAmount + 1; 17 | 18 | if (amount > stockAmount) { 19 | Toast.show('Quantidade indisponível no estoque', { 20 | duration: Toast.durations.SHORT, 21 | position: -75, 22 | backgroundColor: 'red', 23 | shadow: true, 24 | hideOnPress: true, 25 | }); 26 | 27 | return; 28 | } 29 | 30 | Toast.show('Produto adicionado ao carrinho', { 31 | duration: Toast.durations.SHORT, 32 | position: -75, 33 | backgroundColor: 'green', 34 | shadow: true, 35 | hideOnPress: true, 36 | }); 37 | 38 | if (productExists) { 39 | yield put(updateAmountSuccess(id, amount)); 40 | } else { 41 | const response = yield call(api.get, `/products/${id}`); 42 | 43 | const data = { 44 | ...response.data, 45 | amount: 1, 46 | finalPrice: Number( 47 | response.data.price * 48 | Number(response.data.discount > 0 ? response.data.discount : 1) 49 | ), 50 | }; 51 | 52 | yield put(addToCartSuccess(data)); 53 | } 54 | } 55 | 56 | function* updateAmount({ id, amount }) { 57 | if (amount <= 0) return; 58 | 59 | const { data } = yield call(api.get, `stock/${id}`); 60 | const stockAmount = data.amount; 61 | 62 | if (amount > stockAmount) { 63 | Toast.show('Quantidade indisponível no estoque', { 64 | duration: Toast.durations.SHORT, 65 | position: -75, 66 | backgroundColor: 'red', 67 | shadow: true, 68 | hideOnPress: true, 69 | }); 70 | return; 71 | } 72 | 73 | yield put(updateAmountSuccess(id, amount)); 74 | } 75 | 76 | export default all([ 77 | takeLatest('@cart/ADD_REQUEST', addToCart), 78 | takeLatest('@cart/UPDATE_AMOUNT_REQUEST', updateAmount), 79 | ]); 80 | -------------------------------------------------------------------------------- /src/components/ProductCart/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import CustomText from '~/components/CustomText'; 3 | import colors from '~/styles/colors'; 4 | import { RectButton } from 'react-native-gesture-handler'; 5 | 6 | export const ProductItem = styled.View` 7 | height: 200px; 8 | width: 100%; 9 | background-color: rgba(255, 255, 255, 1); 10 | margin-top: 5px; 11 | `; 12 | 13 | export const ProductContent = styled.View` 14 | flex: 1; 15 | flex-direction: row; 16 | padding: 5px; 17 | `; 18 | 19 | export const LeftContent = styled.View` 20 | flex: 1; 21 | justify-content: space-evenly; 22 | `; 23 | 24 | export const ProductImage = styled.Image.attrs({ 25 | resizeMode: 'contain', 26 | aspectRatio: 1, 27 | })``; 28 | 29 | export const RightContent = styled.View` 30 | flex: 2; 31 | border-left-width: 1px; 32 | border-color: rgba(0, 0, 0, 0.1); 33 | margin-left: 4px; 34 | padding: 0 5px 5px 5px; 35 | justify-content: space-evenly; 36 | `; 37 | 38 | export const Description = styled(CustomText).attrs({ 39 | numberOfLines: 2, 40 | })` 41 | font-family: 'roboto-bold'; 42 | font-size: 14px; 43 | color: #737373; 44 | `; 45 | 46 | export const Price = styled(CustomText)` 47 | font-family: 'roboto-bold'; 48 | font-size: 20px; 49 | color: ${colors.primary}; 50 | letter-spacing: 1; 51 | `; 52 | 53 | export const Title = styled(CustomText)` 54 | font-family: 'roboto-regular'; 55 | font-size: 12px; 56 | color: ${colors.grey}; 57 | `; 58 | 59 | export const Wrapper = styled.View` 60 | align-self: stretch; 61 | align-items: center; 62 | justify-content: space-between; 63 | `; 64 | 65 | export const WrapperActions = styled.View` 66 | flex-direction: row; 67 | align-items: center; 68 | justify-content: space-evenly; 69 | `; 70 | 71 | export const ProductActions = styled.View` 72 | flex-direction: row; 73 | justify-content: flex-end; 74 | `; 75 | 76 | export const AmountText = styled(CustomText)` 77 | font-family: 'roboto-bold'; 78 | font-size: 18px; 79 | color: ${colors.grey}; 80 | `; 81 | 82 | export const IconButton = styled(RectButton)` 83 | height: 30px; 84 | width: 30px; 85 | border-radius: 15px; 86 | align-items: center; 87 | justify-content: center; 88 | background: ${colors.primary}; 89 | margin: 5px 10px; 90 | `; 91 | -------------------------------------------------------------------------------- /src/pages/Deals/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FlatList } from 'react-native'; 4 | import CountDown from 'react-native-countdown-component'; 5 | 6 | import { 7 | Container, 8 | HeaderCountDown, 9 | ContainerGradient, 10 | WrapperCount, 11 | Title, 12 | WrapperAnimation, 13 | LoadingAnimation, 14 | } from './styles'; 15 | 16 | import ProductItem from '~/components/ProductItem'; 17 | import loadingAnimation from '~/assets/animations/loading.json'; 18 | 19 | import api from '~/services/api'; 20 | 21 | export default function Deals({ navigation }) { 22 | const [loading, setLoading] = useState(true); 23 | const [products, setProduts] = useState([]); 24 | 25 | useEffect(() => { 26 | async function loadProducts() { 27 | const response = await api.get('products'); 28 | const discountProducts = response.data.filter(p => p.discount > 0); 29 | setProduts(discountProducts); 30 | setLoading(false); 31 | } 32 | loadProducts(); 33 | }, []); 34 | 35 | return ( 36 | 37 | 38 | 39 | Oferta por tempo limitado 40 | 54 | 55 | 56 | 57 | {loading ? ( 58 | 59 | 60 | 61 | ) : ( 62 | 63 | String(item.id)} 67 | renderItem={({ item }) => ( 68 | 69 | )} 70 | /> 71 | 72 | )} 73 | 74 | ); 75 | } 76 | 77 | Deals.propTypes = { 78 | navigation: PropTypes.shape({ 79 | navigate: PropTypes.func.isRequired, 80 | }).isRequired, 81 | }; 82 | -------------------------------------------------------------------------------- /src/pages/Profile/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Text } from 'react-native'; 4 | import { useSelector, useDispatch } from 'react-redux'; 5 | import { FontAwesome } from '@expo/vector-icons/'; 6 | 7 | import * as ProfileActions from '~/store/modules/profile/actions'; 8 | 9 | import { 10 | Container, 11 | HeaderProfile, 12 | ProfileInfo, 13 | Name, 14 | Email, 15 | ProfileAvatar, 16 | SingOutButton, 17 | SignOutText, 18 | } from './styles'; 19 | 20 | import ProfileButton from '~/components/ProfileButton'; 21 | import TabIcon from '~/components/TabIcon'; 22 | import colors from '~/styles/colors'; 23 | 24 | export default function Profile({ navigation }) { 25 | const dispatch = useDispatch(); 26 | const UserProfile = useSelector(state => state.profile); 27 | 28 | function handleSignOut() { 29 | dispatch(ProfileActions.SignOut()); 30 | navigation.navigate('SignIn'); 31 | } 32 | 33 | return ( 34 | 35 | 36 | 41 | 42 | {UserProfile.name} 43 | {UserProfile.email} 44 | 45 | 46 | 47 | Mensagens 48 | 49 | Pedidos 50 | Dados Pessoais 51 | Endereços 52 | Cartões de crédito 53 | 54 | Senha de acesso 55 | 56 | 57 | Sair 58 | 64 | 65 | 66 | ); 67 | } 68 | 69 | Profile.navigationOptions = { 70 | tabBarLabel: Perfil, 71 | tabBarIcon: props => , 72 | tabBarColor: `${colors.primary}`, 73 | }; 74 | 75 | Profile.propTypes = { 76 | navigation: PropTypes.shape({ 77 | navigate: PropTypes.func.isRequired, 78 | }).isRequired, 79 | }; 80 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { createAppContainer, createSwitchNavigator } from 'react-navigation'; 4 | import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs'; 5 | 6 | import { Text } from 'react-native'; 7 | import TabIcon from '~/components/TabIcon'; 8 | import TabStateIcon from '~/components/TabStateIcon'; 9 | 10 | import Home from '~/pages/Home'; 11 | import Deals from '~/pages/Deals'; 12 | import Favorite from '~/pages/Favorite'; 13 | import Profile from '~/pages/Profile'; 14 | import Cart from '~/pages/Cart'; 15 | import SignIn from '~/pages/SignIn'; 16 | import Product from '~/components/Product'; 17 | 18 | import colors from '~/styles/colors'; 19 | 20 | const HomeRoute = createSwitchNavigator( 21 | { 22 | Home, 23 | Product, 24 | }, 25 | { 26 | initialRouteName: 'Home', 27 | navigationOptions: { 28 | tabBarOnPress: ({ navigation }) => { 29 | navigation.navigate('Home'); 30 | }, 31 | tabBarColor: colors.primary, 32 | tabBarLabel: Início, 33 | tabBarIcon: props => , 34 | }, 35 | } 36 | ); 37 | 38 | const DealsRoute = createSwitchNavigator( 39 | { 40 | Deals, 41 | Product, 42 | }, 43 | { 44 | initialRouteName: 'Deals', 45 | navigationOptions: { 46 | tabBarOnPress: ({ navigation }) => { 47 | navigation.navigate('Deals'); 48 | }, 49 | tabBarColor: '#1C1919', 50 | tabBarLabel: Ofertas, 51 | tabBarIcon: props => , 52 | }, 53 | } 54 | ); 55 | 56 | const CartRoute = createSwitchNavigator( 57 | { 58 | Cart, 59 | }, 60 | { 61 | initialRouteName: 'Cart', 62 | navigationOptions: { 63 | tabBarColor: colors.primary, 64 | tabBarLabel: Carrinho, 65 | tabBarIcon: props => , 66 | }, 67 | } 68 | ); 69 | 70 | const BottomRoutes = createMaterialBottomTabNavigator( 71 | { 72 | HomeRoute, 73 | DealsRoute, 74 | Favorite, 75 | CartRoute, 76 | Profile, 77 | }, 78 | { 79 | initialRouteName: 'HomeRoute', 80 | activeColor: '#fff', 81 | inactiveColor: 'rgba(255,255,255,0.5)', 82 | labeled: true, 83 | } 84 | ); 85 | 86 | const EntryPoint = createSwitchNavigator( 87 | { 88 | SignIn, 89 | BottomRoutes, 90 | }, 91 | { 92 | initialRouteName: 'SignIn', 93 | } 94 | ); 95 | 96 | const Routes = createAppContainer(EntryPoint); 97 | 98 | export default Routes; 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | TechStore 4 |
5 |
6 | Tech Store E-commerce 7 |

8 | 9 |

10 | Application created for the purpose of study, an ecommerce store 11 |

12 | 13 |

14 | 15 | License MIT 16 | 17 |

18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 | ## Features 33 | 34 | - ⚛️ **React Native** — A lib that provides a way to create native apps for Android and iOS 35 | - :arrow_up_small: **Expo** - Framework and a platform for universal React applications. 36 | - 💹 **Json Server with Relationships** — Fake rest api with zero coding 37 | - ♻ **Redux with Redux Saga** — State management with middleware 38 | - 💅 **Styled Components** — styled-components 39 | - 🌸 **Reactotron** - Helps debugging process 40 | - 💖 **Lint** — ESlint/Prettier/Editor Config 41 | 42 | ## Getting started 43 | 44 | 1. Clone this repo using `git clone https://github.com/mfzaguiar/e-commerce-app.git` 45 | 2. Move yourself to the appropriate directory: `cd e-commerce-app`
46 | 3. Run `yarn` to install dependencies
47 | 48 | ### Getting started with the json server with relationship 49 | 50 | 1. Open new terminal 51 | 2. Run: `yarn server` 52 | 53 | ### Getting started with the mobile app 54 | 55 | 1. Run `expo start` 56 | 57 | Note: If you choose to start the mobile app in the android. 58 | 59 | Change the baseURL in file api.js located in services/api.js 60 | 61 | - Genymotion emulator: 62 | `http://10.0.3.2:3000` 63 | - Android emulator: 64 | `http://10.0.2.2:3000` 65 | - Android device: 66 | `http://'YOUR-LOCAL-IP':3000` 67 | 68 | If you having trouble with android device, I recommend you to use the 69 | [**Ngrock**](https://ngrok.com/) 70 | 71 | ## Login in app 72 | 73 | 1. Use the follow credentials to login

74 | email: admin@hotmail.com
75 | password: 12345

or

76 | email: teste@hotmail.com
77 | password: 123 78 | 79 | ## License 80 | 81 | This project is licensed under the MIT License - see the [LICENSE](https://opensource.org/licenses/MIT) page for details. 82 | 83 | ## Author 84 | 85 | - [**Matheus Aguiar**](https://www.linkedin.com/in/mfzaguiar/) 86 | -------------------------------------------------------------------------------- /src/pages/Cart/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FlatList } from 'react-native'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { formatPrice } from '~/util/format'; 6 | 7 | import { 8 | Container, 9 | TotalWrapper, 10 | TotalText, 11 | TotalPrice, 12 | FinishButton, 13 | Wrapper, 14 | BuyButton, 15 | ButtonText, 16 | EmptyCartImage, 17 | WrapperAnimation, 18 | CheckoutAnimation, 19 | } from './styles'; 20 | import ProductCart from '~/components/ProductCart'; 21 | import emptyCart from '~/assets/images/empty-cart.png'; 22 | import checkout from '~/assets/animations/checkout.json'; 23 | 24 | import * as CartActions from '~/store/modules/cart/actions'; 25 | 26 | export default function Cart({ navigation }) { 27 | const dispatch = useDispatch(); 28 | 29 | const [isCheckout, setIsCheckout] = useState(false); 30 | 31 | const cart = useSelector(state => 32 | state.cart.map(product => ({ 33 | ...product, 34 | subtotal: formatPrice(product.finalPrice * product.amount), 35 | })) 36 | ); 37 | 38 | const total = useSelector(state => 39 | formatPrice( 40 | state.cart.reduce((totalSum, product) => { 41 | return totalSum + product.finalPrice * product.amount; 42 | }, 0) 43 | ) 44 | ); 45 | 46 | function handleCheckout() { 47 | dispatch(CartActions.checkoutRequest()); 48 | 49 | setIsCheckout(true); 50 | setTimeout(() => { 51 | setIsCheckout(false); 52 | }, 1500); 53 | } 54 | 55 | return ( 56 | 57 | {isCheckout && ( 58 | 59 | 60 | 61 | )} 62 | {cart.length > 0 ? ( 63 | <> 64 | String(item.id)} 68 | renderItem={({ item }) => } 69 | /> 70 | 71 | 72 | TOTAL 73 | {total} 74 | 75 | 76 | handleCheckout()}> 77 | Finalizar pedido 78 | 79 | 80 | ) : ( 81 | !isCheckout && ( 82 | 83 | 84 | navigation.navigate('HomeRoute')}> 85 | Ir às compras 86 | 87 | 88 | ) 89 | )} 90 | 91 | ); 92 | } 93 | 94 | Cart.propTypes = { 95 | navigation: PropTypes.shape({ 96 | navigate: PropTypes.func.isRequired, 97 | }).isRequired, 98 | }; 99 | -------------------------------------------------------------------------------- /src/assets/animations/loading.json: -------------------------------------------------------------------------------- 1 | {"v":"5.1.3","fr":29.9700012207031,"ip":30.0000012219251,"op":48.0000019550801,"w":300,"h":169,"nm":"Final Loop","ddd":0,"assets":[{"id":"comp_5","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[34,84,0],"e":[266,84,0],"to":[38.6666679382324,0,0],"ti":[-38.6666679382324,0,0]},{"t":60.0000024438501}],"ix":2},"a":{"a":0,"k":[4.249,8.249,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.745,0.745,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"n":["0p745_1_0p167_0p167","0p745_1_0p167_0p167","0p667_1_0p167_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.237,0.237,0.333],"y":[0,0,0]},"n":["0p833_0p833_0p237_0","0p833_0p833_0p237_0","0p833_1_0p333_0"],"t":30,"s":[100,100,100],"e":[0,0,100]},{"t":60.0000024438501}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.087,0],[0,-18.087],[-18.087,0],[0,18.087]],"o":[[-18.087,0],[0,18.087],[18.087,0],[0,-18.087]],"v":[[0,-32.749],[-32.749,0],[0,32.749],[32.749,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[4.249,8.249],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Balls","refId":"comp_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,84.5,0],"ix":2},"a":{"a":0,"k":[150,84.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":300,"h":169,"ip":36.0000014663101,"op":936.000038124062,"st":36.0000014663101,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Balls","refId":"comp_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,84.5,0],"ix":2},"a":{"a":0,"k":[150,84.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":300,"h":169,"ip":18.000000733155,"op":918.000037390907,"st":18.000000733155,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Balls","refId":"comp_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,84.5,0],"ix":2},"a":{"a":0,"k":[150,84.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":300,"h":169,"ip":-18.000000733155,"op":882.000035924596,"st":-18.000000733155,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Balls","refId":"comp_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,84.5,0],"ix":2},"a":{"a":0,"k":[150,84.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":300,"h":169,"ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /src/components/Product/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import CustomText from '~/components/CustomText'; 3 | import BuyButton from '~/components/BuyButton'; 4 | import colors from '~/styles/colors'; 5 | import { RectButton } from 'react-native-gesture-handler'; 6 | import Rating from '~/components/Rating'; 7 | 8 | export const Container = styled.ScrollView.attrs({ 9 | showsVerticalScrollIndicator: false, 10 | })` 11 | flex: 1; 12 | background-color: ${colors.background}; 13 | `; 14 | 15 | export const Header = styled.View` 16 | flex-direction: row; 17 | align-items: center; 18 | justify-content: space-between; 19 | background: ${colors.white}; 20 | padding: 10px 15px 0 15px; 21 | `; 22 | 23 | export const Name = styled(CustomText)` 24 | font-family: 'roboto-bold'; 25 | font-size: 18px; 26 | color: ${colors.text}; 27 | letter-spacing: 1; 28 | padding: 10px; 29 | text-align: justify; 30 | `; 31 | 32 | export const Description = styled(CustomText)` 33 | font-family: 'roboto-regular'; 34 | line-height: 25; 35 | letter-spacing: 1.2; 36 | font-size: 16px; 37 | color: ${colors.text}; 38 | padding: 10px; 39 | text-align: justify; 40 | `; 41 | 42 | export const PriceOriginal = styled(CustomText)` 43 | font-family: 'roboto-bold'; 44 | font-size: 22px; 45 | color: ${colors.white}; 46 | letter-spacing: 2; 47 | `; 48 | 49 | export const PriceContainer = styled.View` 50 | flex-direction: column; 51 | flex: 1; 52 | align-items: center; 53 | justify-content: center; 54 | background: ${colors.darkgrey}; 55 | align-self: stretch; 56 | `; 57 | 58 | export const Price = styled(CustomText)` 59 | font-family: 'roboto-regular'; 60 | font-size: 12px; 61 | color: ${colors.white}; 62 | letter-spacing: 1; 63 | `; 64 | 65 | export const PriceDiscount = styled(CustomText)` 66 | font-family: 'roboto-bold'; 67 | font-size: 22px; 68 | color: ${colors.primary}; 69 | letter-spacing: 2; 70 | `; 71 | 72 | export const ProductInfo = styled.View` 73 | /* background: #eee; */ 74 | `; 75 | 76 | export const ProductHeader = styled.View` 77 | flex-direction: row; 78 | align-items: center; 79 | justify-content: space-between; 80 | `; 81 | 82 | export const ProductFinish = styled.View` 83 | height: 70px; 84 | flex-direction: row; 85 | align-self: stretch; 86 | align-items: center; 87 | justify-content: space-between; 88 | `; 89 | 90 | export const Actions = styled.View` 91 | flex-direction: row; 92 | margin-right: 10px; 93 | `; 94 | 95 | export const AddButton = styled(BuyButton)` 96 | flex: 1; 97 | border-radius: 0; 98 | `; 99 | 100 | export const FavoriteButton = styled(RectButton)` 101 | height: 40px; 102 | width: 40px; 103 | border-radius: 20px; 104 | background: rgba(255, 255, 255, 0.3); 105 | align-items: center; 106 | justify-content: center; 107 | `; 108 | 109 | export const CustomRating = styled(Rating)` 110 | padding: 0px 0 5px 10px; 111 | `; 112 | -------------------------------------------------------------------------------- /src/components/ProductCart/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { useDispatch } from 'react-redux'; 4 | import { TouchableOpacity } from 'react-native-gesture-handler'; 5 | import { FontAwesome } from '@expo/vector-icons/'; 6 | 7 | import * as CartActions from '~/store/modules/cart/actions'; 8 | import * as FavoriteActions from '~/store/modules/favorite/actions'; 9 | 10 | import { 11 | ProductItem, 12 | ProductContent, 13 | LeftContent, 14 | ProductImage, 15 | RightContent, 16 | Description, 17 | Wrapper, 18 | WrapperActions, 19 | Price, 20 | Title, 21 | AmountText, 22 | IconButton, 23 | } from './styles'; 24 | 25 | export default function ProductCart({ item }) { 26 | const dispatch = useDispatch(); 27 | const productImage = { ...item.images }; 28 | 29 | function handleDeleteProduct(id) { 30 | dispatch(CartActions.removeFromCart(id)); 31 | } 32 | 33 | function increment(product) { 34 | dispatch(CartActions.updateAmountRequest(product.id, product.amount + 1)); 35 | } 36 | 37 | function decrement(product) { 38 | dispatch(CartActions.updateAmountRequest(product.id, product.amount - 1)); 39 | } 40 | 41 | function handleFavorite(product) { 42 | dispatch(FavoriteActions.toggleFavorite(product)); 43 | } 44 | 45 | return ( 46 | 47 | 48 | 49 | 54 | 55 | handleFavorite(item)}> 56 | 57 | 58 | handleDeleteProduct(item.id)}> 59 | 60 | 61 | 62 | 63 | 64 | {item.title} 65 | 66 | Quantidade 67 | 68 | decrement(item)}> 69 | 70 | 71 | {item.amount} 72 | increment(item)}> 73 | 74 | 75 | 76 | 77 | 78 | Subtotal 79 | {item.subtotal} 80 | 81 | 82 | 83 | 84 | ); 85 | } 86 | 87 | ProductCart.propTypes = { 88 | item: PropTypes.shape({ 89 | id: PropTypes.number.isRequired, 90 | images: PropTypes.arrayOf(PropTypes.string).isRequired, 91 | title: PropTypes.string.isRequired, 92 | amount: PropTypes.number.isRequired, 93 | subtotal: PropTypes.string.isRequired, 94 | }).isRequired, 95 | }; 96 | -------------------------------------------------------------------------------- /src/pages/SignIn/index.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ActivityIndicator } from 'react-native'; 4 | import { useDispatch } from 'react-redux'; 5 | import Toast from 'react-native-root-toast'; 6 | 7 | import { 8 | Container, 9 | Wrapper, 10 | CustomImage, 11 | Label, 12 | LoginButton, 13 | ButtonText, 14 | } from './styles'; 15 | 16 | import * as ProfileActions from '~/store/modules/profile/actions'; 17 | import api from '~/services/api'; 18 | 19 | import TInput from '~/components/Input'; 20 | import logo from '~/assets/icons/logo.png'; 21 | 22 | export default function SignIn({ navigation }) { 23 | const dispatch = useDispatch(); 24 | const emailRef = useRef(); 25 | const passwordRef = useRef(); 26 | 27 | const [loading, setLoading] = useState(false); 28 | const [email, setEmail] = useState(''); 29 | const [password, setPassword] = useState(''); 30 | 31 | async function handleLogin() { 32 | setLoading(true); 33 | const response = await api.get('profile', { 34 | params: { 35 | q: `${email}`, 36 | }, 37 | }); 38 | setLoading(false); 39 | setTimeout(() => { 40 | if (response.data.length > 0) { 41 | if (String(response.data[0].password) === String(password)) { 42 | const user = { 43 | email: response.data[0].email, 44 | name: response.data[0].name, 45 | }; 46 | dispatch(ProfileActions.SignIn(user)); 47 | navigation.navigate('BottomRoutes'); 48 | } else { 49 | setPassword(''); 50 | Toast.show('Email ou senha inválidos', { 51 | duration: Toast.durations.SHORT, 52 | position: 35, 53 | backgroundColor: 'red', 54 | shadow: true, 55 | hideOnPress: true, 56 | }); 57 | } 58 | } 59 | }, 50); 60 | } 61 | 62 | return ( 63 | 64 | 65 | 66 | 67 | passwordRef.current.focus()} 76 | value={email} 77 | onChangeText={setEmail} 78 | /> 79 | 80 | 81 | 82 | 92 | 93 | { 95 | handleLogin(); 96 | }} 97 | > 98 | {!loading ? ( 99 | Login 100 | ) : ( 101 | 102 | )} 103 | 104 | 105 | ); 106 | } 107 | 108 | SignIn.propTypes = { 109 | navigation: PropTypes.shape({ 110 | navigate: PropTypes.func.isRequired, 111 | }).isRequired, 112 | }; 113 | -------------------------------------------------------------------------------- /src/components/ProductItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import { FontAwesome } from '@expo/vector-icons/'; 5 | 6 | import * as CartActions from '~/store/modules/cart/actions'; 7 | import * as FavoriteActions from '~/store/modules/favorite/actions'; 8 | import { formatPrice } from '~/util/format'; 9 | import Rating from '~/components/Rating'; 10 | import Discount from '~/components/Discount'; 11 | 12 | import { 13 | ProductItem, 14 | LeftContent, 15 | ProductImage, 16 | RightContent, 17 | Title, 18 | PriceContainer, 19 | Price, 20 | PriceInfo, 21 | AddButton, 22 | FavoriteButton, 23 | RatingWrapper, 24 | } from './styles'; 25 | 26 | export default function ProdItem({ navigation, item }) { 27 | const [favorited, setFavorited] = useState(false); 28 | const dispatch = useDispatch(); 29 | 30 | const favoritedItem = useSelector(state => 31 | state.favorite.filter(f => f.id === item.id) 32 | ); 33 | 34 | useEffect(() => { 35 | if (favoritedItem >= 0) { 36 | setFavorited(true); 37 | } else { 38 | setFavorited(false); 39 | } 40 | }, [favoritedItem]); 41 | 42 | function handleAddProduct(id) { 43 | dispatch(CartActions.addToCartRequest(id)); 44 | } 45 | 46 | function handleFavorite(product) { 47 | dispatch(FavoriteActions.toggleFavorite(product)); 48 | } 49 | 50 | return ( 51 | 52 | 53 | 58 | 59 | 65 | 66 | 67 | handleFavorite(item)}> 68 | {!favorited ? ( 69 | 70 | ) : ( 71 | 72 | )} 73 | 74 | {item.discount > 0 && {item.discount}} 75 | 76 | 77 | 79 | navigation.navigate('Product', { 80 | product: item, 81 | keyScreen: navigation.state.key, 82 | }) 83 | } 84 | > 85 | {item.title} 86 | 87 | 88 | 89 | 90 | {formatPrice(item.price * (item.discount > 0 ? item.discount : 1))} 91 | à vista 92 | 93 | 94 | handleAddProduct(item.id)}> 95 | Adicionar 96 | 97 | 98 | 99 | ); 100 | } 101 | 102 | ProdItem.propTypes = { 103 | navigation: PropTypes.shape({ 104 | navigate: PropTypes.func.isRequired, 105 | state: PropTypes.object.isRequired, 106 | }).isRequired, 107 | item: PropTypes.shape({ 108 | id: PropTypes.number.isRequired, 109 | images: PropTypes.arrayOf(PropTypes.string).isRequired, 110 | title: PropTypes.string.isRequired, 111 | rating: PropTypes.number.isRequired, 112 | numrating: PropTypes.number.isRequired, 113 | discount: PropTypes.number.isRequired, 114 | price: PropTypes.number.isRequired, 115 | }).isRequired, 116 | }; 117 | -------------------------------------------------------------------------------- /src/components/Product/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { TouchableOpacity } from 'react-native-gesture-handler'; 4 | import { FontAwesome } from '@expo/vector-icons/'; 5 | import PropTypes from 'prop-types'; 6 | 7 | import { formatPrice } from '~/util/format'; 8 | 9 | import * as FavoriteActions from '~/store/modules/favorite/actions'; 10 | import * as CartActions from '~/store/modules/cart/actions'; 11 | 12 | import { 13 | Header, 14 | Container, 15 | ProductInfo, 16 | ProductHeader, 17 | Name, 18 | PriceOriginal, 19 | PriceContainer, 20 | Price, 21 | PriceDiscount, 22 | ProductFinish, 23 | Description, 24 | AddButton, 25 | FavoriteButton, 26 | CustomRating, 27 | } from './styles'; 28 | 29 | import Carousel from '~/components/Carousel'; 30 | 31 | export default function Product({ navigation }) { 32 | const product = navigation.getParam('product'); 33 | const { params } = navigation.state; 34 | 35 | const [favorited, setFavorited] = useState(false); 36 | const dispatch = useDispatch(); 37 | 38 | const favoritedItem = useSelector(state => 39 | state.favorite.filter(f => f.id === product.id) 40 | ); 41 | 42 | useEffect(() => { 43 | if (favoritedItem >= 0) { 44 | setFavorited(true); 45 | } else { 46 | setFavorited(false); 47 | } 48 | }, [favoritedItem]); 49 | 50 | function handleAddProduct(id) { 51 | dispatch(CartActions.addToCartRequest(id)); 52 | } 53 | 54 | function handleFavorite(prod) { 55 | dispatch(FavoriteActions.toggleFavorite(prod)); 56 | } 57 | 58 | return ( 59 | 60 |
61 | navigation.navigate(params.keyScreen)}> 62 | 63 | 64 | 65 | handleFavorite(product)}> 66 | {!favorited ? ( 67 | 72 | ) : ( 73 | 74 | )} 75 | 76 | 77 |
78 | 82 | 83 | {product.title} 84 | 92 | 93 | 94 | {product.discount ? ( 95 | 96 | DE {formatPrice(product.price)} POR 97 | 98 | {formatPrice(product.price * product.discount)} 99 | 100 | 101 | ) : ( 102 | 103 | apenas 104 | {formatPrice(product.price)} 105 | 106 | )} 107 | 108 | handleAddProduct(product.id)}> 109 | Adicionar 110 | 111 | 112 | {product.description} 113 | 114 |
115 | ); 116 | } 117 | 118 | Product.propTypes = { 119 | navigation: PropTypes.shape({ 120 | navigate: PropTypes.func.isRequired, 121 | getParam: PropTypes.func.isRequired, 122 | state: PropTypes.object.isRequired, 123 | }).isRequired, 124 | }; 125 | -------------------------------------------------------------------------------- /src/pages/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useCallback, useState, useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FlatList } from 'react-native'; 4 | 5 | import { 6 | Container, 7 | DepartmentContainer, 8 | DepartmentItem, 9 | DepartmentLogo, 10 | DepartmentImage, 11 | DepartmentText, 12 | SalesContainer, 13 | SpinnerLoading, 14 | Wrapper, 15 | EmptyImage, 16 | } from './styles'; 17 | 18 | import Header from '~/components/Header'; 19 | import ProductItem from '~/components/ProductItem'; 20 | import api from '~/services/api'; 21 | import all from '~/assets/icons/online-store.png'; 22 | import drone from '~/assets/icons/drone.png'; 23 | import tv from '~/assets/icons/tv.png'; 24 | import smartphone from '~/assets/icons/smartphone.png'; 25 | import videogames from '~/assets/icons/videogames.png'; 26 | import laptop from '~/assets/icons/laptop.png'; 27 | import notfound from '~/assets/images/not-found.png'; 28 | 29 | export default function Home({ navigation }) { 30 | const [loading, setLoading] = useState(false); 31 | const [products, setProducts] = useState([]); 32 | const [department, setDepartment] = useState('todos'); 33 | 34 | const loadProducts = useCallback(async () => { 35 | setLoading(true); 36 | const response = await api.get('products'); 37 | setProducts(response.data); 38 | setLoading(false); 39 | }, []); 40 | 41 | useEffect(() => { 42 | loadProducts(); 43 | }, [loadProducts]); 44 | 45 | useMemo(async () => { 46 | setLoading(true); 47 | let response; 48 | if (department !== 'todos') { 49 | response = await api.get('department', { 50 | params: { 51 | q: department, 52 | embed: 'products', 53 | }, 54 | }); 55 | const data = response.data[0].products; 56 | setProducts(data); 57 | } else { 58 | response = await api.get('products'); 59 | setProducts(response.data); 60 | } 61 | setLoading(false); 62 | }, [department]); 63 | 64 | async function handleSearchSubmit(search) { 65 | setLoading(true); 66 | const response = await api.get('products', { 67 | params: { 68 | q: search, 69 | }, 70 | }); 71 | setProducts(response.data); 72 | setLoading(false); 73 | } 74 | 75 | return ( 76 | 77 |
78 | 79 | 80 | { 82 | setDepartment('todos'); 83 | }} 84 | > 85 | 86 | 87 | Todos 88 | 89 | 90 | { 92 | setDepartment('drone'); 93 | }} 94 | > 95 | 96 | 97 | Drone 98 | 99 | 100 | { 102 | setDepartment('tv'); 103 | }} 104 | > 105 | 106 | 107 | TV 108 | 109 | 110 | { 112 | setDepartment('laptop'); 113 | }} 114 | > 115 | 116 | 117 | Notebook 118 | 119 | 120 | { 122 | setDepartment('videogames'); 123 | }} 124 | > 125 | 126 | 127 | Video games 128 | 129 | 130 | { 132 | setDepartment('smartphone'); 133 | }} 134 | > 135 | 136 | 137 | Smarthphone 138 | 139 | 140 | 141 | 142 | {loading ? ( 143 | 144 | ) : ( 145 | <> 146 | {products.length > 0 ? ( 147 | String(item.id)} 152 | renderItem={({ item }) => ( 153 | 154 | )} 155 | /> 156 | ) : ( 157 | 158 | 159 | 160 | )} 161 | 162 | )} 163 | 164 | 165 | ); 166 | } 167 | 168 | Home.propTypes = { 169 | navigation: PropTypes.shape({ 170 | navigate: PropTypes.func.isRequired, 171 | }).isRequired, 172 | }; 173 | -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | { 2 | "stock": [ 3 | { 4 | "id": 1, 5 | "amount": 1 6 | }, 7 | { 8 | "id": 2, 9 | "amount": 2 10 | }, 11 | { 12 | "id": 3, 13 | "amount": 4 14 | }, 15 | { 16 | "id": 4, 17 | "amount": 2 18 | }, 19 | { 20 | "id": 5, 21 | "amount": 3 22 | }, 23 | { 24 | "id": 6, 25 | "amount": 2 26 | }, 27 | { 28 | "id": 7, 29 | "amount": 1 30 | }, 31 | { 32 | "id": 8, 33 | "amount": 5 34 | } 35 | ], 36 | "department": [ 37 | { "id": 1, "title": "drone" }, 38 | { "id": 2, "title": "smartphone" }, 39 | { "id": 3, "title": "tv" }, 40 | { "id": 4, "title": "videogames" }, 41 | { "id": 5, "title": "laptop" } 42 | ], 43 | "products": [ 44 | { 45 | "id": 1, 46 | "departmentId": 1, 47 | "title": "Drone DJI Mavic 2 Zoom 4K CMOS Cinza", 48 | "description": "A aeronave Mavic 2 Pro da DJI foi projetada e construída para se adaptar facilmente as mais diversas aplicações profissionais, especialmente, inspeções aéreas em áreas restritas e de difícil acesso. Ideal para mapeamentos, inspeções e agricultura de precisão.O Mavic 2 PRO é o mais avançado drone de câmera da DJI já construído, projetado para profissionais, fotógrafos aéreos e criadores de conteúdo. Ao incorporar o icônico design dobrável do popular Mavic Pro, o Mavic 2 PRO é uma plataforma robusta com novas câmeras estabilizadas por gimbal e recursos inteligentes avançados, como o Hyperlapse e o ActiveTrack, para uma narrativa mais fácil e dinâmica.", 49 | "rating": 4, 50 | "numrating": 5, 51 | "images": [ 52 | "https://cdn.awsli.com.br/600x450/26/26503/produto/31963057/6bfff3df86.jpg", 53 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSc-dnFqCknqoQj7t3vgjTl5hbCKyE7alW3WyYbHU9HT74Z6AnR&s", 54 | "https://media.takealot.com/covers_tsins/55525188/DJIMAVICPRO2FLYMORECOMBO-1-pdpxl.jpg" 55 | ], 56 | "price": 10500, 57 | "discount": 0 58 | }, 59 | { 60 | "id": 2, 61 | "departmentId": 1, 62 | "title": "Drone Dji Mavic Pro Combo Fly More 3 Bat, Bolsa", 63 | "description": "A aeronave Mavic 2 Pro da DJI foi projetada e construída para se adaptar facilmente as mais diversas aplicações profissionais, especialmente, inspeções aéreas em áreas restritas e de difícil acesso. Ideal para mapeamentos, inspeções e agricultura de precisão.O Mavic 2 PRO é o mais avançado drone de câmera da DJI já construído, projetado para profissionais, fotógrafos aéreos e criadores de conteúdo. Ao incorporar o icônico design dobrável do popular Mavic Pro, o Mavic 2 PRO é uma plataforma robusta com novas câmeras estabilizadas por gimbal e recursos inteligentes avançados, como o Hyperlapse e o ActiveTrack, para uma narrativa mais fácil e dinâmica.", 64 | "rating": 3, 65 | "numrating": 13, 66 | "images": [ 67 | "https://http2.mlstatic.com/drone-dji-mavic-pro-combo-fly-more-3-bat-bolsa-envio-hoje-D_NQ_NP_647842-MLB31359033893_072019-F.webp", 68 | "https://assets.fatllama.com/images/medium/dji-mavic-pro-drone-75120779.jpg" 69 | ], 70 | "price": 9500, 71 | "discount": 0.75 72 | }, 73 | { 74 | "id": 3, 75 | "departmentId": 2, 76 | "title": "iPhone 11 Cinza, com Tela de 6,1, 4G, 64 GB e Câmera de 12 MP - MWLV2BZ/A", 77 | "description": "Design todo tela. Maior duração de bateria. Desempenho mais rápido. Resistência à água e respingos. Fotos com qualidade de estúdio. E mais segurança com o Face ID. O iPhone XR é uma fantástica evolução.", 78 | "rating": 5, 79 | "numrating": 25, 80 | "images": [ 81 | "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9GcTmWiI6B-F35PijsjvuHz4piVjuuP_wiuSSPuCfCGmm7OUEO8L598e5P_1DjuB1PbHEPWgfprL52JlWSKEzpvqhneRVJTQc&usqp=CAE", 82 | "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9GcQy6GSR-uf6br3wSBwrxw5hoB5UvgLkdiy-dyY1-nCm9C-eGnqP59XJYHl703Rk4nR6PUd6KyOq1KgrUMII_dn9L1LpbH7RS4nYZcJzPpvJRyvy_WFBTLCsRw&usqp=CAY" 83 | ], 84 | "price": 3400, 85 | "discount": 0 86 | }, 87 | { 88 | "id": 4, 89 | "departmentId": 2, 90 | "title": "Smartphone Samsung Galaxy S10+ 128GB Dual Chip Android 9.0 Tela 6.4 Octa-Core 4G Câmera Tripla Traseira 12MP + 12MP + 16MP - Preto", 91 | "description": "O smartphone que não apenas se destaca, mas se diferencia Totalmente reprojetado para sua experiência de visualização ser ... Samsung · Galaxy · Galaxy S · Android · Tela de 5,8 polegadas · Impressão digital · Reconhecimento facial · 16 megapixels · 4G LTE · 150 gramas", 92 | "rating": 4, 93 | "numrating": 5, 94 | "images": [ 95 | "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcRCmftiP_hFdt7GYBl-IpL-rl8oy13APczrkGyFWms4X8ya8F3DejbrgOU_JKIj8cOMnI9HBGx1G_H9NAFF-ycQza4WmNk4sgL0dZJAGy-puyY4HbS6_7mhHns&usqp=CAE" 96 | ], 97 | "price": 3150, 98 | "discount": 0.95 99 | }, 100 | { 101 | "id": 5, 102 | "departmentId": 4, 103 | "title": "Console Xbox One X 1TB 4K (Golden Rush) Edição limitada com Jogo (Residente Evil 7)", 104 | "description": "No Xbox One X os jogos rodam muito melhor. Com 40% mais poder do que qualquer outro console, experimente os verdadeiros jogos 4K. Os jogos ficam com uma ótima resolução, funcionam sem problemas e carregam rapidamente, mesmo em uma tela de 1080p. O Xbox One X também funciona com todos os seus jogos e acessórios do Xbox One, bem como o Xbox Live, uma rede multiplayer avançada, que lhe oferece mais maneiras de jogar. Conteúdo da embalagem: Console XBOX One X, Fonte de alimentação universal (Bivolt), Cabo HDMI, Manual de Instruções, Controle sem fio, HDD 1TB 4 K.", 105 | "rating": 5, 106 | "numrating": 12, 107 | "images": [ 108 | "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcSfg2NwaEX2LGG-X0hPXHc9hE3C9Tj6GjqMjVKYjgDtFJ1hSjCDys34PwB9STE5-cp4PEC76cyzh8q9_8MJPSLhZKpf7f8L&usqp=CAE" 109 | ], 110 | "price": 2350, 111 | "discount": 0.9 112 | }, 113 | { 114 | "id": 6, 115 | "departmentId": 5, 116 | "title": "MacBook Air MQD32BZ/A com Intel Core i5 Dual Core 8GB 128GB SSD 13'' Prata - Apple", 117 | "description": "Apple · MacBook Family · MacBook Air · Mac OS · 13 polegadas · HD de 128 GB · SSD · Disco rígido · 8 GB de RAM · Processador de 1,8 GHz A bateria do MacBook Air dura muitas horas entre uma carga e outra. Do café da manhã até a volta para casa, você trabalha sem precisar de uma tomada.", 118 | "rating": 5, 119 | "numrating": 2, 120 | "images": [ 121 | "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcT8894q0YL8SjDVikAj0fktF_JxQJ_83Rkw7edRL4YYCCeHZzKjAPxssaqCrrwTi_6pVjMyP2XApi9DSGcaE9SUBvE2XBx7S7gJ9YypKPZJHp_mHXCGUAcWJg&usqp=CAE" 122 | ], 123 | "price": 4050.55, 124 | "discount": 0 125 | }, 126 | { 127 | "id": 7, 128 | "departmentId": 5, 129 | "title": "Macbook Apple Pro Retina, Intel Core i5, 8GB, SSD 256GB, macOS, 13.3´, Cinza Espacial - MUHP2BZ/A", 130 | "description": "Apple · MacBook Family · MacBook Pro · Mac OS · 13,3 polegadas · HD de 256 GB · SSD · 8 GB de RAM · Touchscreen · Processador de 1,4 GHz. O MacBook Pro leva o notebook para outro patamar de eficiência e mobilidade. Com recursos como processadores e memória de alto desempenho, chips gráficos avançados e armazenamento ultrarrápido, todas as suas ideias ganham fôlego para ir muito mais longe.", 131 | "rating": 5, 132 | "numrating": 2, 133 | "images": [ 134 | "https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcTR6GZNWhgruoIf3QDbimWH9DAdM0-PesKFH1jWbHz22fn0QUFhI2Y6RJB1O1snZ2cV8J6_3T9pg-RRmxtTd5bOKigB-ayymOK4EMs-RSoTPkRone3vt8vd&usqp=CAE" 135 | ], 136 | "price": 9050.55, 137 | "discount": 0.9 138 | }, 139 | { 140 | "id": 8, 141 | "departmentId": 4, 142 | "title": "Console Sony PlayStation 4 Pro Neo Versa Fortnite, 1TB, Preto - CUH-7214B", 143 | "description": "O pacote inclui um sistema PlayStation 4 Pro de 1TB preto onyx, um controle sem fio DUALSHOCK 4 correspondente e um código para o seguinte conteúdo exclusivo de Fortnite: traje épico Neo Versa, mochila épica Neo Phrenzy e 2.000 V-Bucks.", 144 | "rating": 4, 145 | "numrating": 15, 146 | "images": [ 147 | "https://images5.kabum.com.br/produtos/fotos/107285/console-sony-playstation-4-pro-neo-versa-fortnite-1tb-preto-cuh-7214b_console-sony-playstation-4-pro-neo-versa-fortnite-1tb-preto-cuh-7214b_1572531645_gg.jpg" 148 | ], 149 | "price": 2500.0, 150 | "discount": 0.9 151 | } 152 | ], 153 | "profile": [ 154 | { 155 | "id": 1, 156 | "name": "admin", 157 | "email": "admin@hotmail.com", 158 | "password": 12345 159 | }, 160 | { 161 | "id": 2, 162 | "name": "teste", 163 | "email": "teste@hotmail.com", 164 | "password": 123 165 | } 166 | ] 167 | } 168 | -------------------------------------------------------------------------------- /src/assets/animations/checkout.json: -------------------------------------------------------------------------------- 1 | {"v":"5.4.4","fr":60,"ip":0,"op":110,"w":500,"h":500,"nm":"Comp 1","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Cart_02 Outlines 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,251,0],"ix":2},"a":{"a":0,"k":[176,176,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-4.139,8.816],[0,0]],"o":[[0,0],[-9.01,0],[4.139,-8.818],[0,0]],"v":[[105.594,35.195],[-86.055,35.195],[-101.456,16.42],[-73.103,-35.195]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.38823529411764707,0.2784313725490196,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":32,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[197.391,204.374],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[100],"e":[0]},{"t":24}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-1,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Cart_01 Outlines 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[176,176,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-151.759,-71.235],[-110.276,-71.422],[-41.387,71.422],[89.068,71.422],[151.759,-39.57],[-94.884,-39.57]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0],"e":[100]},{"t":30}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.38823529411764707,0.2784313725490196,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":32,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[168.304,103.826],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-1,"op":300,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":101,"s":[100],"e":[0]},{"t":110}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256.001,251.31,0],"ix":2},"a":{"a":0,"k":[-5.999,26.31,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-94,20],[-19,87],[95,-50]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.226],"y":[0.975]},"o":{"x":[0.333],"y":[0]},"t":54,"s":[0],"e":[33]},{"i":{"x":[0.51],"y":[-17462.855]},"o":{"x":[0.333],"y":[0]},"t":59,"s":[33],"e":[33]},{"i":{"x":[0.537],"y":[0.993]},"o":{"x":[0.592],"y":[-0.009]},"t":60,"s":[33],"e":[97]},{"t":71}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-1,"op":110,"st":-50,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":48,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":101,"s":[100],"e":[0]},{"t":110}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":43,"s":[251.152,235.152,0],"e":[251.152,251.152,0],"to":[0,2.667,0],"ti":[0,-2.667,0]},{"t":57}],"ix":2},"a":{"a":0,"k":[27.152,7.152,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":43,"s":[25,25,100],"e":[100,100,100]},{"t":51}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[438.305,438.305],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0.188235294118,0.188235294118,0.2,1],"e":[0.376470595598,0.733333349228,0.345098048449,1]},{"t":72}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.152,7.152],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":43,"op":110,"st":-45,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Pre-comp 1","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[100],"e":[1]},{"t":48}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0],"e":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[5],"e":[5]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.552],"y":[0.004]},"t":32,"s":[5],"e":[-410]},{"t":51}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":32,"s":[250,250,0],"e":[250,238,0],"to":[0,-2,0],"ti":[0,2,0]},{"t":51}],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":500,"h":500,"ip":-11,"op":52,"st":-50,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[344.58,377.42,0],"e":[344.58,384.42,0],"to":[0,1.167,0],"ti":[-0.333,-1.167,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":22,"s":[344.58,384.42,0],"e":[346.58,384.42,0],"to":[0.333,1.167,0],"ti":[0,1.167,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":32,"s":[346.58,384.42,0],"e":[344.58,377.42,0],"to":[0,-1.167,0],"ti":[0.333,1.167,0]},{"t":33}],"ix":2},"a":{"a":0,"k":[-65.42,128.42,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":-27,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":-17,"s":[100,100,100],"e":[120,120,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":-14,"s":[120,120,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":-9,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[100,100,100],"e":[103,77,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":22,"s":[103,77,100],"e":[103,77,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":32,"s":[103,77,100],"e":[100,100,100]},{"t":33}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[64.84,64.84],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.38823529411764707,0.2784313725490196,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-65.42,128.42],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-1,"op":40,"st":-50,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[188.58,377.42,0],"ix":2},"a":{"a":0,"k":[-65.42,128.42,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,16.667]},"t":-30,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":-20,"s":[100,100,100],"e":[120,120,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":-17,"s":[120,120,100],"e":[100,100,100]},{"t":-12}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[64.84,64.84],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.38823529411764707,0.2784313725490196,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-65.42,128.42],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-1,"op":39,"st":-50,"bm":0}],"markers":[]} --------------------------------------------------------------------------------