Skip to content

Commit 23d6880

Browse files
committed
feat: add use case of termsofaccess, add accordion substituting tabs for mobile viewports
1 parent d46de43 commit 23d6880

File tree

13 files changed

+517
-89
lines changed

13 files changed

+517
-89
lines changed

src/dataset/domain/repositories/DatasetRepository.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dataset, DatasetLock } from '../models/Dataset'
1+
import { Dataset, DatasetLock, TermsOfAccess } from '../models/Dataset'
22
import { DatasetVersionDiff } from '../models/DatasetVersionDiff'
33
import { DatasetPaginationInfo } from '../models/DatasetPaginationInfo'
44
import { DatasetDTO } from '../useCases/DTOs/DatasetDTO'
@@ -61,4 +61,5 @@ export interface DatasetRepository {
6161
format: CitationFormat
6262
) => Promise<FormattedCitation>
6363
getTemplates: (collectionIdOrAlias: number | string) => Promise<DatasetTemplate[]>
64+
updateTermsOfAccess: (datasetId: string | number, termsOfAccess: TermsOfAccess) => Promise<void>
6465
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { TermsOfAccess } from '../models/Dataset'
2+
import { DatasetRepository } from '../repositories/DatasetRepository'
3+
4+
export function updateTermsOfAccess(
5+
datasetRepository: DatasetRepository,
6+
datasetId: string | number,
7+
termsOfAccess: TermsOfAccess
8+
): Promise<void> {
9+
return datasetRepository.updateTermsOfAccess(datasetId, termsOfAccess).catch((error: Error) => {
10+
throw new Error(error.message)
11+
})
12+
}

src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
CustomTerms,
44
Dataset,
55
DatasetLock,
6-
DatasetNonNumericVersion
6+
DatasetNonNumericVersion,
7+
TermsOfAccess
78
} from '../../domain/models/Dataset'
89
import { DatasetVersionDiff } from '../../domain/models/DatasetVersionDiff'
910
import {
@@ -365,44 +366,6 @@ export class DatasetJSDataverseRepository implements DatasetRepository {
365366
})
366367
}
367368

368-
updateLicense(
369-
datasetId: string | number,
370-
licenseUpdateRequest: DatasetLicenseUpdateRequest
371-
): Promise<void> {
372-
// TODO: This method should use updateDatasetLicense from js-dataverse when available
373-
// For now, implementing as a direct API call
374-
375-
// The license API requires numeric dataset ID, not persistentId
376-
// We need to use the dataset.id (numeric) for license updates
377-
const apiUrl = `${DatasetJSDataverseRepository.DATAVERSE_BACKEND_URL}/api/datasets/${datasetId}/license`
378-
379-
const payload: {
380-
name?: string
381-
customTerms?: CustomTerms
382-
} = {}
383-
384-
if (licenseUpdateRequest.name) {
385-
payload.name = licenseUpdateRequest.name
386-
}
387-
388-
if (licenseUpdateRequest.customTerms) {
389-
payload.customTerms = licenseUpdateRequest.customTerms
390-
}
391-
392-
return axiosInstance
393-
.put(apiUrl, payload)
394-
.then((response) => {
395-
console.log('🔧 Repository: License update successful')
396-
console.log('🔧 Response status:', response.status)
397-
console.log('🔧 Response data:', response.data)
398-
// Success - no return value needed
399-
})
400-
.catch((error: unknown) => {
401-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
402-
throw new Error(`Failed to update dataset license: ${errorMessage}`)
403-
})
404-
}
405-
406369
deaccession(
407370
datasetId: string | number,
408371
version: string,
@@ -463,4 +426,64 @@ export class DatasetJSDataverseRepository implements DatasetRepository {
463426
return undefined
464427
})
465428
}
429+
430+
updateLicense(
431+
datasetId: string | number,
432+
licenseUpdateRequest: DatasetLicenseUpdateRequest
433+
): Promise<void> {
434+
// return updateTermsOfAccess.execute(datasetId, licenseUpdateRequest)
435+
// TODO: This method should use updateDatasetLicense from js-dataverse when available
436+
// For now, implementing as a direct API call
437+
438+
// The license API requires numeric dataset ID, not persistentId
439+
// We need to use the dataset.id (numeric) for license updates
440+
const apiUrl = `${DatasetJSDataverseRepository.DATAVERSE_BACKEND_URL}/api/datasets/${datasetId}/license`
441+
442+
const payload: {
443+
name?: string
444+
customTerms?: CustomTerms
445+
} = {}
446+
447+
if (licenseUpdateRequest.name) {
448+
payload.name = licenseUpdateRequest.name
449+
}
450+
451+
if (licenseUpdateRequest.customTerms) {
452+
payload.customTerms = licenseUpdateRequest.customTerms
453+
}
454+
455+
return axiosInstance
456+
.put(apiUrl, payload)
457+
.then(() => undefined)
458+
.catch((error: unknown) => {
459+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
460+
throw new Error(`Failed to update dataset license: ${errorMessage}`)
461+
})
462+
}
463+
464+
updateTermsOfAccess(datasetId: string | number, termsOfAccess: TermsOfAccess): Promise<void> {
465+
// return updateTermsOfAccess.execute(datasetId, termsOfAccess)
466+
467+
// TODO: This method should use updateDatasetLicense from js-dataverse when available
468+
// For now, implementing as a direct API call
469+
470+
const apiUrl = `${DatasetJSDataverseRepository.DATAVERSE_BACKEND_URL}/api/datasets/${datasetId}/access`
471+
472+
// Map client model field to API expected field name
473+
const { termsOfAccessForRestrictedFiles, ...rest } = termsOfAccess
474+
const payload = {
475+
...rest,
476+
...(termsOfAccessForRestrictedFiles !== undefined
477+
? { termsOfAccess: termsOfAccessForRestrictedFiles }
478+
: {})
479+
}
480+
481+
return axiosInstance
482+
.put(apiUrl, { customTermsOfAccess: payload })
483+
.then(() => undefined)
484+
.catch((error: unknown) => {
485+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
486+
throw new Error(`Failed to update dataset terms of access: ${errorMessage}`)
487+
})
488+
}
466489
}

