Skip to content

Commit

Permalink
feat: add user email to account page (#566)
Browse files Browse the repository at this point in the history
* feat: add user email to account page

* feat: user email; update loading pattern

* feat: user provider

* feat: global app state

* feat: show user email; abstract user hook
  • Loading branch information
Codigo-Fuentes authored Nov 2, 2021
1 parent 63fe8bc commit a34d976
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/api/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { envAll } from './env.js'
import { statusGet } from './status.js'
import { carHead, carGet, carPut, carPost } from './car.js'
import { uploadPost } from './upload.js'
import { userLoginPost, userTokensPost, userTokensGet, userTokensDelete, userUploadsGet, userUploadsDelete, userAccountGet, userUploadsRename } from './user.js'
import { userLoginPost, userTokensPost, userTokensGet, userTokensDelete, userUploadsGet, userUploadsDelete, userAccountGet, userUploadsRename, userInfoGet } from './user.js'
import { metricsGet } from './metrics.js'
import { versionGet } from './version.js'
import {
Expand Down Expand Up @@ -49,6 +49,7 @@ router.get('/user/tokens', mode['👀'](auth['👮'](userTokensGet
router.post('/user/tokens', mode['📝'](auth['👮'](userTokensPost)))
router.delete('/user/tokens/:id', mode['📝'](auth['👮'](userTokensDelete)))
router.get('/user/account', mode['👀'](auth['👮'](userAccountGet)))
router.get('/user/info', mode['👀'](auth['👮'](userInfoGet)))
/* eslint-enable no-multi-spaces */

// Monitoring
Expand Down
13 changes: 13 additions & 0 deletions packages/api/src/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ export async function userAccountGet (request, env) {
})
}

/**
* Retrieve user info
*
* @param {AuthenticatedRequest} request
* @param {import('./env').Env} env
*/
export async function userInfoGet (request, env) {
const info = await env.db.getUser(request.auth.user.issuer)
return new JSONResponse({
info
})
}

/**
* Retrieve user auth tokens.
*
Expand Down
5 changes: 5 additions & 0 deletions packages/api/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ workers_dev = true
account_id = "d7a6d34e62065b452c82b7fe992b8d88"
vars = { CLUSTER_API_URL = "https://joaopeixoto-cluster-api-web3-storage.loca.lt", ENV = "dev" }

[env.tempus]
workers_dev = true
account_id = "4303c6f9fed102b35399fe94af99f8bd"
vars = { CLUSTER_API_URL = "https://tempus-cluster-api-web3-storage.loca.lt", ENV = "dev" }

# Add your env here. Override the the values you need to change.

# Create your env name as the value of `whoami` on your system, so you can run `npm start` to run in dev mode.
Expand Down
1 change: 1 addition & 0 deletions packages/db/db-client-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type UserOutput = {
name: definitions['user']['name'],
email: definitions['user']['email'],
issuer: definitions['user']['issuer'],
github?: definitions['user']['github']
publicAddress: definitions['user']['public_address']
created: definitions['user']['inserted_at'],
updated: definitions['user']['updated_at']
Expand Down
2 changes: 2 additions & 0 deletions packages/db/fauna/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export class FaunaClient {
findUserByIssuer(issuer: $issuer) {
_id
issuer
email
github
}
}
`, { issuer })
Expand Down
1 change: 1 addition & 0 deletions packages/db/postgres/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export class PostgresClient {
issuer,
name,
email,
github,
publicAddress:public_address,
created:inserted_at,
updated:updated_at
Expand Down
91 changes: 91 additions & 0 deletions packages/website/components/state-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { createContext, useEffect, useReducer } from 'react'
import { useUser } from '../hooks/use-user'

/**
* @typedef {{
* user: {
* info: {
* email: string
* github: string
* issuer: string
* _id: string
* }
* }
* }} State
* */

/**
* @typedef {{
* info: {
* email: string
* github: string
* issuer: string
* _id: string
* }
* }} UserInfo
* */

/**
* @typedef {{
* type: 'updateUser'
* payload: any
* }} Action
* */

const initialState = {
user: {
info: {
email: '',
github: '',
issuer: '',
_id: ''
}
}
}

/**
* @param {State} state
* @param {UserInfo} userInfo
*/
const updateUser = (state, userInfo) => ({ ...state, ...{ user: userInfo }})

/**
* @param {State} state
* @param {Action} action
*/
const reducer = (state, action) => {
switch (action.type) {
case 'updateUser':
return updateUser(state, action.payload)
default:
throw new Error();
}
}

export const AppContext = createContext({
state: initialState,
/**
* @param {any} _dispatch
*/
dispatch: (_dispatch) => {}
})

/**
* @param {Object} props
* @param {JSX.Element} [props.children]
*/
const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)

// init app state with user
const user = useUser()
useEffect(() => dispatch({ type: 'updateUser', payload: user }), [ user ])

return (
<AppContext.Provider value={{ state: state, dispatch: dispatch }}>
{ children }
</AppContext.Provider>
)
}

export default StateProvider
11 changes: 11 additions & 0 deletions packages/website/hooks/use-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useQuery } from 'react-query'
import { useLoggedIn } from '../lib/user'
import { getInfo } from '../lib/api'

