diff --git a/public/globals.js b/public/globals.js index f6c3ec363..91d1cf456 100644 --- a/public/globals.js +++ b/public/globals.js @@ -1014,6 +1014,14 @@ window.pkp = { "Once the user is enabled, they will regain access to OJS, and you'll be able to invite them to roles as needed.", 'grid.user.grid.user.disableReasonDescription': "Please note that once a user is disabled, you won't be able to add them to any roles until they are enabled again.", + 'reviewerInvitation.responseDueDate':'Review Response Date', + 'reviewerInvitation.reviewDueDate':'Review Response Due Date', + 'reviewerInvitation.reviewTypes':'Review Types', + 'reviewerInvitation.modal.message':'{$email} has been invited to review the submission "{$articleTitle}"

You can be updated about the user\'s descision on the reviewer panel in the review workflow or through email and OJS notifications', + 'reviewerInvitation.modal.button':'View submission', + 'reviewerInvitation.reviewTypes.anonymusAuthorOrReviewer':'Anonymus Reviewer / Anonymus Author', + 'reviewerInvitation.reviewTypes.disclosedAuthor':'Anonymus Reviewer / Disclosed Author', + 'reviewerInvitation.reviewTypes.open':'Open' }, tinyMCE: { skinUrl: '/styles/tinymce', diff --git a/src/components/ListPanel/users/SelectReviewerListItem.vue b/src/components/ListPanel/users/SelectReviewerListItem.vue index 65e2b1fec..f729a7cdf 100644 --- a/src/components/ListPanel/users/SelectReviewerListItem.vue +++ b/src/components/ListPanel/users/SelectReviewerListItem.vue @@ -244,6 +244,8 @@ import ListItem from '@/components/List/ListItem.vue'; import Badge from '@/components/Badge/Badge.vue'; import PkpButton from '@/components/Button/Button.vue'; import Icon from '@/components/Icon/Icon.vue'; +import {useWorkflowStore} from '@/pages/workflow/workflowStore'; +import {useUrl} from '@/composables/useUrl'; export default { components: { @@ -482,8 +484,16 @@ export default { * @param */ select() { - this.$emit('select', this.item); - pkp.eventBus.$emit('selected:reviewer', this.item); + const workflow = useWorkflowStore(); + const {redirectToPage: redirectToReviewerInvitationPage} = useUrl( + 'invitation/create/reviewerAccess', + { + userId: this.item.id, + submissionId: workflow.submission.id, + reviewRoundId: workflow.selectedMenuState.reviewRoundId, + }, + ); + redirectToReviewerInvitationPage(); }, /** diff --git a/src/pages/userInvitation/ReviewerReviewDetailsStep.vue b/src/pages/userInvitation/ReviewerReviewDetailsStep.vue new file mode 100644 index 000000000..3064ec491 --- /dev/null +++ b/src/pages/userInvitation/ReviewerReviewDetailsStep.vue @@ -0,0 +1,84 @@ + + diff --git a/src/pages/userInvitation/UserInvitationPage.stories.js b/src/pages/userInvitation/UserInvitationPage.stories.js index 4e0d28edd..fc0ed96d8 100644 --- a/src/pages/userInvitation/UserInvitationPage.stories.js +++ b/src/pages/userInvitation/UserInvitationPage.stories.js @@ -2,6 +2,7 @@ import UserInvitationPage from './UserInvitationPage.vue'; import {http, HttpResponse} from 'msw'; import userMock from './mocks/userMock.js'; import PageInitConfigMock from './mocks/pageInitConfig'; +import ReviewerPageInitConfigMock from './mocks/reviewerPageInitConfig'; export default {title: 'Pages/UserInvitation', component: UserInvitationPage}; @@ -118,3 +119,100 @@ export const Init = { }, args: PageInitConfigMock, }; + +export const Reviewer = { + render: (args) => ({ + components: {UserInvitationPage}, + setup() { + return {args}; + }, + template: '', + }), + parameters: { + msw: { + handlers: [ + http.post( + 'https://mock/index.php/publicknowledge/api/v1/invitations/add/reviewerAccessInvite', + () => { + return HttpResponse.json({invitationId: 15}); + }, + ), + http.post( + 'https://mock/index.php/publicknowledge/api/v1/invitations/15/populate', + async ({request}) => { + const data = await request.json(); + let errors = {}; + console.log(data.invitationData); + + data.invitationData.userGroupsToAdd.forEach((element, index) => { + Object.keys(element).forEach((key) => { + if (element[key] === null) { + errors = { + ...errors, + ['userGroupsToAdd.' + index + '.' + key]: [ + 'This field is required', + ], + }; + } + }); + }); + + if (data.invitationData.email === '') { + errors['email'] = ['This field is required']; + } + if (data.invitationData.familyName === '') { + errors['familyName'] = ['This field is required']; + } + if (data.invitationData.givenName === '') { + errors['givenName'] = ['This field is required']; + } + + if (data.invitationData.responseDueDate === '') { + errors['responseDueDate'] = ['This field is required']; + } + + if (data.invitationData.reviewDueDate === '') { + errors['reviewDueDate'] = ['This field is required']; + } + + if (data.invitationData.reviewTypes === '') { + errors['reviewTypes'] = ['This field is required']; + } + + if (data.invitationData.emailComposer) { + Object.keys(data.invitationData.emailComposer).forEach( + (element) => { + if (data.invitationData.emailComposer[element] === '') { + errors['emailComposer'] = { + ...errors['emailComposer'], + [element]: ['This field is required'], + }; + } + }, + ); + } + + if (Object.keys(errors).length > 0) { + return HttpResponse.json({errors: errors}, {status: 422}); + } + + return HttpResponse.json({status: 201}); + }, + ), + http.post( + 'https://mock/index.php/publicknowledge/api/v1/invitations/15/invite', + () => { + return HttpResponse.json({}); + }, + ), + http.post( + 'https://mock/index.php/publicknowledge/api/v1/user/_invite', + () => { + return HttpResponse.json('invitation send successfully'); + }, + ), + ], + }, + }, + args: ReviewerPageInitConfigMock, +}; diff --git a/src/pages/userInvitation/UserInvitationPage.vue b/src/pages/userInvitation/UserInvitationPage.vue index 1dbdbdebc..1971d126f 100644 --- a/src/pages/userInvitation/UserInvitationPage.vue +++ b/src/pages/userInvitation/UserInvitationPage.vue @@ -91,6 +91,7 @@ import UserInvitationHeader from './UserInvitationHeader.vue'; import UserInvitationDetailsFormStep from './UserInvitationDetailsFormStep.vue'; import UserInvitationSearchFormStep from './UserInvitationSearchFormStep.vue'; import UserInvitationEmailComposerStep from './UserInvitationEmailComposerStep.vue'; +import ReviewerReviewDetailsStep from './ReviewerReviewDetailsStep.vue'; const props = defineProps({ /** steps for invite user */ @@ -140,5 +141,7 @@ const userInvitationComponents = { UserInvitationDetailsFormStep, UserInvitationSearchFormStep, UserInvitationEmailComposerStep, + ReviewerReviewDetailsStep, }; +console.log(props); diff --git a/src/pages/userInvitation/UserInvitationPageStore.js b/src/pages/userInvitation/UserInvitationPageStore.js index 4cd79e6fe..e55649880 100644 --- a/src/pages/userInvitation/UserInvitationPageStore.js +++ b/src/pages/userInvitation/UserInvitationPageStore.js @@ -7,6 +7,8 @@ import {useModal} from '@/composables/useModal'; export const useUserInvitationPageStore = defineComponentStore( 'userInvitationPage', (pageInitConfig) => { + const invitationUserRoleAssignment = 'userRoleAssignment'; + const invitationReviewerAccessInvite = 'reviewerAccess'; const {openDialog} = useModal(); const {t} = useLocalize(); /** @@ -26,6 +28,13 @@ export const useUserInvitationPageStore = defineComponentStore( const invitationUserData = ref(pageInitConfig.invitationUserData); const detectChanges = ref(false); + invitationPayload.value.submissionId && + (updatedPayload.value['submissionId'] = + invitationPayload.value.submissionId); + invitationPayload.value.reviewRoundId && + (updatedPayload.value['reviewRoundId'] = + invitationPayload.value.reviewRoundId); + function updatePayload(fieldName, value, initialValue = true) { invitationPayload.value[fieldName] = value; if (!initialValue) { @@ -76,6 +85,14 @@ export const useUserInvitationPageStore = defineComponentStore( return currentStepIndex.value === steps.value.length - 1; }); + const isUserRoleAssignment = computed(() => { + return invitationType.value === invitationUserRoleAssignment; + }); + + const isReviewerAccess = computed(() => { + return invitationType.value === invitationReviewerAccessInvite; + }); + /** * Add a step change to the browser history so the * user can use the browser's back button @@ -317,7 +334,7 @@ export const useUserInvitationPageStore = defineComponentStore( async (newVal, oldVal) => { isSubmitting.value = invitationPayload.value.disabled; if (invitationPayload.value.userGroupsToAdd.length === 0) { - isSubmitting.value = true; + // isSubmitting.value = true; } detectChanges.value = true; }, @@ -339,12 +356,21 @@ export const useUserInvitationPageStore = defineComponentStore( if (data.value) { openDialog({ title: t('userInvitation.modal.title'), - message: t('userInvitation.modal.message', { - email: invitationPayload.value.inviteeEmail, - }), + message: + invitationType.value === invitationUserRoleAssignment + ? t('userInvitation.modal.message', { + email: invitationPayload.value.inviteeEmail, + }) + : t('reviewerInvitation.modal.message', { + email: invitationPayload.value.inviteeEmail, + articleTitle: '', + }), actions: [ { - label: t('userInvitation.modal.button'), + label: + invitationType.value === invitationUserRoleAssignment + ? t('userInvitation.modal.button') + : t('reviewerInvitation.modal.button'), callback: (close) => { redirectToPage(); }, @@ -413,6 +439,8 @@ export const useUserInvitationPageStore = defineComponentStore( registerActionForStepId, emailTemplatesApiUrl, invitationUserData, + isUserRoleAssignment, + isReviewerAccess, currentStep, currentStepIndex, diff --git a/src/pages/userInvitation/UserInvitationUserGroupsTable.vue b/src/pages/userInvitation/UserInvitationUserGroupsTable.vue index f9fb380ed..80163785b 100644 --- a/src/pages/userInvitation/UserInvitationUserGroupsTable.vue +++ b/src/pages/userInvitation/UserInvitationUserGroupsTable.vue @@ -58,7 +58,16 @@ -