Commit 7a32c366 authored by Dhevan's avatar Dhevan

handling logout

parent cd757087
......@@ -19,6 +19,7 @@ import Configurator from "@/components/Configurator.vue";
import Navbar from "@/components/Navbars/Navbar.vue";
import AppFooter from "@/components/Footer.vue";
import { useMenuStore } from "./store/menu";
import { useAuthStore } from "./store/auth";
const store = useMenuStore();
const isNavFixed = computed(() => store.isNavFixed);
......@@ -32,6 +33,8 @@ const showConfig = computed(() => store.showConfig);
const hideConfigButton = computed(() => store.hideConfigButton);
const toggleConfigurator = () => store.toggleConfigurator;
const authStore = useAuthStore();
const navClasses = computed(() => {
return {
"position-sticky bg-white left-auto top-2 z-index-sticky":
......@@ -49,20 +52,24 @@ const navClasses = computed(() => {
class="landing-bg h-100 bg-gradient-primary position-fixed w-100"
></div>
<sidenav v-if="showSidenav" />
<sidenav v-if="showSidenav && authStore.isAuthenticated" />
<main
class="main-content position-relative max-height-vh-100 h-100 border-radius-lg"
>
<!-- nav -->
<navbar :class="[navClasses]" v-if="showNavbar" />
<navbar
:class="[navClasses]"
v-if="showNavbar && authStore.isAuthenticated"
/>
<router-view />
<app-footer v-show="showFooter" />
<app-footer v-if="authStore.isAuthenticated" v-show="showFooter" />
<configurator
v-if="authStore.isAuthenticated"
:toggle="toggleConfigurator"
:class="[showConfig ? 'show' : '', hideConfigButton ? 'd-none' : '']"
/>
......
import RestClient from "./restClient.js";
export default class BaseAPI {
url = "";
dummy_data = null;
constructor(option) {
if (option.url) {
this.url = option.url;
}
if (option.dummy_data) {
this.dummy_data = option.dummy_data;
}
}
async datatable(options) {
if (this.dummy_data != null) {
return await this.dummyResponse(this.dummy_data);
}
const response = await RestClient.get(`${this.url}/datatable`, { params: options });
return response.data;
}
async detail(id) {
if (this.dummy_data != null) {
return await this.dummyResponse(this.dummy_data.data[0]);
}
const response = await RestClient.get(`${this.url}/${id}`);
return response.data;
}
async store(payload) {
const response = await RestClient.post(`${this.url}`, payload);
return response.data;
}
async update(id, payload) {
const response = await RestClient.patch(`${this.url}/${id}`, payload);
return response.data;
}
async delete(id) {
const response = await RestClient.delete(`${this.url}/${id}`);
return response.data;
}
buildFormData(payload, prefix = null) {
const formData = new FormData();
const getKeyname = (key) => (prefix ? `${prefix}[${key}]` : key);
Object.keys(payload).forEach((key) => {
if (Array.isArray(payload[key])) {
for (let i = 0; i < payload[key].length; i++) {
if (payload[key][i]) {
formData.append(getKeyname(key) + `[]`, payload[key][i]);
}
}
} else if (payload[key]) {
formData.append(getKeyname(key), payload[key]);
}
});
return formData;
}
headersForMultipart(headers = {}) {
return {
"Content-Type": "multipart/form-data",
...headers,
};
}
configForMultipart(config = {}, headers = {}) {
return {
...config,
headers: this.headersForMultipart(headers),
};
}
dummyResponse(response = { result: true }, timeout = 1000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(response);
}, timeout);
});
}
}
import RestClient from "./restClient.js";
export class MenuApi {
static async mine() {
const response = await RestClient.get("/menu/mine");
return response.data;
}
}
......@@ -43,8 +43,8 @@ const successHandler = (response) => {
};
const requestHandler = (request) => {
if (localStorage.getItem("token") != null) {
request.headers.Authorization = `Bearer ${localStorage.getItem("token")}`;
if (localStorage.getItem("access_token") != null) {
request.headers.Authorization = `Bearer ${localStorage.getItem("access_token")}`;
}
return request;
};
......
import BaseAPI from "./baseApi.js";
class Role extends BaseAPI {
constructor() {
super({ url: "/role" });
}
}
export const RoleApi = new Role();
import BaseAPI from "./baseApi.js";
import RestClient from "./restClient.js";
export class UserApi {
static async login(username, password) {
class User extends BaseAPI {
constructor() {
super({ url: "/user" });
}
async login(username, password) {
const response = await RestClient.post("/user/login", {
username,
password,
});
return response.data;
}
static async me() {
async me() {
const response = await RestClient.get("/user/me");
return response.data;
}
}
export const UserApi = new User();
<template>
<nav>
<ul class="pagination justify-content-end">
<li
@click="page != 1 && $emit('onPageClick', page - 1)"
:class="`page-item ${page == 1 ? 'disabled' : 'cursor-pointer'}`"
>
<a class="page-link" tabindex="-1">
<span class="ni ni-chevron-left"></span>
</a>
</li>
<li
v-for="(item, index) in paginateItem"
:key="index"
:class="`page-item cursor-pointer ${item == page ? 'active' : ''}`"
@click="$emit('onPageClick', item)"
>
<span :class="`page-link ${item == page ? 'text-white' : ''}`">{{
item == 0 ? "..." : item
}}</span>
</li>
<li
@click="page != pageCount && $emit('onPageClick', page + 1)"
:class="`page-item ${
page == pageCount ? 'disabled' : 'cursor-pointer'
}`"
>
<a class="page-link" href="javascript:;">
<span class="ni ni-chevron-right"></span>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
props: ["page", "total", "per_page"],
computed: {
pageCount() {
return Math.ceil(this.total / this.per_page);
},
paginateItem() {
const totalPages = this.pageCount;
const currentPage = this.page;
const rangeToShow = 2;
const compressedPagination = [];
let prevHidden = false;
for (let i = 1; i <= totalPages; i++) {
if (
i === 1 ||
i === totalPages ||
Math.abs(i - currentPage) <= rangeToShow
) {
compressedPagination.push(i);
prevHidden = false;
} else {
if (!prevHidden) {
compressedPagination.push(0);
prevHidden = true;
}
}
}
return compressedPagination;
},
},
};
</script>
<template>
<div class="card">
<div class="card-header pb-0">
<h6>{{ title }}</h6>
<div class="d-flex justify-content-between">
<div class="form-group">
<div class="input-icon">
<input
type="text"
v-model="keyword"
class="form-control"
placeholder="Search for..."
/>
</div>
</div>
<div>
<router-link
v-if="canAdd"
:to="`/${path}/create`"
type="button"
class="btn btn-primary btn-round ml-auto"
>
<div class="fa fa-fw fa-plus mr-2"></div>
{{ addDataText ?? "Add Data" }}
</router-link>
</div>
</div>
</div>
<div class="card-body px-0 pt-0 pb-2">
<div class="table-responsive p-0">
<table class="table align-items-center mb-0">
<thead>
<tr>
<th
v-for="(header, index) in headers"
:key="index"
:class="`text-${header['align'] ? header['align'] : 'center'}`"
:style="header['style']"
>
<div
@click="header.sortable ? handleHeaderClick(header) : null"
style="cursor: pointer"
>
{{ header["text"] }}
<span
v-if="
header.sortable &&
sortBy.column == header.value &&
sortBy.type == 'ASC'
"
class="mdi mdi-chevron-up"
></span>
<span
v-if="
header.sortable &&
sortBy.column == header.value &&
sortBy.type == 'DESC'
"
class="mdi mdi-chevron-down"
></span>
</div>
</th>
<th :style="actionStyle" class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<tr v-if="isContentLoading">
<td :colspan="headers.length + 1">
<div class="d-flex justify-content-center">
<div class="spinner-border text-primary" role="status"></div>
</div>
</td>
</tr>
<template v-if="!isContentLoading">
<tr v-for="(content, index) in contents" :key="index">
<td
v-for="(header, _index) in headers"
:key="_index"
:class="`text-${
header['align'] ? header['align'] : 'center'
}`"
:style="header['style']"
>
<slot
:name="`${header['value']}`"
:content="content"
:value="content[header['value']]"
>
<span
:class="`text-${
header['align'] ? header['align'] : 'left'
}`"
>{{ resolve(content, header["value"]) }}</span
>
</slot>
</td>
<td :style="actionStyle" class="text-center">
<!-- <div class="d-flex"> -->
<slot name="left-action" :content="content"> </slot>
<router-link
v-if="canEdit"
:to="`/${path}/${content.id}/edit`"
type="button"
class="btn btn-xs bg-primary me-1 text-white"
>
Edit
</router-link>
<button
v-if="canDelete"
@click="deleteData(content.id)"
type="button"
class="btn btn-xs btn-danger"
>
Delete
</button>
<slot name="right-action" :content="content"> </slot>
<!-- </div> -->
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="px-5 mt-4">
<paginate-content
@onPageClick="handlePageItemClick"
:page="page"
:per_page="per_page"
:total="total"
/>
</div>
</div>
</div>
</template>
<script>
import PaginateContent from "./PaginateContent.vue";
let fetchController = new AbortController();
export default {
components: { PaginateContent },
props: {
addDataText: {
type: String,
required: false,
default: null,
},
addDataLink: {
type: String,
required: false,
default: null,
},
api: {
required: true,
},
path: {
type: String,
required: false,
default: null,
},
listUrl: {
type: String,
required: false,
default: null,
},
headers: {
type: Array,
required: true,
},
title: {
type: String,
reqired: true,
},
actionStyle: {
type: String,
reqired: false,
default: null,
},
canAdd: {
type: Boolean,
default: true,
},
canEdit: {
type: Boolean,
default: true,
},
canDelete: {
type: Boolean,
default: true,
},
additionalFilter: {
type: Object,
default: {},
},
deleteMethod: {
type: Function,
required: false,
},
},
data() {
return {
isContentLoading: false,
contents: [],
keyword: "",
total: 0,
page: 1,
per_page: 10,
isSearchFocused: false,
sortBy: {
column: null,
type: null,
},
};
},
watch: {
page(newPage, oldPage) {
this.fetchData();
},
keyword() {
this.fetchData();
},
sortBy() {
this.fetchData();
},
},
created() {
this.fetchData();
},
methods: {
resolve(obj, path, separator = ".") {
var properties = path.split(separator);
return properties.reduce((prev, curr) => prev && prev[curr], obj);
},
handlePageItemClick(page) {
this.page = page;
},
handleHeaderClick(header) {
if (this.sortBy.column == header.value && this.sortBy.type == "DESC") {
this.sortBy = {
column: null,
type: null,
};
return;
}
if (this.sortBy.column == header.value) {
this.sortBy = {
column: header.value,
type: "DESC",
};
return;
}
this.sortBy = {
column: header.value,
type: "ASC",
};
},
async fetchData() {
try {
fetchController.abort();
} catch (err) {}
this.isContentLoading = true;
const { page, per_page, keyword } = this;
fetchController = new AbortController();
const result = await this.api.datatable({
page,
per_page,
keyword,
...this.additionalFilter,
sortBy: this.sortBy,
});
this.total = result.total;
this.contents = result.result.data;
this.isContentLoading = false;
},
async deleteData(id) {
await this.api.delete(`${id}`);
this.fetchData();
// swal.fire({
// title: "Are you sure want delete this data?",
// showDenyButton: true,
// confirmButtonText: `Yes`,
// denyButtonText: `No`,
// }).then(async (result) => {
// if (result.isConfirmed) {
// await this.api.delete(`${id}`);
// swal.fire("Data berhasil dihapus!", "", "success");
// this.fetchData();
// } else if (result.isDenied) {
// swal.fire("Proses hapus dibatalkan", "", "info");
// this.fetchData();
// }
// });
},
},
};
</script>
......@@ -3,6 +3,7 @@ import { computed, ref } from "vue";
import { useMenuStore } from "@/store/menu";
import { useRoute } from "vue-router";
import Breadcrumbs from "../Breadcrumbs.vue";
import { useAuthStore } from "@/store/auth";
const showMenu = ref(false);
const store = useMenuStore();
......@@ -26,6 +27,10 @@ const closeMenu = () => {
showMenu.value = false;
}, 100);
};
const handleLogout = () => {
useAuthStore().logout();
};
</script>
<template>
<nav
......@@ -75,134 +80,17 @@ const closeMenu = () => {
</div>
</a>
</li>
<li class="px-3 nav-item d-flex align-items-center">
<li class="nav-item d-flex align-items-center">
<a class="p-0 nav-link text-white" @click="toggleConfigurator">
<i class="cursor-pointer fa fa-cog fixed-plugin-button-nav"></i>
</a>
</li>
<li
class="nav-item dropdown d-flex align-items-center"
:class="isRTL ? 'ps-2' : 'pe-2'"
>
<a
href="#"
class="p-0 nav-link text-white"
:class="[showMenu ? 'show' : '']"
id="dropdownMenuButton"
data-bs-toggle="dropdown"
aria-expanded="false"
@click="showMenu = !showMenu"
@blur="closeMenu"
>
<i class="cursor-pointer fa fa-bell"></i>
<li class="ms-3 nav-item d-flex align-items-center">
<a class="p-0 nav-link text-white" @click="handleLogout">
<i
class="cursor-pointer ni ni-arrow-door-out-3 fixed-plugin-button-nav"
></i>
</a>
<ul
class="px-2 py-3 dropdown-menu dropdown-menu-end me-sm-n4"
:class="showMenu ? 'show' : ''"
aria-labelledby="dropdownMenuButton"
>
<li class="mb-2">
<a class="dropdown-item border-radius-md" href="javascript:;">
<div class="py-1 d-flex">
<div class="my-auto">
<img
src="../../assets/img/team-2.jpg"
class="avatar avatar-sm me-3"
alt="user image"
/>
</div>
<div class="d-flex flex-column justify-content-center">
<h6 class="mb-1 text-sm font-weight-normal">
<span class="font-weight-bold">New message</span> from
Laur
</h6>
<p class="mb-0 text-xs text-secondary">
<i class="fa fa-clock me-1"></i>
13 minutes ago
</p>
</div>
</div>
</a>
</li>
<li class="mb-2">
<a class="dropdown-item border-radius-md" href="javascript:;">
<div class="py-1 d-flex">
<div class="my-auto">
<img
src="../../assets/img/small-logos/logo-spotify.svg"
class="avatar avatar-sm bg-gradient-dark me-3"
alt="logo spotify"
/>
</div>
<div class="d-flex flex-column justify-content-center">
<h6 class="mb-1 text-sm font-weight-normal">
<span class="font-weight-bold">New album</span> by
Travis Scott
</h6>
<p class="mb-0 text-xs text-secondary">
<i class="fa fa-clock me-1"></i>
1 day
</p>
</div>
</div>
</a>
</li>
<li>
<a class="dropdown-item border-radius-md" href="javascript:;">
<div class="py-1 d-flex">
<div
class="my-auto avatar avatar-sm bg-gradient-secondary me-3"
>
<svg
width="12px"
height="12px"
viewBox="0 0 43 36"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>credit-card</title>
<g
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<g
transform="translate(-2169.000000, -745.000000)"
fill="#FFFFFF"
fill-rule="nonzero"
>
<g transform="translate(1716.000000, 291.000000)">
<g transform="translate(453.000000, 454.000000)">
<path
class="color-background"
d="M43,10.7482083 L43,3.58333333 C43,1.60354167 41.3964583,0 39.4166667,0 L3.58333333,0 C1.60354167,0 0,1.60354167 0,3.58333333 L0,10.7482083 L43,10.7482083 Z"
opacity="0.593633743"
/>
<path
class="color-background"
d="M0,16.125 L0,32.25 C0,34.2297917 1.60354167,35.8333333 3.58333333,35.8333333 L39.4166667,35.8333333 C41.3964583,35.8333333 43,34.2297917 43,32.25 L43,16.125 L0,16.125 Z M19.7083333,26.875 L7.16666667,26.875 L7.16666667,23.2916667 L19.7083333,23.2916667 L19.7083333,26.875 Z M35.8333333,26.875 L28.6666667,26.875 L28.6666667,23.2916667 L35.8333333,23.2916667 L35.8333333,26.875 Z"
/>
</g>
</g>
</g>
</g>
</svg>
</div>
<div class="d-flex flex-column justify-content-center">
<h6 class="mb-1 text-sm font-weight-normal">
Payment successfully completed
</h6>
<p class="mb-0 text-xs text-secondary">
<i class="fa fa-clock me-1"></i>
2 days
</p>
</div>
</div>
</a>
</li>
</ul>
</li>
</ul>
</div>
......
<template>
<div class="py-4 container-fluid">
<slot></slot>
</div>
</template>
<script setup>
import { computed } from "vue";
import { computed, ref, onBeforeMount } from "vue";
import { useRoute } from "vue-router";
import { useMenuStore } from "@/store/menu";
import SidenavItem from "./SidenavItem.vue";
import { MenuApi } from "@/api/menu";
import SidenavParent from "./SidenavParent.vue";
const store = useMenuStore();
const isRTL = computed(() => store.isRTL);
const menus = ref([]);
const getRoute = () => {
const route = useRoute();
const routeArr = route.path.split("/");
return routeArr[1];
};
const fetchMenu = async () => {
const response = await MenuApi.mine();
menus.value = response.result;
};
onBeforeMount(() => {
fetchMenu();
});
</script>
<template>
<div
......@@ -21,18 +30,20 @@ const getRoute = () => {
id="sidenav-collapse-main"
>
<ul class="navbar-nav">
<li class="nav-item">
<sidenav-item
to="/profile"
:class="getRoute() === 'profile' ? 'active' : ''"
:navText="isRTL ? 'حساب تعريفي' : 'Profile'"
>
<template v-slot:icon>
<i class="ni ni-user text-dark text-sm opacity-10"></i>
</template>
</sidenav-item>
</li>
<li class="nav-item">
<template v-for="(menu, index) in menus">
<li v-if="menu.childs && menu.childs.length > 0" class="nav-item">
<sidenav-parent
:identifier="`${menu.id}`"
:navText="menu.name"
:childs="menu.childs"
>
<template v-slot:icon>
<i :class="`${menu.icon} text-dark text-sm opacity-10`"></i>
</template>
</sidenav-parent>
</li>
</template>
<!-- <li class="nav-item">
<sidenav-item
to="/dashboard"
:class="getRoute() === 'dashboard' ? 'active' : ''"
......@@ -66,7 +77,7 @@ const getRoute = () => {
<i class="ni ni-user text-success text-sm opacity-10"></i>
</template>
</sidenav-item>
</li>
</li> -->
</ul>
</div>
</template>
......@@ -21,7 +21,7 @@ const getRoute = () => {
return routeArr[1];
};
defineProps({
const props = defineProps({
identifier: {
type: String,
required: true,
......@@ -30,6 +30,9 @@ defineProps({
type: String,
required: true,
},
childs: {
default: [],
},
});
</script>
<template>
......@@ -52,15 +55,16 @@ defineProps({
</a>
<div class="collapse" :id="identifier">
<ul class="nav nav-sm flex-column ms-4">
<li class="nav-item">
<sidenav-item
to="/signup"
:class="getRoute() === 'signup' ? 'active' : ''"
:navText="isRTL ? 'اشتراك' : 'Sign Up'"
:hide-icon="true"
>
</sidenav-item>
</li>
<template v-for="(menu, index) in childs">
<li class="nav-item">
<sidenav-item
:to="`/${menu.path}`"
:navText="menu.name"
:hide-icon="true"
>
</sidenav-item>
</li>
</template>
</ul>
</div>
</template>
import { createRouter, createWebHashHistory } from "vue-router";
import {
createRouter,
createWebHashHistory,
createWebHistory,
} from "vue-router";
import Dashboard from "../views/Dashboard.vue";
import Tables from "../views/Tables.vue";
import Billing from "../views/Billing.vue";
import { userRoutes } from "./user.js";
import { roleRoutes } from "./role.js";
import UserLogin from "./../views/user/UserLogin.vue";
import UserSignup from "./../views/user/UserSignup.vue";
const ifAuthenticated = (to, from, next) => {
if (localStorage.getItem("access_token")) {
next();
return;
}
router.push({
path: "/user/login",
params: {
returnTo: to.path,
query: to.query,
},
});
};
const routes = [
...userRoutes,
{
path: "/",
name: "/",
redirect: "/dashboard",
},
{
path: "/dashboard",
name: "Dashboard",
component: Dashboard,
name: "index",
children: [
...userRoutes,
...roleRoutes,
{
path: "/dashboard",
name: "Dashboard",
component: Dashboard,
},
{
path: "/tables",
name: "Tables",
component: Tables,
},
{
path: "/billing",
name: "Billing",
component: Billing,
},
],
beforeEnter: ifAuthenticated,
},
{
path: "/tables",
name: "Tables",
component: Tables,
path: "/user/login",
name: "user-login",
component: UserLogin,
},
{
path: "/billing",
name: "Billing",
component: Billing,
path: "/user/register",
name: "user-register",
component: UserSignup,
},
];
const router = createRouter({
history: createWebHashHistory(),
history: createWebHistory(),
routes,
linkActiveClass: "active",
});
......
import UserIndex from "./../views/user/UserIndex.vue";
export const roleRoutes = [
{
path: "/role",
name: "role-index",
component: UserIndex,
},
{
path: "/role/create",
name: "role-create",
component: UserIndex,
},
{
path: "/role/:id/edit",
name: "role-edit",
component: UserIndex,
},
];
import UserLogin from "./../views/user/UserLogin.vue";
import UserSignup from "./../views/user/UserSignup.vue";
import UserProfile from "./../views/user/UserProfile.vue";
import UserIndex from "./../views/user/UserIndex.vue";
export const userRoutes = [
{
path: "/user/login",
name: "user-login",
component: UserLogin,
path: "/user",
name: "user-index",
component: UserIndex,
},
{
path: "/user/register",
name: "user-register",
component: UserSignup,
path: "/user/create",
name: "user-create",
component: UserIndex,
},
{
path: "/profile",
path: "/user/:id/edit",
name: "user-edit",
component: UserIndex,
},
{
path: "/user/profile",
name: "user-profile",
component: UserProfile,
},
......
......@@ -6,17 +6,15 @@ export const useAuthStore = defineStore({
id: "auth",
state: () => ({
/* Initialize state from local storage to enable user to stay logged in */
user: localStorage.getItem("token")
user: localStorage.getItem("access_token")
? JSON.parse(localStorage.getItem("user"))
: null,
savedToken: localStorage.getItem("token")
? localStorage.getItem("token")
: null,
role: localStorage.getItem("token")
? JSON.parse(localStorage.getItem("role"))
savedToken: localStorage.getItem("access_token")
? localStorage.getItem("access_token")
: null,
}),
getters: {
isAuthenticated: (state) => state.user && state.token,
token: (state) => state.savedToken,
},
actions: {
......@@ -40,7 +38,7 @@ export const useAuthStore = defineStore({
router.replace("/dashboard");
} catch (error) {
// TODO: handle invalid credentials
localStorage.removeItem("token");
localStorage.removeItem("access_token");
localStorage.removeItem("user");
throw error;
}
......@@ -61,7 +59,7 @@ export const useAuthStore = defineStore({
this.user = null;
this.savedToken = null;
localStorage.removeItem("user");
localStorage.removeItem("token");
localStorage.removeItem("access_token");
router.replace("/user/login");
},
......
import Swal from "sweetalert2";
const showLoading = () => {
Swal.fire({
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
},
});
};
const hideLoading = () => {
Swal.close();
};
export { Swal, showLoading, hideLoading };
<template>
<base-page>
<ServerDatatable title="User" :api="UserApi" :headers="headers" path="user">
<template #left-action="{ content }">
<button
@click="handleRoleButtonClick(content.id)"
type="button"
class="btn btn-xs btn-info me-1"
data-toggle="modal"
>
Role
</button>
</template>
</ServerDatatable>
<div
ref="modal"
class="modal fade ms-5"
id="addRoleModal"
tabindex="-1"
role="dialog"
>
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tambah Role</h5>
<button
class="btn btn-outline-primary"
type="button"
data-bs-dismiss="modal"
aria-label="Close"
>
<span class="ni ni-xmark"></span>
</button>
</div>
<div class="modal-body">
<div class="container">
<div class="row align-items-center">
<div class="col-sm-10 mb-2">
<!-- <vue-multiselect
v-model="selectedRoleId"
:searchable="true"
:options="roles"
/> -->
</div>
<div class="col-sm-2">
<div
v-if="isAddingRole"
class="spinner-border text-primary"
></div>
<button
v-if="!isAddingRole"
@click="addUserRole"
type="button"
class="btn btn-xs btn-primary btn-block"
>
Tambah Role
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table align-items-center table-hover">
<thead>
<tr>
<th class="text-center">Role</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<tr v-if="isFetchingUserRole">
<td :colspan="2">
<div class="d-flex justify-content-center">
<div class="spinner-border text-primary"></div>
</div>
</td>
</tr>
<template v-if="!isFetchingUserRole">
<tr v-for="role in userRoles">
<td class="text-center">@{{ role.name }}</td>
<td class="text-center">
<div
v-if="isRemoveRole"
class="spinner-border text-primary"
></div>
<button
v-if="!isRemoveRole"
@click="removeUserRole(role.id)"
type="button"
class="btn btn-xs btn-danger"
>
Delete
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</base-page>
</template>
<script setup lang="ts">
import BasePage from "./../../components/Page/BasePage.vue";
import ProjectsTable from "./../components/ProjectsTable.vue";
import { UserApi } from "@/api/user.js";
import ServerDatatable from "@/components/Common/ServerDatatable.vue";
import { ref, onMounted, inject } from "vue";
import * as bootstrap from "bootstrap";
import { RoleApi } from "@/api/role";
const roles = ref([]);
const userRoles = ref([]);
const isFetchingUserRole = ref(false);
const showModal = ref(false);
const selectedRoleId = ref(null);
const selectedUserId = ref(null);
const isAddingRole = ref(false);
const isRemoveRole = ref(false);
const addRoleModal: any = ref(null);
const headers = ref([
{
text: "Nama User",
value: "name",
},
{
text: "Email User",
value: "email",
},
]);
const handleRoleButtonClick = async (userId) => {
selectedUserId.value = userId;
addRoleModal.value.show();
await fetchUserRoles(userId);
};
const addUserRole = async () => {
isAddingRole.value = true;
await api.user.addRole(selectedUserId, selectedRoleId);
isAddingRole.value = false;
fetchUserRoles(selectedUserId);
};
const removeUserRole = async (role_id) => {
isRemoveRole.value = true;
await api.user.deleteRole(selectedUserId, role_id);
isRemoveRole.value = false;
fetchUserRoles(selectedUserId);
};
const fetchUserRoles = async (userId) => {
isFetchingUserRole.value = true;
const response = api.user.getRole(userId);
userRoles.value = response.data.result;
isFetchingUserRole.value = false;
};
const fetchRoles = async () => {
const response = await RoleApi.datatable();
roles.value = response.result.data.map((role) => {
return {
value: role.id,
label: role.name,
};
});
};
onMounted(() => {
fetchRoles();
addRoleModal.value = new bootstrap.Modal(
document.getElementById("addRoleModal"),
{
keyboard: false,
}
);
});
</script>
......@@ -6,6 +6,8 @@ import ArgonSwitch from "@/components/ArgonSwitch.vue";
import ArgonButton from "@/components/ArgonButton.vue";
import { UserApi } from "@/api/user";
import { useAuthStore } from "@/store/auth";
import router from "@/router";
const store = useMenuStore();
const tools = inject("tools");
......@@ -15,7 +17,7 @@ const login = async () => {
tools.showLoading();
const authStore = useAuthStore();
await authStore.login("developer@gmail.com", "kecilsemuatanpaspasi");
console.log(response);
router.replace("/dashboard");
tools.hideLoading();
};
......@@ -49,7 +51,7 @@ onBeforeUnmount(() => {
<p class="mb-0">Enter your email and password to sign in</p>
</div>
<div class="card-body">
<form role="form">
<form @submit="login" role="form">
<div class="mb-3">
<argon-input
id="email"
......@@ -76,6 +78,7 @@ onBeforeUnmount(() => {
color="success"
fullWidth
size="lg"
type="button"
>
Sign in
</argon-button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment