feat: fixing sidebar and layout
This commit is contained in:
parent
e61d5f6c15
commit
5e00c9cceb
|
|
@ -55,18 +55,6 @@ export default function Login() {
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
error("Username & Password Wajib Diisi !");
|
error("Username & Password Wajib Diisi !");
|
||||||
} else {
|
} else {
|
||||||
// let response = await emailValidation(data);
|
|
||||||
// if (response?.error) {
|
|
||||||
// error("Username / Password Tidak Sesuai");
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (response?.data?.messages[0] === "Continue to setup email") {
|
|
||||||
// setFirstLogin(true);
|
|
||||||
// } else {
|
|
||||||
// setNeedOtp(true);
|
|
||||||
// }
|
|
||||||
|
|
||||||
loading();
|
loading();
|
||||||
const response = await postSignIn(data);
|
const response = await postSignIn(data);
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
|
|
@ -139,7 +127,6 @@ export default function Login() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkUsername = async () => {
|
const checkUsername = async () => {
|
||||||
|
|
@ -173,90 +160,6 @@ export default function Login() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// const submitOtp = async () => {
|
|
||||||
// loading();
|
|
||||||
// const validation = await otpValidationLogin({
|
|
||||||
// username: username,
|
|
||||||
// otpCode: otpValue,
|
|
||||||
// });
|
|
||||||
// if (validation?.error) {
|
|
||||||
// error("OTP Tidak Sesuai");
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const response = await postSignIn({
|
|
||||||
// username: username,
|
|
||||||
// password: password,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const resProfile = await getProfile(response?.data?.data?.access_token);
|
|
||||||
// const profile = resProfile?.data?.data;
|
|
||||||
|
|
||||||
// const dateTime: any = new Date();
|
|
||||||
|
|
||||||
// const newTime: any = dateTime.getTime() + 10 * 60 * 1000;
|
|
||||||
|
|
||||||
// Cookies.set("access_token", response?.data?.data?.access_token, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("refresh_token", response?.data?.data?.refresh_token, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("time_refresh", newTime, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("is_first_login", "true", {
|
|
||||||
// secure: true,
|
|
||||||
// sameSite: "strict",
|
|
||||||
// });
|
|
||||||
// const resActivity = await saveActivity(
|
|
||||||
// {
|
|
||||||
// activityTypeId: 1,
|
|
||||||
// url: "https://kontenhumas.com/auth",
|
|
||||||
// userId: profile?.data?.data?.id,
|
|
||||||
// },
|
|
||||||
// accessData?.id_token
|
|
||||||
// );
|
|
||||||
// Cookies.set("profile_picture", profile?.profilePictureUrl, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("uie", profile?.id, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("ufne", profile?.fullname, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("ulie", profile?.userLevelGroup, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("username", profile?.username, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("urie", profile?.roleId, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("roleName", profile?.roleName, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("masterPoldaId", profile?.masterPoldaId, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("ulne", profile?.userLevelId, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("urce", profile?.roleCode, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// Cookies.set("email", profile?.email, {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// router.push("/admin/dashboard");
|
|
||||||
// Cookies.set("status", "login", {
|
|
||||||
// expires: 1,
|
|
||||||
// });
|
|
||||||
// close();
|
|
||||||
// };
|
|
||||||
|
|
||||||
const submitCheckEmail = async () => {
|
const submitCheckEmail = async () => {
|
||||||
const req = {
|
const req = {
|
||||||
oldEmail: oldEmail,
|
oldEmail: oldEmail,
|
||||||
|
|
@ -281,190 +184,264 @@ export default function Login() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row h-full">
|
<div className="min-h-screen flex">
|
||||||
<div className="hidden md:flex w-full md:w-3/5 items-center justify-center bg-white p-6">
|
{/* Left Side - Logo Section */}
|
||||||
<Link href={"/"}>
|
<div className="hidden lg:flex lg:w-1/2 bg-gradient-to-br from-emerald-600 via-emerald-700 to-emerald-800 relative overflow-hidden">
|
||||||
<img src="/mikul.png" alt="logo" className="max-w-full h-auto" />
|
<div className="absolute inset-0 bg-black/20"></div>
|
||||||
</Link>
|
<div className="relative z-10 flex items-center justify-center w-full p-12">
|
||||||
|
<div className="text-center">
|
||||||
|
<Link href={"/"}>
|
||||||
|
<div className="bg-white/10 backdrop-blur-sm rounded-2xl p-8 shadow-2xl border border-white/20">
|
||||||
|
<img
|
||||||
|
src="/mikul.png"
|
||||||
|
alt="Mikul News Logo"
|
||||||
|
className="max-w-xs h-auto drop-shadow-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div className="mt-8 text-white/90">
|
||||||
|
<h2 className="text-2xl font-bold mb-2">Portal Mikul News</h2>
|
||||||
|
<p className="text-sm opacity-80">Platform berita terpercaya untuk informasi terkini</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Decorative elements */}
|
||||||
|
<div className="absolute top-10 left-10 w-20 h-20 bg-white/10 rounded-full blur-xl"></div>
|
||||||
|
<div className="absolute bottom-20 right-20 w-32 h-32 bg-white/5 rounded-full blur-2xl"></div>
|
||||||
</div>
|
</div>
|
||||||
{isFirstLogin ? (
|
|
||||||
<div className="bg-black w-full md:w-2/5 p-8 md:px-24 justify-center flex flex-col">
|
|
||||||
<p className="text-[72px] text-[#ce3b28] font-semibold mb-10">
|
|
||||||
Setting Account
|
|
||||||
</p>
|
|
||||||
{/* <p className="my-2 text-white">Email Lama</p> */}
|
|
||||||
{/* <Input isRequired type="email" label="" placeholder="" className="my-2" classNames={{ input: "rounded-md", inputWrapper: "rounded-md" }} value={oldEmail} onValueChange={setOldEmail} /> */}
|
|
||||||
<div className="space-y-2 my-4">
|
|
||||||
<Label htmlFor="old-email" className="text-sm font-medium">
|
|
||||||
Email Lama
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="old-email"
|
|
||||||
type="email"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan email lama"
|
|
||||||
className="rounded-md"
|
|
||||||
value={oldEmail}
|
|
||||||
onChange={(e) => setOldEmail(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* <p className="my-2 text-white">Email Baru</p> */}
|
|
||||||
{/* <Input isRequired type="email" label="" placeholder="" className="my-2" classNames={{ input: "rounded-md", inputWrapper: "rounded-md" }} value={newEmail} onValueChange={setNewEmail} /> */}
|
|
||||||
<div className="my-2">
|
|
||||||
<Label htmlFor="new-email" className="text-white">
|
|
||||||
Email Baru
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="new-email"
|
|
||||||
type="email"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan email baru"
|
|
||||||
className="text-white mt-1 rounded-md"
|
|
||||||
value={newEmail}
|
|
||||||
onChange={(e) => setNewEmail(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
className="w-fit bg-[#DD8306] rounded-md font-semibold my-3 text-white hover:bg-[#c87505]"
|
|
||||||
onClick={submitCheckEmail}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : needOtp ? (
|
|
||||||
<div className="bg-black w-full md:w-2/5 p-8 md:px-24 justify-center flex flex-col">
|
|
||||||
{/* <p className="text-[72px] text-[#DD8306] font-semibold mb-10">Submit OTP</p>
|
|
||||||
<p className="my-2 text-white">OTP</p>
|
|
||||||
<Input length={6} value={otpValue} onValueChange={setOtpValue} />
|
|
||||||
|
|
||||||
<Button size="lg" className="w-fit bg-[#DD8306] rounded-md font-semibold my-3 text-white" onPress={submitOtp}>
|
{/* Right Side - Login Form */}
|
||||||
Submit
|
<div className="w-full lg:w-1/2 flex items-center justify-center p-8 bg-gray-50">
|
||||||
</Button>
|
<div className="w-full max-w-md">
|
||||||
<div className="flex justify-between md:justify-end my-2 text-white">
|
{/* Mobile Logo */}
|
||||||
<Link href={`/`} className="text-[#DD8306] cursor-pointer md:hidden">
|
<div className="lg:hidden text-center mb-8">
|
||||||
Beranda
|
<Link href={"/"}>
|
||||||
</Link>
|
<img
|
||||||
</div> */}
|
src="/mikul.png"
|
||||||
</div>
|
alt="Mikul News Logo"
|
||||||
) : isResetPassword ? (
|
className="h-12 mx-auto"
|
||||||
<div className="bg-[#1F1A17] w-full md:w-2/5 p-8 md:px-24 justify-center flex flex-col">
|
|
||||||
<p className="text-[72px] text-[#ce3b28] font-semibold mb-10">
|
|
||||||
Reset Password
|
|
||||||
</p>
|
|
||||||
<Label htmlFor="username" className="text-white">
|
|
||||||
Username
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="username"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
placeholder="Masukkan username"
|
|
||||||
className="my-2 rounded-md text-white"
|
|
||||||
value={checkUsernameValue}
|
|
||||||
onChange={(e) => setCheckUsernameValue(e.target.value.trim())}
|
|
||||||
onPaste={(e) => setCheckUsernameValue(e.currentTarget.value.trim())}
|
|
||||||
onCopy={(e) => setCheckUsernameValue(e.currentTarget.value.trim())}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
className="w-full bg-[#DD8306] rounded-md font-semibold my-3 text-white hover:bg-[#c87505]"
|
|
||||||
onClick={checkUsername}
|
|
||||||
disabled={checkUsernameValue === ""}
|
|
||||||
>
|
|
||||||
Check Username
|
|
||||||
</Button>
|
|
||||||
<div className="flex justify-between md:justify-end my-2 text-white">
|
|
||||||
<Link
|
|
||||||
href={`/`}
|
|
||||||
className="text-[#DD8306] cursor-pointer md:hidden"
|
|
||||||
>
|
|
||||||
Beranda
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<a
|
|
||||||
className="text-[#DD8306] cursor-pointer"
|
|
||||||
onClick={() => setIsResetPassword(false)}
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="bg-[#31942E] w-full md:w-2/5 p-8 md:px-24 flex flex-col justify-center min-h-screen">
|
|
||||||
<div className="w-full max-w-md mx-auto">
|
|
||||||
<div className="text-2xl font-bold text-white">
|
|
||||||
Selamat Datang di Portal Mikul News
|
|
||||||
</div>
|
|
||||||
<div className="text-sm font-semibold pb-4 text-white">
|
|
||||||
Silahkan Login untuk Melihat informasi serta untuk mengetahui
|
|
||||||
status permintaan informasi dan keberatan yang sudah diajukan.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Label htmlFor="username" className="my-2 text-white">
|
|
||||||
Username
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="username"
|
|
||||||
required
|
|
||||||
type="text"
|
|
||||||
placeholder="Masukkan username"
|
|
||||||
className="my-2 rounded-md text-white"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setValUsername(e.target.value.trim())}
|
|
||||||
onPaste={(e) => setValUsername(e.currentTarget.value.trim())}
|
|
||||||
onCopy={(e) => setValUsername(e.currentTarget.value.trim())}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Label htmlFor="password" className="my-2 text-white">
|
|
||||||
Password
|
|
||||||
</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
required
|
|
||||||
type={isVisible ? "text" : "password"}
|
|
||||||
placeholder="Masukkan password"
|
|
||||||
className="pr-10 rounded-md text-white"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
<button
|
</Link>
|
||||||
type="button"
|
|
||||||
onClick={toggleVisibility}
|
|
||||||
className="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600 focus:outline-none"
|
|
||||||
>
|
|
||||||
{isVisible ? (
|
|
||||||
<EyeSlashFilledIcon className="w-5 h-5" />
|
|
||||||
) : (
|
|
||||||
<EyeFilledIcon className="w-5 h-5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
className="w-full text-[#31942E] border-2 border-[#000000] bg-white rounded-md font-semibold my-3"
|
|
||||||
onClick={onSubmit}
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div className="flex justify-between md:justify-end my-2 text-white text-sm">
|
|
||||||
<Link href={`/`} className="text-red-500 md:hidden">
|
|
||||||
Beranda
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
className="text-red-500 cursor-pointer"
|
|
||||||
onClick={() => setIsResetPassword(true)}
|
|
||||||
>
|
|
||||||
Reset Password
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isFirstLogin ? (
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg className="w-8 h-8 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">Setup Akun</h2>
|
||||||
|
<p className="text-gray-600">Lengkapi informasi email Anda</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="old-email" className="text-sm font-medium text-gray-700 mb-2 block">
|
||||||
|
Email Lama
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="old-email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
placeholder="Masukkan email lama"
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 transition-colors"
|
||||||
|
value={oldEmail}
|
||||||
|
onChange={(e) => setOldEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="new-email" className="text-sm font-medium text-gray-700 mb-2 block">
|
||||||
|
Email Baru
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="new-email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
placeholder="Masukkan email baru"
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 transition-colors"
|
||||||
|
value={newEmail}
|
||||||
|
onChange={(e) => setNewEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-semibold py-3 rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl"
|
||||||
|
onClick={submitCheckEmail}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : needOtp ? (
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">Verifikasi OTP</h2>
|
||||||
|
<p className="text-gray-600">Masukkan kode OTP yang telah dikirim</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : isResetPassword ? (
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg className="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">Reset Password</h2>
|
||||||
|
<p className="text-gray-600">Masukkan username untuk reset password</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="reset-username" className="text-sm font-medium text-gray-700 mb-2 block">
|
||||||
|
Username
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="reset-username"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
placeholder="Masukkan username"
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-colors"
|
||||||
|
value={checkUsernameValue}
|
||||||
|
onChange={(e) => setCheckUsernameValue(e.target.value.trim())}
|
||||||
|
onPaste={(e) => setCheckUsernameValue(e.currentTarget.value.trim())}
|
||||||
|
onCopy={(e) => setCheckUsernameValue(e.currentTarget.value.trim())}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white font-semibold py-3 rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
onClick={checkUsername}
|
||||||
|
disabled={checkUsernameValue === ""}
|
||||||
|
>
|
||||||
|
Check Username
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center pt-4 border-t border-gray-100">
|
||||||
|
<Link
|
||||||
|
href={`/`}
|
||||||
|
className="text-sm text-gray-600 hover:text-gray-900 transition-colors lg:hidden"
|
||||||
|
>
|
||||||
|
Beranda
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="text-sm text-red-600 hover:text-red-700 font-medium transition-colors"
|
||||||
|
onClick={() => setIsResetPassword(false)}
|
||||||
|
>
|
||||||
|
Kembali ke Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg className="w-8 h-8 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">Selamat Datang</h2>
|
||||||
|
<p className="text-gray-600">Portal Mikul News - Platform berita terpercaya</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="username" className="text-sm font-medium text-gray-700 mb-2 block">
|
||||||
|
Username
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan username"
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 transition-colors"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setValUsername(e.target.value.trim())}
|
||||||
|
onPaste={(e) => setValUsername(e.currentTarget.value.trim())}
|
||||||
|
onCopy={(e) => setValUsername(e.currentTarget.value.trim())}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="password" className="text-sm font-medium text-gray-700 mb-2 block">
|
||||||
|
Password
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
required
|
||||||
|
type={isVisible ? "text" : "password"}
|
||||||
|
placeholder="Masukkan password"
|
||||||
|
className="w-full px-4 py-3 pr-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 transition-colors"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleVisibility}
|
||||||
|
className="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-gray-600 focus:outline-none transition-colors"
|
||||||
|
>
|
||||||
|
{isVisible ? (
|
||||||
|
<EyeSlashFilledIcon className="w-5 h-5" />
|
||||||
|
) : (
|
||||||
|
<EyeFilledIcon className="w-5 h-5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-gradient-to-r from-emerald-500 to-emerald-600 hover:from-emerald-600 hover:to-emerald-700 text-white font-semibold py-3 rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl"
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
Masuk ke Portal
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center pt-4 border-t border-gray-100">
|
||||||
|
<Link
|
||||||
|
href={`/`}
|
||||||
|
className="text-sm text-gray-600 hover:text-gray-900 transition-colors lg:hidden"
|
||||||
|
>
|
||||||
|
Beranda
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="text-sm text-red-600 hover:text-red-700 font-medium transition-colors"
|
||||||
|
onClick={() => setIsResetPassword(true)}
|
||||||
|
>
|
||||||
|
Lupa Password?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 p-4 bg-blue-50 rounded-lg border border-blue-100">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<svg className="w-5 h-5 text-blue-600 mt-0.5 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<div className="text-sm text-blue-800">
|
||||||
|
<p className="font-medium mb-1">Informasi Portal</p>
|
||||||
|
<p className="text-blue-700">Akses informasi terkini dan status permintaan informasi yang telah diajukan.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ const Option = ({ Icon, title, selected, setSelected, open, notifs, active }: Op
|
||||||
onClick={() => setSelected?.(title)}
|
onClick={() => setSelected?.(title)}
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
onMouseLeave={() => setHovered(false)}
|
onMouseLeave={() => setHovered(false)}
|
||||||
className={`relative flex h-12 w-full items-center rounded-xl transition-all duration-200 cursor-pointer group ${
|
className={`relative flex h-12 w-full px-3 items-center rounded-xl transition-all duration-200 cursor-pointer group ${
|
||||||
isActive
|
isActive
|
||||||
? "bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-lg shadow-blue-500/25"
|
? "bg-gradient-to-r from-emerald-500 to-green-500 text-white shadow-lg shadow-emerald-500/25"
|
||||||
: "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800"
|
: "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800"
|
||||||
}`}
|
}`}
|
||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
|
|
@ -94,7 +94,7 @@ const Option = ({ Icon, title, selected, setSelected, open, notifs, active }: Op
|
||||||
transition={{ delay: 0.3, type: "spring" }}
|
transition={{ delay: 0.3, type: "spring" }}
|
||||||
className={`absolute right-3 top-1/2 -translate-y-1/2 size-5 rounded-full text-xs font-semibold flex items-center justify-center ${
|
className={`absolute right-3 top-1/2 -translate-y-1/2 size-5 rounded-full text-xs font-semibold flex items-center justify-center ${
|
||||||
isActive
|
isActive
|
||||||
? "bg-white text-blue-500"
|
? "bg-white text-emerald-500"
|
||||||
: "bg-red-500 text-white"
|
: "bg-red-500 text-white"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import DashboardContainer from "../main/dashboard/dashboard-container";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import Option from "./option";
|
import Option from "./option";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { useTheme } from "../layout/theme-context";
|
||||||
|
|
||||||
interface RetractingSidebarProps {
|
interface RetractingSidebarProps {
|
||||||
sidebarData: boolean;
|
sidebarData: boolean;
|
||||||
|
|
@ -97,7 +98,7 @@ export const RetractingSidebar = ({
|
||||||
<motion.nav
|
<motion.nav
|
||||||
key="desktop-sidebar"
|
key="desktop-sidebar"
|
||||||
layout
|
layout
|
||||||
className="hidden md:flex sticky top-0 h-screen shrink-0 bg-gradient-to-b from-slate-50 to-white border-r border-slate-200/60 shadow-lg backdrop-blur-sm flex-col justify-between"
|
className="hidden md:flex sticky top-0 h-screen shrink-0 bg-gradient-to-b from-slate-50 to-white dark:from-slate-800 dark:to-slate-900 border-r border-slate-200/60 dark:border-slate-700/60 shadow-lg backdrop-blur-sm flex-col justify-between"
|
||||||
style={{
|
style={{
|
||||||
width: sidebarData ? "280px" : "80px",
|
width: sidebarData ? "280px" : "80px",
|
||||||
}}
|
}}
|
||||||
|
|
@ -138,7 +139,7 @@ export const RetractingSidebar = ({
|
||||||
initial={{ opacity: 0, scale: 0 }}
|
initial={{ opacity: 0, scale: 0 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
exit={{ opacity: 0, scale: 0 }}
|
exit={{ opacity: 0, scale: 0 }}
|
||||||
className="md:hidden fixed top-4 left-4 z-50 p-3 bg-white rounded-xl shadow-lg border border-slate-200/60 hover:shadow-xl transition-all duration-200"
|
className="md:hidden fixed top-4 left-4 z-50 p-3 bg-white dark:bg-slate-800 rounded-xl shadow-lg border border-slate-200/60 dark:border-slate-700/60 hover:shadow-xl transition-all duration-200"
|
||||||
onClick={() => updateSidebarData(true)}
|
onClick={() => updateSidebarData(true)}
|
||||||
>
|
>
|
||||||
<Icon icon="heroicons:chevron-right" className="w-6 h-6 text-slate-600" />
|
<Icon icon="heroicons:chevron-right" className="w-6 h-6 text-slate-600" />
|
||||||
|
|
@ -155,7 +156,7 @@ export const RetractingSidebar = ({
|
||||||
animate={{ x: 0 }}
|
animate={{ x: 0 }}
|
||||||
exit={{ x: "-100%" }}
|
exit={{ x: "-100%" }}
|
||||||
transition={{ type: "tween", duration: 0.3 }}
|
transition={{ type: "tween", duration: 0.3 }}
|
||||||
className="fixed top-0 left-0 z-50 w-[280px] h-full bg-gradient-to-b from-slate-50 to-white p-4 flex flex-col md:hidden shadow-2xl backdrop-blur-sm"
|
className="fixed top-0 left-0 z-50 w-[280px] h-full bg-gradient-to-b from-slate-50 to-white dark:from-slate-800 dark:to-slate-900 p-4 flex flex-col md:hidden shadow-2xl backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
{/* <button onClick={() => updateSidebarData(false)} className="mb-4 self-end text-zinc-500">
|
{/* <button onClick={() => updateSidebarData(false)} className="mb-4 self-end text-zinc-500">
|
||||||
✕
|
✕
|
||||||
|
|
@ -181,95 +182,141 @@ const SidebarContent = ({
|
||||||
pathname: string;
|
pathname: string;
|
||||||
updateSidebarData: (newData: boolean) => void;
|
updateSidebarData: (newData: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { theme, toggleTheme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col h-full">
|
||||||
{/* HEADER SECTION */}
|
{/* SCROLLABLE TOP SECTION */}
|
||||||
<div className="flex flex-col space-y-6">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{/* Logo and Toggle */}
|
{/* HEADER SECTION */}
|
||||||
<div className="flex items-center justify-between px-4 py-6">
|
<div className="flex flex-col space-y-6">
|
||||||
<Link href="/" className="flex items-center space-x-3">
|
{/* Logo and Toggle */}
|
||||||
<div className="relative">
|
<div className="flex items-center justify-between px-4 py-6">
|
||||||
<img src="/mikul.png" className="w-10 h-10 rounded-lg shadow-sm" />
|
<Link href="/" className="flex items-center space-x-3">
|
||||||
<div className="absolute -inset-1 bg-gradient-to-r from-blue-500 to-purple-500 rounded-lg opacity-20 blur-sm"></div>
|
<div className="relative">
|
||||||
</div>
|
<img src="/mikul.png" className="w-10 h-10 rounded-lg shadow-sm" />
|
||||||
{open && (
|
<div className="absolute -inset-1 bg-gradient-to-r from-blue-500 to-purple-500 rounded-lg opacity-20 blur-sm"></div>
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, x: -10 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: 0.1 }}
|
|
||||||
className="flex flex-col"
|
|
||||||
>
|
|
||||||
<span className="text-lg font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-transparent">
|
|
||||||
Mikul News
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-slate-500">Admin Panel</span>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{open && (
|
|
||||||
<motion.button
|
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
|
||||||
transition={{ delay: 0.2 }}
|
|
||||||
className="p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200 group"
|
|
||||||
onClick={() => updateSidebarData(false)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon="heroicons:chevron-left"
|
|
||||||
className="w-5 h-5 text-slate-500 group-hover:text-slate-700 transition-colors"
|
|
||||||
/>
|
|
||||||
</motion.button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Navigation Sections */}
|
|
||||||
<div className="flex-1 space-y-6 px-3">
|
|
||||||
{sidebarSections.map((section, sectionIndex) => (
|
|
||||||
<motion.div
|
|
||||||
key={section.title}
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.1 + sectionIndex * 0.1 }}
|
|
||||||
className="space-y-3"
|
|
||||||
>
|
|
||||||
{open && (
|
|
||||||
<motion.h3
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.2 + sectionIndex * 0.1 }}
|
|
||||||
className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3"
|
|
||||||
>
|
|
||||||
{section.title}
|
|
||||||
</motion.h3>
|
|
||||||
)}
|
|
||||||
<div className="space-y-1">
|
|
||||||
{section.items.map((item, itemIndex) => (
|
|
||||||
<Link href={item.link} key={item.title}>
|
|
||||||
<Option
|
|
||||||
Icon={item.icon}
|
|
||||||
title={item.title}
|
|
||||||
active={pathname === item.link}
|
|
||||||
open={open}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
{open && (
|
||||||
))}
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: 0.1 }}
|
||||||
|
className="flex flex-col"
|
||||||
|
>
|
||||||
|
<span className="text-lg font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-transparent">
|
||||||
|
Mikul News
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-slate-500">Admin Panel</span>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<motion.button
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ delay: 0.2 }}
|
||||||
|
className="p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200 group"
|
||||||
|
onClick={() => updateSidebarData(false)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="heroicons:chevron-left"
|
||||||
|
className="w-5 h-5 text-slate-500 group-hover:text-slate-700 transition-colors"
|
||||||
|
/>
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation Sections */}
|
||||||
|
<div className="space-y-3 px-3 pb-6">
|
||||||
|
{sidebarSections.map((section, sectionIndex) => (
|
||||||
|
<motion.div
|
||||||
|
key={section.title}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.1 + sectionIndex * 0.1 }}
|
||||||
|
className="space-y-3"
|
||||||
|
>
|
||||||
|
{open && (
|
||||||
|
<motion.h3
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.2 + sectionIndex * 0.1 }}
|
||||||
|
className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3"
|
||||||
|
>
|
||||||
|
{section.title}
|
||||||
|
</motion.h3>
|
||||||
|
)}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{section.items.map((item, itemIndex) => (
|
||||||
|
<Link href={item.link} key={item.title}>
|
||||||
|
<Option
|
||||||
|
Icon={item.icon}
|
||||||
|
title={item.title}
|
||||||
|
active={pathname === item.link}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* FOOTER SECTION */}
|
{/* FIXED BOTTOM SECTION */}
|
||||||
<div className="p-4 space-y-1">
|
<div className="flex-shrink-0 space-y-1 border-t border-slate-200/60 dark:border-slate-700/60 bg-white/50 dark:bg-slate-800/50 backdrop-blur-sm">
|
||||||
|
{/* Divider */}
|
||||||
|
{/* <div className="px-3 pb-2">
|
||||||
|
<div className="h-px bg-gradient-to-r from-transparent via-slate-300 to-transparent"></div>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
{/* Theme Toggle */}
|
{/* Theme Toggle */}
|
||||||
<div className="px-3">
|
<div className="px-3 pt-1">
|
||||||
<Option
|
<motion.button
|
||||||
Icon={() => <Icon icon="solar:moon-bold" className="text-lg" />}
|
onClick={toggleTheme}
|
||||||
title="Theme"
|
className={`relative flex h-12 w-full items-center rounded-xl transition-all duration-200 cursor-pointer group ${
|
||||||
active={false}
|
open ? 'px-3' : 'justify-center'
|
||||||
open={open}
|
} ${
|
||||||
/>
|
theme === 'dark'
|
||||||
|
? "bg-gradient-to-r from-emerald-500 to-green-500 text-white shadow-lg shadow-emerald-500/25"
|
||||||
|
: "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800 dark:text-slate-300 dark:hover:bg-slate-700/50"
|
||||||
|
}`}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className={`h-full flex items-center justify-center ${
|
||||||
|
open ? "w-12" : "w-full"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={`text-lg transition-all duration-200 ${
|
||||||
|
theme === 'dark'
|
||||||
|
? "text-white"
|
||||||
|
: "text-slate-500 group-hover:text-slate-700 dark:text-slate-400 dark:group-hover:text-slate-200"
|
||||||
|
}`}>
|
||||||
|
{theme === 'dark' ? (
|
||||||
|
<Icon icon="solar:sun-bold" className="text-lg" />
|
||||||
|
) : (
|
||||||
|
<Icon icon="solar:moon-bold" className="text-lg" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ delay: 0.1, duration: 0.2 }}
|
||||||
|
className={`text-sm font-medium transition-colors duration-200 ${
|
||||||
|
theme === 'dark' ? "text-white" : "text-slate-700 dark:text-slate-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Settings */}
|
{/* Settings */}
|
||||||
|
|
@ -291,9 +338,9 @@ const SidebarContent = ({
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.4 }}
|
transition={{ delay: 0.4 }}
|
||||||
className="px-3 pt-4 border-t border-slate-200/60"
|
className="px-3 py-3 border-t border-slate-200/60"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3 p-3 rounded-xl bg-gradient-to-r from-slate-50 to-slate-100/50 hover:from-slate-100 hover:to-slate-200/50 transition-all duration-200 cursor-pointer group">
|
<div className={`${open ? 'flex items-center space-x-3' : 'flex items-center justify-center'} p-3 rounded-xl bg-gradient-to-r from-slate-50 to-slate-100/50 hover:from-slate-100 hover:to-slate-200/50 transition-all duration-200 cursor-pointer group`}>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white font-semibold text-sm shadow-lg">
|
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white font-semibold text-sm shadow-lg">
|
||||||
A
|
A
|
||||||
|
|
@ -319,7 +366,7 @@ const SidebarContent = ({
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Expand Button for Collapsed State */}
|
{/* Expand Button for Collapsed State */}
|
||||||
{!open && (
|
{/* {!open && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
|
@ -338,9 +385,9 @@ const SidebarContent = ({
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { SidebarProvider } from "./sidebar-context";
|
import { SidebarProvider } from "./sidebar-context";
|
||||||
|
import { ThemeProvider } from "./theme-context";
|
||||||
import { Breadcrumbs } from "./breadcrumbs";
|
import { Breadcrumbs } from "./breadcrumbs";
|
||||||
import { BurgerButtonIcon } from "../icons";
|
import { BurgerButtonIcon } from "../icons";
|
||||||
import { RetractingSidebar } from "../landing-page/retracting-sidedar";
|
import { RetractingSidebar } from "../landing-page/retracting-sidedar";
|
||||||
|
|
@ -31,103 +32,59 @@ export const AdminLayout = ({ children }: { children: ReactNode }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<ThemeProvider>
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50">
|
<SidebarProvider>
|
||||||
<div className="flex h-screen overflow-hidden">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
||||||
<RetractingSidebar
|
<div className="flex h-screen overflow-hidden">
|
||||||
sidebarData={isOpen}
|
<RetractingSidebar
|
||||||
updateSidebarData={updateSidebarData}
|
sidebarData={isOpen}
|
||||||
/>
|
updateSidebarData={updateSidebarData}
|
||||||
|
/>
|
||||||
|
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
key="main-content"
|
key="main-content"
|
||||||
className="flex-1 flex flex-col overflow-hidden"
|
className="flex-1 flex flex-col overflow-hidden"
|
||||||
initial={{ opacity: 0, x: 20 }}
|
initial={{ opacity: 0, x: 20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<motion.header
|
|
||||||
className="bg-white/80 backdrop-blur-sm border-b border-slate-200/60 shadow-sm"
|
|
||||||
initial={{ y: -20, opacity: 0 }}
|
|
||||||
animate={{ y: 0, opacity: 1 }}
|
|
||||||
transition={{ delay: 0.2, duration: 0.3 }}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
{/* Header */}
|
||||||
<div className="flex items-center space-x-4">
|
<motion.header
|
||||||
<button
|
className="bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm border-b border-slate-200/60 dark:border-slate-700/60 shadow-sm"
|
||||||
className="md:hidden p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200"
|
initial={{ y: -20, opacity: 0 }}
|
||||||
onClick={() => updateSidebarData(true)}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
>
|
transition={{ delay: 0.2, duration: 0.3 }}
|
||||||
<BurgerButtonIcon />
|
>
|
||||||
</button>
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Breadcrumbs />
|
<div className="flex items-center space-x-4">
|
||||||
|
<button
|
||||||
|
className="md:hidden p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200"
|
||||||
|
onClick={() => updateSidebarData(true)}
|
||||||
|
>
|
||||||
|
<BurgerButtonIcon />
|
||||||
|
</button>
|
||||||
|
<Breadcrumbs />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</motion.header>
|
||||||
|
|
||||||
{/* Header Actions */}
|
{/* Main Content */}
|
||||||
<div className="flex items-center space-x-3">
|
<motion.main
|
||||||
{/* Notifications */}
|
className="flex-1 overflow-auto"
|
||||||
<motion.button
|
initial={{ opacity: 0, y: 20 }}
|
||||||
whileHover={{ scale: 1.05 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
transition={{ delay: 0.3, duration: 0.3 }}
|
||||||
className="p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200 relative"
|
>
|
||||||
>
|
<div className="h-full">
|
||||||
<div className="w-5 h-5 text-slate-600">
|
{children}
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-5 5v-5z" />
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full"></div>
|
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
{/* Search */}
|
|
||||||
<motion.button
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
className="p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200"
|
|
||||||
>
|
|
||||||
<div className="w-5 h-5 text-slate-600">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</motion.button>
|
|
||||||
|
|
||||||
{/* Profile */}
|
|
||||||
<motion.div
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
className="flex items-center space-x-3 p-2 rounded-lg hover:bg-slate-100 transition-colors duration-200 cursor-pointer"
|
|
||||||
>
|
|
||||||
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white text-sm font-semibold">
|
|
||||||
A
|
|
||||||
</div>
|
|
||||||
<div className="hidden md:block">
|
|
||||||
<p className="text-sm font-medium text-slate-800">Admin</p>
|
|
||||||
<p className="text-xs text-slate-500">admin@mikul.com</p>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.main>
|
||||||
</motion.header>
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
{/* Main Content */}
|
</div>
|
||||||
<motion.main
|
|
||||||
className="flex-1 overflow-auto"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.3, duration: 0.3 }}
|
|
||||||
>
|
|
||||||
<div className="h-full">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</motion.main>
|
|
||||||
</motion.div>
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SidebarProvider>
|
||||||
</SidebarProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark';
|
||||||
|
|
||||||
|
interface ThemeContextType {
|
||||||
|
theme: Theme;
|
||||||
|
toggleTheme: () => void;
|
||||||
|
setTheme: (theme: Theme) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [theme, setThemeState] = useState<Theme>('light');
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
// Get theme from localStorage or default to 'light'
|
||||||
|
const savedTheme = localStorage.getItem('theme') as Theme;
|
||||||
|
if (savedTheme) {
|
||||||
|
setThemeState(savedTheme);
|
||||||
|
} else {
|
||||||
|
// Check system preference
|
||||||
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
setThemeState(systemTheme);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
// Update document class and localStorage
|
||||||
|
document.documentElement.classList.remove('light', 'dark');
|
||||||
|
document.documentElement.classList.add(theme);
|
||||||
|
localStorage.setItem('theme', theme);
|
||||||
|
}, [theme, mounted]);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
setThemeState(prev => prev === 'light' ? 'dark' : 'light');
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTheme = (newTheme: Theme) => {
|
||||||
|
setThemeState(newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent hydration mismatch
|
||||||
|
if (!mounted) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
const context = useContext(ThemeContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useTheme must be used within a ThemeProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
@ -193,7 +193,7 @@ export default function DashboardContainer() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6">
|
||||||
{/* User Profile Card */}
|
{/* User Profile Card */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="col-span-1 md:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6 hover:shadow-xl transition-all duration-300"
|
className="col-span-1 md:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6 hover:shadow-xl transition-all duration-300"
|
||||||
|
|
@ -318,7 +318,7 @@ export default function DashboardContainer() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-80">
|
<div className="h-80">
|
||||||
<ApexChartColumn
|
<ApexChartColumn
|
||||||
type="monthly"
|
type="monthly"
|
||||||
date={`${new Date().getMonth() + 1} ${new Date().getFullYear()}`}
|
date={`${new Date().getMonth() + 1} ${new Date().getFullYear()}`}
|
||||||
|
|
@ -378,94 +378,6 @@ export default function DashboardContainer() {
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Post Statistics Table */}
|
|
||||||
<motion.div
|
|
||||||
className="bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.8 }}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<h3 className="text-lg font-semibold text-slate-800">
|
|
||||||
Post Statistics by Region
|
|
||||||
</h3>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline" className="text-sm">
|
|
||||||
{convertDateFormatNoTime(postContentDate.startDate)}
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0 bg-white border border-slate-200 shadow-xl rounded-xl">
|
|
||||||
<DatePicker
|
|
||||||
selected={postContentDate.startDate}
|
|
||||||
onChange={(date: Date | null) => {
|
|
||||||
if (date) {
|
|
||||||
setPostContentDate((prev) => ({
|
|
||||||
...prev,
|
|
||||||
startDate: date,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
maxDate={postContentDate.endDate}
|
|
||||||
inline
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<span className="text-slate-400">to</span>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline" className="text-sm">
|
|
||||||
{convertDateFormatNoTime(postContentDate.endDate)}
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0 bg-white border border-slate-200 shadow-xl rounded-xl">
|
|
||||||
<DatePicker
|
|
||||||
selected={postContentDate.endDate}
|
|
||||||
onChange={(date: Date | null) => {
|
|
||||||
if (date) {
|
|
||||||
setPostContentDate((prev) => ({
|
|
||||||
...prev,
|
|
||||||
endDate: date,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
minDate={postContentDate.startDate}
|
|
||||||
inline
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead>
|
|
||||||
<tr className="border-b border-slate-200">
|
|
||||||
<th className="text-left py-3 px-4 font-semibold text-slate-700">No</th>
|
|
||||||
<th className="text-left py-3 px-4 font-semibold text-slate-700">Region</th>
|
|
||||||
<th className="text-right py-3 px-4 font-semibold text-slate-700">Posts</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-slate-100">
|
|
||||||
{postCount?.map((list) => (
|
|
||||||
<tr key={list.userLevelId} className="hover:bg-slate-50 transition-colors duration-200">
|
|
||||||
<td className="py-3 px-4 text-slate-600">{list?.no}</td>
|
|
||||||
<td className="py-3 px-4 font-medium text-slate-800">{list?.userLevelName}</td>
|
|
||||||
<td className={`py-3 px-4 text-right font-semibold ${
|
|
||||||
list?.totalArticle === 0
|
|
||||||
? "text-red-600 bg-red-50 rounded-lg"
|
|
||||||
: "text-slate-800"
|
|
||||||
}`}>
|
|
||||||
{list?.totalArticle}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue