api-client/components/firebase/Login.vue

352 lines
10 KiB
Vue
Raw Normal View History

2020-01-20 16:55:48 +00:00
<template>
<SmartModal
v-if="show"
:title="$t('login_to_hoppscotch')"
dialog
@close="hideModal"
>
<template #body>
2021-08-15 09:48:04 +00:00
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2 px-2">
<SmartItem
:loading="signingInWithGoogle"
svg="google"
label="Continue with Google"
@click.native="signInWithGoogle"
/>
<SmartItem
:loading="signingInWithGitHub"
svg="github"
label="Continue with GitHub"
@click.native="signInWithGithub"
/>
<SmartItem
icon="mail"
label="Continue with Email"
@click.native="mode = 'email'"
/>
</div>
<div v-if="mode === 'email'" class="flex flex-col space-y-2">
2021-08-07 09:21:13 +00:00
<div class="flex items-center relative">
<input
id="email"
v-model="form.email"
v-focus
2021-08-07 09:21:13 +00:00
class="input floating-input"
placeholder=" "
type="email"
name="email"
autocomplete="email"
required
spellcheck="false"
autofocus
@keyup.enter="signInWithEmail"
/>
2021-08-07 09:21:13 +00:00
<label for="email">
{{ $t("auth.email") }}
</label>
</div>
2021-07-03 13:14:58 +00:00
<ButtonPrimary
:loading="signingInWithEmail"
:disabled="
form.email.length !== 0
? emailRegex.test(form.email)
? false
: true
: true
"
type="button"
2021-08-02 15:27:18 +00:00
:label="$t('auth.send_magic_link')"
2021-07-03 13:14:58 +00:00
@click.native="signInWithEmail"
/>
</div>
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
2021-07-17 17:40:28 +00:00
<div class="flex flex-col max-w-md justify-center items-center">
<i class="text-accent material-icons !text-4xl">
mark_email_unread
</i>
<h3 class="my-2 text-center text-lg">
2021-08-02 15:27:18 +00:00
{{ $t("auth.we_sent_magic_link") }}
2021-07-03 13:14:58 +00:00
</h3>
<p class="text-center">
2021-08-02 15:27:18 +00:00
{{
$t("auth.we_sent_magic_link_description", { email: form.email })
}}
2021-07-03 13:14:58 +00:00
</p>
</div>
</div>
</template>
<template #footer>
<p v-if="mode === 'sign-in'" class="text-secondaryLight">
By signing in, you are agreeing to our
2021-08-06 16:10:26 +00:00
<SmartAnchor
class="link"
to="https://github.com/hoppscotch/hoppscotch/wiki/Terms-&-Conditions"
blank
label="Terms of Service"
/>
and
2021-08-06 16:10:26 +00:00
<SmartAnchor
class="link"
to="https://github.com/hoppscotch/hoppscotch/wiki/Privacy-Policy"
blank
label="Privacy Policy"
/>.
</p>
<p v-if="mode === 'email'" class="text-secondaryLight">
<SmartAnchor
class="link"
label="← All sign in options"
@click.native="mode = 'sign-in'"
/>
</p>
<p
v-if="mode === 'email-sent'"
class="flex flex-1 text-secondaryLight justify-between"
>
<SmartAnchor
class="link"
label="← Re-enter email"
@click.native="mode = 'email'"
/>
2021-08-02 15:27:18 +00:00
<SmartAnchor
class="link"
:label="$t('action.dismiss')"
@click.native="hideModal"
/>
</p>
</template>
</SmartModal>
2020-01-20 16:55:48 +00:00
</template>
<script>
2021-06-14 04:07:30 +00:00
import { applySetting } from "~/newstore/settings"
import {
signInUserWithGoogle,
getSignInMethodsForEmail,
signInWithEmailAndPassword,
2021-06-14 12:35:53 +00:00
signInUserWithGithub,
2021-06-14 04:07:30 +00:00
setProviderInfo,
2021-07-03 13:14:58 +00:00
currentUser$,
signInWithEmail,
2021-06-14 04:07:30 +00:00
} from "~/helpers/fb/auth"
2021-07-03 13:14:58 +00:00
import { setLocalConfig } from "~/newstore/localpersistence"
2021-08-12 08:14:10 +00:00
import { useStreamSubscriber } from "~/helpers/utils/composables"
2020-01-20 16:55:48 +00:00
export default {
props: {
show: Boolean,
},
2021-08-12 08:14:10 +00:00
setup() {
const { subscribeToStream } = useStreamSubscriber()
return {
subscribeToStream,
}
},
2021-07-03 13:14:58 +00:00
data() {
return {
form: {
email: "",
},
signingInWithGoogle: false,
signingInWithGitHub: false,
2021-07-03 13:14:58 +00:00
signingInWithEmail: false,
emailRegex:
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
mode: "sign-in",
}
},
mounted() {
2021-08-12 08:14:10 +00:00
this.subscribeToStream(currentUser$, (user) => {
2021-07-03 13:14:58 +00:00
if (user) this.hideModal()
})
},
2020-01-20 16:55:48 +00:00
methods: {
showLoginSuccess() {
this.$toast.info(this.$t("login_success"), {
icon: "vpn_key",
})
},
async signInWithGoogle() {
this.signingInWithGoogle = true
try {
2021-06-14 04:07:30 +00:00
const { additionalUserInfo } = await signInUserWithGoogle()
if (additionalUserInfo.isNewUser) {
2021-08-02 15:27:18 +00:00
this.$toast.info(`${this.$t("action.turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
2021-05-18 06:26:59 +00:00
onClick: (_, toastObject) => {
2021-06-14 04:07:30 +00:00
applySetting("syncHistory", true)
applySetting("syncCollections", true)
applySetting("syncEnvironments", true)
toastObject.remove()
2020-02-24 18:44:50 +00:00
},
},
})
}
this.showLoginSuccess()
} catch (e) {
console.error(e)
// An error happened.
if (e.code === "auth/account-exists-with-different-credential") {
// Step 2.
// User's email already exists.
// The pending Google credential.
const pendingCred = e.credential
// The provider account's email address.
const email = e.email
// Get sign-in methods for this email.
2021-06-14 04:07:30 +00:00
const methods = await getSignInMethodsForEmail(email)
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === "password") {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
2021-06-14 04:07:30 +00:00
const user = await signInWithEmailAndPassword(email, password)
await user.linkWithCredential(pendingCred)
2020-12-08 12:20:32 +00:00
this.showLoginSuccess()
return
2020-02-28 00:05:28 +00:00
}
2021-08-02 15:27:18 +00:00
this.$toast.info(`${this.$t("auth.account_exists")}`, {
icon: "vpn_key",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
2021-05-18 06:26:59 +00:00
onClick: async (_, toastObject) => {
2021-06-14 04:07:30 +00:00
const { user } = await signInWithGithub()
await user.linkAndRetrieveDataWithCredential(pendingCred)
this.showLoginSuccess()
toastObject.remove()
},
},
})
}
}
this.signingInWithGoogle = false
2020-01-20 16:55:48 +00:00
},
async signInWithGithub() {
this.signingInWithGitHub = true
try {
2021-06-14 04:07:30 +00:00
const { credential, additionalUserInfo } = await signInUserWithGithub()
2020-12-07 08:44:02 +00:00
2021-06-14 04:07:30 +00:00
setProviderInfo(credential.providerId, credential.accessToken)
if (additionalUserInfo.isNewUser) {
2021-08-02 15:27:18 +00:00
this.$toast.info(`${this.$t("action.turn_on")} ${this.$t("sync")}`, {
icon: "sync",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
2021-05-18 06:26:59 +00:00
onClick: (_, toastObject) => {
2021-06-14 04:07:30 +00:00
applySetting("syncHistory", true)
applySetting("syncCollections", true)
applySetting("syncEnvironments", true)
toastObject.remove()
2020-02-24 18:44:50 +00:00
},
},
})
}
this.showLoginSuccess()
} catch (e) {
console.error(e)
// An error happened.
if (e.code === "auth/account-exists-with-different-credential") {
// Step 2.
// User's email already exists.
// The pending Google credential.
const pendingCred = e.credential
// The provider account's email address.
const email = e.email
// Get sign-in methods for this email.
2021-06-14 04:07:30 +00:00
const methods = await getSignInMethodsForEmail(email)
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === "password") {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
2021-06-14 04:07:30 +00:00
const user = await signInWithEmailAndPassword(email, password)
await user.linkWithCredential(pendingCred)
this.showLoginSuccess()
return
2020-02-28 00:05:28 +00:00
}
2021-08-02 15:27:18 +00:00
this.$toast.info(`${this.$t("auth.account_exists")}`, {
icon: "vpn_key",
duration: null,
closeOnSwipe: false,
action: {
text: this.$t("yes"),
2021-05-18 06:26:59 +00:00
onClick: async (_, toastObject) => {
2021-06-14 04:07:30 +00:00
const { user } = await signInUserWithGoogle()
// TODO: handle deprecation
await user.linkAndRetrieveDataWithCredential(pendingCred)
this.showLoginSuccess()
toastObject.remove()
},
},
})
}
}
this.signingInWithGitHub = false
2020-02-24 18:44:50 +00:00
},
2021-07-03 13:14:58 +00:00
async signInWithEmail() {
this.signingInWithEmail = true
2021-07-03 13:14:58 +00:00
const actionCodeSettings = {
url: `${process.env.BASE_URL}/enter`,
handleCodeInApp: true,
}
await signInWithEmail(this.form.email, actionCodeSettings)
.then(() => {
this.mode = "email-sent"
setLocalConfig("emailForSignIn", this.form.email)
})
.catch((e) => {
console.error(e)
2021-07-03 13:14:58 +00:00
this.$toast.error(error.message, {
icon: "error",
})
this.signingInWithEmail = false
})
.finally(() => {
this.signingInWithEmail = false
})
},
hideModal() {
this.mode = "sign-in"
this.$toast.clear()
this.$emit("hide-modal")
},
2020-02-24 18:44:50 +00:00
},
}
2020-01-20 16:55:48 +00:00
</script>