Skip to content

Commit

Permalink
Create Token (#550)
Browse files Browse the repository at this point in the history
  • Loading branch information
lautarodragan committed Feb 19, 2019
1 parent 042ad49 commit a1b6ea8
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 52 deletions.
11 changes: 8 additions & 3 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"presets": [
"@babel/preset-react",
["@babel/preset-env", {
"targets": "> 3%, not dead"
}],
],
"plugins": [
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-optional-chaining",
]
"@babel/plugin-proposal-optional-chaining"
],
}
47 changes: 47 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"ramda": "0.26.1",
"react": "16.8.2",
"react-dom": "16.8.2",
"react-router-dom": "4.3.1"
"react-router-dom": "4.3.1",
"react-toastify": "4.5.2"
},
"devDependencies": {
"@babel/core": "7.3.3",
Expand Down
File renamed without changes.
7 changes: 6 additions & 1 deletion src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React from 'react'
import { ToastContainer } from 'react-toastify'

import { Router } from 'components/Router'
import { SessionProvider } from 'providers/SessionProvider'
import { ApiProvider } from 'providers/ApiProvider'

export const App = () => (
<SessionProvider>
<Router />
<ApiProvider>
<ToastContainer />
<Router />
</ApiProvider>
</SessionProvider>
)
3 changes: 2 additions & 1 deletion src/components/organisms/Tokens.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import React from 'react'

import classNames from './Tokens.scss'

export const Tokens = ({ tokens }) => {
export const Tokens = ({ tokens, onCreateToken, createDisabled = false }) => {
return (
<section className={classNames.tokens}>
<h1>API Tokens</h1>
<h2>Manage your API Tokens by authenticating with the Frost API</h2>
<TokenTable tokens={tokens} />
<button className={classNames.create} onClick={onCreateToken} disabled={createDisabled}>Create API Token</button>
</section>
)
}
Expand Down
20 changes: 20 additions & 0 deletions src/components/organisms/Tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
width: 100%;
background-color: #fff;
border-spacing: 0;
margin-bottom: 40px;

> thead > tr > td {
padding: 10px;
Expand Down Expand Up @@ -72,4 +73,23 @@
}
}
}

.create {
display: block;
width: 160px;
padding: 10px 15px;
border: 1px solid #000;
border-radius: 6px;
background-color: #fff;
font-family: $open-sans;
color: #2c2e3b;
font-size: 14px;
font-weight: 600;
text-align: center;
cursor: pointer;

&:hover {
color: #36b074;
}
}
}
61 changes: 46 additions & 15 deletions src/components/pages/Tokens.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,60 @@
import React, { useContext, useEffect } from 'react'
import React, { useContext, useEffect, useState, useReducer } from 'react'

import { Main } from 'components/templates/Main'
import { Tokens as TokensOrganism } from 'components/organisms/Tokens'
import { parseJwt } from 'helpers/jwt'
import { useTokens } from 'hooks/useTokens'
import { SessionContext } from 'providers/SessionProvider'
import { ApiContext } from 'providers/ApiProvider'

export const Tokens = () => {
const [account, setAccount] = useContext(SessionContext)
const [tokens, serverError, clientError] = useTokens(account?.token)
const parsedTokens = tokens?.map(serializedToken => ({
...parseJwt(serializedToken),
serializedToken,
}))
const [api, isBusy] = useContext(ApiContext)
const [tokens, dispatch] = useReducer(tokenReducer, [])

const apiResponseToAction = type => tokens => ({
type,
tokens,
})

useEffect(() => {
if (serverError || clientError) {
console.error(serverError || clientError)
setAccount(null)
}
}, [serverError, clientError])
if (api)
api.getTokens().then(apiResponseToAction('get')).then(dispatch)
}, [api])

const onCreateToken = () => {
api.createToken().then(apiResponseToAction('create')).then(dispatch)
}

return (
<Main>
<TokensOrganism tokens={parsedTokens} />
<TokensOrganism tokens={tokens} onCreateToken={onCreateToken} createDisabled={isBusy} />
</Main>
)
}

const tokenReducer = (tokens, action) => {
console.log('tokenReducer', action)

if (action.tokens === undefined)
return tokens

switch (action.type) {
case 'get':
const parsedTokens = action.tokens.apiTokens.map(serializedTokenToToken)
return [
...tokens,
...parsedTokens,
]
case 'create':
const parsedToken = serializedTokenToToken(action.tokens.apiToken)
return [
...tokens,
parsedToken,
]
default:
throw new Error()
}
}

const serializedTokenToToken = serializedToken => ({
...parseJwt(serializedToken),
serializedToken,
})
51 changes: 51 additions & 0 deletions src/helpers/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const isJSON = response => response.headers.get('content-type').split(';')[0] === 'application/json'

const parseResponseBody = response => isJSON(response) ? response.json() : response.text()

const parseResponse = async response => ({
status: response.status,
body: await parseResponseBody(response)
})

export const Api = ({ token, onServerError, onClientError, onRequestStart, onRequestFinish }) => {

const authenticatedFetch = (requestInfo, requestInit = { headers: {}}) => fetch(requestInfo, {
...requestInit,
headers: {
token,
...requestInit.headers,
}
})

const processParsedResponse = ({ url, options }) => ({ status, body }) => {
if (status === 200) {
return body
} else {
onServerError({ status, body, url, options })
}
}

const apiFetch = (url, options) => {
onRequestStart({ url, options })
return authenticatedFetch(url, options)
.then(parseResponse)
.then(processParsedResponse({ url, options }))
.catch(error => onClientError(error, url, options))
.then(_ => {
onRequestFinish({ url, options })
return _
})
}

const tokensUrl = 'https://api.poetnetwork.net/tokens'

const getTokens = () => apiFetch(tokensUrl)

const createToken = () => apiFetch(tokensUrl, { method: 'POST' })

return {
getTokens,
createToken,
}

}
31 changes: 0 additions & 31 deletions src/hooks/useTokens.js

This file was deleted.

1 change: 1 addition & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import { render} from 'react-dom'
import 'react-toastify/dist/ReactToastify.css'

import './index.scss'

Expand Down
44 changes: 44 additions & 0 deletions src/providers/ApiProvider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useState, useEffect, createContext, useContext } from 'react'
import { toast } from 'react-toastify'

import { Api } from 'helpers/api'
import { SessionContext } from './SessionProvider'

export const ApiContext = createContext()

export const ApiProvider = props => {
const [account, setAccount] = useContext(SessionContext)
const [api, setApi] = useState(null)
const [isBusy, setIsBusy] = useState(false)

const clearAccount = () => setAccount(null)

const onServerError = ({ status, body, url, options }) => {
console.error('API Error', 'Server Side', status, body, url, options)
toast.error( body)
}

const onClientError = (error, url, options) => {
console.error('API Error', 'Client Side', error, url, options)
toast.error(error)
clearAccount()
}

const onRequestStart = ({ url, options }) => {
setIsBusy(true)
}

const onRequestFinish = ({ url, options }) => {
setIsBusy(false)
}

useEffect(() => {
setApi(Api({ token: account?.token, onServerError, onClientError, onRequestStart, onRequestFinish }))
}, [account])

return (
<ApiContext.Provider value={[api, isBusy]}>
{ props.children }
</ApiContext.Provider>
)
}
Loading

0 comments on commit a1b6ea8

Please sign in to comment.