working on auth
This commit is contained in:
parent
9705e0d150
commit
7e944ac917
7
src/core/endpoints/auth.ts
Normal file
7
src/core/endpoints/auth.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export const authAPI = {
|
||||
csrfGenerate: "users/auth/csrf/",
|
||||
login: "users/auth/login/",
|
||||
refresh: "users/auth/refresh/",
|
||||
logout: "users/auth/logout/",
|
||||
self: "users/self/",
|
||||
};
|
||||
0
src/core/endpoints/dartachalani.ts
Normal file
0
src/core/endpoints/dartachalani.ts
Normal file
8
src/dtos/User/auth.d.ts
vendored
Normal file
8
src/dtos/User/auth.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export type LoginPayload = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type LoginResponse = {
|
||||
access: string;
|
||||
};
|
||||
6
src/dtos/User/user.d.ts
vendored
Normal file
6
src/dtos/User/user.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export type UserDetail = {
|
||||
id: string;
|
||||
username: string;
|
||||
is_active: boolean;
|
||||
is_superuser?: boolean;
|
||||
};
|
||||
44
src/main.ts
44
src/main.ts
|
|
@ -1,12 +1,40 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import "./assets/css/main.css";
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import "@/assets/ts/main.ts";
|
||||
|
||||
const app = createApp(App)
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
import App from "./App.vue";
|
||||
import { loadComponents } from "./utils/setup/component";
|
||||
import initRouter from "./utils/setup/routerSetup";
|
||||
import { loadDirectives } from "./utils/setup/directives";
|
||||
import { loadPlugins } from "./utils/setup/plugins";
|
||||
|
||||
app.mount('#app')
|
||||
interface AppConfig {
|
||||
API_URL: string;
|
||||
APP_ENVIRONMENT: "PRODUCTION" | "DEVELOPMENT";
|
||||
|
||||
CLIENT_NAME: string;
|
||||
CLIENT_LOGO: string;
|
||||
CLIENT_PAGE_TITLE: string;
|
||||
CLIENT_PAGE_TITLE_LOGO: string;
|
||||
WEBSOCKET_URL: string;
|
||||
}
|
||||
declare global {
|
||||
interface Window {
|
||||
APP_CONFIG: AppConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export const initAPP = () => {
|
||||
const app = createApp(App);
|
||||
app.use(createPinia());
|
||||
|
||||
initRouter(app);
|
||||
loadComponents(app);
|
||||
loadDirectives(app);
|
||||
loadPlugins(app);
|
||||
|
||||
app.mount("#app");
|
||||
};
|
||||
|
|
|
|||
74
src/services/API/api.ts
Normal file
74
src/services/API/api.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import axios from "axios";
|
||||
import { authAPI } from "@/core/endpoints/auth";
|
||||
import { getCSRFTokenFromCookie } from "./utilities";
|
||||
import { useUser } from "@/stores/User/User";
|
||||
|
||||
const apiURL: string = window.APP_CONFIG.API_URL;
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: apiURL,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
api.interceptors.request.use(
|
||||
function (config) {
|
||||
// const authStore = useAuth();
|
||||
// const { token } = storeToRefs(authStore);
|
||||
// //Attach the Bearer Token if it exist during api request
|
||||
// if (token.value) {
|
||||
// config.headers["Authorization"] = `Bearer ${token.value}`;
|
||||
// }
|
||||
const csrfToken = getCSRFTokenFromCookie();
|
||||
if (["post", "put", "patch", "delete"].includes(config.method || "")) {
|
||||
config.headers["X-CSRFToken"] = csrfToken;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
function (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
function (response) {
|
||||
return response;
|
||||
},
|
||||
async function (error) {
|
||||
const {
|
||||
config,
|
||||
response: { status },
|
||||
} = error;
|
||||
const originalRequest = config;
|
||||
if (status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
const userStore = useUser();
|
||||
const csrfToken = getCSRFTokenFromCookie();
|
||||
if (["post", "put", "patch", "delete"].includes(config.method || "")) {
|
||||
originalRequest.headers["X-CSRFToken"] = csrfToken;
|
||||
}
|
||||
try {
|
||||
await axios.post(apiURL + authAPI.refresh, null, {
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
});
|
||||
userStore.isAuthenticated = true;
|
||||
// const token = response.data.data.access;
|
||||
// authStore.token = token;
|
||||
// originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||
return await axios(originalRequest);
|
||||
} catch (e) {
|
||||
console.error("Token refresh failed", e);
|
||||
userStore.isAuthenticated = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
16
src/services/API/utilities.ts
Normal file
16
src/services/API/utilities.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export function getCSRFTokenFromCookie(): string | null {
|
||||
const name = "csrftoken=";
|
||||
const cookies = document.cookie.split(";");
|
||||
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const c = cookies[i];
|
||||
if (c) {
|
||||
const cookie = c.trim();
|
||||
if (cookie.startsWith(name)) {
|
||||
return decodeURIComponent(cookie.substring(name.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
85
src/stores/User/Auth.ts
Normal file
85
src/stores/User/Auth.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { authAPI } from "@/core/endpoints/auth";
|
||||
import api from "@/services/API/api";
|
||||
import type { LoginPayload } from "@/dtos/User/auth";
|
||||
import router from "@/router";
|
||||
import { acceptHMRUpdate, defineStore } from "pinia";
|
||||
import { useUser } from "./User";
|
||||
import { Toast } from "dolphin-components";
|
||||
|
||||
export const useAuth = defineStore("auth", {
|
||||
state: () => ({
|
||||
loginDetails: {
|
||||
username: "",
|
||||
password: "",
|
||||
} as LoginPayload,
|
||||
token: null as null | string,
|
||||
loginError: false,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
getLoginDetail(state) {
|
||||
return state.loginDetails;
|
||||
},
|
||||
getLoginError(state) {
|
||||
return state.loginError;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async csrfGenerate() {
|
||||
try {
|
||||
await api.get(authAPI.csrfGenerate);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
async login() {
|
||||
try {
|
||||
// const response = await api.post(authAPI.login, this.loginDetails);
|
||||
await api.post(authAPI.login, this.loginDetails);
|
||||
Toast.success("Logged in successfully.");
|
||||
router.push({ name: "dashboard" });
|
||||
} catch {
|
||||
Toast.error("Unable to login!");
|
||||
this.hasLoginError();
|
||||
}
|
||||
},
|
||||
async logout() {
|
||||
const userStore = useUser();
|
||||
try {
|
||||
await api.post(authAPI.logout);
|
||||
userStore.$reset();
|
||||
this.loginDetails.username = "";
|
||||
this.loginDetails.password = "";
|
||||
this.token = null;
|
||||
Toast.success("Logged out successfully.");
|
||||
router.push({ name: "login" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
async refreshToken() {
|
||||
try {
|
||||
await api.post(authAPI.refresh);
|
||||
// if (response.data) {
|
||||
// this.token = response.data.data.access;
|
||||
// }
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
hasLoginError(status: boolean = true) {
|
||||
if (status) {
|
||||
this.loginError = true;
|
||||
setTimeout(() => {
|
||||
this.loginError = false;
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot));
|
||||
}
|
||||
36
src/stores/User/User.ts
Normal file
36
src/stores/User/User.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { authAPI } from "@/core/endpoints/auth";
|
||||
import api from "@/services/API/api";
|
||||
import type { UserDetail } from "@/dtos/User/user";
|
||||
// import { Toast } from "dolphin-components";
|
||||
import { acceptHMRUpdate, defineStore } from "pinia";
|
||||
|
||||
export const useUser = defineStore("user", {
|
||||
state: () => ({
|
||||
isAuthenticated: false,
|
||||
user: {} as UserDetail,
|
||||
}),
|
||||
getters: {
|
||||
getUser(state) {
|
||||
return state.user;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchUserDetail() {
|
||||
try {
|
||||
const response = await api.get(authAPI.self);
|
||||
this.user = response.data.data;
|
||||
this.isAuthenticated = true;
|
||||
} catch (error: any) {
|
||||
if (error.response.status == 403) {
|
||||
// Toast.error("You do not have the permissions to perform this action.");
|
||||
} else {
|
||||
// Toast.error("Login failed!");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot));
|
||||
}
|
||||
13
src/utils/setup/SidebarItems.ts
Normal file
13
src/utils/setup/SidebarItems.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import type { RouteRecordRaw } from "vue-router";
|
||||
const authChildren: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "dashboard",
|
||||
component: () => import("@/views/Dashboard/Components/Dashboard.vue"),
|
||||
meta: {
|
||||
permission: "",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default authChildren;
|
||||
46
src/utils/setup/routerSetup.ts
Normal file
46
src/utils/setup/routerSetup.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import router from "@/router";
|
||||
// import { useAuth } from "@/stores/User/Auth";
|
||||
import { useUser } from "@/stores/User/User";
|
||||
import type { App } from "vue";
|
||||
// import { hasPermission } from "../common/permission";
|
||||
|
||||
const initRouter = (app: App) => {
|
||||
const useUserStore = useUser();
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
await useUserStore.fetchUserDetail();
|
||||
if (to.name == "login") {
|
||||
if (useUserStore.isAuthenticated) {
|
||||
next({
|
||||
name: "dashboard",
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
if (to.meta.requireAuth) {
|
||||
if (useUserStore.isAuthenticated) {
|
||||
if (to.meta.permission) {
|
||||
// if (hasPermission(to.meta.permission)) {
|
||||
// next();
|
||||
// } else {
|
||||
// next({
|
||||
// name: "404",
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next({
|
||||
name: "login",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
app.use(router);
|
||||
};
|
||||
|
||||
export default initRouter;
|
||||
120
src/views/Auth/Login.vue
Normal file
120
src/views/Auth/Login.vue
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div style="height: 100vh; background-image: url(/svg/large-triangles.svg); background-size: cover">
|
||||
<div class="w-[400px] mx-auto">
|
||||
<div class="flex justify-center">
|
||||
<img src="/img/Dolphin/dolphin-logo.png" class="w-36 my-5" />
|
||||
</div>
|
||||
<div class="border border-secondary-100 rounded-xs px-10 py-10 bg-white select-none">
|
||||
<div class="text-center" :class="getLoginError ? '' : 'mb-[10px]'">
|
||||
<p class="text-[30px] text-semibold">Sign In</p>
|
||||
<div class="text-sm font-normal">Fill your detail to sign in to Dolphin DartaChalani.</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-[12px] mb-[-19px]" v-if="getLoginError">
|
||||
<div class="text-sm text-red-700 font-semibold text-center flex gap-[15px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5 -mr-3"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
The username or password you entered is incorrect.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="onLogin" class="pt-10">
|
||||
<div class="">
|
||||
<label for="username">Username</label>
|
||||
<div class="my-1">
|
||||
<input
|
||||
v-model="getLoginDetail.username"
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
autocomplete="username"
|
||||
required
|
||||
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-[15px]">
|
||||
<label for="username">Password</label>
|
||||
<div class="my-1 flex">
|
||||
<!--When show password is false-->
|
||||
<input
|
||||
v-model="getLoginDetail.password"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
v-if="!showPassword"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
|
||||
/>
|
||||
<!--When show password is true-->
|
||||
<input
|
||||
v-model="getLoginDetail.password"
|
||||
id="password"
|
||||
name="password"
|
||||
type="text"
|
||||
v-else
|
||||
autocomplete="current-password"
|
||||
required
|
||||
class="p-[10px] max-h-[40px]! h-[40px]! text-[14px] w-full"
|
||||
/>
|
||||
<span
|
||||
class="bg-gray-100 flex w-[50px] border-r border-t border-b border-gray-300 hover:bg-gray-200 cursor-pointer transition-all"
|
||||
@click="showPassword = !showPassword"
|
||||
>
|
||||
<div class="m-auto">
|
||||
<Icons
|
||||
name="EyeOff"
|
||||
size="20"
|
||||
class="text-lg text-gray-900 mt-1 mr-[-1px]"
|
||||
v-if="!showPassword"
|
||||
/>
|
||||
<Icons name="Eye" class="text-lg text-gray-900 mt-1" size="20" v-else />
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn w-full max-h-[40px]! h-[40px]! text-lg!">Sign In</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-center my-5 select-none">
|
||||
<div>
|
||||
© 2024
|
||||
<a href="https://mavorion.com" class="text-[#224CAD] underline">Mavorion Systems</a>
|
||||
. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuth } from "@/stores/User/Auth";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const authStore = useAuth();
|
||||
|
||||
const showPassword = ref(false);
|
||||
|
||||
const { getLoginDetail, getLoginError } = storeToRefs(authStore);
|
||||
|
||||
const onLogin = () => {
|
||||
authStore.login();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
authStore.csrfGenerate();
|
||||
});
|
||||
</script>
|
||||
107
src/views/Dashboard/Components/Dashboard.vue
Normal file
107
src/views/Dashboard/Components/Dashboard.vue
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<div class="text-[16px] font-bold pb-[15px]">Documents Details</div>
|
||||
<div class="grid grid-cols-4 gap-[15px] rounded-default">
|
||||
<div class="p-[15px] bg-white">
|
||||
<div class="text-[#2f2f2f]">Total Documents</div>
|
||||
<div class="text-[24px] text-[#333131]">
|
||||
{{ data.total }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-[15px] bg-white relative">
|
||||
<div class="text-[#2f2f2f]">Incoming (Darta)</div>
|
||||
<div class="text-[24px] text-[#333131]">
|
||||
{{ data.incoming }}
|
||||
</div>
|
||||
<!-- <div class="text-[#2f2f2f]">({{ getPercentage(data.total, data.scanned) }} % of {{ data.total }})</div>
|
||||
<div class="w-[100px] h-[70px] ml-auto absolute right-0 top-[20px]">
|
||||
<v-chart :option="getRadialOption(getPercentage(data.total, data.scanned))" />
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="p-[15px] bg-white relative">
|
||||
<div class="w-fit">
|
||||
<div class="text-[#2f2f2f]">Outgoing (Chalani)</div>
|
||||
<div class="text-[24px] text-[#333131]">
|
||||
{{ data.outgoing }}
|
||||
</div>
|
||||
<!-- <div class="text-[#2f2f2f]">({{ getPercentage(data.total, data.pending) }} % of {{ data.total }})</div> -->
|
||||
</div>
|
||||
<!-- <div class="w-[100px] h-[70px] ml-auto absolute right-0 top-[20px]">
|
||||
<v-chart :option="getRadialOption(getPercentage(data.total, data.pending))" />
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="p-[15px] bg-white relative">
|
||||
<div class="w-fit">
|
||||
<div class="text-[#2f2f2f]">Tippani</div>
|
||||
<div class="text-[24px] text-[#333131]">
|
||||
{{ data.tippani }}
|
||||
</div>
|
||||
<!-- <div class="text-[#2f2f2f]">({{ getPercentage(data.total, data.pending) }} % of {{ data.total }})</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import api from "@/services/API/api";
|
||||
import { ref, onMounted } from "vue";
|
||||
// import VChart from "vue-echarts";
|
||||
|
||||
// import { use } from "echarts/core";
|
||||
// import { CanvasRenderer } from "echarts/renderers";
|
||||
// import { PieChart } from "echarts/charts";
|
||||
// import { TitleComponent, TooltipComponent, LegendComponent } from "echarts/components";
|
||||
|
||||
// use([CanvasRenderer, PieChart, TitleComponent, TooltipComponent, LegendComponent]);
|
||||
|
||||
const data = ref({
|
||||
total: 6,
|
||||
incoming: 3,
|
||||
outgoing: 2,
|
||||
tippani: 1,
|
||||
// schedule: {
|
||||
// name: "",
|
||||
// running: 0,
|
||||
// },
|
||||
});
|
||||
|
||||
// const getRadialOption = (percent: number) => ({
|
||||
// series: [
|
||||
// {
|
||||
// type: "pie",
|
||||
// radius: ["80%", "100%"],
|
||||
// avoidLabelOverlap: false,
|
||||
// silent: true,
|
||||
// data: [
|
||||
// { value: percent, itemStyle: { color: "#4a8e57" } },
|
||||
// { value: 100 - percent, itemStyle: { color: "#e6edf7" } },
|
||||
// ],
|
||||
// label: {
|
||||
// show: true,
|
||||
// position: "center",
|
||||
// formatter: `${Math.round(percent)}%`,
|
||||
// fontSize: 12,
|
||||
// color: "#6b7280", // Tailwind's gray-500
|
||||
// fontWeight: 500,
|
||||
// },
|
||||
// labelLine: { show: false },
|
||||
// emphasis: { disabled: true },
|
||||
// hoverAnimation: false,
|
||||
// },
|
||||
// ],
|
||||
// tooltip: { show: false },
|
||||
// legend: { show: false },
|
||||
// });
|
||||
|
||||
// onMounted(() => {
|
||||
// api.get("/canteen/meal-report-count/").then((e) => {
|
||||
// data.value = e.data.data;
|
||||
// });
|
||||
// });
|
||||
|
||||
// const getPercentage = (total: number, by: number) => {
|
||||
// if (!by) {
|
||||
// return 0;
|
||||
// }
|
||||
// return parseFloat(((by / total) * 100).toFixed(2));
|
||||
// };
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user