├── public ├── google016148dd2d2c5c97.html ├── favicon.ico ├── favicon.png ├── api-with-github.png ├── manifest.json └── index.html ├── src ├── config │ ├── history.js │ ├── firebase │ │ ├── index.js │ │ ├── auth.js │ │ ├── firebase.js │ │ ├── firebase-dev.js │ │ ├── firebase-prod.js │ │ └── keys.js │ ├── withAuthentication.js │ └── api.js ├── assets │ ├── images │ │ ├── CreateRepoGif.gif │ │ ├── GivePermission.gif │ │ ├── video-poster.png │ │ └── cofee.svg │ ├── apiwithgithub-tutorial.mp4 │ └── cmswithgithub-tutorial.mp4 ├── components │ ├── JSONEditorReact.css │ ├── JSONEditorReact.js │ ├── LandingPage.js │ ├── RepositoryList.js │ └── Application.js ├── index.js ├── logo.svg ├── serviceWorker.js └── index.scss ├── .gitignore ├── .eslintrc ├── package.json └── README.md /public/google016148dd2d2c5c97.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google016148dd2d2c5c97.html -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/api-with-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/public/api-with-github.png -------------------------------------------------------------------------------- /src/config/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | 3 | export default createBrowserHistory(); 4 | -------------------------------------------------------------------------------- /src/assets/images/CreateRepoGif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/src/assets/images/CreateRepoGif.gif -------------------------------------------------------------------------------- /src/assets/images/GivePermission.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/src/assets/images/GivePermission.gif -------------------------------------------------------------------------------- /src/assets/images/video-poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/src/assets/images/video-poster.png -------------------------------------------------------------------------------- /src/config/firebase/index.js: -------------------------------------------------------------------------------- 1 | import firebase from './firebase'; 2 | import * as auth from './auth'; 3 | 4 | export { firebase, auth }; 5 | -------------------------------------------------------------------------------- /src/assets/apiwithgithub-tutorial.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/src/assets/apiwithgithub-tutorial.mp4 -------------------------------------------------------------------------------- /src/assets/cmswithgithub-tutorial.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mddanishyusuf/json-apis-with-github/HEAD/src/assets/cmswithgithub-tutorial.mp4 -------------------------------------------------------------------------------- /src/components/JSONEditorReact.css: -------------------------------------------------------------------------------- 1 | .jsoneditor-react-container { 2 | width: 100%; 3 | min-height: 100vh; 4 | } 5 | 6 | .jsoneditor-tree { 7 | padding: 20px; 8 | } -------------------------------------------------------------------------------- /src/config/firebase/auth.js: -------------------------------------------------------------------------------- 1 | import firebase from './firebase'; 2 | 3 | export const getAuth = () => firebase.auth(); 4 | 5 | export const GithubOAuth = () => new firebase.auth.GithubAuthProvider(); 6 | -------------------------------------------------------------------------------- /src/config/firebase/firebase.js: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase'; 2 | 3 | import { FirebaseConfig } from './keys'; 4 | 5 | firebase.initializeApp(FirebaseConfig); 6 | 7 | export default firebase; 8 | -------------------------------------------------------------------------------- /src/config/firebase/firebase-dev.js: -------------------------------------------------------------------------------- 1 | export const FirebaseConfig = { 2 | apiKey: 'AIzaSyA_Y_EG7tkO767vGoBdYEvGwz_eL0v2XTA', 3 | authDomain: 'cmswithgithub.firebaseapp.com', 4 | databaseURL: 'https://cmswithgithub.firebaseio.com', 5 | }; 6 | -------------------------------------------------------------------------------- /src/config/firebase/firebase-prod.js: -------------------------------------------------------------------------------- 1 | export const FirebaseConfig = { 2 | apiKey: 'AIzaSyA_Y_EG7tkO767vGoBdYEvGwz_eL0v2XTA', 3 | authDomain: 'cmswithgithub.firebaseapp.com', 4 | databaseURL: 'https://cmswithgithub.firebaseio.com', 5 | }; 6 | -------------------------------------------------------------------------------- /src/config/firebase/keys.js: -------------------------------------------------------------------------------- 1 | const prodENV = require('./firebase-prod'); 2 | const devENV = require('./firebase-prod'); 3 | 4 | if (process.env.NODE_ENV === 'production') { 5 | module.exports = prodENV; 6 | } else { 7 | module.exports = devENV; 8 | } 9 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "wesbos" 4 | ], 5 | "rules": { 6 | "no-console": 0, 7 | "prettier/prettier": [ 8 | "error", 9 | { 10 | "trailingComma": "es5", 11 | "singleQuote": true, 12 | "printWidth": 120, 13 | "tabWidth": 4 14 | } 15 | ], 16 | "react/prop-types": [ 17 | "enabled" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /src/config/withAuthentication.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Delay from 'react-delay'; 3 | 4 | import { auth } from './firebase'; 5 | 6 | export default WrappedComponent => { 7 | function WithAuthentication({ history }, props) { 8 | const [data, setData] = useState([]); 9 | 10 | useEffect(() => { 11 | auth.getAuth().onAuthStateChanged(user => { 12 | if (user) { 13 | setData(user.providerData); 14 | } else { 15 | history.push('/'); 16 | } 17 | }); 18 | }, [history]); 19 | 20 | return data.length > 0 ? ( 21 | 22 | ) : ( 23 | 24 |

