profile.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <template>
  2. <div>
  3. <div class="container">
  4. <div class="p-4">
  5. <div
  6. v-if="currentUser === null"
  7. class="flex flex-col items-center justify-center"
  8. >
  9. <img
  10. :src="`/images/states/${$colorMode.value}/login.svg`"
  11. loading="lazy"
  12. class="flex-col object-contain object-center h-24 my-4 w-24 inline-flex"
  13. :alt="`${t('empty.parameters')}`"
  14. />
  15. <p class="text-center text-secondaryLight pb-4">
  16. {{ t("empty.profile") }}
  17. </p>
  18. <ButtonPrimary
  19. :label="t('auth.login')"
  20. class="mb-4"
  21. @click.native="showLogin = true"
  22. />
  23. </div>
  24. <div v-else class="space-y-8">
  25. <div
  26. class="bg-primaryLight rounded h-24 -mb-11 md:h-32"
  27. style="background-image: url('/images/cover.svg')"
  28. ></div>
  29. <div class="flex flex-col space-y-8 px-4 justify-between md:flex-row">
  30. <div class="flex items-end">
  31. <img
  32. v-if="currentUser.photoURL"
  33. :src="currentUser.photoURL"
  34. class="rounded-lg h-16 ring-primary ring-4 w-16"
  35. :alt="currentUser.displayName"
  36. />
  37. <SmartIcon v-else name="user" class="svg-icons" />
  38. <div class="ml-4">
  39. <label class="heading">
  40. {{ currentUser.displayName || t("state.nothing_found") }}
  41. </label>
  42. <p class="flex text-secondaryLight items-center">
  43. {{ currentUser.email }}
  44. <SmartIcon
  45. v-if="currentUser.emailVerified"
  46. name="verified"
  47. class="ml-2 text-green-500 svg-icons"
  48. />
  49. <ButtonSecondary
  50. v-else
  51. :label="t('settings.verify_email')"
  52. svg="verified"
  53. class="ml-2 py-0 px-1"
  54. :loading="verifyingEmailAddress"
  55. @click.native="sendEmailVerification"
  56. />
  57. </p>
  58. </div>
  59. </div>
  60. <div class="flex space-x-2 items-end">
  61. <div>
  62. <SmartItem
  63. to="/settings"
  64. svg="settings"
  65. :label="t('profile.app_settings')"
  66. outline
  67. />
  68. </div>
  69. <FirebaseLogout outline />
  70. </div>
  71. </div>
  72. <SmartTabs>
  73. <SmartTab
  74. :id="'sync'"
  75. :label="t('settings.account')"
  76. :selected="true"
  77. >
  78. <section class="p-4">
  79. <h4 class="font-semibold text-secondaryDark">
  80. {{ t("settings.profile") }}
  81. </h4>
  82. <div class="mt-1 text-secondaryLight">
  83. {{ t("settings.profile_description") }}
  84. </div>
  85. <div class="py-4">
  86. <label for="displayName">
  87. {{ t("settings.profile_name") }}
  88. </label>
  89. <form
  90. class="flex mt-2 md:max-w-sm"
  91. @submit.prevent="updateDisplayName"
  92. >
  93. <input
  94. id="displayName"
  95. v-model="displayName"
  96. class="input"
  97. :placeholder="`${t('settings.profile_name')}`"
  98. type="text"
  99. autocomplete="off"
  100. required
  101. />
  102. <ButtonSecondary
  103. filled
  104. outline
  105. :label="t('action.save')"
  106. class="ml-2 min-w-16"
  107. type="submit"
  108. :loading="updatingDisplayName"
  109. />
  110. </form>
  111. </div>
  112. <div class="py-4">
  113. <label for="emailAddress">
  114. {{ t("settings.profile_email") }}
  115. </label>
  116. <form
  117. class="flex mt-2 md:max-w-sm"
  118. @submit.prevent="updateEmailAddress"
  119. >
  120. <input
  121. id="emailAddress"
  122. v-model="emailAddress"
  123. class="input"
  124. :placeholder="`${t('settings.profile_name')}`"
  125. type="email"
  126. autocomplete="off"
  127. required
  128. />
  129. <ButtonSecondary
  130. filled
  131. outline
  132. :label="t('action.save')"
  133. class="ml-2 min-w-16"
  134. type="submit"
  135. :loading="updatingEmailAddress"
  136. />
  137. </form>
  138. </div>
  139. </section>
  140. <section class="p-4">
  141. <h4 class="font-semibold text-secondaryDark">
  142. {{ t("settings.sync") }}
  143. </h4>
  144. <div class="mt-1 text-secondaryLight">
  145. {{ t("settings.sync_description") }}
  146. </div>
  147. <div class="space-y-4 py-4">
  148. <div class="flex items-center">
  149. <SmartToggle
  150. :on="SYNC_COLLECTIONS"
  151. @change="toggleSetting('syncCollections')"
  152. >
  153. {{ t("settings.sync_collections") }}
  154. </SmartToggle>
  155. </div>
  156. <div class="flex items-center">
  157. <SmartToggle
  158. :on="SYNC_ENVIRONMENTS"
  159. @change="toggleSetting('syncEnvironments')"
  160. >
  161. {{ t("settings.sync_environments") }}
  162. </SmartToggle>
  163. </div>
  164. <div class="flex items-center">
  165. <SmartToggle
  166. :on="SYNC_HISTORY"
  167. @change="toggleSetting('syncHistory')"
  168. >
  169. {{ t("settings.sync_history") }}
  170. </SmartToggle>
  171. </div>
  172. </div>
  173. </section>
  174. </SmartTab>
  175. <SmartTab :id="'teams'" :label="t('team.title')">
  176. <AppSection label="teams">
  177. <Teams :modal="false" />
  178. </AppSection>
  179. </SmartTab>
  180. </SmartTabs>
  181. </div>
  182. </div>
  183. <FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
  184. </div>
  185. </div>
  186. </template>
  187. <script setup lang="ts">
  188. import {
  189. ref,
  190. useMeta,
  191. defineComponent,
  192. watchEffect,
  193. } from "@nuxtjs/composition-api"
  194. import {
  195. currentUser$,
  196. setDisplayName,
  197. setEmailAddress,
  198. verifyEmailAddress,
  199. } from "~/helpers/fb/auth"
  200. import {
  201. useReadonlyStream,
  202. useI18n,
  203. useToast,
  204. } from "~/helpers/utils/composables"
  205. import { toggleSetting, useSetting } from "~/newstore/settings"
  206. const t = useI18n()
  207. const toast = useToast()
  208. const showLogin = ref(false)
  209. const SYNC_COLLECTIONS = useSetting("syncCollections")
  210. const SYNC_ENVIRONMENTS = useSetting("syncEnvironments")
  211. const SYNC_HISTORY = useSetting("syncHistory")
  212. const currentUser = useReadonlyStream(currentUser$, null)
  213. const displayName = ref(currentUser.value?.displayName)
  214. const updatingDisplayName = ref(false)
  215. watchEffect(() => (displayName.value = currentUser.value?.displayName))
  216. const updateDisplayName = () => {
  217. updatingDisplayName.value = true
  218. setDisplayName(displayName.value as string)
  219. .then(() => {
  220. toast.success(`${t("profile.updated")}`)
  221. })
  222. .catch(() => {
  223. toast.error(`${t("error.something_went_wrong")}`)
  224. })
  225. .finally(() => {
  226. updatingDisplayName.value = false
  227. })
  228. }
  229. const emailAddress = ref(currentUser.value?.email)
  230. const updatingEmailAddress = ref(false)
  231. watchEffect(() => (emailAddress.value = currentUser.value?.email))
  232. const updateEmailAddress = () => {
  233. updatingEmailAddress.value = true
  234. setEmailAddress(emailAddress.value as string)
  235. .then(() => {
  236. toast.success(`${t("profile.updated")}`)
  237. })
  238. .catch(() => {
  239. toast.error(`${t("error.something_went_wrong")}`)
  240. })
  241. .finally(() => {
  242. updatingEmailAddress.value = false
  243. })
  244. }
  245. const verifyingEmailAddress = ref(false)
  246. const sendEmailVerification = () => {
  247. verifyingEmailAddress.value = true
  248. verifyEmailAddress()
  249. .then(() => {
  250. toast.success(`${t("profile.email_verification_mail")}`)
  251. })
  252. .catch(() => {
  253. toast.error(`${t("error.something_went_wrong")}`)
  254. })
  255. .finally(() => {
  256. verifyingEmailAddress.value = false
  257. })
  258. }
  259. useMeta({
  260. title: `${t("navigation.profile")} • Hoppscotch`,
  261. })
  262. </script>
  263. <script lang="ts">
  264. export default defineComponent({
  265. head: {},
  266. })
  267. </script>