diff --git a/packages/app-root/public/assets/LTSignature.png b/packages/app-root/public/assets/LTSignature.png new file mode 100644 index 0000000000..3ebdbf5f25 Binary files /dev/null and b/packages/app-root/public/assets/LTSignature.png differ diff --git a/packages/app-root/src/app/users/[login]/groups/MyGroupsContainer.js b/packages/app-root/src/app/users/[login]/groups/MyGroupsContainer.js index bd540906da..dca115c9b1 100644 --- a/packages/app-root/src/app/users/[login]/groups/MyGroupsContainer.js +++ b/packages/app-root/src/app/users/[login]/groups/MyGroupsContainer.js @@ -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 diff --git a/packages/app-root/src/app/users/[login]/stats/UserStatsContainer.js b/packages/app-root/src/app/users/[login]/stats/UserStatsContainer.js index 2695de1247..fd56539310 100644 --- a/packages/app-root/src/app/users/[login]/stats/UserStatsContainer.js +++ b/packages/app-root/src/app/users/[login]/stats/UserStatsContainer.js @@ -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 ( ) diff --git a/packages/app-root/src/app/users/[login]/stats/certificate/CertificateContainer.js b/packages/app-root/src/app/users/[login]/stats/certificate/CertificateContainer.js new file mode 100644 index 0000000000..663b326d72 --- /dev/null +++ b/packages/app-root/src/app/users/[login]/stats/certificate/CertificateContainer.js @@ -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 ( + + + + ) +} + +export default CertificateContainer diff --git a/packages/app-root/src/app/users/[login]/stats/certificate/page.js b/packages/app-root/src/app/users/[login]/stats/certificate/page.js new file mode 100644 index 0000000000..ba6c38b277 --- /dev/null +++ b/packages/app-root/src/app/users/[login]/stats/certificate/page.js @@ -0,0 +1,14 @@ +import CertificateContainer from './CertificateContainer' + +export const metadata = { + title: 'Certificate', + description: 'My Zooniverse certificate page' +} + +export default function Certificate({ params }) { + return ( + + ) +} diff --git a/packages/app-root/src/app/users/[login]/stats/layout.js b/packages/app-root/src/app/users/[login]/stats/layout.js new file mode 100644 index 0000000000..dbeee24759 --- /dev/null +++ b/packages/app-root/src/app/users/[login]/stats/layout.js @@ -0,0 +1,9 @@ +import UserStatsContextProvider from '../../../../components/UserStatsContextProvider' + +export default function UserStatsLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/packages/app-root/src/app/users/[login]/components/AuthenticatedUsersPageContainer.js b/packages/app-root/src/components/AuthenticatedUsersPageContainer.js similarity index 100% rename from packages/app-root/src/app/users/[login]/components/AuthenticatedUsersPageContainer.js rename to packages/app-root/src/components/AuthenticatedUsersPageContainer.js diff --git a/packages/app-root/src/components/UserStatsContextProvider.js b/packages/app-root/src/components/UserStatsContextProvider.js new file mode 100644 index 0000000000..d8129e1255 --- /dev/null +++ b/packages/app-root/src/components/UserStatsContextProvider.js @@ -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 ( + + {children} + + ) +} + +export default UserStatsContextProvider diff --git a/packages/app-root/src/contexts/UserStatsContext.js b/packages/app-root/src/contexts/UserStatsContext.js new file mode 100644 index 0000000000..776ff67847 --- /dev/null +++ b/packages/app-root/src/contexts/UserStatsContext.js @@ -0,0 +1,5 @@ +import { createContext } from 'react' + +const UserStatsContext = createContext({}) + +export default UserStatsContext diff --git a/packages/app-root/src/contexts/index.js b/packages/app-root/src/contexts/index.js index 88e3911ba3..bf9fc3e20d 100644 --- a/packages/app-root/src/contexts/index.js +++ b/packages/app-root/src/contexts/index.js @@ -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' diff --git a/packages/app-root/src/hooks/index.js b/packages/app-root/src/hooks/index.js index e178f6d0c7..654a10f187 100644 --- a/packages/app-root/src/hooks/index.js +++ b/packages/app-root/src/hooks/index.js @@ -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' diff --git a/packages/app-root/src/hooks/useStatsDateRange.js b/packages/app-root/src/hooks/useStatsDateRange.js new file mode 100644 index 0000000000..93ffea0c7d --- /dev/null +++ b/packages/app-root/src/hooks/useStatsDateRange.js @@ -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 } +} diff --git a/packages/app-root/src/hooks/useStatsProject.js b/packages/app-root/src/hooks/useStatsProject.js new file mode 100644 index 0000000000..17bc261aa3 --- /dev/null +++ b/packages/app-root/src/hooks/useStatsProject.js @@ -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 } +} diff --git a/packages/lib-react-components/CHANGELOG.md b/packages/lib-react-components/CHANGELOG.md index b0bfe6ef6a..05ae88be96 100644 --- a/packages/lib-react-components/CHANGELOG.md +++ b/packages/lib-react-components/CHANGELOG.md @@ -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 diff --git a/packages/lib-react-components/src/ZooFooter/ZooFooter.js b/packages/lib-react-components/src/ZooFooter/ZooFooter.js index 28acc1a494..f70c84e0be 100644 --- a/packages/lib-react-components/src/ZooFooter/ZooFooter.js +++ b/packages/lib-react-components/src/ZooFooter/ZooFooter.js @@ -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' @@ -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', @@ -112,8 +120,8 @@ export default function ZooFooter({ const talkNavListLabels = [t('ZooFooter.talkLabels.talk')] return ( - {hasMounted && adminContainer} - + ) } diff --git a/packages/lib-react-components/src/ZooHeader/ZooHeader.js b/packages/lib-react-components/src/ZooHeader/ZooHeader.js index 29a796c963..9b7f9e7b68 100644 --- a/packages/lib-react-components/src/ZooHeader/ZooHeader.js +++ b/packages/lib-react-components/src/ZooHeader/ZooHeader.js @@ -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)` diff --git a/packages/lib-user/dev/components/App/App.js b/packages/lib-user/dev/components/App/App.js index 374a164169..589ee61bcd 100644 --- a/packages/lib-user/dev/components/App/App.js +++ b/packages/lib-user/dev/components/App/App.js @@ -6,6 +6,7 @@ import { string } from 'prop-types' import { useEffect, useState } from 'react' import { + Certificate, Contributors, GroupStats, MyGroups, @@ -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(() => { @@ -76,7 +79,9 @@ function App({
  • /stats - user stats page
  • @@ -130,12 +135,27 @@ function App({ if (login === '[login]') { content =

    In the url query param ?users=, please replace [login] with a user login.

    } else if (subpaths[1] === 'stats') { - content = ( - - ) + if (subpaths[2] === 'certificate') { + content = ( + + ) + } else { + content = ( + + ) + } } else if (subpaths[1] === 'groups') { content = ( -
    +

    lib-user - dev app

    diff --git a/packages/lib-user/dev/index.html b/packages/lib-user/dev/index.html index 958af80c36..631042e654 100644 --- a/packages/lib-user/dev/index.html +++ b/packages/lib-user/dev/index.html @@ -9,6 +9,12 @@ padding: 0; box-sizing: border-box; } + + @media print { + .dev-app-header { + display: none; + } + } diff --git a/packages/lib-user/src/components/Certificate/Certificate.js b/packages/lib-user/src/components/Certificate/Certificate.js new file mode 100644 index 0000000000..a4e197e1a5 --- /dev/null +++ b/packages/lib-user/src/components/Certificate/Certificate.js @@ -0,0 +1,277 @@ +import { SpacedText, ZooniverseLogo } from '@zooniverse/react-components' +import { Box } from 'grommet' +import { number, string } from 'prop-types' +import styled from 'styled-components' + +import { getDateInterval } from '@utils' + +import { + ContentBox, + HeaderLink, + Layout +} from '@components/shared' + +import { formatDateRange } from './helpers/formatDateRange' + +const PrintableBox = styled(Box)` + font-size: 16px; + + #certificate { + box-shadow: 1px 1px 4px 0px rgba(0, 0, 0, 0.25); + } + + @media print { + > *:not(#certificate) { + visibility: hidden; + } + + #certificate, #certificate * { + visibility: visible; + } + + #certificate { + box-shadow: none; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + } + + @page { + margin: 0; + size: 'landscape'; + } + + .userName { + letter-spacing: 6px; + } + + .userHours { + letter-spacing: 8px; + } +` + +function handleClickPrint() { + window.print() +} + +function Certificate({ + creditedName = '', + displayName = '', + hours = 0, + login = '', + projectDisplayName = '', + projectsCount = 0, + selectedDateRange = 'AllTime' +}) { + const { start_date, end_date } = getDateInterval(selectedDateRange) + + const formattedDateRange = formatDateRange(start_date, end_date) + + return ( + + + } + > + + + + + + + + + + + ) +} + +Certificate.propTypes = { + creditedName: string, + displayName: string, + hours: number, + login: string, + projectDisplayName: string, + projectsCount: number, + selectedDateRange: string +} + +export default Certificate diff --git a/packages/lib-user/src/components/Certificate/Certificate.stories.js b/packages/lib-user/src/components/Certificate/Certificate.stories.js new file mode 100644 index 0000000000..0d00c11ba6 --- /dev/null +++ b/packages/lib-user/src/components/Certificate/Certificate.stories.js @@ -0,0 +1,53 @@ +import Certificate from './Certificate' + +export default { + title: 'Components/Certificate', + component: Certificate +} + +export const Default = { + args: { + creditedName: 'Example T. User', + displayName: 'ExampleUser', + hours: 45, + login: 'testUser', + projectsCount: 67, + selectedDateRange: 'AllTime', + selectedProject: 'AllProjects' + } +} + +export const ProjectSpecific = { + args: { + creditedName: 'Example T. User', + displayName: 'Example User', + hours: 45, + login: 'testUser', + selectedDateRange: 'AllTime', + selectedProject: 'Galaxy Zoo' + } +} + +export const Last7Days = { + args: { + creditedName: 'Example T. User', + displayName: 'Example User', + hours: 45, + login: 'testUser', + projectsCount: 67, + selectedDateRange: 'Last7Days', + selectedProject: 'AllProjects' + } +} + +export const Last30Days = { + args: { + creditedName: 'Example T. User', + displayName: 'Example User', + hours: 45, + login: 'testUser', + projectsCount: 67, + selectedDateRange: 'Last30Days', + selectedProject: 'AllProjects' + } +} diff --git a/packages/lib-user/src/components/Certificate/CertificateContainer.js b/packages/lib-user/src/components/Certificate/CertificateContainer.js new file mode 100644 index 0000000000..ba81c0fd07 --- /dev/null +++ b/packages/lib-user/src/components/Certificate/CertificateContainer.js @@ -0,0 +1,81 @@ +'use client' + +import { shape, string } from 'prop-types' + +import { + usePanoptesProjects, + useStats +} from '@hooks' + +import { + convertStatsSecondsToHours, + getDateInterval +} from '@utils' + +import Certificate from './Certificate' + +const STATS_ENDPOINT = '/classifications/users' + +function CertificateContainer({ + authUser, + login, + selectedDateRange = 'AllTime', + selectedProject = 'AllProjects' +}) { + // TODO: fetch user data if authUser is not login user (admin view) + + // fetch stats + const statsQuery = getDateInterval(selectedDateRange) + statsQuery.time_spent = true + if (selectedProject !== 'AllProjects') { + statsQuery.project_id = parseInt(selectedProject) + } else { + statsQuery.project_contributions = true + } + + const { + data: stats, + error: statsError, + isLoading: statsLoading + } = useStats({ + endpoint: STATS_ENDPOINT, + sourceId: authUser?.id, + query: statsQuery + }) + + // fetch projects, if selectedProject is not 'AllProjects' + const projectIds = selectedProject !== 'AllProjects' ? [selectedProject] : null + const { + data: projects, + error: projectsError, + isLoading: projectsLoading + } = usePanoptesProjects(projectIds) + + const hours = convertStatsSecondsToHours(stats?.time_spent) + const projectsCount = stats?.project_contributions?.length || 0 + const projectDisplayName = projects?.[0]?.display_name + + return ( + + ) +} + +CertificateContainer.propTypes = { + authUser: shape({ + id: string, + login: string + }), + login: string, + selectedDateRange: string, + selectedProject: string, +} + +export default CertificateContainer diff --git a/packages/lib-user/src/components/Certificate/helpers/formatDateRange.js b/packages/lib-user/src/components/Certificate/helpers/formatDateRange.js new file mode 100644 index 0000000000..ba1b0bbd69 --- /dev/null +++ b/packages/lib-user/src/components/Certificate/helpers/formatDateRange.js @@ -0,0 +1,17 @@ +export function formatDateRange(startDate, endDate) { + const start = new Date(startDate) + const end = new Date(endDate) + + let formattedStartDate = `${start.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' })} ${start.getUTCDate()}` + let formattedEndDate = `${end.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' })} ${end.getUTCDate()}, ${end.getUTCFullYear()}` + + if (start.getUTCFullYear() !== end.getUTCFullYear()) { + formattedStartDate += `, ${start.getUTCFullYear()} - ` + } else if (start.getUTCMonth() !== end.getUTCMonth()) { + formattedStartDate += ` - ` + } else { + formattedEndDate = ` - ${end.getUTCDate()}, ${end.getUTCFullYear()}` + } + + return formattedStartDate + formattedEndDate +} diff --git a/packages/lib-user/src/components/Certificate/helpers/formatDateRange.spec.js b/packages/lib-user/src/components/Certificate/helpers/formatDateRange.spec.js new file mode 100644 index 0000000000..822ded0d0a --- /dev/null +++ b/packages/lib-user/src/components/Certificate/helpers/formatDateRange.spec.js @@ -0,0 +1,27 @@ +import { formatDateRange } from './formatDateRange' + +describe('components > Certificate > formatDateRange', function() { + it('should format date range with same month', function() { + const startDate = '2021-01-01' + const endDate = '2021-01-15' + const formattedDateRange = formatDateRange(startDate, endDate) + + expect(formattedDateRange).to.equal('Jan 1 - 15, 2021') + }) + + it('should format date range with same year', function() { + const startDate = '2021-01-01' + const endDate = '2021-12-31' + const formattedDateRange = formatDateRange(startDate, endDate) + + expect(formattedDateRange).to.equal('Jan 1 - Dec 31, 2021') + }) + + it('should format date range with different year', function() { + const startDate = '2021-01-01' + const endDate = '2022-01-31' + const formattedDateRange = formatDateRange(startDate, endDate) + + expect(formattedDateRange).to.equal('Jan 1, 2021 - Jan 31, 2022') + }) +}) diff --git a/packages/lib-user/src/components/Certificate/index.js b/packages/lib-user/src/components/Certificate/index.js new file mode 100644 index 0000000000..8d52ea00af --- /dev/null +++ b/packages/lib-user/src/components/Certificate/index.js @@ -0,0 +1 @@ +export { default } from './CertificateContainer' diff --git a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js index abd289a5fa..13ec196fa3 100644 --- a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js +++ b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react' import Meta, { Default } from './SubjectCard.stories.js' -describe('UserHome > compoents > SubjectCard', function () { +describe('UserHome > components > SubjectCard', function () { const DefaultStory = composeStory(Default, Meta) it('should show the subject id', function () { diff --git a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js index 99d4e4db1a..3e0e5e0b14 100644 --- a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js +++ b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js @@ -9,7 +9,7 @@ export default { // .jpeg or .png export const Default = { args: { - subjectID: 123, + subjectID: '123', mediaSrc: 'https://panoptes-uploads-staging.zooniverse.org/subject_location/34d154ca-1d8a-4c8a-ae29-96f58e0700f4.jpeg', projectSlug: 'brooke/i-fancy-cats' }, @@ -26,7 +26,7 @@ export const Default = { // .mp4 export const Video = { args: { - subjectID: 456, + subjectID: '456', mediaSrc: 'https://panoptes-uploads.zooniverse.org/subject_location/7da396d3-04f6-44b9-aef5-db4cac5dc172.mp4', projectSlug: 'sophiemu/solar-jet-hunter' }, @@ -43,7 +43,7 @@ export const Video = { // .txt export const Text = { args: { - subjectID: 789, + subjectID: '789', mediaSrc: 'https://panoptes-uploads.zooniverse.org/subject_location/f5506d1c-a0e9-4aba-a418-6a6c46a7731a.txt', projectSlug: 'markb-panoptes/digileap-testing-staging' }, @@ -60,7 +60,7 @@ export const Text = { // .json export const Data = { args: { - subjectID: 8364, + subjectID: '8364', mediaSrc: 'https://panoptes-uploads.zooniverse.org/subject_location/bdc78b71-ce68-4e10-9ae7-0e643e215143.json', projectSlug: 'cobalt-lensing/black-hole-hunters' }, diff --git a/packages/lib-user/src/components/UserStats/UserStatsContainer.js b/packages/lib-user/src/components/UserStats/UserStatsContainer.js index 50fdb6c52f..1233e05d08 100644 --- a/packages/lib-user/src/components/UserStats/UserStatsContainer.js +++ b/packages/lib-user/src/components/UserStats/UserStatsContainer.js @@ -1,7 +1,6 @@ 'use client' -import { shape, string } from 'prop-types' -import { useState } from 'react' +import { func, shape, string } from 'prop-types' import { usePanoptesProjects, @@ -15,15 +14,17 @@ import { import UserStats from './UserStats' +const DEFAULT_HANDLER = () => true const STATS_ENDPOINT = '/classifications/users' function UserStatsContainer({ authUser, login, + selectedDateRange = 'Last7Days', + selectedProject = 'AllProjects', + setSelectedDateRange = DEFAULT_HANDLER, + setSelectedProject = DEFAULT_HANDLER }) { - const [selectedProject, setSelectedProject] = useState('AllProjects') - const [selectedDateRange, setSelectedDateRange] = useState('Last7Days') - // fetch user const { data: user, @@ -73,14 +74,14 @@ function UserStatsContainer({ isLoading: projectsLoading } = usePanoptesProjects(projectIDs) - function handleProjectSelect (project) { - setSelectedProject(project.value) - } - function handleDateRangeSelect (dateRange) { setSelectedDateRange(dateRange.value) } + function handleProjectSelect (project) { + setSelectedProject(project.value) + } + return ( MyGroups > GroupForm', function() { +describe('components > shared > GroupForm', function() { describe('without a provided group (create mode)', function() { const user = userEvent.setup() diff --git a/packages/lib-user/src/components/shared/Layout/Layout.js b/packages/lib-user/src/components/shared/Layout/Layout.js index 1d2c331d0b..acb7c0c125 100644 --- a/packages/lib-user/src/components/shared/Layout/Layout.js +++ b/packages/lib-user/src/components/shared/Layout/Layout.js @@ -29,6 +29,10 @@ const PageBody = styled(Box)` border-radius: 8px 8px 0 0; padding-bottom: 50px; + @media print { + margin-top: 0; + } + @media (width > 90rem) { &::before { content: ''; diff --git a/packages/lib-user/src/components/shared/Layout/components/PageHeader.js b/packages/lib-user/src/components/shared/Layout/components/PageHeader.js index 69d6b04fc9..5388b16521 100644 --- a/packages/lib-user/src/components/shared/Layout/components/PageHeader.js +++ b/packages/lib-user/src/components/shared/Layout/components/PageHeader.js @@ -6,6 +6,12 @@ import styled from 'styled-components' import HeaderDropdown from './HeaderDropdown' +const StyledBox = styled(Box)` + @media print { + display: none; + } +` + const HeaderItems = styled(Box)` max-width: calc(90rem - 160px); ` @@ -14,8 +20,8 @@ function PageHeader({ primaryHeaderItem = '', secondaryHeaderItems = [] }) { const size = useContext(ResponsiveContext) return ( - - + ) } diff --git a/packages/lib-user/src/components/shared/MainContent/MainContent.js b/packages/lib-user/src/components/shared/MainContent/MainContent.js index 3d9d7a4809..826dc03cb4 100644 --- a/packages/lib-user/src/components/shared/MainContent/MainContent.js +++ b/packages/lib-user/src/components/shared/MainContent/MainContent.js @@ -1,6 +1,7 @@ -import { Box, Tab } from 'grommet' +import { Box, Button, Tab } from 'grommet' import { arrayOf, func, number, shape, string } from 'prop-types' import { useState } from 'react' +import styled from 'styled-components' import { convertStatsSecondsToHours, @@ -26,6 +27,12 @@ const DEFAULT_SOURCE = { display_name: '', } +const StyledButton = styled(Button)` + background-color: ${props => props.theme.global.colors['neutral-1']}; + border-radius: 4px; + color: ${props => props.theme.global.colors['neutral-6']}; +` + function MainContent({ handleDateRangeSelect = DEFAULT_HANDLER, handleProjectSelect = DEFAULT_HANDLER, @@ -130,7 +137,12 @@ function MainContent({ gap='16px' justify='end' > - + ) : null} diff --git a/packages/lib-user/src/components/shared/MainContent/MainContent.spec.js b/packages/lib-user/src/components/shared/MainContent/MainContent.spec.js index 4bba0dd08f..06cc37d847 100644 --- a/packages/lib-user/src/components/shared/MainContent/MainContent.spec.js +++ b/packages/lib-user/src/components/shared/MainContent/MainContent.spec.js @@ -6,7 +6,7 @@ import { STATS } from '../../../../test/mocks/stats.mock.js' import Meta, { Default } from './MainContent.stories.js' -describe('components > UserStats > MainContent', function () { +describe('components > shared > MainContent', function () { const DefaultStory = composeStory(Default, Meta) it('should show the user display name', function () { diff --git a/packages/lib-user/src/components/shared/Tabs/theme.js b/packages/lib-user/src/components/shared/Tabs/theme.js index 3374e42379..c7b6719d80 100644 --- a/packages/lib-user/src/components/shared/Tabs/theme.js +++ b/packages/lib-user/src/components/shared/Tabs/theme.js @@ -2,11 +2,9 @@ import { css } from 'styled-components' const tabTheme = { tab: { - active: { - color: { - dark: 'brand', - light: 'brand' - }, + color: { + dark: 'light-3', + light: 'dark-5' }, border: { active: { diff --git a/packages/lib-user/src/components/shared/TopProjects/TopProjects.spec.js b/packages/lib-user/src/components/shared/TopProjects/TopProjects.spec.js index 6be0653a4d..15c22c417c 100644 --- a/packages/lib-user/src/components/shared/TopProjects/TopProjects.spec.js +++ b/packages/lib-user/src/components/shared/TopProjects/TopProjects.spec.js @@ -5,7 +5,7 @@ import { PROJECTS } from '../../../../test/mocks/panoptes' import Meta, { Default } from './TopProjects.stories.js' -describe('components > UserStats > TopProjects', function () { +describe('components > shared > TopProjects', function () { const DefaultStory = composeStory(Default, Meta) it('should show "Top Projects" content section title', function () { diff --git a/packages/lib-user/src/index.js b/packages/lib-user/src/index.js index 223cac5034..3e5ebe32be 100644 --- a/packages/lib-user/src/index.js +++ b/packages/lib-user/src/index.js @@ -1,7 +1,8 @@ // components -export { default as Contributors } from './components/Contributors' +export { default as Certificate } from './components/Certificate' export { default as GroupStats } from './components/GroupStats' export { default as MyGroups } from './components/MyGroups' +export { default as Contributors } from './components/Contributors' export { default as UserHome } from './components/UserHome' export { default as UserStats } from './components/UserStats' diff --git a/packages/lib-user/src/utils/getDateInterval.js b/packages/lib-user/src/utils/getDateInterval.js index 8f24c2024d..8455e4e9a2 100644 --- a/packages/lib-user/src/utils/getDateInterval.js +++ b/packages/lib-user/src/utils/getDateInterval.js @@ -71,7 +71,9 @@ export function getDateInterval(dateRange) { } } else if (dateRange === 'AllTime') { return { - period: 'year' + end_date, + period: 'year', + start_date: '2015-07-01' } } else { return { diff --git a/packages/lib-user/src/utils/getDateInterval.spec.js b/packages/lib-user/src/utils/getDateInterval.spec.js index 4a61d66b9e..bdb279dd26 100644 --- a/packages/lib-user/src/utils/getDateInterval.spec.js +++ b/packages/lib-user/src/utils/getDateInterval.spec.js @@ -61,7 +61,9 @@ describe('utils > getDateInterval', function () { it('should return the expected date interval for the AllTime date range', function () { const dateInterval = getDateInterval('AllTime') expect(dateInterval).to.deep.equal({ + end_date: '2023-04-15', period: 'year', + start_date: '2015-07-01' }) }) })