EditUser.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <template>
  2. <div>
  3. <modal title="Edit User">
  4. <template #body v-if="user && user._id">
  5. <div class="section">
  6. <label class="label"> Change username </label>
  7. <p class="control has-addons">
  8. <input
  9. v-model="user.username"
  10. class="input is-expanded"
  11. type="text"
  12. placeholder="Username"
  13. autofocus
  14. />
  15. <a class="button is-info" @click="updateUsername()"
  16. >Update Username</a
  17. >
  18. </p>
  19. <label class="label"> Change email address </label>
  20. <p class="control has-addons">
  21. <input
  22. v-model="user.email.address"
  23. class="input is-expanded"
  24. type="text"
  25. placeholder="Email Address"
  26. autofocus
  27. />
  28. <a class="button is-info" @click="updateEmail()"
  29. >Update Email Address</a
  30. >
  31. </p>
  32. <label class="label"> Change user role </label>
  33. <div class="control is-grouped input-with-button">
  34. <div class="control is-expanded select">
  35. <select v-model="user.role">
  36. <option>default</option>
  37. <option>admin</option>
  38. </select>
  39. </div>
  40. <p class="control">
  41. <a class="button is-info" @click="updateRole()"
  42. >Update Role</a
  43. >
  44. </p>
  45. </div>
  46. </div>
  47. <div class="section">
  48. <label class="label"> Punish/Ban User </label>
  49. <p class="control has-addons">
  50. <span class="select">
  51. <select v-model="ban.expiresAt">
  52. <option value="1h">1 Hour</option>
  53. <option value="12h">12 Hours</option>
  54. <option value="1d">1 Day</option>
  55. <option value="1w">1 Week</option>
  56. <option value="1m">1 Month</option>
  57. <option value="3m">3 Months</option>
  58. <option value="6m">6 Months</option>
  59. <option value="1y">1 Year</option>
  60. </select>
  61. </span>
  62. <input
  63. v-model="ban.reason"
  64. class="input is-expanded"
  65. type="text"
  66. placeholder="Ban reason"
  67. autofocus
  68. />
  69. <a class="button is-danger" @click="banUser()">
  70. Ban user
  71. </a>
  72. </p>
  73. </div>
  74. </template>
  75. <template #footer>
  76. <confirm @confirm="removeSessions()">
  77. <a class="button is-warning"> Remove all sessions </a>
  78. </confirm>
  79. <confirm @confirm="removeAccount()">
  80. <a class="button is-danger"> Remove account </a>
  81. </confirm>
  82. </template>
  83. </modal>
  84. </div>
  85. </template>
  86. <script>
  87. import { mapState, mapGetters, mapActions } from "vuex";
  88. import Toast from "toasters";
  89. import validation from "@/validation";
  90. import ws from "@/ws";
  91. import Modal from "../Modal.vue";
  92. import Confirm from "@/components/Confirm.vue";
  93. export default {
  94. components: { Modal, Confirm },
  95. props: {
  96. userId: { type: String, default: "" },
  97. sector: { type: String, default: "admin" }
  98. },
  99. data() {
  100. return {
  101. ban: {
  102. expiresAt: "1h"
  103. }
  104. };
  105. },
  106. computed: {
  107. ...mapState("modals/editUser", {
  108. user: state => state.user
  109. }),
  110. ...mapGetters({
  111. socket: "websockets/getSocket"
  112. })
  113. },
  114. mounted() {
  115. ws.onConnect(this.init);
  116. },
  117. beforeUnmount() {
  118. this.socket.dispatch(
  119. "apis.leaveRoom",
  120. `edit-user.${this.userId}`,
  121. () => {}
  122. );
  123. },
  124. methods: {
  125. init() {
  126. this.socket.dispatch(`users.getUserFromId`, this.userId, res => {
  127. if (res.status === "success") {
  128. const user = res.data;
  129. this.editUser(user);
  130. this.socket.dispatch(
  131. "apis.joinRoom",
  132. `edit-user.${this.userId}`
  133. );
  134. this.socket.on(
  135. "event:user.removed",
  136. res => {
  137. if (res.data.userId === this.userId)
  138. this.closeModal("editUser");
  139. },
  140. { modal: "editUser" }
  141. );
  142. } else {
  143. new Toast("User with that ID not found");
  144. this.closeModal("editUser");
  145. }
  146. });
  147. },
  148. updateUsername() {
  149. const { username } = this.user;
  150. if (!validation.isLength(username, 2, 32))
  151. return new Toast(
  152. "Username must have between 2 and 32 characters."
  153. );
  154. if (!validation.regex.custom("a-zA-Z0-9_-").test(username))
  155. return new Toast(
  156. "Invalid username format. Allowed characters: a-z, A-Z, 0-9, _ and -."
  157. );
  158. return this.socket.dispatch(
  159. `users.updateUsername`,
  160. this.user._id,
  161. username,
  162. res => {
  163. new Toast(res.message);
  164. }
  165. );
  166. },
  167. updateEmail() {
  168. const email = this.user.email.address;
  169. if (!validation.isLength(email, 3, 254))
  170. return new Toast(
  171. "Email must have between 3 and 254 characters."
  172. );
  173. if (
  174. email.indexOf("@") !== email.lastIndexOf("@") ||
  175. !validation.regex.emailSimple.test(email) ||
  176. !validation.regex.ascii.test(email)
  177. )
  178. return new Toast("Invalid email format.");
  179. return this.socket.dispatch(
  180. `users.updateEmail`,
  181. this.user._id,
  182. email,
  183. res => {
  184. new Toast(res.message);
  185. }
  186. );
  187. },
  188. updateRole() {
  189. this.socket.dispatch(
  190. `users.updateRole`,
  191. this.user._id,
  192. this.user.role,
  193. res => {
  194. new Toast(res.message);
  195. }
  196. );
  197. },
  198. banUser() {
  199. const { reason } = this.ban;
  200. if (!validation.isLength(reason, 1, 64))
  201. return new Toast(
  202. "Reason must have between 1 and 64 characters."
  203. );
  204. if (!validation.regex.ascii.test(reason))
  205. return new Toast(
  206. "Invalid reason format. Only ascii characters are allowed."
  207. );
  208. return this.socket.dispatch(
  209. `users.banUserById`,
  210. this.user._id,
  211. this.ban.reason,
  212. this.ban.expiresAt,
  213. res => {
  214. new Toast(res.message);
  215. }
  216. );
  217. },
  218. removeAccount() {
  219. this.socket.dispatch(`users.adminRemove`, this.user._id, res => {
  220. new Toast(res.message);
  221. });
  222. },
  223. removeSessions() {
  224. this.socket.dispatch(`users.removeSessions`, this.user._id, res => {
  225. new Toast(res.message);
  226. });
  227. },
  228. ...mapActions("modals/editUser", ["editUser"]),
  229. ...mapActions("modalVisibility", ["closeModal"])
  230. }
  231. };
  232. </script>
  233. <style lang="scss" scoped>
  234. .night-mode .section {
  235. background-color: transparent !important;
  236. }
  237. .section {
  238. padding: 15px 0 !important;
  239. }
  240. .save-changes {
  241. color: var(--white);
  242. }
  243. .tag:not(:last-child) {
  244. margin-right: 5px;
  245. }
  246. .select:after {
  247. border-color: var(--primary-color);
  248. }
  249. </style>