Skip to content

Commit

Permalink
lib-user: Add certificate page (#6124)
Browse files Browse the repository at this point in the history
* Move AuthenticatedUsersPageContainer

* Initial Certificate page

* Add CertificateContainer, refactor styling

* Refactor Layout and PageHeader media print styling

* Add Certificate page box-shadow, refactor borders

* Refactor Box flex for printing

* Format ContentLink when button

* Refactor Certificate date range

* Update test descriptions

* Refactor Generate Volunteer Certificate link

* Refactor lib-user UserStats and Certificate containers for selectedDateRange and selectedProject as props

* Create app-root UserStatsContext and UserStatsContextProvider

* Refactor app-root UserStats and Certificate containers with UserStats context

* Refactor lib-user dev app

* Update lib-react-components CHANGELOG and add comments for print display none styling

* Refactor user stats dateRange and project hooks to localStorage for persistence across browser tabs

* Clean up

* Reset user stats dateRange and project context on sign-out

* Update packages/lib-user/src/components/Certificate/Certificate.js

* Fix Tab color contrast issue

* Refactor Generate Volunteer Certificate button

* Add svg gradient line under signature img
  • Loading branch information
mcbouslog authored Jun 14, 2024
1 parent f1e524f commit cdd1c1c
Show file tree
Hide file tree
Showing 39 changed files with 735 additions and 44 deletions.
Binary file added packages/app-root/public/assets/LTSignature.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MyGroups } from '@zooniverse/user'
import { useContext } from 'react'

import { PanoptesAuthContext } from '../../../../contexts'
import AuthenticatedUsersPageContainer from '../components/AuthenticatedUsersPageContainer'
import AuthenticatedUsersPageContainer from '../../../../components/AuthenticatedUsersPageContainer'

