Procházet zdrojové kódy

新增 blog檔案 固定測試帳號

oransheep před 2 roky
rodič
revize
42f6784372
30 změnil soubory, kde provedl 1519 přidání a 226 odebrání
  1. 85 0
      app/Http/Controllers/MessageController.php
  2. 17 0
      app/Models/Message.php
  3. 1 0
      app/Models/User.php
  4. 26 0
      database/factories/MessageFactory.php
  5. 1 0
      database/factories/UserFactory.php
  6. 1 0
      database/migrations/2014_10_12_000000_create_users_table.php
  7. 34 0
      database/migrations/2022_12_12_100037_create_messages_table.php
  8. 27 1
      database/seeders/DatabaseSeeder.php
  9. 1 0
      resources/js/src/@core/auth/jwt/jwtDefaultConfig.js
  10. 8 0
      resources/js/src/@core/auth/jwt/jwtService.js
  11. binární
      resources/js/src/assets/images/slider/01.jpg
  12. binární
      resources/js/src/assets/images/slider/02.jpg
  13. binární
      resources/js/src/assets/images/slider/03.jpg
  14. binární
      resources/js/src/assets/images/slider/04.jpg
  15. binární
      resources/js/src/assets/images/slider/05.jpg
  16. binární
      resources/js/src/assets/images/slider/06.jpg
  17. binární
      resources/js/src/assets/images/slider/07.jpg
  18. binární
      resources/js/src/assets/images/slider/08.jpg
  19. binární
      resources/js/src/assets/images/slider/09.jpg
  20. binární
      resources/js/src/assets/images/slider/10.jpg
  21. 111 83
      resources/js/src/layouts/components/Navbar.vue
  22. 3 0
      resources/js/src/libs/acl/ability.js
  23. 3 3
      resources/js/src/navigation/vertical/index.js
  24. 3 4
      resources/js/src/router/index.js
  25. 168 119
      resources/js/src/views/CardAction.vue
  26. 8 8
      resources/js/src/views/Login.vue
  27. 450 0
      resources/js/src/views/blog/BlogDetail.vue
  28. 245 0
      resources/js/src/views/blog/BlogEdit.vue
  29. 301 0
      resources/js/src/views/blog/BlogList.vue
  30. 26 8
      routes/api.php

+ 85 - 0
app/Http/Controllers/MessageController.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Message;
+use Illuminate\Http\Request;
+
+class MessageController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\Models\Message  $message
+     * @return \Illuminate\Http\Response
+     */
+    public function show(Message $message)
+    {
+        //
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  \App\Models\Message  $message
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(Message $message)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\Models\Message  $message
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, Message $message)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\Models\Message  $message
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(Message $message)
+    {
+        //
+    }
+}

+ 17 - 0
app/Models/Message.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Message extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'title',
+        'content',
+        'writer',
+    ];
+}

+ 1 - 0
app/Models/User.php

@@ -21,6 +21,7 @@ class User extends Authenticatable
         'name',
         'email',
         'password',
+        'ability',
     ];
 
     /**

+ 26 - 0
database/factories/MessageFactory.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace Database\Factories;
+
+use Illuminate\Database\Eloquent\Factories\Factory;
+use App\Models\User;
+
+/**
+ * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Message>
+ */
+class MessageFactory extends Factory
+{
+    /**
+     * Define the model's default state.
+     *
+     * @return array<string, mixed>
+     */
+    public function definition()
+    {
+        return [
+            'title' => $this->faker->realText($maxNbChars = 20, $indexSize = 2),
+            'content' => $this->faker->realText($maxNbChars = 100, $indexSize = 2),
+            'writer' => $this->faker->randomElement(User::query()->get('id')),
+        ];
+    }
+}

+ 1 - 0
database/factories/UserFactory.php

@@ -23,6 +23,7 @@ class UserFactory extends Factory
             'email_verified_at' => now(),
             'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
             'remember_token' => Str::random(10),
+            'ability' => 'visitor',
         ];
     }
 

+ 1 - 0
database/migrations/2014_10_12_000000_create_users_table.php

@@ -21,6 +21,7 @@ return new class extends Migration
             $table->string('password');
             $table->rememberToken();
             $table->timestamps();
+            $table->string('ability');
         });
     }
 

+ 34 - 0
database/migrations/2022_12_12_100037_create_messages_table.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('messages', function (Blueprint $table) {
+            $table->id();
+            $table->string('title');
+            $table->string('content');
+            $table->string('writer');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('messages');
+    }
+};

+ 27 - 1
database/seeders/DatabaseSeeder.php

@@ -4,6 +4,8 @@ namespace Database\Seeders;
 
 use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 use Illuminate\Database\Seeder;
+use \App\Models\User;
+use \App\Models\Message;
 
 class DatabaseSeeder extends Seeder
 {
@@ -14,6 +16,30 @@ class DatabaseSeeder extends Seeder
      */
     public function run()
     {
-        \App\Models\User::factory(3)->create();
+
+        User::factory(3)->create();
+        User::insert([
+            [
+                'name' => 'kevin',
+                'email' => 'a@a.aa',
+                'email_verified_at' => now(),
+                'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
+                'remember_token' => 'uAyNEKCgra',
+                'created_at' => now(),
+                'updated_at' => now(),
+                'ability' => 'manager'
+            ],
+            [
+                'name' => 'test',
+                'email' => 'b@b.bb',
+                'email_verified_at' => now(),
+                'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
+                'remember_token' => 'L3mp4ZChgX',
+                'created_at' => now(),
+                'updated_at' => now(),
+                'ability' => 'visitor'
+            ]
+        ]);
+        Message::factory(50)->create();
     }
 }

+ 1 - 0
resources/js/src/@core/auth/jwt/jwtDefaultConfig.js

@@ -4,6 +4,7 @@ export default {
   registerEndpoint: '/jwt/register',
   refreshEndpoint: '/jwt/refresh-token',
   logoutEndpoint: '/api/logout',
+  getDatasetEndpoint: '/api/get_dataset',
 
   // This will be prefixed in authorization header with token
   // e.g. Authorization: Bearer <token>

+ 8 - 0
resources/js/src/@core/auth/jwt/jwtService.js

@@ -112,4 +112,12 @@ export default class JwtService {
       refreshToken: this.getRefreshToken(),
     })
   }
+
+  getDataset(...args) {
+    return this.axiosIns.post(this.jwtConfig.getDatasetEndpoint, ...args)
+  }
+
+  getMessages(...args) {
+    return this.axiosIns.post('/api/get_messages', ...args)
+  }
 }

binární
resources/js/src/assets/images/slider/01.jpg


binární
resources/js/src/assets/images/slider/02.jpg


binární
resources/js/src/assets/images/slider/03.jpg


binární
resources/js/src/assets/images/slider/04.jpg


binární
resources/js/src/assets/images/slider/05.jpg


binární
resources/js/src/assets/images/slider/06.jpg


binární
resources/js/src/assets/images/slider/07.jpg


binární
resources/js/src/assets/images/slider/08.jpg


binární
resources/js/src/assets/images/slider/09.jpg


