Skip to content

Commit

Permalink
feat: get terms of use repository convention
Browse files Browse the repository at this point in the history
  • Loading branch information
g-saracca committed Nov 28, 2024
1 parent e674e26 commit edf4429
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 30 deletions.
1 change: 1 addition & 0 deletions src/info/domain/models/TermsOfUse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type TermsOfUse = string | null
2 changes: 2 additions & 0 deletions src/info/domain/repositories/DataverseInfoRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DataverseVersion } from '../models/DataverseVersion'
import { TermsOfUse } from '../models/TermsOfUse'

export interface DataverseInfoRepository {
getVersion(): Promise<DataverseVersion>
getTermsOfUse: () => Promise<TermsOfUse>
}
10 changes: 10 additions & 0 deletions src/info/domain/useCases/getTermsOfUse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type TermsOfUse } from '../../../info/domain/models/TermsOfUse'
import { DataverseInfoRepository } from '../repositories/DataverseInfoRepository'

export function getTermsOfUse(
dataverseInfoRepository: DataverseInfoRepository
): Promise<TermsOfUse> {
return dataverseInfoRepository.getTermsOfUse().catch((error) => {
throw error
})
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getDataverseVersion, ReadError } from '@iqss/dataverse-client-javascript'
import { DataverseInfoRepository } from '../../domain/repositories/DataverseInfoRepository'
import { DataverseVersion } from '../../domain/models/DataverseVersion'
import { axiosInstance } from '@/axiosInstance'
import { DataverseInfoRepository } from '@/info/domain/repositories/DataverseInfoRepository'
import { DataverseVersion } from '@/info/domain/models/DataverseVersion'
import { TermsOfUse } from '@/info/domain/models/TermsOfUse'

interface JSDataverseDataverseVersion {
number: string
Expand All @@ -26,4 +28,13 @@ export class DataverseInfoJSDataverseRepository implements DataverseInfoReposito
throw new Error(error.message)
})
}

async getTermsOfUse() {
//TODO - implement using js-dataverse
const response = await axiosInstance.get<{ data: { message: TermsOfUse } }>(
'/api/v1/info/apiTermsOfUse',
{ excludeToken: true }
)
return response.data.data.message
}
}
11 changes: 9 additions & 2 deletions src/sections/sign-up/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Tabs } from '@iqss/dataverse-design-system'
import { DataverseInfoRepository } from '@/info/domain/repositories/DataverseInfoRepository'
import { useLoading } from '../loading/LoadingContext'
import { ValidTokenNotLinkedAccountForm } from './valid-token-not-linked-account-form/ValidTokenNotLinkedAccountForm'
import styles from './SignUp.module.scss'
Expand All @@ -9,10 +10,14 @@ import styles from './SignUp.module.scss'
// TODO:ME - How to handle 401 Unauthorized {"status":"ERROR","message":"Unauthorized bearer token."} globally, maybe redirect to oidc login page?

interface SignUpProps {
dataverseInfoRepository: DataverseInfoRepository
hasValidTokenButNotLinkedAccount: boolean
}