function MyGroupsContainer({
login
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
import { UserStats } from '@zooniverse/user'
import { useContext } from 'react'

import { PanoptesAuthContext } from '../../../../contexts'
import AuthenticatedUsersPageContainer from '../components/AuthenticatedUsersPageContainer'
import { PanoptesAuthContext, UserStatsContext } from '../../../../contexts'
import AuthenticatedUsersPageContainer from '../../../../components/AuthenticatedUsersPageContainer'

function UserStatsContainer({
login
}) {
const { adminMode, isLoading, user } = useContext(PanoptesAuthContext)
const {
selectedDateRange,
selectedProject,
setSelectedDateRange,
setSelectedProject
} = useContext(UserStatsContext)

return (
<AuthenticatedUsersPageContainer
Expand All @@ -21,6 +27,10 @@ function UserStatsContainer({
<UserStats
authUser={user}
login={login}
selectedDateRange={selectedDateRange}
selectedProject={selectedProject}
setSelectedDateRange={setSelectedDateRange}
setSelectedProject={setSelectedProject}
/>
</AuthenticatedUsersPageContainer>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use client'

import { Certificate } from '@zooniverse/user'
import { useContext } from 'react'

import { PanoptesAuthContext, UserStatsContext } from '../../../../../contexts'
import AuthenticatedUsersPageContainer from '../../../../../components/AuthenticatedUsersPageContainer'

function CertificateContainer({
login
}) {
const { adminMode, isLoading, user } = useContext(PanoptesAuthContext)
const {
selectedDateRange,
selectedProject
} = useContext(UserStatsContext)

return (
<AuthenticatedUsersPageContainer
adminMode={adminMode}
isLoading={isLoading}
login={login}
user={user}
>
<Certificate
authUser={user}
login={login}
selectedDateRange={selectedDateRange}
selectedProject={selectedProject}
/>
</AuthenticatedUsersPageContainer>
)
}

export default CertificateContainer
14 changes: 14 additions & 0 deletions packages/app-root/src/app/users/[login]/stats/certificate/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import CertificateContainer from './CertificateContainer'

export const metadata = {
title: 'Certificate',
description: 'My Zooniverse certificate page'
}

export default function Certificate({ params }) {
return (
<CertificateContainer
login={params.login}
/>
)
}
9 changes: 9 additions & 0 deletions packages/app-root/src/app/users/[login]/stats/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import UserStatsContextProvider from '../../../../components/UserStatsContextProvider'

export default function UserStatsLayout({ children }) {
return (
<UserStatsContextProvider>
{children}
</UserStatsContextProvider>
)
}
27 changes: 27 additions & 0 deletions packages/app-root/src/components/UserStatsContextProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client'

import { useContext } from 'react'

import { PanoptesAuthContext, UserStatsContext } from '../contexts'
import { useStatsDateRange, useStatsProject } from '../hooks'

function UserStatsContextProvider({ children }) {
const { isLoading, user } = useContext(PanoptesAuthContext)
const { selectedDateRange, setSelectedDateRange } = useStatsDateRange({ isLoading, user })
const { selectedProject, setSelectedProject } = useStatsProject({ isLoading, user })

const statsContext = {
selectedDateRange,
selectedProject,
setSelectedDateRange,
setSelectedProject
}

return (
<UserStatsContext.Provider value={statsContext}>
{children}
</UserStatsContext.Provider>
)
}

export default UserStatsContextProvider
5 changes: 5 additions & 0 deletions packages/app-root/src/contexts/UserStatsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from 'react'

const UserStatsContext = createContext({})

export default UserStatsContext
1 change: 1 addition & 0 deletions packages/app-root/src/contexts/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as PanoptesAuthContext } from './PanoptesAuthContext.js'
export { default as ThemeModeContext } from './ThemeModeContext.js'
export { default as UserStatsContext } from './UserStatsContext.js'
2 changes: 2 additions & 0 deletions packages/app-root/src/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { default as useAdminMode } from './useAdminMode.js'
export { default as usePreferredTheme } from './usePreferredTheme.js'
export { default as useStatsDateRange } from './useStatsDateRange.js'
export { default as useStatsProject } from './useStatsProject.js'
26 changes: 26 additions & 0 deletions packages/app-root/src/hooks/useStatsDateRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useState } from 'react'

const isBrowser = typeof window !== 'undefined'
const localStorage = isBrowser ? window.localStorage : null

let initialDateRange = 'Last7Days'
if (isBrowser) {
initialDateRange = localStorage?.getItem('selectedDateRange') || initialDateRange
}

export default function useStatsDateRange({ isLoading, user }) {
const [selectedDateRange, setSelectedDateRange] = useState(initialDateRange)

useEffect(function onDateRangeChange() {
localStorage?.setItem('selectedDateRange', selectedDateRange)
}, [selectedDateRange])

useEffect(function onUserChange() {
// when a user successfully logs out isLoading is false and user is undefined
if (!isLoading && !user?.login) {
setSelectedDateRange('Last7Days')
}
}, [isLoading, user?.login])

return { selectedDateRange, setSelectedDateRange }
}
26 changes: 26 additions & 0 deletions packages/app-root/src/hooks/useStatsProject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useState } from 'react'

const isBrowser = typeof window !== 'undefined'
const localStorage = isBrowser ? window.localStorage : null

let initialProject = 'AllProjects'
if (isBrowser) {
initialProject = localStorage?.getItem('selectedProject') || initialProject
}

export default function useStatsProject({ isLoading, user }) {
const [selectedProject, setSelectedProject] = useState(initialProject)

useEffect(function onProjectChange() {
localStorage?.setItem('selectedProject', selectedProject)
}, [selectedProject])

useEffect(function onUserChange() {
// when a user successfully logs out isLoading is false and user is undefined
if (!isLoading && !user?.login) {
setSelectedProject('AllProjects')
}
}, [isLoading, user?.login])

return { selectedProject, setSelectedProject }
}
7 changes: 7 additions & 0 deletions packages/lib-react-components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Change Log

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Changed

- ZooFooter and ZooHeader styling to include `display: none;` when printing (`@media print`)

## [1.13.0] 2024-05-17

### Added
Expand Down
14 changes: 11 additions & 3 deletions packages/lib-react-components/src/ZooFooter/ZooFooter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Box, Grid } from 'grommet'
import { arrayOf, node, string } from 'prop-types'
import { useEffect } from 'react'
import styled from 'styled-components'
import i18n, { useTranslation } from '../translations/i18n'
import { useHasMounted } from '../hooks'

Expand All @@ -11,6 +12,13 @@ import {
SocialAnchor
} from './components'

const StyledFooter = styled(Box)`
// hide the footer when printing, added for the user stats certificate, but applies generally
@media print {
display: none;
}
`

const defaultProps = {
aboutNavListURLs: [
'https://www.zooniverse.org/about',
Expand Down Expand Up @@ -112,8 +120,8 @@ export default function ZooFooter({
const talkNavListLabels = [t('ZooFooter.talkLabels.talk')]

return (
<Box
as='footer'
<StyledFooter
forwardedAs='footer'
background={{
dark: 'dark-1',
light: 'white'
Expand Down Expand Up @@ -191,7 +199,7 @@ export default function ZooFooter({
/>
<Box>{hasMounted && adminContainer}</Box>
</Box>
</Box>
</StyledFooter>
)
}

Expand Down
5 changes: 5 additions & 0 deletions packages/lib-react-components/src/ZooHeader/ZooHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import ZooniverseLogo from '../ZooniverseLogo'
export const StyledHeader = styled(Box)`
color: #b2b2b2;
font-size: 1em;
// hide the header when printing, added for the user stats certificate, but applies generally
@media print {
display: none;
}
`

export const StyledLogoAnchor = styled(Anchor)`
Expand Down
36 changes: 28 additions & 8 deletions packages/lib-user/dev/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { string } from 'prop-types'
import { useEffect, useState } from 'react'

import {
Certificate,
Contributors,
GroupStats,
MyGroups,
Expand All @@ -27,6 +28,8 @@ function App({
const [activeIndex, setActiveIndex] = useState(-1)
const [dark, setDarkTheme] = useState(false)
const [loading, setLoading] = useState(false)
const [selectedDateRange, setSelectedDateRange] = useState('Last7Days')
const [selectedProject, setSelectedProject] = useState('AllProjects')
const [user, setUser] = useState(null)

useEffect(() => {
Expand Down Expand Up @@ -76,7 +79,9 @@ function App({
<li>
<a href={`./?users=${userSubpath}/stats`}>/stats - user stats page</a>
<ul>
<li>/certificate - Volunteer Certificate</li>
<li>
<a href={`./?users=${userSubpath}/stats/certificate`}>/certificate - Volunteer Certificate</a>
</li>
</ul>
</li>
<li>
Expand Down Expand Up @@ -130,12 +135,27 @@ function App({
if (login === '[login]') {
content = <p>In the url query param <code>?users=</code>, please replace <code>[login]</code> with a user login.</p>
} else if (subpaths[1] === 'stats') {
content = (
<UserStats
authUser={user}
login={login}
/>
)
if (subpaths[2] === 'certificate') {
content = (
<Certificate
authUser={user}
login={login}
selectedDateRange={selectedDateRange}
selectedProject={selectedProject}
/>
)
} else {
content = (
<UserStats
authUser={user}
login={login}
selectedDateRange={selectedDateRange}
selectedProject={selectedProject}
setSelectedDateRange={setSelectedDateRange}
setSelectedProject={setSelectedProject}
/>
)
}
} else if (subpaths[1] === 'groups') {
content = (
<MyGroups
Expand Down Expand Up @@ -163,7 +183,7 @@ function App({
closeModal={closeAuthModal}
onActive={setActiveIndex}
/>
<header>
<header className='dev-app-header'>
<p>
<a href='/'>lib-user - dev app</a>
</p>
Expand Down
6 changes: 6 additions & 0 deletions packages/lib-user/dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
padding: 0;
box-sizing: border-box;
}

@media print {
.dev-app-header {
display: none;
}
}
</style>
</head>
<body>
Expand Down
Loading

0 comments on commit cdd1c1c

Please sign in to comment.