Profile.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <template>
  2. <div class="content profile-tab">
  3. <h4 class="section-title">Change Profile</h4>
  4. <p class="section-description">
  5. Edit your public profile so users can find out more about you.
  6. </p>
  7. <hr class="section-horizontal-rule" />
  8. <div
  9. class="control is-expanded avatar-selection-outer-container"
  10. v-if="modifiedUser.avatar"
  11. >
  12. <label>Avatar</label>
  13. <div id="avatar-selection-inner-container">
  14. <profile-picture
  15. :avatar="modifiedUser.avatar"
  16. :name="modifiedUser.name"
  17. />
  18. <div class="select">
  19. <select v-model="modifiedUser.avatar.type">
  20. <option value="gravatar">Using Gravatar</option>
  21. <option value="initials">Based on initials</option>
  22. </select>
  23. </div>
  24. </div>
  25. </div>
  26. <p class="control is-expanded margin-top-zero">
  27. <label for="name">Name</label>
  28. <input
  29. class="input"
  30. id="name"
  31. type="text"
  32. placeholder="Enter name here..."
  33. maxlength="64"
  34. v-model="modifiedUser.name"
  35. />
  36. <span v-if="modifiedUser.name" class="character-counter"
  37. >{{ modifiedUser.name.length }}/64</span
  38. >
  39. </p>
  40. <p class="control is-expanded">
  41. <label for="location">Location</label>
  42. <input
  43. class="input"
  44. id="location"
  45. type="text"
  46. placeholder="Enter location here..."
  47. maxlength="50"
  48. v-model="modifiedUser.location"
  49. />
  50. <span v-if="modifiedUser.location" class="character-counter"
  51. >{{ modifiedUser.location.length }}/50</span
  52. >
  53. </p>
  54. <p class="control is-expanded">
  55. <label for="bio">Bio</label>
  56. <textarea
  57. class="textarea"
  58. id="bio"
  59. placeholder="Enter bio here..."
  60. maxlength="200"
  61. autocomplete="off"
  62. v-model="modifiedUser.bio"
  63. />
  64. <span v-if="modifiedUser.bio" class="character-counter"
  65. >{{ modifiedUser.bio.length }}/200</span
  66. >
  67. </p>
  68. <save-button ref="saveButton" @clicked="saveChanges()" />
  69. </div>
  70. </template>
  71. <script>
  72. import { mapState, mapActions, mapGetters } from "vuex";
  73. import Toast from "toasters";
  74. import ProfilePicture from "@/components/ProfilePicture.vue";
  75. import SaveButton from "@/components/SaveButton.vue";
  76. import validation from "@/validation";
  77. export default {
  78. components: { ProfilePicture, SaveButton },
  79. computed: {
  80. ...mapState({
  81. userId: state => state.user.auth.userId,
  82. originalUser: state => state.settings.originalUser,
  83. modifiedUser: state => state.settings.modifiedUser
  84. }),
  85. ...mapGetters({
  86. socket: "websockets/getSocket"
  87. })
  88. },
  89. watch: {
  90. "modifiedUser.avatar.type": function watchAvatarType(newType, oldType) {
  91. if (
  92. oldType &&
  93. this.modifiedUser.avatar.type !==
  94. this.originalUser.avatar.type &&
  95. newType === "initials"
  96. ) {
  97. const colors = ["blue", "orange", "green", "purple", "teal"];
  98. const color = colors[Math.floor(Math.random() * colors.length)];
  99. this.modifiedUser.avatar.color = color;
  100. }
  101. }
  102. },
  103. methods: {
  104. saveChanges() {
  105. const nameChanged =
  106. this.modifiedUser.name !== this.originalUser.name;
  107. const locationChanged =
  108. this.modifiedUser.location !== this.originalUser.location;
  109. const bioChanged = this.modifiedUser.bio !== this.originalUser.bio;
  110. const avatarChanged =
  111. this.modifiedUser.avatar.type !== this.originalUser.avatar.type;
  112. if (nameChanged) this.changeName();
  113. if (locationChanged) this.changeLocation();
  114. if (bioChanged) this.changeBio();
  115. if (avatarChanged) this.changeAvatarType();
  116. if (
  117. !avatarChanged &&
  118. !bioChanged &&
  119. !locationChanged &&
  120. !nameChanged
  121. ) {
  122. this.$refs.saveButton.handleFailedSave();
  123. new Toast("Please make a change before saving.");
  124. }
  125. },
  126. changeName() {
  127. const { name } = this.modifiedUser;
  128. if (!validation.isLength(name, 1, 64))
  129. return new Toast("Name must have between 1 and 64 characters.");
  130. this.$refs.saveButton.status = "disabled";
  131. return this.socket.dispatch(
  132. "users.updateName",
  133. this.userId,
  134. name,
  135. res => {
  136. if (res.status !== "success") {
  137. new Toast(res.message);
  138. this.$refs.saveButton.handleFailedSave();
  139. } else {
  140. new Toast("Successfully changed name");
  141. this.updateOriginalUser({
  142. property: "name",
  143. value: name
  144. });
  145. this.$refs.saveButton.handleSuccessfulSave();
  146. }
  147. }
  148. );
  149. },
  150. changeLocation() {
  151. const { location } = this.modifiedUser;
  152. if (!validation.isLength(location, 0, 50))
  153. return new Toast(
  154. "Location must have between 0 and 50 characters."
  155. );
  156. this.$refs.saveButton.status = "disabled";
  157. return this.socket.dispatch(
  158. "users.updateLocation",
  159. this.userId,
  160. location,
  161. res => {
  162. if (res.status !== "success") {
  163. new Toast(res.message);
  164. this.$refs.saveButton.handleFailedSave();
  165. } else {
  166. new Toast("Successfully changed location");
  167. this.updateOriginalUser({
  168. property: "location",
  169. value: location
  170. });
  171. this.$refs.saveButton.handleSuccessfulSave();
  172. }
  173. }
  174. );
  175. },
  176. changeBio() {
  177. const { bio } = this.modifiedUser;
  178. if (!validation.isLength(bio, 0, 200))
  179. return new Toast("Bio must have between 0 and 200 characters.");
  180. this.$refs.saveButton.status = "disabled";
  181. return this.socket.dispatch(
  182. "users.updateBio",
  183. this.userId,
  184. bio,
  185. res => {
  186. if (res.status !== "success") {
  187. new Toast(res.message);
  188. this.$refs.saveButton.handleFailedSave();
  189. } else {
  190. new Toast("Successfully changed bio");
  191. this.updateOriginalUser({
  192. property: "bio",
  193. value: bio
  194. });
  195. this.$refs.saveButton.handleSuccessfulSave();
  196. }
  197. }
  198. );
  199. },
  200. changeAvatarType() {
  201. const { avatar } = this.modifiedUser;
  202. this.$refs.saveButton.status = "disabled";
  203. return this.socket.dispatch(
  204. "users.updateAvatarType",
  205. this.userId,
  206. avatar,
  207. res => {
  208. if (res.status !== "success") {
  209. new Toast(res.message);
  210. this.$refs.saveButton.handleFailedSave();
  211. } else {
  212. new Toast("Successfully updated avatar type");
  213. this.updateOriginalUser({
  214. property: "avatar",
  215. value: avatar
  216. });
  217. this.$refs.saveButton.handleSuccessfulSave();
  218. }
  219. }
  220. );
  221. },
  222. ...mapActions("settings", ["updateOriginalUser"])
  223. }
  224. };
  225. </script>
  226. <style lang="scss" scoped>
  227. .content .control {
  228. margin-bottom: 15px;
  229. }
  230. .character-counter {
  231. height: initial;
  232. }
  233. .avatar-selection-outer-container {
  234. display: flex;
  235. flex-direction: column;
  236. align-items: flex-start;
  237. .select:after {
  238. border-color: var(--primary-color);
  239. }
  240. #avatar-selection-inner-container {
  241. display: flex;
  242. align-items: center;
  243. margin-top: 5px;
  244. .profile-picture {
  245. margin-right: 10px;
  246. width: 50px;
  247. height: 50px;
  248. font-size: 25px;
  249. }
  250. }
  251. }
  252. </style>