Loading...

25 |
26 | ); 27 | } 28 | 29 | return WithAuthentication; 30 | }; 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss'; 4 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import 'bootstrap/dist/css/bootstrap.css'; 7 | 8 | import Application from './components/Application'; 9 | import LandingPage from './components/LandingPage'; 10 | import RepositoryList from './components/RepositoryList'; 11 | 12 | import withAuthentication from './config/withAuthentication'; 13 | 14 | const router = ( 15 | 16 | 17 | 18 | 19 | 20 | {/* } /> */} 21 | 22 | 23 | ); 24 | 25 | ReactDOM.render(router, document.getElementById('root')); 26 | 27 | // If you want your app to work offline and load faster, you can change 28 | // unregister() to register() below. Note this comes with some pitfalls. 29 | // Learn more about service workers: http://bit.ly/CRA-PWA 30 | serviceWorker.unregister(); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmswithgithub", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^3.9.0", 7 | "@material-ui/icons": "^3.0.2", 8 | "axios": "^0.18.0", 9 | "bootstrap": "^4.3.1", 10 | "brace": "^0.11.1", 11 | "dotenv": "^6.2.0", 12 | "firebase": "^5.8.0", 13 | "history": "^4.9.0", 14 | "isomorphic-unfetch": "^3.0.0", 15 | "js-base64": "^2.5.1", 16 | "jsoneditor": "^5.32.4", 17 | "jsoneditor-react": "^1.0.0", 18 | "lodash": "^4.17.11", 19 | "moment": "^2.29.4", 20 | "node-sass": "^4.11.0", 21 | "react": "^16.8.6", 22 | "react-bootstrap": "^1.0.0-beta.8", 23 | "react-delay": "^0.1.0", 24 | "react-dom": "^16.8.6", 25 | "react-feather": "^1.1.6", 26 | "react-json-editor-viewer": "^1.0.4", 27 | "react-json-table": "^0.1.1", 28 | "react-json-to-table": "^0.1.5", 29 | "react-jsonschema-form": "^1.5.0", 30 | "react-media-player": "^0.7.7", 31 | "react-router-dom": "^4.3.1", 32 | "react-scripts": "2.1.8", 33 | "video-react": "^0.13.6" 34 | }, 35 | "scripts": { 36 | "start": "react-scripts start", 37 | "build": "react-scripts build && cp build/index.html build/404.html", 38 | "test": "react-scripts test", 39 | "eject": "react-scripts eject" 40 | }, 41 | "browserslist": [ 42 | ">0.2%", 43 | "not dead", 44 | "not ie <= 11", 45 | "not op_mini all" 46 | ], 47 | "devDependencies": { 48 | "babel-eslint": "^9.0.0", 49 | "eslint-config-airbnb": "^17.1.0", 50 | "eslint-config-prettier": "^4.1.0", 51 | "eslint-config-wesbos": "0.0.19", 52 | "eslint-plugin-html": "^5.0.3", 53 | "eslint-plugin-import": "^2.16.0", 54 | "eslint-plugin-jsx-a11y": "^6.2.1", 55 | "eslint-plugin-prettier": "^3.0.1", 56 | "eslint-plugin-react": "^7.12.4", 57 | "eslint-plugin-react-hooks": "^1.3.0", 58 | "prettier": "^1.16.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | APIs with GitHub 3 |

