├── .gitignore ├── README.md ├── capacitor.config.ts ├── ionic.config.json ├── package-lock.json ├── package.json ├── public ├── assets │ ├── icon │ │ ├── favicon.png │ │ └── icon.png │ └── shapes.svg ├── index.html └── manifest.json ├── src ├── App.test.tsx ├── App.tsx ├── Firebase.tsx ├── components │ ├── Map │ │ ├── Map.scss │ │ └── Map.tsx │ └── WorkerSelector │ │ ├── WorkerSelector.scss │ │ └── WorkerSelector.tsx ├── data │ └── workerList.tsx ├── index.tsx ├── pages │ ├── ConfirmTab │ │ ├── ConfirmTab.scss │ │ └── ConfirmTab.tsx │ ├── GetStarted │ │ ├── GetStarted.scss │ │ └── GetStarted.tsx │ ├── HomeTab │ │ ├── HomeTab.scss │ │ └── HomeTab.tsx │ ├── LoginTab │ │ ├── Login.scss │ │ └── Login.tsx │ ├── RootTab │ │ └── RootTab.tsx │ └── SearchTab │ │ ├── SearchTab.scss │ │ └── SearchTab.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── service-worker.ts ├── serviceWorkerRegistration.ts ├── setupTests.ts ├── theme │ └── variables.scss └── utilities │ └── useRouter │ └── useRouter.tsx └── tsconfig.json /.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 | .vscode 21 | .idea 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Optional eslint cache 28 | .eslintcache 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wuberapp-ionic6-react-and-firebase-app 2 | 3 | [DEMO](https://uberclone-ionic.vercel.app/) 4 | 5 | ## Packages installed 6 | 7 | 1. [sass - npm](https://www.npmjs.com/package/sass) 8 | 9 | ``` 10 | npm install sass 11 | ``` 12 | 13 | 2. [Add the Mapbox GL JS module](https://www.mapbox.com/install/js/bundler-install/) 14 | 15 | 3. [mapbox-gl-js Installation](https://docs.mapbox.com/mapbox-gl-js/guides/install/) 16 | 17 | ``` 18 | npm install mapbox-gl --save 19 | 20 | // & add 21 | // mapbox-gl in _app.js 22 | import "mapbox-gl/dist/mapbox-gl.css" 23 | 24 | // Include the GL JS CSS file in the of your HTML file. 25 | ``` 26 | 27 | ### References 28 | 29 | 1. [router-link](https://ionicframework.com/docs/v4/api/router-link) 30 | 31 | 2. [useRouter in Typescript](//https://codesandbox.io/s/3rwq8r85p?file=/src/useRouter.ts) 32 | 33 | 3. [“typescript get url params” Code Answer](https://www.codegrepper.com/code-examples/javascript/typescript++get+url+params) 34 | 35 | 4. [forceRefresh: bool](https://v5.reactrouter.com/web/api/BrowserRouter/forcerefresh-bool) 36 | 37 | 5. [Firebase Auth with React Typescript](https://javascript.plainenglish.io/firebase-auth-with-react-typescript-4b9d9605fa53) 38 | 39 | #### Tasks 40 | 41 | 1. Add Navigation [x] 42 | 2. Calculate the prices for the worker[x] 43 | 3. Add Login Page[x] 44 | 4. Add firebase[] 45 | 5. Implement Type safety using TypeScript. 46 | Protect Routes using Router Guard. 47 | Protect & Safely access Firebase credentials. 48 | Social Authentification using Firebase. 49 | Global access to Authenticated User object 50 | -------------------------------------------------------------------------------- /capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'io.ionic.starter', 5 | appName: 'wuberAppIonic', 6 | webDir: 'build', 7 | bundledWebRuntime: false 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wuberAppIonic", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wuberAppIonic", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@capacitor/app": "1.1.1", 7 | "@capacitor/core": "3.6.0", 8 | "@capacitor/haptics": "1.1.4", 9 | "@capacitor/keyboard": "1.2.3", 10 | "@capacitor/status-bar": "1.0.8", 11 | "@ionic/react": "^6.0.0", 12 | "@ionic/react-router": "^6.0.0", 13 | "@testing-library/jest-dom": "^5.11.9", 14 | "@testing-library/react": "^11.2.5", 15 | "@testing-library/user-event": "^12.6.3", 16 | "@types/jest": "^26.0.20", 17 | "@types/node": "^12.19.15", 18 | "@types/react": "^16.14.3", 19 | "@types/react-dom": "^16.9.10", 20 | "@types/react-router": "^5.1.11", 21 | "@types/react-router-dom": "^5.1.7", 22 | "firebase": "^9.8.4", 23 | "ionicons": "^5.4.0", 24 | "mapbox-gl": "^2.9.0", 25 | "react": "^17.0.1", 26 | "react-dom": "^17.0.1", 27 | "react-router": "^5.2.0", 28 | "react-router-dom": "^5.2.0", 29 | "react-scripts": "^5.0.0", 30 | "sass": "^1.52.3", 31 | "typescript": "^4.1.3", 32 | "web-vitals": "^0.2.4", 33 | "workbox-background-sync": "^5.1.4", 34 | "workbox-broadcast-update": "^5.1.4", 35 | "workbox-cacheable-response": "^5.1.4", 36 | "workbox-core": "^5.1.4", 37 | "workbox-expiration": "^5.1.4", 38 | "workbox-google-analytics": "^5.1.4", 39 | "workbox-navigation-preload": "^5.1.4", 40 | "workbox-precaching": "^5.1.4", 41 | "workbox-range-requests": "^5.1.4", 42 | "workbox-routing": "^5.1.4", 43 | "workbox-strategies": "^5.1.4", 44 | "workbox-streams": "^5.1.4" 45 | }, 46 | "scripts": { 47 | "start": "react-scripts start", 48 | "build": "react-scripts build", 49 | "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'", 50 | "eject": "react-scripts eject" 51 | }, 52 | "eslintConfig": { 53 | "extends": [ 54 | "react-app", 55 | "react-app/jest" 56 | ] 57 | }, 58 | "browserslist": { 59 | "production": [ 60 | ">0.2%", 61 | "not dead", 62 | "not op_mini all" 63 | ], 64 | "development": [ 65 | "last 1 chrome version", 66 | "last 1 firefox version", 67 | "last 1 safari version" 68 | ] 69 | }, 70 | "devDependencies": { 71 | "@capacitor/cli": "5.7.4" 72 | }, 73 | "description": "An Ionic project" 74 | } 75 | -------------------------------------------------------------------------------- /public/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cboy220/uberclone-ts-ionic-firebase/8f54725e74c4b3cb1ece251d583f702e1b7a0d56/public/assets/icon/favicon.png -------------------------------------------------------------------------------- /public/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cboy220/uberclone-ts-ionic-firebase/8f54725e74c4b3cb1ece251d583f702e1b7a0d56/public/assets/icon/icon.png -------------------------------------------------------------------------------- /public/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ionic App", 3 | "name": "My Ionic App", 4 | "icons": [ 5 | { 6 | "src": "assets/icon/favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "assets/icon/icon.png", 12 | "type": "image/png", 13 | "sizes": "512x512", 14 | "purpose": "maskable" 15 | } 16 | ], 17 | "start_url": ".", 18 | "display": "standalone", 19 | "theme_color": "#ffffff", 20 | "background_color": "#ffffff" 21 | } 22 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders without crashing', () => { 6 | const { baseElement } = render(); 7 | expect(baseElement).toBeDefined(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route, Switch } from "react-router-dom"; 2 | import { 3 | IonApp, 4 | setupIonicReact, 5 | } from "@ionic/react"; 6 | import { IonReactRouter } from "@ionic/react-router"; 7 | 8 | //Tabs 9 | import RootTab from "./pages/RootTab/RootTab"; 10 | // 11 | import HomeTab from "./pages/HomeTab/HomeTab"; 12 | import SearchTab from "./pages/SearchTab/SearchTab"; 13 | import ConfirmTab from "./pages/ConfirmTab/ConfirmTab"; 14 | 15 | /* Core CSS required for Ionic components to work properly */ 16 | import "@ionic/react/css/core.css"; 17 | 18 | /* Basic CSS for apps built with Ionic */ 19 | import "@ionic/react/css/normalize.css"; 20 | import "@ionic/react/css/structure.css"; 21 | import "@ionic/react/css/typography.css"; 22 | 23 | /* Optional CSS utils that can be commented out */ 24 | import "@ionic/react/css/padding.css"; 25 | import "@ionic/react/css/float-elements.css"; 26 | import "@ionic/react/css/text-alignment.css"; 27 | import "@ionic/react/css/text-transformation.css"; 28 | import "@ionic/react/css/flex-utils.css"; 29 | import "@ionic/react/css/display.css"; 30 | 31 | /* Theme variables */ 32 | import "./theme/variables.scss"; 33 | 34 | /**Mapbox-gl */ 35 | import "mapbox-gl/dist/mapbox-gl.css"; 36 | import Login from "./pages/LoginTab/Login"; 37 | 38 | setupIonicReact(); 39 | 40 | const App: React.FC = () => ( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | } 60 | /> 61 | 62 | 63 | 64 | ); 65 | 66 | export default App; 67 | -------------------------------------------------------------------------------- /src/Firebase.tsx: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | import { getAnalytics } from "firebase/analytics"; 4 | //Auth 5 | import {GoogleAuthProvider, getAuth} from 'firebase/auth'; 6 | // TODO: Add SDKs for Firebase products that you want to use 7 | // https://firebase.google.com/docs/web/setup#available-libraries 8 | 9 | // Your web app's Firebase configuration 10 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 11 | const firebaseConfig = { 12 | apiKey: "AIzaSyADscLF9ri8wA6SxWp4ep5NKYti-IloqHo", 13 | authDomain: "fixit-ionic-app.firebaseapp.com", 14 | projectId: "fixit-ionic-app", 15 | storageBucket: "fixit-ionic-app.appspot.com", 16 | messagingSenderId: "842200201597", 17 | appId: "1:842200201597:web:e0bed9e4c5acbe303ef414", 18 | measurementId: "G-KMRQTJJZY4", 19 | }; 20 | 21 | // Initialize Firebase 22 | const app = initializeApp(firebaseConfig); 23 | const analytics = getAnalytics(app); 24 | 25 | 26 | const provider = new GoogleAuthProvider(); 27 | 28 | // gets error when i try to export them all 29 | export const auth = getAuth(); 30 | 31 | //exported here so other files can have access to auth, provider and app 32 | export {app, provider}; 33 | -------------------------------------------------------------------------------- /src/components/Map/Map.scss: -------------------------------------------------------------------------------- 1 | .container__map{ 2 | height: 50%; 3 | max-height: 50vh; 4 | width: 100%; 5 | overflow-x: hidden; 6 | overflow-y: hidden; 7 | overflow: auto; 8 | position: fixed; 9 | } -------------------------------------------------------------------------------- /src/components/Map/Map.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //scss 3 | import "./Map.scss"; 4 | import { useEffect } from "react"; 5 | 6 | const mapboxgl = require("mapbox-gl"); 7 | 8 | mapboxgl.accessToken = 9 | "pk.eyJ1Ijoib21hcmFzaHplaW5ob205OCIsImEiOiJjbDRrMXY5c3MwN3ZpM2NxcHp3ZGVmN3ZyIn0.3Ziuh7Utama_wz_4s8qh2g"; 10 | 11 | //import passed map id 12 | interface MapsProps { 13 | name: string; 14 | pickupCoordinates: number[]; 15 | dropOffCoordinates: number[]; 16 | } 17 | 18 | const Map: React.FC = (props, { name }) => { 19 | //debug props 20 | // console.log(props); 21 | 22 | //initializes map 23 | useEffect(() => { 24 | const map = new mapboxgl.Map({ 25 | //important id for the container insert into div 26 | container: "map", 27 | style: 28 | "mapbox://styles/omarashzeinhom98/cl4k5xuzh002h14mtoho5qips?optimize=true", 29 | center: [31.239661,30.056156], 30 | zoom: 15 31 | }); 32 | if (props.pickupCoordinates) { 33 | addToMap(map, props.pickupCoordinates); 34 | } 35 | if (props.dropOffCoordinates) { 36 | addToMap(map, props.dropOffCoordinates); 37 | } 38 | if (props.pickupCoordinates && props.dropOffCoordinates) { 39 | map.fitBounds([props.dropOffCoordinates, props.pickupCoordinates, {}], { 40 | padding: 50, 41 | zoom: 10, 42 | }); 43 | } 44 | }, [props.pickupCoordinates, props.dropOffCoordinates]); 45 | 46 | const addToMap = (map: any, coordinates: any) => { 47 | // Set marker options. 48 | const marker = new mapboxgl.Marker({ 49 | color: "#568203", 50 | draggable: true, 51 | }) 52 | .setLngLat(coordinates) 53 | .addTo(map); 54 | }; 55 | 56 | // Pick Up & Drop Off Coordinates useEffect Displays props 57 | useEffect(() => { 58 | /*debug code */ 59 | //console.log(props); 60 | //console.log(props.pickupCoordinates); 61 | //console.log(props.dropOffCoordinates); 62 | }, [props.pickupCoordinates, props.dropOffCoordinates]); 63 | 64 | // 65 | 66 | return ( 67 |
72 | {name} 73 |
74 | ); 75 | }; 76 | 77 | export default Map; 78 | -------------------------------------------------------------------------------- /src/components/WorkerSelector/WorkerSelector.scss: -------------------------------------------------------------------------------- 1 | .worker__time { 2 | color: green; 3 | } 4 | 5 | ion-toggle { 6 | --handle-box-shadow: 0 3px 12px rgba(255, 0, 0, 0.6), 7 | 0 3px 1px rgba(50, 70, 255, 0.6); 8 | 9 | overflow: visible; 10 | 11 | contain: none; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/WorkerSelector/WorkerSelector.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonAvatar, 3 | IonImg, 4 | IonItem, 5 | IonItemDivider, 6 | IonList, 7 | IonText, 8 | } from "@ionic/react"; 9 | import React, { useState, useEffect } from "react"; 10 | import "./WorkerSelector.scss"; 11 | // Fake api data to test out functionality for now 12 | import workerList from "../../data/workerList"; 13 | 14 | interface MapsProps { 15 | pickupCoordinates: number[]; 16 | dropOffCoordinates: number[]; 17 | } 18 | 19 | const WorkerSelector: React.FC = ({ 20 | pickupCoordinates, 21 | dropOffCoordinates, 22 | }) => { 23 | // Make Sure Maps 24 | //console.log(pickupCoordinates,dropOffCoordinates) 25 | const [tripTime, setTripTime] = useState(0); 26 | 27 | useEffect(() => { 28 | const pickupdrive = `${pickupCoordinates[0]},${pickupCoordinates[1]}`; 29 | const dropoffdrive = `${dropOffCoordinates[0]},${dropOffCoordinates[1]}`; 30 | const access_token = `?access_token=pk.eyJ1Ijoib21hcmFzaHplaW5ob205OCIsImEiOiJjbDRrMXY5c3MwN3ZpM2NxcHp3ZGVmN3ZyIn0.3Ziuh7Utama_wz_4s8qh2g`; 31 | const apidirections = `https://api.mapbox.com/directions/v5/mapbox/driving/${pickupdrive};${dropoffdrive}${access_token}`; 32 | //console.log(`${htt}`) 33 | 34 | //get trip time from map box api 35 | //2pickup coordinates and 2 points for the dropoff location [x] 36 | const tripTime = fetch(`${[apidirections]}`) 37 | .then((response) => response.json()) 38 | .then((data) => { 39 | setTripTime(data.routes[0].duration / 100); 40 | }); 41 | //these are added to the dependency array to show the setTripTime dont remove or it will not be shown 42 | }, [pickupCoordinates, dropOffCoordinates]); 43 | 44 | return ( 45 | 46 | {workerList.map((worker, index) => ( 47 | 48 | 49 | 50 | 51 | 52 |

{worker.service}

53 | 15 mins away 54 |
55 | 56 |

{"$" + (tripTime * worker.multiplier).toFixed(2)}

57 |
58 |
59 | ))} 60 | 61 | 62 |
63 | ); 64 | }; 65 | 66 | export default WorkerSelector; 67 | -------------------------------------------------------------------------------- /src/data/workerList.tsx: -------------------------------------------------------------------------------- 1 | const workerList = [ 2 | { 3 | imgUrl: 4 | "https://res.cloudinary.com/dxgqvvg0z/image/upload/v1656005176/FIXITAPP/nextjs-app-images/confirm/worker-svgrepo-com_vipu1n.svg", 5 | service: "Worker", 6 | multiplier: 1, 7 | }, 8 | 9 | { 10 | imgUrl: 11 | "https://res.cloudinary.com/dxgqvvg0z/image/upload/v1656005555/FIXITAPP/ionic-app-images/confirm/worker-svgrepo-com_5_d2cql2.svg", 12 | service: "Female Worker(Females Only)", 13 | multiplier: 1, 14 | }, 15 | 16 | { 17 | imgUrl: 18 | "https://res.cloudinary.com/dxgqvvg0z/image/upload/v1656005313/FIXITAPP/ionic-app-images/confirm/worker-svgrepo-com_1_qldnrx.svg", 19 | service: "Worker+", 20 | multiplier: 1.5, 21 | }, 22 | { 23 | imgUrl: 24 | "https://res.cloudinary.com/dxgqvvg0z/image/upload/v1656005176/FIXITAPP/nextjs-app-images/confirm/worker-svgrepo-com_3_efxhpc.svg", 25 | service: "Worker Female+", 26 | multiplier: 1.5, 27 | }, 28 | { 29 | imgUrl: 30 | "https://res.cloudinary.com/dxgqvvg0z/image/upload/v1656005176/FIXITAPP/nextjs-app-images/confirm/worker-svgrepo-com_4_rl35jn.svg", 31 | service: "Multiple Workers rate increases by 1 to be split upon workers", 32 | multiplier: 2, 33 | }, 34 | ]; 35 | 36 | export default workerList; 37 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorkerRegistration from './serviceWorkerRegistration'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | 8 | 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | // If you want your app to work offline and load faster, you can change 18 | // unregister() to register() below. Note this comes with some pitfalls. 19 | // Learn more about service workers: https://cra.link/PWA 20 | serviceWorkerRegistration.unregister(); 21 | 22 | // If you want to start measuring performance in your app, pass a function 23 | // to log results (for example: reportWebVitals(console.log)) 24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 25 | reportWebVitals(); 26 | -------------------------------------------------------------------------------- /src/pages/ConfirmTab/ConfirmTab.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cboy220/uberclone-ts-ionic-firebase/8f54725e74c4b3cb1ece251d583f702e1b7a0d56/src/pages/ConfirmTab/ConfirmTab.scss -------------------------------------------------------------------------------- /src/pages/ConfirmTab/ConfirmTab.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonBackButton, 3 | IonButton, 4 | IonButtons, 5 | IonContent, 6 | IonGrid, 7 | IonHeader, 8 | IonPage, 9 | IonRefresherContent, 10 | IonText, 11 | IonTitle, 12 | IonToolbar, 13 | } from "@ionic/react"; 14 | import "./ConfirmTab.scss"; 15 | import Map from "../../components/Map/Map"; 16 | import { useEffect, useState } from "react"; 17 | // 18 | //import { useRouter } from "../../utilities/useRouter/useRouter"; 19 | import WorkerSelector from "../../components/WorkerSelector/WorkerSelector"; 20 | 21 | interface MapsProps { 22 | name: string; 23 | pickupCoordinates: number[]; 24 | dropOffCoordinates: number[]; 25 | } 26 | 27 | const ConfirmTab: React.FC = () => { 28 | //Defined router from utils 29 | //const router = useRouter(); 30 | // used query here as a string with window & @@params 31 | const queryString = window.location.search; 32 | const urlParams = new URLSearchParams(queryString); 33 | // Get the drop off and pickup 34 | const dropoff = urlParams.get("dropoff"); 35 | const pickUp = urlParams.get("pickup"); 36 | // Debug the drop off and 37 | //console.log("PickUp:", pickUp); 38 | //console.log("DropOff:", dropoff); 39 | 40 | //debug router 41 | //console.log(router); 42 | 43 | //debug props 44 | //console.log(props); 45 | 46 | const [pickupCoordinates, setPickupCoordinates] = useState([0,0]); 47 | const [dropOffCoordinates, setDropOffCoordinates] = useState([0,0]); 48 | 49 | //get PickupCoordinates Start 50 | const getPickUpCoordinates = () => { 51 | // Fetch Function 52 | 53 | fetch( 54 | `https://api.mapbox.com/geocoding/v5/mapbox.places/${pickUp}.json?` + 55 | new URLSearchParams({ 56 | access_token: 57 | "pk.eyJ1Ijoib21hcmFzaHplaW5ob205OCIsImEiOiJjbDRrMXY5c3MwN3ZpM2NxcHp3ZGVmN3ZyIn0.3Ziuh7Utama_wz_4s8qh2g", 58 | }) 59 | ) 60 | .then((response) => response.json()) 61 | .then((data) => { 62 | setPickupCoordinates(data.features[0].center); 63 | }); 64 | }; 65 | //get PickupCoordinates end 66 | 67 | // get DropOffCoordinates Start 68 | const getDropOffCoordinates = () => { 69 | // Fetch Function 70 | fetch( 71 | `https://api.mapbox.com/geocoding/v5/mapbox.places/${dropoff}.json?` + 72 | new URLSearchParams({ 73 | access_token: 74 | "pk.eyJ1Ijoib21hcmFzaHplaW5ob205OCIsImEiOiJjbDRrMXY5c3MwN3ZpM2NxcHp3ZGVmN3ZyIn0.3Ziuh7Utama_wz_4s8qh2g", 75 | }) 76 | ) 77 | .then((response) => response.json()) 78 | .then((data) => { 79 | setDropOffCoordinates(data.features[0].center); 80 | }); 81 | }; 82 | //debug useState 83 | //console.log(pickupCoordinates, dropOffCoordinates); 84 | //call functions with useEffect 85 | 86 | useEffect(() => { 87 | getPickUpCoordinates(); 88 | getDropOffCoordinates(); 89 | //console.log(pickUp, dropoff); 90 | /** 91 | * return () => { 92 | ; 93 | }; 94 | */ 95 | }, [pickUp, dropoff]); 96 | 97 | return ( 98 | 99 | 100 | 101 | Confirm 102 | 103 | 104 | 105 | 106 | 107 | 111 | 112 | 113 | Confirm 114 | 115 | 116 | 117 | 122 | 123 | 124 | {/**Worker Selector */} 125 | 126 |

