Account.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <script setup lang="ts">
  2. import {
  3. defineAsyncComponent,
  4. ref,
  5. watch,
  6. reactive,
  7. computed,
  8. onMounted
  9. } from "vue";
  10. import { useStore } from "vuex";
  11. import { useRoute } from "vue-router";
  12. import Toast from "toasters";
  13. import _validation from "@/validation";
  14. const InputHelpBox = defineAsyncComponent(
  15. () => import("@/components/InputHelpBox.vue")
  16. );
  17. const SaveButton = defineAsyncComponent(
  18. () => import("@/components/SaveButton.vue")
  19. );
  20. const store = useStore();
  21. const route = useRoute();
  22. const { socket } = store.state.websockets;
  23. const saveButton = ref();
  24. const userId = computed(() => store.state.user.auth.userId);
  25. const originalUser = computed(() => store.state.settings.originalUser);
  26. const modifiedUser = computed(() => store.state.settings.modifiedUser);
  27. const validation = reactive({
  28. username: {
  29. entered: false,
  30. valid: false,
  31. message: "Please enter a valid username."
  32. },
  33. email: {
  34. entered: false,
  35. valid: false,
  36. message: "Please enter a valid email address."
  37. }
  38. });
  39. const updateOriginalUser = payload =>
  40. store.dispatch("settings/updateOriginalUser", payload);
  41. const openModal = payload =>
  42. store.dispatch("modalVisibility/openModal", payload);
  43. const onInput = inputName => {
  44. validation[inputName].entered = true;
  45. };
  46. const changeEmail = () => {
  47. const email = modifiedUser.value.email.address;
  48. if (!_validation.isLength(email, 3, 254))
  49. return new Toast("Email must have between 3 and 254 characters.");
  50. if (
  51. email.indexOf("@") !== email.lastIndexOf("@") ||
  52. !_validation.regex.emailSimple.test(email)
  53. )
  54. return new Toast("Invalid email format.");
  55. saveButton.value.saveStatus = "disabled";
  56. return socket.dispatch("users.updateEmail", userId.value, email, res => {
  57. if (res.status !== "success") {
  58. new Toast(res.message);
  59. saveButton.value.handleFailedSave();
  60. } else {
  61. new Toast("Successfully changed email address");
  62. updateOriginalUser({
  63. property: "email.address",
  64. value: email
  65. });
  66. saveButton.value.handleSuccessfulSave();
  67. }
  68. });
  69. };
  70. const changeUsername = () => {
  71. const { username } = modifiedUser.value;
  72. if (!_validation.isLength(username, 2, 32))
  73. return new Toast("Username must have between 2 and 32 characters.");
  74. if (!_validation.regex.azAZ09_.test(username))
  75. return new Toast(
  76. "Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _."
  77. );
  78. if (username.replaceAll(/[_]/g, "").length === 0)
  79. return new Toast(
  80. "Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _, and there has to be at least one letter or number."
  81. );
  82. saveButton.value.saveStatus = "disabled";
  83. return socket.dispatch(
  84. "users.updateUsername",
  85. userId.value,
  86. username,
  87. res => {
  88. if (res.status !== "success") {
  89. new Toast(res.message);
  90. saveButton.value.handleFailedSave();
  91. } else {
  92. new Toast("Successfully changed username");
  93. updateOriginalUser({
  94. property: "username",
  95. value: username
  96. });
  97. saveButton.value.handleSuccessfulSave();
  98. }
  99. }
  100. );
  101. };
  102. const saveChanges = () => {
  103. const usernameChanged =
  104. modifiedUser.value.username !== originalUser.value.username;
  105. const emailAddressChanged =
  106. modifiedUser.value.email.address !== originalUser.value.email.address;
  107. if (usernameChanged) changeUsername();
  108. if (emailAddressChanged) changeEmail();
  109. if (!usernameChanged && !emailAddressChanged) {
  110. saveButton.value.handleFailedSave();
  111. new Toast("Please make a change before saving.");
  112. }
  113. };
  114. const removeActivities = () => {
  115. socket.dispatch("activities.removeAllForUser", res => {
  116. new Toast(res.message);
  117. });
  118. };
  119. onMounted(() => {
  120. if (
  121. route.query.removeAccount === "relinked-github" &&
  122. !localStorage.getItem("github_redirect")
  123. ) {
  124. openModal("removeAccount");
  125. // TODO fix/redo this logic, broke after composition refactor
  126. // setTimeout(() => {
  127. // const modal = this.$parent.$children.find(
  128. // child => child.name === "RemoveAccount"
  129. // );
  130. // modal.confirmGithubLink();
  131. // }, 50);
  132. }
  133. });
  134. watch(
  135. () => modifiedUser.value.username,
  136. value => {
  137. // const value = newModifiedUser.username;
  138. if (!_validation.isLength(value, 2, 32)) {
  139. validation.username.message =
  140. "Username must have between 2 and 32 characters.";
  141. validation.username.valid = false;
  142. } else if (
  143. !_validation.regex.azAZ09_.test(value) &&
  144. value !== originalUser.value.username // Sometimes a username pulled from GitHub won't succeed validation
  145. ) {
  146. validation.username.message =
  147. "Invalid format. Allowed characters: a-z, A-Z, 0-9 and _.";
  148. validation.username.valid = false;
  149. } else if (value.replaceAll(/[_]/g, "").length === 0) {
  150. validation.username.message =
  151. "Invalid format. Allowed characters: a-z, A-Z, 0-9 and _, and there has to be at least one letter or number.";
  152. validation.username.valid = false;
  153. } else {
  154. validation.username.message = "Everything looks great!";
  155. validation.username.valid = true;
  156. }
  157. }
  158. );
  159. watch(
  160. () => modifiedUser.value.email.address,
  161. value => {
  162. // const value = newModifiedUser.email.address;
  163. if (!_validation.isLength(value, 3, 254)) {
  164. validation.email.message =
  165. "Email must have between 3 and 254 characters.";
  166. validation.email.valid = false;
  167. } else if (
  168. value.indexOf("@") !== value.lastIndexOf("@") ||
  169. !_validation.regex.emailSimple.test(value)
  170. ) {
  171. validation.email.message = "Invalid format.";
  172. validation.email.valid = false;
  173. } else {
  174. validation.email.message = "Everything looks great!";
  175. validation.email.valid = true;
  176. }
  177. }
  178. );
  179. </script>
  180. <template>
  181. <div class="content account-tab">
  182. <h4 class="section-title">Change account details</h4>
  183. <p class="section-description">Keep these details up-to-date</p>
  184. <hr class="section-horizontal-rule" />
  185. <p class="control is-expanded margin-top-zero">
  186. <label for="username">Username</label>
  187. <input
  188. class="input"
  189. id="username"
  190. type="text"
  191. placeholder="Enter username here..."
  192. v-model="modifiedUser.username"
  193. maxlength="32"
  194. autocomplete="off"
  195. @keypress="onInput('username')"
  196. @paste="onInput('username')"
  197. />
  198. <span v-if="modifiedUser.username" class="character-counter"
  199. >{{ modifiedUser.username.length }}/32</span
  200. >
  201. </p>
  202. <transition name="fadein-helpbox">
  203. <input-help-box
  204. :entered="validation.username.entered"
  205. :valid="validation.username.valid"
  206. :message="validation.username.message"
  207. />
  208. </transition>
  209. <p class="control is-expanded">
  210. <label for="email">Email</label>
  211. <input
  212. class="input"
  213. id="email"
  214. type="text"
  215. placeholder="Enter email address here..."
  216. v-if="modifiedUser.email"
  217. v-model="modifiedUser.email.address"
  218. @keypress="onInput('email')"
  219. @paste="onInput('email')"
  220. autocomplete="off"
  221. />
  222. </p>
  223. <transition name="fadein-helpbox">
  224. <input-help-box
  225. :entered="validation.email.entered"
  226. :valid="validation.email.valid"
  227. :message="validation.email.message"
  228. />
  229. </transition>
  230. <SaveButton ref="saveButton" @clicked="saveChanges()" />
  231. <div class="section-margin-bottom" />
  232. <h4 class="section-title">Remove any data we hold on you</h4>
  233. <p class="section-description">
  234. Permanently remove your account and/or data we store on you
  235. </p>
  236. <hr class="section-horizontal-rule" />
  237. <div class="row">
  238. <quick-confirm @confirm="removeActivities()">
  239. <a class="button is-warning">
  240. <i class="material-icons icon-with-button">cancel</i>
  241. Clear my activities
  242. </a>
  243. </quick-confirm>
  244. <a class="button is-danger" @click="openModal('removeAccount')">
  245. <i class="material-icons icon-with-button">delete</i>
  246. Remove my account
  247. </a>
  248. </div>
  249. </div>
  250. </template>
  251. <style lang="less" scoped>
  252. .control {
  253. margin-bottom: 2px !important;
  254. }
  255. .row {
  256. display: flex;
  257. }
  258. </style>