Skip to content

Commit

Permalink
accept invitations (#358)
Browse files Browse the repository at this point in the history
  • Loading branch information
ipula authored Jun 27, 2024
1 parent ee1952c commit 6940ab2
Show file tree
Hide file tree
Showing 15 changed files with 1,225 additions and 7 deletions.
18 changes: 11 additions & 7 deletions public/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,14 +341,14 @@ window.pkp = {
'form.saved': 'Saved',
'grid.action.sort': 'Sort',
'help.help': 'Help',
'invitation.orcid.message': '##invitation.orcid.message##',
'invitation.role.addRole.button': '##invitation.role.addRole.button##',
'invitation.role.dateEnd': '##invitation.role.dateEnd##',
'invitation.role.dateStart': '##invitation.role.dateStart##',
'invitation.role.masthead': '##invitation.role.masthead##',
'invitation.orcid.message': 'On accepting the invite, the user will redirected to ORCID to verify their account, if the wish to',
'invitation.role.addRole.button': 'Add Another Role',
'invitation.role.dateEnd': 'End Date',
'invitation.role.dateStart': 'Start Date',
'invitation.role.masthead': 'Journal Masthead',
'invitation.role.removeRole.button':
'##invitation.role.removeRole.button##',
'invitation.role.selectRole': '##invitation.role.selectRole##',
'Remove Role',
'invitation.role.selectRole': 'Select a new role',
'invitation.wizard.completeSteps': '##invitation.wizard.completeSteps##',
'issue.issue': 'Issue',
'list.collapseAll': 'Collapse all',
Expand Down Expand Up @@ -530,6 +530,10 @@ window.pkp = {
'user.orcid': 'ORCID iD',
'user.username': 'Username',
'validator.required': 'This field is required.',
'invitation.notification.closeBtn':'View all users',
'user.password': 'Password',
'invitation.orcid.acceptInvitation.message':'Not verified. You can verify your ORCID iD from your profile section in OJS',

},

tinyMCE: {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Container/PageOJS.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import Page from '@/components/Container/Page.vue';
import DashboardPage from '@/pages/dashboard/DashboardPage.vue';
import UserInvitationPage from '@/pages/userInvitation/UserInvitationPage.vue';
import AcceptInvitationPage from '@/pages/acceptInvitation/AcceptInvitationPage.vue';
export default {
components: {
DashboardPage,
UserInvitationPage,
AcceptInvitationPage,
},
extends: Page,
};
Expand Down
1 change: 1 addition & 0 deletions src/composables/useForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function useForm(_form) {
watch(
errors,
(newErrors) => {
form.value.errors = {};
Object.keys(newErrors).forEach((key) => {
if (doesFieldExist(form.value, key)) {
form.value.errors[key] = newErrors[key];
Expand Down
17 changes: 17 additions & 0 deletions src/pages/acceptInvitation/AcceptInvitationHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<h1 ref="wrapper" class="app__pageHeading">
{{ pageTitle }}
</h1>
<p>
{{ pageTitleDescription }}
</p>
</template>

<script setup>
import {defineProps} from 'vue';
defineProps({
pageTitle: {type: String, required: true},
pageTitleDescription: {type: String, required: true},
});
</script>
9 changes: 9 additions & 0 deletions src/pages/acceptInvitation/AcceptInvitationPage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Primary, Controls, Stories, Meta, ArgTypes} from '@storybook/blocks';

import * as AcceptInvitationPage from './AcceptInvitationPage.stories.js';

<Meta of={AcceptInvitationPage} />

# Accept Invitation page

<ArgTypes />
70 changes: 70 additions & 0 deletions src/pages/acceptInvitation/AcceptInvitationPage.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import AcceptInvitationPage from './AcceptInvitationPage.vue';
import {http, HttpResponse} from 'msw';
import PageInitConfigMock from './mocks/pageInitConfig';
import invitationMock from './mocks/invitationMock';

export default {
title: 'Pages/AcceptInvitation',
component: AcceptInvitationPage,
};

export const Init = {
render: (args) => ({
components: {AcceptInvitationPage},
setup() {
return {args};
},
template: '<AcceptInvitationPage v-bind="args" />',
}),
parameters: {
msw: {
handlers: [
http.get(
'https://mock/index.php/publicknowledge/api/v1/invitations/65/key/8aqc3W',
async (r) => {
return HttpResponse.json(invitationMock, {status: 200});
},
),
http.post(
'https://mock/index.php/publicknowledge/api/v1/invitations/65/key/8aqc3W/refine',
async ({request}) => {
const postBody = await request.json();
let errors = {};

if (!Object.keys(postBody).includes('username')) {
errors['username'] = ['This field is required'];
}
if (!Object.keys(postBody).includes('password')) {
errors['password'] = ['This field is required'];
}
if (!Object.keys(postBody).includes('affiliation')) {
errors['affiliation'] = ['This field is required'];
}
if (!Object.keys(postBody).includes('country')) {
errors['country'] = ['This field is required'];
}
Object.keys(postBody).forEach((element) => {
if (element !== 'orcid') {
if (postBody[element] === '') {
errors[element] = ['This field is required'];
}
}
});

if (Object.keys(errors).length > 0) {
return HttpResponse.json(errors, {status: 422});
}
return HttpResponse.json(postBody, {status: 200});
},
),
http.post(
'https://mock/index.php/publicknowledge/api/v1/invitations/65/key/8aqc3W/finalize',
async (r) => {
return HttpResponse.json(invitationMock, {status: 201});
},
),
],
},
},
args: PageInitConfigMock,
};
182 changes: 182 additions & 0 deletions src/pages/acceptInvitation/AcceptInvitationPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<template>
<div class="userInvitation">
<AcceptInvitationHeader
:page-title="store.pageTitle"
:page-title-description="store.pageTitleDescription"
/>
<steps
v-if="store.steps.length"
class="userInvitation__steps"
:current="store.currentStep.id"
:started-steps="store.startedSteps"
:label="t('invitation.wizard.completeSteps')"
:progress-label="t('common.showingSteps')"
:show-steps-label="t('common.showingSteps')"
:scroll-to="wrapper"
@step:open="store.openStep"
>
<step
v-for="step in store.steps"
:id="step.id"
:key="step.id"
:label="step.name"
>
<panel class="decision__stepPanel">
<panel-section class="decision__stepHeader">
<h2>{{ store.stepTitle }}</h2>
<p class="error">{{ store.errors.error }}</p>
<p>{{ step.description }}</p>
</panel-section>
<panel-section v-for="section in step.sections" :key="section.id">
<template v-if="step.type === 'review'">
<notification
v-if="Object.keys(store.errors).length > 0"
type="warning"
>
{{ t('invitation.wizard.errors') }}
</notification>
</template>
<component
:is="acceptInvitationComponents[section.sectionComponent]"
:key="section.sectionComponent"
v-bind="section.props"
/>
</panel-section>
</panel>
</step>
</steps>
<button-row class="panel panel--wide userInvitationForm__footer">
<pkp-button @click="store.cancel">Cancel</pkp-button>
<pkp-button v-if="!store.isOnFirstStep" @click="store.previousStep">
Previous
</pkp-button>
<pkp-button
v-if="!store.isOnFirstStep"
:is-primary="true"
@click="store.nextStep"
>
{{ store.stepButtonTitle }}
</pkp-button>
</button-row>
</div>
</template>

<script setup>
import ButtonRow from '@/components/ButtonRow/ButtonRow.vue';
import PkpButton from '@/components/Button/Button.vue';
import Panel from '@/components/Panel/Panel.vue';
import PanelSection from '@/components/Panel/PanelSection.vue';
import Steps from '@/components/Steps/Steps.vue';
import Step from '@/components/Steps/Step.vue';
import {useAcceptInvitationPageStore} from './AcceptInvitationPageStore';
import {useTranslation} from '@/composables/useTranslation';
import {ref} from 'vue';
import AcceptInvitationHeader from './AcceptInvitationHeader.vue';
import AcceptInvitationUserDetailsForms from './AcceptInvitationUserDetailsForms.vue';
import AcceptInvitationUserAccountDetails from './AcceptInvitationUserAccountDetails.vue';
import AcceptInvitationReview from './AcceptInvitationReview.vue';
import AcceptInvitationVerifyOrcid from './AcceptInvitationVerifyOrcid.vue';
const props = defineProps({
/** steps for invite user */
steps: {
type: Array,
required: true,
},
/** primary language */
primaryLocale: {
type: String,
required: true,
},
pageTitle: {
type: String,
required: true,
},
pageTitleDescription: {
type: String,
required: true,
},
/** valid invitation id */
invitationId: {
type: Number,
required: true,
},
/** valid invitation key */
invitationKey: {
type: String,
required: true,
},
});
const {t} = useTranslation();
const wrapper = ref(null);
const store = useAcceptInvitationPageStore(props);
const acceptInvitationComponents = {
AcceptInvitationUserDetailsForms,
AcceptInvitationUserAccountDetails,
AcceptInvitationVerifyOrcid,
AcceptInvitationReview,
};
</script>

<style lang="less">
@import '../../styles/_import';
.userInvitation .app__pageHeading {
display: flex;
margin: 2rem 0 0.25rem;
> .pkpButton {
margin-inline-start: auto;
}
}
.userInvitation .pkpSteps__buttonWrapper {
border: @bg-border-light;
border-bottom: 0;
border-top-left-radius: @radius;
border-top-right-radius: @radius;
}
.userInvitation__submissionDetails,
.userInvitation__submissionConfiguration {
font-size: @font-sml;
line-height: @line-sml;
}
// Override the form locale switcher styles
.userInvitation__stepForm .pkpFormLocales {
border: none;
margin-top: -1rem;
margin-bottom: 1rem;
padding-right: 0;
.pkpFormLocales__locale--isPrimary {
border: none;
}
}
// Hide the form footer for each form, since
// buttons and errors are handled separately
.userInvitation__stepForm .pkpFormPage__footer {
display: none;
}
// buttons and errors are handled separately
.userInvitationForm__footer {
padding: 2rem;
display: flex;
justify-content: flex-end;
align-items: center;
border: none;
* + .pkpButton {
margin-inline-start: 0.5rem;
}
}
.error {
color: red;
font-weight: bold;
}
</style>
Loading

0 comments on commit 6940ab2

Please sign in to comment.