News.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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="removeNews(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. news: [],
  220. creating: {
  221. title: "",
  222. description: "",
  223. bugs: [],
  224. features: [],
  225. improvements: [],
  226. upcoming: []
  227. }
  228. };
  229. },
  230. mounted() {
  231. io.getSocket(socket => {
  232. this.socket = socket;
  233. this.socket.emit("news.index", res => {
  234. this.news = res.data;
  235. return res.data;
  236. });
  237. this.socket.on("event:admin.news.created", news => {
  238. this.news.unshift(news);
  239. });
  240. this.socket.on("event:admin.news.removed", news => {
  241. this.news = this.news.filter(item => item._id !== news._id);
  242. });
  243. if (this.socket.connected) this.init();
  244. io.onConnect(() => this.init());
  245. });
  246. },
  247. computed: {
  248. ...mapState("modals", {
  249. modals: state => state.modals.admin
  250. }),
  251. ...mapState("admin/news", {
  252. editing: state => state.editing
  253. })
  254. },
  255. methods: {
  256. createNews() {
  257. const {
  258. creating: { bugs, features, improvements, upcoming }
  259. } = this;
  260. if (this.creating.title === "")
  261. return new Toast({
  262. content: "Field (Title) cannot be empty",
  263. timeout: 3000
  264. });
  265. if (this.creating.description === "")
  266. return new Toast({
  267. content: "Field (Description) cannot be empty",
  268. timeout: 3000
  269. });
  270. if (
  271. bugs.length <= 0 &&
  272. features.length <= 0 &&
  273. improvements.length <= 0 &&
  274. upcoming.length <= 0
  275. )
  276. return new Toast({
  277. content: "You must have at least one News Item",
  278. timeout: 3000
  279. });
  280. return this.socket.emit("news.create", this.creating, result => {
  281. new Toast(result.message, 4000);
  282. if (result.status === "success")
  283. this.creating = {
  284. title: "",
  285. description: "",
  286. bugs: [],
  287. features: [],
  288. improvements: [],
  289. upcoming: []
  290. };
  291. });
  292. },
  293. removeNews(news) {
  294. this.socket.emit(
  295. "news.remove",
  296. news,
  297. res => new Toast({ content: res.message, timeout: 8000 })
  298. );
  299. },
  300. editNewsClick(news) {
  301. this.editNews(news);
  302. this.openModal({ sector: "admin", modal: "editNews" });
  303. },
  304. addChange(type) {
  305. const change = document.getElementById(`new-${type}`).value.trim();
  306. if (this.creating[type].indexOf(change) !== -1)
  307. return new Toast({
  308. content: `Tag already exists`,
  309. timeout: 3000
  310. });
  311. if (change) {
  312. document.getElementById(`new-${type}`).value = "";
  313. this.creating[type].push(change);
  314. return true;
  315. }
  316. return new Toast({
  317. content: `${type} cannot be empty`,
  318. timeout: 3000
  319. });
  320. },
  321. removeChange(type, index) {
  322. this.creating[type].splice(index, 1);
  323. },
  324. init() {
  325. this.socket.emit("apis.joinAdminRoom", "news", () => {});
  326. },
  327. ...mapActions("modals", ["openModal", "closeModal"]),
  328. ...mapActions("admin/news", ["editNews"])
  329. }
  330. };
  331. </script>
  332. <style lang="scss" scoped>
  333. @import "styles/global.scss";
  334. .night-mode {
  335. .table {
  336. color: #ddd;
  337. background-color: #222;
  338. thead tr {
  339. background: $night-mode-secondary;
  340. td {
  341. color: #fff;
  342. }
  343. }
  344. tbody tr:hover {
  345. background-color: #111 !important;
  346. }
  347. tbody tr:nth-child(even) {
  348. background-color: #444;
  349. }
  350. strong {
  351. color: #ddd;
  352. }
  353. }
  354. .card {
  355. background: #222;
  356. .card-header {
  357. box-shadow: 0 1px 2px rgba(10, 10, 10, 0.8);
  358. }
  359. p,
  360. .label {
  361. color: #ddd;
  362. }
  363. }
  364. }
  365. .tag:not(:last-child) {
  366. margin-right: 5px;
  367. }
  368. td {
  369. vertical-align: middle;
  370. }
  371. .is-info:focus {
  372. background-color: $primary-color;
  373. }
  374. .card-footer-item {
  375. color: $primary-color;
  376. }
  377. </style>