Compare commits

..

2 Commits

7 changed files with 155 additions and 59 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
VITE_API_URL=http://ba.digital-tps.tk

59
src/api/fetch-wrapper.js Normal file
View File

@ -0,0 +1,59 @@
import { useAuthStore } from "@/stores";
export const fetchWrapper = {
get: request("GET"),
post: request("POST"),
put: request("PUT"),
delete: request("DELETE"),
};
function request(method) {
return (url, body) => {
console.log("url: " + url, "body: ", body);
const requestOptions = {
method,
headers: authHeader(url),
};
if (body) {
requestOptions.headers["Content-Type"] = "application/json";
requestOptions.body = JSON.stringify(body);
}
return fetch(url, requestOptions).then(handleResponse);
};
}
// helper functions
function authHeader(url) {
// return auth header with jwt if user is logged in and request is to the api url
const { user } = useAuthStore();
const isLoggedIn = !!user?.token;
const isApiUrl = url.startsWith(import.meta.env.VITE_API_URL);
if (isLoggedIn && isApiUrl) {
return { Authorization: `Bearer ${user.token}` };
} else {
return {};
}
}
async function handleResponse(response) {
const isJson = response.headers
?.get("content-type")
?.includes("application/json");
const data = isJson ? await response.json() : null;
// check for error response
if (!response.ok) {
const { user, logout } = useAuthStore();
if ([401, 403].includes(response.status) && user) {
// auto logout if 401 Unauthorized or 403 Forbidden response returned from api
logout();
}
// get error message from body or default to response status
const error = (data && data.message) || response.status;
return Promise.reject(error);
}
return data;
}

1
src/api/index.js Normal file
View File

@ -0,0 +1 @@
export * from "./fetch-wrapper";

19
src/stores/alert.store.js Normal file
View File

@ -0,0 +1,19 @@
import { defineStore } from "pinia";
export const useAlertStore = defineStore({
id: "alert",
state: () => ({
alert: null,
}),
actions: {
success(message) {
this.alert = { message, type: "alert-success" };
},
error(message) {
this.alert = { message, type: "alert-danger" };
},
clear() {
this.alert = null;
},
},
});

45
src/stores/auth.js Normal file
View File

@ -0,0 +1,45 @@
import { defineStore } from "pinia";
import { fetchWrapper } from "@/api";
// import { router } from "@/router";
import { useAlertStore } from "@/stores";
const baseUrl = `${import.meta.env.VITE_API_URL}/api`;
export const useAuthStore = defineStore({
id: "auth",
state: () => ({
// initialize state from local storage to enable user to stay logged in
user: JSON.parse(localStorage.getItem("user")),
returnUrl: null,
}),
actions: {
async login(email, password) {
return fetchWrapper
.post(`${baseUrl}/login`, { email, password })
.then((response) => {
console.log("response: " + response);
console.log("user: " + response["success"]["token"]["plainTextToken"]);
// update pinia state
this.user = user;
// store user details and jwt in local storage to keep user logged in between page refreshes
// localStorage.setItem("user", JSON.stringify(user));
// redirect to previous url or default to home page
// router.push(this.returnUrl || "/");
})
.catch((error) => {
const alertStore = useAlertStore();
alertStore.error(error);
});
},
logout() {
// this.user = null;
// localStorage.removeItem("user");
// router.push("/account/login");
},
},
});

2
src/stores/index.js Normal file
View File

@ -0,0 +1,2 @@
export * from "./alert.store";
export * from "./auth";

View File

