profile.vue 9.0 KB

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