Skip to content

Commit

Permalink
Add UI for email login
Browse files Browse the repository at this point in the history
  • Loading branch information
ml-evs committed Feb 20, 2024
1 parent 90d3c16 commit 757bb55
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 18 deletions.
100 changes: 100 additions & 0 deletions webapp/src/components/GetEmailModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<template>
<form @submit.prevent="submitForm" class="modal-enclosure">
<Modal
:modelValue="modelValue"
@update:modelValue="$emit('update:modelValue', $event)"
:disableSubmit="Boolean(successMessage) || !Boolean(emailAddress)"
>
<template v-slot:body>
<div class="form-row">
<div class="form-group">
<label for="sample-id" class="col-form-label"
>Enter your email address to receive a sign-in/registration link:</label
>
<input
v-model="emailAddress"
type="text"
class="form-control"
id="email-address"
required
/>
<div class="form-error">{{ emailValidationMessage }}</div>
<div class="form-success">{{ successMessage }}</div>
</div>
</div>
</template>
</Modal>
</form>
</template>

<script>
import Modal from "@/components/Modal.vue";
import { requestMagicLink } from "@/server_fetch_utils.js";
export default {
name: "CreateSampleModal",
data() {
return {
emailAddress: null,
emailSent: false,
emailError: null,
emailValidationMessage: "",
successMessage: "",
};
},
props: {
modelValue: Boolean,
},
emits: ["update:modelValue"],
methods: {
async submitForm() {
this.validateEmail();
if (this.emailValidationMessage) {
return;
}
let response = await requestMagicLink(this.emailAddress);
if (response.status == "success") {
this.emailSent = true;
this.successMessage =
"Email sent! Please check your inbox (and spam folder) for a link to sign in or register. It may take a few minutes to arrive.";
} else {
this.emailError = true;
this.emailValidationMessage =
"Email failed to send; please verify the address you entered and try again.";
}
},
validateEmail() {
if (this.emailAddress == null) {
this.emailValidationMessage = "Please enter your email address.";
} else if (!/^\S+@\S+\.\S+$/.test(this.emailAddress)) {
this.emailValidationMessage =
"Email address appears to be invalid; please verify and contact us if this error persists.";
} else {
this.emailValidationMessage = "";
}
},
},
components: {
Modal,
},
};
</script>

<style scoped>
.form-error {
color: red;
}
.form-success {
color: dodgerblue;
}
:deep(.form-error a) {
color: #820000;
font-weight: 600;
}
.modal-enclosure :deep(.modal-content) {
max-height: 90vh;
overflow: auto;
scroll-behavior: smooth;
}
</style>
34 changes: 16 additions & 18 deletions webapp/src/components/LoginDetails.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div class="row justify-content-center pt-3">
<GetEmailModal v-model="emailModalIsOpen" />
<template v-if="currentUser != null">
<div class="dropdown">
<button
Expand Down Expand Up @@ -48,7 +49,7 @@
aria-expanded="false"
@click="isLoginDropdownVisible = !isLoginDropdownVisible"
>
<font-awesome-icon icon="sign-in-alt" />&nbsp;Login
<font-awesome-icon icon="sign-in-alt" />&nbsp;Login/Register
</button>
<div
class="dropdown-menu"
Expand All @@ -70,13 +71,14 @@
:href="this.apiUrl + '/login/orcid'"
><font-awesome-icon class="orcid-icon" :icon="['fab', 'orcid']" /> Login via ORCID</a
>
<a
<button
type="button"
class="disabled dropdown-item btn login btn-link btn-default"
class="dropdown-item btn login btn-link btn-default"
aria-label="Login via email"
:href="this.apiUrl + '/login/email'"
><font-awesome-icon :icon="['fa', 'envelope']" /> Login via email</a
@click="emailModalIsOpen = true"
>
<font-awesome-icon :icon="['fa', 'envelope']" /> Login via email
</button>
</div>
</div>
</template>
Expand All @@ -87,34 +89,30 @@
import { API_URL } from "@/resources.js";
import UserBubble from "@/components/UserBubble.vue";
import { getUserInfo } from "@/server_fetch_utils.js";
import GetEmailModal from "@/components/GetEmailModal.vue";
export default {
data() {
return {
isLoginDropdownVisible: false,
isUserDropdownVisible: false,
emailModalIsOpen: false,
apiUrl: API_URL,
currentUser: null,
currentUserInfo: {},
};
},
components: {
UserBubble,
},
props: {
modelValue: Boolean,
},
watch: {
modelValue(newValue) {
if (newValue) {
this.openModal();
}
if (!newValue) {
this.closeModal();
}
},
GetEmailModal,
},
methods: {
async getUser() {
// Need to reload the page if this is a magic-link login
let token = this.$route.query.token;
if (token != null) {
window.location.href = this.apiUrl + "/login/email?token=" + token;
}
let user = await getUserInfo();
if (user != null) {
this.currentUser = user.display_name;
Expand Down
13 changes: 13 additions & 0 deletions webapp/src/server_fetch_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,19 @@ export async function getUserInfo() {
});
}

export async function requestMagicLink(email_address) {
return fetch_post(`${API_URL}/login/magic-link`, {
email: email_address,
referrer: window.location.origin,
})
.then((response_json) => {
return response_json;
})
.catch((err) => {
return err;
});
}

export function searchUsers(query, nresults = 100) {
// construct a url with parameters:
var url = new URL(`${API_URL}/search-users/`);
Expand Down

0 comments on commit 757bb55

Please sign in to comment.