binární
resources/js/src/assets/images/slider/10.jpg


+ 111 - 83
resources/js/src/layouts/components/Navbar.vue

@@ -1,107 +1,135 @@
 <template>
-  <div class="navbar-container d-flex content align-items-center">
+    <div class="navbar-container d-flex content align-items-center">
+        <!-- Nav Menu Toggler -->
+        <ul class="nav navbar-nav d-xl-none">
+            <li class="nav-item">
+                <b-link class="nav-link" @click="toggleVerticalMenuActive">
+                    <feather-icon icon="MenuIcon" size="21" />
+                </b-link>
+            </li>
+        </ul>
 
-    <!-- Nav Menu Toggler -->
-    <ul class="nav navbar-nav d-xl-none">
-      <li class="nav-item">
-        <b-link class="nav-link" @click="toggleVerticalMenuActive">
-          <feather-icon icon="MenuIcon" size="21" />
-        </b-link>
-      </li>
-    </ul>
+        <!-- Left Col -->
+        <div class="bookmark-wrapper align-items-center flex-grow-1 d-none d-lg-flex">
+            <dark-Toggler class="d-none d-lg-block" />
+        </div>
 
-    <!-- Left Col -->
-    <div class="bookmark-wrapper align-items-center flex-grow-1 d-none d-lg-flex">
-      <dark-Toggler class="d-none d-lg-block" />
-    </div>
-
-    <b-navbar-nav class="nav align-items-center ml-auto">
-      <b-nav-item-dropdown right toggle-class="d-flex align-items-center dropdown-user-link" class="dropdown-user">
-        <template #button-content>
-          <div class="d-sm-flex d-none user-nav">
-            <p class="user-name font-weight-bolder mb-0" >
-              {{ getName() }}
-            </p>
-            <span class="user-status">{{ getAbility() }}</span>
-          </div>
-          <b-avatar size="40" variant="light-primary" badge :src="require('@/assets/images/avatars/1-small.png')"
-            class="badge-minimal" badge-variant="success" />
-        </template>
+        <b-navbar-nav class="nav align-items-center ml-auto">
+            <b-nav-item-dropdown right toggle-class="d-flex align-items-center dropdown-user-link"
+                class="dropdown-user">
+                <template #button-content>
+                    <div class="d-sm-flex d-none user-nav">
+                        <p class="user-name font-weight-bolder mb-0">
+                            {{ getName() }}
+                        </p>
+                        <span class="user-status">{{ getAbility() }}</span>
+                    </div>
+                    <b-avatar size="40" variant="light-primary" badge
+                        :src="require('@/assets/images/avatars/1-small.png')" class="badge-minimal"
+                        badge-variant="success" />
+                </template>
 
-        <b-dropdown-item link-class="d-flex align-items-center">
-          <feather-icon size="16" icon="UserIcon" class="mr-50" />
-          <span>Profile</span>
-        </b-dropdown-item>
+                <div v-if="isLogin()">
+                    <b-dropdown-item link-class="d-flex align-items-center">
+                        <feather-icon size="16" icon="UserIcon" class="mr-50" />
+                        <span>Profile</span>
+                    </b-dropdown-item>
 
-        <b-dropdown-item link-class="d-flex align-items-center">
-          <feather-icon size="16" icon="MailIcon" class="mr-50" />
-          <span>Inbox</span>
-        </b-dropdown-item>
+                    <b-dropdown-item link-class="d-flex align-items-center">
+                        <feather-icon size="16" icon="MailIcon" class="mr-50" />
+                        <span>Inbox</span>
+                    </b-dropdown-item>
 
-        <b-dropdown-item link-class="d-flex align-items-center">
-          <feather-icon size="16" icon="CheckSquareIcon" class="mr-50" />
-          <span>Task</span>
-        </b-dropdown-item>
+                    <b-dropdown-item link-class="d-flex align-items-center">
+                        <feather-icon size="16" icon="CheckSquareIcon" class="mr-50" />
+                        <span>Task</span>
+                    </b-dropdown-item>
 
-        <b-dropdown-item link-class="d-flex align-items-center">
-          <feather-icon size="16" icon="MessageSquareIcon" class="mr-50" />
-          <span>Chat</span>
-        </b-dropdown-item>
+                    <b-dropdown-item link-class="d-flex align-items-center">
+                        <feather-icon size="16" icon="MessageSquareIcon" class="mr-50" />
+                        <span>Chat</span>
+                    </b-dropdown-item>
 
-        <b-dropdown-divider />
+                    <b-dropdown-divider />
 