src/sections/edit-dataset-terms/EditDatasetTerms.tsx

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState } from 'react'
22
import { useTranslation } from 'react-i18next'
3-
import { Alert, Tabs } from '@iqss/dataverse-design-system'
3+
import { Alert, Accordion, Tabs } from '@iqss/dataverse-design-system'
44
import { EditDatasetTermsHelper, EditDatasetTermsTabKey } from './EditDatasetTermsHelper'
55
import { useLoading } from '../../shared/contexts/loading/LoadingContext'
66
import { DatasetTermsTab } from './dataset-terms-tab/DatasetTermsTab'
@@ -14,6 +14,7 @@ import { NotFoundPage } from '../not-found-page/NotFoundPage'
1414
import { AppLoader } from '../shared/layout/app-loader/AppLoader'
1515
import { DatasetLicense } from '@/dataset/domain/models/Dataset'
1616
import styles from './EditDatasetTerms.module.scss'
17+
import { useMediaQuery } from '@/shared/hooks/useMediaQuery'
1718

1819
const tabsKeys = EditDatasetTermsHelper.EDIT_DATASET_TERMS_TABS_KEYS
1920

@@ -44,6 +45,7 @@ export const EditDatasetTerms = ({
4445
const [activeKey, setActiveKey] = useState<string>(defaultActiveTabKey)
4546
const { dataset, isLoading } = useDataset()
4647
const { setIsLoading } = useLoading()
48+
const isMobile = useMediaQuery('(max-width: 576px)')
4749

4850
useEffect(() => {
4951
setIsLoading(isLoading)
@@ -76,38 +78,80 @@ export const EditDatasetTerms = ({
7678
{t('editTerms.infoAlert.text')}
7779
</Alert>
7880

79-
<Tabs activeKey={activeKey} onSelect={updateTabOnSelect}>
80-
<Tabs.Tab eventKey={tabsKeys.datasetTerms} title={t('editTerms.tabs.datasetTerms')}>
81-
<div className={styles['tab-container']}>
82-
<DatasetTermsTab
83-
licenseRepository={licenseRepository}
84-
datasetRepository={datasetRepository}
85-
initialLicense={
86-
(dataset.license as DatasetLicense) ||
87-
(dataset.termsOfUse.customTerms as CustomTermsData)
88-
}
89-
isInitialCustomTerms={dataset.termsOfUse.customTerms !== undefined}
90-
/>
91-
</div>
92-
</Tabs.Tab>
81+
{isMobile ? (
82+
<Accordion defaultActiveKey={[tabsKeys.datasetTerms]} alwaysOpen={true}>
83+
<Accordion.Item eventKey={tabsKeys.datasetTerms}>
84+
<Accordion.Header>{t('editTerms.tabs.datasetTerms')}</Accordion.Header>
85+
<Accordion.Body>
86+
<div className={styles['tab-container']}>
87+
<DatasetTermsTab
88+
licenseRepository={licenseRepository}
89+
datasetRepository={datasetRepository}
90+
initialLicense={
91+
(dataset.license as DatasetLicense) ||
92+
(dataset.termsOfUse.customTerms as CustomTermsData)
93+
}
94+
isInitialCustomTerms={dataset.termsOfUse.customTerms !== undefined}
95+
/>
96+
</div>
97+
</Accordion.Body>
98+
</Accordion.Item>
9399

94-
<Tabs.Tab
95-
eventKey={tabsKeys.restrictedFilesTerms}
96-
title={t('editTerms.tabs.restrictedFilesTerms')}>
97-
<div className={styles['tab-container']}>
98-
<RestrictedFilesTab
99-
datasetRepository={datasetRepository}
100-
initialTermsOfAccess={dataset.termsOfUse.termsOfAccess}
101-
/>
102-
</div>
103-
</Tabs.Tab>
100+
<Accordion.Item eventKey={tabsKeys.restrictedFilesTerms}>
101+
<Accordion.Header>{t('editTerms.tabs.restrictedFilesTerms')}</Accordion.Header>
102+
<Accordion.Body>
103+
<div className={styles['tab-container']}>
104+
<RestrictedFilesTab
105+
datasetRepository={datasetRepository}
106+
initialTermsOfAccess={dataset.termsOfUse.termsOfAccess}
107+
/>
108+
</div>
109+
</Accordion.Body>
110+
</Accordion.Item>
104111

105-
<Tabs.Tab eventKey={tabsKeys.guestBook} title={t('editTerms.tabs.guestBook')}>
106-
<div className={styles['tab-container']}>
107-
<GuestBookTab />
108-
</div>
109-
</Tabs.Tab>
110-
</Tabs>
112+
<Accordion.Item eventKey={tabsKeys.guestBook}>
113+
<Accordion.Header>{t('editTerms.tabs.guestBook')}</Accordion.Header>
114+
<Accordion.Body>
115+
<div className={styles['tab-container']}>
116+
<GuestBookTab />
117+
</div>
118+
</Accordion.Body>
119+
</Accordion.Item>
120+
</Accordion>
121+
) : (
122+
<Tabs activeKey={activeKey} onSelect={updateTabOnSelect}>
123+
<Tabs.Tab eventKey={tabsKeys.datasetTerms} title={t('editTerms.tabs.datasetTerms')}>
124+
<div className={styles['tab-container']}>
125+
<DatasetTermsTab
126+
licenseRepository={licenseRepository}
127+
datasetRepository={datasetRepository}
128+
initialLicense={
129+
(dataset.license as DatasetLicense) ||
130+
(dataset.termsOfUse.customTerms as CustomTermsData)
131+
}
132+
isInitialCustomTerms={dataset.termsOfUse.customTerms !== undefined}
133+
/>
134+
</div>
135+
</Tabs.Tab>
136+
137+
<Tabs.Tab
138+
eventKey={tabsKeys.restrictedFilesTerms}
139+
title={t('editTerms.tabs.restrictedFilesTerms')}>
140+
<div className={styles['tab-container']}>
141+
<RestrictedFilesTab
142+
datasetRepository={datasetRepository}
143+
initialTermsOfAccess={dataset.termsOfUse.termsOfAccess}
144+
/>
145+
</div>
146+
</Tabs.Tab>
147+
148+
<Tabs.Tab eventKey={tabsKeys.guestBook} title={t('editTerms.tabs.guestBook')}>
149+
<div className={styles['tab-container']}>
150+
<GuestBookTab />
151+
</div>
152+
</Tabs.Tab>
153+
</Tabs>
154+
)}
111155
</section>
112156
)
113157
}

src/sections/edit-dataset-terms/EditDatasetTermsFactory.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { EditDatasetTermsHelper } from './EditDatasetTermsHelper'
44
import { LicenseJSDataverseRepository } from '../../licenses/infrastructure/repositories/LicenseJSDataverseRepository'
55
import { DatasetJSDataverseRepository } from '@/dataset/infrastructure/repositories/DatasetJSDataverseRepository'
66
import { DatasetProvider } from '../dataset/DatasetProvider'
7-
import { searchParamVersionToDomainVersion } from '../../router'
87
import { ReactElement } from 'react'
8+
import { DatasetNonNumericVersion } from '@/dataset/domain/models/Dataset'
99

1010
const licenseRepository = new LicenseJSDataverseRepository()
1111
const datasetRepository = new DatasetJSDataverseRepository()
@@ -20,8 +20,8 @@ function EditDatasetTermsWithSearchParams() {
2020
const [searchParams] = useSearchParams()
2121
const defaultActiveTabKey = EditDatasetTermsHelper.defineSelectedTabKey(searchParams)
2222
const persistentId = searchParams.get('persistentId') ?? undefined
23-
const searchParamVersion = searchParams.get('version') ?? undefined
24-
const version = searchParamVersionToDomainVersion(searchParamVersion)
23+
// Always load the latest version (draft if exists, otherwise latest published)
24+
const version = DatasetNonNumericVersion.LATEST
2525

2626
return (
2727
<DatasetProvider

src/sections/edit-dataset-terms/dataset-terms-tab/useUpdateDatasetLicense.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { useState } from 'react'
2+
import { useTranslation } from 'react-i18next'
13
import { DatasetRepository } from '../../../dataset/domain/repositories/DatasetRepository'
24
import { DatasetLicenseUpdateRequest } from '../../../dataset/domain/models/DatasetLicenseUpdateRequest'
35
import { JSDataverseWriteErrorHandler } from '../../../shared/helpers/JSDataverseWriteErrorHandler'
46
import { WriteError } from '@iqss/dataverse-client-javascript'
5-
import { useState } from 'react'
6-
import { useTranslation } from 'react-i18next'
77

88
export interface UseUpdateDatasetLicense {
99
datasetRepository: DatasetRepository

src/sections/edit-dataset-terms/restricted-files-tab/RestrictedFilesTab.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { useMemo } from 'react'
1+
import { useEffect, useMemo } from 'react'
22
import { useTranslation } from 'react-i18next'
33
import { useForm, Controller } from 'react-hook-form'
44
import { Form, Row, Col, Button, Alert } from '@iqss/dataverse-design-system'
55
import styles from '../dataset-terms-tab/DatasetTermsTab.module.scss'
66
import { TermsOfAccess } from '@/dataset/domain/models/Dataset'
77
import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository'
8+
import { useDataset } from '../../dataset/DatasetContext'
9+
import { useUpdateTermsOfAccess } from './useUpdateTermsOfAccess'
810

911
// Use TermsOfAccess directly from model
1012
type RestrictedFilesFormData = TermsOfAccess
@@ -32,6 +34,12 @@ export function RestrictedFilesTab({
3234
}: RestrictedFilesTabProps) {
3335
const { t } = useTranslation('dataset')
3436
const { t: tShared } = useTranslation('shared')
37+
const { dataset, refreshDataset } = useDataset()
38+
39+
const { handleUpdateTermsOfAccess, isLoading, error } = useUpdateTermsOfAccess({
40+
datasetRepository: _datasetRepository,
41+
onSuccessfulUpdateTermsOfAccess: () => refreshDataset()
42+
})
3543

3644
const {
3745
control,
@@ -40,16 +48,25 @@ export function RestrictedFilesTab({
4048
watch,
4149
formState: { isValid }
4250
} = useForm<RestrictedFilesFormData>({
43-
defaultValues: initialTermsOfAccess,
51+
defaultValues:
52+
(dataset?.termsOfUse.termsOfAccess as RestrictedFilesFormData) ?? initialTermsOfAccess,
4453
mode: 'onChange'
4554
})
4655

56+
// When the dataset loads/changes, reset the form to the original terms from dataset first
57+
useEffect(() => {
58+
const original = (dataset?.termsOfUse.termsOfAccess as RestrictedFilesFormData) ?? null
59+
if (original) {
60+
reset(original)
61+
}
62+
}, [dataset, reset])
63+
4764
// Watch the fileAccessRequest field to show/hide info alert
4865
const watchedFileAccessRequest = watch('fileAccessRequest')
4966

50-
// TODO: Implement actual save logic using datasetRepository
51-
const onSubmit = (data: RestrictedFilesFormData) => {
52-
console.log('TODO: Implement actual save logic using datasetRepository', data)
67+
const onSubmit = async (data: RestrictedFilesFormData) => {
68+
if (!dataset) return
69+
await handleUpdateTermsOfAccess(dataset.id, data)
5370
}
5471

5572
// Generate terms of access fields dynamically from DEFAULT_TERMS_OF_ACCESS (excluding fileAccessRequest)
@@ -148,11 +165,26 @@ export function RestrictedFilesTab({
148165
</Form.Group>
149166
))}
150167

168+
{error && (
169+
<Alert variant="danger" dismissible>
170+
{error}
171+
</Alert>
172+
)}
173+
151174
<div className={styles['form-actions']}>
152-
<Button type="submit" disabled={!isValid}>
153-
{tShared('saveChanges')}
175+
<Button type="submit" disabled={!isValid || isLoading}>
176+
{isLoading ? tShared('saving') : tShared('saveChanges')}
154177
</Button>
155-
<Button variant="secondary" type="button" onClick={() => reset(initialTermsOfAccess)}>
178+
<Button
179+
variant="secondary"
180+
type="button"
181+
disabled={isLoading}
182+
onClick={() =>
183+
reset(
184+
(dataset?.termsOfUse.termsOfAccess as RestrictedFilesFormData) ??
185+
initialTermsOfAccess
186+
)
187+
}>
156188
{tShared('cancel')}
157189
</Button>
158190
</div>

0 commit comments

Comments
 (0)