Tampilan Layout sudah sesuai dengan roleIdnya
Sudah sessionnya Sudah disesuaikan juga semisal superadmin ngubah role admin, maka admin tersebut akan logOut dan diarahkan ke halama login sudah bisa logOut
This commit is contained in:
@@ -90,40 +90,88 @@ const userState = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
deleteUser: {
|
||||
loading: false,
|
||||
|
||||
async submit(payload: { id: string; isActive?: boolean; roleId?: string }) {
|
||||
this.loading = true;
|
||||
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/user/updt`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
userState.deleteUser.loading = true;
|
||||
|
||||
const response = await fetch(`/api/user/delUser/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.status === 200 && data.success) {
|
||||
toast.success(data.message);
|
||||
|
||||
// refresh list
|
||||
userState.findMany.load(
|
||||
userState.findMany.page,
|
||||
10,
|
||||
userState.findMany.search
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "User berhasil dihapus permanen");
|
||||
await userState.findMany.load(); // refresh list user setelah delete
|
||||
} else {
|
||||
toast.error(data.message || "Gagal update user");
|
||||
toast.error(result?.message || "Gagal menghapus user");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error("Gagal update user");
|
||||
} catch (error) {
|
||||
console.error("Gagal delete user:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus user");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
userState.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
// Di file userState.ts atau dimana state user berada
|
||||
|
||||
update: {
|
||||
loading: false,
|
||||
|
||||
async submit(payload: { id: string; isActive?: boolean; roleId?: string }) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await fetch(`/api/user/updt`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.status === 200 && data.success) {
|
||||
// ✅ Tampilkan pesan yang berbeda jika role berubah
|
||||
if (data.roleChanged) {
|
||||
toast.success(
|
||||
`${data.message}\n\nUser akan logout otomatis dalam beberapa detik.`,
|
||||
{
|
||||
autoClose: 5000,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
toast.success(data.message);
|
||||
}
|
||||
|
||||
// Refresh list
|
||||
await userState.findMany.load(
|
||||
userState.findMany.page,
|
||||
10,
|
||||
userState.findMany.search
|
||||
);
|
||||
|
||||
return true; // ✅ Return success untuk handling di component
|
||||
} else {
|
||||
toast.error(data.message || "Gagal update user");
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("❌ Error update user:", e);
|
||||
toast.error("Gagal update user");
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templateRole = z.object({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// app/validasi/page.tsx
|
||||
//
|
||||
|
||||
'use client';
|
||||
|
||||
import { apiFetchOtpData, apiFetchVerifyOtp } from '@/app/api/auth/_lib/api_fetch_auth';
|
||||
@@ -17,89 +18,151 @@ export default function Validasi() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [kodeId, setKodeId] = useState<string | null>(null);
|
||||
|
||||
// Inisialisasi data OTP
|
||||
useEffect(() => {
|
||||
const storedKodeId = localStorage.getItem('auth_kodeId');
|
||||
if (!storedKodeId) {
|
||||
toast.error('Akses tidak valid');
|
||||
router.push('/login');
|
||||
router.replace('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
setKodeId(storedKodeId);
|
||||
|
||||
const fetchOtpData = async () => {
|
||||
const loadOtpData = async () => {
|
||||
try {
|
||||
const result = await apiFetchOtpData({ kodeId: storedKodeId });
|
||||
if (result.success && result.data?.nomor) {
|
||||
setNomor(result.data.nomor);
|
||||
} else {
|
||||
throw new Error('OTP tidak valid');
|
||||
throw new Error('Data OTP tidak valid');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Gagal muat OTP:', error);
|
||||
console.error('Gagal memuat data OTP:', error);
|
||||
toast.error('Kode verifikasi tidak valid');
|
||||
router.push('/login');
|
||||
router.replace('/login');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchOtpData();
|
||||
loadOtpData();
|
||||
}, [router]);
|
||||
|
||||
// Verifikasi OTP
|
||||
const handleVerify = async () => {
|
||||
if (!kodeId || !nomor || otp.length < 4) return;
|
||||
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
setLoading(true);
|
||||
const verifyResult = await apiFetchVerifyOtp({ nomor, otp, kodeId });
|
||||
|
||||
if (verifyResult.success && verifyResult.user) {
|
||||
// ✅ SET USER KE STORE
|
||||
authStore.setUser({
|
||||
id: verifyResult.user.id,
|
||||
name: verifyResult.user.name,
|
||||
roleId: Number(verifyResult.user.roleId),
|
||||
});
|
||||
|
||||
cleanupStorage();
|
||||
router.push('/admin/landing-page/profil/program-inovasi');
|
||||
return;
|
||||
}
|
||||
|
||||
// Hanya coba registrasi jika akun tidak ditemukan
|
||||
if (verifyResult.status === 404 && verifyResult.message?.includes('Akun tidak ditemukan')) {
|
||||
const username = localStorage.getItem('auth_username');
|
||||
if (!username) {
|
||||
toast.error('Data registrasi hilang');
|
||||
|
||||
if (!verifyResult.success) {
|
||||
// Registrasi baru?
|
||||
if (
|
||||
verifyResult.status === 404 &&
|
||||
verifyResult.message?.includes('Akun tidak ditemukan')
|
||||
) {
|
||||
await handleNewRegistration();
|
||||
return;
|
||||
}
|
||||
|
||||
const regRes = await fetch('/api/auth/finalize-registration', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nomor, username, otp, kodeId }),
|
||||
});
|
||||
|
||||
const regData = await regRes.json();
|
||||
if (regData.success) {
|
||||
cleanupStorage();
|
||||
router.push('/waiting-room'); // ✅
|
||||
} else {
|
||||
toast.error(regData.message || 'Registrasi gagal');
|
||||
}
|
||||
} else {
|
||||
// Hanya tampilkan error jika bukan kasus "akun tidak ditemukan"
|
||||
|
||||
// Error lain
|
||||
toast.error(verifyResult.message || 'Verifikasi gagal');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Verifikasi sukses → simpan user ke store
|
||||
const user = verifyResult.user;
|
||||
console.log('=== DEBUG USER ===');
|
||||
console.log('Full user object:', user);
|
||||
|
||||
if (!user || !user.id) {
|
||||
toast.error('Data pengguna tidak lengkap');
|
||||
return;
|
||||
}
|
||||
|
||||
const roleId = Number(user.roleId);
|
||||
authStore.setUser({
|
||||
id: user.id,
|
||||
name: user.name || user.username || 'User',
|
||||
roleId: roleId,
|
||||
});
|
||||
|
||||
cleanupStorage();
|
||||
|
||||
const isUserActive = user.isActive ?? user.is_active ?? true;
|
||||
|
||||
// Redirect berdasarkan status approval
|
||||
if (!isUserActive) {
|
||||
router.replace('/waiting-room');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Switch statement lebih clean
|
||||
let redirectPath: string;
|
||||
|
||||
switch (roleId) {
|
||||
case 0:
|
||||
case 1:
|
||||
redirectPath = '/admin/landing-page/profil/program-inovasi';
|
||||
break;
|
||||
case 2:
|
||||
redirectPath = '/admin/kesehatan/posyandu';
|
||||
break;
|
||||
case 3:
|
||||
redirectPath = '/admin/pendidikan/info-sekolah/jenjang-pendidikan';
|
||||
break;
|
||||
default:
|
||||
redirectPath = '/admin';
|
||||
console.warn('Unknown roleId:', roleId);
|
||||
}
|
||||
|
||||
console.log('Redirecting to:', redirectPath);
|
||||
router.replace(redirectPath);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Verifikasi error:', error);
|
||||
toast.error('Terjadi kesalahan');
|
||||
console.error('Error saat verifikasi:', error);
|
||||
toast.error('Terjadi kesalahan sistem');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Registrasi baru
|
||||
const handleNewRegistration = async () => {
|
||||
const username = localStorage.getItem('auth_username');
|
||||
if (!username) {
|
||||
toast.error('Data registrasi tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/finalize-registration', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nomor, username, otp, kodeId }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
// Set user sementara (tanpa roleId, akan diisi saat approve)
|
||||
authStore.setUser({
|
||||
id: 'pending',
|
||||
name: username,
|
||||
});
|
||||
cleanupStorage();
|
||||
router.replace('/waiting-room');
|
||||
} else {
|
||||
toast.error(data.message || 'Registrasi gagal');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error registrasi:', error);
|
||||
toast.error('Gagal menyelesaikan registrasi');
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupStorage = () => {
|
||||
localStorage.removeItem('auth_kodeId');
|
||||
localStorage.removeItem('auth_nomor');
|
||||
@@ -118,12 +181,15 @@ export default function Validasi() {
|
||||
if (data.success) {
|
||||
localStorage.setItem('auth_kodeId', data.kodeId);
|
||||
toast.success('OTP baru dikirim');
|
||||
} else {
|
||||
toast.error(data.message || 'Gagal mengirim ulang OTP');
|
||||
}
|
||||
} catch {
|
||||
toast.error('Gagal kirim ulang');
|
||||
toast.error('Gagal menghubungi server');
|
||||
}
|
||||
};
|
||||
|
||||
// Loading
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} align="center" justify="center" h="100vh">
|
||||
@@ -148,7 +214,7 @@ export default function Validasi() {
|
||||
Kami telah mengirim kode ke nomor <strong>{nomor}</strong>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box w="100%">
|
||||
<Box mb={20}>
|
||||
<Text c={colors['blue-button']} ta="center" fz="sm" fw="bold">
|
||||
Masukkan Kode Verifikasi
|
||||
@@ -176,7 +242,14 @@ export default function Validasi() {
|
||||
|
||||
<Text ta="center" size="sm" mt="md">
|
||||
Tidak menerima kode?{' '}
|
||||
<Button variant="subtle" onClick={handleResend} size="xs" p={0} h="auto" color={colors['blue-button']}>
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={handleResend}
|
||||
size="xs"
|
||||
p={0}
|
||||
h="auto"
|
||||
color={colors['blue-button']}
|
||||
>
|
||||
Kirim Ulang
|
||||
</Button>
|
||||
</Text>
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Select, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCheck, IconSearch, IconX } from '@tabler/icons-react';
|
||||
import { IconCheck, IconSearch, IconTrash, IconX } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import user from '../../_state/user/user-state';
|
||||
|
||||
|
||||
function User() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
@@ -27,10 +26,10 @@ function User() {
|
||||
}
|
||||
|
||||
function ListUser({ search }: { search: string }) {
|
||||
const stateUser = useProxy(user.userState)
|
||||
const stateRole = useProxy(user.roleState)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const stateUser = useProxy(user.userState);
|
||||
const stateRole = useProxy(user.roleState);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -42,21 +41,80 @@ function ListUser({ search }: { search: string }) {
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateUser.delete.submit(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stateUser.findMany.load()
|
||||
stateUser.deleteUser.delete(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
stateUser.findMany.load();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateRole.findMany.load()
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
stateRole.findMany.load();
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
const filteredData = data || []
|
||||
// ✅ Helper function untuk nama role
|
||||
const getRoleName = (roleId: string) => {
|
||||
// Cari dari data role yang sudah diload
|
||||
const role = stateRole.findMany.data.find((r) => r.id === roleId);
|
||||
return role?.name || "Unknown Role";
|
||||
};
|
||||
|
||||
// ✅ Handler untuk perubahan role dengan konfirmasi
|
||||
const handleRoleChange = async (
|
||||
userId: string,
|
||||
username: string,
|
||||
oldRoleId: string,
|
||||
newRoleId: string
|
||||
) => {
|
||||
// Skip jika sama
|
||||
if (oldRoleId === newRoleId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ✅ Konfirmasi perubahan role
|
||||
const confirmed = window.confirm(
|
||||
`⚠️ PERINGATAN\n\n` +
|
||||
`Mengubah role untuk "${username}" akan:\n` +
|
||||
`• Logout user otomatis dari semua device\n` +
|
||||
`• Mengubah akses menu sesuai role baru\n\n` +
|
||||
`Role: ${getRoleName(oldRoleId)} → ${getRoleName(newRoleId)}\n\n` +
|
||||
`Lanjutkan?`
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
// Reload data untuk reset dropdown ke nilai lama
|
||||
stateUser.findMany.load(page, 10, search);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ Submit update
|
||||
const success = await stateUser.update.submit({
|
||||
id: userId,
|
||||
roleId: newRoleId,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
// Reload data setelah berhasil update
|
||||
stateUser.findMany.load(page, 10, search);
|
||||
}
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
// ✅ Handler untuk toggle isActive
|
||||
const handleToggleActive = async (userId: string, currentStatus: boolean) => {
|
||||
const success = await stateUser.update.submit({
|
||||
id: userId,
|
||||
isActive: !currentStatus,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
stateUser.findMany.load(page, 10, search);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -80,16 +138,19 @@ function ListUser({ search }: { search: string }) {
|
||||
<TableTh style={{ width: '20%' }}>Nomor</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Role</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aktif / Nonaktif</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd style={{ width: '25%', }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>{item.username}</Text>
|
||||
<TableTd style={{ width: '25%' }}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.username}
|
||||
</Text>
|
||||
</TableTd>
|
||||
<TableTd style={{ width: '20%', }}>
|
||||
<TableTd style={{ width: '20%' }}>
|
||||
<Text truncate fz="sm" c="dimmed">
|
||||
{item.nomor}
|
||||
</Text>
|
||||
@@ -101,21 +162,22 @@ function ListUser({ search }: { search: string }) {
|
||||
label: r.name,
|
||||
value: r.id,
|
||||
}))}
|
||||
value={item.roleId} // ⬅ role milik user ini
|
||||
onChange={async (val) => {
|
||||
value={item.roleId}
|
||||
onChange={(val) => {
|
||||
if (!val) return;
|
||||
|
||||
await stateUser.update.submit({
|
||||
id: item.id,
|
||||
roleId: val, // ⬅ kirim roleId
|
||||
});
|
||||
|
||||
// reload data supaya UI up-to-date
|
||||
stateUser.findMany.load(page, 10, search);
|
||||
// ✅ Panggil handleRoleChange dengan konfirmasi
|
||||
handleRoleChange(
|
||||
item.id,
|
||||
item.username,
|
||||
item.roleId,
|
||||
val
|
||||
);
|
||||
}}
|
||||
searchable
|
||||
clearable={false} // role harus ada
|
||||
clearable={false}
|
||||
nothingFoundMessage="Role tidak ditemukan"
|
||||
disabled={stateUser.update.loading}
|
||||
/>
|
||||
</TableTd>
|
||||
|
||||
@@ -127,26 +189,34 @@ function ListUser({ search }: { search: string }) {
|
||||
<Button
|
||||
variant="light"
|
||||
color={item.isActive ? "green" : "red"}
|
||||
onClick={async () => {
|
||||
await stateUser.update.submit({
|
||||
id: item.id,
|
||||
isActive: !item.isActive, // toggle
|
||||
});
|
||||
|
||||
stateUser.findMany.load(page, 10, search);
|
||||
}}
|
||||
onClick={() => handleToggleActive(item.id, item.isActive)}
|
||||
disabled={stateUser.update.loading}
|
||||
>
|
||||
{item.isActive ? <IconCheck size={20} /> : <IconX size={20} />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
|
||||
<TableTd style={{ width: '15%' }}>
|
||||
<Button
|
||||
variant="light"
|
||||
color='red'
|
||||
disabled={stateUser.deleteUser.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<TableTd colSpan={5}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data user yang cocok</Text>
|
||||
<Text c="dimmed">Tidak ada data user yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
@@ -155,6 +225,7 @@ function ListUser({ search }: { search: string }) {
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
@@ -169,6 +240,7 @@ function ListUser({ search }: { search: string }) {
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
@@ -180,4 +252,4 @@ function ListUser({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default User;
|
||||
export default User;
|
||||
@@ -1,3 +1,4 @@
|
||||
// /* eslint-disable react-hooks/exhaustive-deps */
|
||||
// 'use client'
|
||||
|
||||
// import colors from "@/con/colors";
|
||||
@@ -257,9 +258,6 @@
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
'use client'
|
||||
|
||||
import colors from "@/con/colors";
|
||||
@@ -271,9 +269,11 @@ import {
|
||||
AppShellMain,
|
||||
AppShellNavbar,
|
||||
Burger,
|
||||
Center,
|
||||
Flex,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
NavLink,
|
||||
ScrollArea,
|
||||
Text,
|
||||
@@ -289,23 +289,164 @@ import {
|
||||
import _ from "lodash";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { useEffect, useState } from "react";
|
||||
import { navigationByRole } from "./_com/navigationByRole";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const [opened, { toggle }] = useDisclosure();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
|
||||
const router = useRouter();
|
||||
const segments = useSelectedLayoutSegments().map((s) => _.lowerCase(s));
|
||||
|
||||
//ambil user dari authStore
|
||||
const {user} = useSnapshot(authStore)
|
||||
console.log("Current User:", user); // 👈 Tambahkan ini
|
||||
const { user } = useSnapshot(authStore);
|
||||
console.log("Current user in store:", user);
|
||||
|
||||
//ambil navigation berdasarkan role
|
||||
// ✅ Fetch user dari backend jika belum ada di store
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setLoading(false);
|
||||
return;
|
||||
} // Sudah ada → jangan fetch
|
||||
|
||||
const fetchUser = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me');
|
||||
const data = await res.json();
|
||||
|
||||
if (data.user) {
|
||||
authStore.setUser({
|
||||
id: data.user.id,
|
||||
name: data.user.name,
|
||||
roleId: Number(data.user.roleId),
|
||||
});
|
||||
} else {
|
||||
authStore.setUser(null);
|
||||
router.replace('/login');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data pengguna:', error);
|
||||
authStore.setUser(null);
|
||||
router.replace('/login');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUser();
|
||||
}, [user, router]); // ✅ Sekarang 'user' terdefinisi
|
||||
|
||||
// ✅ Polling untuk cek perubahan role setiap 30 detik
|
||||
// Di layout.tsx - useEffect polling
|
||||
useEffect(() => {
|
||||
if (!user?.id) return;
|
||||
|
||||
const checkRoleUpdate = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me');
|
||||
const data = await res.json();
|
||||
|
||||
// ✅ Session tidak valid (sudah dihapus karena role berubah)
|
||||
if (!data.success || !data.user) {
|
||||
console.log('⚠️ Session tidak valid, logout...');
|
||||
authStore.setUser(null);
|
||||
|
||||
// Clear cookie manual (backup)
|
||||
document.cookie = `${process.env.NEXT_PUBLIC_SESSION_KEY}=; Max-Age=0; path=/;`;
|
||||
|
||||
toast.info('Role Anda telah diubah. Silakan login kembali.', {
|
||||
autoClose: 5000,
|
||||
});
|
||||
|
||||
router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Cek perubahan roleId (seharusnya tidak sampai sini jika session dihapus)
|
||||
const currentRoleId = Number(data.user.roleId);
|
||||
|
||||
if (currentRoleId !== user.roleId) {
|
||||
console.log('🔄 Role berubah! Dari', user.roleId, 'ke', currentRoleId);
|
||||
|
||||
// Update store
|
||||
authStore.setUser({
|
||||
id: data.user.id,
|
||||
name: data.user.name || data.user.username,
|
||||
roleId: currentRoleId,
|
||||
});
|
||||
|
||||
// Redirect ke halaman default role baru
|
||||
let redirectPath = '/admin';
|
||||
|
||||
switch (currentRoleId) {
|
||||
case 0:
|
||||
case 1:
|
||||
redirectPath = '/admin/landing-page/profil/program-inovasi';
|
||||
break;
|
||||
case 2:
|
||||
redirectPath = '/admin/kesehatan/posyandu';
|
||||
break;
|
||||
case 3:
|
||||
redirectPath = '/admin/pendidikan/info-sekolah/jenjang-pendidikan';
|
||||
break;
|
||||
}
|
||||
|
||||
toast.info('Role Anda telah diubah. Mengalihkan halaman...', {
|
||||
autoClose: 5000,
|
||||
});
|
||||
|
||||
router.push(redirectPath);
|
||||
|
||||
// Reload untuk clear semua state
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error checking role update:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Polling setiap 10 detik (lebih responsif dari 30 detik)
|
||||
const interval = setInterval(checkRoleUpdate, 10000);
|
||||
|
||||
// Juga cek saat window focus (user kembali ke tab)
|
||||
const handleFocus = () => {
|
||||
checkRoleUpdate();
|
||||
};
|
||||
|
||||
window.addEventListener('focus', handleFocus);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
window.removeEventListener('focus', handleFocus);
|
||||
};
|
||||
}, [user, router]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<AppShell>
|
||||
<AppShellMain>
|
||||
<Center h="100vh">
|
||||
<Loader />
|
||||
</Center>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ Ambil menu berdasarkan roleId
|
||||
const currentNav = user?.roleId !== undefined
|
||||
? navigationByRole[user.roleId as keyof typeof navigationByRole] || []
|
||||
: [];
|
||||
? (navigationByRole[user.roleId as keyof typeof navigationByRole] || [])
|
||||
: [];
|
||||
|
||||
const handleLogout = () => {
|
||||
authStore.setUser(null);
|
||||
document.cookie = `${process.env.BASE_SESSION_KEY}=; Max-Age=0; path=/;`;
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
@@ -388,13 +529,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
variant="gradient"
|
||||
gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||
>
|
||||
<Image
|
||||
src="/assets/images/darmasaba-icon.png"
|
||||
alt="Logo Darmasaba"
|
||||
w={20}
|
||||
h={20}
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
<Image
|
||||
src="/assets/images/darmasaba-icon.png"
|
||||
alt="Logo Darmasaba"
|
||||
w={20}
|
||||
h={20}
|
||||
radius="md"
|
||||
loading="lazy"
|
||||
style={{
|
||||
minWidth: '20px',
|
||||
height: 'auto',
|
||||
@@ -404,9 +545,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
</Tooltip>
|
||||
<Tooltip label="Keluar" position="bottom" withArrow>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
router.push("/darmasaba");
|
||||
}}
|
||||
onClick={handleLogout}
|
||||
color={colors["blue-button"]}
|
||||
radius="xl"
|
||||
size="lg"
|
||||
|
||||
Reference in New Issue
Block a user