export const useUser = () => {
const { isLoggedIn } = useLoggedIn()
const { data } = useQuery('get-info', getInfo, {
enabled: isLoggedIn
})
return data
}
16 changes: 16 additions & 0 deletions packages/website/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ export async function getStorage() {
return res.json()
}

export async function getInfo() {
const res = await fetch(API + '/user/info', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + (await getToken()),
},
})

if (!res.ok) {
throw new Error(`failed to get user info: ${await res.text()}`)
}

return res.json()
}

/**
* Delete Token
*
Expand Down
9 changes: 6 additions & 3 deletions packages/website/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactQueryDevtools } from 'react-query/devtools'
import Router from 'next/router'
import '../styles/global.css'
import { QueryClient, QueryClientProvider } from 'react-query'
import StateProvider from '../components/state-provider'
import Layout from '../components/layout.js'
import countly from '../lib/countly';

Expand Down Expand Up @@ -31,9 +32,11 @@ export default function App({ Component, pageProps }) {

return (
<QueryClientProvider client={queryClient}>
<Layout {...pageProps}>
{(props) => <Component {...pageProps} {...props} />}
</Layout>
<StateProvider>
<Layout {...pageProps}>
{(props) => <Component {...pageProps} {...props} />}
</Layout>
</StateProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
Expand Down
24 changes: 16 additions & 8 deletions packages/website/pages/account.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-env browser */
import React, { useState, useEffect, useMemo, useCallback } from 'react'
import React, { useState, useEffect, useMemo, useCallback, useContext } from 'react'
import { useQuery } from 'react-query'
import Link from 'next/link'
import { getTokens, getStorage, getUploads } from '../lib/api'
Expand All @@ -10,6 +10,7 @@ import VerticalLines from '../illustrations/vertical-lines'
import { When } from 'react-if'
import emailContent from '../content/file-a-request'
import fileSize from 'filesize'
import { AppContext } from '../components/state-provider'

const MAX_STORAGE = 1.1e+12 /* 1 TB */

Expand Down Expand Up @@ -87,6 +88,8 @@ export default function Account({ isLoggedIn }) {
enabled: isLoggedIn
})

const { state } = useContext(AppContext)

/** @type {import('./tokens').Token[]} */
const tokens = tokensData || []

Expand All @@ -108,7 +111,7 @@ export default function Account({ isLoggedIn }) {
return () => clearTimeout(timer)
}, [copied])

const isLoaded = !isLoadingTokens && !isFetchingTokens && !isLoadingUploads && !isFetchingUploads
const isLoaded = !isLoadingTokens && !isFetchingTokens && !isLoadingUploads && !isFetchingUploads && !!state.user

const hasUsedTokensToUploadBefore = tokens.some(t => t.hasUploads)
/**
Expand Down Expand Up @@ -144,13 +147,18 @@ export default function Account({ isLoggedIn }) {
</When>
<div className="layout-margins">
<main className="max-w-screen-xl mx-auto my-4 lg:my-16 text-w3storage-purple">
<h3 className="mb-8">Your account</h3>
<StorageInfo isLoggedIn={isLoggedIn}/>
<div className="flex mb-8 flex-wrap items-center">
<h3 className="mr-2">Your account</h3>
<When condition={isLoaded}>
{ state.user?.info.email && <span>({state.user?.info.email}{state.user?.info.github && ` via GitHub` })</span> }
</When>
</div>
<StorageInfo isLoggedIn={isLoggedIn} />
<When condition={!isLoaded}>
<div className="relative w-52 pt-60">
<Loading />
</div>
</When>
<div className="relative w-52 pt-60">
<Loading />
</div>
</When>
<When condition={isLoaded}>
<When condition={tokens.length === 0 || !hasUsedTokensToUploadBefore}>
<div className="mt-9">
Expand Down

0 comments on commit a34d976

Please sign in to comment.