diff --git a/.github/workflows/qa-checks.yml b/.github/workflows/qa-checks.yml index f69022a..fc638d5 100644 --- a/.github/workflows/qa-checks.yml +++ b/.github/workflows/qa-checks.yml @@ -21,9 +21,6 @@ jobs: run: npm install - name: Linters and Formatters Check - uses: wearerequired/lint-action@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - # Enable linters - eslint: true - prettier: true + run: | + npm run lint + npm run check diff --git a/README.md b/README.md index cc5679b..86a087e 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,24 @@ Documentation for the project is hosted [here](https://osp-web-docs.surge.sh/). **Note:** Before setting up the frontend make sure to have Setup the [Backend Repo](https://github.com/anitab-org/open-source-programs-backend). -1. To start the server: +1. Create a `.env` file in the project root directory and add **Client ID** and **Callback URL** of GitHub App like this: + +``` +REACT_APP_GITHUB_CLIENT_ID=< GitHub App Client ID > +REACT_APP_GITHUB_CALLBACK_URL=< GitHub App Callback URL > +``` + +To get **Client ID** and **Callback URL** of GitHub App follow [this docs](https://docs.github.com/en/developers/apps/creating-a-github-app). + +2. To start the server: ``` -cd open-source-programs-web npm install npm start ``` -2. Navigate to `http://localhost:3000/` in your browser. -3. You can terminate the process by `Ctrl+C` in your terminal. +3. Navigate to `http://localhost:3000/` in your browser. +4. You can terminate the process by `Ctrl+C` in your terminal. ## Contributing diff --git a/package.json b/package.json index 0824938..d0ff220 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,18 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "aws-sdk": "^2.734.0", - "axios": "^0.19.2", + "axios": "^0.21.1", "dotenv": "^8.2.0", + "github-login": "^1.0.12", "moment": "^2.27.0", "prop-types": "^15.7.2", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-github-login": "^1.0.3", "react-redux": "^7.2.0", "react-router-dom": "^5.2.0", "react-router-redux": "^4.0.8", - "react-scripts": "3.4.1", + "react-scripts": "^4.0.3", "redux": "^4.0.5", "redux-api-middleware": "^3.2.1", "redux-persist": "^6.0.0", @@ -33,7 +35,8 @@ "eject": "react-scripts eject", "lint": "eslint .", "lint:fix": "eslint . --fix", - "format": "prettier --write \"**/*.+(js|jsx|json|css|md)\"" + "format": "prettier --write \"**/*.+(js|jsx|json|css|md)\"", + "check": "prettier -c \"**/*.+(js|jsx|json|css|md)\"" }, "browserslist": { "production": [ diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 4db7ebc..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/actions/login.js b/src/actions/login.js index c2edb5c..b2e6e36 100644 --- a/src/actions/login.js +++ b/src/actions/login.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { urlLogin, urlRegister } from '../urls'; +import { urlLogin, urlRegister, urlGithubLogin } from '../urls'; import { LOGIN, REGISTER, LOGIN_ERRORS, REGISTER_ERRORS } from './types'; export const postLogin = (data, callback) => async (dispatch) => { @@ -48,3 +48,27 @@ export const postRegister = (data, callback) => async (dispatch) => { callback(); } }; + +export const postGithubCode = (data, callback) => async (dispatch) => { + try { + const config = { + headers: { + 'content-type': 'application/json', + }, + }; + const res = await axios.post(urlGithubLogin(), data, config); + dispatch({ + type: LOGIN, + payload: res.data, + }); + + localStorage.setItem('token', res.data.access_token); + callback(); + } catch (err) { + dispatch({ + type: LOGIN_ERRORS, + payload: err.response, + }); + callback(); + } +}; diff --git a/src/components/GitHubAuth.js b/src/components/GitHubAuth.js new file mode 100644 index 0000000..d4e33bc --- /dev/null +++ b/src/components/GitHubAuth.js @@ -0,0 +1,128 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import GitHubLogin from 'react-github-login'; +import { postGithubCode } from '../actions/login'; +import { Icon, Message } from 'semantic-ui-react'; + +class GitHubAuth extends Component { + constructor(props) { + super(props); + this.state = { + code: '', + error: null, + posted: false, + errorText: '', + }; + this.postCode = this.postCode.bind(this); + this.GithubError = this.GithubError.bind(this); + } + + postCode = (response) => { + this.setState({ + code: response.code, + }); + const data = { + code: this.state.code, + }; + this.props.postGithubCode(data, this.callback); + this.setState({ + code: '', + }); + }; + + callback = () => { + this.setState({ + error: this.props.loginerror ? true : false, + posted: true, + errorText: 'Server Error: try again.', + }); + if (!this.state.error) { + this.props.history.push('/'); + } + setTimeout(() => { + this.setState({ + error: false, + posted: false, + }); + }, 4000); + }; + + githubIcon = () => { + return ( + +
+ Log in with GitHub +
+
+ ); + }; + + GithubError = (response) => { + console.log(response); + this.setState({ + error: true, + posted: true, + errorText: 'GitHub API Error.', + }); + setTimeout(() => { + this.setState({ + error: false, + posted: false, + }); + }, 4000); + }; + + componentDidMount() { + this.setState({ + code: '', + error: null, + posted: false, + errorText: '', + }); + } + + componentWillUnmount() { + // eslint-disable-next-line + this.setState = (state, callback) => { + return; + }; + } + + render() { + const { + REACT_APP_GITHUB_CLIENT_ID, + REACT_APP_GITHUB_CALLBACK_URL, + } = process.env; + const { error, posted, errorText } = this.state; + const onFailure = (response) => this.GithubError(response); + return ( + <> + {error && posted ? ( + + ) : ( + + {this.githubIcon()} + + )} + + ); + } +} + +GitHubAuth.propTypes = { + postGithubCode: PropTypes.func.isRequired, +}; + +const mapStateToProps = (state) => ({ + login: state.login.login, + loginerror: state.login.loginerror, +}); + +export default connect(mapStateToProps, { postGithubCode })(GitHubAuth); diff --git a/src/components/Login.js b/src/components/Login.js index 4884f45..1d255a3 100644 --- a/src/components/Login.js +++ b/src/components/Login.js @@ -10,11 +10,13 @@ import { Icon, Message, Button, + Segment, } from 'semantic-ui-react'; import PropTypes from 'prop-types'; import './../styles/Login.css'; import orgLogo from '../assets/org-logo.jpg'; import { register } from '../urls'; +import GitHubAuth from './GitHubAuth'; class Login extends Component { constructor(props) { @@ -168,6 +170,9 @@ class Login extends Component { ) : null} + + + Don't have an account?{' '} Register here. diff --git a/src/tests/GitHubAuth.test.js b/src/tests/GitHubAuth.test.js new file mode 100644 index 0000000..ce10ae0 --- /dev/null +++ b/src/tests/GitHubAuth.test.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; +import { render, cleanup } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import GitHubAuth from '../components/GitHubAuth'; +import loginReducer from '../reducers/login'; + +afterEach(cleanup); + +function renderWithRedux( + component, + { initialState, store = createStore(loginReducer, initialState) } = {} +) { + return { + ...render({component}), + }; +} + +it('Renders GitHub Login Button', () => { + const { getByTestId } = renderWithRedux(); + expect(getByTestId('GitHubLoginButton')).toHaveTextContent( + 'Log in with GitHub' + ); +}); diff --git a/src/urls.js b/src/urls.js index 6831eec..f75c1c3 100644 --- a/src/urls.js +++ b/src/urls.js @@ -47,6 +47,10 @@ export function urlRegister() { return `${urlBaseBackend()}/token_auth/register/`; } +export function urlGithubLogin() { + return `${urlBaseBackend()}/token_auth/github/`; +} + export function urlInfo() { return `${urlBaseBackend()}/info/`; }