diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f90edbeb..24677b8e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,11 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Share button in File Page. - Link Collection and Link Dataset features. - Edit Terms Integration. +- File upload now shows the remaining storage quota when upload limits are available. - With the addition of the new runtime configuration approach, we now support dynamic configuration for languages. If more than one language is configured, the Language Switcher will be shown in the header to allow users to change the language. - Added Notifications tab in Account Page +- Added runtime configuration options for homepage branding and support link. +- Added an environment variable to docker-compose-dev.yml to hide the OIDC client used in the SPA from the JSF frontend: DATAVERSE_AUTH_OIDC_HIDDEN_JSF: 1 ### Changed @@ -24,6 +27,9 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Changed the way we were handling DATE type metadata field validation to better match the backend validation and give users better error messages. For example, for an input like “foo AD”, we now show “Production Date is not a valid date. The AD year must be numeric.“. For an input like “99999 AD”, we now show “Production Date is not a valid date. The AD year cant be higher than 9999.“. For an input like “[-9999?], we now show “Production Date is not a valid date. The year in brackets cannot be negative.“, etc. - The SPA now fetches the runtime configuration from `config.js` on each load, allowing configurations without rebuilding the app. We don't use `.env` variables at build time anymore. - Renamed dataset template fetch/create use cases and DTOs to `getTemplatesByCollectionId` and `CreateTemplateDTO` for API alignment, and added new `getTemplate` and `deleteTemplate` use cases for retrieving a single template by ID and deleting templates. +- Added disclaimer text and custom popup text to Publish Dataset modal for better communication of the implications of publishing a dataset. The text can be configured through the runtime configuration. +- Dataset page Terms tab title now depends on permissions: users with dataset update permission see `Terms and Guestbook`, and read-only users see `Terms`. +- Avoided prop-drilling for collection repository, so used context to share epository instances. ### Fixed @@ -31,6 +37,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Show toast notification when API token is copied to clipboard. - Dataset versions: (1) file changes should be `Access: Restricted` instead of `isResticted: true/false`; (2) logic of View Detail button. (#879) - File versions: (1) logic of linking to a file version; (2)If file not included, show text information "File not included in this version.". (#879) +- Dataset page publish flow now avoids rendering duplicate tab sets by making tabs skeleton and tabs content mutually exclusive. ### Removed diff --git a/cypress.config.ts b/cypress.config.ts index f5efec081..b9f6c55e6 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -46,6 +46,16 @@ export default defineConfig({ { code: 'es', name: 'Español' } ], defaultLanguage: 'en', + branding: { + dataverseName: 'Dataverse' + }, + homepage: { + supportUrl: 'https://support.dataverse.harvard.edu/' + }, + footer: { + copyrightHolder: 'The President & Fellows of Harvard College', + privacyPolicyUrl: 'https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy' + }, codeCoverage: { exclude: ['tests/**/*.*', '**/ErrorPage.tsx'] } diff --git a/dev-env/docker-compose-dev.yml b/dev-env/docker-compose-dev.yml index 2969b9bc0..7db94a04c 100644 --- a/dev-env/docker-compose-dev.yml +++ b/dev-env/docker-compose-dev.yml @@ -49,6 +49,7 @@ services: DATAVERSE_FEATURE_API_BEARER_AUTH_PROVIDE_MISSING_CLAIMS: 1 DATAVERSE_FEATURE_API_BEARER_AUTH_USE_BUILTIN_USER_ON_ID_MATCH: 1 DATAVERSE_AUTH_OIDC_ENABLED: 1 + DATAVERSE_AUTH_OIDC_HIDDEN_JSF: 1 DATAVERSE_AUTH_OIDC_CLIENT_ID: test DATAVERSE_AUTH_OIDC_CLIENT_SECRET: 94XHrfNRwXsjqTqApRrwWmhDLDHpIYV8 DATAVERSE_AUTH_OIDC_AUTH_SERVER_URL: http://keycloak.mydomain.com:9080/realms/test diff --git a/package-lock.json b/package-lock.json index cf249ad1d..fc56c996d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.85", + "@iqss/dataverse-client-javascript": "2.1.0-alpha.1", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -1953,10 +1953,9 @@ } }, "node_modules/@iqss/dataverse-client-javascript": { - "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-alpha.85", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-alpha.85/9f7d8be5c1fbe022c11fcc6d956bbf1cc4821776", - "integrity": "sha512-2aEA0MdkhFjugxImJ3a334jlVbwmNMDVsFXnwPutbbkqn89UMXClMxADkqlmLQ3/4dOMtOrdVDitxYsYmYZ6RQ==", + "version": "2.1.0-alpha.1", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.1.0-alpha.1/01f62a538cb340fa4b2f32da9b61c7d271b642f5", + "integrity": "sha512-UarvBKchCVOAj2CcsdQi8/2g78LoTFDtSBHB4c8MmOjWlerZnO0I+khDE1cW2MbQ3RSiHCeTtBVuvDhnf1dEhw==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", @@ -1967,9 +1966,9 @@ } }, "node_modules/@iqss/dataverse-client-javascript/node_modules/@types/node": { - "version": "18.19.127", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz", - "integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -8570,9 +8569,9 @@ "optional": true }, "node_modules/@types/turndown": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", - "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.6.tgz", + "integrity": "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==", "license": "MIT" }, "node_modules/@types/unist": { diff --git a/package.json b/package.json index 3b8b27edf..ba3c24bdf 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.85", + "@iqss/dataverse-client-javascript": "2.1.0-alpha.1", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/packages/design-system/src/lib/components/rich-text-editor/EditorActions.tsx b/packages/design-system/src/lib/components/rich-text-editor/EditorActions.tsx index e3dd9c4a8..b97b46fdd 100644 --- a/packages/design-system/src/lib/components/rich-text-editor/EditorActions.tsx +++ b/packages/design-system/src/lib/components/rich-text-editor/EditorActions.tsx @@ -148,6 +148,10 @@ export const EditorActions = ({ editor, disabled, locales }: EditorActionsProps) const handleUndo = () => editor?.chain().focus().undo().run() const handleRedo = () => editor?.chain().focus().redo().run() + const linkDialogTitle = + locales?.linkDialog?.title ?? richTextEditorDefaultLocales.linkDialog?.title + const imageDialogTitle = + locales?.imageDialog?.title ?? richTextEditorDefaultLocales.imageDialog?.title return ( <> @@ -378,11 +382,13 @@ export const EditorActions = ({ editor, disabled, locales }: EditorActionsProps) {/* Dialog for pasting a url to the link */} - + - - {locales?.linkDialog?.title ?? richTextEditorDefaultLocales.linkDialog?.title} - + {linkDialogTitle} @@ -416,11 +422,13 @@ export const EditorActions = ({ editor, disabled, locales }: EditorActionsProps) {/* Dialog for adding the url and alt text of the image */} - + - - {locales?.imageDialog?.title ?? richTextEditorDefaultLocales.imageDialog?.title} - + {imageDialogTitle} diff --git a/packages/design-system/src/lib/stories/modal/Modal.stories.tsx b/packages/design-system/src/lib/stories/modal/Modal.stories.tsx index 815037cf9..43d09c17a 100644 --- a/packages/design-system/src/lib/stories/modal/Modal.stories.tsx +++ b/packages/design-system/src/lib/stories/modal/Modal.stories.tsx @@ -29,6 +29,7 @@ const meta: Meta = { function DefaultExample(size?: 'sm' | 'lg' | 'xl') { const [show, setShow] = useState(false) + const modalTitle = 'Modal heading' const handleClose = () => setShow(false) const handleShow = () => setShow(true) @@ -37,9 +38,9 @@ function DefaultExample(size?: 'sm' | 'lg' | 'xl') { <> - + - Modal heading + {modalTitle} You are reading this text in a modal! diff --git a/packages/design-system/tests/component/modal/Modal.spec.tsx b/packages/design-system/tests/component/modal/Modal.spec.tsx index 6fbc11617..510ce4f02 100644 --- a/packages/design-system/tests/component/modal/Modal.spec.tsx +++ b/packages/design-system/tests/component/modal/Modal.spec.tsx @@ -3,10 +3,11 @@ import { Modal } from '../../../src/lib/components/modal/Modal' describe('Modal', () => { it('renders the modal with header, body and footer', () => { const onHide = cy.stub() + const modalTitle = 'Modal Title' cy.mount( - + - Modal Title + {modalTitle} Modal Body @@ -25,10 +26,11 @@ describe('Modal', () => { it('calls onHide when the close button is clicked', () => { const onHide = cy.stub().as('onHide') + const modalTitle = 'Modal Title' cy.mount( - + - Modal Title + {modalTitle} Modal Body @@ -40,10 +42,11 @@ describe('Modal', () => { it('renders the modal with a custom size', () => { const onHide = cy.stub() + const modalTitle = 'Modal Title' cy.mount( - + - Modal Title + {modalTitle} Modal Body diff --git a/packages/design-system/tsconfig.json b/packages/design-system/tsconfig.json index 757f3a260..002865b24 100644 --- a/packages/design-system/tsconfig.json +++ b/packages/design-system/tsconfig.json @@ -11,7 +11,7 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/public/config.js b/public/config.js index fc815f483..bd63f3f93 100644 --- a/public/config.js +++ b/public/config.js @@ -20,5 +20,17 @@ window.__APP_CONFIG__ = { { code: 'es', name: 'Español' } ], // Default language code from the list above - defaultLanguage: 'en' + defaultLanguage: 'en', + // Optional branding values for homepage/footer text + branding: { + // Used in homepage strings such as "{{dataverseName}} is a repository..." + dataverseName: 'Harvard Dataverse' + }, + homepage: { + supportUrl: 'https://support.dataverse.harvard.edu/' + }, + footer: { + copyrightHolder: 'The President & Fellows of Harvard College', + privacyPolicyUrl: 'https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy' + } } diff --git a/public/locales/en/collection.json b/public/locales/en/collection.json index c6f1a3a88..6690be4a1 100644 --- a/public/locales/en/collection.json +++ b/public/locales/en/collection.json @@ -52,6 +52,12 @@ "editCollection": { "edit": "Edit", "generalInfo": "General Information", + "themeAndWidgets": "Theme + Widgets", + "permissions": "Permissions", + "groups": "Groups", + "datasetTemplates": "Dataset Templates", + "datasetGuestbooks": "Dataset Guestbooks", + "featuredDataverses": "Featured Dataverses", "deleteCollection": "Delete Collection" }, "featuredItems": { diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index 47c91d018..f744f2bfc 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -2,6 +2,7 @@ "filesTabTitle": "Files", "metadataTabTitle": "Metadata", "termsTabTitle": "Terms and Guestbook", + "termsTabTitleReadOnly": "Terms", "anonymizedFieldValue": "withheld", "customTerms": { "title": "Custom Dataset Terms", @@ -69,7 +70,8 @@ }, "deleteDatasetModal": { "title": "Delete Dataset", - "message": "Are you sure you want to delete this dataset? This action cannot be undone.", + "messageDraft": "Are you sure you want to delete this draft version? Files will be reverted to the most recently published version. You cannot undelete this draft.", + "messageDataset": "Are you sure you want to delete this dataset? You cannot undelete this dataset.", "defaultDatasetDeleteError": "An error occurred while deleting the dataset." } }, @@ -142,7 +144,8 @@ "heading": "Unpublished Dataset Private URL", "alertText": "This unpublished dataset is being privately shared." }, - "datasetDeletedSuccess": "The dataset has been deleted." + "datasetDeletedSuccessDraft": "The dataset draft has been deleted.", + "datasetDeletedSuccessDataset": "The dataset has been deleted." }, "termsTab": { "editTermsButton": "Edit Terms and Guestbook", diff --git a/public/locales/en/footer.json b/public/locales/en/footer.json index 4532c74f3..cdf13602d 100644 --- a/public/locales/en/footer.json +++ b/public/locales/en/footer.json @@ -1,5 +1,5 @@ { - "copyright": "Copyright © {{year}}, The President & Fellows of Harvard College | ", + "copyright": "Copyright © {{year}}, {{copyrightHolder}}", "privacyPolicy": "Privacy Policy", "poweredBy": "Powered by" } diff --git a/public/locales/en/homepage.json b/public/locales/en/homepage.json index fe846e929..78f74ab25 100644 --- a/public/locales/en/homepage.json +++ b/public/locales/en/homepage.json @@ -20,7 +20,7 @@ "usage": { "datasets": { "title": "Deposit and share your data. Get academic credit.", - "content": "Harvard Dataverse is a repository for research data. Deposit data and code here.", + "content": "{{dataverseName}} is a repository for research data. Deposit data and code here.", "text_button": "Add a dataset" }, "collections": { @@ -29,7 +29,7 @@ "text_button": "Add a collection" }, "general": { - "title": "Publishing your data is easy on Harvard Dataverse!", + "title": "Publishing your data is easy on {{dataverseName}}!", "content": "Learn about getting started creating your own dataverse repository here.", "text_button": "Getting started" } diff --git a/public/locales/en/shared.json b/public/locales/en/shared.json index d4dbc4e26..e00b67b0b 100644 --- a/public/locales/en/shared.json +++ b/public/locales/en/shared.json @@ -220,6 +220,9 @@ "selectFileMultiple": "Select files to add", "dragDropSingle": "Drag and drop file here.", "dragDropMultiple": "Drag and drop files and/or directories here.", + "uploadWidgetHelp": "Select files or drag and drop into the upload widget. Maximum of {{maxFilesPerUpload}} files per upload.", + "uploadWidgetMaxFilesHelp": "Maximum of {{maxFilesAvailableToUpload}} files available to upload.", + "uploadWidgetStorageQuotaHelp": "Storage quota: {{storageQuotaRemaining}} remaining.", "cancelUpload": "Cancel upload", "uploadFailed": "There was an error uploading this file.", "loadingConfiguration": "Loading configuration", diff --git a/public/locales/es/collection.json b/public/locales/es/collection.json index a7b718fa1..adaa711ba 100644 --- a/public/locales/es/collection.json +++ b/public/locales/es/collection.json @@ -52,6 +52,12 @@ "editCollection": { "edit": "Editar", "generalInfo": "Información general", + "themeAndWidgets": "Tema + Widgets", + "permissions": "Permisos", + "groups": "Grupos", + "datasetTemplates": "Plantillas de dataset", + "datasetGuestbooks": "Libros de visitas de dataset", + "featuredDataverses": "Dataverses destacados", "deleteCollection": "Eliminar colección" }, "featuredItems": { diff --git a/public/locales/es/dataset.json b/public/locales/es/dataset.json index 30ad2dc4e..b1567e78a 100644 --- a/public/locales/es/dataset.json +++ b/public/locales/es/dataset.json @@ -1,7 +1,8 @@ { "filesTabTitle": "Ficheros", "metadataTabTitle": "Metadatos", - "termsTabTitle": "Términos", + "termsTabTitle": "Términos y libros de visitas", + "termsTabTitleReadOnly": "Términos", "anonymizedFieldValue": "retirado", "customTerms": { "title": "Términos personalizados del dataset", @@ -68,7 +69,8 @@ }, "deleteDatasetModal": { "title": "Eliminar dataset", - "message": "¿Seguro que quieres eliminar este dataset? Esta acción no se puede deshacer.", + "messageDraft": "¿Seguro que quieres eliminar esta versión borrador? No podrás recuperar este borrador.", + "messageDataset": "¿Seguro que quieres eliminar este dataset? Esta acción no se puede deshacer.", "defaultDatasetDeleteError": "Ocurrió un error al eliminar el dataset." } }, @@ -137,7 +139,8 @@ "heading": "URL privada de dataset no publicado", "alertText": "Este dataset no publicado se está compartiendo de forma privada." }, - "datasetDeletedSuccess": "El dataset ha sido eliminado." + "datasetDeletedSuccessDraft": "El borrador del dataset ha sido eliminado.", + "datasetDeletedSuccessDataset": "El dataset ha sido eliminado." }, "termsTab": { "editTermsButton": "Editar requisitos de términos", diff --git a/public/locales/es/footer.json b/public/locales/es/footer.json index 062908bb9..ac3cb7e89 100644 --- a/public/locales/es/footer.json +++ b/public/locales/es/footer.json @@ -1,5 +1,5 @@ { - "copyright": "Copyright © {{year}}, El Presidente y Miembros de la Universidad de Harvard | ", + "copyright": "Copyright © {{year}}, {{copyrightHolder}}", "privacyPolicy": "Política de privacidad", "poweredBy": "Desarrollado por" } diff --git a/public/locales/es/homepage.json b/public/locales/es/homepage.json index 3a4e28f96..97dd4f80f 100644 --- a/public/locales/es/homepage.json +++ b/public/locales/es/homepage.json @@ -20,7 +20,7 @@ "usage": { "datasets": { "title": "Deposita y comparte tus datos. Obtén crédito académico.", - "content": "Harvard Dataverse es un repositorio para datos de investigación. Deposita aquí tus datos y código.", + "content": "{{dataverseName}} es un repositorio para datos de investigación. Deposita aquí tus datos y código.", "text_button": "Agregar un dataset" }, "collections": { @@ -29,7 +29,7 @@ "text_button": "Agregar una colección" }, "general": { - "title": "¡Publicar tus datos es fácil en Harvard Dataverse!", + "title": "¡Publicar tus datos es fácil en {{dataverseName}}!", "content": "Aprende cómo comenzar a crear tu propio repositorio Dataverse aquí.", "text_button": "Comenzar" } diff --git a/public/locales/es/shared.json b/public/locales/es/shared.json index a3010a550..d22e10dcd 100644 --- a/public/locales/es/shared.json +++ b/public/locales/es/shared.json @@ -220,6 +220,9 @@ "selectFileMultiple": "Seleccionar ficheros para agregar", "dragDropSingle": "Arrastra y suelta el fichero aquí.", "dragDropMultiple": "Arrastra y suelta ficheros y/o directorios aquí.", + "uploadWidgetHelp": "Selecciona ficheros o arrástralos y suéltalos en el widget de carga. Máximo de {{maxFilesPerUpload}} ficheros por carga.", + "uploadWidgetMaxFilesHelp": "Máximo de {{maxFilesAvailableToUpload}} ficheros disponibles para cargar.", + "uploadWidgetStorageQuotaHelp": "Cuota de almacenamiento: {{storageQuotaRemaining}} restantes.", "cancelUpload": "Cancelar carga", "uploadFailed": "Ocurrió un error al cargar este fichero.", "loadingConfiguration": "Cargando configuración", diff --git a/src/alert/domain/models/Alert.ts b/src/alert/domain/models/Alert.ts index 9597123e2..e87a41c9f 100644 --- a/src/alert/domain/models/Alert.ts +++ b/src/alert/domain/models/Alert.ts @@ -1,4 +1,7 @@ -import { AlertVariant } from '@iqss/dataverse-design-system/dist/components/alert/AlertVariant' +import { ComponentProps } from 'react' +import { Alert as DesignSystemAlert } from '@iqss/dataverse-design-system' + +type AlertVariant = ComponentProps['variant'] export enum AlertMessageKey { DRAFT_VERSION = 'draftVersion', diff --git a/src/config.ts b/src/config.ts index a6989ab2c..2f6d423f2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -23,7 +23,23 @@ const AppConfigSchema = z.object({ code: z.string(), name: z.string() }) - ) + ), + branding: z + .object({ + dataverseName: z.string().optional() + }) + .optional(), + homepage: z + .object({ + supportUrl: z.url().optional() + }) + .optional(), + footer: z + .object({ + copyrightHolder: z.string().optional(), + privacyPolicyUrl: z.url().optional() + }) + .optional() }) export type AppConfig = z.infer diff --git a/src/dataset/domain/models/DatasetUploadLimits.ts b/src/dataset/domain/models/DatasetUploadLimits.ts new file mode 100644 index 000000000..6dbf22205 --- /dev/null +++ b/src/dataset/domain/models/DatasetUploadLimits.ts @@ -0,0 +1,4 @@ +export interface DatasetUploadLimits { + numberOfFilesRemaining?: number + storageQuotaRemaining?: number +} diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index 4a3e332dd..1bd783499 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -11,6 +11,7 @@ import { FormattedCitation, CitationFormat } from '../models/DatasetCitation' import { DatasetLicenseUpdateRequest } from '../models/DatasetLicenseUpdateRequest' import { CollectionSummary } from '@/collection/domain/models/CollectionSummary' import { DatasetVersionPaginationInfo } from '../models/DatasetVersionPaginationInfo' +import { DatasetUploadLimits } from '../models/DatasetUploadLimits' export interface DatasetRepository { getByPersistentId: ( @@ -68,4 +69,5 @@ export interface DatasetRepository { link(datasetId: string | number, collectionIdOrAlias: string | number): Promise unlink(datasetId: string | number, collectionIdOrAlias: string | number): Promise getDatasetLinkedCollections: (datasetId: string | number) => Promise + getDatasetUploadLimits: (datasetId: string | number) => Promise } diff --git a/src/dataset/domain/useCases/getDatasetUploadLimits.ts b/src/dataset/domain/useCases/getDatasetUploadLimits.ts new file mode 100644 index 000000000..fb9a5f63f --- /dev/null +++ b/src/dataset/domain/useCases/getDatasetUploadLimits.ts @@ -0,0 +1,9 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { DatasetUploadLimits } from '../models/DatasetUploadLimits' + +export async function getDatasetUploadLimits( + datasetId: string | number, + datasetRepository: DatasetRepository +): Promise { + return datasetRepository.getDatasetUploadLimits(datasetId) +} diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 7dd2f3354..a26660169 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -43,7 +43,8 @@ import { unlinkDataset, getDatasetLinkedCollections, updateTermsOfAccess, - updateDatasetLicense + updateDatasetLicense, + getDatasetUploadLimits } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' import { DatasetPaginationInfo } from '../../domain/models/DatasetPaginationInfo' @@ -62,6 +63,7 @@ import { requireAppConfig } from '../../../config' import { AxiosResponse } from 'axios' import { JSDataverseReadErrorHandler } from '@/shared/helpers/JSDataverseReadErrorHandler' import { CollectionSummary } from '@/collection/domain/models/CollectionSummary' +import { DatasetUploadLimits } from '@/dataset/domain/models/DatasetUploadLimits' const includeDeaccessioned = true @@ -468,4 +470,8 @@ export class DatasetJSDataverseRepository implements DatasetRepository { updateTermsOfAccess(datasetId: string | number, termsOfAccess: TermsOfAccess): Promise { return updateTermsOfAccess.execute(datasetId, termsOfAccess) } + + getDatasetUploadLimits(datasetId: string | number): Promise { + return getDatasetUploadLimits.execute(datasetId) + } } diff --git a/src/info/domain/repositories/DataverseInfoRepository.ts b/src/info/domain/repositories/DataverseInfoRepository.ts index aa19be558..874fa6498 100644 --- a/src/info/domain/repositories/DataverseInfoRepository.ts +++ b/src/info/domain/repositories/DataverseInfoRepository.ts @@ -12,4 +12,6 @@ export interface DataverseInfoRepository { getHasPublicStore: () => Promise> getExternalStatusesAllowed: () => Promise> getAvailableDatasetMetadataExportFormats: () => Promise + getDatasetPublishPopupCustomText: () => Promise> + getPublishDatasetDisclaimerText: () => Promise> } diff --git a/src/info/domain/useCases/getDatasetPublishPopupCustomText.ts b/src/info/domain/useCases/getDatasetPublishPopupCustomText.ts new file mode 100644 index 000000000..5c20966c8 --- /dev/null +++ b/src/info/domain/useCases/getDatasetPublishPopupCustomText.ts @@ -0,0 +1,8 @@ +import { Setting } from '@/settings/domain/models/Setting' +import { DataverseInfoRepository } from '../repositories/DataverseInfoRepository' + +export function getDatasetPublishPopupCustomText( + dataverseInfoRepository: DataverseInfoRepository +): Promise> { + return dataverseInfoRepository.getDatasetPublishPopupCustomText() +} diff --git a/src/info/domain/useCases/getPublishDatasetDisclaimerText.ts b/src/info/domain/useCases/getPublishDatasetDisclaimerText.ts new file mode 100644 index 000000000..fa2064b44 --- /dev/null +++ b/src/info/domain/useCases/getPublishDatasetDisclaimerText.ts @@ -0,0 +1,8 @@ +import { Setting } from '@/settings/domain/models/Setting' +import { DataverseInfoRepository } from '../repositories/DataverseInfoRepository' + +export function getPublishDatasetDisclaimerText( + dataverseInfoRepository: DataverseInfoRepository +): Promise> { + return dataverseInfoRepository.getPublishDatasetDisclaimerText() +} diff --git a/src/info/infrastructure/repositories/DataverseInfoJSDataverseRepository.ts b/src/info/infrastructure/repositories/DataverseInfoJSDataverseRepository.ts index 54544f84f..d1d3c6580 100644 --- a/src/info/infrastructure/repositories/DataverseInfoJSDataverseRepository.ts +++ b/src/info/infrastructure/repositories/DataverseInfoJSDataverseRepository.ts @@ -4,6 +4,8 @@ import { getMaxEmbargoDurationInMonths, getZipDownloadLimit, getAvailableDatasetMetadataExportFormats, + getPublishDatasetDisclaimerText, + getDatasetPublishPopupCustomText, ReadError } from '@iqss/dataverse-client-javascript' import { DataverseInfoRepository } from '@/info/domain/repositories/DataverseInfoRepository' @@ -102,4 +104,37 @@ export class DataverseInfoJSDataverseRepository implements DataverseInfoReposito getAvailableDatasetMetadataExportFormats(): Promise { return getAvailableDatasetMetadataExportFormats.execute() } + + getPublishDatasetDisclaimerText(): Promise> { + return getPublishDatasetDisclaimerText + .execute() + .then((text) => { + return { + name: SettingName.PUBLISH_DATASET_DISCLAIMER_TEXT, + value: text + } + }) + .catch(() => { + // In case of error, we default to an empty string which indicates no disclaimer text. + return { + name: SettingName.PUBLISH_DATASET_DISCLAIMER_TEXT, + value: '' + } + }) + } + getDatasetPublishPopupCustomText(): Promise> { + return getDatasetPublishPopupCustomText + .execute() + .then((text) => ({ + name: SettingName.DATASET_PUBLISH_POPUP_CUSTOM_TEXT, + value: text + })) + .catch(() => { + // In case of error, we default to an empty string which indicates no custom text. + return { + name: SettingName.DATASET_PUBLISH_POPUP_CUSTOM_TEXT, + value: '' + } + }) + } } diff --git a/src/notifications/domain/hooks/useNeedsUpdate.ts b/src/notifications/domain/hooks/useNeedsUpdate.ts index c8f6c03e7..fa0d2451b 100644 --- a/src/notifications/domain/hooks/useNeedsUpdate.ts +++ b/src/notifications/domain/hooks/useNeedsUpdate.ts @@ -1,30 +1,5 @@ -// hooks/useNeedsUpdate.ts import { useSyncExternalStore } from 'react' -type Listener = () => void - -class NeedsUpdateStore { - private needsUpdate = false - private listeners = new Set() - - getSnapshot = () => this.needsUpdate - - subscribe = (callback: Listener) => { - this.listeners.add(callback) - return () => this.listeners.delete(callback) - } - - setNeedsUpdate(value: boolean) { - if (this.needsUpdate !== value) { - this.needsUpdate = value - this.emit() - } - } - - private emit() { - this.listeners.forEach((listener) => listener()) - } -} -const needsUpdateStore = new NeedsUpdateStore() +import { needsUpdateStore } from './needsUpdateStore' export function useNeedsUpdate() { return useSyncExternalStore(needsUpdateStore.subscribe, needsUpdateStore.getSnapshot) diff --git a/src/notifications/domain/hooks/useNotifications.ts b/src/notifications/domain/hooks/useNotifications.ts index cb9a1d1d9..7afa097d3 100644 --- a/src/notifications/domain/hooks/useNotifications.ts +++ b/src/notifications/domain/hooks/useNotifications.ts @@ -4,6 +4,7 @@ import { Notification } from '@/notifications/domain/models/Notification' import { NotificationRepository } from '@/notifications/domain/repositories/NotificationRepository' import { NotificationsPaginationInfo } from '@/notifications/domain/models/NotificationsPaginationInfo' import { getAllNotificationsByUser } from '@/notifications/domain/useCases/getAllNotificationsByUser' +import { needsUpdateStore } from './needsUpdateStore' const POLLING_NOTIFICATIONS_INTERVAL_TIME = 30_000 @@ -67,6 +68,7 @@ export function useNotifications( try { await Promise.all(ids.map((id) => repository.markNotificationAsRead(id))) setError(null) + needsUpdateStore.setNeedsUpdate(true) } catch (err) { const message = err instanceof Error ? err.message : 'Failed to mark as read' setError(message) @@ -78,6 +80,7 @@ export function useNotifications( try { await Promise.all(ids.map((id) => repository.deleteNotification(id))) setError(null) + needsUpdateStore.setNeedsUpdate(true) } catch (err) { const message = err instanceof Error ? err.message : 'Failed to delete notifications' setError(message) diff --git a/src/notifications/domain/hooks/useUnreadCount.ts b/src/notifications/domain/hooks/useUnreadCount.ts index 8c1e5ab84..00241ffbf 100644 --- a/src/notifications/domain/hooks/useUnreadCount.ts +++ b/src/notifications/domain/hooks/useUnreadCount.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { needsUpdateStore } from './needsUpdateStore' import { NotificationRepository } from '@/notifications/domain/repositories/NotificationRepository' import { User } from '@/users/domain/models/User' @@ -6,22 +6,59 @@ import { useNeedsUpdate } from '@/notifications/domain/hooks/useNeedsUpdate' import { getUnreadNotificationsCount } from '@/notifications/domain/useCases/getUnreadNotificationsCount' const POLLING_INTERVAL = 30000 // 30 seconds +const INVALIDATION_RETRY_INTERVAL = 1000 +const INVALIDATION_RETRY_ATTEMPTS = 6 export function useUnreadCount(user: User, notificationRepository: NotificationRepository) { const [unreadCount, setUnreadCount] = useState(0) + const retryTimeoutRef = useRef | null>(null) + const refreshCycleRef = useRef(0) const needsUpdate = useNeedsUpdate() - const fetchUnread = useCallback(async () => { - if (user) { - const count = await getUnreadNotificationsCount(notificationRepository) - setUnreadCount(count) + + const clearRetryTimeout = useCallback(() => { + if (retryTimeoutRef.current) { + clearTimeout(retryTimeoutRef.current) + retryTimeoutRef.current = null } - needsUpdateStore.setNeedsUpdate(false) - }, [user, notificationRepository]) + }, []) + + const fetchUnread = useCallback( + async (refreshCycleId?: number) => { + if (user) { + const count = await getUnreadNotificationsCount(notificationRepository) + if (refreshCycleId === undefined || refreshCycleRef.current === refreshCycleId) { + setUnreadCount(count) + } + } + }, + [user, notificationRepository] + ) + useEffect(() => { if (needsUpdate) { - void fetchUnread() + const refreshCycleId = refreshCycleRef.current + 1 + refreshCycleRef.current = refreshCycleId + clearRetryTimeout() + needsUpdateStore.setNeedsUpdate(false) + + const runRefreshCycle = async (attempt: number) => { + await fetchUnread(refreshCycleId) + + if (refreshCycleRef.current !== refreshCycleId) { + return + } + + if (attempt < INVALIDATION_RETRY_ATTEMPTS - 1) { + retryTimeoutRef.current = setTimeout(() => { + void runRefreshCycle(attempt + 1) + }, INVALIDATION_RETRY_INTERVAL) + } + } + + void runRefreshCycle(0) } - }, [needsUpdate, fetchUnread, notificationRepository]) + }, [needsUpdate, fetchUnread, clearRetryTimeout]) + // Polling trigger useEffect(() => { const interval = setInterval(() => { @@ -29,11 +66,13 @@ export function useUnreadCount(user: User, notificationRepository: NotificationR }, POLLING_INTERVAL) return () => clearInterval(interval) - }, [fetchUnread, notificationRepository]) + }, [fetchUnread]) useEffect(() => { void fetchUnread() // run once when the component mounts }, [fetchUnread]) + useEffect(() => clearRetryTimeout, [clearRetryTimeout]) + return unreadCount } diff --git a/src/sections/account/Account.tsx b/src/sections/account/Account.tsx index 24def2254..bd29f9185 100644 --- a/src/sections/account/Account.tsx +++ b/src/sections/account/Account.tsx @@ -4,7 +4,6 @@ import { useSearchParams } from 'react-router-dom' import { Tabs } from '@iqss/dataverse-design-system' import { AccountHelper, AccountPanelTabKey } from './AccountHelper' import { UserJSDataverseRepository } from '@/users/infrastructure/repositories/UserJSDataverseRepository' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { NotificationRepository } from '@/notifications/domain/repositories/NotificationRepository' import { ApiTokenSection } from './api-token-section/ApiTokenSection' import { AccountInfoSection } from './account-info-section/AccountInfoSection' @@ -19,7 +18,6 @@ const tabsKeys = AccountHelper.ACCOUNT_PANEL_TABS_KEYS interface AccountProps { defaultActiveTabKey: AccountPanelTabKey userRepository: UserJSDataverseRepository - collectionRepository: CollectionRepository roleRepository: RoleJSDataverseRepository notificationRepository: NotificationRepository } @@ -27,7 +25,6 @@ interface AccountProps { export const Account = ({ defaultActiveTabKey, userRepository, - collectionRepository, roleRepository, notificationRepository }: AccountProps) => { @@ -54,10 +51,7 @@ export const Account = ({
- +
diff --git a/src/sections/account/AccountFactory.tsx b/src/sections/account/AccountFactory.tsx index dc5960fa7..13315fe74 100644 --- a/src/sections/account/AccountFactory.tsx +++ b/src/sections/account/AccountFactory.tsx @@ -6,6 +6,7 @@ import { UserJSDataverseRepository } from '@/users/infrastructure/repositories/U import { CollectionJSDataverseRepository } from '@/collection/infrastructure/repositories/CollectionJSDataverseRepository' import { RoleJSDataverseRepository } from '@/roles/infrastructure/repositories/RoleJSDataverseRepository' import { NotificationJSDataverseRepository } from '@/notifications/infrastructure/repositories/NotificationJSDataverseRepository' +import { RepositoriesProvider } from '@/shared/contexts/repositories/RepositoriesProvider' const userRepository = new UserJSDataverseRepository() const collectionRepository = new CollectionJSDataverseRepository() @@ -23,12 +24,13 @@ function AccountWithSearchParams() { const defaultActiveTabKey = AccountHelper.defineSelectedTabKey(searchParams) return ( - + + + ) } diff --git a/src/sections/account/my-data-section/MyDataItemsPanel.tsx b/src/sections/account/my-data-section/MyDataItemsPanel.tsx index a9c7b9801..88d7db853 100644 --- a/src/sections/account/my-data-section/MyDataItemsPanel.tsx +++ b/src/sections/account/my-data-section/MyDataItemsPanel.tsx @@ -1,7 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Alert, Stack } from '@iqss/dataverse-design-system' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { CollectionItemsPaginationInfo } from '@/collection/domain/models/CollectionItemsPaginationInfo' import { CollectionItemType } from '@/collection/domain/models/CollectionItemType' import { @@ -22,9 +21,9 @@ import { UserNameSearch } from '@/sections/account/my-data-section/user-name-sea import styles from './MyDataItemsPanel.module.scss' import { RoleRepository } from '@/roles/domain/repositories/RoleRepository' import { useSelectableRoles } from '@/sections/account/my-data-section/useSelectableRoles' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface MyDataItemsPanelProps { - collectionRepository: CollectionRepository roleRepository: RoleRepository } @@ -37,10 +36,8 @@ interface MyDataItemsPanelProps { * 4. When the user changes the item types, roles or publication statuses in the filter panel */ -export const MyDataItemsPanel = ({ - collectionRepository, - roleRepository -}: MyDataItemsPanelProps) => { +export const MyDataItemsPanel = ({ roleRepository }: MyDataItemsPanelProps) => { + const { collectionRepository } = useCollectionRepositories() const { user } = useSession() const { t } = useTranslation('account') const [roleIds, setRoleIds] = useState([]) diff --git a/src/sections/account/notifications-section/NotificationsSection.tsx b/src/sections/account/notifications-section/NotificationsSection.tsx index c64429db7..7b43fc01d 100644 --- a/src/sections/account/notifications-section/NotificationsSection.tsx +++ b/src/sections/account/notifications-section/NotificationsSection.tsx @@ -2,7 +2,6 @@ import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Alert, Button, CloseButton, Stack } from '@iqss/dataverse-design-system' import { getTranslatedNotification } from '@/sections/account/notifications-section/NotificationsHelper' -import { needsUpdateStore } from '@/notifications/domain/hooks/needsUpdateStore' import { useNotifications } from '@/notifications/domain/hooks/useNotifications' import { NotificationRepository } from '@/notifications/domain/repositories/NotificationRepository' import { NotificationsPaginationInfo } from '@/notifications/domain/models/NotificationsPaginationInfo' @@ -39,7 +38,6 @@ export const NotificationsSection = ({ notificationRepository }: NotificationsSe await markAsRead(unreadIds) setReadIds((prev) => [...prev, ...unreadIds]) await refetch() - needsUpdateStore.setNeedsUpdate(true) })() }, 2000) return () => clearTimeout(timer) @@ -86,7 +84,9 @@ export const NotificationsSection = ({ notificationRepository }: NotificationsSe direction="horizontal" gap={2} style={{ width: '100%', justifyContent: 'space-between', alignItems: 'center' }}> -
{t('notifications.displayingNotifications', { start, end, total })}
+ {notifications.length > 0 && ( +
{t('notifications.displayingNotifications', { start, end, total })}
+ )} {notifications.length > 0 && ( - {} : handleClose} centered size="lg"> + {} : handleClose} + centered + size="lg"> {t('datasetActionButtons.linkDataset.title')} @@ -121,7 +123,6 @@ export function LinkDatasetButton({ mode="link" linkingObjectType="dataset" datasetPersistentId={dataset.persistentId} - collectionRepository={collectionRepository} onCollectionSelected={handleCollectionSelected} helpText={t('datasetActionButtons.linkDataset.helper')} /> diff --git a/src/sections/dataset/dataset-action-buttons/link-and-unlink-actions/unlink-dataset-button/UnlinkDatasetButton.tsx b/src/sections/dataset/dataset-action-buttons/link-and-unlink-actions/unlink-dataset-button/UnlinkDatasetButton.tsx index eb42833eb..a2881a5ed 100644 --- a/src/sections/dataset/dataset-action-buttons/link-and-unlink-actions/unlink-dataset-button/UnlinkDatasetButton.tsx +++ b/src/sections/dataset/dataset-action-buttons/link-and-unlink-actions/unlink-dataset-button/UnlinkDatasetButton.tsx @@ -12,25 +12,23 @@ import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteE import { unlinkDataset } from '@/dataset/domain/useCases/unlinkDataset' import { RouteWithParams } from '@/sections/Route.enum' import { useGetDatasetLinkedCollections } from '@/dataset/domain/hooks/useGetDatasetLinkedCollections' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' const BASENAME_URL = import.meta.env.BASE_URL ?? '' interface UnlinkDatasetButtonProps { dataset: Dataset datasetRepository: DatasetRepository - collectionRepository: CollectionRepository updateParent: () => void } export function UnlinkDatasetButton({ dataset, datasetRepository, - collectionRepository, updateParent }: UnlinkDatasetButtonProps) { const { t } = useTranslation('dataset') const { t: tShared } = useTranslation('shared') + const modalTitle = t('datasetActionButtons.unlinkDataset.title') const { user } = useSession() const { datasetLinkedCollections } = useGetDatasetLinkedCollections({ datasetRepository, @@ -112,16 +110,16 @@ export function UnlinkDatasetButton({ show={showModal} onHide={isUnlinkingDataset ? () => {} : handleClose} centered - size="lg"> + size="lg" + ariaLabel={modalTitle}> - {t('datasetActionButtons.unlinkDataset.title')} + {modalTitle} diff --git a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx index a7191c70e..d79768bb1 100644 --- a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx @@ -6,19 +6,13 @@ import { DatasetRepository } from '../../../../dataset/domain/repositories/Datas import { Dataset, DatasetPublishingStatus } from '../../../../dataset/domain/models/Dataset' import { ChangeCurationStatusMenu } from './ChangeCurationStatusMenu' import { PublishDatasetModal } from '../../publish-dataset/PublishDatasetModal' -import { CollectionRepository } from '../../../../collection/domain/repositories/CollectionRepository' interface PublishDatasetMenuProps { dataset: Dataset datasetRepository: DatasetRepository - collectionRepository: CollectionRepository } -export function PublishDatasetMenu({ - dataset, - datasetRepository, - collectionRepository -}: PublishDatasetMenuProps) { +export function PublishDatasetMenu({ dataset, datasetRepository }: PublishDatasetMenuProps) { const { user } = useSession() const { t } = useTranslation('dataset') const [showModal, setShowModal] = useState(false) @@ -41,7 +35,6 @@ export function PublishDatasetMenu({ + - {t('actions.alreadyDeletedAlert.title')} + {modalTitle}

diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx index d580b4b8c..aef84d4d0 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx @@ -10,10 +10,11 @@ interface NoSelectedFilesModalProps { export function NoSelectedFilesModal({ show, handleClose }: NoSelectedFilesModalProps) { const { t } = useTranslation('files') + const modalTitle = t('actions.noSelectedFilesAlert.title') return ( - + - {t('actions.noSelectedFilesAlert.title')} + {modalTitle}

diff --git a/src/sections/dataset/dataset-versions/view-difference/DatasetVersionsDetailModal.tsx b/src/sections/dataset/dataset-versions/view-difference/DatasetVersionsDetailModal.tsx index 8bea8a186..8ea7e878c 100644 --- a/src/sections/dataset/dataset-versions/view-difference/DatasetVersionsDetailModal.tsx +++ b/src/sections/dataset/dataset-versions/view-difference/DatasetVersionsDetailModal.tsx @@ -22,11 +22,12 @@ export const VersionDetailModal = ({ }: VersionDetailModalProps) => { const { t } = useTranslation('dataset') const { t: tShared } = useTranslation('shared') + const modalTitle = t('versions.viewDifferencesDetail') return ( - + - {t('versions.viewDifferencesDetail')} + {modalTitle} diff --git a/src/sections/dataset/deaccession-dataset/DeaccessionDatasetModal.tsx b/src/sections/dataset/deaccession-dataset/DeaccessionDatasetModal.tsx index 043982047..a1630ee74 100644 --- a/src/sections/dataset/deaccession-dataset/DeaccessionDatasetModal.tsx +++ b/src/sections/dataset/deaccession-dataset/DeaccessionDatasetModal.tsx @@ -27,6 +27,7 @@ export function DeaccessionDatasetModal({ handleCloseDeaccessionModal }: DeaccessionDatasetModalProps) { const { t } = useTranslation(['dataset', 'shared']) + const modalTitle = 'Deaccession Dataset' const [showConfirmationModal, setShowConfirmationModal] = useState(false) const { refreshDataset } = useDataset() @@ -110,9 +111,9 @@ export function DeaccessionDatasetModal({ return ( <> - + - Deaccession Dataset + {modalTitle} {isLoadingDatasetVersionSummaries ? ( diff --git a/src/sections/dataset/publish-dataset/PublishDatasetModal.module.scss b/src/sections/dataset/publish-dataset/PublishDatasetModal.module.scss index 59a26cb95..e568ea919 100644 --- a/src/sections/dataset/publish-dataset/PublishDatasetModal.module.scss +++ b/src/sections/dataset/publish-dataset/PublishDatasetModal.module.scss @@ -7,3 +7,21 @@ .secondaryText { color: $dv-subtext-color; } + +.disclaimerText { + + label { + color: $dv-danger-color; + } +} + +.customPopupTextBlock { + border: 1px solid $dv-border-color; + background-color: #f8f9fa; + padding: 0.75rem 1rem; + border-radius: 0.25rem; +} + +.customPopupText { + margin: 0; + color: $dv-text-color; +} diff --git a/src/sections/dataset/publish-dataset/PublishDatasetModal.tsx b/src/sections/dataset/publish-dataset/PublishDatasetModal.tsx index e2909fe40..e1300cef8 100644 --- a/src/sections/dataset/publish-dataset/PublishDatasetModal.tsx +++ b/src/sections/dataset/publish-dataset/PublishDatasetModal.tsx @@ -14,17 +14,18 @@ import { SubmissionStatus } from '../../shared/form/DatasetMetadataForm/useSubmi import { QueryParamKey, Route } from '../../Route.enum' import { usePublishDataset } from './usePublishDataset' import { PublishDatasetHelpText } from './PublishDatasetHelpText' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { UpwardHierarchyNode } from '@/shared/hierarchy/domain/models/UpwardHierarchyNode' import { DatasetLicense } from '@/dataset/domain/models/Dataset' -import styles from './PublishDatasetModal.module.scss' import { PublishLicense } from '@/sections/dataset/publish-dataset/PublishLicense' import { CustomTerms } from '@/sections/dataset/dataset-terms/CustomTerms' +import { useSettings } from '@/sections/settings/SettingsContext' +import { SettingName } from '@/settings/domain/models/Setting' +import styles from './PublishDatasetModal.module.scss' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface PublishDatasetModalProps { show: boolean repository: DatasetRepository - collectionRepository: CollectionRepository parentCollection: UpwardHierarchyNode persistentId: string license: DatasetLicense | undefined @@ -39,7 +40,6 @@ interface PublishDatasetModalProps { export function PublishDatasetModal({ show, repository, - collectionRepository, parentCollection, persistentId, license, @@ -50,9 +50,11 @@ export function PublishDatasetModal({ nextMinorVersion, requiresMajorVersionUpdate }: PublishDatasetModalProps) { + const { collectionRepository } = useCollectionRepositories() const { t } = useTranslation('dataset') const { user } = useSession() const navigate = useNavigate() + const { getSettingByName } = useSettings() const { submissionStatus, submitPublish, publishError } = usePublishDataset( repository, collectionRepository, @@ -69,6 +71,19 @@ export function PublishDatasetModal({ } const nextMajorVersionString = nextMajorVersion ? nextMajorVersion : '' const nextMinorVersionString = nextMinorVersion ? nextMinorVersion : '' + + const publishDisclaimerText = getSettingByName( + SettingName.PUBLISH_DATASET_DISCLAIMER_TEXT + )?.value + + const datasetPublishPopupCustomText = getSettingByName( + SettingName.DATASET_PUBLISH_POPUP_CUSTOM_TEXT + )?.value + + const shouldShowCustomPopupText = Boolean(datasetPublishPopupCustomText?.trim()) + const shouldShowDisclaimer = Boolean(publishDisclaimerText?.trim()) + const [isDisclaimerAccepted, setIsDisclaimerAccepted] = useState(false) + function onPublishSucceed() { navigate( `${Route.DATASETS}?${QueryParamKey.PERSISTENT_ID}=${persistentId}&${QueryParamKey.VERSION}=${DatasetNonNumericVersionSearchParam.DRAFT}`, @@ -81,8 +96,13 @@ export function PublishDatasetModal({ } const modalTitle = t('publish.title') + const handleCloseWithReset = () => { + setIsDisclaimerAccepted(false) + handleClose() + } + return ( - + {t('publish.title')} @@ -97,6 +117,12 @@ export function PublishDatasetModal({ requiresMajorVersionUpdate={requiresMajorVersionUpdate ?? false} /> + {shouldShowCustomPopupText && ( +

+

{datasetPublishPopupCustomText}

+
+ )} + {releasedVersionExists && !requiresMajorVersionUpdate && ( <> @@ -139,10 +165,24 @@ export function PublishDatasetModal({ window.open(newUrl, '_blank') }} /> +

{t('publish.termsText')}

+ + {shouldShowDisclaimer && ( + + setIsDisclaimerAccepted(e.target.checked)} + label={publishDisclaimerText} + /> + + )} {submissionStatus === SubmissionStatus.Errored && @@ -159,7 +199,10 @@ export function PublishDatasetModal({ submitPublish(versionUpdateType) }} type="submit" - disabled={submissionStatus === SubmissionStatus.IsSubmitting}> + disabled={ + submissionStatus === SubmissionStatus.IsSubmitting || + (shouldShowDisclaimer && !isDisclaimerAccepted) + }> {t('publish.continueButton')} {submissionStatus === SubmissionStatus.IsSubmitting && ( @@ -171,7 +214,7 @@ export function PublishDatasetModal({ withSpacing variant="secondary" type="button" - onClick={handleClose} + onClick={handleCloseWithReset} disabled={submissionStatus === SubmissionStatus.IsSubmitting}> {t('publish.cancelButton')} diff --git a/src/sections/dataset/publish-dataset/usePublishDataset.tsx b/src/sections/dataset/publish-dataset/usePublishDataset.tsx index 6a46df4fc..020848efc 100644 --- a/src/sections/dataset/publish-dataset/usePublishDataset.tsx +++ b/src/sections/dataset/publish-dataset/usePublishDataset.tsx @@ -7,6 +7,7 @@ import { SubmissionStatus } from '../../shared/form/DatasetMetadataForm/useSubmi import { CollectionRepository } from '../../../collection/domain/repositories/CollectionRepository' import { UpwardHierarchyNode } from '../../../shared/hierarchy/domain/models/UpwardHierarchyNode' import { publishCollection } from '../../../collection/domain/useCases/publishCollection' +import { needsUpdateStore } from '@/notifications/domain/hooks/needsUpdateStore' type UsePublishDatasetReturnType = | { @@ -43,6 +44,7 @@ export function usePublishDataset( .then(() => { setPublishError(null) setSubmissionStatus(SubmissionStatus.SubmitComplete) + needsUpdateStore.setNeedsUpdate(true) onPublishSucceed() }) .catch((err) => { diff --git a/src/sections/dataset/useCheckPublishCompleted.ts b/src/sections/dataset/useCheckPublishCompleted.ts index 48103ca7e..e0d571a68 100644 --- a/src/sections/dataset/useCheckPublishCompleted.ts +++ b/src/sections/dataset/useCheckPublishCompleted.ts @@ -3,6 +3,7 @@ import { getDatasetLocks } from '../../dataset/domain/useCases/getDatasetLocks' import { Dataset } from '../../dataset/domain/models/Dataset' import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' import { PUBLISH_DATASET_POLL_INTERVAL } from './config' +import { needsUpdateStore } from '@/notifications/domain/hooks/needsUpdateStore' const useCheckPublishCompleted = ( publishInProgress: boolean | undefined, @@ -11,6 +12,11 @@ const useCheckPublishCompleted = ( ): boolean => { const [publishCompleted, setPublishCompleted] = useState(false) + const handlePublishCompleted = () => { + setPublishCompleted(true) + needsUpdateStore.setNeedsUpdate(true) + } + useEffect(() => { let intervalId: NodeJS.Timeout | null = null @@ -18,7 +24,7 @@ const useCheckPublishCompleted = ( const waitForDatasetLocksReleased = async () => { const initialLocks = await getDatasetLocks(datasetRepository, dataset.persistentId) if (initialLocks.length === 0) { - setPublishCompleted(true) + handlePublishCompleted() } else { intervalId = setInterval(() => { const pollLocks = async () => { @@ -26,7 +32,7 @@ const useCheckPublishCompleted = ( const locks = await getDatasetLocks(datasetRepository, dataset.persistentId) if (locks.length === 0) { if (intervalId) clearInterval(intervalId) - setPublishCompleted(true) + handlePublishCompleted() } } catch (error) { if (intervalId) clearInterval(intervalId) diff --git a/src/sections/edit-collection-featured-items/EditFeaturedItems.tsx b/src/sections/edit-collection-featured-items/EditFeaturedItems.tsx index 3b3900bd7..99d31142e 100644 --- a/src/sections/edit-collection-featured-items/EditFeaturedItems.tsx +++ b/src/sections/edit-collection-featured-items/EditFeaturedItems.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Alert } from '@iqss/dataverse-design-system' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { useGetFeaturedItems } from '../collection/useGetFeaturedItems' import { useCollection } from '../collection/useCollection' import { useLoading } from '../../shared/contexts/loading/LoadingContext' @@ -12,16 +11,14 @@ import { FeaturedItemsForm } from './featured-items-form/FeaturedItemsForm' import { FeaturedItemsFormHelper } from './featured-items-form/FeaturedItemsFormHelper' import { FeaturedItemsFormData } from './types' import { AppLoader } from '../shared/layout/app-loader/AppLoader' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface EditFeaturedItemsProps { - collectionRepository: CollectionRepository collectionIdFromParams: string | undefined } -export const EditFeaturedItems = ({ - collectionRepository, - collectionIdFromParams -}: EditFeaturedItemsProps) => { +export const EditFeaturedItems = ({ collectionIdFromParams }: EditFeaturedItemsProps) => { + const { collectionRepository } = useCollectionRepositories() const { t } = useTranslation('editFeaturedItems') const { setIsLoading } = useLoading() const { collection, isLoading } = useCollection(collectionRepository, collectionIdFromParams) @@ -74,7 +71,6 @@ export const EditFeaturedItems = ({ collectionId={collection.id} defaultValues={formDefaultValues} initialExistingFeaturedItems={featuredItems} - collectionRepository={collectionRepository} /> ) diff --git a/src/sections/edit-collection-featured-items/EditFeaturedItemsFactory.tsx b/src/sections/edit-collection-featured-items/EditFeaturedItemsFactory.tsx index 08a1f11c9..d4622e20b 100644 --- a/src/sections/edit-collection-featured-items/EditFeaturedItemsFactory.tsx +++ b/src/sections/edit-collection-featured-items/EditFeaturedItemsFactory.tsx @@ -2,6 +2,7 @@ import { ReactElement } from 'react' import { useParams } from 'react-router-dom' import { CollectionJSDataverseRepository } from '@/collection/infrastructure/repositories/CollectionJSDataverseRepository' import { EditFeaturedItems } from './EditFeaturedItems' +import { RepositoriesProvider } from '@/shared/contexts/repositories/RepositoriesProvider' const collectionRepository = new CollectionJSDataverseRepository() @@ -15,9 +16,8 @@ function EditFeaturedItemsWithSearchParams() { const { collectionId } = useParams<{ collectionId: string }>() return ( - + + + ) } diff --git a/src/sections/edit-collection-featured-items/featured-items-form/ConfirmDeleteModal.tsx b/src/sections/edit-collection-featured-items/featured-items-form/ConfirmDeleteModal.tsx index b022817a1..4c63f8496 100644 --- a/src/sections/edit-collection-featured-items/featured-items-form/ConfirmDeleteModal.tsx +++ b/src/sections/edit-collection-featured-items/featured-items-form/ConfirmDeleteModal.tsx @@ -14,11 +14,12 @@ export const ConfirmDeleteModal = ({ }: ConfirmDeleteModalProps) => { const { t: tShared } = useTranslation('shared') const { t } = useTranslation('editFeaturedItems') + const modalTitle = t('deleteAll.action') return ( - + - {t('deleteAll.action')} + {modalTitle} {t('deleteAll.confirmation')} diff --git a/src/sections/edit-collection-featured-items/featured-items-form/FeaturedItemsForm.tsx b/src/sections/edit-collection-featured-items/featured-items-form/FeaturedItemsForm.tsx index a2519422b..85b08dff5 100644 --- a/src/sections/edit-collection-featured-items/featured-items-form/FeaturedItemsForm.tsx +++ b/src/sections/edit-collection-featured-items/featured-items-form/FeaturedItemsForm.tsx @@ -5,7 +5,6 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers' import { SortableContext } from '@dnd-kit/sortable' import { useTranslation } from 'react-i18next' import { toast } from 'react-toastify' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { FeaturedItem, CustomFeaturedItem, @@ -19,20 +18,20 @@ import { useDeleteFeaturedItems } from './useDeleteFeaturedItems' import { ActionButtons } from './ActionButtons' import { ConfirmDeleteModal } from './ConfirmDeleteModal' import styles from './FeaturedItemsForm.module.scss' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface FeaturedItemsFormProps { collectionId: string - collectionRepository: CollectionRepository defaultValues: FeaturedItemsFormData initialExistingFeaturedItems: FeaturedItem[] } export const FeaturedItemsForm = ({ collectionId, - collectionRepository, defaultValues, initialExistingFeaturedItems }: FeaturedItemsFormProps) => { + const { collectionRepository } = useCollectionRepositories() const { t } = useTranslation('editFeaturedItems') const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false) @@ -182,7 +181,6 @@ export const FeaturedItemsForm = ({ onAddField={handleOnAddField} onRemoveField={handleOnRemoveField} onSelectType={handleSelectType} - collectionRepository={collectionRepository} initialImageUrl={ (initialExistingFeaturedItems as CustomFeaturedItem[]).find( (item) => item.id === field.itemId diff --git a/src/sections/edit-collection-featured-items/featured-items-form/featured-item-field/FeaturedItemField.tsx b/src/sections/edit-collection-featured-items/featured-items-form/featured-item-field/FeaturedItemField.tsx index 547dcd50d..7b0fd9c5d 100644 --- a/src/sections/edit-collection-featured-items/featured-items-form/featured-item-field/FeaturedItemField.tsx +++ b/src/sections/edit-collection-featured-items/featured-items-form/featured-item-field/FeaturedItemField.tsx @@ -8,7 +8,6 @@ import { ExclamationTriangle, Pencil, Plus, Trash } from 'react-bootstrap-icons' import { WriteError } from '@iqss/dataverse-client-javascript' import cn from 'classnames' import { Badge, Button, Col, Row, Tooltip } from '@iqss/dataverse-design-system' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { FeaturedItemType } from '@/collection/domain/models/FeaturedItem' import { BaseFormItem } from './base-form-item/BaseFormItem' import { DvObjectFormItem } from './dv-object-form-item/DvObjectFormItem' @@ -20,6 +19,7 @@ import { deleteFeaturedItem } from '@/collection/domain/useCases/deleteFeaturedI import { JSDataverseWriteErrorHandler } from '@/shared/helpers/JSDataverseWriteErrorHandler' import { FeaturedItemsFormHelper } from '../FeaturedItemsFormHelper' import styles from './FeaturedItemField.module.scss' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface FeaturedItemFieldProps { sortableId: string @@ -28,7 +28,6 @@ interface FeaturedItemFieldProps { onAddField: (index: number) => void onRemoveField: (index: number) => void onSelectType: (index: number, type: FeaturedItemType.CUSTOM | '' | 'base') => void - collectionRepository: CollectionRepository disableDragWhenOnlyOneItem: boolean initialImageUrl?: string featuredItemType: FeaturedItemType | 'base' | '' @@ -42,12 +41,12 @@ export const FeaturedItemField = ({ onAddField, onRemoveField, onSelectType, - collectionRepository, disableDragWhenOnlyOneItem, initialImageUrl, featuredItemType, itemsLength }: FeaturedItemFieldProps) => { + const { collectionRepository } = useCollectionRepositories() const isExistingItem = itemId !== undefined && itemId !== null const { t: tShared } = useTranslation('shared') const { t } = useTranslation('editFeaturedItems') diff --git a/src/sections/edit-collection/EditCollection.tsx b/src/sections/edit-collection/EditCollection.tsx index 0ee6c71a2..8ca512841 100644 --- a/src/sections/edit-collection/EditCollection.tsx +++ b/src/sections/edit-collection/EditCollection.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Alert } from '@iqss/dataverse-design-system' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { MetadataBlockInfoRepository } from '@/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { useGetCollectionUserPermissions } from '@/shared/hooks/useGetCollectionUserPermissions' import { useLoading } from '../../shared/contexts/loading/LoadingContext' @@ -15,18 +14,18 @@ import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine' import { RequiredFieldText } from '../shared/form/RequiredFieldText/RequiredFieldText' import { EditCreateCollectionForm } from '../shared/form/EditCreateCollectionForm/EditCreateCollectionForm' import { EditCollectionSkeleton } from './EditCollectionSkeleton' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface EditCollectionProps { collectionId: string - collectionRepository: CollectionRepository metadataBlockInfoRepository: MetadataBlockInfoRepository } export const EditCollection = ({ collectionId, - collectionRepository, metadataBlockInfoRepository }: EditCollectionProps) => { + const { collectionRepository } = useCollectionRepositories() const { t } = useTranslation('editCollection') const { setIsLoading } = useLoading() const { user } = useSession() @@ -113,7 +112,6 @@ export const EditCollection = ({ } : undefined } - collectionRepository={collectionRepository} metadataBlockInfoRepository={metadataBlockInfoRepository} /> diff --git a/src/sections/edit-collection/EditCollectionFactory.tsx b/src/sections/edit-collection/EditCollectionFactory.tsx index 21dc9dc52..e35bafbbb 100644 --- a/src/sections/edit-collection/EditCollectionFactory.tsx +++ b/src/sections/edit-collection/EditCollectionFactory.tsx @@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom' import { CollectionJSDataverseRepository } from '@/collection/infrastructure/repositories/CollectionJSDataverseRepository' import { MetadataBlockInfoJSDataverseRepository } from '@/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' import { EditCollection } from './EditCollection' +import { RepositoriesProvider } from '@/shared/contexts/repositories/RepositoriesProvider' const collectionRepository = new CollectionJSDataverseRepository() const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() @@ -19,10 +20,11 @@ function EditCollectionWithParams() { } return ( - + + + ) } diff --git a/src/sections/edit-dataset-terms/EditDatasetTerms.tsx b/src/sections/edit-dataset-terms/EditDatasetTerms.tsx index edbfe7f02..ede3fd5f7 100644 --- a/src/sections/edit-dataset-terms/EditDatasetTerms.tsx +++ b/src/sections/edit-dataset-terms/EditDatasetTerms.tsx @@ -40,7 +40,6 @@ export const EditDatasetTerms = ({ const [licenseFormIsDirty, setLicenseFormIsDirty] = useState(false) const [termsOfAccessFormIsDirty, setTermsOfAccessFormIsDirty] = useState(false) - const [guestBookFormIsDirty, setGuestBookFormIsDirty] = useState(false) useEffect(() => { setIsLoading(isLoading) @@ -60,8 +59,6 @@ export const EditDatasetTerms = ({ return licenseFormIsDirty case tabsKeys.restrictedFilesTerms: return termsOfAccessFormIsDirty - case tabsKeys.guestBook: - return guestBookFormIsDirty default: return false } diff --git a/src/sections/edit-dataset-terms/confirm-leave-modal/ConfirmLeaveModal.tsx b/src/sections/edit-dataset-terms/confirm-leave-modal/ConfirmLeaveModal.tsx index 12b47b82e..63ea9024f 100644 --- a/src/sections/edit-dataset-terms/confirm-leave-modal/ConfirmLeaveModal.tsx +++ b/src/sections/edit-dataset-terms/confirm-leave-modal/ConfirmLeaveModal.tsx @@ -11,11 +11,12 @@ interface ConfirmLeaveModalProps { export const ConfirmLeaveModal = ({ show, onStay, onLeave }: ConfirmLeaveModalProps) => { const { t } = useTranslation('dataset') + const modalTitle = t('editTerms.unsavedChangesModal.title') return ( - + - {t('editTerms.unsavedChangesModal.title')} + {modalTitle} diff --git a/src/sections/featured-item/FeaturedItem.tsx b/src/sections/featured-item/FeaturedItem.tsx index 0403a96f5..634fd7bcd 100644 --- a/src/sections/featured-item/FeaturedItem.tsx +++ b/src/sections/featured-item/FeaturedItem.tsx @@ -1,25 +1,24 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Alert } from '@iqss/dataverse-design-system' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { CustomFeaturedItem } from '@/collection/domain/models/FeaturedItem' import { useGetFeaturedItems } from '../collection/useGetFeaturedItems' import { AppLoader } from '../shared/layout/app-loader/AppLoader' import { useLoading } from '../../shared/contexts/loading/LoadingContext' import { FeaturedItemView } from './featured-item-view/FeaturedItemView' import { useCollection } from '../collection/useCollection' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface FeaturedItemProps { - collectionRepository: CollectionRepository parentCollectionIdFromParams: string featuredItemId: string } export const FeaturedItem = ({ - collectionRepository, parentCollectionIdFromParams, featuredItemId }: FeaturedItemProps) => { + const { collectionRepository } = useCollectionRepositories() const { setIsLoading } = useLoading() const { t } = useTranslation('featuredItem') diff --git a/src/sections/featured-item/FeaturedItemFactory.tsx b/src/sections/featured-item/FeaturedItemFactory.tsx index 45fcdcae9..ebcf661b8 100644 --- a/src/sections/featured-item/FeaturedItemFactory.tsx +++ b/src/sections/featured-item/FeaturedItemFactory.tsx @@ -2,6 +2,7 @@ import { ReactElement } from 'react' import { useParams } from 'react-router-dom' import { CollectionJSDataverseRepository } from '../../collection/infrastructure/repositories/CollectionJSDataverseRepository' import { FeaturedItem } from './FeaturedItem' +import { RepositoriesProvider } from '@/shared/contexts/repositories/RepositoriesProvider' const collectionRepository = new CollectionJSDataverseRepository() @@ -18,10 +19,11 @@ function FeaturedItemWithParams() { } return ( - + + + ) } diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx index ee0fce16e..530179864 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx @@ -19,6 +19,7 @@ export function FileNonTabularDownloadOptions({ return ( {type.displayFormatIsUnknown diff --git a/src/sections/file/file-action-buttons/access-file-menu/RequestAccessModal.tsx b/src/sections/file/file-action-buttons/access-file-menu/RequestAccessModal.tsx index d4a7b49a4..8deed5467 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/RequestAccessModal.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/RequestAccessModal.tsx @@ -15,6 +15,7 @@ interface RequestAccessButtonProps { export function RequestAccessModal({ fileId }: RequestAccessButtonProps) { const { t } = useTranslation('files') const { user } = useSession() + const modalTitle = t('requestAccess.title') const [show, setShow] = useState(false) const handleClose = () => { setShow(false) @@ -24,9 +25,9 @@ export function RequestAccessModal({ fileId }: RequestAccessButtonProps) { return ( <> {t('requestAccess.title')} - + - {t('requestAccess.title')} + {modalTitle} {user ? ( diff --git a/src/sections/file/file-action-buttons/edit-file-menu/delete-file-button/confirm-delete-file-modal/ConfirmDeleteFileModal.tsx b/src/sections/file/file-action-buttons/edit-file-menu/delete-file-button/confirm-delete-file-modal/ConfirmDeleteFileModal.tsx index a00e1e4d6..2722a96f9 100644 --- a/src/sections/file/file-action-buttons/edit-file-menu/delete-file-button/confirm-delete-file-modal/ConfirmDeleteFileModal.tsx +++ b/src/sections/file/file-action-buttons/edit-file-menu/delete-file-button/confirm-delete-file-modal/ConfirmDeleteFileModal.tsx @@ -22,11 +22,17 @@ export const ConfirmDeleteFileModal = ({ }: ConfirmDeleteFileModalProps) => { const { t: tShared } = useTranslation('shared') const { t } = useTranslation('file') + const modalTitle = t('deleteFileModal.title') return ( - {} : handleClose} centered size="lg"> + {} : handleClose} + centered + size="lg" + ariaLabel={modalTitle}> - {t('deleteFileModal.title')} + {modalTitle} diff --git a/src/sections/file/file-action-buttons/edit-file-menu/edit-file-tags/edit-file-tags-modal/EditFileTagsModal.tsx b/src/sections/file/file-action-buttons/edit-file-menu/edit-file-tags/edit-file-tags-modal/EditFileTagsModal.tsx index a7e7bc4a6..8fac80b7f 100644 --- a/src/sections/file/file-action-buttons/edit-file-menu/edit-file-tags/edit-file-tags-modal/EditFileTagsModal.tsx +++ b/src/sections/file/file-action-buttons/edit-file-menu/edit-file-tags/edit-file-tags-modal/EditFileTagsModal.tsx @@ -59,6 +59,7 @@ export const EditFileTagsModal = ({ }: EditFileTagsModalProps) => { const { t: tShared } = useTranslation('shared') const { t } = useTranslation('file') + const modalTitle = t('editFileTagsModal.title') const { refreshFiles } = useFilesContext() const [customTag, setCustomTag] = useState('') const [hasDuplicateCustomTag, setHasDuplicateCustomTag] = useState(false) @@ -149,9 +150,9 @@ export const EditFileTagsModal = ({ ] return ( - + - {t('editFileTagsModal.title')} + {modalTitle} diff --git a/src/sections/file/file-action-buttons/edit-file-menu/restrict-file-button/confirm-restrict-file-modal/ConfirmRestrictFileModal.tsx b/src/sections/file/file-action-buttons/edit-file-menu/restrict-file-button/confirm-restrict-file-modal/ConfirmRestrictFileModal.tsx index 323243ac5..33c884e0d 100644 --- a/src/sections/file/file-action-buttons/edit-file-menu/restrict-file-button/confirm-restrict-file-modal/ConfirmRestrictFileModal.tsx +++ b/src/sections/file/file-action-buttons/edit-file-menu/restrict-file-button/confirm-restrict-file-modal/ConfirmRestrictFileModal.tsx @@ -37,6 +37,7 @@ export const ConfirmRestrictFileModal = ({ }: ConfirmRestrictFileModalProps) => { const { t: tShared } = useTranslation('shared') const { t } = useTranslation('file') + const modalTitle = t('restriction.restrictAccess') const [enableAccessRequest, setEnableAccessRequest] = useState(requestAccess) const [terms, setTerms] = useState(termsOfAccessForRestrictedFiles) @@ -49,9 +50,14 @@ export const ConfirmRestrictFileModal = ({ }, [show, requestAccess, termsOfAccessForRestrictedFiles]) return ( - {} : handleClose} centered size="lg"> + {} : handleClose} + centered + size="lg" + ariaLabel={modalTitle}> - {t('restriction.restrictAccess')} + {modalTitle} {' '} diff --git a/src/sections/homepage/Homepage.tsx b/src/sections/homepage/Homepage.tsx index cc583427d..b7ee5a408 100644 --- a/src/sections/homepage/Homepage.tsx +++ b/src/sections/homepage/Homepage.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react' import { Link } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { DataverseHubRepository } from '@/dataverse-hub/domain/repositories/DataverseHubRepository' import { SearchRepository } from '@/search/domain/repositories/SearchRepository' import { useGetSearchServices } from '@/search/domain/hooks/useGetSearchServices' @@ -13,18 +12,15 @@ import { SearchInput } from './search-input/SearchInput' import { Metrics } from './metrics/Metrics' import { Usage } from './usage/Usage' import styles from './Homepage.module.scss' +import { useCollectionRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' interface HomepageProps { - collectionRepository: CollectionRepository dataverseHubRepository: DataverseHubRepository searchRepository: SearchRepository } -export const Homepage = ({ - collectionRepository, - dataverseHubRepository, - searchRepository -}: HomepageProps) => { +export const Homepage = ({ dataverseHubRepository, searchRepository }: HomepageProps) => { + const { collectionRepository } = useCollectionRepositories() const { collection, isLoading: isLoadingCollection } = useCollection(collectionRepository) const { searchServices, isLoadingSearchServices } = useGetSearchServices({ searchRepository, @@ -59,11 +55,7 @@ export const Homepage = ({ {collection && ( <>
- +
)} diff --git a/src/sections/homepage/HomepageFactory.tsx b/src/sections/homepage/HomepageFactory.tsx index 8fcad9c6f..305d91a91 100644 --- a/src/sections/homepage/HomepageFactory.tsx +++ b/src/sections/homepage/HomepageFactory.tsx @@ -2,6 +2,7 @@ import { ReactElement } from 'react' import { CollectionJSDataverseRepository } from '@/collection/infrastructure/repositories/CollectionJSDataverseRepository' import { ApiDataverseHubRepository } from '@/dataverse-hub/infrastructure/repositories/ApiDataverseHubRepository' import { SearchJSRepository } from '@/search/infrastructure/repositories/SearchJSRepository' +import { RepositoriesProvider } from '@/shared/contexts/repositories/RepositoriesProvider' import Homepage from './Homepage' const collectionRepository = new CollectionJSDataverseRepository() @@ -11,11 +12,12 @@ const searchRepository = new SearchJSRepository() export class HomepageFactory { static create(): ReactElement { return ( - + + + ) } } diff --git a/src/sections/homepage/usage/Usage.tsx b/src/sections/homepage/usage/Usage.tsx index f390560ec..63cad114e 100644 --- a/src/sections/homepage/usage/Usage.tsx +++ b/src/sections/homepage/usage/Usage.tsx @@ -3,14 +3,20 @@ import { useTranslation } from 'react-i18next' import { Card, Col, Row, Stack } from '@iqss/dataverse-design-system' import { BoxArrowUpRight, Plus } from 'react-bootstrap-icons' import { RouteWithParams } from '@/sections/Route.enum' +import { requireAppConfig } from '@/config' import styles from './Usage.module.scss' interface UsageProps { collectionId: string } +const DEFAULT_SUPPORT_URL = 'https://guides.dataverse.org/en/latest/user/index.html' + export const Usage = ({ collectionId }: UsageProps) => { const { t } = useTranslation('homepage', { keyPrefix: 'usage' }) + const appConfig = requireAppConfig() + const dataverseName = appConfig.branding?.dataverseName ?? 'Dataverse' + const supportUrl = appConfig.homepage?.supportUrl ?? DEFAULT_SUPPORT_URL return ( @@ -18,7 +24,12 @@ export const Usage = ({ collectionId }: UsageProps) => {
{t('datasets.title')}
-

{t('datasets.content')}

+

+ {t('datasets.content', { + dataverseName, + interpolation: { escapeValue: false } + })} +