Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request User Emails for Certain Site Features #181

Merged
merged 11 commits into from
May 7, 2024
135 changes: 135 additions & 0 deletions src/components/common/EmailPrompt.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<template>
<Dialog v-model:visible="visible" :closable="false" :header=title :style="{ width: '25rem' }">
<span class="p-text-secondary block mb-5">{{ dialog }}</span>
<div class="flex align-items-center gap-3 mb-3">
<label for="email" class="font-semibold w-6rem">Email</label>
<InputText v-model:model-value="email" id="email" class="flex-auto" />
bencap marked this conversation as resolved.
Show resolved Hide resolved
</div>
<div><span v-if="emailValidationError" class="mave-field-error">{{ emailValidationError }}</span></div>
<div class="flex justify-content-end gap-2">
<Button type="button" label="Ignore" severity="secondary" @click="ignoreEmail"></Button>
bencap marked this conversation as resolved.
Show resolved Hide resolved
<Button type="button" label="Save" @click="saveEmail"></Button>
bencap marked this conversation as resolved.
Show resolved Hide resolved
</div>
</Dialog>
</template>

<script>
import Button from 'primevue/button'
import Dialog from 'primevue/dialog'
import InputText from 'primevue/inputtext'

import useItem from '@/composition/item'
import { ref } from 'vue'

export default {
components: { Button, Dialog, InputText },

props: {
title: {
type: String,
required: false,
default: "Add User Email"
},
dialog: {
type: String,
required: false,
default: "Your email is required to use this feature. Please add it below"
bencap marked this conversation as resolved.
Show resolved Hide resolved
},
isFirstLoginPrompt: {
type: Boolean,
required: true
}
},

setup: () => {
const { item: user, setItemId: setUserId, saveItem: saveUser } = useItem({ itemTypeName: 'me' })
const visible = ref(false);

return {
user,
setUserId,
saveUser,
visible,
}
},

data: function() {
return {
email: null,
emailValidationError: null
}
},

watch: {
user: {
handler: function() {
this.email = this.user?.email

if (this.$props.isFirstLoginPrompt) {
this.visible = this.user.isFirstLogin && this.user.email == null
}
else {
this.visible = this.user.email == null
}
}
}
},

mounted: function() {
this.setUserId('me')
},

methods: {
saveEmail: async function() {
const email = this.email ? this.email.trim() : null

if (!email) {
this.emailValidationError = "Email cannot be empty."
}

if (this.user && email && email != this.user.email) {
let errorResponse = null
try {
await this.saveUser({
item: {
...this.user,
email
}
})
} catch (error) {
errorResponse = error.response
}

if (errorResponse) {
if (typeof errorResponse.data.detail === 'string' || errorResponse.data.detail instanceof String) {
// Handle generic errors that are not surfaced by the API as objects
this.$toast.add({ severity: 'error', summary: `Encountered an error saving email: ${errorResponse.data.detail}` })
}
else {
// There will only be one validation error surfaced for emails.
this.emailValidationError = errorResponse.data.detail[0].msg
}
}
else {
this.emailValidationError = null
this.user.email = email
this.$toast.add({ severity: 'success', summary: `Your email was succesfully updated.`, life: 3000 })
this.visible = false
}
}
},

ignoreEmail: async function() {
bencap marked this conversation as resolved.
Show resolved Hide resolved
await this.saveUser({
item: {
...this.user
}
})
this.visible = false
}
}
}

</script>

<style scoped src="../../assets/forms.css"></style>
8 changes: 7 additions & 1 deletion src/components/layout/DefaultLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

<div class="mavedb-default-layout mavedb-fill-parent">
<Toolbar />
<EmailPrompt
title="Welcome to MaveDB!"
dialog="This appears to be your first time logging into MaveDB. Your email is required for certain site features, such as creating an experiment or score set. You may add it now, or ignore this message and add it prior to uploading data."
bencap marked this conversation as resolved.
Show resolved Hide resolved
:isFirstLoginPrompt="true"
/>
<div :class="wrapperClasses">
<div :class="mainClasses">
<slot />
Expand All @@ -14,11 +19,12 @@
<script>

import Toolbar from '@/components/layout/Toolbar'
import EmailPrompt from '@/components/common/EmailPrompt.vue'
import '@fontsource/raleway'
import '@/assets/app.css'

