EditUser.vue 6.9 KB

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