export const SignUp = ({ hasValidTokenButNotLinkedAccount }: SignUpProps) => {
export const SignUp = ({
dataverseInfoRepository,
hasValidTokenButNotLinkedAccount
}: SignUpProps) => {
const { t } = useTranslation('signUp')
const { setIsLoading } = useLoading()

Expand Down Expand Up @@ -46,7 +51,9 @@ export const SignUp = ({ hasValidTokenButNotLinkedAccount }: SignUpProps) => {
<Tabs defaultActiveKey="accountInfo">
<Tabs.Tab eventKey="accountInfo" title={t('accountInfo')}>
<div className={styles['tab-container']}>
<ValidTokenNotLinkedAccountForm />
{hasValidTokenButNotLinkedAccount && (
<ValidTokenNotLinkedAccountForm dataverseInfoRepository={dataverseInfoRepository} />
)}
</div>
</Tabs.Tab>
</Tabs>
Expand Down
10 changes: 9 additions & 1 deletion src/sections/sign-up/SignUpFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { ReactElement } from 'react'
import { useSearchParams } from 'react-router-dom'
import { SignUp } from './SignUp'
import { QueryParamKey } from '../Route.enum'
import { DataverseInfoJSDataverseRepository } from '@/info/infrastructure/repositories/DataverseInfoJSDataverseRepository'

const dataverseInfoRepository = new DataverseInfoJSDataverseRepository()

export class SignUpFactory {
static create(): ReactElement {
Expand All @@ -15,5 +18,10 @@ function SignUpWithSearchParams() {
const hasValidTokenButNotLinkedAccount =
searchParams.get(QueryParamKey.VALID_TOKEN_BUT_NOT_LINKED_ACCOUNT) === 'true'

return <SignUp hasValidTokenButNotLinkedAccount={hasValidTokenButNotLinkedAccount} />
return (
<SignUp
dataverseInfoRepository={dataverseInfoRepository}
hasValidTokenButNotLinkedAccount={hasValidTokenButNotLinkedAccount}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,9 @@ import { useSession } from '@/sections/session/SessionContext'
import { Validator } from '@/shared/helpers/Validator'
import { type ValidTokenNotLinkedAccountFormData } from './types'
import { ValidTokenNotLinkedAccountFormHelper } from './ValidTokenNotLinkedAccountFormHelper'
import { useGetTermsOfUse } from '@/shared/hooks/useGetTermsOfUse'
import { AppLoader } from '@/sections/shared/layout/app-loader/AppLoader'
import { TermsOfUse } from '@/info/domain/models/TermsOfUse'
import styles from './FormFields.module.scss'

interface FormFieldsProps {
formDefaultValues: ValidTokenNotLinkedAccountFormData
}

// TODO:ME - Maybe we should redirect to a welcome page after success? ask if there is one, maybe not the case for this scenario
// TODO:ME - Ask about the format of the terms of use, html string? just text string? what is shown in the box if there is just a url string ?
// TODO:ME - Ask about logout when clicking the Cancel button because of the BEARER_TOKEN_IS_VALID_BUT_NOT_LINKED_MESSAGE error
Expand All @@ -40,15 +35,18 @@ interface FormFieldsProps {
}
*/

export const FormFields = ({ formDefaultValues }: FormFieldsProps) => {
interface FormFieldsProps {
formDefaultValues: ValidTokenNotLinkedAccountFormData
termsOfUse: TermsOfUse
}

export const FormFields = ({ formDefaultValues, termsOfUse }: FormFieldsProps) => {
const navigate = useNavigate()
const { refetchUserSession } = useSession()
const { tokenData, logOut: oidcLogout } = useContext(AuthContext)
const { t } = useTranslation('signUp')
const { t: tShared } = useTranslation('shared')

const { termsOfUse, isLoading: isLoadingTermsOfUse } = useGetTermsOfUse()

const isUsernameRequired = formDefaultValues.username === ''
const isEmailRequired = formDefaultValues.emailAddress === ''
const isFirstNameRequired = formDefaultValues.firstName === ''
Expand Down Expand Up @@ -121,10 +119,6 @@ export const FormFields = ({ formDefaultValues }: FormFieldsProps) => {

const hasAcceptedTheTermsOfUse = form.watch('termsAccepted')

if (isLoadingTermsOfUse) {
return <AppLoader />
}

return (
<div>
{/* <div className={styles['about-prefilled-fields-wrapper']}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'
import { Col, Row, Stack } from '@iqss/dataverse-design-system'

export const FormFieldsSkeleton = () => (
<SkeletonTheme>
<section data-testid="form-fields-skeleton">
<LabelAndFieldSkeleton withHelperText />
<LabelAndFieldSkeleton />
<LabelAndFieldSkeleton />
<LabelAndFieldSkeleton />
<LabelAndFieldSkeleton />
<LabelAndFieldSkeleton />
<LabelAndFieldSkeleton termsOfUse />

<Stack direction="horizontal" className="pt-3">
<Skeleton width={120} height={38} />
<Skeleton width={80} height={38} />
</Stack>
</section>
</SkeletonTheme>
)

interface LabelAndFieldSkeletonProps {
withHelperText?: boolean
termsOfUse?: boolean
}

const LabelAndFieldSkeleton = ({ withHelperText, termsOfUse }: LabelAndFieldSkeletonProps) => (
<Row style={{ marginBottom: 16 }}>
<Col md={3} className="text-end">
<Skeleton width={120} />
</Col>
<Col md={6}>
<Stack gap={2}>
{withHelperText && <Skeleton width="100%" height={26} />}
<Skeleton width="100%" height={termsOfUse ? 60 : 38} />
{termsOfUse && (
<Stack direction="horizontal" gap={2}>
<Skeleton width={16} height={16} />
<Skeleton width={300} height={16} />
</Stack>
)}
</Stack>
</Col>
</Row>
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { useContext } from 'react'
import { AuthContext } from 'react-oauth2-code-pkce'
import { DataverseInfoRepository } from '@/info/domain/repositories/DataverseInfoRepository'
import { useGetTermsOfUse } from '@/shared/hooks/useGetTermsOfUse'
import { OIDC_STANDARD_CLAIMS, type ValidTokenNotLinkedAccountFormData } from './types'
import { FormFields } from './FormFields'
import { ValidTokenNotLinkedAccountFormHelper } from './ValidTokenNotLinkedAccountFormHelper'
import { FormFields } from './FormFields'
import { FormFieldsSkeleton } from './FormFieldsSkeleton'

interface ValidTokenNotLinkedAccountFormProps {
dataverseInfoRepository: DataverseInfoRepository
}

export const ValidTokenNotLinkedAccountForm = () => {
export const ValidTokenNotLinkedAccountForm = ({
dataverseInfoRepository
}: ValidTokenNotLinkedAccountFormProps) => {
const { tokenData } = useContext(AuthContext)
const { termsOfUse, isLoading: isLoadingTermsOfUse } = useGetTermsOfUse(dataverseInfoRepository)

const defaultUserName =
ValidTokenNotLinkedAccountFormHelper.getTokenDataValue<string>(
Expand Down Expand Up @@ -45,5 +55,9 @@ export const ValidTokenNotLinkedAccountForm = () => {
termsAccepted: false
}

return <FormFields formDefaultValues={formDefaultValues} />
if (isLoadingTermsOfUse) {
return <FormFieldsSkeleton />
}

return <FormFields formDefaultValues={formDefaultValues} termsOfUse={termsOfUse} />
}
15 changes: 7 additions & 8 deletions src/shared/hooks/useGetTermsOfUse.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useEffect, useState } from 'react'
import { axiosInstance } from '@/axiosInstance'
import { DataverseInfoRepository } from '@/info/domain/repositories/DataverseInfoRepository'

interface UseGetTermsOfUseReturnType {
termsOfUse: string | null
error: string | null
isLoading: boolean
}

export const useGetTermsOfUse = (): UseGetTermsOfUseReturnType => {
export const useGetTermsOfUse = (
dataverseInfoRepository: DataverseInfoRepository
): UseGetTermsOfUseReturnType => {
const [termsOfUse, setTermsOfUse] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)
Expand All @@ -16,12 +18,9 @@ export const useGetTermsOfUse = (): UseGetTermsOfUseReturnType => {
const handleGetUseOfTerms = async () => {
setIsLoading(true)
try {
const response = await axiosInstance.get<{ data: { message: string } }>(
'/api/v1/info/apiTermsOfUse',
{ excludeToken: true }
)
const termsOfUse = await dataverseInfoRepository.getTermsOfUse()

setTermsOfUse(response.data.data.message)
setTermsOfUse(termsOfUse)
} catch (err) {
const errorMessage =
err instanceof Error && err.message
Expand All @@ -34,7 +33,7 @@ export const useGetTermsOfUse = (): UseGetTermsOfUseReturnType => {
}

void handleGetUseOfTerms()
}, [])
}, [dataverseInfoRepository])

return {
termsOfUse,
Expand Down
2 changes: 1 addition & 1 deletion src/stories/sign-up/SignUp.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { WithLayout } from '../WithLayout'
import { WithI18next } from '../WithI18next'
import { SignUp } from '@/sections/sign-up/SignUp'
import { DataverseInfoMockRepository } from '../shared-mock-repositories/info/DataverseInfoMockRepository'
import { DataverseInfoMockLoadingRepository } from '../shared-mock-repositories/info/DataverseInfoMocLoadingkRepository'
import { DataverseInfoMockLoadingRepository } from '../shared-mock-repositories/info/DataverseInfoMockLoadingkRepository'
import { WithOIDCAuthContext } from '../WithOIDCAuthContext'

// TODO:ME - After implementing register use case in js-dataverse, we should mock the register function here also.
Expand Down
62 changes: 62 additions & 0 deletions tests/component/shared/hooks/useGetTermsOfUse.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { act, renderHook } from '@testing-library/react'
import { useGetTermsOfUse } from '@/shared/hooks/useGetTermsOfUse'
import { DataverseInfoRepository } from '@/info/domain/repositories/DataverseInfoRepository'
import { TermsOfUseMother } from '@tests/component/info/models/TermsOfUseMother'

const dataverseInfoRepository: DataverseInfoRepository = {} as DataverseInfoRepository
const termsOfUseMock = TermsOfUseMother.create()

describe('useGetTermsOfUse', () => {
it('should return terms of use correctly', async () => {
dataverseInfoRepository.getTermsOfUse = cy.stub().resolves(termsOfUseMock)

const { result } = renderHook(() => useGetTermsOfUse(dataverseInfoRepository))

await act(() => {
expect(result.current.isLoading).to.deep.equal(true)
return expect(result.current.termsOfUse).to.deep.equal(null)
})

await act(() => {
expect(result.current.isLoading).to.deep.equal(false)

return expect(result.current.termsOfUse).to.deep.equal(termsOfUseMock)
})
})

describe('Error handling', () => {
it('should return correct error message when there is an error type catched', async () => {
dataverseInfoRepository.getTermsOfUse = cy.stub().rejects(new Error('Error message'))

const { result } = renderHook(() => useGetTermsOfUse(dataverseInfoRepository))

await act(() => {
expect(result.current.isLoading).to.deep.equal(true)
return expect(result.current.error).to.deep.equal(null)
})

await act(() => {
expect(result.current.isLoading).to.deep.equal(false)
return expect(result.current.error).to.deep.equal('Error message')
})
})

it('should return correct error message when there is not an error type catched', async () => {
dataverseInfoRepository.getTermsOfUse = cy.stub().rejects('Error message')

const { result } = renderHook(() => useGetTermsOfUse(dataverseInfoRepository))

await act(() => {
expect(result.current.isLoading).to.deep.equal(true)
return expect(result.current.error).to.deep.equal(null)
})

await act(() => {
expect(result.current.isLoading).to.deep.equal(false)
return expect(result.current.error).to.deep.equal(
'Something went wrong getting the use of terms. Try again later.'
)
})
})
})
})

0 comments on commit edf4429

Please sign in to comment.