Login.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <template>
  2. <SmartModal
  3. v-if="show"
  4. :title="`${$t('auth.login_to_hoppscotch')}`"
  5. dialog
  6. @close="hideModal"
  7. >
  8. <template #body>
  9. <div v-if="mode === 'sign-in'" class="flex flex-col space-y-2 px-2">
  10. <SmartItem
  11. :loading="signingInWithGitHub"
  12. svg="auth/github"
  13. :label="`${$t('auth.continue_with_github')}`"
  14. @click.native="signInWithGithub"
  15. />
  16. <SmartItem
  17. :loading="signingInWithGoogle"
  18. svg="auth/google"
  19. :label="`${$t('auth.continue_with_google')}`"
  20. @click.native="signInWithGoogle"
  21. />
  22. <SmartItem
  23. icon="mail"
  24. :label="`${$t('auth.continue_with_email')}`"
  25. @click.native="mode = 'email'"
  26. />
  27. </div>
  28. <div v-if="mode === 'email'" class="flex flex-col space-y-2">
  29. <div class="flex flex-col">
  30. <input
  31. id="email"
  32. v-model="form.email"
  33. v-focus
  34. class="input floating-input"
  35. placeholder=" "
  36. type="email"
  37. name="email"
  38. autocomplete="off"
  39. required
  40. spellcheck="false"
  41. autofocus
  42. @keyup.enter="signInWithEmail"
  43. />
  44. <label for="email">
  45. {{ $t("auth.email") }}
  46. </label>
  47. </div>
  48. <ButtonPrimary
  49. :loading="signingInWithEmail"
  50. :disabled="
  51. form.email.length !== 0
  52. ? emailRegex.test(form.email)
  53. ? false
  54. : true
  55. : true
  56. "
  57. type="button"
  58. :label="`${$t('auth.send_magic_link')}`"
  59. @click.native="signInWithEmail"
  60. />
  61. </div>
  62. <div v-if="mode === 'email-sent'" class="flex flex-col px-4">
  63. <div class="flex flex-col max-w-md justify-center items-center">
  64. <SmartIcon class="h-6 text-accent w-6" name="inbox" />
  65. <h3 class="my-2 text-center text-lg">
  66. {{ $t("auth.we_sent_magic_link") }}
  67. </h3>
  68. <p class="text-center">
  69. {{
  70. $t("auth.we_sent_magic_link_description", { email: form.email })
  71. }}
  72. </p>
  73. </div>
  74. </div>
  75. </template>
  76. <template #footer>
  77. <p v-if="mode === 'sign-in'" class="text-secondaryLight">
  78. By signing in, you are agreeing to our
  79. <SmartAnchor
  80. class="link"
  81. to="https://docs.hoppscotch.io/terms"
  82. blank
  83. label="Terms of Service"
  84. />
  85. and
  86. <SmartAnchor
  87. class="link"
  88. to="https://docs.hoppscotch.io/privacy"
  89. blank
  90. label="Privacy Policy"
  91. />.
  92. </p>
  93. <p v-if="mode === 'email'" class="text-secondaryLight">
  94. <SmartAnchor
  95. class="link"
  96. :label="`← \xA0 ${$t('auth.all_sign_in_options')}`"
  97. @click.native="mode = 'sign-in'"
  98. />
  99. </p>
  100. <p
  101. v-if="mode === 'email-sent'"
  102. class="flex flex-1 text-secondaryLight justify-between"
  103. >
  104. <SmartAnchor
  105. class="link"
  106. :label="`← \xA0 ${$t('auth.re_enter_email')}`"
  107. @click.native="mode = 'email'"
  108. />
  109. <SmartAnchor
  110. class="link"
  111. :label="`${$t('action.dismiss')}`"
  112. @click.native="hideModal"
  113. />
  114. </p>
  115. </template>
  116. </SmartModal>
  117. </template>
  118. <script lang="ts">
  119. import { defineComponent } from "@nuxtjs/composition-api"
  120. import {
  121. signInUserWithGoogle,
  122. signInUserWithGithub,
  123. setProviderInfo,
  124. currentUser$,
  125. signInWithEmail,
  126. linkWithFBCredential,
  127. getGithubCredentialFromResult,
  128. } from "~/helpers/fb/auth"
  129. import { setLocalConfig } from "~/newstore/localpersistence"
  130. import { useStreamSubscriber } from "~/helpers/utils/composables"
  131. export default defineComponent({
  132. props: {
  133. show: Boolean,
  134. },
  135. setup() {
  136. const { subscribeToStream } = useStreamSubscriber()
  137. return {
  138. subscribeToStream,
  139. }
  140. },
  141. data() {
  142. return {
  143. form: {
  144. email: "",
  145. },
  146. signingInWithGoogle: false,
  147. signingInWithGitHub: false,
  148. signingInWithEmail: false,
  149. emailRegex:
  150. /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
  151. mode: "sign-in",
  152. }
  153. },
  154. mounted() {
  155. this.subscribeToStream(currentUser$, (user) => {
  156. if (user) this.hideModal()
  157. })
  158. },
  159. methods: {
  160. showLoginSuccess() {
  161. this.$toast.success(`${this.$t("auth.login_success")}`, {
  162. icon: "vpn_key",
  163. })
  164. },
  165. async signInWithGoogle() {
  166. this.signingInWithGoogle = true
  167. try {
  168. await signInUserWithGoogle()
  169. this.showLoginSuccess()
  170. } catch (e) {
  171. console.error(e)
  172. // An error happened.
  173. if (e.code === "auth/account-exists-with-different-credential") {
  174. // Step 2.
  175. // User's email already exists.
  176. // The pending Google credential.
  177. const pendingCred = e.credential
  178. this.$toast.info(`${this.$t("auth.account_exists")}`, {
  179. icon: "vpn_key",
  180. duration: 0,
  181. closeOnSwipe: false,
  182. action: {
  183. text: `${this.$t("action.yes")}`,
  184. onClick: async (_, toastObject) => {
  185. const { user } = await signInUserWithGithub()
  186. await linkWithFBCredential(user, pendingCred)
  187. this.showLoginSuccess()
  188. toastObject.goAway(0)
  189. },
  190. },
  191. })
  192. } else {
  193. this.$toast.error(`${this.$t("error.something_went_wrong")}`, {
  194. icon: "error_outline",
  195. })
  196. }
  197. }
  198. this.signingInWithGoogle = false
  199. },
  200. async signInWithGithub() {
  201. this.signingInWithGitHub = true
  202. try {
  203. const result = await signInUserWithGithub()
  204. const credential = getGithubCredentialFromResult(result)!
  205. const token = credential.accessToken
  206. setProviderInfo(result.providerId!, token!)
  207. this.showLoginSuccess()
  208. } catch (e) {
  209. console.error(e)
  210. // An error happened.
  211. if (e.code === "auth/account-exists-with-different-credential") {
  212. // Step 2.
  213. // User's email already exists.
  214. // The pending Google credential.
  215. const pendingCred = e.credential
  216. this.$toast.info(`${this.$t("auth.account_exists")}`, {
  217. icon: "vpn_key",
  218. duration: 0,
  219. closeOnSwipe: false,
  220. action: {
  221. text: `${this.$t("action.yes")}`,
  222. onClick: async (_, toastObject) => {
  223. const { user } = await signInUserWithGoogle()
  224. await linkWithFBCredential(user, pendingCred)
  225. this.showLoginSuccess()
  226. toastObject.goAway(0)
  227. },
  228. },
  229. })
  230. } else {
  231. this.$toast.error(`${this.$t("error.something_went_wrong")}`, {
  232. icon: "error_outline",
  233. })
  234. }
  235. }
  236. this.signingInWithGitHub = false
  237. },
  238. async signInWithEmail() {
  239. this.signingInWithEmail = true
  240. const actionCodeSettings = {
  241. url: `${process.env.BASE_URL}/enter`,
  242. handleCodeInApp: true,
  243. }
  244. await signInWithEmail(this.form.email, actionCodeSettings)
  245. .then(() => {
  246. this.mode = "email-sent"
  247. setLocalConfig("emailForSignIn", this.form.email)
  248. })
  249. .catch((e) => {
  250. console.error(e)
  251. this.$toast.error(e.message, {
  252. icon: "error_outline",
  253. })
  254. this.signingInWithEmail = false
  255. })
  256. .finally(() => {
  257. this.signingInWithEmail = false
  258. })
  259. },
  260. hideModal() {
  261. this.mode = "sign-in"
  262. this.$toast.clear()
  263. this.$emit("hide-modal")
  264. },
  265. },
  266. })
  267. </script>