News.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <div>
  3. <metadata title="Admin | News" />
  4. <div class="container">
  5. <table class="table is-striped">
  6. <thead>
  7. <tr>
  8. <td>Title</td>
  9. <td>Description</td>
  10. <td>Bugs</td>
  11. <td>Features</td>
  12. <td>Improvements</td>
  13. <td>Upcoming</td>
  14. <td>Options</td>
  15. </tr>
  16. </thead>
  17. <tbody>
  18. <tr v-for="(news, index) in news" :key="index">
  19. <td>
  20. <strong>{{ news.title }}</strong>
  21. </td>
  22. <td>{{ news.description }}</td>
  23. <td>{{ news.bugs.join(", ") }}</td>
  24. <td>{{ news.features.join(", ") }}</td>
  25. <td>{{ news.improvements.join(", ") }}</td>
  26. <td>{{ news.upcoming.join(", ") }}</td>
  27. <td>
  28. <button
  29. class="button is-primary"
  30. @click="editNewsClick(news)"
  31. >
  32. Edit
  33. </button>
  34. <confirm @confirm="remove(news._id)">
  35. <button class="button is-danger">Remove</button>
  36. </confirm>
  37. </td>
  38. </tr>
  39. </tbody>
  40. </table>
  41. <div class="card is-fullwidth">
  42. <header class="card-header">
  43. <p class="card-header-title">Create News</p>
  44. </header>
  45. <div class="card-content">
  46. <div class="content">
  47. <label class="label">Title & Description</label>
  48. <div class="control is-horizontal">
  49. <div class="control is-grouped">
  50. <p class="control is-expanded">
  51. <input
  52. v-model="creating.title"
  53. class="input"
  54. type="text"
  55. placeholder="Title"
  56. />
  57. </p>
  58. <p class="control is-expanded">
  59. <input
  60. v-model="creating.description"
  61. class="input"
  62. type="text"
  63. placeholder="Short description"
  64. />
  65. </p>
  66. </div>
  67. </div>
  68. <div class="columns">
  69. <div class="column">
  70. <label class="label">Bugs</label>
  71. <p class="control has-addons">
  72. <input
  73. ref="new-bugs"
  74. class="input"
  75. type="text"
  76. placeholder="Bug"
  77. @keyup.enter="addChange('bugs')"
  78. />
  79. <a
  80. class="button is-info"
  81. href="#"
  82. @click="addChange('bugs')"
  83. >Add</a
  84. >
  85. </p>
  86. <span
  87. v-for="(bug, index) in creating.bugs"
  88. :key="index"
  89. class="tag is-info"
  90. >
  91. {{ bug }}
  92. <button
  93. class="delete is-info"
  94. @click="removeChange('bugs', index)"
  95. />
  96. </span>
  97. </div>
  98. <div class="column">
  99. <label class="label">Features</label>
  100. <p class="control has-addons">
  101. <input
  102. ref="new-features"
  103. class="input"
  104. type="text"
  105. placeholder="Feature"
  106. @keyup.enter="addChange('features')"
  107. />
  108. <a
  109. class="button is-info"
  110. href="#"
  111. @click="addChange('features')"
  112. >Add</a
  113. >
  114. </p>
  115. <span
  116. v-for="(feature,
  117. index) in creating.features"
  118. :key="index"
  119. class="tag is-info"
  120. >
  121. {{ feature }}
  122. <button
  123. class="delete is-info"
  124. @click="removeChange('features', index)"
  125. />
  126. </span>
  127. </div>
  128. </div>
  129. <div class="columns">
  130. <div class="column">
  131. <label class="label">Improvements</label>
  132. <p class="control has-addons">
  133. <input
  134. ref="new-improvements"
  135. class="input"
  136. type="text"
  137. placeholder="Improvement"
  138. @keyup.enter="addChange('improvements')"
  139. />
  140. <a
  141. class="button is-info"
  142. href="#"
  143. @click="addChange('improvements')"
  144. >Add</a
  145. >
  146. </p>
  147. <span
  148. v-for="(improvement,
  149. index) in creating.improvements"
  150. :key="index"
  151. class="tag is-info"
  152. >
  153. {{ improvement }}
  154. <button
  155. class="delete is-info"
  156. @click="
  157. removeChange('improvements', index)
  158. "
  159. />
  160. </span>
  161. </div>
  162. <div class="column">
  163. <label class="label">Upcoming</label>
  164. <p class="control has-addons">
  165. <input
  166. ref="new-upcoming"
  167. class="input"
  168. type="text"
  169. placeholder="Upcoming"
  170. @keyup.enter="addChange('upcoming')"
  171. />
  172. <a
  173. class="button is-info"
  174. href="#"
  175. @click="addChange('upcoming')"
  176. >Add</a
  177. >
  178. </p>
  179. <span
  180. v-for="(upcoming,
  181. index) in creating.upcoming"
  182. :key="index"
  183. class="tag is-info"
  184. >
  185. {{ upcoming }}
  186. <button
  187. class="delete is-info"
  188. @click="removeChange('upcoming', index)"
  189. />
  190. </span>
  191. </div>
  192. </div>
  193. </div>
  194. </div>
  195. <footer class="card-footer">
  196. <a class="card-footer-item" @click="createNews()" href="#"
  197. >Create</a
  198. >
  199. </footer>
  200. </div>
  201. </div>
  202. <edit-news
  203. v-if="modals.editNews"
  204. :news-id="editingNewsId"
  205. sector="admin"
  206. />
  207. </div>
  208. </template>
  209. <script>
  210. import { mapActions, mapState, mapGetters } from "vuex";
  211. import Toast from "toasters";
  212. import ws from "@/ws";
  213. import Confirm from "@/components/Confirm.vue";
  214. export default {
  215. components: {
  216. Confirm,
  217. EditNews: () => import("@/components/modals/EditNews.vue")
  218. },
  219. data() {
  220. return {
  221. editingNewsId: "",
  222. creating: {
  223. title: "",
  224. description: "",
  225. bugs: [],
  226. features: [],
  227. improvements: [],
  228. upcoming: []
  229. }
  230. };
  231. },
  232. computed: {
  233. ...mapState("modalVisibility", {
  234. modals: state => state.modals
  235. }),
  236. ...mapState("admin/news", {
  237. news: state => state.news
  238. }),
  239. ...mapGetters({
  240. socket: "websockets/getSocket"
  241. })
  242. },
  243. mounted() {
  244. this.socket.dispatch("news.index", res => {
  245. if (res.status === "success")
  246. res.data.news.forEach(news => this.addNews(news));
  247. });
  248. this.socket.on("event:admin.news.created", res =>
  249. this.addNews(res.data.news)
  250. );
  251. this.socket.on("event:admin.news.updated", res =>
  252. this.updateNews(res.data.news)
  253. );
  254. this.socket.on("event:admin.news.removed", res =>
  255. this.removeNews(res.data.newsId)
  256. );
  257. if (this.socket.readyState === 1) this.init();
  258. ws.onConnect(() => this.init());
  259. },
  260. methods: {
  261. createNews() {
  262. const {
  263. creating: { bugs, features, improvements, upcoming }
  264. } = this;
  265. if (this.creating.title === "")
  266. return new Toast("Field (Title) cannot be empty");
  267. if (this.creating.description === "")
  268. return new Toast("Field (Description) cannot be empty");
  269. if (
  270. bugs.length <= 0 &&
  271. features.length <= 0 &&
  272. improvements.length <= 0 &&
  273. upcoming.length <= 0
  274. )
  275. return new Toast("You must have at least one News Item");
  276. return this.socket.dispatch("news.create", this.creating, res => {
  277. new Toast(res.message, 4000);
  278. if (res.status === "success")
  279. this.creating = {
  280. title: "",
  281. description: "",
  282. bugs: [],
  283. features: [],
  284. improvements: [],
  285. upcoming: []
  286. };
  287. });
  288. },
  289. remove(id) {
  290. this.socket.dispatch(
  291. "news.remove",
  292. id,
  293. res => new Toast(res.message)
  294. );
  295. },
  296. editNewsClick(news) {
  297. this.editingNewsId = news._id;
  298. this.openModal("editNews");
  299. },
  300. addChange(type) {
  301. const change = this.$refs[`new-${type}`].value.trim();
  302. if (this.creating[type].indexOf(change) !== -1)
  303. return new Toast(`Tag already exists`);
  304. if (change) {
  305. this.$refs[`new-${type}`].value = "";
  306. this.creating[type].push(change);
  307. return true;
  308. }
  309. return new Toast(`${type} cannot be empty`);
  310. },
  311. removeChange(type, index) {
  312. this.creating[type].splice(index, 1);
  313. },
  314. init() {
  315. this.socket.dispatch("apis.joinAdminRoom", "news", () => {});
  316. },
  317. ...mapActions("modalVisibility", ["openModal", "closeModal"]),
  318. ...mapActions("admin/news", [
  319. "editNews",
  320. "addNews",
  321. "removeNews",
  322. "updateNews"
  323. ])
  324. }
  325. };
  326. </script>
  327. <style lang="scss" scoped>
  328. .night-mode {
  329. .table {
  330. color: var(--light-grey-2);
  331. background-color: var(--dark-grey-3);
  332. thead tr {
  333. background: var(--dark-grey-3);
  334. td {
  335. color: var(--white);
  336. }
  337. }
  338. tbody tr:hover {
  339. background-color: var(--dark-grey-4) !important;
  340. }
  341. tbody tr:nth-child(even) {
  342. background-color: var(--dark-grey-2);
  343. }
  344. strong {
  345. color: var(--light-grey-2);
  346. }
  347. }
  348. .card {
  349. background: var(--dark-grey-3);
  350. .card-header {
  351. box-shadow: 0 1px 2px rgba(10, 10, 10, 0.8);
  352. }
  353. p,
  354. .label {
  355. color: var(--light-grey-2);
  356. }
  357. }
  358. }
  359. .tag:not(:last-child) {
  360. margin-right: 5px;
  361. }
  362. td {
  363. vertical-align: middle;
  364. & > div {
  365. display: inline-flex;
  366. }
  367. }
  368. .is-info:focus {
  369. background-color: var(--primary-color);
  370. }
  371. .card-footer-item {
  372. color: var(--primary-color);
  373. }
  374. </style>