feat: sign in with email
This commit is contained in:
@@ -73,7 +73,7 @@
|
|||||||
<i class="material-icons">login</i>
|
<i class="material-icons">login</i>
|
||||||
</button>
|
</button>
|
||||||
<template slot="popover">
|
<template slot="popover">
|
||||||
<FirebaseLogin />
|
<FirebaseLogin @show-email="showEmail = true" />
|
||||||
</template>
|
</template>
|
||||||
</v-popover>
|
</v-popover>
|
||||||
<v-popover v-else>
|
<v-popover v-else>
|
||||||
@@ -160,6 +160,7 @@
|
|||||||
/>
|
/>
|
||||||
<AppShortcuts :show="showShortcuts" @hide-modal="showShortcuts = false" />
|
<AppShortcuts :show="showShortcuts" @hide-modal="showShortcuts = false" />
|
||||||
<AppSupport :show="showSupport" @hide-modal="showSupport = false" />
|
<AppSupport :show="showSupport" @hide-modal="showSupport = false" />
|
||||||
|
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -178,6 +179,7 @@ export default {
|
|||||||
showExtensions: false,
|
showExtensions: false,
|
||||||
showShortcuts: false,
|
showShortcuts: false,
|
||||||
showSupport: false,
|
showSupport: false,
|
||||||
|
showEmail: false,
|
||||||
navigatorShare: navigator.share,
|
navigatorShare: navigator.share,
|
||||||
fb,
|
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>
|
<span>GitHub</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p class="info">{{ $t("login_first") }}</p>
|
<p class="info">{{ $t("login_first") }}</p>
|
||||||
<FirebaseLogin />
|
<FirebaseLogin @show-email="showEmail = true" />
|
||||||
</div>
|
</div>
|
||||||
|
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||||
</AppSection>
|
</AppSection>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fb,
|
fb,
|
||||||
|
showEmail: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default {
|
|||||||
@apply ease-in-out;
|
@apply ease-in-out;
|
||||||
@apply duration-150;
|
@apply duration-150;
|
||||||
|
|
||||||
background-color: rgba(255, 255, 255, 0.02);
|
background-color: rgba(0, 0, 0, 0.32);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-wrapper {
|
.modal-wrapper {
|
||||||
@@ -66,6 +66,8 @@ export default {
|
|||||||
@apply bg-bgColor;
|
@apply bg-bgColor;
|
||||||
@apply rounded-lg;
|
@apply rounded-lg;
|
||||||
@apply shadow-2xl;
|
@apply shadow-2xl;
|
||||||
|
@apply border;
|
||||||
|
@apply border-ttColor;
|
||||||
|
|
||||||
max-height: calc(100vh - 128px);
|
max-height: calc(100vh - 128px);
|
||||||
max-width: 640px;
|
max-width: 640px;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<label>{{ $t("login_with") }}</label>
|
<label>{{ $t("login_with") }}</label>
|
||||||
<p>
|
<p>
|
||||||
<FirebaseLogin />
|
<FirebaseLogin @show-email="showEmail = true" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||||
</AppSection>
|
</AppSection>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ export default {
|
|||||||
me: {},
|
me: {},
|
||||||
myTeams: [],
|
myTeams: [],
|
||||||
fb,
|
fb,
|
||||||
|
showEmail: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
|
|||||||
@@ -327,6 +327,20 @@ export class FirebaseInstance {
|
|||||||
return await this.app.auth().fetchSignInMethodsForEmail(email)
|
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() {
|
async signOutUser() {
|
||||||
if (!this.currentUser) throw new Error("No user has logged in")
|
if (!this.currentUser) throw new Error("No user has logged in")
|
||||||
|
|
||||||
|
|||||||
@@ -332,5 +332,6 @@
|
|||||||
"role_updated": "User role(s) updated successfully",
|
"role_updated": "User role(s) updated successfully",
|
||||||
"user_removed": "User removed successfully",
|
"user_removed": "User removed successfully",
|
||||||
"import_from_my_collections": "Import from My Collections",
|
"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>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label for="name" class="text-sm">{{ $t("token_req_name") }}</label>
|
<label for="request-name" class="text-sm">{{ $t("token_req_name") }}</label>
|
||||||
<input id="name" name="name" type="text" v-model="name" class="text-sm" />
|
<input id="request-name" name="request-name" type="text" v-model="name" class="text-sm" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div label="Request Body" v-if="['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)">
|
<div label="Request Body" v-if="['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)">
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<label>{{ $t("login_with") }}</label>
|
<label>{{ $t("login_with") }}</label>
|
||||||
<p>
|
<p>
|
||||||
<FirebaseLogin />
|
<FirebaseLogin @show-email="showEmail = true" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -207,6 +207,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppSection>
|
</AppSection>
|
||||||
|
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -244,6 +245,8 @@ export default Vue.extend({
|
|||||||
|
|
||||||
EXTENSIONS_ENABLED: true,
|
EXTENSIONS_ENABLED: true,
|
||||||
PROXY_ENABLED: true,
|
PROXY_ENABLED: true,
|
||||||
|
|
||||||
|
showEmail: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subscriptions() {
|
subscriptions() {
|
||||||
|
|||||||
Reference in New Issue
Block a user