This commit is contained in:
Dede Fuji Abdul
2023-10-16 14:29:26 +07:00
parent 4ee87a0aa9
commit d80e8d2b5a
9 changed files with 979 additions and 96 deletions

4
.env.development Normal file
View File

@ -0,0 +1,4 @@
VITE_BASE_URL=https://api.domain.com/v1/
VITE_BASE_DIRECTORY=/
VITE_APP_VERSION=0.0.1
VITE_APP_NAME='Executive Information System'

4
.env.production Normal file
View File

@ -0,0 +1,4 @@
VITE_BASE_URL=https://api.domain.com/v1/
VITE_BASE_DIRECTORY=/
VITE_APP_VERSION=0.0.1
VITE_APP_NAME='Executive Information System'

877
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "apkt", "name": "eis",
"version": "0.0.0", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
@ -25,7 +25,9 @@
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "^4.2.2", "vue-router": "^4.2.2",
"vue-tailwind-datepicker": "^1.4.5" "vue-tailwind-datepicker": "^1.4.5",
"encrypt-storage": "^2.12.16",
"dotenv": "^16.3.1"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.5.1", "@rushstack/eslint-patch": "^1.5.1",

View File

@ -762,6 +762,10 @@ select {
left: -0.5rem; left: -0.5rem;
} }
.bottom-0 {
bottom: 0px;
}
.left-0 { .left-0 {
left: 0px; left: 0px;
} }
@ -802,34 +806,6 @@ select {
top: 100%; top: 100%;
} }
.top-\[50\%\] {
top: 50%;
}
.bottom-0 {
bottom: 0px;
}
.\!bottom-0 {
bottom: 0px !important;
}
.\!right-0 {
right: 0px !important;
}
.\!top-0 {
top: 0px !important;
}
.bottom-auto {
bottom: auto;
}
.top-auto {
top: auto;
}
.z-10 { .z-10 {
z-index: 10; z-index: 10;
} }
@ -867,6 +843,11 @@ select {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.mx-3 {
margin-left: 0.75rem;
margin-right: 0.75rem;
}
.mx-auto { .mx-auto {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -887,26 +868,6 @@ select {
margin-bottom: auto; margin-bottom: auto;
} }
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.mx-3 {
margin-left: 0.75rem;
margin-right: 0.75rem;
}
.\!mx-3 {
margin-left: 0.75rem !important;
margin-right: 0.75rem !important;
}
.\!my-auto {
margin-top: auto !important;
margin-bottom: auto !important;
}
.\!mt-0 { .\!mt-0 {
margin-top: 0px !important; margin-top: 0px !important;
} }
@ -1107,6 +1068,11 @@ select {
height: 74vh; height: 74vh;
} }
.h-fit {
height: -moz-fit-content;
height: fit-content;
}
.h-full { .h-full {
height: 100%; height: 100%;
} }
@ -1115,11 +1081,6 @@ select {
height: 80vh; height: 80vh;
} }
.h-fit {
height: -moz-fit-content;
height: fit-content;
}
.max-h-0 { .max-h-0 {
max-height: 0px; max-height: 0px;
} }
@ -1498,10 +1459,6 @@ select {
--tw-divide-opacity: 0.1; --tw-divide-opacity: 0.1;
} }
.justify-self-center {
justify-self: center;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }

View File