Confirm Worker

127 |
128 | 132 | {/**Confirm Button */} 133 | 134 | Confirm Worker Reservation 135 | 136 |
137 |
138 |
139 | ); 140 | }; 141 | 142 | export default ConfirmTab; 143 | -------------------------------------------------------------------------------- /src/pages/GetStarted/GetStarted.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cboy220/uberclone-ts-ionic-firebase/8f54725e74c4b3cb1ece251d583f702e1b7a0d56/src/pages/GetStarted/GetStarted.scss -------------------------------------------------------------------------------- /src/pages/GetStarted/GetStarted.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./GetStarted.scss"; 3 | 4 | const GetStarted = () => { 5 | return
GetStarted
; 6 | }; 7 | 8 | export default GetStarted; 9 | -------------------------------------------------------------------------------- /src/pages/HomeTab/HomeTab.scss: -------------------------------------------------------------------------------- 1 | .fixit__header__logo { 2 | width: 100%; 3 | height: 100%; 4 | max-height: 150px; 5 | max-width: 150px; 6 | } 7 | 8 | .fixit__header__avatar { 9 | background-color: #000000; 10 | background-image: linear-gradient(315deg, #000000 0%, #414141 74%); 11 | border-radius: 28px; 12 | width: 100%; 13 | height: 100%; 14 | max-width: 150px; 15 | max-height: 150px; 16 | padding-bottom: 1px; 17 | 18 | cursor: pointer; 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/HomeTab/HomeTab.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonAvatar, 3 | IonButton, 4 | IonCard, 5 | IonCardContent, 6 | IonCardSubtitle, 7 | IonCol, 8 | IonContent, 9 | IonGrid, 10 | IonHeader, 11 | IonImg, 12 | IonPage, 13 | IonRouterLink, 14 | IonRow, 15 | IonTitle, 16 | IonToolbar, 17 | } from "@ionic/react"; 18 | //Scss 19 | import "./HomeTab.scss"; 20 | //Components 21 | import Map from "../../components/Map/Map"; 22 | //Custom function useRouter like next js 23 | import { useRouter } from "../../utilities/useRouter/useRouter"; 24 | 25 | //React 26 | import { useState, useEffect } from "react"; 27 | import { useHistory } from "react-router"; 28 | 29 | //FireBase 30 | import { onAuthStateChanged, signOut, User } from "firebase/auth"; 31 | //FireBase File in root folder 32 | import { auth } from "../../Firebase"; 33 | 34 | interface MapsProps { 35 | pickupCoordinates: number[]; 36 | dropOffCoordinates: number[]; 37 | username: string; 38 | password: string; 39 | prevState: null; 40 | name: string; 41 | } 42 | 43 | const HomeTab: React.FC = () => { 44 | const [user, setUser] = useState(null); 45 | 46 | console.log(user); 47 | console.log(setUser); 48 | 49 | useEffect(() => { 50 | return onAuthStateChanged(auth, (user) => { 51 | if (user != null) { 52 | //history.push('/'); 53 | setUser({ 54 | name: user.displayName, 55 | photoUrl: user.photoURL, 56 | }); 57 | } else { 58 | setUser(null); 59 | //history.push("/login"); 60 | } 61 | }); 62 | }, []); 63 | 64 | return ( 65 | 66 | 67 | 68 | Home 69 | 70 | 71 | 72 | 77 | {/**Header */} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Worker 87 | 88 | 89 | {/**{user && user.phototUrl} */} 90 | signOut(auth)} 93 | /> 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Profession 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Reserve 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Where to? 133 | 134 | 135 | 136 | 137 | 138 | {/**Action Btns */} 139 | 140 | {/**input btn */} 141 | 142 | 143 | 144 | 145 | 146 | 147 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | {user && user.name} 163 | 164 | 165 | 166 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | ); 179 | }; 180 | 181 | export default HomeTab; 182 | -------------------------------------------------------------------------------- /src/pages/LoginTab/Login.scss: -------------------------------------------------------------------------------- 1 | .login__icon{ 2 | max-width: 75px; 3 | max-height: 75px; 4 | width: 100%; 5 | height: 100%; 6 | object-fit: contain; 7 | } 8 | 9 | 10 | 11 | .login__svg{ 12 | max-height: 350px; 13 | height: 100%; 14 | width: 100%; 15 | border-radius: 8px; 16 | justify-content: center; 17 | left: 50%; 18 | margin-bottom: 0.4rem; 19 | } -------------------------------------------------------------------------------- /src/pages/LoginTab/Login.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonButton, 3 | IonCol, 4 | IonContent, 5 | IonGrid, 6 | IonHeader, 7 | IonImg, 8 | IonPage, 9 | IonRouterLink, 10 | IonRow, 11 | IonTitle, 12 | IonToolbar, 13 | } from "@ionic/react"; 14 | import React, { useEffect } from "react"; 15 | import "./Login.scss"; 16 | //Custom use router 17 | import { useRouter } from "../../utilities/useRouter/useRouter"; 18 | //Sign with popup imported directly from firebase auth 19 | import { signInWithPopup, onAuthStateChanged, getAuth } from "firebase/auth"; 20 | //from Firebase.tsx file in root folder 21 | import { auth, provider } from "../../Firebase"; 22 | import { useHistory } from "react-router"; 23 | // 24 | 25 | const Login = () => { 26 | const history = useHistory(); 27 | console.log(history); 28 | 29 | useEffect(() => { 30 | return onAuthStateChanged(auth, (user) => { 31 | if (user) { 32 | history.push("/"); 33 | setUser({ 34 | name: user.displayName, 35 | photoUrl: user.photoURL, 36 | }); 37 | } 38 | }); 39 | }); 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | Login 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 65 | 66 | signInWithPopup(auth, provider)} 70 | > 71 | Sign in with Google 72 | 73 | 74 | 75 | 76 | Forgot Password ? 77 | 78 | 79 | 80 | Register account 81 | 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | }; 89 | 90 | export default Login; 91 | 92 | 93 | function setUser(arg0: { name: string | null; photoUrl: string | null }) { 94 | throw new Error("Function not implemented."); 95 | } 96 | -------------------------------------------------------------------------------- /src/pages/RootTab/RootTab.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect, Route } from "react-router-dom"; 3 | import { 4 | IonIcon, 5 | IonLabel, 6 | IonRouterOutlet, 7 | IonTabBar, 8 | IonTabButton, 9 | IonTabs, 10 | } from "@ionic/react"; 11 | import { search, map, checkmarkDone, logInSharp } from "ionicons/icons"; 12 | import HomeTab from "../HomeTab/HomeTab"; 13 | import SearchTab from "../SearchTab/SearchTab"; 14 | import ConfirmTab from "../ConfirmTab/ConfirmTab"; 15 | import Login from "../LoginTab/Login"; 16 | 17 | // import Tab1Detail from './Tab1Detail'; 18 | 19 | const RootTab: React.FC = () => ( 20 | 21 | 22 | 23 | 24 | 25 | {/**Change Login tab to a sidemenu item */} 26 | } 29 | exact={true} 30 | /> 31 | } 34 | exact={true} 35 | /> 36 | 37 | 38 | 39 | {/**Change Login tab to a sidemenu item */} 40 | 41 | 42 | 43 | 44 | 45 | Home 46 | 47 | 48 | 49 | Search 50 | 51 | 52 | 53 | Confirm 54 | 55 | 56 | 57 | ); 58 | 59 | export default React.memo(RootTab); 60 | -------------------------------------------------------------------------------- /src/pages/SearchTab/SearchTab.scss: -------------------------------------------------------------------------------- 1 | .search__ion__input { 2 | background: gainsboro; 3 | width: 100%; 4 | max-width: 100%; 5 | margin-bottom: 0.2rem; 6 | font-size: 1rem; 7 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 8 | transition: ease-in-out 0.9s; 9 | margin-top: 0.5rem; 10 | } 11 | 12 | .search__ion__input:hover { 13 | background: rgb(32, 156, 1); 14 | } 15 | 16 | .search__ion__line__icon { 17 | max-width: 15px; 18 | width: 100%; 19 | height: 100%; 20 | max-height: 25px; 21 | } 22 | 23 | /*Icons*/ 24 | .circle__ion__icon { 25 | size: 1.5rem; 26 | color: blue; 27 | transition: 0.9s ease-in-out; 28 | } 29 | 30 | .circle__ion__icon__success { 31 | size: 1.5rem; 32 | color: green; 33 | transition: 0.9s ease-in-out; 34 | } 35 | 36 | .circle__ion__icon:hover { 37 | transform: scale(1.5); 38 | } 39 | 40 | .circle__ion__icon__success:hover { 41 | transform: scale(1.5); 42 | } 43 | 44 | .add__ion__icon { 45 | size: 3rem; 46 | margin-top: 1.5rem; 47 | font-size: 3rem; 48 | padding: 0.5rem; 49 | width: 100%; 50 | height: 100%; 51 | max-height: 35px; 52 | max-width: 35px; 53 | transition: 0.9s ease-in-out; 54 | } 55 | 56 | .add__ion__icon:hover { 57 | transform: scale(1.5); 58 | } 59 | 60 | .line__ion__icon { 61 | padding-right: 3rem; 62 | width: 100%; 63 | height: 100%; 64 | max-height: 35px; 65 | max-width: 150px; 66 | color: black; 67 | } 68 | -------------------------------------------------------------------------------- /src/pages/SearchTab/SearchTab.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonBackButton, 3 | IonButton, 4 | IonButtons, 5 | IonCol, 6 | IonContent, 7 | IonGrid, 8 | IonHeader, 9 | IonIcon, 10 | IonImg, 11 | IonInput, 12 | IonItemDivider, 13 | IonPage, 14 | IonRow, 15 | IonTitle, 16 | IonToolbar, 17 | } from "@ionic/react"; 18 | import { 19 | addCircleOutline, 20 | ellipsisHorizontalCircle, 21 | ellipsisHorizontalCircleOutline, 22 | star, 23 | } from "ionicons/icons"; 24 | import "./SearchTab.scss"; 25 | 26 | //React imports 27 | import React from "react"; 28 | import { useState } from "react"; 29 | //import { useRouter } from "../../utilities/useRouter/useRouter"; 30 | 31 | //React Router Dom 32 | import { Link } from "react-router-dom"; 33 | 34 | interface MapsProps { 35 | name: string; 36 | } 37 | 38 | const SearchTab: React.FC = (/*props*/) => { 39 | //debug router 40 | //const router = useRouter(); 41 | //console.log(router); 42 | 43 | //debug props 44 | //console.log(props); 45 | 46 | const [homeAddrrQuery, setHomeAddrQuery] = useState(""); 47 | const [workerAddrrQuery, setWorkerAddrQuery] = useState(""); 48 | 49 | console.log(homeAddrrQuery, workerAddrrQuery); 50 | console.log(setHomeAddrQuery); 51 | console.log(setWorkerAddrQuery); 52 | 53 | // Set location const for home and worker addresses. 54 | const location = { 55 | pathname: `/tabs/confirm?pickup=${homeAddrrQuery}&dropoff=${workerAddrrQuery}`, 56 | state: { 57 | query: { 58 | pickup: homeAddrrQuery, 59 | dropoff: workerAddrrQuery, 60 | }, 61 | }, 62 | staticContext: true, 63 | }; 64 | 65 | class LocationLink extends React.Component { 66 | render() { 67 | return ( 68 | 69 | 70 | Confirm worker reservation 71 | 72 | 73 | ); 74 | } 75 | } 76 | 77 | return ( 78 | 79 | 80 | 81 | Search 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 97 |
98 | 102 |
103 | 108 |
109 | 110 | 111 | 116 | setHomeAddrQuery(event.detail.value!) 117 | } 118 | /> 119 | 120 | 125 | setWorkerAddrQuery(event.detail.value!) 126 | } 127 | /> 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | Saved Places 136 | 137 | 138 |
139 | {/**Confirm WorkerReservation Link */} 140 | 141 | {/**Confirm WorkerReservation Link */} 142 |
143 |
144 |
145 | ); 146 | }; 147 | 148 | export default SearchTab; 149 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/service-worker.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /* eslint-disable no-restricted-globals */ 3 | 4 | // This service worker can be customized! 5 | // See https://developers.google.com/web/tools/workbox/modules 6 | // for the list of available Workbox modules, or add any other 7 | // code you'd like. 8 | // You can also remove this file if you'd prefer not to use a 9 | // service worker, and the Workbox build step will be skipped. 10 | 11 | import { clientsClaim } from 'workbox-core'; 12 | import { ExpirationPlugin } from 'workbox-expiration'; 13 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; 14 | import { registerRoute } from 'workbox-routing'; 15 | import { StaleWhileRevalidate } from 'workbox-strategies'; 16 | 17 | declare const self: ServiceWorkerGlobalScope; 18 | 19 | clientsClaim(); 20 | 21 | // Precache all of the assets generated by your build process. 22 | // Their URLs are injected into the manifest variable below. 23 | // This variable must be present somewhere in your service worker file, 24 | // even if you decide not to use precaching. See https://cra.link/PWA 25 | precacheAndRoute(self.__WB_MANIFEST); 26 | 27 | // Set up App Shell-style routing, so that all navigation requests 28 | // are fulfilled with your index.html shell. Learn more at 29 | // https://developers.google.com/web/fundamentals/architecture/app-shell 30 | const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); 31 | registerRoute( 32 | // Return false to exempt requests from being fulfilled by index.html. 33 | ({ request, url }: { request: Request; url: URL }) => { 34 | // If this isn't a navigation, skip. 35 | if (request.mode !== 'navigate') { 36 | return false; 37 | } 38 | 39 | // If this is a URL that starts with /_, skip. 40 | if (url.pathname.startsWith('/_')) { 41 | return false; 42 | } 43 | 44 | // If this looks like a URL for a resource, because it contains 45 | // a file extension, skip. 46 | if (url.pathname.match(fileExtensionRegexp)) { 47 | return false; 48 | } 49 | 50 | // Return true to signal that we want to use the handler. 51 | return true; 52 | }, 53 | createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') 54 | ); 55 | 56 | // An example runtime caching route for requests that aren't handled by the 57 | // precache, in this case same-origin .png requests like those from in public/ 58 | registerRoute( 59 | // Add in any other file extensions or routing criteria as needed. 60 | ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), 61 | // Customize this strategy as needed, e.g., by changing to CacheFirst. 62 | new StaleWhileRevalidate({ 63 | cacheName: 'images', 64 | plugins: [ 65 | // Ensure that once this runtime cache reaches a maximum size the 66 | // least-recently used images are removed. 67 | new ExpirationPlugin({ maxEntries: 50 }), 68 | ], 69 | }) 70 | ); 71 | 72 | // This allows the web app to trigger skipWaiting via 73 | // registration.waiting.postMessage({type: 'SKIP_WAITING'}) 74 | self.addEventListener('message', (event) => { 75 | if (event.data && event.data.type === 'SKIP_WAITING') { 76 | self.skipWaiting(); 77 | } 78 | }); 79 | 80 | // Any other custom service worker logic can go here. 81 | -------------------------------------------------------------------------------- /src/serviceWorkerRegistration.ts: -------------------------------------------------------------------------------- 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://cra.link/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.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) 19 | ); 20 | 21 | type Config = { 22 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 23 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 24 | }; 25 | 26 | export function register(config?: Config) { 27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 28 | // The URL constructor is available in all browsers that support SW. 29 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 30 | if (publicUrl.origin !== window.location.origin) { 31 | // Our service worker won't work if PUBLIC_URL is on a different origin 32 | // from what our page is served on. This might happen if a CDN is used to 33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 34 | return; 35 | } 36 | 37 | window.addEventListener('load', () => { 38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 39 | 40 | if (isLocalhost) { 41 | // This is running on localhost. Let's check if a service worker still exists or not. 42 | checkValidServiceWorker(swUrl, config); 43 | 44 | // Add some additional logging to localhost, pointing developers to the 45 | // service worker/PWA documentation. 46 | navigator.serviceWorker.ready.then(() => { 47 | console.log( 48 | 'This web app is being served cache-first by a service ' + 49 | 'worker. To learn more, visit https://cra.link/PWA' 50 | ); 51 | }); 52 | } else { 53 | // Is not localhost. Just register service worker 54 | registerValidSW(swUrl, config); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | function registerValidSW(swUrl: string, config?: Config) { 61 | navigator.serviceWorker 62 | .register(swUrl) 63 | .then((registration) => { 64 | registration.onupdatefound = () => { 65 | const installingWorker = registration.installing; 66 | if (installingWorker == null) { 67 | return; 68 | } 69 | installingWorker.onstatechange = () => { 70 | if (installingWorker.state === 'installed') { 71 | if (navigator.serviceWorker.controller) { 72 | // At this point, the updated precached content has been fetched, 73 | // but the previous service worker will still serve the older 74 | // content until all client tabs are closed. 75 | console.log( 76 | 'New content is available and will be used when all ' + 77 | 'tabs for this page are closed. See https://cra.link/PWA.' 78 | ); 79 | 80 | // Execute callback 81 | if (config && config.onUpdate) { 82 | config.onUpdate(registration); 83 | } 84 | } else { 85 | // At this point, everything has been precached. 86 | // It's the perfect time to display a 87 | // "Content is cached for offline use." message. 88 | console.log('Content is cached for offline use.'); 89 | 90 | // Execute callback 91 | if (config && config.onSuccess) { 92 | config.onSuccess(registration); 93 | } 94 | } 95 | } 96 | }; 97 | }; 98 | }) 99 | .catch((error) => { 100 | console.error('Error during service worker registration:', error); 101 | }); 102 | } 103 | 104 | function checkValidServiceWorker(swUrl: string, config?: Config) { 105 | // Check if the service worker can be found. If it can't reload the page. 106 | fetch(swUrl, { 107 | headers: { 'Service-Worker': 'script' }, 108 | }) 109 | .then((response) => { 110 | // Ensure service worker exists, and that we really are getting a JS file. 111 | const contentType = response.headers.get('content-type'); 112 | if ( 113 | response.status === 404 || 114 | (contentType != null && contentType.indexOf('javascript') === -1) 115 | ) { 116 | // No service worker found. Probably a different app. Reload the page. 117 | navigator.serviceWorker.ready.then((registration) => { 118 | registration.unregister().then(() => { 119 | window.location.reload(); 120 | }); 121 | }); 122 | } else { 123 | // Service worker found. Proceed as normal. 124 | registerValidSW(swUrl, config); 125 | } 126 | }) 127 | .catch(() => { 128 | console.log('No internet connection found. App is running in offline mode.'); 129 | }); 130 | } 131 | 132 | export function unregister() { 133 | if ('serviceWorker' in navigator) { 134 | navigator.serviceWorker.ready 135 | .then((registration) => { 136 | registration.unregister(); 137 | }) 138 | .catch((error) => { 139 | console.error(error.message); 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | 7 | // Mock matchmedia 8 | window.matchMedia = window.matchMedia || function() { 9 | return { 10 | matches: false, 11 | addListener: function() {}, 12 | removeListener: function() {} 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | /* Ionic Variables and Theming. For more info, please see: 2 | http://ionicframework.com/docs/theming/ */ 3 | 4 | /** Ionic CSS Variables **/ 5 | :root { 6 | /** primary **/ 7 | --ion-color-primary: #3880ff; 8 | --ion-color-primary-rgb: 56, 128, 255; 9 | --ion-color-primary-contrast: #ffffff; 10 | --ion-color-primary-contrast-rgb: 255, 255, 255; 11 | --ion-color-primary-shade: #3171e0; 12 | --ion-color-primary-tint: #4c8dff; 13 | 14 | /** secondary **/ 15 | --ion-color-secondary: #3dc2ff; 16 | --ion-color-secondary-rgb: 61, 194, 255; 17 | --ion-color-secondary-contrast: #ffffff; 18 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 19 | --ion-color-secondary-shade: #36abe0; 20 | --ion-color-secondary-tint: #50c8ff; 21 | 22 | /** tertiary **/ 23 | --ion-color-tertiary: #5260ff; 24 | --ion-color-tertiary-rgb: 82, 96, 255; 25 | --ion-color-tertiary-contrast: #ffffff; 26 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 27 | --ion-color-tertiary-shade: #4854e0; 28 | --ion-color-tertiary-tint: #6370ff; 29 | 30 | /** success **/ 31 | --ion-color-success: #2dd36f; 32 | --ion-color-success-rgb: 45, 211, 111; 33 | --ion-color-success-contrast: #ffffff; 34 | --ion-color-success-contrast-rgb: 255, 255, 255; 35 | --ion-color-success-shade: #28ba62; 36 | --ion-color-success-tint: #42d77d; 37 | 38 | /** warning **/ 39 | --ion-color-warning: #ffc409; 40 | --ion-color-warning-rgb: 255, 196, 9; 41 | --ion-color-warning-contrast: #000000; 42 | --ion-color-warning-contrast-rgb: 0, 0, 0; 43 | --ion-color-warning-shade: #e0ac08; 44 | --ion-color-warning-tint: #ffca22; 45 | 46 | /** danger **/ 47 | --ion-color-danger: #eb445a; 48 | --ion-color-danger-rgb: 235, 68, 90; 49 | --ion-color-danger-contrast: #ffffff; 50 | --ion-color-danger-contrast-rgb: 255, 255, 255; 51 | --ion-color-danger-shade: #cf3c4f; 52 | --ion-color-danger-tint: #ed576b; 53 | 54 | /** dark **/ 55 | --ion-color-dark: #222428; 56 | --ion-color-dark-rgb: 34, 36, 40; 57 | --ion-color-dark-contrast: #ffffff; 58 | --ion-color-dark-contrast-rgb: 255, 255, 255; 59 | --ion-color-dark-shade: #1e2023; 60 | --ion-color-dark-tint: #383a3e; 61 | 62 | /** medium **/ 63 | --ion-color-medium: #92949c; 64 | --ion-color-medium-rgb: 146, 148, 156; 65 | --ion-color-medium-contrast: #ffffff; 66 | --ion-color-medium-contrast-rgb: 255, 255, 255; 67 | --ion-color-medium-shade: #808289; 68 | --ion-color-medium-tint: #9d9fa6; 69 | 70 | /** light **/ 71 | --ion-color-light: #f4f5f8; 72 | --ion-color-light-rgb: 244, 245, 248; 73 | --ion-color-light-contrast: #000000; 74 | --ion-color-light-contrast-rgb: 0, 0, 0; 75 | --ion-color-light-shade: #d7d8da; 76 | --ion-color-light-tint: #f5f6f9; 77 | } 78 | 79 | @media (prefers-color-scheme: dark) { 80 | /* 81 | * Dark Colors 82 | * ------------------------------------------- 83 | */ 84 | 85 | body { 86 | --ion-color-primary: #428cff; 87 | --ion-color-primary-rgb: 66,140,255; 88 | --ion-color-primary-contrast: #ffffff; 89 | --ion-color-primary-contrast-rgb: 255,255,255; 90 | --ion-color-primary-shade: #3a7be0; 91 | --ion-color-primary-tint: #5598ff; 92 | 93 | --ion-color-secondary: #50c8ff; 94 | --ion-color-secondary-rgb: 80,200,255; 95 | --ion-color-secondary-contrast: #ffffff; 96 | --ion-color-secondary-contrast-rgb: 255,255,255; 97 | --ion-color-secondary-shade: #46b0e0; 98 | --ion-color-secondary-tint: #62ceff; 99 | 100 | --ion-color-tertiary: #6a64ff; 101 | --ion-color-tertiary-rgb: 106,100,255; 102 | --ion-color-tertiary-contrast: #ffffff; 103 | --ion-color-tertiary-contrast-rgb: 255,255,255; 104 | --ion-color-tertiary-shade: #5d58e0; 105 | --ion-color-tertiary-tint: #7974ff; 106 | 107 | --ion-color-success: #2fdf75; 108 | --ion-color-success-rgb: 47,223,117; 109 | --ion-color-success-contrast: #000000; 110 | --ion-color-success-contrast-rgb: 0,0,0; 111 | --ion-color-success-shade: #29c467; 112 | --ion-color-success-tint: #44e283; 113 | 114 | --ion-color-warning: #ffd534; 115 | --ion-color-warning-rgb: 255,213,52; 116 | --ion-color-warning-contrast: #000000; 117 | --ion-color-warning-contrast-rgb: 0,0,0; 118 | --ion-color-warning-shade: #e0bb2e; 119 | --ion-color-warning-tint: #ffd948; 120 | 121 | --ion-color-danger: #ff4961; 122 | --ion-color-danger-rgb: 255,73,97; 123 | --ion-color-danger-contrast: #ffffff; 124 | --ion-color-danger-contrast-rgb: 255,255,255; 125 | --ion-color-danger-shade: #e04055; 126 | --ion-color-danger-tint: #ff5b71; 127 | 128 | --ion-color-dark: #f4f5f8; 129 | --ion-color-dark-rgb: 244,245,248; 130 | --ion-color-dark-contrast: #000000; 131 | --ion-color-dark-contrast-rgb: 0,0,0; 132 | --ion-color-dark-shade: #d7d8da; 133 | --ion-color-dark-tint: #f5f6f9; 134 | 135 | --ion-color-medium: #989aa2; 136 | --ion-color-medium-rgb: 152,154,162; 137 | --ion-color-medium-contrast: #000000; 138 | --ion-color-medium-contrast-rgb: 0,0,0; 139 | --ion-color-medium-shade: #86888f; 140 | --ion-color-medium-tint: #a2a4ab; 141 | 142 | --ion-color-light: #222428; 143 | --ion-color-light-rgb: 34,36,40; 144 | --ion-color-light-contrast: #ffffff; 145 | --ion-color-light-contrast-rgb: 255,255,255; 146 | --ion-color-light-shade: #1e2023; 147 | --ion-color-light-tint: #383a3e; 148 | } 149 | 150 | /* 151 | * iOS Dark Theme 152 | * ------------------------------------------- 153 | */ 154 | 155 | .ios body { 156 | --ion-background-color: #000000; 157 | --ion-background-color-rgb: 0,0,0; 158 | 159 | --ion-text-color: #ffffff; 160 | --ion-text-color-rgb: 255,255,255; 161 | 162 | --ion-color-step-50: #0d0d0d; 163 | --ion-color-step-100: #1a1a1a; 164 | --ion-color-step-150: #262626; 165 | --ion-color-step-200: #333333; 166 | --ion-color-step-250: #404040; 167 | --ion-color-step-300: #4d4d4d; 168 | --ion-color-step-350: #595959; 169 | --ion-color-step-400: #666666; 170 | --ion-color-step-450: #737373; 171 | --ion-color-step-500: #808080; 172 | --ion-color-step-550: #8c8c8c; 173 | --ion-color-step-600: #999999; 174 | --ion-color-step-650: #a6a6a6; 175 | --ion-color-step-700: #b3b3b3; 176 | --ion-color-step-750: #bfbfbf; 177 | --ion-color-step-800: #cccccc; 178 | --ion-color-step-850: #d9d9d9; 179 | --ion-color-step-900: #e6e6e6; 180 | --ion-color-step-950: #f2f2f2; 181 | 182 | --ion-item-background: #000000; 183 | 184 | --ion-card-background: #1c1c1d; 185 | } 186 | 187 | .ios ion-modal { 188 | --ion-background-color: var(--ion-color-step-100); 189 | --ion-toolbar-background: var(--ion-color-step-150); 190 | --ion-toolbar-border-color: var(--ion-color-step-250); 191 | } 192 | 193 | 194 | /* 195 | * Material Design Dark Theme 196 | * ------------------------------------------- 197 | */ 198 | 199 | .md body { 200 | --ion-background-color: #121212; 201 | --ion-background-color-rgb: 18,18,18; 202 | 203 | --ion-text-color: #ffffff; 204 | --ion-text-color-rgb: 255,255,255; 205 | 206 | --ion-border-color: #222222; 207 | 208 | --ion-color-step-50: #1e1e1e; 209 | --ion-color-step-100: #2a2a2a; 210 | --ion-color-step-150: #363636; 211 | --ion-color-step-200: #414141; 212 | --ion-color-step-250: #4d4d4d; 213 | --ion-color-step-300: #595959; 214 | --ion-color-step-350: #656565; 215 | --ion-color-step-400: #717171; 216 | --ion-color-step-450: #7d7d7d; 217 | --ion-color-step-500: #898989; 218 | --ion-color-step-550: #949494; 219 | --ion-color-step-600: #a0a0a0; 220 | --ion-color-step-650: #acacac; 221 | --ion-color-step-700: #b8b8b8; 222 | --ion-color-step-750: #c4c4c4; 223 | --ion-color-step-800: #d0d0d0; 224 | --ion-color-step-850: #dbdbdb; 225 | --ion-color-step-900: #e7e7e7; 226 | --ion-color-step-950: #f3f3f3; 227 | 228 | --ion-item-background: #1e1e1e; 229 | 230 | --ion-toolbar-background: #1f1f1f; 231 | 232 | --ion-tab-bar-background: #1f1f1f; 233 | 234 | --ion-card-background: #1e1e1e; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/utilities/useRouter/useRouter.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { RouteComponentProps } from "react-router"; 3 | import { __RouterContext } from "react-router"; 4 | 5 | 6 | const queryString = window.location.search; 7 | const urlParams = new URLSearchParams(queryString); 8 | const code = urlParams.get('code') 9 | 10 | export const useRouter = () => { 11 | return useContext(__RouterContext) as RouteComponentProps; 12 | }; 13 | 14 | //https://codesandbox.io/s/3rwq8r85p?file=/src/useRouter.ts 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------