-        <b-dropdown-item link-class="d-flex align-items-center" @click="logout">
-          <feather-icon size="16" icon="LogOutIcon" class="mr-50" />
-          <span>Logout</span>
-        </b-dropdown-item>
-      </b-nav-item-dropdown>
-    </b-navbar-nav>
-  </div>
+                    <b-dropdown-item link-class="d-flex align-items-center" @click="logout">
+                        <feather-icon size="16" icon="LogOutIcon" class="mr-50" />
+                        <span>登出</span>
+                    </b-dropdown-item>
+                </div>
+                <div v-else>
+                    <b-dropdown-item link-class="d-flex align-items-center" @click="toLogin">
+                        <feather-icon size="16" icon="LogInIcon" class="mr-50" />
+                        <span>登入</span>
+                    </b-dropdown-item>
+                </div>
+            </b-nav-item-dropdown>
+        </b-navbar-nav>
+    </div>
 </template>
 
 <script>
 import {
-  BLink, BNavbarNav, BNavItemDropdown, BDropdownItem, BDropdownDivider, BAvatar,
-} from 'bootstrap-vue'
-import DarkToggler from '@core/layouts/components/app-navbar/components/DarkToggler.vue'
-import useJwt from '@/auth/jwt/useJwt'
-
-export default {
-  components: {
     BLink,
     BNavbarNav,
     BNavItemDropdown,
     BDropdownItem,
     BDropdownDivider,
     BAvatar,
+} from "bootstrap-vue";
+import DarkToggler from "@core/layouts/components/app-navbar/components/DarkToggler.vue";
+import useJwt from "@/auth/jwt/useJwt";
+import { isUserLoggedIn } from "../../auth/utils";
 
-    // Navbar Components
-    DarkToggler,
-  },
-  props: {
-    toggleVerticalMenuActive: {
-      type: Function,
-      default: () => { },
-    },
-  },
-  methods: {
-    getName() {
-      return JSON.parse(localStorage.getItem('userData')).name
-    },
-    getEmail() {
-      return JSON.parse(localStorage.getItem('userData')).email
+export default {
+    components: {
+        BLink,
+        BNavbarNav,
+        BNavItemDropdown,
+        BDropdownItem,
+        BDropdownDivider,
+        BAvatar,
+
+        // Navbar Components
+        DarkToggler,
     },
-    getAbility() {
-      return JSON.parse(localStorage.getItem('userData')).ability
+    props: {
+        toggleVerticalMenuActive: {
+            type: Function,
+            default: () => { },
+        },
     },
-    logout() {
-      useJwt.logout()
-        // eslint-disable-next-line no-unused-vars
-        .then(res => {
-          localStorage.clear()
-          this.$router.push({ path: '/login' })
-        })
+    methods: {
+        getName() {
+            if (JSON.parse(localStorage.getItem("userData"))) {
+                return JSON.parse(localStorage.getItem("userData")).name;
+            }
+            return "訪客帳號";
+        },
+        getEmail() {
+            return JSON.parse(localStorage.getItem("userData")).email;
+        },
+        getAbility() {
+            if (JSON.parse(localStorage.getItem("userData"))) {
+                return JSON.parse(localStorage.getItem("userData")).ability;
+            }
+            return "guest";
+        },
+        logout() {
+            useJwt
+                .logout()
+                // eslint-disable-next-line no-unused-vars
+                .then((res) => {
+                    localStorage.clear();
+                    this.$router.push({ path: "/login" });
+                });
+        },
+        isLogin() {
+            return isUserLoggedIn();
+        },
+        toLogin() {
+            this.$router.push({ path: "/login" });
+        },
     },
-  },
-}
+};
 </script>

+ 3 - 0
resources/js/src/libs/acl/ability.js

@@ -26,6 +26,9 @@ export function getExistingAbility(userAbility) {
       break
 
     default:
+      can('read', 'all')
+      cannot('read', 'visited')
+      cannot('read', 'management')
       break
   }
   return rules

+ 3 - 3
resources/js/src/navigation/vertical/index.js

@@ -1,7 +1,7 @@
 export default [
   {
     header: '基本功能',
-    group: 'basic',
+    group: 'visited',
   },
   {
     title: 'Home',
@@ -13,13 +13,13 @@ export default [
     title: 'Second Page',
     route: 'second-page',
     icon: 'FileIcon',
-    group: 'basic',
+    group: 'visited',
   },
   {
     title: 'Card Action',
     route: 'card-action',
     icon: 'CreditCardIcon',
-    group: 'basic',
+    group: 'visited',
   },
   {
     header: '管理功能',

+ 3 - 4
resources/js/src/router/index.js

@@ -34,7 +34,7 @@ const router = new VueRouter({
       component: () => import('@/views/SecondPage.vue'),
       meta: {
         pageTitle: 'Second Page',
-        group: 'basic',
+        group: 'visited',
         breadcrumb: [
           {
             text: 'Second Page',
@@ -49,7 +49,7 @@ const router = new VueRouter({
       component: () => import('@/views/CardAction.vue'),
       meta: {
         pageTitle: 'Card Action',
-        group: 'basic',
+        group: 'visited',
         breadcrumb: [
           {
             text: 'Card Action',
@@ -105,7 +105,7 @@ const router = new VueRouter({
 })
 
 router.beforeEach((to, from, next) => {
-  if (to.path === '/login' || to.path === '/error-404') {
+  if (to.path === '/login' || to.path === '/error-404' || to.path === '/') {
     next()
   } else if (isUserLoggedIn()) {
     if (haveAbility(to)) {
@@ -113,7 +113,6 @@ router.beforeEach((to, from, next) => {
     } else {
       next({ path: '/error-401' })
     }
-    next()
   } else {
     next({ path: '/login', query: { redirect: to.fullPath } })
   }

+ 168 - 119
resources/js/src/views/CardAction.vue

@@ -1,138 +1,187 @@
 <template>
-  <section id="card-actions">
-    <b-row>
+	<section id="card-actions">
+		<b-row>
 
-      <b-col cols="12">
-        <b-card-actions ref="cardAction" title="ability test" @refresh="refreshStop('cardAction')">
-          <b-row>
-            <b-col cols="12">
-              <div v-if="checkAbility">
-                <b-table responsive :items="items" bordered>
-                  <template #cell(ICON)="data" class="text-center">
-                    <div class="text-center">
-                      <feather-icon :icon="data.value" />
-                    </div>
-                  </template>
-                </b-table>
-              </div>
-              <div v-else>沒權限</div>
-            </b-col>
-          </b-row>
-        </b-card-actions>
-      </b-col>
+			<b-col cols="12">
+				<b-card-actions ref="cardAction" title="ability test" @refresh="refreshStop('cardAction')">
+					<b-row>
+						<b-col cols="12">
+							<div v-if="checkAbility">
+								<b-table responsive :items="messages" bordered>
+									<template #cell(ICON)="data" class="text-center">
+										<div class="text-center">
+											<feather-icon :icon="data.value" />
+										</div>
+									</template>
+								</b-table>
+							</div>
+							<div v-else>沒權限</div>
+						</b-col>
+					</b-row>
+				</b-card-actions>
+			</b-col>
 
-      <!-- card actions -->
-      <b-col cols="12">
-        <b-card-actions ref="cardAction" title="Card Actions" @refresh="refreshStop('cardAction')">
-          <b-row>
-            <b-col cols="12">
-              <b-table responsive :items="items" bordered>
-                <template #cell(ICON)="data" class="text-center">
-                  <div class="text-center">
-                    <feather-icon :icon="data.value" />
-                  </div>
-                </template>
-              </b-table>
-            </b-col>
-          </b-row>
-        </b-card-actions>
-      </b-col>
 
-      <!-- card collapsible  -->
-      <b-col md="6">
-        <b-card-actions title="Collapsible" action-collapse>
-          <b-card-text>
-            <span>You can create a collapsible content by adding </span>
-            <code>actionCollapse</code>
-            <span> prop in </span>
-            <code>&lt;b-card-actions&gt;</code>
-          </b-card-text>
-          <b-card-text>
-            <span>Click on </span>
-            <feather-icon icon="ChevronDownIcon" />
-            <span> to see card collapse in action.</span>
-          </b-card-text>
-        </b-card-actions>
-      </b-col>
+			<b-col md="6" v-for="message in messages" :key="message.id">
+				<b-card :img-src="require('@/assets/images/slider/01.jpg')" img-alt="Card image" img-top no-body>
+					<!-- body -->
+					<b-card-body>
+						<b-card-title>{{ message.title }}</b-card-title>
+						<b-card-text>
+							{{ message.content }}
+						</b-card-text>
+					</b-card-body>
 
-      <!-- card refresh -->
-      <b-col md="6">
-        <b-card-actions ref="refreshCard" title="Refresh Content" action-refresh @refresh="refreshStop('refreshCard')">
-          <b-card-text>
-            To create a card with refresh action use <code>actionRefresh</code> prop along with
-            <code>&lt;b-card-actions&gt;</code>
-          </b-card-text>
-          <b-card-text>
-            <span>Click on </span>
-            <feather-icon icon="RotateCwIcon" />
-            <span> icon to see refresh card content in action.</span>
-          </b-card-text>
-        </b-card-actions>
-      </b-col>
+					<!-- card links -->
+					<b-card-body>
+						<b-card-text>
+							{{ message.name }} - {{ message.updated_at }}
+						</b-card-text>
+						<b-link class="card-link">
+							詳細內容
+						</b-link>
+					</b-card-body>
+				</b-card>
+			</b-col>
 
-      <!-- card remove -->
-      <b-col md="6">
-        <b-card-actions title="Remove Card" action-close>
-          <b-card-text>
-            You can create a closeable card by using <code>actionClose</code> prop along with
-            <code>&lt;b-card-actions&gt;</code>
-          </b-card-text>
-          <b-card-text>
-            <span>Click on </span>
-            <feather-icon icon="XIcon" />
-            <span> icon to see closeable card in action.</span>
-          </b-card-text>
-        </b-card-actions>
-      </b-col>
-    </b-row>
-  </section>
+
+			<b-col md="6" lg="6">
+				<b-card :img-src="require('@/assets/images/slider/01.jpg')" img-alt="Card image" img-top no-body>
+					<!-- body -->
+					<b-card-body>
+						<b-card-title>Card title</b-card-title>
+						<b-card-text>
+							Some quick example text to build on the card title.
+						</b-card-text>
+					</b-card-body>
+
+					<!-- kitchen sink link -->
+					<b-list-group flush>
+						<b-list-group-item v-for="data in kitchenSinkList" :key="data.text">
+							{{ data.text }}
+						</b-list-group-item>
+					</b-list-group>
+
+					<!-- card links -->
+					<b-card-body>
+						<b-link class="card-link">
+							Card link
+						</b-link>
+						<b-link class="card-link">
+							Another link
+						</b-link>
+					</b-card-body>
+				</b-card>
+			</b-col>
+
+			<!-- card collapsible  -->
+			<b-col md="6">
+				<b-card-actions title="Collapsible" action-collapse>
+					<b-card-text>
+						<span>You can create a collapsible content by adding </span>
+						<code>actionCollapse</code>
+						<span> prop in </span>
+						<code>&lt;b-card-actions&gt;</code>
+					</b-card-text>
+					<b-card-text>
+						<span>Click on </span>
+						<feather-icon icon="ChevronDownIcon" />
+						<span> to see card collapse in action.</span>
+					</b-card-text>
+				</b-card-actions>
+			</b-col>
+
+			<!-- card refresh -->
+			<b-col md="6">
+				<b-card-actions ref="refreshCard" title="Refresh Content" action-refresh
+					@refresh="refreshStop('refreshCard')">
+					<b-card-text>
+						To create a card with refresh action use <code>actionRefresh</code> prop along with
+						<code>&lt;b-card-actions&gt;</code>
+					</b-card-text>
+					<b-card-text>
+						<span>Click on </span>
+						<feather-icon icon="RotateCwIcon" />
+						<span> icon to see refresh card content in action.</span>
+					</b-card-text>
+				</b-card-actions>
+			</b-col>
+
+			<!-- card remove -->
+			<b-col md="6">
+				<b-card-actions title="Remove Card" action-close>
+					<b-card-text>
+						You can create a closeable card by using <code>actionClose</code> prop along with
+						<code>&lt;b-card-actions&gt;</code>
+					</b-card-text>
+					<b-card-text>
+						<span>Click on </span>
+						<feather-icon icon="XIcon" />
+						<span> icon to see closeable card in action.</span>
+					</b-card-text>
+				</b-card-actions>
+			</b-col>
+		</b-row>
+	</section>
 </template>
 
 <script>
 import BCardActions from '@core/components/b-card-actions/BCardActions.vue'
 import {
-  BRow, BCol, BTable, BCardText,
+	BTable, BRow, BCol, BCard, BCardText, BLink, BListGroup, BListGroupItem, BCardTitle, BCardBody,
 } from 'bootstrap-vue'
+import useJwt from '@/auth/jwt/useJwt'
 import ability from '../libs/acl/ability'
 
 export default {
-  components: {
-    BCardActions,
-    BRow,
-    BCol,
-    BTable,
-    BCardText,
-  },
-  data() {
-    return {
-      fields: [
-        'ACTION',
-        { key: 'ICON', label: 'ICON' },
-        'DETAILS',
-      ],
-      items: [
-        { ACTION: 'Collapse', ICON: 'ChevronDownIcon', DETAILS: 'Collapse card content using collapse action.' },
-        { ACTION: 'Refresh Content', ICON: 'RotateCwIcon', DETAILS: 'Refresh your card content using refresh action.' },
-        { ACTION: 'Remove Card', ICON: 'XIcon', DETAILS: 'Remove card from page using remove card action' },
-      ],
-      show: false,
-    }
-  },
-  computed: {
-    checkAbility() {
-      return ability.can('read', 'management')
-    },
-  },
-  methods: {
-
-    // stop refreshing card in 3 sec
-    refreshStop(cardName) {
-      setTimeout(() => {
-        this.$refs[cardName].showLoading = false
-      }, 3000)
-    },
+	components: {
+		BCardActions,
+		BTable,
+		BRow,
+		BCol,
+		BCard,
+		BCardText,
+		BLink,
+		BCardTitle,
+		BListGroup,
+		BListGroupItem,
+		BCardBody,
+	},
+	data() {
+		return {
+			fields: [
+				'ACTION',
+				{ key: 'ICON', label: 'ICON' },
+				'DETAILS',
+			],
+			show: false,
+			messages: [],
+			kitchenSinkList: [
+				{ text: 'Cras justo odio' },
+				{ text: 'Vestibulum at eros' },
+			],
+		}
+	},
+	computed: {
+		checkAbility() {
+			return ability.can('read', 'management')
+		},
+	},
+	created() {
+		useJwt.getMessages({ number: 2 })
+			.then(res => {
+				this.messages = res.data
+			})
+	},
+	methods: {
 
-  },
+		// stop refreshing card in 3 sec
+		refreshStop(cardName) {
+			setTimeout(() => {
+				this.$refs[cardName].showLoading = false
+			}, 3000)
+		},
+	},
 }
 
 </script>

+ 8 - 8
resources/js/src/views/Login.vue

@@ -175,14 +175,14 @@ export default {
     validationForm() {
       this.$refs.loginValidation.validate().then(success => {
         if (success) {
-          this.$toast({
-            component: ToastificationContent,
-            props: {
-              title: 'Form Submitted',
-              icon: 'EditIcon',
-              variant: 'success',
-            },
-          })
+          // this.$toast({
+          //   component: ToastificationContent,
+          //   props: {
+          //     title: 'Form Submitted',
+          //     icon: 'EditIcon',
+          //     variant: 'success',
+          //   },
+          // })
 
           useJwt.login({ email: this.userEmail, password: this.password })
             .then(res => {

+ 450 - 0
resources/js/src/views/blog/BlogDetail.vue

@@ -0,0 +1,450 @@
+<template>
+  <content-with-sidebar
+    v-if="Object.keys(blogDetail).length"
+    class="cws-container cws-sidebar-right blog-wrapper"
+  >
+
+    <!-- content -->
+    <div class="blog-detail-wrapper">
+      <b-row>
+        <!-- blogs -->
+        <b-col cols="12">
+          <b-card
+            :img-src="blogDetail.blog.img"
+            img-top
+            img-alt="Blog Detail Pic"
+            :title="blogDetail.blog.title"
+          >
+            <b-media no-body>
+              <b-media-aside
+                vertical-align="center"
+                class="mr-50"
+              >
+                <b-avatar
+                  href="javascript:void(0)"
+                  size="24"
+                  :src="blogDetail.blog.avatar"
+                />
+              </b-media-aside>
+              <b-media-body>
+                <small class="text-muted mr-50">by</small>
+                <small>
+                  <b-link class="text-body">{{ blogDetail.blog.userFullName }}</b-link>
+                </small>
+                <span class="text-muted ml-75 mr-50">|</span>
+                <small class="text-muted">{{ blogDetail.blog.createdTime }}</small>
+              </b-media-body>
+            </b-media>
+            <div class="my-1 py-25">
+              <b-link
+                v-for="tag in blogDetail.blog.tags"
+                :key="tag"
+              >
+                <b-badge
+                  pill
+                  class="mr-75"
+                  :variant="tagsColor(tag)"
+                >
+                  {{ tag }}
+                </b-badge>
+              </b-link>
+            </div>
+            <!-- eslint-disable vue/no-v-html -->
+            <div
+              class="blog-content"
+              v-html="blogDetail.blog.content"
+            />
+
+            <!-- user commnets -->
+            <b-media
+              v-for="user in blogDetail.blog.UserComment"
+              :key="user.avatar"
+              no-body
+            >
+              <b-media-aside>
+                <b-avatar
+                  size="60"
+                  :src="user.avatar"
+                />
+              </b-media-aside>
+              <b-media-body>
+                <h6 class="font-weight-bolder">
+                  {{ user.fullName }}
+                </h6>
+                <b-card-text>
+                  {{ user.comment }}
+                </b-card-text>
+              </b-media-body>
+            </b-media>
+            <!-- eslint-enable -->
+            <hr class="my-2">
+
+            <div class="d-flex align-items-center justify-content-between">
+              <div class="d-flex align-items-center">
+                <div class="d-flex align-items-center mr-1">
+                  <b-link class="mr-50">
+                    <feather-icon
+                      icon="MessageSquareIcon"
+                      size="21"
+                      class="text-body"
+                    />
+                  </b-link>
+                  <b-link>
+                    <div class="text-body">
+                      {{ kFormatter(blogDetail.blog.comments) }}
+                    </div>
+                  </b-link>
+                </div>
+                <div class="d-flex align-items-center">
+                  <b-link class="mr-50">
+                    <feather-icon
+                      size="21"
+                      icon="BookmarkIcon"
+                      class="text-body"
+                    />
+                  </b-link>
+                  <b-link>
+                    <div class="text-body">
+                      {{ kFormatter(blogDetail.blog.bookmarked) }}
+                    </div>
+                  </b-link>
+                </div>
+              </div>
+
+              <!-- dropdown -->
+              <div class="blog-detail-share">
+                <b-dropdown
+                  variant="link"
+                  toggle-class="p-0"
+                  no-caret
+                  right
+                >
+                  <template #button-content>
+                    <feather-icon
+                      size="21"
+                      icon="Share2Icon"
+                      class="text-body"
+                    />
+                  </template>
+                  <b-dropdown-item
+                    v-for="icon in socialShareIcons"
+                    :key="icon"
+                    href="#"
+                  >
+                    <feather-icon
+                      :icon="icon"
+                      size="18"
+                    />
+                  </b-dropdown-item>
+                </b-dropdown>
+              </div>
+              <!--/ dropdown -->
+            </div>
+          </b-card>
+        </b-col>
+        <!--/ blogs -->
+
+        <!-- blog comment -->
+        <b-col
+          id="blogComment"
+          cols="12"
+          class="mt-2"
+        >
+          <h6 class="section-label">
+            Comment
+          </h6>
+          <b-card
+            v-for="(comment,index) in blogDetail.comments"
+            :key="index"
+          >
+            <b-media no-body>
+              <b-media-aside class="mr-75">
+                <b-avatar
+                  :src="comment.avatar"
+                  size="38"
+                />
+              </b-media-aside>
+              <b-media-body>
+                <h6 class="font-weight-bolder mb-25">
+                  {{ comment.userFullName }}
+                </h6>
+                <b-card-text>{{ comment.commentedAt }}</b-card-text>
+                <b-card-text>
+                  {{ comment.commentText }}
+                </b-card-text>
+                <b-link>
+                  <div class="d-inline-flex align-items-center">
+                    <feather-icon
+                      icon="CornerUpLeftIcon"
+                      size="18"
+                      class="mr-50"
+                    />
+                    <span>Reply</span>
+                  </div>
+                </b-link>
+              </b-media-body>
+            </b-media>
+          </b-card>
+        </b-col>
+        <!--/ blog comment -->
+
+        <!-- Leave a Blog Comment -->
+        <b-col
+          cols="12"
+          class="mt-2"
+        >
+          <h6 class="section-label">
+            Leave a Comment
+          </h6>
+          <b-card>
+            <b-form>
+              <b-row>
+                <b-col sm="6">
+                  <b-form-group class="mb-2">
+                    <b-form-input
+                      name="name"
+                      placeholder="Name"
+                    />
+                  </b-form-group>
+                </b-col>
+                <b-col sm="6">
+                  <b-form-group class="mb-2">
+                    <b-form-input
+                      name="email"
+                      type="email"
+                      placeholder="Email"
+                    />
+                  </b-form-group>
+                </b-col>
+                <b-col sm="6">
+                  <b-form-group class="mb-2">
+                    <b-form-input
+                      name="website"
+                      placeholder="Website"
+                    />
+                  </b-form-group>
+                </b-col>
+                <b-col cols="12">
+                  <b-form-group class="mb-2">
+                    <b-form-textarea
+                      name="textarea"
+                      rows="4"
+                      placeholder="Website"
+                    />
+                  </b-form-group>
+                </b-col>
+                <b-col cols="12">
+                  <b-form-checkbox
+                    id="checkbox-1"
+                    v-model="commentCheckmark"
+                    name="checkbox-1"
+                    class="mb-2"
+                  >
+                    Save my name, email, and website in this browser for the next time I comment.
+                  </b-form-checkbox>
+                </b-col>
+                <b-col cols="12">
+                  <b-button
+                    v-ripple.400="'rgba(255, 255, 255, 0.15)'"
+                    variant="primary"
+                  >
+                    Post Comment
+                  </b-button>
+                </b-col>
+              </b-row>
+            </b-form>
+          </b-card>
+        </b-col>
+        <!--/ Leave a Blog Comment -->
+      </b-row>
+      <!--/ blogs -->
+    </div>
+    <!--/ content -->
+
+    <!-- sidebar -->
+    <div
+      slot="sidebar"
+      class="blog-sidebar py-2 py-lg-0"
+    >
+      <!-- input search -->
+      <b-form-group class="blog-search">
+        <b-input-group class="input-group-merge">
+          <b-form-input
+            id="search-input"
+            v-model="search_query"
+            placeholder="Search here"
+          />
+          <b-input-group-append
+            class="cursor-pointer"
+            is-text
+          >
+            <feather-icon
+              icon="SearchIcon"
+            />
+          </b-input-group-append>
+        </b-input-group>
+      </b-form-group>
+      <!--/ input search -->
+
+      <!-- recent posts -->
+      <div class="blog-recent-posts mt-3">
+        <h6 class="section-label mb-75">
+          Recent Posts
+        </h6>
+        <b-media
+          v-for="(recentpost,index) in blogSidebar.recentPosts"
+          :key="recentpost.img"
+          no-body
+          :class="index? 'mt-2':''"
+        >
+          <b-media-aside class="mr-2">
+            <b-link>
+              <b-img
+                :src="recentpost.img"
+                :alt="recentpost.img.slice(6)"
+                width="100"
+                rounded
+                height="70"
+              />
+            </b-link>
+          </b-media-aside>
+          <b-media-body>
+            <h6 class="blog-recent-post-title">
+              <b-link class="text-body-heading">
+                {{ recentpost.title }}
+              </b-link>
+            </h6>
+            <span class="text-muted mb-0">
+              {{ recentpost.createdTime }}
+            </span>
+          </b-media-body>
+        </b-media>
+      </div>
+      <!--/ recent posts -->
+
+      <!-- categories -->
+      <div class="blog-categories mt-3">
+        <h6 class="section-label mb-1">
+          Categories
+        </h6>
+
+        <div
+          v-for="category in blogSidebar.categories"
+          :key="category.icon"
+          class="d-flex justify-content-start align-items-center mb-75"
+        >
+          <b-link>
+            <b-avatar
+              rounded
+              size="32"
+              :variant="tagsColor(category.category)"
+              class="mr-75"
+            >
+              <feather-icon
+                :icon="category.icon"
+                size="16"
+              />
+            </b-avatar>
+          </b-link>
+          <b-link>
+            <div class="blog-category-title text-body">
+              {{ category.category }}
+            </div>
+          </b-link>
+        </div>
+      </div>
+      <!--/ categories -->
+    </div>
+  </content-with-sidebar>
+</template>
+
+<script>
+import {
+  BFormInput,
+  BMedia,
+  BAvatar,
+  BMediaAside,
+  BMediaBody,
+  BImg,
+  BLink,
+  BFormGroup,
+  BInputGroup,
+  BInputGroupAppend,
+  BCard,
+  BRow,
+  BCol,
+  BBadge,
+  BCardText,
+  BDropdown,
+  BDropdownItem,
+  BForm,
+  BFormTextarea,
+  BFormCheckbox,
+  BButton,
+} from 'bootstrap-vue'
+import Ripple from 'vue-ripple-directive'
+import { kFormatter } from '@core/utils/filter'
+import ContentWithSidebar from '@core/layouts/components/content-with-sidebar/ContentWithSidebar.vue'
+
+export default {
+  components: {
+    BFormInput,
+    BMedia,
+    BAvatar,
+    BMediaAside,
+    BMediaBody,
+    BLink,
+    BCard,
+    BRow,
+    BCol,
+    BFormGroup,
+    BInputGroup,
+    BInputGroupAppend,
+    BImg,
+    BBadge,
+    BCardText,
+    BDropdown,
+    BForm,
+    BDropdownItem,
+    BFormTextarea,
+    BFormCheckbox,
+    BButton,
+    ContentWithSidebar,
+  },
+  directives: {
+    Ripple,
+  },
+  data() {
+    return {
+      search_query: '',
+      commentCheckmark: '',
+      blogDetail: [],
+      blogSidebar: {},
+      socialShareIcons: ['GithubIcon', 'GitlabIcon', 'FacebookIcon', 'TwitterIcon', 'LinkedinIcon'],
+    }
+  },
+  created() {
+    this.$http.get('/blog/list/data/detail').then(res => {
+      this.blogDetail = res.data
+    })
+    this.$http.get('/blog/list/data/sidebar').then(res => {
+      this.blogSidebar = res.data
+    })
+  },
+  methods: {
+    kFormatter,
+    tagsColor(tag) {
+      if (tag === 'Quote') return 'light-info'
+      if (tag === 'Gaming') return 'light-danger'
+      if (tag === 'Fashion') return 'light-primary'
+      if (tag === 'Video') return 'light-warning'
+      if (tag === 'Food') return 'light-success'
+      return 'light-primary'
+    },
+  },
+}
+</script>
+
+<style lang="scss">
+@import '~@resources/scss/vue/pages/page-blog.scss';
+</style>

+ 245 - 0
resources/js/src/views/blog/BlogEdit.vue

@@ -0,0 +1,245 @@
+<template>
+  <b-card
+    v-if="Object.keys(blogEdit).length"
+    class="blog-edit-wrapper"
+  >
+    <!-- media -->
+    <b-media
+      no-body
+      vertical-align="center"
+    >
+      <b-media-aside class="mr-75">
+        <b-avatar
+          size="38"
+          :src="blogEdit.avatar"
+        />
+      </b-media-aside>
+      <b-media-body>
+        <h6 class="mb-25">
+          {{ blogEdit.userFullName }}
+        </h6>
+        <b-card-text>{{ blogEdit.createdTime }}</b-card-text>
+      </b-media-body>
+    </b-media>
+    <!--/ media -->
+
+    <!-- form -->
+    <b-form class="mt-2">
+      <b-row>
+        <b-col md="6">
+          <b-form-group
+            label="Title"
+            label-for="blog-edit-title"
+            class="mb-2"
+          >
+            <b-form-input
+              id="blog-edit-title"
+              v-model="blogEdit.blogTitle"
+            />
+          </b-form-group>
+        </b-col>
+        <b-col md="6">
+          <b-form-group
+            label="Category"
+            label-for="blog-edit-category"
+            class="mb-2"
+          >
+            <v-select
+              id="blog-edit-category"
+              v-model="blogEdit.blogCategories"
+              :dir="$store.state.appConfig.isRTL ? 'rtl' : 'ltr'"
+              multiple
+              :options="categoryOption"
+            />
+          </b-form-group>
+        </b-col>
+        <b-col md="6">
+          <b-form-group
+            label="Slug"
+            label-for="blog-edit-slug"
+            class="mb-2"
+          >
+            <b-form-input
+              id="blog-edit-slug"
+              v-model="blogEdit.slug"
+            />
+          </b-form-group>
+        </b-col>
+        <b-col md="6">
+          <b-form-group
+            label="Status"
+            label-for="blog-edit-category"
+            class="mb-2"
+          >
+            <v-select
+              id="blog-edit-category"
+              v-model="blogEdit.status"
+              :dir="$store.state.appConfig.isRTL ? 'rtl' : 'ltr'"
+              :options="statusOption"
+            />
+          </b-form-group>
+        </b-col>
+        <b-col cols="12">
+          <b-form-group
+            label="Content"
+            label-for="blog-content"
+            class="mb-2"
+          >
+            <quill-editor
+              id="blog-content"
+              v-model="blogEdit.excerpt"
+              :options="snowOption"
+            />
+          </b-form-group>
+        </b-col>
+        <b-col
+          cols="12"
+          class="mb-2"
+        >
+          <div class="border rounded p-2">
+            <h4 class="mb-1">
+              Featured Image
+            </h4>
+            <b-media
+              no-body
+              vertical-align="center"
+              class="flex-column flex-md-row"
+            >
+              <b-media-aside>
+                <b-img
+                  ref="refPreviewEl"
+                  :src="blogEdit.featuredImage"
+                  height="110"
+                  width="170"
+                  class="rounded mr-2 mb-1 mb-md-0"
+                />
+              </b-media-aside>
+              <b-media-body>
+                <small class="text-muted">Required image resolution 800x400, image size 10mb.</small>
+                <b-card-text class="my-50">
+                  <b-link id="blog-image-text">
+                    C:\fakepath\{{ blogFile ? blogFile.name : 'banner.jpg' }}
+                  </b-link>
+                </b-card-text>
+                <div class="d-inline-block">
+                  <b-form-file
+                    ref="refInputEl"
+                    v-model="blogFile"
+                    accept=".jpg, .png, .gif"
+                    placeholder="Choose file"
+                    @input="inputImageRenderer"
+                  />
+                </div>
+              </b-media-body>
+            </b-media>
+          </div>
+        </b-col>
+        <b-col
+          cols="12"
+          class="mt-50"
+        >
+          <b-button
+            v-ripple.400="'rgba(255, 255, 255, 0.15)'"
+            variant="primary"
+            class="mr-1"
+          >
+            Save Changes
+          </b-button>
+          <b-button
+            v-ripple.400="'rgba(186, 191, 199, 0.15)'"
+            variant="outline-secondary"
+          >
+            Cancel
+          </b-button>
+        </b-col>
+      </b-row>
+    </b-form>
+    <!--/ form -->
+  </b-card>
+</template>
+
+<script>
+import {
+  BCard,
+  BMedia,
+  BAvatar,
+  BCardText,
+  BMediaAside,
+  BMediaBody,
+  BForm,
+  BRow,
+  BCol,
+  BFormGroup,
+  BFormInput,
+  BImg,
+  BFormFile,
+  BLink,
+  BButton,
+} from 'bootstrap-vue'
+import vSelect from 'vue-select'
+import { quillEditor } from 'vue-quill-editor'
+import Ripple from 'vue-ripple-directive'
+import { useInputImageRenderer } from '@core/comp-functions/forms/form-utils'
+import { ref } from '@vue/composition-api'
+
+export default {
+  components: {
+    BCard,
+    BMedia,
+    BAvatar,
+    BCardText,
+    BMediaAside,
+    BMediaBody,
+    BForm,
+    BLink,
+    BImg,
+    BRow,
+    BCol,
+    BButton,
+    BFormGroup,
+    BFormInput,
+    BFormFile,
+    vSelect,
+    quillEditor,
+  },
+  directives: {
+    Ripple,
+  },
+  data() {
+    return {
+      blogEdit: {},
+      blogFile: null,
+      categoryOption: ['Fashion', 'Food', 'Gaming', 'Quote', 'Video'],
+      statusOption: ['Published', 'Pending', 'Draft'],
+      snowOption: {
+        theme: 'snow',
+      },
+    }
+  },
+  created() {
+    this.$http.get('/blog/list/data/edit').then(res => {
+      this.blogEdit = res.data
+    })
+  },
+  setup() {
+    const refInputEl = ref(null)
+    const refPreviewEl = ref(null)
+
+    const { inputImageRenderer } = useInputImageRenderer(refInputEl, base64 => {
+      refPreviewEl.value.src = base64
+    })
+
+    return {
+      refInputEl,
+      refPreviewEl,
+      inputImageRenderer,
+    }
+  },
+}
+</script>
+
+<style lang="scss">
+@import '~@resources/scss/vue/libs/vue-select.scss';
+@import '~@resources/scss/vue/libs/quill.scss';
+@import '~@resources/scss/vue/pages/page-blog.scss';
+</style>

+ 301 - 0
resources/js/src/views/blog/BlogList.vue

@@ -0,0 +1,301 @@
+<template>
+  <content-with-sidebar class="blog-wrapper">
+
+    <!-- blogs -->
+    <b-row class="blog-list-wrapper">
+      <b-col
+        v-for="blog in blogList"
+        :key="blog.img"
+        md="6"
+      >
+        <b-card
+          tag="article"
+          no-body
+        >
+          <b-link :to="{ name: 'pages-blog-detail', params: { id: blog.id } }">
+            <b-img
+              :src="blog.img"
+              :alt="blog.img.slice(5)"
+              class="card-img-top"
+            />
+          </b-link>
+          <b-card-body>
+            <b-card-title>
+              <b-link
+                :to="{ name: 'pages-blog-detail', params: { id: blog.id } }"
+                class="blog-title-truncate text-body-heading"
+              >
+                {{ blog.title }}
+              </b-link>
+            </b-card-title>
+            <b-media no-body>
+              <b-media-aside
+                vertical-align="center"
+                class="mr-50"
+              >
+                <b-avatar
+                  href="javascript:void(0)"
+                  size="24"
+                  :src="blog.avatar"
+                />
+              </b-media-aside>
+              <b-media-body>
+                <small class="text-muted mr-50">by</small>
+                <small>
+                  <b-link class="text-body">{{ blog.userFullName }}</b-link>
+                </small>
+                <span class="text-muted ml-75 mr-50">|</span>
+                <small class="text-muted">{{ blog.blogPosted }}</small>
+              </b-media-body>
+            </b-media>
+            <div class="my-1 py-25">
+              <b-link
+                v-for="(tag,index) in blog.tags"
+                :key="index"
+              >
+                <b-badge
+                  pill
+                  class="mr-75"
+                  :variant="tagsColor(tag)"
+                >
+                  {{ tag }}
+                </b-badge>
+              </b-link>
+            </div>
+            <b-card-text class="blog-content-truncate">
+              {{ blog.excerpt }}
+            </b-card-text>
+            <hr>
+            <div class="d-flex justify-content-between align-items-center">
+              <b-link :to="{ path: `/pages/blog/${blog.id}#blogComment`}">
+                <div class="d-flex align-items-center text-body">
+                  <feather-icon
+                    icon="MessageSquareIcon"
+                    class="mr-50"
+                  />
+                  <span class="font-weight-bold">{{ kFormatter(blog.comment) }} Comments</span>
+                </div>
+              </b-link>
+              <b-link
+                :to="{ name: 'pages-blog-detail', params: { id: blog.id } }"
+                class="font-weight-bold"
+              >
+                Read More
+              </b-link>
+            </div>
+          </b-card-body>
+        </b-card>
+      </b-col>
+      <b-col cols="12">
+        <!-- pagination -->
+        <div class="my-2">
+          <b-pagination
+            v-model="currentPage"
+            align="center"
+            :total-rows="rows"
+            first-number
+            last-number
+            prev-class="prev-item"
+            next-class="next-item"
+          >
+            <template #prev-text>
+              <feather-icon
+                icon="ChevronLeftIcon"
+                size="18"
+              />
+            </template>
+            <template #next-text>
+              <feather-icon
+                icon="ChevronRightIcon"
+                size="18"
+              />
+            </template>
+          </b-pagination>
+        </div>
+      </b-col>
+    </b-row>
+
+    <!--/ blogs -->
+
+    <!-- sidebar -->
+    <div
+      slot="sidebar"
+      class="blog-sidebar py-2 py-lg-0"
+    >
+      <!-- input search -->
+      <b-form-group class="blog-search">
+        <b-input-group class="input-group-merge">
+          <b-form-input
+            id="search-input"
+            v-model="search_query"
+            placeholder="Search here"
+          />
+          <b-input-group-append
+            class="cursor-pointer"
+            is-text
+          >
+            <feather-icon
+              icon="SearchIcon"
+            />
+          </b-input-group-append>
+        </b-input-group>
+      </b-form-group>
+      <!--/ input search -->
+
+      <!-- recent posts -->
+      <div class="blog-recent-posts mt-3">
+        <h6 class="section-label mb-75">
+          Recent Posts
+        </h6>
+        <b-media
+          v-for="(recentpost,index) in blogSidebar.recentPosts"
+          :key="recentpost.img"
+          no-body
+          :class="index? 'mt-2':''"
+        >
+          <b-media-aside class="mr-2">
+            <b-link :to="{ name: 'pages-blog-detail', params:{ id :recentpost.id } }">
+              <b-img
+                :src="recentpost.img"
+                :alt="recentpost.img.slice(6)"
+                width="100"
+                rounded
+                height="70"
+              />
+            </b-link>
+          </b-media-aside>
+          <b-media-body>
+            <h6 class="blog-recent-post-title">
+              <b-link
+                :to="{ name: 'pages-blog-detail', params:{ id :recentpost.id } }"
+                class="text-body-heading"
+              >
+                {{ recentpost.title }}
+              </b-link>
+            </h6>
+            <span class="text-muted mb-0">
+              {{ recentpost.createdTime }}
+            </span>
+          </b-media-body>
+        </b-media>
+      </div>
+      <!--/ recent posts -->
+
+      <!-- categories -->
+      <div class="blog-categories mt-3">
+        <h6 class="section-label mb-1">
+          Categories
+        </h6>
+
+        <div
+          v-for="category in blogSidebar.categories"
+          :key="category.icon"
+          class="d-flex justify-content-start align-items-center mb-75"
+        >
+          <b-link>
+            <b-avatar
+              rounded
+              size="32"
+              :variant="tagsColor(category.category)"
+              class="mr-75"
+            >
+              <feather-icon
+                :icon="category.icon"
+                size="16"
+              />
+            </b-avatar>
+          </b-link>
+          <b-link>
+            <div class="blog-category-title text-body">
+              {{ category.category }}
+            </div>
+          </b-link>
+        </div>
+      </div>
+      <!--/ categories -->
+    </div>
+    <!--/ sidebar -->
+  </content-with-sidebar>
+</template>
+
+<script>
+import {
+  BRow,
+  BCol,
+  BCard,
+  BFormInput,
+  BCardText,
+  BCardTitle,
+  BMedia,
+  BAvatar,
+  BMediaAside,
+  BMediaBody,
+  BImg,
+  BCardBody,
+  BLink,
+  BBadge,
+  BFormGroup,
+  BInputGroup,
+  BInputGroupAppend,
+  BPagination,
+} from 'bootstrap-vue'
+import { kFormatter } from '@core/utils/filter'
+import ContentWithSidebar from '@core/layouts/components/content-with-sidebar/ContentWithSidebar.vue'
+
+export default {
+  components: {
+    BRow,
+    BCol,
+    BCard,
+    BFormInput,
+    BCardText,
+    BCardBody,
+    BCardTitle,
+    BMedia,
+    BAvatar,
+    BMediaAside,
+    BMediaBody,
+    BLink,
+    BBadge,
+    BFormGroup,
+    BInputGroup,
+    BInputGroupAppend,
+    BImg,
+    BPagination,
+    ContentWithSidebar,
+  },
+  data() {
+    return {
+      search_query: '',
+      blogList: [],
+      blogSidebar: {},
+      currentPage: 1,
+      perPage: 1,
+      rows: 140,
+    }
+  },
+  created() {
+    this.$http.get('/blog/list/data').then(res => {
+      this.blogList = res.data
+    })
+    this.$http.get('/blog/list/data/sidebar').then(res => {
+      this.blogSidebar = res.data
+    })
+  },
+  methods: {
+    kFormatter,
+    tagsColor(tag) {
+      if (tag === 'Quote') return 'light-info'
+      if (tag === 'Gaming') return 'light-danger'
+      if (tag === 'Fashion') return 'light-primary'
+      if (tag === 'Video') return 'light-warning'
+      if (tag === 'Food') return 'light-success'
+      return 'light-primary'
+    },
+  },
+}
+</script>
+
+<style lang="scss">
+@import '~@resources/scss/vue/pages/page-blog.scss';
+</style>

+ 26 - 8
routes/api.php

@@ -7,6 +7,7 @@ use Illuminate\Validation\ValidationException;
 use Illuminate\Support\Facades\DB;
 
 use App\Models\User;
+use App\Models\Message;
 
 /*
 |--------------------------------------------------------------------------
@@ -28,10 +29,10 @@ Route::post('/login', function (Request $request) {
         'email' => 'required|email',
         'password' => 'required',
     ]);
- 
+
     $user = User::where('email', $request->email)->first();
- 
-    if (! $user || ! Hash::check($request->password, $user->password)) {
+
+    if (!$user || !Hash::check($request->password, $user->password)) {
         throw ValidationException::withMessages([
             'email' => ['The provided credentials are incorrect.'],
         ]);
@@ -49,8 +50,25 @@ Route::middleware('auth:sanctum')->post('/logout', function (Request $request) {
     return $user;
 });
 
-Route::get('/test', function (Request $request) {
-    // $user = User::all()->sortByDesc("created_at")->take(2);
-    $user = DB::select("SELECT TOP (2) *  FROM [laravel_kevin].[dbo].[users] ORDER BY created_at desc");
-    return $user;
-});
+Route::middleware('auth:sanctum')->post('/get_dataset', function (Request $request) {
+    $request->validate([
+        'table' => 'required',
+        'order' => 'required',
+        'number' => 'required',
+    ]);
+
+    $dataset = DB::select("SELECT TOP ({$request->number}) *  FROM [laravel_kevin].[dbo].[{$request->table}] ORDER BY {$request->order} desc");
+    return $dataset;
+});
+
+Route::middleware('auth:sanctum')->post('/get_messages', function (Request $request) {
+    $request->validate([
+        'number' => 'required',
+    ]);
+
+    $dataset = DB::select("SELECT TOP ({$request->number}) title, content, name, messages.updated_at  
+        FROM [laravel_kevin].[dbo].[messages] 
+        INNER JOIN [dbo].[users] ON [messages].[writer]=[users].[id]
+        ORDER BY updated_at desc");
+    return $dataset;
+});