Files
hoppscotch/packages/hoppscotch-common/src/components/firebase/Login.vue
2023-08-18 21:23:56 +05:30

329 lines
8.5 KiB
Vue

<template>
<HoppSmartModal
v-if="show"
dialog
:title="`${t('auth.login_to_hoppscotch')}`"
styles="sm:max-w-md"
@close="hideModal"
>
<template #body>
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
<HoppSmartItem
v-for="provider in allowedAuthProviders"
:key="provider.id"
:loading="provider.isLoading.value"
:icon="provider.icon"
:label="provider.label"
@click="provider.action"
/>
<hr v-if="additonalLoginItems.length > 0" />
<HoppSmartItem
v-for="loginItem in additonalLoginItems"
:key="loginItem.id"
:icon="loginItem.icon"
:label="loginItem.text(t)"
@click="doAdditionalLoginItemClickAction(loginItem)"
/>
</div>
<form
v-if="mode === 'email'"
class="flex flex-col space-y-2"
@submit.prevent="signInWithEmail"
>
<HoppSmartInput
v-model="form.email"
type="email"
placeholder=" "
:label="t('auth.email')"
input-styles="floating-input"
/>
<HoppButtonPrimary
:loading="signingInWithEmail"
type="submit"
:label="`${t('auth.send_magic_link')}`"
/>
</form>
<div v-if="mode === 'email-sent'" class="flex flex-col px-4">
<div class="flex flex-col items-center justify-center max-w-md">
<icon-lucide-inbox class="w-6 h-6 text-accent" />
<h3 class="my-2 text-lg text-center">
{{ t("auth.we_sent_magic_link") }}
</h3>
<p class="text-center">
{{
t("auth.we_sent_magic_link_description", { email: form.email })
}}
</p>
</div>
</div>
</template>
<template #footer>
<div
v-if="mode === 'sign-in' && tosLink && privacyPolicyLink"
class="text-secondaryLight text-tiny"
>
By signing in, you are agreeing to our
<HoppSmartAnchor
class="link"
:to="tosLink"
blank
label="Terms of Service"
/>
and
<HoppSmartAnchor
class="link"
:to="privacyPolicyLink"
blank
label="Privacy Policy"
/>
</div>
<div v-if="mode === 'email'">
<HoppButtonSecondary
:label="t('auth.all_sign_in_options')"
:icon="IconArrowLeft"
class="!p-0"
@click="mode = 'sign-in'"
/>
</div>
<div
v-if="mode === 'email-sent'"
class="flex justify-between flex-1 text-secondaryLight"
>
<HoppSmartAnchor
class="link"
:label="t('auth.re_enter_email')"
:icon="IconArrowLeft"
@click="mode = 'email'"
/>
<HoppSmartAnchor
class="link"
:label="`${t('action.dismiss')}`"
@click="hideModal"
/>
</div>
</template>
</HoppSmartModal>
</template>
<script setup lang="ts">
import { Ref, computed, onMounted, ref } from "vue"
import { useStreamSubscriber } from "@composables/stream"
import { useToast } from "@composables/toast"
import { useI18n } from "@composables/i18n"
import { platform } from "~/platform"
import { setLocalConfig } from "~/newstore/localpersistence"
import IconGithub from "~icons/auth/github"
import IconGoogle from "~icons/auth/google"
import IconEmail from "~icons/auth/email"
import IconMicrosoft from "~icons/auth/microsoft"
import IconArrowLeft from "~icons/lucide/arrow-left"
import { LoginItemDef } from "~/platform/auth"
defineProps<{
show: boolean
}>()
const emit = defineEmits<{
(e: "hide-modal"): void
}>()
const { subscribeToStream } = useStreamSubscriber()
const t = useI18n()
const toast = useToast()
const form = {
email: "",
}
const signingInWithGoogle = ref(false)
const signingInWithGitHub = ref(false)
const signingInWithMicrosoft = ref(false)
const signingInWithEmail = ref(false)
const mode = ref("sign-in")
const tosLink = import.meta.env.VITE_APP_TOS_LINK
const privacyPolicyLink = import.meta.env.VITE_APP_PRIVACY_POLICY_LINK
type AuthProviderItem = {
id: string
icon: typeof IconGithub
label: string
action: (...args: any[]) => any
isLoading: Ref<boolean>
}
const additonalLoginItems = computed(
() => platform.auth.additionalLoginItems ?? []
)
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
await item.onClick()
emit("hide-modal")
}
onMounted(() => {
const currentUser$ = platform.auth.getCurrentUserStream()
subscribeToStream(currentUser$, (user) => {
if (user) hideModal()
})
})
const showLoginSuccess = () => {
toast.success(`${t("auth.login_success")}`)
}
const signInWithGoogle = async () => {
signingInWithGoogle.value = true
try {
await platform.auth.signInUserWithGoogle()
} catch (e) {
console.error(e)
/*
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
*/
toast.error(`${t("error.something_went_wrong")}`)
}
signingInWithGoogle.value = false
}
const signInWithGithub = async () => {
signingInWithGitHub.value = true
const result = await platform.auth.signInUserWithGithub()
if (!result) {
signingInWithGitHub.value = false
return
}
if (result.type === "success") {
// this.showLoginSuccess()
} else if (result.type === "account-exists-with-different-cred") {
toast.info(`${t("auth.account_exists")}`, {
duration: 0,
closeOnSwipe: false,
action: {
text: `${t("action.yes")}`,
onClick: async (_, toastObject) => {
await result.link()
showLoginSuccess()
toastObject.goAway(0)
},
},
})
} else {
console.log("error logging into github", result.err)
toast.error(`${t("error.something_went_wrong")}`)
}
signingInWithGitHub.value = false
}
const signInWithMicrosoft = async () => {
signingInWithMicrosoft.value = true
try {
await platform.auth.signInUserWithMicrosoft()
// this.showLoginSuccess()
} catch (e) {
console.error(e)
/*
A auth/account-exists-with-different-credential Firebase error wont happen between MS with Google or Github
If a Github account exists and user then logs in with MS email we get a "Something went wrong toast" and console errors and MS replaces GH as only provider.
The error messages are as follows:
FirebaseError: Firebase: Error (auth/popup-closed-by-user).
@firebase/auth: Auth (9.6.11): INTERNAL ASSERTION FAILED: Pending promise was never set
They may be related to https://github.com/firebase/firebaseui-web/issues/947
*/
toast.error(`${t("error.something_went_wrong")}`)
}
signingInWithMicrosoft.value = false
}
const signInWithEmail = async () => {
signingInWithEmail.value = true
await platform.auth
.signInWithEmail(form.email)
.then(() => {
mode.value = "email-sent"
setLocalConfig("emailForSignIn", form.email)
})
.catch((e) => {
console.error(e)
toast.error(e.message)
signingInWithEmail.value = false
})
.finally(() => {
signingInWithEmail.value = false
})
}
const hideModal = () => {
mode.value = "sign-in"
toast.clear()
emit("hide-modal")
}
const authProviders: AuthProviderItem[] = [
{
id: "GITHUB",
icon: IconGithub,
label: t("auth.continue_with_github"),
action: signInWithGithub,
isLoading: signingInWithGitHub,
},
{
id: "GOOGLE",
icon: IconGoogle,
label: t("auth.continue_with_google"),
action: signInWithGoogle,
isLoading: signingInWithGoogle,
},
{
id: "MICROSOFT",
icon: IconMicrosoft,
label: t("auth.continue_with_microsoft"),
action: signInWithMicrosoft,
isLoading: signingInWithMicrosoft,
},
{
id: "EMAIL",
icon: IconEmail,
label: t("auth.continue_with_email"),
action: () => {
mode.value = "email"
},
isLoading: signingInWithEmail,
},
]
const allowedAuthProvidersIDsString: string | undefined = import.meta.env
.VITE_ALLOWED_AUTH_PROVIDERS
const allowedAuthProvidersIDs = allowedAuthProvidersIDsString
? allowedAuthProvidersIDsString.split(",")
: []
const allowedAuthProviders =
allowedAuthProvidersIDs.length > 0
? authProviders.filter((provider) =>
allowedAuthProvidersIDs.includes(provider.id)
)
: authProviders
</script>