feat: sign in with email
This commit is contained in:
@@ -73,7 +73,7 @@
|
||||
<i class="material-icons">login</i>
|
||||
</button>
|
||||
<template slot="popover">
|
||||
<FirebaseLogin />
|
||||
<FirebaseLogin @show-email="showEmail = true" />
|
||||
</template>
|
||||
</v-popover>
|
||||
<v-popover v-else>
|
||||
@@ -160,6 +160,7 @@
|
||||
/>
|
||||
<AppShortcuts :show="showShortcuts" @hide-modal="showShortcuts = false" />
|
||||
<AppSupport :show="showSupport" @hide-modal="showSupport = false" />
|
||||
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -178,6 +179,7 @@ export default {
|
||||
showExtensions: false,
|
||||
showShortcuts: false,
|
||||
showSupport: false,
|
||||
showEmail: false,
|
||||
navigatorShare: navigator.share,
|
||||
fb,
|
||||
}
|
||||
|
||||
117
components/firebase/Email.vue
Normal file
117
components/firebase/Email.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<SmartModal v-if="show" @close="hideModal">
|
||||
<div slot="header">
|
||||
<div class="row-wrapper">
|
||||
<h3 class="title">{{ $t("login_with") }} {{ $t("email") }}</h3>
|
||||
<div>
|
||||
<button class="icon" @click="hideModal">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="body" class="flex flex-col">
|
||||
<label for="email"> E-mail </label>
|
||||
<input
|
||||
id="email"
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="you@mail.com"
|
||||
autocomplete="email"
|
||||
required
|
||||
spellcheck="false"
|
||||
autofocus
|
||||
@keyup.enter="signInWithEmail"
|
||||
/>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<div class="row-wrapper">
|
||||
<span></span>
|
||||
<span>
|
||||
<button v-if="signingInWithEmail" class="icon" type="button">
|
||||
{{ $t("loading") }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="rounded-md"
|
||||
:disabled="
|
||||
form.email.length !== 0
|
||||
? emailRegex.test(form.email)
|
||||
? false
|
||||
: true
|
||||
: true
|
||||
"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
@click="signInWithEmail"
|
||||
>
|
||||
{{ $t("send_magic_link") }}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SmartModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fb,
|
||||
form: {
|
||||
email: "",
|
||||
},
|
||||
signingInWithEmail: false,
|
||||
emailRegex:
|
||||
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this._keyListener = function (e) {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
this.hideModal()
|
||||
}
|
||||
}
|
||||
document.addEventListener("keydown", this._keyListener.bind(this))
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("keydown", this._keyListener)
|
||||
},
|
||||
methods: {
|
||||
async signInWithEmail() {
|
||||
this.signingInWithEmail = true
|
||||
const actionCodeSettings = {
|
||||
url: `${process.env.BASE_URL}/enter`,
|
||||
handleCodeInApp: true,
|
||||
}
|
||||
await fb
|
||||
.signInWithEmail(this.form.email, actionCodeSettings)
|
||||
.then(() => {
|
||||
this.$toast.success("Check your inbox", {
|
||||
icon: "person",
|
||||
})
|
||||
window.localStorage.setItem("emailForSignIn", this.form.email)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.error(error.message, {
|
||||
icon: "error",
|
||||
})
|
||||
this.signingInWithEmail = false
|
||||
})
|
||||
.finally(() => {
|
||||
this.signingInWithEmail = false
|
||||
})
|
||||
},
|
||||
hideModal() {
|
||||
this.$emit("hide-modal")
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -30,6 +30,12 @@
|
||||
<span>GitHub</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button v-close-popover class="icon" @click="$emit('show-email')">
|
||||
<i class="material-icons">mail</i>
|
||||
<span>{{ $t("email") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="info">{{ $t("login_first") }}</p>
|
||||
<FirebaseLogin />
|
||||
<FirebaseLogin @show-email="showEmail = true" />
|
||||
</div>
|
||||
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||
</AppSection>
|
||||
</template>
|
||||
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
fb,
|
||||
showEmail: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
@apply ease-in-out;
|
||||
@apply duration-150;
|
||||
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
@@ -66,6 +66,8 @@ export default {
|
||||
@apply bg-bgColor;
|
||||
@apply rounded-lg;
|
||||
@apply shadow-2xl;
|
||||
@apply border;
|
||||
@apply border-ttColor;
|
||||
|
||||
max-height: calc(100vh - 128px);
|
||||
max-width: 640px;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div v-else>
|
||||
<label>{{ $t("login_with") }}</label>
|
||||
<p>
|
||||
<FirebaseLogin />
|
||||
<FirebaseLogin @show-email="showEmail = true" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,6 +50,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||
</AppSection>
|
||||
</template>
|
||||
|
||||
@@ -67,6 +68,7 @@ export default {
|
||||
me: {},
|
||||
myTeams: [],
|
||||
fb,
|
||||
showEmail: false,
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
|
||||
@@ -327,6 +327,20 @@ export class FirebaseInstance {
|
||||
return await this.app.auth().fetchSignInMethodsForEmail(email)
|
||||
}
|
||||
|
||||
async signInWithEmail(email, actionCodeSettings) {
|
||||
return await this.app
|
||||
.auth()
|
||||
.sendSignInLinkToEmail(email, actionCodeSettings)
|
||||
}
|
||||
|
||||
async isSignInWithEmailLink(url) {
|
||||
return await this.app.auth().isSignInWithEmailLink(url)
|
||||
}
|
||||
|
||||
async signInWithEmailLink(email, url) {
|
||||
return await this.app.auth().signInWithEmailLink(email, url)
|
||||
}
|
||||
|
||||
async signOutUser() {
|
||||
if (!this.currentUser) throw new Error("No user has logged in")
|
||||
|
||||
|
||||
@@ -332,5 +332,6 @@
|
||||
"role_updated": "User role(s) updated successfully",
|
||||
"user_removed": "User removed successfully",
|
||||
"import_from_my_collections": "Import from My Collections",
|
||||
"export_as_json": "Export as JSON"
|
||||
"export_as_json": "Export as JSON",
|
||||
"send_magic_link": "Send a magic link to sign in"
|
||||
}
|
||||
|
||||
42
pages/enter.vue
Normal file
42
pages/enter.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="flex container flex-col min-h-screen">
|
||||
<span v-if="signingInWithEmail" class="info">{{ $t("loading") }}</span>
|
||||
<span v-else class="info">{{ $t("waiting_for_connection") }}</span>
|
||||
<pre v-if="error">{{ error }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fb } from "~/helpers/fb"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
signingInWithEmail: false,
|
||||
error: null,
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (fb.isSignInWithEmailLink(window.location.href)) {
|
||||
this.signingInWithEmail = true
|
||||
let email = window.localStorage.getItem("emailForSignIn")
|
||||
if (!email) {
|
||||
email = window.prompt("Please provide your email for confirmation")
|
||||
}
|
||||
await fb
|
||||
.signInWithEmailLink(email, window.location.href)
|
||||
.then(() => {
|
||||
window.localStorage.removeItem("emailForSignIn")
|
||||
this.$router.push({ path: "/" })
|
||||
})
|
||||
.catch((error) => {
|
||||
this.signingInWithEmail = false
|
||||
this.error = error.message
|
||||
})
|
||||
.finally(() => {
|
||||
this.signingInWithEmail = false
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -77,8 +77,8 @@
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<label for="name" class="text-sm">{{ $t("token_req_name") }}</label>
|
||||
<input id="name" name="name" type="text" v-model="name" class="text-sm" />
|
||||
<label for="request-name" class="text-sm">{{ $t("token_req_name") }}</label>
|
||||
<input id="request-name" name="request-name" type="text" v-model="name" class="text-sm" />
|
||||
</li>
|
||||
</ul>
|
||||
<div label="Request Body" v-if="['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)">
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<div v-else>
|
||||
<label>{{ $t("login_with") }}</label>
|
||||
<p>
|
||||
<FirebaseLogin />
|
||||
<FirebaseLogin @show-email="showEmail = true" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,6 +207,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</AppSection>
|
||||
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -244,6 +245,8 @@ export default Vue.extend({
|
||||
|
||||
EXTENSIONS_ENABLED: true,
|
||||
PROXY_ENABLED: true,
|
||||
|
||||
showEmail: false,
|
||||
}
|
||||
},
|
||||
subscriptions() {
|
||||
|
||||
Reference in New Issue
Block a user