EditUser.vue 6.7 KB

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