News.vue 8.6 KB

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