Users.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <template>
  2. <div>
  3. <page-metadata title="Admin | Users" />
  4. <div class="container">
  5. <h2 v-if="dataRequests.length > 0">Data Requests</h2>
  6. <table class="table is-striped" v-if="dataRequests.length > 0">
  7. <thead>
  8. <tr>
  9. <td>User ID</td>
  10. <td>Request Type</td>
  11. <td>Options</td>
  12. </tr>
  13. </thead>
  14. <tbody>
  15. <tr v-for="(request, index) in dataRequests" :key="index">
  16. <td>{{ request.userId }}</td>
  17. <td>
  18. {{
  19. request.type === "remove"
  20. ? "Remove all associated data"
  21. : request.type
  22. }}
  23. </td>
  24. <td>
  25. <button
  26. class="button is-primary"
  27. @click="resolveDataRequest(request._id)"
  28. >
  29. Resolve
  30. </button>
  31. </td>
  32. </tr>
  33. </tbody>
  34. </table>
  35. <h1 id="page-title">Users</h1>
  36. <table class="table is-striped">
  37. <thead>
  38. <tr>
  39. <td class="ppRow">Profile Picture</td>
  40. <td>User ID</td>
  41. <td>GitHub ID</td>
  42. <td>Password</td>
  43. <td>Username</td>
  44. <td>Role</td>
  45. <td>Email Address</td>
  46. <td>Email Verified</td>
  47. <td>Songs Requested</td>
  48. <td>Options</td>
  49. </tr>
  50. </thead>
  51. <tbody>
  52. <tr v-for="user in users" :key="user._id">
  53. <td>
  54. <profile-picture
  55. :avatar="user.avatar"
  56. :name="user.name ? user.name : user.username"
  57. />
  58. </td>
  59. <td>{{ user._id }}</td>
  60. <td v-if="user.services.github">
  61. {{ user.services.github.id }}
  62. </td>
  63. <td v-else>Not Linked</td>
  64. <td v-if="user.hasPassword">Yes</td>
  65. <td v-else>Not Linked</td>
  66. <td>
  67. <a :href="'/u/' + user.username" target="_blank">{{
  68. user.username
  69. }}</a>
  70. </td>
  71. <td>{{ user.role }}</td>
  72. <td>{{ user.email.address }}</td>
  73. <td>{{ user.email.verified }}</td>
  74. <td>{{ user.songsRequested }}</td>
  75. <td>
  76. <button
  77. class="button is-primary"
  78. @click="edit(user)"
  79. >
  80. Edit
  81. </button>
  82. </td>
  83. </tr>
  84. </tbody>
  85. </table>
  86. </div>
  87. <edit-user
  88. v-if="modals.editUser"
  89. :user-id="editingUserId"
  90. sector="admin"
  91. />
  92. </div>
  93. </template>
  94. <script>
  95. import { mapState, mapActions, mapGetters } from "vuex";
  96. import { defineAsyncComponent } from "vue";
  97. import Toast from "toasters";
  98. import ProfilePicture from "@/components/ProfilePicture.vue";
  99. import ws from "@/ws";
  100. export default {
  101. components: {
  102. EditUser: defineAsyncComponent(() =>
  103. import("@/components/modals/EditUser.vue")
  104. ),
  105. ProfilePicture
  106. },
  107. data() {
  108. return {
  109. editingUserId: "",
  110. dataRequests: [],
  111. users: []
  112. };
  113. },
  114. computed: {
  115. ...mapState("modalVisibility", {
  116. modals: state => state.modals
  117. }),
  118. ...mapGetters({
  119. socket: "websockets/getSocket"
  120. })
  121. },
  122. mounted() {
  123. ws.onConnect(this.init);
  124. this.socket.on("event:admin.dataRequests.created", res =>
  125. this.dataRequests.push(res.data.request)
  126. );
  127. this.socket.on("event:admin.dataRequests.resolved", res => {
  128. this.dataRequests = this.dataRequests.filter(
  129. request => request._id !== res.data.dataRequestId
  130. );
  131. });
  132. },
  133. methods: {
  134. edit(user) {
  135. this.editingUserId = user._id;
  136. this.openModal("editUser");
  137. },
  138. init() {
  139. this.socket.dispatch("users.index", res => {
  140. if (res.status === "success") {
  141. this.users = res.data.users;
  142. if (this.$route.query.userId) {
  143. const user = this.users.find(
  144. user => user._id === this.$route.query.userId
  145. );
  146. if (user) this.edit(user);
  147. }
  148. }
  149. });
  150. this.socket.dispatch("dataRequests.index", res => {
  151. if (res.status === "success")
  152. this.dataRequests = res.data.requests;
  153. });
  154. this.socket.dispatch("apis.joinAdminRoom", "users", () => {});
  155. },
  156. resolveDataRequest(id) {
  157. this.socket.dispatch("dataRequests.resolve", id, res => {
  158. if (res.status === "success") new Toast(res.message);
  159. });
  160. },
  161. ...mapActions("modalVisibility", ["openModal"])
  162. }
  163. };
  164. </script>
  165. <style lang="scss" scoped>
  166. .night-mode {
  167. .table {
  168. color: var(--light-grey-2);
  169. background-color: var(--dark-grey-3);
  170. thead tr {
  171. background: var(--dark-grey-3);
  172. td {
  173. color: var(--white);
  174. }
  175. }
  176. tbody tr:hover {
  177. background-color: var(--dark-grey-4) !important;
  178. }
  179. tbody tr:nth-child(even) {
  180. background-color: var(--dark-grey-2);
  181. }
  182. strong {
  183. color: var(--light-grey-2);
  184. }
  185. }
  186. }
  187. body {
  188. font-family: "Hind", sans-serif;
  189. }
  190. .profile-picture {
  191. max-width: 50px !important;
  192. max-height: 50px !important;
  193. }
  194. /deep/ .profile-picture.using-initials span {
  195. font-size: 20px; // 2/5th of .profile-picture height/width
  196. }
  197. td {
  198. vertical-align: middle;
  199. &.ppRow {
  200. max-width: 50px;
  201. }
  202. }
  203. .is-primary:focus {
  204. background-color: var(--primary-color) !important;
  205. }
  206. </style>