4 | 5 | 6 | 7 | 8 | ## What’s In This Document 9 | 10 | - [About This Tool](#-about-this-tool) 11 | - [How to Setup Locally](#-how-to-setup-locally) 12 | - [How to Contribute](#-how-to-contribute) 13 | - [License](#license) 14 | 15 |
16 | 17 | ## 📖 About This Tool 18 | 19 | I was re-designing my portfolio website and my main task was this where I'll host my basic data from where I can manage the content easily and in a simple way. But I couldn't find are no solution. So, I get the idea about GitHub API and using GitHub APIs I build this tool. So, my website(https://mohddanish.me) content is managed by this tool. So, I published this on ProductHunt(https://www.producthunt.com/posts/apis-with-github) and when people ask me that it's open-source? And then I say No. So, I quickly make this tool open Source. Because I love when people want to contribute to your projects. check this thread (https://twitter.com/mddanishyusuf/status/1124261537537974278) 20 | 21 | 22 |
23 | 24 | ## ✍ How to Setup Locally 25 | 26 | This tool is build with React(CRA), GitHub APIs, JSON Editor(https://github.com/josdejong/jsoneditor) and Bootstrap for Grid System. And Hosted on Netlify. 27 | 28 | ## Steps 29 | 30 | 1. `git clone https://github.com/mddanishyusuf/json-apis-with-github` 31 | 2. `cd json-apis-with-github` 32 | 3. `yarn install` 33 | 4. `npm run start` 34 | 35 |
36 | 37 | ### Tool Build With 38 | 39 | - **ReactJS**- Create React APP(https://github.com/facebook/create-react-app) 40 | - **GitHub APIs:** https://developer.github.com/v3/ 41 | - **Netlify:** To host the tool. 42 | 43 |
44 | 45 | ## License 46 | 47 | [![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](https://creativecommons.org/publicdomain/zero/1.0/) 48 | -------------------------------------------------------------------------------- /src/assets/images/cofee.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/JSONEditorReact.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEqual from 'lodash/isEqual'; 3 | import cloneDeep from 'lodash/cloneDeep'; 4 | 5 | import JSONEditor from 'jsoneditor'; 6 | import 'jsoneditor/dist/jsoneditor.css'; 7 | 8 | import './JSONEditorReact.css'; 9 | 10 | export default class JSONEditorReact extends Component { 11 | componentDidMount() { 12 | // copy all properties into options for the editor 13 | // (except the properties for the JSONEditorReact component itself) 14 | const options = Object.assign({}, this.props); 15 | delete options.json; 16 | delete options.text; 17 | 18 | this.jsoneditor = new JSONEditor(this.container, options); 19 | 20 | if ('json' in this.props) { 21 | this.jsoneditor.set(this.props.json); 22 | } 23 | if ('text' in this.props) { 24 | this.jsoneditor.setText(this.props.text); 25 | } 26 | this.schema = cloneDeep(this.props.schema); 27 | this.schemaRefs = cloneDeep(this.props.schemaRefs); 28 | } 29 | 30 | componentWillUpdate(nextProps, nextState) { 31 | if ('json' in nextProps) { 32 | this.jsoneditor.update(nextProps.json); 33 | } 34 | 35 | if ('text' in nextProps) { 36 | this.jsoneditor.updateText(nextProps.text); 37 | } 38 | 39 | if ('mode' in nextProps) { 40 | this.jsoneditor.setMode(nextProps.mode); 41 | } 42 | 43 | // store a clone of the schema to keep track on when it actually changes. 44 | // (When using a PureComponent all of this would be redundant) 45 | const schemaChanged = !isEqual(nextProps.schema, this.schema); 46 | const schemaRefsChanged = !isEqual(nextProps.schemaRefs, this.schemaRefs); 47 | if (schemaChanged || schemaRefsChanged) { 48 | this.schema = cloneDeep(nextProps.schema); 49 | this.schemaRefs = cloneDeep(nextProps.schemaRefs); 50 | this.jsoneditor.setSchema(nextProps.schema, nextProps.schemaRefs); 51 | } 52 | } 53 | 54 | componentWillUnmount() { 55 | if (this.jsoneditor) { 56 | this.jsoneditor.destroy(); 57 | } 58 | } 59 | 60 | render() { 61 | return
(this.container = elem)} />; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/config/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export function sortContentArray(array) { 4 | if (array.length > 0) { 5 | array.sort(function(x, y) { 6 | // true values first 7 | return x.type === y.type ? 0 : x.type === 'dir' ? -1 : 1; 8 | // false values first 9 | // return (x === y)? 0 : x? 1 : -1; 10 | }); 11 | } 12 | return array; 13 | } 14 | 15 | export function checkFileType(content) { 16 | const fileNameIntoArray = content.name.split('.'); 17 | const fileExt = fileNameIntoArray[1] === 'json' || fileNameIntoArray[1] === undefined; 18 | return fileExt; 19 | } 20 | 21 | export async function getData(location) { 22 | const param = location.pathname.split('/editor/')[1]; 23 | const accessToken = localStorage.getItem('token'); 24 | try { 25 | const response = await axios.get(`https://api.github.com/repos/${param}/contents?access_token=${accessToken}`, { 26 | headers: { 'If-None-Match': '' }, 27 | }); 28 | return response; 29 | } catch (error) { 30 | return error.response; 31 | } 32 | } 33 | 34 | export async function UpdateFile(path, param, location) { 35 | const accessToken = localStorage.getItem('token'); 36 | const pathName = location.pathname.split('/editor/')[1]; 37 | try { 38 | const response = await axios.put( 39 | `https://api.github.com/repos/${pathName}/contents/${path}?access_token=${accessToken}`, 40 | param 41 | ); 42 | return response; 43 | } catch (error) { 44 | return error; 45 | } 46 | } 47 | 48 | export async function deleteFile(param, location, path) { 49 | const accessToken = localStorage.getItem('token'); 50 | const pathName = location.pathname.split('/editor/')[1]; 51 | try { 52 | const response = await axios.delete( 53 | `https://api.github.com/repos/${pathName}/contents/${path}?access_token=${accessToken}`, 54 | { data: param } 55 | ); 56 | return response; 57 | } catch (error) { 58 | return error; 59 | } 60 | } 61 | 62 | export async function CrateANewFile(path, param, location) { 63 | const accessToken = localStorage.getItem('token'); 64 | const pathName = location.pathname.split('/editor/')[1]; 65 | try { 66 | const response = await axios.put( 67 | `https://api.github.com/repos/${pathName}/contents/${path}?access_token=${accessToken}`, 68 | param 69 | ); 70 | return response; 71 | } catch (error) { 72 | return error; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 43 | API with GitHub | Small and Tiny APIs with GitHub for quick use 44 | 45 | 46 | 47 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /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/components/LandingPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Player, ControlBar } from 'video-react'; 3 | import { auth, firebase } from '../config/firebase'; 4 | 5 | function LandingPage({ history }) { 6 | const loginWithGitHub = () => { 7 | const providerOAuth = new auth.GithubOAuth(); 8 | 9 | firebase 10 | .auth() 11 | .signInWithPopup(providerOAuth) 12 | .then(result => { 13 | localStorage.setItem('token', result.credential.accessToken); 14 | history.push('/database'); 15 | }) 16 | .catch(err => console.error(err)); 17 | }; 18 | 19 | return ( 20 |
21 |
22 |
23 |
24 |
25 |

API with GitHub

26 |
27 | Now build simple API quickly with JSON and store on GitHub Repository. Seems cool? 28 |
29 | 32 |
33 |
34 |
35 | 41 | 42 | 43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |

How it works

52 |
53 |
54 |
1. Setup GitHub APP
55 |

56 | Install Our GitHub App on your GitHub Account and give the access to new repository you 57 | make to build APIs. We are not storing any data on our server. 58 |

59 |
60 |
61 | setup github app 65 |
66 |
67 |
68 |
69 |
70 | create reposiotry 74 |
75 |
76 |
77 |
2. Create Repository
78 |

79 | Make new public GitHub repository with README file. So, we using repository as a JSON 80 | files Storage. 81 |

82 |
83 |
84 |
85 |
86 |
3. Make API & Save
87 |

88 | Make new JSON files with our editor and save. So, when you save the file the changes 89 | will reflect on your GitHub repository. 90 |

91 |
92 |
93 | make api and save 97 |
98 |
99 |
100 |
101 |
102 |
103 | Build by{' '} 104 | 105 | @mddanishyusuf 106 | {' '} 107 | I love 🍳 ☕ & 🔨 More projects{' '} 108 | 109 | mohddanish.me 110 | 111 |
112 |
113 | ); 114 | } 115 | 116 | export default LandingPage; 117 | -------------------------------------------------------------------------------- /src/components/RepositoryList.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Coffee } from 'react-feather'; 4 | import axios from 'axios'; 5 | import moment from 'moment'; 6 | 7 | function RepositoryList({ history }) { 8 | const [repoList, setRepoList] = useState(null); 9 | const [isAppInstalled, setIsAppInstalled] = useState(false); 10 | 11 | function getRepositryList(installationId) { 12 | const accessToken = localStorage.getItem('token'); 13 | axios 14 | .get( 15 | `https://api.github.com/user/installations/${installationId}/repositories?access_token=${accessToken}`, 16 | { 17 | headers: { Accept: 'application/vnd.github.machine-man-preview+json', 'If-None-Match': '' }, 18 | } 19 | ) 20 | .then(res => { 21 | setRepoList(res.data.repositories); 22 | }); 23 | } 24 | 25 | useEffect(() => { 26 | const accessToken = localStorage.getItem('token'); 27 | axios 28 | .get(`https://api.github.com/user/installations?access_token=${accessToken}`, { 29 | headers: { Accept: 'application/vnd.github.machine-man-preview+json', 'If-None-Match': '' }, 30 | }) 31 | .then(res => { 32 | const appsArray = res.data.installations; 33 | const appInstalled = appsArray.findIndex(x => x.app_id === 29383); 34 | if (appInstalled > -1) { 35 | setIsAppInstalled(true); 36 | getRepositryList(appsArray[appInstalled].id); 37 | } 38 | }); 39 | }, []); 40 | 41 | function AddedRepoList() { 42 | return ( 43 |
44 | {repoList.map((repo, key) => ( 45 |
46 | {repo.full_name} 47 |
48 | 49 | last change: {moment(repo.pushed_at).fromNow()} 50 | 51 |
52 | ))} 53 |
54 | ); 55 | } 56 | 57 | return ( 58 |
59 |
60 |
61 |

62 | API with 63 |
64 | GitHub 65 |

66 | 74 |
75 | {repoList === null ? ( 76 |
77 |
78 |

Create your first Database

79 | 80 | 1. Create new Public GitHub Repository with default README.md file, go to{' '} 81 | 82 | https://github.com/new{' '} 83 | to create new repository. 84 |
85 | {isAppInstalled ? '✅' : ''}2. Install {/* 2. {' '} */} 86 | 91 | this 92 | {' '} 93 | app on your GitHub Profile and give repository access. 94 |
95 |
96 |
97 | {/* 103 | Install the App 104 | 105 | how to make new repository */} 106 |
107 | ) : ( 108 |
109 |

List of Repository(DataBase)

110 | 111 | Make new database{' '} 112 | 113 | https://github.com/new{' '} 114 | 115 | 116 |
117 |
118 |
119 | 120 | {/*
121 |
122 |
123 |

If Repository not listed above

124 |

125 | There is only one possibility that we don;t have access to that Repository. So, No problem 126 | Below is the GIF image is showing how to give access. 127 |

128 | Tutorial to give access to repository */} 133 |
134 | )} 135 |
136 |
137 | ); 138 | } 139 | 140 | export default RepositoryList; 141 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700"); 2 | @import "~video-react/styles/scss/video-react"; 3 | 4 | * { 5 | margin: 0px; 6 | padding: 0px; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | padding: 0; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | 15 | 16 | font-family: 'Poppins', sans-serif !important; 17 | } 18 | 19 | .how-it-work { 20 | padding: 80px 0px; 21 | 22 | .key-points { 23 | padding: 60px 0px; 24 | 25 | .col-md-6 { 26 | margin-bottom: 40px; 27 | } 28 | 29 | .key-points-value { 30 | display: grid; 31 | align-self: center; 32 | padding: 40px; 33 | } 34 | 35 | img { 36 | padding: 20px; 37 | width: 80% 38 | } 39 | 40 | .overlay { 41 | background-color: rgba(0, 0, 0, 0.2); 42 | bottom: 0; 43 | left: 0; 44 | padding: 20px; 45 | padding-right: 20px; 46 | position: absolute; 47 | right: 0; 48 | top: 0; 49 | border-radius: 2px; 50 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); 51 | } 52 | } 53 | } 54 | 55 | .footer { 56 | background-color: #FF9100; 57 | padding: 10px; 58 | font-size: 13px; 59 | display: inline-block; 60 | 61 | span { 62 | border: 2px solid #1A2172; 63 | padding: 3px 10px; 64 | margin: 0px 10px 65 | } 66 | 67 | a { 68 | color: #1A2172; 69 | font-weight: 700; 70 | } 71 | } 72 | 73 | .landing-section { 74 | height: 80vh; 75 | 76 | .cta-section { 77 | align-self: center; 78 | color: #fff; 79 | padding: 20px; 80 | .login-button { 81 | margin-top: 40px; 82 | } 83 | } 84 | 85 | .product-video-section { 86 | display: grid; 87 | align-self: center; 88 | box-shadow: rgba(0, 0, 0, 0.24) 0px 0px 40px, rgba(0, 0, 0, 0.16) 0px 0px 12px; 89 | padding: 0px; 90 | background: rgb(255, 255, 255); 91 | border-radius: 5px; 92 | overflow: hidden; 93 | video { 94 | 95 | width: 100%; 96 | } 97 | } 98 | } 99 | 100 | .list-header { 101 | display: grid; 102 | grid-template-columns: auto 200px; 103 | 104 | } 105 | 106 | .donate-btn { 107 | align-self: center; 108 | 109 | button { 110 | font-size: 13px; 111 | 112 | span { 113 | padding: 0px 10px; 114 | } 115 | } 116 | 117 | button.btn-warning { 118 | display: flex; 119 | } 120 | } 121 | 122 | .db-options { 123 | height: 70px; 124 | 125 | .file-table { 126 | table { 127 | font-size: 14px; 128 | 129 | th { 130 | padding: 5px 20px 5px 0px; 131 | } 132 | 133 | td { 134 | padding: 5px 20px 5px 0px; 135 | } 136 | } 137 | } 138 | } 139 | 140 | .btn-bottom { 141 | position: absolute; 142 | bottom: 20px; 143 | 144 | } 145 | 146 | .repository-section { 147 | h3 { 148 | padding: 20px 0px; 149 | } 150 | 151 | img { 152 | width: 100%; 153 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); 154 | margin: 40px 0px; 155 | } 156 | } 157 | 158 | .no-repository-container { 159 | margin-top: 40px; 160 | text-align: center; 161 | padding: 60px; 162 | } 163 | 164 | .side-navbar { 165 | padding: 10px; 166 | 167 | h3 { 168 | padding: 10px 20px; 169 | 170 | a { 171 | color: #000; 172 | text-decoration: none 173 | } 174 | } 175 | 176 | } 177 | 178 | .events-btn { 179 | font-size: 12px; 180 | 181 | div { 182 | padding: 10px; 183 | } 184 | 185 | button { 186 | padding: 2px 10px; 187 | } 188 | } 189 | 190 | button { 191 | padding: 2px 10px; 192 | 193 | } 194 | 195 | .list-container { 196 | padding: 60px 0px; 197 | } 198 | 199 | .list-repo { 200 | 201 | .list { 202 | font-size: 1rem; 203 | padding: 10px; 204 | 205 | &:nth-child(odd) { 206 | background-color: rgb(245, 244, 242); 207 | } 208 | } 209 | } 210 | 211 | .editor { 212 | display: grid; 213 | grid-template-columns: 20% auto; 214 | grid-gap: 20px; 215 | } 216 | 217 | ul { 218 | margin: 0px 0px 0px 20px; 219 | list-style: none; 220 | line-height: 2em; 221 | font-family: Arial; 222 | 223 | li { 224 | font-size: 13px; 225 | position: relative; 226 | cursor: pointer; 227 | 228 | font-family: 'Poppins', sans-serif; 229 | 230 | 231 | 232 | .file-list { 233 | display: flex; 234 | } 235 | 236 | .file-icon { 237 | padding-right: 10px; 238 | } 239 | 240 | &:before { 241 | position: absolute; 242 | left: -15px; 243 | top: 0px; 244 | content: ''; 245 | display: block; 246 | border-left: 1px solid #ddd; 247 | height: 1em; 248 | border-bottom: 1px solid #ddd; 249 | width: 10px; 250 | } 251 | 252 | &:after { 253 | position: absolute; 254 | left: -15px; 255 | bottom: -7px; 256 | content: ''; 257 | display: block; 258 | border-left: 1px solid #ddd; 259 | height: 100%; 260 | } 261 | 262 | &.root { 263 | margin: 0px 0px 0px -20px; 264 | 265 | &:before { 266 | display: none; 267 | } 268 | 269 | &:after { 270 | display: none; 271 | } 272 | 273 | 274 | } 275 | 276 | 277 | &:last-child { 278 | &:after { 279 | display: none 280 | } 281 | } 282 | } 283 | } 284 | 285 | .delete-file { 286 | cursor: pointer; 287 | } 288 | 289 | .event-message { 290 | font-size: 13px; 291 | } 292 | 293 | 294 | div.jsoneditor-value.jsoneditor-string { 295 | padding: 3px 10px 0px 10px; 296 | } 297 | 298 | #snackbar { 299 | visibility: hidden; 300 | /* Hidden by default. Visible on click */ 301 | min-width: 250px; 302 | /* Set a default minimum width */ 303 | margin-left: -125px; 304 | /* Divide value of min-width by 2 */ 305 | background-color: #333; 306 | /* Black background color */ 307 | color: #fff; 308 | /* White text color */ 309 | text-align: center; 310 | /* Centered text */ 311 | border-radius: 2px; 312 | /* Rounded borders */ 313 | padding: 16px; 314 | /* Padding */ 315 | position: fixed; 316 | /* Sit on top of the screen */ 317 | z-index: 1; 318 | /* Add a z-index if needed */ 319 | left: 50%; 320 | /* Center the snackbar */ 321 | top: 30px; 322 | /* 30px from the bottom */ 323 | } 324 | 325 | /* Show the snackbar when clicking on a button (class added with JavaScript) */ 326 | #snackbar.show { 327 | visibility: visible; 328 | /* Show the snackbar */ 329 | /* Add animation: Take 0.5 seconds to fade in and out the snackbar. 330 | However, delay the fade out process for 2.5 seconds */ 331 | -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; 332 | animation: fadein 0.5s, fadeout 0.5s 2.5s; 333 | } 334 | 335 | /* Animations to fade the snackbar in and out */ 336 | @-webkit-keyframes fadein { 337 | from { 338 | bottom: 0; 339 | opacity: 0; 340 | } 341 | 342 | to { 343 | bottom: 30px; 344 | opacity: 1; 345 | } 346 | } 347 | 348 | @keyframes fadein { 349 | from { 350 | bottom: 0; 351 | opacity: 0; 352 | } 353 | 354 | to { 355 | bottom: 30px; 356 | opacity: 1; 357 | } 358 | } 359 | 360 | @-webkit-keyframes fadeout { 361 | from { 362 | bottom: 30px; 363 | opacity: 1; 364 | } 365 | 366 | to { 367 | bottom: 0; 368 | opacity: 0; 369 | } 370 | } 371 | 372 | @keyframes fadeout { 373 | from { 374 | bottom: 30px; 375 | opacity: 1; 376 | } 377 | 378 | to { 379 | bottom: 0; 380 | opacity: 0; 381 | } 382 | } -------------------------------------------------------------------------------- /src/components/Application.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import axios from 'axios'; 3 | import { Button, Modal, Form } from 'react-bootstrap'; 4 | import { Base64 } from 'js-base64'; 5 | import { Folder, Code, PlusSquare, Save, ExternalLink, Coffee, Trash2 } from 'react-feather'; 6 | import { Link } from 'react-router-dom'; 7 | import JSONEditorReact from './JSONEditorReact'; 8 | import { getData, UpdateFile, CrateANewFile, sortContentArray, checkFileType, deleteFile } from '../config/api'; 9 | 10 | // https://codepen.io/khoama/pen/hpljA 11 | 12 | function Application({ location, history }) { 13 | const [filesList, setFilesList] = useState([]); 14 | const [jsonContent, setJsonContent] = useState(null); 15 | const [activeMode, setActiveMode] = useState('tree'); 16 | const [newFileName, setNewFileName] = useState(''); 17 | const [eventMessage, setEventMessage] = useState(''); 18 | const [showSnackbar, setShowSnackbar] = useState(false); 19 | const [show, setShow] = useState(false); 20 | const [showActionModal, setShowActionModal] = useState(false); 21 | 22 | const [activePathObj, setActivePathObj] = useState({}); 23 | const modes = ['tree', 'form', 'view', 'code', 'text']; 24 | 25 | useEffect(() => { 26 | let ignore = false; 27 | 28 | async function firstCall() { 29 | const result = await getData(location); 30 | const dataArray = result.data; 31 | if (result.status === 404) { 32 | history.push('/database'); 33 | } 34 | for (let i = 0; i < dataArray.length; i += 1) { 35 | dataArray[i].childObj = []; 36 | } 37 | setFilesList(dataArray); 38 | } 39 | 40 | firstCall(); 41 | return () => { 42 | ignore = true; 43 | }; 44 | }, [history, location]); 45 | 46 | const fetchData = async paramObj => { 47 | const result = await getData(location); 48 | const dataArray = result.data; 49 | if (result.status === 404) { 50 | history.push('/database'); 51 | } 52 | for (let i = 0; i < dataArray.length; i += 1) { 53 | dataArray[i].childObj = []; 54 | } 55 | setFilesList(dataArray); 56 | }; 57 | 58 | const previewData = (content, e) => { 59 | e.preventDefault(); 60 | setActivePathObj(content); 61 | // const urlParamArray = content.git_url.split('/'); 62 | // console.log(urlParamArray); 63 | // const userName = urlParamArray[4]; 64 | // const repoName = urlParamArray[5]; 65 | const accessToken = localStorage.getItem('token'); 66 | axios 67 | .get(`${content.git_url}?access_token=${accessToken}`, { 68 | headers: { 'If-None-Match': '' }, 69 | }) 70 | .then(res => { 71 | const decoded = JSON.parse(Base64.decode(res.data.content)); 72 | setJsonContent(decoded); 73 | }); 74 | }; 75 | 76 | function onChangeJSON(json) { 77 | setJsonContent(json); 78 | } 79 | 80 | async function commitThisFile() { 81 | const { path, sha } = activePathObj; 82 | const activePathObjSHA = filesList.findIndex(x => x.path === path); 83 | const shaID = filesList[activePathObjSHA].sha; 84 | setEventMessage(`${path} is commiting...`); 85 | setShowSnackbar(true); 86 | const param = { 87 | message: 'Updating file with API', 88 | content: Base64.encode(JSON.stringify(jsonContent)), 89 | sha: shaID, 90 | }; 91 | const result = await UpdateFile(path, param, location); 92 | if (result.status === 200) { 93 | setEventMessage(`${path} is saved`); 94 | setTimeout(() => { 95 | setShowSnackbar(false); 96 | fetchData(); 97 | }, 2000); 98 | } 99 | } 100 | 101 | async function createAFile() { 102 | setEventMessage('Creating File and pushing on GitHub'); 103 | const path = newFileName; 104 | const param = { 105 | message: 'Creating file with API', 106 | content: Base64.encode('{}'), 107 | }; 108 | const result = await CrateANewFile(path, param, location); 109 | if (result.status === 201) { 110 | setEventMessage('Successfully! Yes, file created. Refresh after 60s'); 111 | fetchData(); 112 | setTimeout(() => { 113 | setShow(false); 114 | }, 1500); 115 | } 116 | } 117 | 118 | function openDeleteAction() { 119 | setShowActionModal(true); 120 | } 121 | 122 | function closeActionModal() { 123 | setShowActionModal(false); 124 | } 125 | 126 | async function deleteThisFile() { 127 | setEventMessage(`Deleting file ${activePathObj.path}`); 128 | const { path } = activePathObj; 129 | const param = { 130 | message: `Deleting file ${activePathObj.path}`, 131 | sha: activePathObj.sha, 132 | }; 133 | const result = await deleteFile(param, location, path); 134 | if (result.status === 200) { 135 | setEventMessage('Successfully! Yes, file deleted. Refresh after 60s'); 136 | setActivePathObj({}); 137 | setJsonContent(null); 138 | fetchData(); 139 | setTimeout(() => { 140 | setShowActionModal(false); 141 | }, 1500); 142 | } 143 | } 144 | 145 | async function renderNestedList(obj, index, e) { 146 | e.preventDefault(); 147 | const accessToken = localStorage.getItem('token'); 148 | const result = await axios.get(`${obj.url}&access_token=${accessToken}`); 149 | filesList[index].childObj = result.data; 150 | 151 | setFilesList(filesList); 152 | setJsonContent({}); 153 | } 154 | 155 | function updateNewFileName(e) { 156 | setNewFileName(e.target.value); 157 | } 158 | 159 | function renderFilesList(content, key) { 160 | if (checkFileType(content)) { 161 | return ( 162 |
  • 171 |
    172 |
    173 | {content.type === 'dir' ? : } 174 |
    175 |
    {content.name}
    176 |
    177 |
      178 | {content.childObj.map(child => ( 179 |
    • 180 |
      181 |
      182 | {child.type === 'dir' ? : } 183 |
      184 |
      {child.name}
      185 |
      186 |
    • 187 | ))} 188 |
    189 |
  • 190 | ); 191 | } 192 | } 193 | 194 | function handleClose() { 195 | setShow(false); 196 | } 197 | 198 | function handleShow() { 199 | setShow(true); 200 | } 201 | 202 | // function RefreshList() { 203 | // fetchData(); 204 | // } 205 | 206 | return ( 207 |
    208 |
    209 | {eventMessage} 210 |
    211 | 212 |
    213 |
    214 |

    215 | 216 | API with 217 |
    218 | GitHub 219 | 220 |

    221 |
    222 |
    223 |
    224 | 227 |
    228 |
    229 |
      {sortContentArray(filesList).map(renderFilesList)}
    230 | 238 |
    239 |
    240 |
    241 | {activePathObj.download_url && ( 242 |
    243 |
    244 | 245 | 246 | 247 | 248 | 249 | 250 | 264 | 265 | 270 | 271 | 272 |
    Active File{activePathObj.path}API 251 | 256 | {activePathObj.download_url && ( 257 | 258 | link 259 | 260 | 261 | )} 262 | 263 | Action 266 | 267 | Delete This File 268 | 269 |
    273 |
    274 |
    275 | {' '} 279 |
    280 |
    281 | )} 282 |
    283 | 290 |
    291 | 292 | 293 | 294 | Create New JSON File 295 | 296 | 297 | Just give the name to the file and create. So, File will save to your GitHub Repository. 298 | 303 | {eventMessage} 304 | 305 | 306 | 309 | 312 | 313 | 314 | 315 | 316 | 317 | Delete File 318 | 319 | 320 | Sure, you wanna delete the file {activePathObj.path} 321 |
    322 |
    323 | {eventMessage} 324 |
    325 | 326 | 329 | 332 | 333 |
    334 |
    335 |
    336 | ); 337 | } 338 | 339 | export default Application; 340 | --------------------------------------------------------------------------------