@ -12,7 +12,7 @@ const router = createRouter({
linkActiveClass: 'active', linkActiveClass: 'active',
routes: [ routes: [
{ {
path: '/', path: '/home',
name: 'Home', name: 'Home',
'meta': { requiresAuth: true }, 'meta': { requiresAuth: true },
component: HomeView, component: HomeView,
@ -295,9 +295,9 @@ const router = createRouter({
path: '/logout', path: '/logout',
name: 'Logout', name: 'Logout',
beforeEnter(to, from, next) { beforeEnter(to, from, next) {
const auth = useAuthStore(); const auth = useAuthStore()
auth.logout(); auth.logout()
next('/login'); next('/login')
}, },
redirect: '/logout' redirect: '/logout'
}, },
@ -319,26 +319,36 @@ const router = createRouter({
] ]
}) })
router.beforeEach((to, _from) => { router.beforeEach((to, from, next) => {
// redirect to login page if not logged in and trying to access a restricted page // redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const auth = useAuthStore(); const auth = useAuthStore();
// if to is not found, redirect to 404 // if to is not found, redirect to 404
if (to.matched.length === 0) { if (to.matched.length === 0) {
return '/404'; if (to.path === '/') {
} else { if (auth.isLoggedIn) {
// if to is not login and user is not logged in, redirect to login next('/home')
if (!auth.isLoggedIn) { } else {
// if to is 404, redirect to 404 next('/login')
if (to.path !== '/404' && to.path !== '/login') {
return '/login';
} }
} else { } else {
next('/404')
}
} else {
// if to is not login and user is not logged in, redirect to login
if (auth.isLoggedIn) {
// if to is login and user is logged in, redirect to home // if to is login and user is logged in, redirect to home
if (to.path === '/login') { if (to.path === '/login') {
return '/'; next('/home')
} else {
next()
}
} else {
// if to is 404, redirect to 404
if (to.path !== '/404' && to.path !== '/login') {
next('/login')
} else {
next()
} }
} }
} }

View File

@ -1,11 +1,12 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { dispatchNotification } from '@/components/Notification' import { dispatchNotification } from '@/components/Notification'
import { readData, removeData, writeData } from './storage'
import router from '@/router' import router from '@/router'
export const useAuthStore = defineStore('auth', () => { export const useAuthStore = defineStore('auth', () => {
// token from localStorage // token from localStorage
const token = ref(localStorage.getItem('token') || '') const token = ref(readData('token') || '')
// create a shared state // create a shared state
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
@ -13,7 +14,7 @@ export const useAuthStore = defineStore('auth', () => {
const isLoggedIn = computed(() => token.value !== '') const isLoggedIn = computed(() => token.value !== '')
// define your actions // define your actions
function login() { const login = () => {
if (username.value == '' || password.value == '') { if (username.value == '' || password.value == '') {
dispatchNotification({ title: 'Perhatian', content: 'Username atau password tidak boleh kosong', type: 'warning' }) dispatchNotification({ title: 'Perhatian', content: 'Username atau password tidak boleh kosong', type: 'warning' })
} else { } else {
@ -22,11 +23,10 @@ export const useAuthStore = defineStore('auth', () => {
isLoading.value = false isLoading.value = false
if (username.value == 'demo' && password.value == 'demo') { if (username.value == 'demo' && password.value == 'demo') {
// store token in localStorage // store token in localStorage
localStorage.setItem('token', 'secret-token') writeData('token', 'secret-token')
dispatchNotification({ title: 'Berhasil', content: 'Login berhasil, selamat datang kembali!', type: 'success' }) dispatchNotification({ title: 'Berhasil', content: 'Login berhasil, selamat datang kembali!', type: 'success' })
// redirect to home page after login // redirect to home page after login
// router.replace('/') router.push('/home')
router.go(0)
} else { } else {
dispatchNotification({ title: 'Login Gagal', content: 'Username atau password salah', type: 'error' }) dispatchNotification({ title: 'Login Gagal', content: 'Username atau password salah', type: 'error' })
} }
@ -34,10 +34,15 @@ export const useAuthStore = defineStore('auth', () => {
} }
} }
function logout() { const logout = () => removeData('token')
localStorage.removeItem('token')
}
// expose everything return {
return { token, username, password, isLoggedIn, login, logout, isLoading } token,
username,
password,
isLoggedIn,
isLoading,
login,
logout,
}
}) })

28
src/stores/storage.ts Normal file
View File

@ -0,0 +1,28 @@
import { EncryptStorage } from 'encrypt-storage'
const encryptStorage = new EncryptStorage('qwertyuiopasdfghjklzxcvbnm-1234567890')
const writeData = (key: string, data: any) => encryptStorage.setItem(key, data)
const writeDataJson = (key: string, data: any) => encryptStorage.setItem(key, JSON.stringify(data))
const removeData = (key: string) => encryptStorage.removeItem(key)
const readData = (key: string) => {
try {
return encryptStorage.getItem(key)
} catch (error) {
return undefined
}
}
const readDataJson = (key: string) => {
try {
return encryptStorage.getItem(key)
} catch (error) {
return undefined
}
}
export {
removeData,
writeData,
writeDataJson,
readData,
readDataJson
}

View File

@ -1,11 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from '@/stores/auth'; import { useAuthStore } from '@/stores/auth'
import Icon from '@/assets/images/pln-with-text.png'; import Icon from '@/assets/images/pln-with-text.png'
import Hero from '@/assets/images/hero.png'; import Hero from '@/assets/images/hero.png'
import Button from '@/components/ButtonPrimary.vue'; import Button from '@/components/ButtonPrimary.vue'
import InputText from '@/components/InputText.vue'; import InputText from '@/components/InputText.vue'
import { onMounted } from 'vue'
const authStore = useAuthStore(); const authStore = useAuthStore()
onMounted(() => {
window.document.title = `Login - ${import.meta.env.VITE_APP_NAME}`
})
</script> </script>
<template> <template>
@ -35,7 +40,6 @@ const authStore = useAuthStore();
<img :src="Hero" alt="logo" class="h-full" /> <img :src="Hero" alt="logo" class="h-full" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>