Profile.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template>
  2. <div class="content profile-tab">
  3. <h4 class="section-title">
  4. Change Profile
  5. </h4>
  6. <p class="section-description">
  7. Edit your public profile so users can find out more about you.
  8. </p>
  9. <hr class="section-horizontal-rule" />
  10. <div
  11. class="control is-expanded avatar-selection-outer-container"
  12. v-if="modifiedUser.avatar"
  13. >
  14. <label>Avatar</label>
  15. <div id="avatar-selection-inner-container">
  16. <div class="profile-picture">
  17. <img
  18. :src="
  19. modifiedUser.avatar.url &&
  20. modifiedUser.avatar.type === 'gravatar'
  21. ? `${modifiedUser.avatar.url}?d=${notesUri}&s=250`
  22. : '/assets/notes.png'
  23. "
  24. onerror="this.src='/assets/notes.png'; this.onerror=''"
  25. />
  26. </div>
  27. <div class="select">
  28. <select v-model="modifiedUser.avatar.type">
  29. <option value="gravatar">Using Gravatar</option>
  30. <option value="initials">Based on initials</option>
  31. </select>
  32. </div>
  33. </div>
  34. </div>
  35. <p class="control is-expanded margin-top-zero">
  36. <label for="name">Name</label>
  37. <input
  38. class="input"
  39. id="name"
  40. type="text"
  41. placeholder="Enter name here..."
  42. maxlength="64"
  43. v-model="modifiedUser.name"
  44. />
  45. <span v-if="modifiedUser.name" class="character-counter"
  46. >{{ modifiedUser.name.length }}/64</span
  47. >
  48. </p>
  49. <p class="control is-expanded">
  50. <label for="location">Location</label>
  51. <input
  52. class="input"
  53. id="location"
  54. type="text"
  55. placeholder="Enter location here..."
  56. maxlength="50"
  57. v-model="modifiedUser.location"
  58. />
  59. <span v-if="modifiedUser.location" class="character-counter"
  60. >{{ modifiedUser.location.length }}/50</span
  61. >
  62. </p>
  63. <p class="control is-expanded">
  64. <label for="bio">Bio</label>
  65. <textarea
  66. class="textarea"
  67. id="bio"
  68. placeholder="Enter bio here..."
  69. maxlength="200"
  70. autocomplete="off"
  71. v-model="modifiedUser.bio"
  72. />
  73. <span v-if="modifiedUser.bio" class="character-counter"
  74. >{{ modifiedUser.bio.length }}/200</span
  75. >
  76. </p>
  77. <transition name="saving-changes-transition" mode="out-in">
  78. <button
  79. class="button save-changes"
  80. :class="saveButtonStyle"
  81. @click="saveChanges()"
  82. :key="saveStatus"
  83. :disabled="saveStatus === 'disabled'"
  84. v-html="saveButtonMessage"
  85. />
  86. </transition>
  87. </div>
  88. </template>
  89. <script>
  90. import { mapState, mapActions } from "vuex";
  91. import Toast from "toasters";
  92. import validation from "../../../validation";
  93. import io from "../../../io";
  94. import SaveButton from "../mixins/SaveButton.vue";
  95. export default {
  96. mixins: [SaveButton],
  97. data() {
  98. return {
  99. notesUri: ""
  100. };
  101. },
  102. computed: mapState({
  103. userId: state => state.user.auth.userId,
  104. originalUser: state => state.settings.originalUser,
  105. modifiedUser: state => state.settings.modifiedUser
  106. }),
  107. mounted() {
  108. lofig.get("frontendDomain").then(frontendDomain => {
  109. this.notesUri = encodeURI(`${frontendDomain}/assets/notes.png`);
  110. });
  111. io.getSocket(socket => {
  112. this.socket = socket;
  113. });
  114. },
  115. methods: {
  116. saveChanges() {
  117. const nameChanged =
  118. this.modifiedUser.name !== this.originalUser.name;
  119. const locationChanged =
  120. this.modifiedUser.location !== this.originalUser.location;
  121. const bioChanged = this.modifiedUser.bio !== this.originalUser.bio;
  122. const avatarChanged =
  123. this.modifiedUser.avatar.type !== this.originalUser.avatar.type;
  124. if (nameChanged) this.changeName();
  125. if (locationChanged) this.changeLocation();
  126. if (bioChanged) this.changeBio();
  127. if (avatarChanged) this.changeAvatarType();
  128. if (
  129. !avatarChanged &&
  130. !bioChanged &&
  131. !locationChanged &&
  132. !nameChanged
  133. ) {
  134. this.failedSave();
  135. new Toast({
  136. content: "Please make a change before saving.",
  137. timeout: 8000
  138. });
  139. }
  140. },
  141. changeName() {
  142. const { name } = this.modifiedUser;
  143. if (!validation.isLength(name, 1, 64))
  144. return new Toast({
  145. content: "Name must have between 1 and 64 characters.",
  146. timeout: 8000
  147. });
  148. this.saveStatus = "disabled";
  149. return this.socket.emit(
  150. "users.updateName",
  151. this.userId,
  152. name,
  153. res => {
  154. if (res.status !== "success") {
  155. new Toast({ content: res.message, timeout: 8000 });
  156. this.failedSave();
  157. } else {
  158. new Toast({
  159. content: "Successfully changed name",
  160. timeout: 4000
  161. });
  162. this.updateOriginalUser({
  163. property: "name",
  164. value: name
  165. });
  166. this.successfulSave();
  167. }
  168. }
  169. );
  170. },
  171. changeLocation() {
  172. const { location } = this.modifiedUser;
  173. if (!validation.isLength(location, 0, 50))
  174. return new Toast({
  175. content: "Location must have between 0 and 50 characters.",
  176. timeout: 8000
  177. });
  178. this.saveStatus = "disabled";
  179. return this.socket.emit(
  180. "users.updateLocation",
  181. this.userId,
  182. location,
  183. res => {
  184. if (res.status !== "success") {
  185. new Toast({ content: res.message, timeout: 8000 });
  186. this.failedSave();
  187. } else {
  188. new Toast({
  189. content: "Successfully changed location",
  190. timeout: 4000
  191. });
  192. this.updateOriginalUser({
  193. property: "location",
  194. value: location
  195. });
  196. this.successfulSave();
  197. }
  198. }
  199. );
  200. },
  201. changeBio() {
  202. const { bio } = this.modifiedUser;
  203. if (!validation.isLength(bio, 0, 200))
  204. return new Toast({
  205. content: "Bio must have between 0 and 200 characters.",
  206. timeout: 8000
  207. });
  208. this.saveStatus = "disabled";
  209. return this.socket.emit(
  210. "users.updateBio",
  211. this.userId,
  212. bio,
  213. res => {
  214. if (res.status !== "success") {
  215. new Toast({ content: res.message, timeout: 8000 });
  216. this.failedSave();
  217. } else {
  218. new Toast({
  219. content: "Successfully changed bio",
  220. timeout: 4000
  221. });
  222. this.updateOriginalUser({
  223. property: "bio",
  224. value: bio
  225. });
  226. this.successfulSave();
  227. }
  228. }
  229. );
  230. },
  231. changeAvatarType() {
  232. const { avatar } = this.modifiedUser;
  233. this.saveStatus = "disabled";
  234. return this.socket.emit(
  235. "users.updateAvatarType",
  236. this.userId,
  237. avatar.type,
  238. res => {
  239. if (res.status !== "success") {
  240. new Toast({ content: res.message, timeout: 8000 });
  241. this.failedSave();
  242. } else {
  243. new Toast({
  244. content: "Successfully updated avatar type",
  245. timeout: 4000
  246. });
  247. this.updateOriginalUser({
  248. property: "avatar",
  249. value: avatar
  250. });
  251. this.successfulSave();
  252. }
  253. }
  254. );
  255. },
  256. ...mapActions("settings", ["updateOriginalUser"])
  257. }
  258. };
  259. </script>
  260. <style lang="scss" scoped>
  261. @import "../../../styles/global.scss";
  262. .content .control {
  263. margin-bottom: 15px;
  264. }
  265. .character-counter {
  266. height: initial;
  267. }
  268. .avatar-selection-outer-container {
  269. display: flex;
  270. flex-direction: column;
  271. align-items: flex-start;
  272. .select:after {
  273. border-color: $musare-blue;
  274. }
  275. #avatar-selection-inner-container {
  276. display: flex;
  277. align-items: center;
  278. margin-top: 5px;
  279. .profile-picture {
  280. line-height: 1;
  281. cursor: pointer;
  282. img {
  283. background-color: #fff;
  284. width: 50px;
  285. height: 50px;
  286. border-radius: 100%;
  287. border: 2px solid $light-grey;
  288. margin-right: 10px;
  289. }
  290. }
  291. }
  292. }
  293. </style>