├── .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 |
9 |
10 | {children}
11 |
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 |
20 | avaliações ({defaultNumRating})
21 |
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 |
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 |
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 | E-mail
67 | passwordRef.current.focus()}
76 | value={email}
77 | onChangeText={setEmail}
78 | />
79 |
80 |
81 | Senha
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":[]}
--------------------------------------------------------------------------------