learn: add custom graphiql for react native (#2169)

This commit is contained in:
Rishichandra Wawhal
2019-05-13 15:27:59 +05:30
committed by Shahidh K Muhammed
parent 36e49ea75b
commit 83234ef48a
19 changed files with 3381 additions and 2975 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@
"hoist-non-react-statics": "^1.0.3",
"invariant": "^2.2.0",
"isomorphic-fetch": "^2.2.1",
"jwt-decode": "^2.2.0",
"less": "^3.7.1",
"lru-memoize": "^1.0.0",
"map-props": "^1.0.0",

View File

@@ -816,10 +816,10 @@ label
}
.common_checkbox:checked + .common_checkbox_label:before {
content: url('./tick.png');
background: #FFCA27;
color: #fff;
padding-top: 4px;
content: url('./tick.png');
background: #FFCA27;
color: #fff;
padding-top: 4px;
}
.authPanelSubHeadings {
@@ -865,3 +865,58 @@ label
}
}
}
.loginTabs {
display: flex;
flex-direction: column;
}
.loginTabsHeader {
display: flex;
align-items: center;
height: 60px;
}
.loginFormWrapper {
margin-top: 20px;
}
.loginTabActive {
display: flex;
align-items: center;
justify-content: center;
flex: 0.5;
border-bottom: solid;
border-bottom-color: #5f5f5f;
padding-bottom: 5px;
cursor: pointer;
font-weight: bold;
}
.loginTabInactive {
display: flex;
align-items: center;
justify-content: center;
flex: 0.5;
padding-bottom: 5px;
cursor: pointer;
}
.loginFormElement {
margin-bottom: 20px;
}
.formTextbox {
width: 300px;
}

View File

