EditUser.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  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. <quick-confirm @confirm="resendVerificationEmail()">
  77. <a class="button is-warning"> Resend verification email </a>
  78. </quick-confirm>
  79. <quick-confirm @confirm="requestPasswordReset()">
  80. <a class="button is-warning"> Request password reset </a>
  81. </quick-confirm>
  82. <quick-confirm @confirm="removeSessions()">
  83. <a class="button is-warning"> Remove all sessions </a>
  84. </quick-confirm>
  85. <quick-confirm @confirm="removeAccount()">
  86. <a class="button is-danger"> Remove account </a>
  87. </quick-confirm>
  88. </template>
  89. </modal>
  90. </div>
  91. </template>
  92. <script>
  93. import { mapState, mapGetters, mapActions } from "vuex";
  94. import Toast from "toasters";
  95. import validation from "@/validation";
  96. import ws from "@/ws";
  97. import Modal from "../Modal.vue";
  98. import QuickConfirm from "@/components/QuickConfirm.vue";
  99. export default {
  100. components: { Modal, QuickConfirm },
  101. props: {
  102. userId: { type: String, default: "" },
  103. sector: { type: String, default: "admin" }
  104. },
  105. data() {
  106. return {
  107. ban: {
  108. expiresAt: "1h"
  109. }
  110. };
  111. },
  112. computed: {
  113. ...mapState("modals/editUser", {
  114. user: state => state.user
  115. }),
  116. ...mapGetters({
  117. socket: "websockets/getSocket"
  118. })
  119. },
  120. mounted() {
  121. ws.onConnect(this.init);
  122. },
  123. beforeUnmount() {
  124. this.socket.dispatch(
  125. "apis.leaveRoom",
  126. `edit-user.${this.userId}`,
  127. () => {}
  128. );
  129. },
  130. methods: {
  131. init() {
  132. this.socket.dispatch(`users.getUserFromId`, this.userId, res => {
  133. if (res.status === "success") {
  134. const user = res.data;
  135. this.editUser(user);
  136. this.socket.dispatch(
  137. "apis.joinRoom",
  138. `edit-user.${this.userId}`
  139. );
  140. this.socket.on(
  141. "event:user.removed",
  142. res => {
  143. if (res.data.userId === this.userId)
  144. this.closeModal("editUser");
  145. },
  146. { modal: "editUser" }
  147. );
  148. } else {
  149. new Toast("User with that ID not found");
  150. this.closeModal("editUser");
  151. }
  152. });
  153. },
  154. updateUsername() {
  155. const { username } = this.user;
  156. if (!validation.isLength(username, 2, 32))
  157. return new Toast(
  158. "Username must have between 2 and 32 characters."
  159. );
  160. if (!validation.regex.custom("a-zA-Z0-9_-").test(username))
  161. return new Toast(
  162. "Invalid username format. Allowed characters: a-z, A-Z, 0-9, _ and -."
  163. );
  164. return this.socket.dispatch(
  165. `users.updateUsername`,
  166. this.user._id,
  167. username,
  168. res => {
  169. new Toast(res.message);
  170. }
  171. );
  172. },
  173. updateEmail() {
  174. const email = this.user.email.address;
  175. if (!validation.isLength(email, 3, 254))
  176. return new Toast(
  177. "Email must have between 3 and 254 characters."
  178. );
  179. if (
  180. email.indexOf("@") !== email.lastIndexOf("@") ||
  181. !validation.regex.emailSimple.test(email) ||
  182. !validation.regex.ascii.test(email)
  183. )
  184. return new Toast("Invalid email format.");
  185. return this.socket.dispatch(
  186. `users.updateEmail`,
  187. this.user._id,
  188. email,
  189. res => {
  190. new Toast(res.message);
  191. }
  192. );
  193. },
  194. updateRole() {
  195. this.socket.dispatch(
  196. `users.updateRole`,
  197. this.user._id,
  198. this.user.role,
  199. res => {
  200. new Toast(res.message);
  201. }
  202. );
  203. },
  204. banUser() {
  205. const { reason } = this.ban;
  206. if (!validation.isLength(reason, 1, 64))
  207. return new Toast(
  208. "Reason must have between 1 and 64 characters."
  209. );
  210. if (!validation.regex.ascii.test(reason))
  211. return new Toast(
  212. "Invalid reason format. Only ascii characters are allowed."
  213. );
  214. return this.socket.dispatch(
  215. `users.banUserById`,
  216. this.user._id,
  217. this.ban.reason,
  218. this.ban.expiresAt,
  219. res => {
  220. new Toast(res.message);
  221. }
  222. );
  223. },
  224. resendVerificationEmail() {
  225. this.socket.dispatch(
  226. `users.resendVerifyEmail`,
  227. this.user._id,
  228. res => {
  229. new Toast(res.message);
  230. }
  231. );
  232. },
  233. requestPasswordReset() {
  234. this.socket.dispatch(
  235. `users.adminRequestPasswordReset`,
  236. this.user._id,
  237. res => {
  238. new Toast(res.message);
  239. }
  240. );
  241. },
  242. removeAccount() {
  243. this.socket.dispatch(`users.adminRemove`, this.user._id, res => {
  244. new Toast(res.message);
  245. });
  246. },
  247. removeSessions() {
  248. this.socket.dispatch(`users.removeSessions`, this.user._id, res => {
  249. new Toast(res.message);
  250. });
  251. },
  252. ...mapActions("modals/editUser", ["editUser"]),
  253. ...mapActions("modalVisibility", ["closeModal"])
  254. }
  255. };
  256. </script>
  257. <style lang="scss" scoped>
  258. .night-mode .section {
  259. background-color: transparent !important;
  260. }
  261. .section {
  262. padding: 15px 0 !important;
  263. }
  264. .save-changes {
  265. color: var(--white);
  266. }
  267. .tag:not(:last-child) {
  268. margin-right: 5px;
  269. }
  270. .select:after {
  271. border-color: var(--primary-color);
  272. }
  273. </style>