├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── assets ├── logo.png └── preloader.svg ├── components ├── Dialog │ └── index.js ├── Main │ ├── index.css │ └── index.js └── Ride │ ├── index.css │ └── index.js ├── index.js ├── serviceWorker.js └── store ├── actions └── main.actions.js ├── constants.js ├── fedux.js ├── reducers └── main.reducer.js └── state └── initialState.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Bird app in-complete clone

2 | 3 |

4 | property of Bird company 5 |

6 | 7 |

8 | 9 | circleci 10 | 11 | 12 | npm downloads 13 | 14 |

15 | 16 | This app is build with? 17 | ---- 18 | - Nodejs 19 | - Reactjs 20 | - MaterialUI 21 | - ES6 22 | - JSX 23 | - Third parties modules like (axios, money) 24 | - Fedux (one-way-flow, one-directional state container) 25 | 26 | Features 27 | ---- 28 | - Can login registered users with Bird App. 29 | - Can enter any `vehicleId` and find vehicle details like status,battery,code from Bird. 30 | - Can start/stop any `vehicle` Bird from anywhere without verifying driving-license, parking-picture - **(will implement, still in development-process)**. 31 | 32 | Installation 33 | ---- 34 | 35 | ```node 36 | $ npm install 37 | $ npm run start || npm run build 38 | ``` 39 | 40 | Additional information 41 | ---- 42 | For testing this app only, you can copy `vehicle_id` value from this Get Vehicles Nearby JSON response. 43 | 44 | 45 | **Please note:** This application is only for demo purposes to check/show how Bird app works also all assets and apis get used is property of `Bird Company` and created for testing my **Fedux** library. 46 | 47 | License 48 | ---- 49 | 50 | MIT 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bird-clone", 3 | "version": "0.1.0", 4 | "description": "A in-complete Bird app clone", 5 | "main": "src/index.js", 6 | "dependencies": { 7 | "@material-ui/core": "^4.0.0-rc.0", 8 | "@material-ui/icons": "^4.0.1", 9 | "axios": "^0.19.0", 10 | "faker": "^4.1.0", 11 | "react": "^16.8.6", 12 | "react-dom": "^16.8.6", 13 | "react-scripts": "3.0.1" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/fadidevv/react-bird-clone.git" 24 | }, 25 | "keywords": [ 26 | "react", 27 | "react-component", 28 | "component", 29 | "bird", 30 | "vehicle", 31 | "node-bird", 32 | "scooter", 33 | "fedux" 34 | ], 35 | "author": "@FadiDev ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/fadidevv/react-bird-clone/issues" 39 | }, 40 | "homepage": "https://birder.netlify.com", 41 | "eslintConfig": { 42 | "extends": "react-app" 43 | }, 44 | "peerDependencies": { 45 | "react": "~0.13.x || ~0.14.x || ^15.0.0 || ^16.0.0" 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fadidevv/react-bird-clone/54195c8cfa69b0cac47664960f494a4e5c63ff86/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | Bird - Clone 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bird clone", 3 | "name": "Bird - Clone", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fadidevv/react-bird-clone/54195c8cfa69b0cac47664960f494a4e5c63ff86/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/preloader.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Dialog/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Dialog as AlertBird, Button } from '@material-ui/core' 3 | import DialogActions from '@material-ui/core/DialogActions' 4 | import DialogContent from '@material-ui/core/DialogContent' 5 | import DialogContentText from '@material-ui/core/DialogContentText' 6 | import DialogTitle from '@material-ui/core/DialogTitle' 7 | 8 | import { connectWith } from '../../store/fedux' 9 | 10 | const Dialog = props => { 11 | return ( 12 | <> 13 | 18 | {props.dialog.title} 19 | 20 | 21 | {props.dialog.content} 22 | 23 | 24 | 25 | 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default connectWith()(Dialog, 'dialog')() 41 | -------------------------------------------------------------------------------- /src/components/Main/index.css: -------------------------------------------------------------------------------- 1 | #inputRide, 2 | .MuiInputBase-input { 3 | width: 310px; 4 | margin-top: 300px; 5 | padding: 5px; 6 | color: #212121; 7 | text-align: center; 8 | } 9 | 10 | .MuiInput-underline:before { 11 | border-bottom-color: #4f2277 !important; 12 | } 13 | 14 | .MuiInput-underline:after { 15 | border-bottom-color: #714399 !important; 16 | } 17 | 18 | #birdLogo { 19 | width: 150px; 20 | } 21 | 22 | #buttonRide { 23 | font-family: sans-serif; 24 | color: white; 25 | background-color: #714399; 26 | font-weight: 320; 27 | width: 300px; 28 | border-radius: 2px; 29 | box-shadow: none; 30 | margin-top: 20px; 31 | } 32 | 33 | #birdPreloader { 34 | width: 70px; 35 | margin-top: 10px; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Main/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Grid, Button, Input } from '@material-ui/core' 3 | 4 | import '../Main/index.css' 5 | import logo from '../../assets/logo.png' 6 | import preloader from '../../assets/preloader.svg' 7 | import Dialog from '../Dialog/index' 8 | import Ride from '../Ride/index' 9 | 10 | class Main extends Component { 11 | render() { 12 | return ( 13 | 14 | 15 | {this.props.data.token ? ( 16 | 17 | ) : ( 18 | <> 19 | {this.props.data.preloader && ( 20 | 21 | )} 22 | 28 | this.props.emit({ 29 | handleInputRide: true, 30 | inputRideValue: [target] 31 | }) 32 | } 33 | required 34 | autoFocus={true} 35 | /> 36 | 55 | 56 | )} 57 | {this.props.data.error && ( 58 | 63 | )} 64 | 65 | ) 66 | } 67 | } 68 | 69 | export default Main 70 | -------------------------------------------------------------------------------- /src/components/Ride/index.css: -------------------------------------------------------------------------------- 1 | #inputStartStopRide, 2 | .MuiInputBase-input { 3 | width: 310px; 4 | padding: 5px; 5 | color: #212121; 6 | text-align: center; 7 | } 8 | 9 | #buttonStartStopRide { 10 | font-family: sans-serif; 11 | color: white; 12 | background-color: #61a263; 13 | font-weight: 320; 14 | width: 80px; 15 | height: 80px; 16 | box-shadow: none; 17 | margin-top: 20px; 18 | } 19 | 20 | #scooterDetails { 21 | display: flex; 22 | font-family: sans-serif; 23 | padding: 10px; 24 | margin-top: 30px; 25 | } 26 | 27 | #batteryColor { 28 | color: #4caf50; 29 | } 30 | 31 | #codeColor { 32 | color: #714399; 33 | } 34 | 35 | #lockColor { 36 | color: #714399; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Ride/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Input } from '@material-ui/core' 3 | import Fab from '@material-ui/core/Fab' 4 | import Card from '@material-ui/core/Card' 5 | import CardContent from '@material-ui/core/CardContent' 6 | import { Battery90Sharp, LockSharp } from '@material-ui/icons' 7 | 8 | import '../Ride/index.css' 9 | import { connectWith } from '../../store/fedux' 10 | 11 | const Ride = props => { 12 | return ( 13 | <> 14 | {props.ride.code && ( 15 | 16 | 17 |

18 | CODE: {props.ride.code} 19 |

20 |

21 | Battery Level: 22 |

23 |

24 | Locked: 25 |

26 |
27 |
28 | )} 29 | 35 | props.emit({ 36 | handleStartStopRide: true, 37 | inputStartStopRideValue: [target] 38 | }) 39 | } 40 | style={{ marginTop: props.ride.code ? 100 : 300 }} 41 | required 42 | autoFocus={true} 43 | /> 44 | 48 | props.emit({ 49 | handleRideConfirmations: true, 50 | payload: [ 51 | props.ride.inputStartStopRideValue, 52 | props.ride.lat, 53 | props.ride.lang, 54 | props.ride.token 55 | ] 56 | }) 57 | } 58 | > 59 | {props.ride.buttonStartStopText} 60 | 61 | 62 | ) 63 | } 64 | 65 | export default connectWith()(Ride, 'ride')() 66 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import { createStore } from './store/fedux' 5 | import mainReducer from './store/reducers/main.reducer' 6 | import initialState from './store/state/initialState' 7 | import { 8 | handleInputRide, 9 | handleStartStopRide, 10 | handleConfirmation, 11 | verifyBirdLoginRequest, 12 | handleRideConfirmations 13 | } from './store/actions/main.actions' 14 | import Main from './components/Main' 15 | import * as serviceWorker from './serviceWorker' 16 | 17 | const FeduxProvider = createStore( 18 | mainReducer, 19 | { 20 | handleInputRide, 21 | handleStartStopRide, 22 | handleConfirmation, 23 | verifyBirdLoginRequest, 24 | handleRideConfirmations 25 | }, 26 | false 27 | )(Main)(initialState) 28 | 29 | ReactDOM.render(, document.getElementById('root')) 30 | serviceWorker.unregister() 31 | 32 | if (module.hot) module.hot.accept() 33 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/store/actions/main.actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | HANDLE_INPUT_RIDE, 3 | SAVE_TOKEN, 4 | HANDLE_START_STOP_RIDE, 5 | CONTROL_RIDE_BUTTON_TEXT, 6 | CONTROL_RIDE_INPUT_TEXT, 7 | HANDLE_PRELOADER, 8 | HANDLE_CODE, 9 | HANDLE_RIDE_BUTTON_TEXT, 10 | HANDLE_ERROR 11 | } from '../constants' 12 | 13 | import { dispatcher } from '../fedux' 14 | import axios from 'axios' 15 | 16 | export const handleInputRide = inputRideValue => { 17 | const { value: currentValue } = inputRideValue 18 | return { 19 | type: HANDLE_INPUT_RIDE, 20 | payload: currentValue 21 | } 22 | } 23 | 24 | export const handleStartStopRide = inputStartStopRideValue => { 25 | const { value: currentValue } = inputStartStopRideValue 26 | return { 27 | type: HANDLE_START_STOP_RIDE, 28 | payload: currentValue 29 | } 30 | } 31 | 32 | export const handleConfirmation = inputRideValue => { 33 | if ( 34 | inputRideValue === '' || 35 | inputRideValue === null || 36 | !inputRideValue.match('@') 37 | ) { 38 | return { 39 | type: HANDLE_ERROR, 40 | payload: 'Please enter your email' 41 | } 42 | } else { 43 | (async () => await makeBirdLoginRequest(inputRideValue))() 44 | return { 45 | type: HANDLE_ERROR, 46 | payload: false 47 | } 48 | } 49 | } 50 | 51 | export const handleRideConfirmations = ( 52 | inputStartStopRideValue, 53 | lat, 54 | lang, 55 | token 56 | ) => { 57 | if (inputStartStopRideValue === '' || inputStartStopRideValue === null) { 58 | return { 59 | type: HANDLE_ERROR, 60 | payload: 'Please enter any scooterId' 61 | } 62 | } else { 63 | (async () => 64 | await getBirdScooterById(inputStartStopRideValue, lat, lang, token))() 65 | return { 66 | type: HANDLE_ERROR, 67 | payload: false 68 | } 69 | } 70 | } 71 | 72 | export const saveToken = token => { 73 | return { 74 | type: SAVE_TOKEN, 75 | payload: token 76 | } 77 | } 78 | 79 | export const controlRideButtonText = () => { 80 | return { 81 | type: CONTROL_RIDE_BUTTON_TEXT, 82 | payload: 'ENTER CODE' 83 | } 84 | } 85 | 86 | export const controlRideInputText = () => { 87 | return { 88 | type: CONTROL_RIDE_INPUT_TEXT, 89 | payload: 'ENTER YOUR CODE' 90 | } 91 | } 92 | 93 | const _birdRequestConfig = (_token = null) => { 94 | const token = _token !== null ? { Authorization: `Bird ${_token}` } : '' 95 | const request = axios.create({ 96 | baseURL: 'https://api.birdapp.com', 97 | headers: { 98 | 'Device-id': '06b8b764-22dd-4fe1-b897-c78af5231ca1', 99 | Platform: 'android', 100 | 'App-Version': '4.35.1.1', 101 | ...token 102 | } 103 | }) 104 | return request 105 | } 106 | 107 | const makeBirdLoginRequest = async email => { 108 | try { 109 | dispatcher([{ type: HANDLE_PRELOADER, payload: true }]) 110 | let response = await _birdRequestConfig().request({ 111 | method: 'POST', 112 | url: '/user/login', 113 | data: { 114 | email: email 115 | }, 116 | responseType: 'json' 117 | }) 118 | if (response.status === 200 && !response.data.token) { 119 | dispatcher([ 120 | { type: HANDLE_INPUT_RIDE, payload: '' }, 121 | controlRideButtonText, 122 | controlRideInputText, 123 | { type: HANDLE_PRELOADER, payload: false } 124 | ]) 125 | return response.data 126 | } else { 127 | dispatcher([ 128 | { type: HANDLE_INPUT_RIDE, payload: '' }, 129 | { 130 | type: HANDLE_ERROR, 131 | payload: 132 | 'Sorry, only already registered users are allowed from Bird App' 133 | }, 134 | { type: HANDLE_PRELOADER, payload: false } 135 | ]) 136 | } 137 | } catch (error) { 138 | if (error.response) { 139 | dispatcher([ 140 | { type: HANDLE_ERROR, payload: error.response.data.errors }, 141 | { type: HANDLE_INPUT_RIDE, payload: '' }, 142 | { type: HANDLE_PRELOADER, payload: false } 143 | ]) 144 | } 145 | } 146 | } 147 | 148 | export const verifyBirdLoginRequest = async code => { 149 | try { 150 | dispatcher([{ type: HANDLE_PRELOADER, payload: true }]) 151 | let response = await _birdRequestConfig().request({ 152 | method: 'PUT', 153 | url: '/request/accept', 154 | data: { 155 | token: code 156 | }, 157 | responseType: 'json' 158 | }) 159 | if (response.status === 200 && response.data.token) { 160 | dispatcher([ 161 | { type: SAVE_TOKEN, payload: response.data.token }, 162 | { type: HANDLE_PRELOADER, payload: false } 163 | ]) 164 | } 165 | } catch (error) { 166 | if (error.response) { 167 | dispatcher([ 168 | { type: HANDLE_ERROR, payload: error.response.data.message }, 169 | { type: HANDLE_PRELOADER, payload: false } 170 | ]) 171 | } 172 | } 173 | } 174 | 175 | export const getBirdScooterById = async ( 176 | scooterId, 177 | lat = '47.3817767', 178 | lang = '8.5043783', 179 | token 180 | ) => { 181 | try { 182 | dispatcher([ 183 | { type: HANDLE_CODE, payload: '' }, 184 | { type: HANDLE_PRELOADER, payload: true } 185 | ]) 186 | let response = await _birdRequestConfig(token).request({ 187 | method: 'PUT', 188 | url: `/bird/chirp`, 189 | data: { 190 | alarm: false, 191 | bird_id: scooterId 192 | }, 193 | headers: { 194 | Location: JSON.stringify({ latitude: lat, longitude: lang }) 195 | }, 196 | responseType: 'json' 197 | }) 198 | if (response.status === 200) { 199 | dispatcher([ 200 | { type: HANDLE_START_STOP_RIDE, payload: '' }, 201 | { type: HANDLE_RIDE_BUTTON_TEXT, payload: 'START' }, 202 | { type: HANDLE_CODE, payload: response.data.code }, 203 | { type: HANDLE_PRELOADER, payload: false } 204 | ]) 205 | } 206 | } catch (error) { 207 | if (error.response) { 208 | dispatcher([ 209 | { type: HANDLE_ERROR, payload: error.response.data.message }, 210 | { type: HANDLE_PRELOADER, payload: false } 211 | ]) 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/store/constants.js: -------------------------------------------------------------------------------- 1 | export const HANDLE_INPUT_RIDE = 'HANDLE_INPUT_RIDE' 2 | export const SAVE_TOKEN = 'SAVE_TOKEN' 3 | export const CONTROL_RIDE_BUTTON_TEXT = 'CONTROL_RIDE_BUTTON_TEXT' 4 | export const CONTROL_RIDE_INPUT_TEXT = 'CONTROL_RIDE_INPUT_TEXT' 5 | export const HANDLE_START_STOP_RIDE = 'HANDLE_START_STOP_RIDE' 6 | export const HANDLE_PRELOADER = 'HANDLE_PRELOADER' 7 | export const HANDLE_CODE = 'HANDLE_CODE' 8 | export const HANDLE_RIDE_BUTTON_TEXT = 'HANDLE_RIDE_BUTTON_TEXT' 9 | export const HANDLE_ERROR = 'HANDLE_ERROR' 10 | -------------------------------------------------------------------------------- /src/store/fedux.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author @FadiDev gray at Thailand, Bangkok 3 | */ 4 | /* eslint-disable */ 5 | import React from 'react' 6 | var { EventEmitter } = require('eventemitter3'), 7 | feduxListener = new EventEmitter() 8 | 9 | var _holdOn 10 | var globalStore = {} 11 | var _propers = {} 12 | 13 | const _ParentConnector = (dispatchToProps, setFeduxToGlobal) => ( 14 | Component, 15 | _propName, 16 | _feduxName, 17 | _state = {}, 18 | _initialState = {} 19 | ) => { 20 | window.toComponents = _initialState 21 | if (Component.toString().match('componentWillUpdate')) { 22 | class Fedux extends React.Component { 23 | static displayName = _feduxName || this.name 24 | componentDidMount() { 25 | if (setFeduxToGlobal) setToGlobal() 26 | if (Object.keys(dispatchToProps).length > 0) { 27 | window.GLOBAL_FEDUX_ACTION_PROPS = dispatchToProps 28 | window.globalFeduxContext = this 29 | } 30 | } 31 | render() { 32 | return ( 33 | <> 34 | 45 | 46 | ) 47 | } 48 | } 49 | return Fedux 50 | } 51 | } 52 | 53 | const _ParentEnchancer = (Component, _enchancerName, _reducers) => { 54 | return class Enchancer extends Component { 55 | static displayName = _enchancerName || this.name 56 | componentDidMount() { 57 | window.globalParentContext = this 58 | window.globalParentName = Component.name 59 | _globalFedux(_reducers) 60 | } 61 | componentWillUpdate = (nextProps, nextState) => { 62 | window.toComponents = nextState 63 | _objectMerge(globalStore, nextState) 64 | window.Store = { ...globalStore } 65 | } 66 | render = () => super.render() 67 | } 68 | } 69 | 70 | const _globalFedux = _reducers => { 71 | if (_reducers !== null) return (window.GLOBAL_FEDUX_REDUCERS = _reducers) 72 | } 73 | 74 | const _emit = (name = {}) => { 75 | let globalActionsFromProps = window.GLOBAL_FEDUX_ACTION_PROPS 76 | if (typeof globalActionsFromProps === 'object') { 77 | let ifTruthy = Object.keys(name).some( 78 | isTruthful => name[isTruthful] === true 79 | ) 80 | if (ifTruthy) { 81 | if (name instanceof Object && Object.keys(name).length === 1) { 82 | for (var actions in name) { 83 | if (globalActionsFromProps[actions]) { 84 | return _dispatch(globalActionsFromProps[actions]()) 85 | } 86 | } 87 | } else if (name instanceof Object && Object.keys(name).length === 2) { 88 | for (var actions in name) { 89 | if (globalActionsFromProps[actions]) { 90 | return _dispatch( 91 | globalActionsFromProps[actions]( 92 | ...name[Object.entries(name)[1][0]] 93 | ) 94 | ) 95 | } 96 | } 97 | } else { 98 | throw new Error(` 99 | '${JSON.stringify(name)}' is not a object type or it contains 100 | more then 1 or 2 objects or objects are empty 101 | `) 102 | } 103 | } else { 104 | throw new Error(` 105 | Did you set the action ${JSON.stringify(name)} object 106 | value to 'true' like this ${JSON.stringify(name).replace( 107 | 'false', 108 | 'true' 109 | )}? 110 | `) 111 | } 112 | } else { 113 | throw new Error(` 114 | Did you passed your actions or actionCreators to the parent 115 | class '${window.globalParentName}' component as a props? 116 | `) 117 | } 118 | } 119 | 120 | const dispose = () => 121 | window.globalParentContext !== null || undefined 122 | ? window.globalParentContext 123 | : '' 124 | 125 | const _dispatch = (action = {}) => { 126 | if (action !== null) { 127 | var response = window.GLOBAL_FEDUX_REDUCERS(action) 128 | window.globalParentContext.setState({ ...response }, () => { 129 | window.globalFeduxContext.forceUpdate() 130 | }) 131 | } 132 | } 133 | 134 | const dispatcher = actions => { 135 | if (Array.isArray(actions)) { 136 | actions.map(dispose => { 137 | if (typeof dispose === 'object') { 138 | _dispatch(dispose) 139 | } else { 140 | _dispatch(dispose()) 141 | } 142 | }) 143 | } else { 144 | return typeof actions === 'object' 145 | ? _dispatch(actions) 146 | : _dispatch(actions()) 147 | } 148 | } 149 | 150 | const _ifGlobalFedux = _reducersRef => { 151 | return { 152 | ['getState']: getState, 153 | ['subscribe']: subscribe, 154 | ['dispatch']: dispatcher 155 | } 156 | } 157 | 158 | const setToGlobal = () => (window.Fedux = _ifGlobalFedux()) 159 | 160 | const _feduxListenerHandler = ( 161 | hookDefault = '__hookFeduxStore', 162 | _propers = {} 163 | ) => 164 | feduxListener.on(hookDefault, state => _objectMerge(_propers, { ...state })) 165 | 166 | const connectWith = ( 167 | reducers = null, 168 | dispatchToProps = {}, 169 | setFeduxToGlobal = false 170 | ) => (Component, propName = 'data', feduxName, enchancerName) => ( 171 | initialState = {} 172 | ) => { 173 | if ( 174 | (Component.toString().match('return Object') && 175 | Component.toString().match('apply')) || 176 | Component.toString().match('class') 177 | ) { 178 | return _ParentConnector(dispatchToProps, setFeduxToGlobal)( 179 | _ParentEnchancer(Component, enchancerName, reducers), 180 | propName, 181 | feduxName, 182 | getState, 183 | initialState 184 | ) 185 | } 186 | if ( 187 | Component.toString().match('createElement') && 188 | Component.toString().match('Fragment') 189 | ) { 190 | return _ParentChildScope()(Component, enchancerName, propName, getState) 191 | } 192 | } 193 | 194 | const _ParentChildScope = () => ( 195 | Component, 196 | _enchancerName, 197 | _propName, 198 | _state = {} 199 | ) => { 200 | const Enchancer = props => { 201 | Enchancer.displayName = _enchancerName || Enchancer.name 202 | _objectMerge(_propers, { ..._state() }) 203 | return ( 204 | <> 205 | 215 | 216 | ) 217 | } 218 | return Enchancer 219 | } 220 | 221 | const _store = () => { 222 | subscribe() 223 | return !!window.Store || !!global.Store !== undefined || null 224 | ? window.Store || global.Store 225 | : '' 226 | } 227 | 228 | const getState = () => 229 | _store() !== undefined || null ? _store() || clearTimeout(_holdOn) : '' 230 | 231 | const _hookFedux = _reference => ( 232 | (window.hookFedux = _reference), (global.hookFedux = _reference) 233 | ) 234 | 235 | const _updateOccurred = _state => 236 | (_holdOn = setTimeout( 237 | () => ( 238 | window.hookFedux.setState({ ..._state }), 239 | global.hookFedux.setState({ ..._state }) 240 | ), 241 | null 242 | )) 243 | 244 | const subscribe = listener => 245 | typeof listener === 'function' ? listener() : null 246 | 247 | const _objectMerge = (_propers, _state) => 248 | Object.assign(_propers, { ..._state }) 249 | 250 | export { 251 | connectWith as createStore, 252 | connectWith, 253 | getState, 254 | subscribe, 255 | dispatcher 256 | } 257 | -------------------------------------------------------------------------------- /src/store/reducers/main.reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | HANDLE_INPUT_RIDE, 3 | SAVE_TOKEN, 4 | HANDLE_START_STOP_RIDE, 5 | CONTROL_RIDE_BUTTON_TEXT, 6 | CONTROL_RIDE_INPUT_TEXT, 7 | HANDLE_PRELOADER, 8 | HANDLE_CODE, 9 | HANDLE_RIDE_BUTTON_TEXT, 10 | HANDLE_ERROR 11 | } from '../constants' 12 | import state from '../state/initialState' 13 | 14 | const mainReducer = actions => { 15 | switch (actions.type) { 16 | case HANDLE_INPUT_RIDE: 17 | return { 18 | inputRideValue: actions.payload 19 | } 20 | case SAVE_TOKEN: 21 | return { 22 | token: actions.payload 23 | } 24 | case CONTROL_RIDE_BUTTON_TEXT: 25 | return { 26 | textRide: actions.payload 27 | } 28 | case CONTROL_RIDE_INPUT_TEXT: 29 | return { 30 | textInputRide: actions.payload 31 | } 32 | case HANDLE_START_STOP_RIDE: 33 | return { 34 | inputStartStopRideValue: actions.payload 35 | } 36 | case HANDLE_PRELOADER: 37 | return { 38 | preloader: actions.payload 39 | } 40 | case HANDLE_CODE: 41 | return { 42 | code: actions.payload 43 | } 44 | case HANDLE_RIDE_BUTTON_TEXT: 45 | return { 46 | buttonStartStopText: actions.payload 47 | } 48 | case HANDLE_ERROR: 49 | return { 50 | error: actions.payload 51 | } 52 | default: 53 | return state 54 | } 55 | } 56 | 57 | export default mainReducer 58 | -------------------------------------------------------------------------------- /src/store/state/initialState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | inputRideValue: '', 3 | inputStartStopRideValue: '', 4 | token: '', 5 | textInputRide: 'ENTER EMAIL', 6 | textRide: 'RIDE', 7 | inputStartStopText: 'ENTER ANY SCOOTER ID', 8 | buttonStartStopText: 'FIND', 9 | preloader: false, 10 | code: '', 11 | error: '' 12 | } 13 | --------------------------------------------------------------------------------