@ -6,28 +6,17 @@
<!-- BEGIN: Login Info -->
<div class="hidden xl:flex flex-col min-h-screen">
<a href="" class="-intro-x flex items-center pt-5">
<img
alt="Midone Tailwind HTML Admin Template"
class="w-6"
src="@/assets/images/logo.svg"
/>
<img alt="Midone Tailwind HTML Admin Template" class="w-6" src="@/assets/images/logo.svg" />
<span class="text-white text-lg ml-3"> Icewall </span>
</a>
<div class="my-auto">
<img
alt="Midone Tailwind HTML Admin Template"
class="-intro-x w-1/2 -mt-16"
src="@/assets/images/illustration.svg"
/>
<div
class="-intro-x text-white font-medium text-4xl leading-tight mt-10"
>
<img alt="Midone Tailwind HTML Admin Template" class="-intro-x w-1/2 -mt-16"
src="@/assets/images/illustration.svg" />
<div class="-intro-x text-white font-medium text-4xl leading-tight mt-10">
A few more clicks to <br />
sign in to your account.
</div>
<div
class="-intro-x mt-5 text-lg text-white text-opacity-70 dark:text-slate-400"
>
<div class="-intro-x mt-5 text-lg text-white text-opacity-70 dark:text-slate-400">
Manage all your e-commerce accounts in one place
</div>
</div>
@ -36,67 +25,39 @@
<!-- BEGIN: Login Form -->
<div class="h-screen xl:h-auto flex py-5 xl:py-0 my-10 xl:my-0">
<div
class="my-auto mx-auto xl:ml-20 bg-white dark:bg-darkmode-600 xl:bg-transparent px-5 sm:px-8 py-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto"
>
<h2
class="intro-x font-bold text-2xl xl:text-3xl text-center xl:text-left"
>
class="my-auto mx-auto xl:ml-20 bg-white dark:bg-darkmode-600 xl:bg-transparent px-5 sm:px-8 py-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto">
<h2 class="intro-x font-bold text-2xl xl:text-3xl text-center xl:text-left">
Sign In
</h2>
<div class="intro-x mt-2 text-slate-400 xl:hidden text-center">
A few more clicks to sign in to your account. Manage all your
e-commerce accounts in one place
A few more clicks to sign in to your account. Manage all your e-commerce
accounts in one place
</div>
<div class="intro-x mt-8">
<input
type="text"
class="intro-x login__input form-control py-3 px-4 block"
placeholder="Email"
/>
<input
type="password"
class="intro-x login__input form-control py-3 px-4 block mt-4"
placeholder="Password"
/>
<input type="text" class="intro-x login__input form-control py-3 px-4 block" placeholder="Email" />
<input type="password" class="intro-x login__input form-control py-3 px-4 block mt-4"
placeholder="Password" />
</div>
<div
class="intro-x flex text-slate-600 dark:text-slate-500 text-xs sm:text-sm mt-4"
>
<div class="intro-x flex text-slate-600 dark:text-slate-500 text-xs sm:text-sm mt-4">
<div class="flex items-center mr-auto">
<input
id="remember-me"
type="checkbox"
class="form-check-input border mr-2"
/>
<label class="cursor-pointer select-none" for="remember-me"
>Remember me</label
>
<input id="remember-me" type="checkbox" class="form-check-input border mr-2" />
<label class="cursor-pointer select-none" for="remember-me">Remember me</label>
</div>
<a href="">Forgot Password?</a>
</div>
<div class="intro-x mt-5 xl:mt-8 text-center xl:text-left">
<button
class="btn btn-primary py-3 px-4 w-full xl:w-32 xl:mr-3 align-top"
>
<button class="btn btn-primary py-3 px-4 w-full xl:w-32 xl:mr-3 align-top" @click.prevent="onSubmit">
Login
</button>
<button
class="btn btn-outline-secondary py-3 px-4 w-full xl:w-32 mt-3 xl:mt-0 align-top"
>
<button class="btn btn-outline-secondary py-3 px-4 w-full xl:w-32 mt-3 xl:mt-0 align-top">
Register
</button>
</div>
<div
class="intro-x mt-10 xl:mt-24 text-slate-600 dark:text-slate-500 text-center xl:text-left"
>
<div class="intro-x mt-10 xl:mt-24 text-slate-600 dark:text-slate-500 text-center xl:text-left">
By signin up, you agree to our
<a class="text-primary dark:text-slate-200" href=""
>Terms and Conditions</a
>
<a class="text-primary dark:text-slate-200" href="">Terms and Conditions</a>
&
<a class="text-primary dark:text-slate-200" href=""
>Privacy Policy</a
>
<a class="text-primary dark:text-slate-200" href="">Privacy Policy</a>
</div>
</div>
</div>
@ -110,6 +71,14 @@
import { onMounted } from "vue";
import DarkModeSwitcher from "@/components/dark-mode-switcher/Main.vue";
import dom from "@left4code/tw-starter/dist/js/dom";
import { useAuthStore } from '@/stores'
const onSubmit = () => {
console.log("onSubmit");
const authStore = useAuthStore();
// const { username, password } = values;
return authStore.login("ilmedovamahri@gmail.com", "12345678");
};
onMounted(() => {
dom("body").removeClass("main").removeClass("error-page").addClass("login");