Skip to content

Commit

Permalink
app-root, lib-user: Add ProfileHeader to UserStats, refactor related …
Browse files Browse the repository at this point in the history
…API requests (#5898)

* Pass authClient to UserStats

* Init UserStats add ProfileHeader

* Refactor lib-user auth and user request

* Refactor useUserStats

* Refactor hooks for auth and oauth

* Add stats hosts

* Fix ProfileHeader test

* Refactor ProfileHeader titled stats

* Move dateRanges

* Add statsQuery to UserStats

* Remove withResponsiveContext, replace with Grommet ResponsiveContext

* Refactor user login usage
  • Loading branch information
mcbouslog authored Feb 21, 2024
1 parent 2acf138 commit eb3aef8
Show file tree
Hide file tree
Showing 20 changed files with 137 additions and 119 deletions.
6 changes: 4 additions & 2 deletions packages/app-root/src/app/users/[login]/stats/page.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client'

import { UserStats } from '@zooniverse/user'
import auth from 'panoptes-client/lib/auth'

export default function UserPage() {
return (
<UserStats />
<UserStats
authClient={auth}
/>
)
}
8 changes: 4 additions & 4 deletions packages/lib-user/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@zooniverse/panoptes-js": "~0.4.0",
"panoptes-client": "~5.6.0",
"swr": "~2.2.4"
},
"peerDependencies": {
"@zooniverse/grommet-theme": "3.x.x",
"@zooniverse/panoptes-js": "~0.4.0",
"@zooniverse/react-components": "~1.x.x",
"grommet": "2.x.x",
"react": "~18.2.0",
Expand Down Expand Up @@ -70,9 +71,8 @@
"html-webpack-plugin": "~5.6.0",
"ignore-styles": "~5.0.1",
"jsdom": "~24.0.0",
"mocha": "~10.3.0",
"nock": "~13.5.1",
"panoptes-client": "~5.6.0",
"mocha": "~10.2.0",
"nock": "~13.4.0",
"process": "~0.11.10",
"prop-types": "^15.8.1",
"sinon": "~17.0.0",
Expand Down
57 changes: 31 additions & 26 deletions packages/lib-user/src/components/UserStats/UserStats.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,49 @@
'use client'

// This component is a work in progress. It is not intended to be imported as-is, but is currently being used for initial UserStats local development.
import { object } from 'prop-types'

import { node } from 'prop-types'
import {
usePanoptesUser,
useUserStats
} from '@hooks'

import Layout from '../shared/Layout/Layout'
import ContentBox from '../shared/ContentBox/ContentBox'
import ProfileHeader from '../shared/ProfileHeader/ProfileHeader'

function UserStats ({
children
authClient
}) {
const { data: user, error, isLoading } = usePanoptesUser(authClient)

const statsQuery = {
period: 'year',
project_contributions: true,
time_spent: true
}
const { data: userStats, error: statsError, isLoading: statsLoading } = useUserStats({ authClient, query: statsQuery, userID: user?.id })

return (
<Layout>
<div>
<div style={{
borderRadius: '8px',
border: '0.5px solid #A6A7A9',
boxShadow: '0px 1px 4px 0px rgba(0, 0, 0, 0.25)',
color: 'black',
height: '472px',
marginBottom: '30px'
}}>
<p>User profile header goes here.</p>
<p>Bar chart goes here.</p>
</div>
<div style={{
borderRadius: '8px',
border: '0.5px solid #A6A7A9',
boxShadow: '0px 1px 4px 0px rgba(0, 0, 0, 0.25)',
color: 'black',
height: '300px'
}}>
<p>Top projects goes here.</p>
</div>
</div>
<ContentBox
direction='column'
gap='32px'
height='400px'
>
<ProfileHeader
avatar={user?.avatar_src}
classifications={userStats?.total_count}
displayName={user?.display_name}
login={user?.login}
projects={userStats?.project_contributions?.length}
/>
</ContentBox>
</Layout>
)
}

UserStats.propTypes = {
children: node
authClient: object
}

export default UserStats
18 changes: 10 additions & 8 deletions packages/lib-user/src/components/shared/BarChart/BarChart.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { DataChart, Text } from 'grommet'
import { DataChart, ResponsiveContext, Text } from 'grommet'
import { arrayOf, number, shape, string } from 'prop-types'
import withResponsiveContext from '@zooniverse/react-components/helpers/withResponsiveContext'
import { useContext } from 'react'

import {
dateRanges
} from '@utils'

import dateRanges from './helpers/dateRanges'
import getDateRangeLabel from './helpers/getDateRangeLabel'

function BarChart ({
data = [],
dateRange = dateRanges.Last7Days,
screenSize = 'small',
type = 'count'
}) {
const size = useContext(ResponsiveContext)
const dateRangeLabel = getDateRangeLabel(dateRange)
const typeLabel = type === 'count' ? 'Classifications' : 'Time'
const readableDateRange = dateRange
Expand All @@ -24,10 +27,10 @@ function BarChart ({
property: type,
type: 'bar'
}
if (screenSize !== 'small' && data.length < 9) {
if (size !== 'small' && data.length < 9) {
chartOptions.thickness = 'xlarge'
}
if (screenSize === 'small') {
if (size === 'small') {
if (data.length < 12) {
chartOptions.thickness = 'small'
} else if (data.length > 11 && data.length < 19) {
Expand Down Expand Up @@ -104,8 +107,7 @@ BarChart.propTypes = {
session_time: number
})),
dateRange: string,
screenSize: string,
type: string
}

export default withResponsiveContext(BarChart)
export default BarChart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Box } from 'grommet'

import dateRanges from './helpers/dateRanges.js'
import {
dateRanges
} from '@utils'

import {
last7days,
last30days,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function ContentBox({
}}
border={border}
elevation={screenSize === 'small' ? 'none' : 'xsmall'}
margin={screenSize === 'small' ? 'none' : '30px'}
margin={screenSize === 'small' ? '30px' : 'none'}
pad='30px'
round={screenSize === 'small' ? 'none' : '8px'}
{...rest}
Expand Down
1 change: 1 addition & 0 deletions packages/lib-user/src/components/shared/Layout/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ function Layout ({ children }) {
dark: 'dark-3',
light: 'neutral-6'
}}
gap='32px'
>
{children}
</PageBody>
Expand Down
1 change: 1 addition & 0 deletions packages/lib-user/src/components/shared/Layout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Layout } from './Layout.js'
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ function TitledStat ({
title = '',
value = 0
}) {
let displayValue = value
if (isNaN(value)) {
displayValue = 0
}

return (
<Box
align='center'
>
<SpacedText
color={{ dark: 'neutral-6', light: 'neutral-7' }}
size='xsmall'
uppercase={false}
>
{title}
Expand All @@ -32,7 +36,7 @@ function TitledStat ({
size='xlarge'
weight='bold'
>
{value}
{Math.round(displayValue).toLocaleString()}
</SpacedText>
</Box>
)
Expand All @@ -45,12 +49,12 @@ TitledStat.propTypes = {

function ProfileHeader ({
avatar = '',
classifications = 0,
contributors = 0,
classifications = undefined,
contributors = undefined,
displayName = '',
hours = 0,
hours = undefined,
login = '',
projects = 0,
projects = undefined,
screenSize = 'medium'
}) {
return (
Expand Down Expand Up @@ -97,25 +101,25 @@ function ProfileHeader ({
direction='row'
gap='small'
>
{classifications ?
{classifications !== undefined ?
<TitledStat
title='Classifications'
value={classifications}
/>
: null}
{hours ?
{hours !== undefined ?
<TitledStat
title='Hours'
value={hours}
/>
: null}
{contributors ?
{contributors !== undefined ?
<TitledStat
title='Contributors'
value={contributors}
/>
: null}
{projects ?
{projects !== undefined ?
<TitledStat
title='Projects'
value={projects}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('components > shared > ProfileHeader', function () {

it('should show the group\'s classifications', function () {
render(<GroupStory />)
const classifications = screen.getByText('1526')
const classifications = screen.getByText('1,526')
expect(classifications).to.be.ok()
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { composeStory } from '@storybook/react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import dateRanges from '../BarChart/helpers/dateRanges'
import {
dateRanges
} from '@utils'

import Meta, { DateRanges } from './Select.stories'

describe('components > shared > Select', function() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Box } from 'grommet'

import Select from './Select.js'

import dateRanges from '../BarChart/helpers/dateRanges.js'
import {
dateRanges
} from '@utils'

export default {
title: 'Components/shared/Select',
Expand Down
11 changes: 5 additions & 6 deletions packages/lib-user/src/hooks/usePanoptesAuth.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useEffect, useState } from 'react'

import { getBearerToken } from '@utils/index.js'
import { getBearerToken } from '@utils'

export default function usePanoptesAuth({ authClient, userID }) {
const [authorization, setAuthorization] = useState()
async function checkAuth() {
const token = await getBearerToken(authClient)
setAuthorization(token)
}

useEffect(function onUserChange() {
async function checkAuth() {
const token = await getBearerToken(authClient)
setAuthorization(token)
}

checkAuth()
}, [authClient, userID])

Expand Down
34 changes: 11 additions & 23 deletions packages/lib-user/src/hooks/usePanoptesUser.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
import { auth } from '@zooniverse/panoptes-js'
import auth from 'panoptes-client/lib/auth'
import { useEffect, useState } from 'react'

import { getBearerToken } from '@utils/index.js'

// TODO: refactor with SWR

async function fetchPanoptesUser(authClient) {
try {
const authorization = await getBearerToken(authClient)
if (authorization) {
const { user, error } = await auth.decodeJWT(authorization)
if (user) {
return user
}
if (error) {
throw error
}
}
return await authClient.checkCurrent()
} catch (error) {
console.log(error)
return null
async function fetchPanoptesUser() {
const panoptesUser = await auth.checkCurrent()
if (panoptesUser) {
// A lot of user properties are not needed in lib-user, so we only return the ones we need; edit as needed.
const { admin, avatar_src, display_name, id, login } = panoptesUser
return { admin, avatar_src, display_name, id, login }
}

return null
}

export default function usePanoptesUser(authClient) {
Expand All @@ -33,7 +21,7 @@ export default function usePanoptesUser(authClient) {
async function checkUserSession() {
setLoading(true)
try {
const panoptesUser = await fetchPanoptesUser(authClient)
const panoptesUser = await fetchPanoptesUser()
setUser(panoptesUser)
} catch (error) {
setError(error)
Expand All @@ -45,7 +33,7 @@ export default function usePanoptesUser(authClient) {
authClient.listen('change', checkUserSession)

return function () {
authClient?.stopListening('change', checkUserSession)
authClient.stopListening('change', checkUserSession)
}
}, [authClient])

Expand Down
Loading

0 comments on commit eb3aef8

Please sign in to comment.