@@ -1,5 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { isReactNative } from './utils';
import CustomAuth from './CustomAuth'
class ApiRequestDetails extends Component {
login() {
@@ -15,7 +17,7 @@ class ApiRequestDetails extends Component {
const styles = require('./ApiExplorer.scss');
const loginButton = (
let loginButton = (
<button
id="qsLoginBtn"
className={'btn btn-primary'}
@@ -25,7 +27,13 @@ class ApiRequestDetails extends Component {
</button>
);
const logoutButton = (
const isCustomAuth = isReactNative();
if (isCustomAuth) {
loginButton = <CustomAuth />
}
let logoutButton = (
<button
id="qsLogoutBtn"
className={'btn btn-danger'}
@@ -35,6 +43,18 @@ class ApiRequestDetails extends Component {
</button>
);
if (isCustomAuth) {
logoutButton = (
<button
id="qsLogoutBtn"
className={'btn btn-danger'}
onClick={this.logout.bind(this)}
>
Log Out
</button>
);
}
const loginOverlay = (
<div className={styles.overlay}>
<div className={styles.overlayContent}>

View File

@@ -0,0 +1,195 @@
import React from 'react';
import styles from './ApiExplorer.scss';
import { signup, login } from './customAuthActions';
import { emailRegex } from './utils';
import jwtDecoder from 'jwt-decode';
class CustomAuth extends React.Component {
state = {
tabIndex: 0,
email: "",
password: "",
loading: false
}
handleEmailChange = (e) => {
this.setState({
email: e.target.value
})
}
handlePasswordChange = (e) => {
this.setState({
password: e.target.value
});
}
performSignup = (e, p) => {
const successCallback = () => { this.setState({
loading: false
})
this.setState({
tabIndex: 0,
loading: false
});
alert('Successfully signed up! Please login.')
}
const errorCallback = (e) => {
this.setState({
loading: false
});
alert(
`
${e.title}
${e.message}
`
)
}
signup(this.state.email, this.state.password, successCallback, errorCallback);
}
performLogin = () => {
const successCallback = (response) => {
this.setState({
tabIndex: 0,
email: '',
password: '',
});
const decodedToken = jwtDecoder(response.token);
window.localStorage.setItem('@learn.hasura.io:graphiql-react-native-token', response.token);
window.localStorage.setItem('@learn.hasura.io:graphiql-react-native-exp', decodedToken.exp);
window.location.replace(window.location.href);
}
const errorCallback = (e) => {
this.setState({
loading: false
})
alert(
`
${e.title}
${e.message}
`
)
}
login(this.state.email, this.state.password, successCallback, errorCallback);
}
submit = () => {
this.setState({ loading: true });
if (this.state.tabIndex === 0) {
this.performLogin(this.state.email, this.state.password)
} else {
this.performSignup(this.state.email, this.state.password)
}
}
toggleTab = (i) => {
this.setState({
tabIndex: i
});
}
render() {
const { email, password, tabIndex, loading } = this.state;
const { handleEmailChange, handlePasswordChange } = this;
const toggleLogin = () => this.toggleTab(0);
const toggleSignup = () => this.toggleTab(1);
let buttonText = tabIndex === 0 ? 'Log in' : 'Sign up';
if (loading) {
buttonText = "Please wait...";
}
let loginTabStyle, signupTabStyle;
if (tabIndex === 0) {
loginTabStyle = styles.loginTabActive;
signupTabStyle = styles.loginTabInactive;
} else {
loginTabStyle = styles.loginTabInactive;
signupTabStyle = styles.loginTabActive;
}
return (
<div className={styles.loginTabs}>
<div className={styles.loginTabsHeader}>
<div className={loginTabStyle} onClick={toggleLogin}>
Login
</div>
<div className={signupTabStyle} onClick={toggleSignup}>
Signup
</div>
</div>
<div className={styles.loginFormWrapper}>
<Form
email={email}
password={password}
handleEmailChange={handleEmailChange}
handlePasswordChange={handlePasswordChange}
buttonText={buttonText}
loading={loading}
submit={this.submit}
/>
</div>
</div>
)
}
}
const Form = ({
email,
handleEmailChange,
password,
handlePasswordChange,
submit,
loading,
buttonText
}) => {
const validateAndSubmit = (e) => {
e.preventDefault();
if (!emailRegex.test(email.toLowerCase())) {
alert('Invalid email', 'Please enter a valid email address');
return;
}
if (!email || !password) {
alert('Email or password cannot be empty');
return;
}
submit();
}
return (
<form
onSubmit={validateAndSubmit}
>
<div className={styles.loginFormElement}>
<input
value={email}
type="text"
onChange={handleEmailChange}
placeholder="Email"
className="form-control"
/>
</div>
<div className={styles.loginFormElement}>
<input
value={password}
type="password"
onChange={handlePasswordChange}
placeholder="Password"
className="form-control"
/>
</div>
<div className={styles.loginFormElement}>
<button
type="submit"
className="btn btn-primary"
disabled={loading}
>
{buttonText}
</button>
</div>
</form>
)
}
export default CustomAuth;

View File

@@ -0,0 +1,94 @@
export const signup = (email, password, successCb, errorCb) => {
fetch(
'https://learn.hasura.io/auth/signup',
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
username: email,
password
})
}
)
.then(resp => resp.json()
.then(respObj => {
if (resp.status === 200) {
successCb(respObj);
return;
}
if (respObj.errors && respObj.errors.length > 0) {
errorCb({
title: 'Error',
message: respObj.errors[0].message
})
return;
} else if (respObj.message && respObj.message.includes('unique')) {
errorCb({
title: 'This email is already signed up',
message: 'Try logging in'
})
return;
}
errorCb({
title: 'Unknown Error',
message: 'Please try again'
})
})
)
.catch(err => {
console.log(err);
errorCb({
title: 'Unexpected',
message: 'Please try again'
})
})
};
export const login = (email, password, successCb, errorCb) => {
fetch(
'https://learn.hasura.io/auth/login',
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
username: email,
password
})
}
)
.then(resp => {
console.log(resp);
resp.json()
.then(respObj => {
console.log(respObj);
if (resp.status === 200) {
successCb(respObj);
return;
}
if (respObj.error) {
errorCb({
title: 'Error',
message: respObj.error
});
return;
}
errorCb({
title: 'Unknown Error',
message: 'Please try again'
})
})}
)
.catch(err => {
console.log(err);
errorCb({
title: 'Unexpected',
message: 'Please try again'
})
})
};

View File

@@ -1,5 +1,14 @@
import { isReactNative } from './utils';
const existingHeaders = window.__env.headers;
const authToken = window.localStorage.getItem('auth0:id_token');
let authToken;
if (!isReactNative()) {
authToken = window.localStorage.getItem('auth0:id_token');
} else {
authToken = window.localStorage.getItem('@learn.hasura.io:graphiql-react-native-token');
}
const defaultHeader = [
{

View File

@@ -9,4 +9,14 @@ const getHeadersAsJSON = headers => {
return headerJSON;
};
export { getHeadersAsJSON };
const isReactNative = () => {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('tutorial') === 'react-native') {
return true;
}
return false;
};
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export { getHeadersAsJSON, isReactNative, emailRegex };

View File

@@ -1,5 +1,6 @@
import auth0 from "auth0-js";
import { AUTH_CONFIG } from "./auth0-variables";
import { isReactNative } from '../ApiExplorer/utils';
export default class Auth {
auth0 = new auth0.WebAuth({
@@ -47,6 +48,12 @@ export default class Auth {
}
logout() {
if (isReactNative()) {
window.localStorage.removeItem('@learn.hasura.io:graphiql-react-native-token');
window.localStorage.removeItem('@learn.hasura.io:graphiql-react-native-exp');
window.location.replace("/graphql/graphiql?tutorial=react-native");
return;
}
// Clear access token and ID token from local storage
localStorage.removeItem("auth0:access_token");
localStorage.removeItem("auth0:id_token");
@@ -60,7 +67,18 @@ export default class Auth {
isAuthenticated() {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = JSON.parse(localStorage.getItem("auth0:expires_at"));
return new Date().getTime() < expiresAt;
if (!isReactNative()) {
let expiresAt = JSON.parse(localStorage.getItem("auth0:expires_at"));
return new Date().getTime() < expiresAt;
} else {
const token = window.localStorage.getItem('@learn.hasura.io:graphiql-react-native-token');
const exp = window.localStorage.getItem('@learn.hasura.io:graphiql-react-native-exp');
if (!exp || !token) {
return false;
}
var currentTime = Math.floor(new Date().getTime() / 1000);
return currentTime < exp;
}
return false;
}
}

View File

@@ -5,7 +5,6 @@
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-59768903-1');
</script>
<link rel="icon" type="image/png" href="./favicon.png" />
@@ -62,4 +61,4 @@
<script src="https://storage.googleapis.com/graphql-engine-cdn.hasura.io/learn-hasura/graphiql/main.js" charset="UTF-8"></script>
</body>
</html>
</html>

View File

@@ -20,14 +20,14 @@ to use and integrate APIs in your app without requiring
external documentation tools.
You can access the GraphiQL for this realtime todo app tutorial here:
[learn.hasura.io/graphql/graphiql](https://learn.hasura.io/graphql/graphiql)
[learn.hasura.io/graphql/graphiql?tutorial=react-native](https://learn.hasura.io/graphql/graphiql?tutorial=react-native)
When you work with a GraphQL API in a project you will almost always
use a tool like GraphiQL to explore and test your GraphQL queries.
## Basic GraphQL query
1. Open GraphiQL at: [learn.hasura.io/graphql/graphiql](https://learn.hasura.io/graphql/graphiql).
1. Open GraphiQL at: [learn.hasura.io/graphql/graphiql?tutorial=react-native](https://learn.hasura.io/graphql/graphiql?tutorial=react-native).
You'll have to login to get an auth token to query the API. In a real-world scenario
your GraphQL APIs will be protected.
2. You'll see a URL, and headers that contain the auth
@@ -45,7 +45,7 @@ use a tool like GraphiQL to explore and test your GraphQL queries.
4. Hit `ctrl + enter` or `cmd + enter` (mac) or click on the icon to run the GraphQL query
5. On the right, you should see a list of users by their names that are in the system!
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
Recall that there is no magic here! The hosted GraphiQL app is sending a GraphQL query string
to the server at the given endpoint with the HTTP headers. The server then sends the response
@@ -77,7 +77,7 @@ This GraphQL query will fetch all the users and their publicly visible todos:
}
```
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
### Fetch online users and their profile information
@@ -96,7 +96,7 @@ and their profile information (which is just their name for now):
}
```
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
## Adding parameters (arguments) to GraphQL queries
@@ -120,7 +120,7 @@ query {
}
```
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
The most important bit to check here is `limit: 10`. GraphQL servers will provide a list of
arguments that can be used in `()` next to specific fields. In our case, we are using
@@ -145,7 +145,7 @@ query {
Notice that we are passing arguments to different fields. This GraphQL query reads as:
> Fetch users (with limit 1), and their todos (ordered by descending creation time, and limited to 5).
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
<a name="query-variables"></a>
## GraphQL variables: Passing arguments to your queries dynamically
@@ -193,7 +193,7 @@ Let's try this out in GraphiQL:
3. Scroll to the bottom of the page, where you see a smaller panel "Query Variables"
4. Add the query variable as a JSON object
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
## Summary

View File

@@ -46,7 +46,7 @@ mutation {
```
<!-- [//]: # TODO: -->
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
## Returning data after the mutation
Notice that the data of the todo that is to be inserted is sent as
@@ -71,7 +71,7 @@ mutation {
```
<!-- [//]: # TODO: -->
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
## Parametrise what you insert
@@ -103,7 +103,7 @@ mutation($todo: insert_todo_input!){
```
<!-- [//]: # TODO: -->
<b><a href="https://learn.hasura.io/graphql/graphiql" target="_blank">Try it out in GraphiQL</a></b>
<b><a href="https://learn.hasura.io/graphql/graphiql?tutorial=react-native" target="_blank">Try it out in GraphiQL</a></b>
We'll explore more mutations to update or delete data a little later.
This is a good start to grokking mutations!

View File

@@ -14,7 +14,7 @@ they allow you to build great experiences without having to deal with websocket
## Make your first GraphQL subscription
1. Step 1: Head to https://learn.hasura.io/graphql/graphiql
1. Step 1: Head to https://learn.hasura.io/graphql/graphiql?tutorial=react-native
2. Step 2: Write this GraphQL query in the textarea:
```graphql

View File

@@ -30,7 +30,7 @@ Let's define a graphql query to do a mutation into todos.
You will also need to pass in the values for the variables.
[Try](https://learn.hasura.io/graphql/graphiql) this mutation in GraphiQL against the application database to see what the response looks like.
[Try](https://learn.hasura.io/graphql/graphiql?tutorial=react-native) this mutation in GraphiQL against the application database to see what the response looks like.
Let's now integrate this graphql mutation into our react app.

View File

@@ -19,7 +19,7 @@ query getMyTodos {
}
```
[Try](https://learn.hasura.io/graphql/graphiql) this query in GraphiQL against the application database to see what the response looks like.
[Try](https://learn.hasura.io/graphql/graphiql?tutorial=react-native) this query in GraphiQL against the application database to see what the response looks like.
**Note**: You need to pass the `Authorization: Bearer <token>` header before querying to get the results. The token is auto-filled in the UI after logging in via Auth0.

View File

@@ -61,7 +61,7 @@ What does this query do?
------------------------
The query fetches `todos` with a simple condition; `is_public` must be false. We sort the todos descending by its `created_at` time according to the schema. We specify which fields we need for the todos node.
[Try](https://learn.hasura.io/graphql/graphiql) out this query now!
[Try](https://learn.hasura.io/graphql/graphiql?tutorial=react-native) out this query now!
Introducing query variables
---------------------------

View File

@@ -25,7 +25,7 @@ After you login, you should get something like this:
### Load GraphiQL to play with your GraphQL APIs
1. Head to https://learn.hasura.io/graphql/graphiql
1. Head to https://learn.hasura.io/graphql/graphiql?tutorial=react-native
2. Log in (so that you can test the GraphQL APIs with a valid user token)
This is what you should see after the steps above:

View File

@@ -35,6 +35,6 @@ Let's define a graphql query to do a mutation into todos.
You will also need to pass in the values for the variables.
[Try](https://learn.hasura.io/graphql/graphiql) this mutation in GraphiQL against the application database to see what the response looks like.
[Try](https://learn.hasura.io/graphql/graphiql?tutorial=react-native) this mutation in GraphiQL against the application database to see what the response looks like.
Let's now integrate this graphql mutation into our react native app.

View File

@@ -20,6 +20,6 @@ mutation ($id: Int) {
}
```
[Try](https://learn.hasura.io/graphql/graphiql) this mutation in GraphiQL against the application database to see what the response looks like.
[Try](https://learn.hasura.io/graphql/graphiql?tutorial=react-native) this mutation in GraphiQL against the application database to see what the response looks like.
Let's now integrate this graphql mutation into our react app.