install depen
npm install axios
npm install chart.js
npm install js-cookie
npm install lodash
npm install luxon
npm install numeral
npm install pinia
npm install pinia-plugin-persistedstate
npm install vue-router
npm install tailwindcss @tailwindcss/vite
//vite.config.ts
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
// css
@import "tailwindcss";
edit App.vue
<script setup>
import { RouterLink, RouterView } from "vue-router";
</script>
<template>
<RouterView />
</template>
edit main.ts/js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
# force artinya jika folder atau filenya sudah tidak ada ,tidak akan error dan di overwrite
mkdir src/helpers -Force; New-Item -ItemType File src/helpers/errorHelper.js -Force
New-Item -ItemType File src/helpers/format.js -Force
New-Item -ItemType File src/helpers/permissionHelper.js -Force
mkdir src/plugins -Force; New-Item -ItemType File src/plugins/axios.js -Force
mkdir src/layouts -Force; New-Item -ItemType File src/layouts/Auth.vue -Force
New-Item -ItemType File src/layouts/Main.vue -Force
mkdir src/views -Force; New-Item -ItemType File src/views/Login.vue -Force
New-Item -ItemType File src/views/Dashboard.vue -Force
mkdir src/stores -Force; New-Item -ItemType File src/stores/auth.js -Force
New-Item -ItemType File src/index.css -Force
mkdir src/router -Force; New-Item -ItemType File src/router/index.js -Force
// src/roter/index.ts
import Auth from "@/layouts/Auth.vue";
import Main from "@/layouts/Main.vue";
import Dashboard from "@/views/Dashboard.vue";
import Login from "@/views/Login.vue";
import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "@/stores/auth";
import HeadOfFamilies from "@/views/head-of-family/HeadOfFamilies.vue";
import HeadOfFamily from "@/views/head-of-family/HeadOfFamily.vue";
import HeadOfFamilyCreate from "@/views/head-of-family/HeadOfFamilyCreate.vue";
import SocialAssistances from "@/views/social-assistance/SocialAssistances.vue";
import SocialAssistance from "@/views/social-assistance/SocialAssistance.vue";
import SocialAssistanceEdit from "@/views/social-assistance/SocialAssistanceEdit.vue";
import SocialAssistanceCreate from "@/views/social-assistance/SocialAssistanceCreate.vue";
import SocialAssistanceRecipients from "@/views/social-assistance-recipient/socialAssistanceRecipients.vue";
import SocialAssistanceRecipient from "@/views/social-assistance-recipient/SocialAssistanceRecipient.vue";
import Developments from "@/views/development/developments.vue";
import Development from "@/views/development/development.vue";
import DevelopmentCreate from "@/views/development/developmentCreate.vue";
import DevelopmentEdit from "@/views/development/developmentEdit.vue";
import Events from "@/views/Event/Events.vue";
import Event from "@/views/Event/Event.vue";
import EventEdit from "@/views/Event/EventEdit.vue";
import EventCreate from "@/views/Event/EventCreate.vue";
import Profile from "@/views/profile/Profile.vue";
import ProfileCreate from "@/views/profile/ProfileCreate.vue";
import FamilyMembers from "@/views/family-member/FamilyMembers.vue";
import FamilyMember from "@/views/family-member/FamilyMember.vue";
import FamilyMemberCreate from "@/views/family-member/FamilyMemberCreate.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
component: Main,
children: [
{
path: "",
name: "dashboard",
component: Dashboard,
meta: { requiresAuth: true, permission: "dashboard-menu" },
},
{
path: "family-member/:id",
name: "manage-family-member",
component: FamilyMember,
meta: { requiresAuth: true, permission: "family-member-list" },
},
{
path: "family-member",
name: "family-member",
component: FamilyMembers,
meta: { requiresAuth: true, permission: "family-member-list" },
},
{
path: "family-member/create",
name: "create-family-member",
component: FamilyMemberCreate,
meta: { requiresAuth: true, permission: "family-member-create" },
},
{
path: "head-of-family",
name: "head-of-family",
component: HeadOfFamilies,
meta: { requiresAuth: true, permission: "head-of-family-list" },
},
{
path: "head-of-family/:id", // ✅ fix param route
name: "manage-head-of-family",
component: HeadOfFamily,
meta: { requiresAuth: true, permission: "head-of-family-list" },
},
{
path: "head-of-family/create",
name: "create-head-of-family",
component: HeadOfFamilyCreate,
meta: { requiresAuth: true, permission: "head-of-family-create" },
},
{
path: "social-assistance",
name: "social-assistance",
component: SocialAssistances,
meta: { requiresAuth: true, permission: "social-assistance-list" },
},
{
path: "social-assistance/:id", // ✅ fix param route
name: "manage-social-assistance",
component: SocialAssistance,
meta: { requiresAuth: true, permission: "social-assistance-list" },
},
{
path: "social-assistance/create",
name: "create-social-assistance",
component: SocialAssistanceCreate,
meta: { requiresAuth: true, permission: "social-assistance-create" },
},
{
path: "social-assistance/edit/:id", // ✅ fix param route
name: "edit-social-assistance",
component: SocialAssistanceEdit,
meta: { requiresAuth: true, permission: "social-assistance-edit" },
},
{
path: "social-assistance-recipient",
name: "social-assistance-recipient",
component: SocialAssistanceRecipients,
meta: {
requiresAuth: true,
permission: "social-assistance-recipient-list",
},
},
{
path: "social-assistance-recipient/:id", // ✅ fix param route
name: "manage-social-assistance-recipient",
component: SocialAssistanceRecipient,
meta: { requiresAuth: true, permission: "social-assistance-list" },
},
{
path: "development",
name: "development",
component: Developments,
meta: { requiresAuth: true, permission: "development-list" },
},
{
path: "development/:id", // ✅ fix param route
name: "manage-development",
component: Development,
meta: { requiresAuth: true, permission: "development-list" },
},
{
path: "development/create",
name: "create-development",
component: DevelopmentCreate,
meta: { requiresAuth: true, permission: "development-create" },
},
{
path: "development/edit/:id", // ✅ fix param route
name: "edit-development",
component: DevelopmentEdit,
meta: { requiresAuth: true, permission: "development-edit" },
},
{
path: "event",
name: "event",
component: Events,
meta: { requiresAuth: true, permission: "event-list" },
},
{
path: "event/:id",
name: "manage-event",
component: Event,
meta: { requiresAuth: true, permission: "event-list" },
},
{
path: "event/create",
name: "create-event",
component: EventCreate,
meta: { requiresAuth: true, permission: "event-create" },
},
{
path: "event/edit/:id",
name: "edit-event",
component: EventEdit,
meta: { requiresAuth: true, permission: "event-edit" },
},
{
path: "profile",
name: "profile",
component: Profile,
meta: { requiresAuth: true, permission: "profile-menu" },
},
{
path: "profile/create",
name: "create-profile",
component: ProfileCreate,
meta: { requiresAuth: true, permission: "profile-create" },
},
],
},
{
path: "/login",
component: Auth,
children: [
{
path: "",
name: "login",
component: Login,
meta: { requiresUnauth: true },
},
],
},
// {
// path: "/403", // ✅ tambahin biar nggak error
// name: "Error 403",
// component: () => import("@/views/errors/Error403.vue"),
// },
],
});
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
if (to.meta.requiresAuth) {
if (authStore.isAuthenticated) {
try {
if (!authStore.user) {
await authStore.checkAuth();
}
const userPermissions = authStore.user?.permissions || [];
if (
to.meta.permission &&
!userPermissions.includes(to.meta.permission)
) {
next({ name: "Error 403" });
return;
}
next();
} catch (error) {
next({ name: "login" });
}
} else {
next({ name: "login" });
}
} else if (to.meta.requiresUnauth && authStore.isAuthenticated) {
next({ name: "dashboard" });
} else {
next();
}
});
export default router;
// src/stores/auth.ts
import { handleError } from "@/helpers/errorHelper";
import { axiosInstance } from "@/plugins/axios";
import router from "@/router";
import Cookies from "js-cookie";
import { defineStore } from "pinia";
export const useAuthStore = defineStore("auth", {
state: () => ({
user: null,
token: Cookies.get("token") || null, // ✅ simpan di state
loading: false,
error: null,
success: null,
}),
getters: {
isAuthenticated: state => !!state.token, // ✅ gampang cek login
},
actions: {
async login(credentials) {
this.loading = true;
try {
const response = await axiosInstance.post("/login", credentials);
const token = response.data.token;
this.token = token; // ✅ simpan ke state
Cookies.set("token", token);
axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${token}`;
this.success = "Login successful";
await router.push({ name: "dashboard" });
} catch (error) {
this.error = handleError(error);
} finally {
this.loading = false;
}
},
async logout() {
this.loading = true;
try {
await axiosInstance.post("/logout");
this.token = null; // ✅ clear dari state
Cookies.remove("token");
delete axiosInstance.defaults.headers.common["Authorization"]; // ✅ clear header
this.user = null;
this.error = null;
this.success = "Logout successful";
await router.push({ name: "login" });
} catch (error) {
this.error = handleError(error);
} finally {
this.loading = false;
}
},
async checkAuth() {
this.loading = true;
try {
const response = await axiosInstance.get("/me");
this.user = response.data.data;
return this.user;
} catch (error) {
if (error.response && error.response.status === 401) {
await this.logout();
}
} finally {
this.loading = false;
}
},
},
});
// Main.vue
<script setup lang="ts"></script>
<template>
<div class="flex flex1">
<div id="Main-Container" class="flex flex-col flex-1">
<div
id="Content"
class="relative flec flex-col flex-1 gap-[14px] p6 pb-[30px] w-full shrink-0"
>
<RouterView />
</div>
</div>
</div>
</template>
// Auth.vue
<script setup lang="ts">
import { RouterView } from 'vue-router';
</script>
<template>
<main class="flex min-h-screen">
<RouterView />
</main>
</template>
// axios.js
import axios from 'axios';
import Cookies from 'js-cookie';
axios.defaults.baseURL = 'http://localhost:8000/api';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.headers.common['Accept'] = 'application/json';
// axios.defaults.headers.common['Content-Type'] = 'application/json';
axios.defaults.headers.common['Content-Type'] = 'multipart/form-data';
axios.interceptors.request.use(
config => {
const token = Cookies.get('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
export const axiosInstance = axios;
errorHelper.js
export function handleError(error) {
if (error.response && error.response.status === 422) {
return error.response.data.errors;
} else if (error.response && error.response.status === 401) {
return error.response.data.message;
} else if (error.response && error.response.status === 400) {
return error.response.data.message;
} else if (error.response && error.response.status === 500) {
return error.response.data.message;
} else {
alert("Something went wrong, Please try again later");
}
}
format.js
import numeral from 'numeral';
import { DateTime } from 'luxon';
export function formatRupiah(value) {
return numeral(value).format('0,0[.]00');
}
export function parseRupiah(value) {
return numeral(value).value();
}
export function formatPercentage(value) {
return numeral(value).format('0,0[.]00%');
}
export function formatDate(date) {
const options = { day: 'numeric', month: 'long', year: 'numeric' };
return new Date(date).toLocaleDateString('id-ID', options);
}
export function formatDateTime(date) {
const options = { day: 'numeric', month: 'long', year: 'numeric', hour: 'numeric', minute: 'numeric' };
return new Date(date).toLocaleDateString('id-ID', options);
}
export function formatToClientTimezone(date) {
const originalDate = DateTime.fromISO(date, { zone: 'utc' });
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return originalDate
.setZone(timezone)
.setLocale('id')
.toFormat('dd LLLL yyyy, HH:mm');
}
export function ucfirst(string) {
return string ? string.charAt(0).toUpperCase() + string.slice(1) : '';
}
permissionHelper.js
import { useAuthStore } from '@/stores/auth'
export const can = permission => {
const authStore = useAuthStore()
const userPermissions = authStore.user?.permissions || []
return userPermissions.includes(permission)
}
import axios from "axios";
import Cookies from "js-cookie";
const token = Cookies.get("token");
axios.defaults.baseURL = "http://localhost:8000/api";
axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
axios.defaults.headers.common["Content-Type"] = "multipart/form-data";
axios.defaults.headers.common["Accept"] = "application/json";
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
axios.interceptors.request.use((config) => {
const token = Cookies.get("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export const axiosInstance = axios;
<script setup>
import { RouterView } from "vue-router";
</script>
<template>
<main class="flex min-h-screen">
<RouterView />
</main>
</template>