Account.vue 7.5 KB

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