News.vue 8.6 KB

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