Login.vue 7.2 KB

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