export default {
components: {Toolbar},
components: {Toolbar, EmailPrompt},

props: {
height: {
Expand Down
31 changes: 21 additions & 10 deletions src/components/screens/ExperimentEditor.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template>
<DefaultLayout>
<EmailPrompt
dialog="Your email is required to create or edit an experiment. Please add it below."
bencap marked this conversation as resolved.
Show resolved Hide resolved
:isFirstLoginPrompt="false"
/>
{{ experimentSetUrn }}
<div class="mave-experiment-editor">
<div class="grid">
Expand Down Expand Up @@ -243,6 +247,7 @@ import TabView from 'primevue/tabview'
import Textarea from 'primevue/textarea'

import DefaultLayout from '@/components/layout/DefaultLayout'
import EmailPrompt from '@/components/common/EmailPrompt'
import useItem from '@/composition/item'
import useItems from '@/composition/items'
import config from '@/config'
Expand All @@ -251,7 +256,7 @@ import useFormatters from '@/composition/formatters'

export default {
name: 'ExperimentEditor',
components: { AutoComplete, Button, Card, Chips, Multiselect, DefaultLayout, FileUpload, InputText, ProgressSpinner, TabPanel, TabView, Textarea },
components: { AutoComplete, Button, Card, Chips, Multiselect, DefaultLayout, EmailPrompt, FileUpload, InputText, ProgressSpinner, TabPanel, TabView, Textarea },

setup: () => {
const publicationIdentifierSuggestions = useItems({itemTypeName: 'publication-identifier-search'})
Expand Down Expand Up @@ -565,17 +570,23 @@ export default {
this.$toast.add({severity:'success', summary: 'The new experiment was saved.', life: 3000})
}
} else if (response.data && response.data.detail) {
const formValidationErrors = {}
for (const error of response.data.detail) {
let path = error.loc
if (path[0] == 'body') {
path = path.slice(1)
if (typeof response.data.detail === 'string' || response.data.detail instanceof String) {
// Handle generic errors that are not surfaced by the API as objects
this.$toast.add({ severity: 'error', summary: `Encountered an error saving experiment: ${response.data.detail}` })
}
else {
const formValidationErrors = {}
for (const error of response.data.detail) {
let path = error.loc
if (path[0] == 'body') {
path = path.slice(1)
}
path = path.join('.')
formValidationErrors[path] = error.msg
}
path = path.join('.')
formValidationErrors[path] = error.msg
this.serverSideValidationErrors = formValidationErrors
this.mergeValidationErrors()
}
this.serverSideValidationErrors = formValidationErrors
this.mergeValidationErrors()
}
},

Expand Down
13 changes: 9 additions & 4 deletions src/components/screens/ScoreSetEditor.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<template>
<EmailPrompt
dialog="Your email is required to create or edit an experiment. Please add it below."
bencap marked this conversation as resolved.
Show resolved Hide resolved
:isFirstLoginPrompt="false"
/>
<DefaultLayout>
<div class="mave-score-set-editor">
<div class="grid">
Expand Down Expand Up @@ -325,11 +329,11 @@
<div class="field">
<span class="p-float-label">
<AutoComplete
ref="taxonomyInput"
v-model="taxonomy"
ref="taxonomyInput"
v-model="taxonomy"
dropdown
:id="$scopedId('input-targetSequenceTaxonomy')"
:suggestions="taxonomySuggestionsList"
:suggestions="taxonomySuggestionsList"
field="organismName"
:multiple="false"
:options="taxonomies"
Expand Down Expand Up @@ -608,6 +612,7 @@ import { ref } from 'vue'

import EntityLink from '@/components/common/EntityLink'
import DefaultLayout from '@/components/layout/DefaultLayout'
import EmailPrompt from '@/components/common/EmailPrompt'
import useItem from '@/composition/item'
import useItems from '@/composition/items'
import config from '@/config'
Expand Down Expand Up @@ -641,7 +646,7 @@ function emptyTargetGene() {

export default {
name: 'ScoreSetEditor',
components: { AutoComplete, Button, Card, Chips, Column, DataTable, DefaultLayout, Dropdown, EntityLink, FileUpload, InputNumber, InputText, Message, Multiselect, ProgressSpinner, SelectButton, TabPanel, TabView, Textarea },
components: { AutoComplete, Button, Card, Chips, Column, DataTable, DefaultLayout, Dropdown, EmailPrompt, EntityLink, FileUpload, InputNumber, InputText, Message, Multiselect, ProgressSpinner, SelectButton, TabPanel, TabView, Textarea },

setup: () => {
const editableExperiments = useItems({
Expand Down
41 changes: 34 additions & 7 deletions src/components/screens/SettingsScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<label :for="$scopedId('input-email')">Email</label>
</span>
</div>
<div>
<span v-if="emailValidationError" class="mave-field-error">{{ emailValidationError }}</span>
</div>
</template>
<template #footer>
<Button :disabled="!user || email == user.email" icon="pi pi-check" label="Save" @click="saveEmail" />
Expand Down Expand Up @@ -139,7 +142,8 @@ export default {

data: function() {
return {
email: null
email: null,
emailValidationError: null
}
},

Expand Down Expand Up @@ -174,15 +178,36 @@ export default {
cancelEmailEditing: function() {
this.email = this.user?.email
},
saveEmail: function() {
saveEmail: async function() {
const email = this.email ? this.email.trim() : null
if (this.user && email && email != this.user.email) {
this.saveUser({
item: {
...this.user,
email
let errorResponse = null
try {
await this.saveUser({
item: {
...this.user,
email
}
})
} catch (error) {
errorResponse = error.response
}

if (errorResponse) {
if (typeof errorResponse.data.detail === 'string' || errorResponse.data.detail instanceof String) {
// Handle generic errors that are not surfaced by the API as objects
this.$toast.add({ severity: 'error', summary: `Encountered an error saving email: ${errorResponse.data.detail}` })
}
})
else {
// There will only be one validation error surfaced for emails.
this.emailValidationError = errorResponse.data.detail[0].msg
}
}
else {
this.emailValidationError = null
this.user.email = email
this.$toast.add({ severity: 'success', summary: `Your email was succesfully updated.`, life: 3000 })
}
}
},
setActiveRoles: function(newRoles) {
Expand Down Expand Up @@ -230,6 +255,8 @@ export default {

</script>

<style scoped src="../../assets/forms.css"></style>

<style scoped>
.p-card {
margin-bottom: 1em;
Expand Down