feat : screen for user create and update
feat : unlocked users.
This commit is contained in:
@@ -14,8 +14,8 @@ interface SendOtpPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getStoredMobileNumber(): string {
|
function getStoredMobileNumber(): string {
|
||||||
const mobileNumber = localStorage.getItem('remitter_mobile_no');
|
// const mobileNumber = localStorage.getItem('remitter_mobile_no');
|
||||||
// const mobileNumber = "7890544527";
|
const mobileNumber = "7890544527";
|
||||||
if (!mobileNumber) throw new Error('Mobile number not found.');
|
if (!mobileNumber) throw new Error('Mobile number not found.');
|
||||||
return mobileNumber;
|
return mobileNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
265
src/app/administrator/home/UnlockedUsers.tsx
Normal file
265
src/app/administrator/home/UnlockedUsers.tsx
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
TextInput,
|
||||||
|
Button,
|
||||||
|
Title,
|
||||||
|
Stack,
|
||||||
|
Group,
|
||||||
|
Text,
|
||||||
|
Paper,
|
||||||
|
LoadingOverlay,
|
||||||
|
Divider,
|
||||||
|
Select,
|
||||||
|
Center,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
export default function UserUnlockPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [CIF, setCIF] = useState("");
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [userDetails, setUserDetails] = useState<any>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Redirect if not logged in
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
if (!token) {
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
router.push("/administrator/login");
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
const checkAuth = () => {
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
if (!token) {
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
router.push("/administrator/login");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch user info
|
||||||
|
const handleSearch = async () => {
|
||||||
|
setError(null);
|
||||||
|
setUserDetails(null);
|
||||||
|
|
||||||
|
if (!CIF && !username) {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "Missing Input",
|
||||||
|
message: "Please enter either CIF number or username.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const token = checkAuth();
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = CIF ? CIF : username;
|
||||||
|
const response = await fetch(`/api/auth/admin/user/rights?CIF=${query}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "Admin",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || !data?.customer_no) {
|
||||||
|
setError("User not found or invalid CIF/username.");
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "User Not Found",
|
||||||
|
message: "Invalid CIF or username.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserDetails(data);
|
||||||
|
|
||||||
|
notifications.show({
|
||||||
|
color: data.locked ? "red" : "green",
|
||||||
|
title: "User Found",
|
||||||
|
message: `User ${data.customer_no} is currently ${data.locked ? "Locked 🔒" : "Unlocked 🔓"
|
||||||
|
}`,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "Error",
|
||||||
|
message: "Failed to fetch user details.",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔄 Unlock or lock user
|
||||||
|
const handleStatusChange = async (value: string) => {
|
||||||
|
if (!userDetails?.customer_no) return;
|
||||||
|
const isUnlock = value === "unlocked";
|
||||||
|
|
||||||
|
const token = checkAuth();
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth/admin/user/unlock", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "Admin",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
user: userDetails.customer_no, // can also be username if API supports
|
||||||
|
action: !isUnlock, // false => unlock, true => lock
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setUserDetails({ ...userDetails, locked: !isUnlock });
|
||||||
|
notifications.show({
|
||||||
|
color: "green",
|
||||||
|
title: "Success",
|
||||||
|
message: `User ${userDetails.customer_no} ${isUnlock ? "unlocked" : "locked"
|
||||||
|
} successfully.`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "Failed",
|
||||||
|
message: data.message || "Unable to update lock status.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
title: "Error",
|
||||||
|
message: "Something went wrong while updating user status.",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setCIF("");
|
||||||
|
setUsername("");
|
||||||
|
setUserDetails(null);
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title order={4}>User Unlock Management</Title>
|
||||||
|
|
||||||
|
<Paper p="md" mt="md" radius="md" withBorder>
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} />
|
||||||
|
|
||||||
|
<Group grow>
|
||||||
|
<TextInput
|
||||||
|
label="CIF Number"
|
||||||
|
placeholder="Enter CIF Number"
|
||||||
|
value={CIF}
|
||||||
|
onChange={(e) => {
|
||||||
|
const input = e.currentTarget.value.replace(/\D/g, "");
|
||||||
|
if (input.length <= 11) setCIF(input);
|
||||||
|
setError(null);
|
||||||
|
}}
|
||||||
|
disabled={!!username}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text c="dimmed" ta="center">
|
||||||
|
OR
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Username"
|
||||||
|
placeholder="Enter Username"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => {
|
||||||
|
const sanitized = e.currentTarget.value.replace(/[^a-zA-Z0-9@_]/g, "");
|
||||||
|
if (sanitized.length <= 15) setUsername(sanitized);
|
||||||
|
setError(null);
|
||||||
|
}}
|
||||||
|
disabled={!!CIF}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Center mt="xs">
|
||||||
|
<Text c="red" size="sm" fw={500}>
|
||||||
|
{error}
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Group mt="md">
|
||||||
|
<Button onClick={handleSearch} disabled={loading}>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
<Button variant="light" color="red" onClick={handleReset}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{userDetails && (
|
||||||
|
<Paper withBorder p="md" mt="xl" radius="md">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Divider label="User Details" labelPosition="center" />
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
<strong>Customer No:</strong> {userDetails.customer_no}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
<strong>Registration Status:</strong>{" "}
|
||||||
|
{userDetails.last_login === null ? (
|
||||||
|
<Text span c="red" fw={600}>
|
||||||
|
Not Registered
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Text span c="green" fw={600}>
|
||||||
|
Registered
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Locked Status"
|
||||||
|
value={userDetails.locked ? "locked" : "unlocked"}
|
||||||
|
data={[
|
||||||
|
{ value: "locked", label: "Locked 🔒" },
|
||||||
|
{ value: "unlocked", label: "Unlocked 🔓" },
|
||||||
|
]}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (!value) return;
|
||||||
|
handleStatusChange(value);
|
||||||
|
}}
|
||||||
|
mt="md"
|
||||||
|
disabled={!userDetails.locked} // only allow change if locked
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,10 +15,11 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
Tabs,
|
Tabs,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
|
Center,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { IconGavel, IconUser } from "@tabler/icons-react";
|
import { IconGavel, IconHome, IconUser } from "@tabler/icons-react";
|
||||||
|
|
||||||
export default function UserConfiguration() {
|
export default function UserConfiguration() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -32,11 +33,15 @@ export default function UserConfiguration() {
|
|||||||
const [ibAccess, setIbAccess] = useState<"2" | "1" | null>(null);
|
const [ibAccess, setIbAccess] = useState<"2" | "1" | null>(null);
|
||||||
const [mbEnabled, setMbEnabled] = useState(false);
|
const [mbEnabled, setMbEnabled] = useState(false);
|
||||||
const [mbAccess, setMbAccess] = useState<"2" | "1" | null>(null);
|
const [mbAccess, setMbAccess] = useState<"2" | "1" | null>(null);
|
||||||
|
const [ibLimit, setIbLimit] = useState("");
|
||||||
|
const [mbLimit, setMbLimit] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const isValidated = !!userDetails;
|
const isValidated = !!userDetails;
|
||||||
const canSubmit =
|
const canSubmit =
|
||||||
isValidated && savingsAccount && userDetails?.mobile && confirmedPreview;
|
isValidated && savingsAccount && userDetails?.mobile && confirmedPreview;
|
||||||
|
|
||||||
|
// 🔹 Fetch both user details and rights
|
||||||
const handleValidate = async () => {
|
const handleValidate = async () => {
|
||||||
if (!CIF) {
|
if (!CIF) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
@@ -55,48 +60,74 @@ export default function UserConfiguration() {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Login-Type": "Admin",
|
"X-Login-Type": "Admin",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
if (!response.ok || !Array.isArray(data) || data.length === 0) {
|
||||||
|
|
||||||
|
setError("User not present or invalid CIF number.");
|
||||||
|
}
|
||||||
|
const saAccount = data.find(
|
||||||
|
(acc: { stAccountType: string; }) => acc.stAccountType === "SA" || acc.stAccountType === "SB"
|
||||||
|
);
|
||||||
|
setSavingsAccount(saAccount?.stAccountNo ?? null);
|
||||||
|
|
||||||
|
const user = data[0];
|
||||||
|
setUserDetails({
|
||||||
|
name: user.custname,
|
||||||
|
userId: user.id,
|
||||||
|
mobile: user.mobileno ?? null,
|
||||||
|
address: user.custaddress,
|
||||||
|
activeAccount: user.activeAccounts,
|
||||||
|
branchNo: user.stBranchNo,
|
||||||
|
dateOfBirth: user.custdob,
|
||||||
|
pinCode: user.pincode,
|
||||||
|
});
|
||||||
|
// Fetch existing rights if any
|
||||||
|
const rightsRes = await fetch(
|
||||||
|
`/api/auth/admin/user/rights?CIF=${CIF}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "Admin",
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = await response.json();
|
if (rightsRes.ok) {
|
||||||
if (response.ok && Array.isArray(data) && data.length > 0) {
|
const rights = await rightsRes.json();
|
||||||
const saAccount = data.find((acc) => acc.stAccountType === "SA");
|
if (rights?.ib_access_level || rights?.mb_access_level) {
|
||||||
setSavingsAccount(saAccount?.stAccountNo ?? null);
|
// populate existing rights
|
||||||
|
if (rights.ib_access_level) {
|
||||||
const user = data[0];
|
setIbEnabled(true);
|
||||||
setUserDetails({
|
setIbAccess(rights.ib_access_level as "1" | "2");
|
||||||
name: user.custname,
|
setIbLimit(rights.inb_limit_amount?.toString() || "");
|
||||||
userId: user.id,
|
}
|
||||||
mobile: user.mobileno ?? null,
|
if (rights.mb_access_level) {
|
||||||
address: user.custaddress,
|
setMbEnabled(true);
|
||||||
activeAccount: user.activeAccounts,
|
setMbAccess(rights.mb_access_level as "1" | "2");
|
||||||
});
|
setMbLimit(rights.mobile_limit_amount?.toString() || "");
|
||||||
} else {
|
}
|
||||||
notifications.show({
|
}
|
||||||
color: "red",
|
|
||||||
title: "Validation Failed",
|
|
||||||
message: "User not found",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
notifications.show({
|
setError("User not present or invalid CIF number.");
|
||||||
color: "red",
|
|
||||||
title: "Validation Failed",
|
|
||||||
message: "User not found",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Preview before submit
|
||||||
const handlePreview = () => {
|
const handlePreview = () => {
|
||||||
if (!savingsAccount || !userDetails?.mobile) {
|
if (!savingsAccount || !userDetails?.mobile) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!ibAccess && !mbAccess) {
|
if (!ibAccess && !mbAccess) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: "Rights Required",
|
title: "Rights Required",
|
||||||
@@ -106,12 +137,20 @@ export default function UserConfiguration() {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((ibEnabled && !ibLimit) || (mbEnabled && !mbLimit)) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Limit Required",
|
||||||
|
message: "Please enter fund transfer limits for enabled services.",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
setShowPreviewModal(true);
|
setShowPreviewModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Submit or update rights
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!canSubmit) return;
|
if (!canSubmit) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("admin_access_token");
|
const token = localStorage.getItem("admin_access_token");
|
||||||
const response = await fetch("/api/auth/admin/user/rights", {
|
const response = await fetch("/api/auth/admin/user/rights", {
|
||||||
@@ -125,52 +164,58 @@ export default function UserConfiguration() {
|
|||||||
CIF,
|
CIF,
|
||||||
ib_access_level: ibAccess,
|
ib_access_level: ibAccess,
|
||||||
mb_access_level: mbAccess,
|
mb_access_level: mbAccess,
|
||||||
|
ib_limit: ibLimit ? parseInt(ibLimit) : 0,
|
||||||
|
mb_limit: mbLimit ? parseInt(mbLimit) : 0,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (data?.otp) {
|
if (data?.otp) {
|
||||||
// Registration case
|
await fetch("/api/otp/send", {
|
||||||
await fetch('/api/otp/send', {
|
method: "POST",
|
||||||
method: 'POST',
|
headers: {
|
||||||
headers: { 'Content-Type': 'application/json',"X-Login-Type": "Admin", },
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "Admin",
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
mobileNumber: userDetails.mobile,
|
mobileNumber: userDetails.mobile,
|
||||||
type: 'REGISTRATION',
|
type: "REGISTRATION",
|
||||||
userOtp: data.otp,
|
userOtp: data.otp,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: 'blue',
|
color: "blue",
|
||||||
title: 'Submitted',
|
title: "Submitted",
|
||||||
message: `User ${CIF} rights submitted successfully. OTP sent to the user.`,
|
message: `User ${CIF} rights submitted successfully. OTP sent to the user.`,
|
||||||
});
|
});
|
||||||
}
|
} else if (data?.message) {
|
||||||
else if (data?.message) {
|
await fetch("/api/otp/send", {
|
||||||
// Rights update case
|
method: "POST",
|
||||||
await fetch('/api/otp/send', {
|
headers: {
|
||||||
method: 'POST',
|
"Content-Type": "application/json",
|
||||||
headers: { 'Content-Type': 'application/json',"X-Login-Type": "Admin", },
|
"X-Login-Type": "Admin",
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
mobileNumber: userDetails.mobile,
|
mobileNumber: userDetails.mobile,
|
||||||
type: 'RIGHT_UPDATE',
|
type: "RIGHT_UPDATE",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: 'blue',
|
color: "blue",
|
||||||
title: 'Submitted',
|
title: "Updated",
|
||||||
message: `User ${CIF} rights updated successfully. Confirmation sent to the user.`,
|
message: `User ${CIF} rights updated successfully. Confirmation sent to the user.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} else if (
|
||||||
else if (response.status === 401 || data.message === "invalid or expired token") {
|
response.status === 401 ||
|
||||||
|
data.message === "invalid or expired token"
|
||||||
|
) {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
router.push("/administrator/login");
|
router.push("/administrator/login");
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
console.error(err);
|
console.error(err);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: "red",
|
color: "red",
|
||||||
@@ -190,20 +235,19 @@ export default function UserConfiguration() {
|
|||||||
setConfirmedPreview(false);
|
setConfirmedPreview(false);
|
||||||
setIbEnabled(false);
|
setIbEnabled(false);
|
||||||
setIbAccess(null);
|
setIbAccess(null);
|
||||||
|
setIbLimit("");
|
||||||
setMbEnabled(false);
|
setMbEnabled(false);
|
||||||
setMbAccess(null);
|
setMbAccess(null);
|
||||||
|
setMbLimit("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMainAction = () => {
|
const handleMainAction = () => {
|
||||||
if (!isValidated) {
|
if (!isValidated) handleValidate();
|
||||||
handleValidate();
|
else if (!confirmedPreview) handlePreview();
|
||||||
} else if (!confirmedPreview) {
|
else handleSubmit();
|
||||||
handlePreview();
|
|
||||||
} else {
|
|
||||||
handleSubmit();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title order={4}>User Configuration</Title>
|
<Title order={4}>User Configuration</Title>
|
||||||
@@ -223,16 +267,26 @@ export default function UserConfiguration() {
|
|||||||
value={CIF}
|
value={CIF}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const input = e.currentTarget.value.replace(/\D/g, "");
|
const input = e.currentTarget.value.replace(/\D/g, "");
|
||||||
if (input.length <= 11) setCIF(input);
|
if (input.length <= 11)
|
||||||
|
setCIF(input);
|
||||||
|
setError(null);
|
||||||
}}
|
}}
|
||||||
disabled={isValidated}
|
disabled={isValidated}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
/>
|
/>
|
||||||
|
{error && (
|
||||||
|
<Center mt="xl">
|
||||||
|
<Text c="red" fw={500} size="md">
|
||||||
|
{error}
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
{isValidated && (
|
{isValidated && (
|
||||||
<Tabs defaultValue="details" mt="md" variant="outline">
|
<Tabs defaultValue="details" mt="md" variant="outline">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab value="details" leftSection={<IconUser size={12} />}>User Details</Tabs.Tab>
|
<Tabs.Tab value="details" leftSection={<IconUser size={12} />}>User Details</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="address" leftSection={<IconHome size={12} />}>Address</Tabs.Tab>
|
||||||
<Tabs.Tab value="rights" leftSection={<IconGavel size={12} />}>Rights</Tabs.Tab>
|
<Tabs.Tab value="rights" leftSection={<IconGavel size={12} />}>Rights</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
||||||
@@ -241,24 +295,32 @@ export default function UserConfiguration() {
|
|||||||
<Paper withBorder p="md" radius="md">
|
<Paper withBorder p="md" radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<Text fw={700} >User ID:</Text>
|
<Text fw={700} >ID:</Text>
|
||||||
<Text c="dimmed">{userDetails?.userId ?? "N/A"}</Text>
|
<Text>{userDetails?.userId ?? "N/A"}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<Text fw={700} >User Name:</Text>
|
<Text fw={700} >Branch:</Text>
|
||||||
|
<Text>{userDetails?.branchNo ?? "N/A"}</Text>
|
||||||
|
</Group>
|
||||||
|
<Group grow>
|
||||||
|
<Text fw={700} >Name:</Text>
|
||||||
<Text>{userDetails?.name ?? "N/A"}</Text>
|
<Text>{userDetails?.name ?? "N/A"}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<Text fw={700} >Mobile Number:</Text>
|
<Text fw={700} >Date of Birth:</Text>
|
||||||
<Text c="dimmed">{userDetails?.mobile ?? "N/A"}</Text>
|
<Text>
|
||||||
</Group>
|
{userDetails?.dateOfBirth
|
||||||
<Group grow>
|
? `${userDetails.dateOfBirth.slice(0, 2)}/${userDetails.dateOfBirth.slice(2, 4)}/${userDetails.dateOfBirth.slice(4)}`
|
||||||
<Text fw={700} >User Address:</Text>
|
: "N/A"}
|
||||||
<Text>{userDetails?.address ?? "N/A"}</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<Text fw={700} >Savings Account:</Text>
|
<Text fw={700} >Savings Account:</Text>
|
||||||
<Text c="blue">{savingsAccount ?? "N/A"}</Text>
|
<Text c="blue">
|
||||||
|
{Array.isArray(savingsAccount)
|
||||||
|
? savingsAccount.join(", ")
|
||||||
|
: savingsAccount ?? "N/A"}
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<Text fw={700}>Active Accounts:</Text>
|
<Text fw={700}>Active Accounts:</Text>
|
||||||
@@ -269,9 +331,31 @@ export default function UserConfiguration() {
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel value="address" pt="xs">
|
||||||
|
<ScrollArea h={250} type="auto" offsetScrollbars>
|
||||||
|
<Paper withBorder p="md" radius="md">
|
||||||
|
<Stack>
|
||||||
|
<Group grow>
|
||||||
|
<Text fw={700} >Address:</Text>
|
||||||
|
<Text>{userDetails?.address ?? "N/A"}</Text>
|
||||||
|
</Group>
|
||||||
|
<Group grow>
|
||||||
|
<Text fw={700} >Pincode:</Text>
|
||||||
|
<Text>{userDetails?.pinCode ?? "N/A"}</Text>
|
||||||
|
</Group>
|
||||||
|
<Group grow>
|
||||||
|
<Text fw={700} >Mobile Number:</Text>
|
||||||
|
<Text>{userDetails?.mobile ?? "N/A"}</Text>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</ScrollArea>
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="rights" pt="xs">
|
<Tabs.Panel value="rights" pt="xs">
|
||||||
<Stack gap="sm">
|
<Stack gap="md">
|
||||||
<Group gap="xl" align="center">
|
{/* Internet Banking Section */}
|
||||||
|
<Group>
|
||||||
<Box w={150}>
|
<Box w={150}>
|
||||||
<Text fw={700}>Internet Banking</Text>
|
<Text fw={700}>Internet Banking</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -281,7 +365,10 @@ export default function UserConfiguration() {
|
|||||||
checked={ibEnabled}
|
checked={ibEnabled}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setIbEnabled(e.currentTarget.checked);
|
setIbEnabled(e.currentTarget.checked);
|
||||||
if (!e.currentTarget.checked) setIbAccess(null);
|
if (!e.currentTarget.checked) {
|
||||||
|
setIbAccess(null);
|
||||||
|
setIbLimit("");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -302,8 +389,25 @@ export default function UserConfiguration() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
<Group align="center" gap="sm">
|
||||||
|
<Text fw={700}>
|
||||||
|
Fund Transfer Limit for Internet Banking
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Enter Amount"
|
||||||
|
value={ibLimit}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.currentTarget.value;
|
||||||
|
if (val === "" || (/^\d{0,7}$/.test(val))) {
|
||||||
|
setIbLimit(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Group gap="xl" align="center">
|
<Divider my="sm" labelPosition="center" />
|
||||||
|
|
||||||
|
<Group >
|
||||||
<Box w={150}>
|
<Box w={150}>
|
||||||
<Text fw={700}>Mobile Banking</Text>
|
<Text fw={700}>Mobile Banking</Text>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -313,7 +417,10 @@ export default function UserConfiguration() {
|
|||||||
checked={mbEnabled}
|
checked={mbEnabled}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMbEnabled(e.currentTarget.checked);
|
setMbEnabled(e.currentTarget.checked);
|
||||||
if (!e.currentTarget.checked) setMbAccess(null);
|
if (!e.currentTarget.checked) {
|
||||||
|
setMbAccess(null);
|
||||||
|
setMbLimit("");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -334,6 +441,21 @@ export default function UserConfiguration() {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
<Group align="center" gap="sm">
|
||||||
|
<Text fw={700}>
|
||||||
|
Fund Transfer Limit for Mobile Banking
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Enter Amount"
|
||||||
|
value={mbLimit}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.currentTarget.value;
|
||||||
|
if (val === "" || (/^\d{0,7}$/.test(val))) {
|
||||||
|
setMbLimit(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -355,9 +477,12 @@ export default function UserConfiguration() {
|
|||||||
<Text>
|
<Text>
|
||||||
<strong>Mobile Number:</strong> {userDetails?.mobile ?? "N/A"}
|
<strong>Mobile Number:</strong> {userDetails?.mobile ?? "N/A"}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Divider label="Rights" size="xs" />
|
<Divider label="Rights" size="xs" />
|
||||||
|
|
||||||
|
{/* Internet Banking Section */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Internet Banking"
|
label="Internet Banking Access"
|
||||||
value={
|
value={
|
||||||
ibAccess === "1"
|
ibAccess === "1"
|
||||||
? "Transaction"
|
? "Transaction"
|
||||||
@@ -367,8 +492,16 @@ export default function UserConfiguration() {
|
|||||||
}
|
}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Mobile Banking"
|
label="Internet Banking Limit"
|
||||||
|
value={ibLimit || "N/A"}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Mobile Banking Section */}
|
||||||
|
<TextInput
|
||||||
|
label="Mobile Banking Access"
|
||||||
value={
|
value={
|
||||||
mbAccess === "1"
|
mbAccess === "1"
|
||||||
? "Transaction"
|
? "Transaction"
|
||||||
@@ -378,7 +511,14 @@ export default function UserConfiguration() {
|
|||||||
}
|
}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Mobile Banking Limit"
|
||||||
|
value={mbLimit || "N/A"}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Group justify="right" mt="md">
|
<Group justify="right" mt="md">
|
||||||
<Button variant="default" onClick={() => setShowPreviewModal(false)}>
|
<Button variant="default" onClick={() => setShowPreviewModal(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -1,176 +1,200 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
TextInput,
|
TextInput,
|
||||||
Button,
|
Button,
|
||||||
Title,
|
Title,
|
||||||
Stack,
|
Stack,
|
||||||
Group,
|
Group,
|
||||||
Text,
|
Text,
|
||||||
Divider,
|
Divider,
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
Box,
|
Box,
|
||||||
Paper,
|
Paper,
|
||||||
|
Center,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
interface UserRights {
|
interface UserRights {
|
||||||
customer_no: string;
|
customer_no: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
last_login: Date;
|
last_login: Date;
|
||||||
is_first_login: boolean;
|
is_first_login: boolean;
|
||||||
ibAccess: "1" | "2" | "0" | null;
|
ibAccess: "1" | "2" | "0" | null;
|
||||||
mbAccess: "1" | "2" | "0" | null;
|
mbAccess: "1" | "2" | "0" | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ViewUserRights() {
|
export default function ViewUserRights() {
|
||||||
const [CIFNo, setCIFNo] = useState("");
|
const [CIFNo, setCIFNo] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [userData, setUserData] = useState<UserRights | null>(null);
|
const [userData, setUserData] = useState<UserRights | null>(null);
|
||||||
const [detailsExpanded, setDetailsExpanded] = useState(true);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
if (!CIFNo) {
|
setError(null);
|
||||||
notifications.show({
|
if (!CIFNo) {
|
||||||
color: "red",
|
notifications.show({
|
||||||
title: "Required",
|
color: "red",
|
||||||
message: "Please enter CIF Number.",
|
title: "CIF Missing",
|
||||||
});
|
message: "Please enter a CIF number to proceed.",
|
||||||
return;
|
});
|
||||||
}
|
setUserData(null);
|
||||||
setLoading(true);
|
return;
|
||||||
setUserData(null);
|
}
|
||||||
|
setLoading(true);
|
||||||
|
setUserData(null);
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
const response = await fetch(`/api/auth/admin/user/rights?CIF=${CIFNo}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "Admin",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
const data = await response.json();
|
||||||
const token = localStorage.getItem("admin_access_token");
|
|
||||||
const response = await fetch(
|
|
||||||
`/api/auth/admin/user/rights?CIF=${CIFNo}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Login-Type": "Admin",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
if (response.ok && data) {
|
||||||
if (response.ok && data) {
|
setUserData({
|
||||||
setUserData({
|
customer_no: data.customer_no,
|
||||||
customer_no: data.customer_no,
|
created_at: data.created_at,
|
||||||
created_at: data.created_at,
|
last_login: data.last_login,
|
||||||
last_login: data.last_login,
|
is_first_login: data.is_first_login,
|
||||||
is_first_login: data.is_first_login,
|
ibAccess: data.ib_access_level,
|
||||||
ibAccess: data.ib_access_level,
|
mbAccess: data.mb_access_level,
|
||||||
mbAccess: data.mb_access_level,
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
setError("User not present or invalid CIF number.");
|
||||||
notifications.show({
|
}
|
||||||
color: "red",
|
} catch (err) {
|
||||||
title: "Not Found",
|
setError("Error fetching user rights. Please try again.");
|
||||||
message: "No user rights found for this identifier.",
|
} finally {
|
||||||
});
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} />
|
||||||
|
<Title order={4}>View User Rights</Title>
|
||||||
|
<Group mt="sm">
|
||||||
|
<TextInput
|
||||||
|
placeholder="Enter CIF Number"
|
||||||
|
value={CIFNo}
|
||||||
|
onInput={(e) => {
|
||||||
|
const input = e.currentTarget.value.replace(/\D/g, "");
|
||||||
|
if (input.length <= 11) {
|
||||||
|
setCIFNo(input);
|
||||||
|
setUserData(null);
|
||||||
|
setError(null);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}}
|
||||||
notifications.show({
|
style={{ flex: 1 }}
|
||||||
color: "red",
|
/>
|
||||||
title: "Failed",
|
<Button onClick={handleSearch} disabled={loading}>
|
||||||
message: "Error fetching user rights",
|
Search
|
||||||
});
|
</Button>
|
||||||
} finally {
|
</Group>
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
{/* Error message in center */}
|
||||||
<Box>
|
{error && (
|
||||||
<LoadingOverlay visible={loading} zIndex={1000} />
|
<Center mt="xl">
|
||||||
<Title order={4}>View User Rights</Title>
|
<Text c="red" fw={500} size="md">
|
||||||
<Group mt="sm">
|
{error}
|
||||||
<TextInput
|
</Text>
|
||||||
// label="Enter CIF No"
|
</Center>
|
||||||
placeholder="Enter CIF Number"
|
)}
|
||||||
value={CIFNo}
|
|
||||||
onInput={(e) => {
|
|
||||||
const input = e.currentTarget.value.replace(/\D/g, "");
|
|
||||||
if (input.length <= 11) {
|
|
||||||
setCIFNo(input);
|
|
||||||
setUserData(null)
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
<Button onClick={handleSearch} disabled={loading}>
|
|
||||||
Search
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
{userData && (
|
{userData && (
|
||||||
<>
|
<>
|
||||||
<Divider size="xs" label="User Info" mt="md" />
|
<Divider size="xs" label="User Info" mt="md" />
|
||||||
<Paper
|
<Paper
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid #47C44D",
|
border: "1px solid #47C44D",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: 12,
|
padding: 12,
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text size="sm" fw={500} c="dimmed" w={150}>CIF Number:</Text>
|
<Text size="sm" fw={500} c="dimmed" w={150}>
|
||||||
<Text size="md">{userData.customer_no}</Text>
|
CIF Number:
|
||||||
</Group>
|
</Text>
|
||||||
<Group justify="space-between">
|
<Text size="md">{userData.customer_no}</Text>
|
||||||
<Text size="sm" fw={500} c="dimmed">User Created :</Text>
|
</Group>
|
||||||
<Text size="md">{new Date(userData.created_at).toLocaleString()}</Text>
|
<Group justify="space-between">
|
||||||
</Group>
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
<Group justify="space-between">
|
User Created :
|
||||||
<Text size="sm" fw={500} c="dimmed">User Status:</Text>
|
</Text>
|
||||||
<Text size="md" c="green">Active</Text>
|
<Text size="md">
|
||||||
</Group>
|
{new Date(userData.created_at).toLocaleString()}
|
||||||
<Group justify="space-between">
|
</Text>
|
||||||
<Text size="sm" fw={500} c="dimmed">User Registered Status:</Text>
|
</Group>
|
||||||
<Text size="md" c={userData.is_first_login ? "orange" : "green"}>
|
<Group justify="space-between">
|
||||||
{userData.is_first_login ? "Not Registered" : "Registered"}
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
</Text>
|
User Status:
|
||||||
</Group>
|
</Text>
|
||||||
</Stack>
|
<Text size="md" c="green">
|
||||||
</Paper>
|
Active
|
||||||
<Divider label="Rights" size="xs" mt="md" />
|
</Text>
|
||||||
<Paper style={{
|
</Group>
|
||||||
border: "1px solid #47C44D",
|
<Group justify="space-between">
|
||||||
borderRadius: 8,
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
padding: 12,
|
User Registered Status:
|
||||||
marginTop: 8,
|
</Text>
|
||||||
}}>
|
<Text
|
||||||
<Stack gap="sm" mt="sm">
|
size="md"
|
||||||
<Group justify="space-between">
|
c={userData.is_first_login ? "orange" : "green"}
|
||||||
<Text size="sm" fw={500} c="dimmed" w={150}>Internet Banking Rights:</Text>
|
>
|
||||||
<Text size="md">
|
{userData.is_first_login
|
||||||
{userData.ibAccess === "1"
|
? "Not Registered"
|
||||||
? "Transaction Mode"
|
: "Registered"}
|
||||||
: userData.ibAccess === "2"
|
</Text>
|
||||||
? "Read Mode"
|
</Group>
|
||||||
: "Not Declared"}
|
</Stack>
|
||||||
</Text>
|
</Paper>
|
||||||
</Group>
|
|
||||||
<Group justify="space-between">
|
<Divider label="Rights" size="xs" mt="md" />
|
||||||
<Text size="sm" fw={500} c="dimmed" w={150}>Mobile Banking Rights:</Text>
|
<Paper
|
||||||
<Text size="md">
|
style={{
|
||||||
{userData.mbAccess === "1"
|
border: "1px solid #47C44D",
|
||||||
? "Transaction Mode"
|
borderRadius: 8,
|
||||||
: userData.mbAccess === "2"
|
padding: 12,
|
||||||
? "Read Mode"
|
marginTop: 8,
|
||||||
: "Not Declared"}
|
}}
|
||||||
</Text>
|
>
|
||||||
</Group>
|
<Stack gap="sm" mt="sm">
|
||||||
</Stack>
|
<Group justify="space-between">
|
||||||
</Paper>
|
<Text size="sm" fw={500} c="dimmed" w={150}>
|
||||||
</>
|
Internet Banking Rights:
|
||||||
)
|
</Text>
|
||||||
}
|
<Text size="md">
|
||||||
</Box >
|
{userData.ibAccess === "1"
|
||||||
);
|
? "Transaction Mode"
|
||||||
|
: userData.ibAccess === "2"
|
||||||
|
? "Read Mode"
|
||||||
|
: "Not Declared"}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" fw={500} c="dimmed" w={150}>
|
||||||
|
Mobile Banking Rights:
|
||||||
|
</Text>
|
||||||
|
<Text size="md">
|
||||||
|
{userData.mbAccess === "1"
|
||||||
|
? "Transaction Mode"
|
||||||
|
: userData.mbAccess === "2"
|
||||||
|
? "Read Mode"
|
||||||
|
: "Not Declared"}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,183 +1,332 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Text, Box, Image, Button, Stack, Divider, Title } from "@mantine/core";
|
import {
|
||||||
|
Text,
|
||||||
|
Box,
|
||||||
|
Image,
|
||||||
|
Stack,
|
||||||
|
Divider,
|
||||||
|
Title,
|
||||||
|
Collapse,
|
||||||
|
Group,
|
||||||
|
UnstyledButton,
|
||||||
|
} from "@mantine/core";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { Providers } from "@/app/providers";
|
import { Providers } from "@/app/providers";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
import logo from '@/app/image/logo1.jpg';
|
import logo from "@/app/image/logo1.jpg";
|
||||||
import { IconEye, IconLogout, IconPhoneFilled, IconUsers, IconUserScreen } from "@tabler/icons-react";
|
import {
|
||||||
|
IconLogout,
|
||||||
|
IconPhoneFilled,
|
||||||
|
IconUsers,
|
||||||
|
IconChevronDown,
|
||||||
|
IconChevronUp,
|
||||||
|
IconSettings,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
import UserConfiguration from "./UserConfiguration";
|
import UserConfiguration from "./UserConfiguration";
|
||||||
import ViewUserConfiguration from "./ViewUserConfiguration";
|
import ViewUserConfiguration from "./ViewUserConfiguration";
|
||||||
|
import UnlockedUsers from "./UnlockedUsers";
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
const [authorized, SetAuthorized] = useState<boolean | null>(null);
|
||||||
const [view, setView] = useState<'userConf' | 'view' | null>(null);
|
const [view, setView] = useState<string | null>(null);
|
||||||
const [lastLoginDetails, setLastLoginDetails] = useState(null);
|
const [name, setName] = useState<string | null>(null);
|
||||||
const [name, setName] = useState(null);
|
const [lastLoginDetails, setLastLoginDetails] = useState<string | null>(null);
|
||||||
|
const [userMenuOpen, setUserMenuOpen] = useState(true);
|
||||||
|
const [configMenuOpen, setConfigMenuOpen] = useState(false);
|
||||||
|
|
||||||
async function handleLogout(e: React.FormEvent) {
|
async function handleLogout(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
localStorage.removeItem("admin_access_token");
|
localStorage.clear();
|
||||||
localStorage.clear();
|
sessionStorage.clear();
|
||||||
sessionStorage.clear();
|
router.push("/administrator/login");
|
||||||
router.push("/administrator/login");
|
}
|
||||||
|
|
||||||
|
async function handleFetchUserDetails(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const token = localStorage.getItem("admin_access_token");
|
||||||
|
const response = await fetch("/api/auth/admin/admin_details", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Login-Type": "Admin",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
return data;
|
||||||
|
} else if (response.status === 401 || data.message === "invalid or expired token") {
|
||||||
|
localStorage.removeItem("admin_access_token");
|
||||||
|
router.push("/administrator/login");
|
||||||
|
} else {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: "red",
|
||||||
|
title: "Please try again later",
|
||||||
|
message: "Unable to fetch timestamp, please try again later",
|
||||||
|
autoClose: 5000,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
async function handleFetchUserDetails(e: React.FormEvent) {
|
}
|
||||||
e.preventDefault();
|
|
||||||
const token = localStorage.getItem("admin_access_token");
|
useEffect(() => {
|
||||||
const response = await fetch('/api/auth/admin/admin_details', {
|
const token = localStorage.getItem("admin_access_token");
|
||||||
method: 'GET',
|
if (!token) {
|
||||||
headers: {
|
SetAuthorized(false);
|
||||||
'Content-Type': 'application/json',
|
router.push("/administrator/login/");
|
||||||
"X-Login-Type": "Admin",
|
} else {
|
||||||
'Authorization': `Bearer ${token}`
|
SetAuthorized(true);
|
||||||
},
|
const fetchLoginTime = async () => {
|
||||||
});
|
const result = await handleFetchUserDetails({
|
||||||
const data = await response.json();
|
preventDefault: () => { },
|
||||||
// console.log(data)
|
} as React.FormEvent);
|
||||||
if (response.ok) {
|
if (result) {
|
||||||
return data;
|
setLastLoginDetails(result.last_login);
|
||||||
}
|
setName(result.name);
|
||||||
else if (response.status === 401 || data.message === 'invalid or expired token') {
|
|
||||||
localStorage.removeItem("admin_access_token");
|
|
||||||
router.push('/administrator/login');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
notifications.show({
|
|
||||||
withBorder: true,
|
|
||||||
color: "red",
|
|
||||||
title: "Please try again later",
|
|
||||||
message: "Unable to fetch timestamp, please try again later",
|
|
||||||
autoClose: 5000,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
fetchLoginTime();
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
if (!authorized) return null;
|
||||||
const token = localStorage.getItem("admin_access_token");
|
|
||||||
if (!token) {
|
|
||||||
SetAuthorized(false);
|
|
||||||
router.push("/administrator/login/");
|
|
||||||
} else {
|
|
||||||
SetAuthorized(true);
|
|
||||||
const fetchLoginTime = async () => {
|
|
||||||
const result = await handleFetchUserDetails({ preventDefault: () => { } } as React.FormEvent);
|
|
||||||
if (result) {
|
|
||||||
setLastLoginDetails(result.last_login);
|
|
||||||
setName(result.name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchLoginTime();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClick = (type: 'UserConf' | 'ViewUser' | 'logout') => {
|
return (
|
||||||
if (type === 'UserConf') {
|
<Providers>
|
||||||
setView('userConf');
|
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%" }}>
|
||||||
} else if (type === 'ViewUser') {
|
{/* Header */}
|
||||||
setView('view');
|
<Box
|
||||||
}
|
style={{
|
||||||
};
|
height: "60px",
|
||||||
|
position: "relative",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
background:
|
||||||
|
"linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
fit="cover"
|
||||||
|
src={logo}
|
||||||
|
component={NextImage}
|
||||||
|
alt="ebanking"
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
/>
|
||||||
|
<Title
|
||||||
|
order={2}
|
||||||
|
style={{
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
position: "absolute",
|
||||||
|
top: "30%",
|
||||||
|
left: "6%",
|
||||||
|
color: "White",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "80%",
|
||||||
|
color: "white",
|
||||||
|
textShadow: "1px 1px 2px black",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconPhoneFilled size={18} /> Toll Free No : 1800-180-8008
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
if (!authorized) return null;
|
{/* Layout */}
|
||||||
|
<Box style={{ display: "flex", height: "90vh" }}>
|
||||||
|
{/* Sidebar */}
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
width: 240,
|
||||||
|
background: "#02a355",
|
||||||
|
padding: "0.75rem",
|
||||||
|
borderRight: "1px solid #ccc",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "13px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Title order={4} c="white" style={{ textAlign: "center", marginBottom: "6px" }}>
|
||||||
|
Admin Portal
|
||||||
|
</Title>
|
||||||
|
<Divider my="xs" color="rgba(255,255,255,0.3)" />
|
||||||
|
|
||||||
if (authorized) {
|
{/* User Maintenance */}
|
||||||
return (
|
<UnstyledButton
|
||||||
<Providers>
|
onClick={() => setUserMenuOpen(!userMenuOpen)}
|
||||||
<div style={{ backgroundColor: "#f8f9fa", width: "100%", height: "100%", }}>
|
style={{
|
||||||
{/* Header */}
|
display: "flex",
|
||||||
<Box
|
alignItems: "center",
|
||||||
style={{
|
justifyContent: "space-between",
|
||||||
height: "60px",
|
width: "100%",
|
||||||
position: 'relative',
|
padding: "5px 6px",
|
||||||
width: '100%',
|
borderRadius: "4px",
|
||||||
display: "flex",
|
backgroundColor: "rgba(255,255,255,0.1)",
|
||||||
justifyContent: "flex-start",
|
color: "white",
|
||||||
background: "linear-gradient(15deg,rgba(10, 114, 40, 1) 55%, rgba(101, 101, 184, 1) 100%)",
|
fontSize: "13px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Group gap="xs">
|
||||||
fit="cover"
|
<IconUsers size={15} />
|
||||||
src={logo}
|
<Text fw={600} size="sm">
|
||||||
component={NextImage}
|
User Maintenance
|
||||||
alt="ebanking"
|
</Text>
|
||||||
style={{ width: "100%", height: "100%" }}
|
</Group>
|
||||||
/>
|
{userMenuOpen ? <IconChevronUp size={15} /> : <IconChevronDown size={15} />}
|
||||||
<Title
|
</UnstyledButton>
|
||||||
order={2}
|
<Collapse in={userMenuOpen}>
|
||||||
style={{
|
<Stack gap={2} pl="md" mt={4}>
|
||||||
fontFamily: 'Roboto',
|
<Box
|
||||||
position: 'absolute',
|
onClick={() => setView("userConf")}
|
||||||
top: '30%',
|
style={{
|
||||||
left: '6%',
|
cursor: "pointer",
|
||||||
color: 'White',
|
color: view === "userConf" ? "#02a355" : "white",
|
||||||
transition: "opacity 0.5s ease-in-out",
|
backgroundColor: view === "userConf" ? "white" : "transparent",
|
||||||
}}
|
borderRadius: "3px",
|
||||||
>
|
padding: "3px 6px",
|
||||||
THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.
|
transition: "0.2s",
|
||||||
</Title>
|
}}
|
||||||
<Text
|
>
|
||||||
style={{
|
• User Create / Update
|
||||||
position: 'absolute',
|
</Box>
|
||||||
top: '50%',
|
<Box
|
||||||
left: '80%',
|
onClick={() => setView("viewUser")}
|
||||||
color: 'white',
|
style={{
|
||||||
textShadow: '1px 1px 2px black',
|
cursor: "pointer",
|
||||||
}}
|
color: view === "viewUser" ? "#02a355" : "white",
|
||||||
>
|
backgroundColor: view === "viewUser" ? "white" : "transparent",
|
||||||
<IconPhoneFilled size={20} /> Toll Free No : 1800-180-8008
|
borderRadius: "3px",
|
||||||
</Text>
|
padding: "3px 6px",
|
||||||
</Box>
|
transition: "0.2s",
|
||||||
{/* layout and */}
|
}}
|
||||||
<Box style={{ display: 'flex', height: '90vh' }}>
|
>
|
||||||
{/* Sidebar manually placed under header */}
|
• Users Status
|
||||||
<Box
|
</Box>
|
||||||
style={{
|
<Box
|
||||||
width: 250,
|
onClick={() => setView("unlockUser")}
|
||||||
background: "#ebf5f5ff",
|
style={{
|
||||||
// background: "linear-gradient(90deg,rgba(42, 123, 155, 1) 0%, rgba(87, 199, 133, 1) 30%)",
|
cursor: "pointer",
|
||||||
padding: '1rem',
|
color: view === "unlockUser" ? "#02a355" : "white",
|
||||||
borderRight: '1px solid #ccc',
|
backgroundColor: view === "unlockUser" ? "white" : "transparent",
|
||||||
}}
|
borderRadius: "3px",
|
||||||
>
|
padding: "3px 6px",
|
||||||
<Title order={3} style={{ textAlign: 'center' }}>Admin Portal</Title>
|
transition: "0.2s",
|
||||||
<Divider my="xs" label="Menu" labelPosition="center" />
|
}}
|
||||||
<Stack>
|
>
|
||||||
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('UserConf')}>
|
• User Unlock
|
||||||
<IconUsers size="15" /> User Configuration
|
</Box>
|
||||||
</Text>
|
</Stack>
|
||||||
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={() => handleClick('ViewUser')}>
|
</Collapse>
|
||||||
<IconEye size="15" /> View User Configuration
|
|
||||||
</Text>
|
|
||||||
<Text td="underline" variant="light" style={{ cursor: 'pointer' }} onClick={handleLogout}>
|
|
||||||
<IconLogout size="15" /> Logout
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Configuration */}
|
||||||
<Stack style={{ flex: 1, padding: '1rem' }}>
|
<UnstyledButton
|
||||||
<Box>
|
onClick={() => setConfigMenuOpen(!configMenuOpen)}
|
||||||
<Text c="Blue" size="lg" fs='italic'>Welcome ,{name} </Text>
|
style={{
|
||||||
<Text size="xs" c="gray" style={{ fontFamily: "inter", fontSize: '13px' }}>
|
display: "flex",
|
||||||
Last logged in at {lastLoginDetails ? new Date(lastLoginDetails).toLocaleString() : "N/A"}
|
alignItems: "center",
|
||||||
</Text>
|
justifyContent: "space-between",
|
||||||
</Box>
|
width: "100%",
|
||||||
<Box>
|
padding: "5px 6px",
|
||||||
{view === 'userConf' && <UserConfiguration />}
|
marginTop: "8px",
|
||||||
{view === 'view' && <Text size="xl"><ViewUserConfiguration /></Text>}
|
borderRadius: "4px",
|
||||||
{!view &&
|
backgroundColor: "rgba(255,255,255,0.1)",
|
||||||
<Text size="xl">Welcome To The Admin Portal</Text>
|
color: "white",
|
||||||
}
|
fontSize: "13px",
|
||||||
</Box>
|
}}
|
||||||
</Stack>
|
>
|
||||||
</Box>
|
<Group gap="xs">
|
||||||
<Divider size="xs" color='#99c2ff' />
|
<IconSettings size={15} />
|
||||||
<Text size="sm" style={{ textAlign: "center" }}>© 2025 The Kangra Central Co-Operative Bank</Text>
|
<Text fw={600} size="sm">
|
||||||
</div>
|
Configuration
|
||||||
</Providers>
|
</Text>
|
||||||
);
|
</Group>
|
||||||
}
|
{configMenuOpen ? <IconChevronUp size={15} /> : <IconChevronDown size={15} />}
|
||||||
|
</UnstyledButton>
|
||||||
|
<Collapse in={configMenuOpen}>
|
||||||
|
<Stack gap={2} pl="md" mt={4}>
|
||||||
|
<Box
|
||||||
|
onClick={() => setView("ifscConfig")}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
color: view === "ifscConfig" ? "#02a355" : "white",
|
||||||
|
backgroundColor: view === "ifscConfig" ? "white" : "transparent",
|
||||||
|
borderRadius: "3px",
|
||||||
|
padding: "3px 6px",
|
||||||
|
transition: "0.2s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
• IFSC Configuration
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<Divider my="xs" color="rgba(255,255,255,0.3)" />
|
||||||
|
<Text
|
||||||
|
onClick={handleLogout}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
color: "white",
|
||||||
|
marginTop: "8px",
|
||||||
|
fontSize: "13px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconLogout size={14} /> Logout
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<Stack style={{ flex: 1, padding: "1rem" }}>
|
||||||
|
<Box>
|
||||||
|
<Text c="Blue" size="sm" fs="italic">
|
||||||
|
Welcome, {name}
|
||||||
|
</Text>
|
||||||
|
<Text size="xs" c="gray" style={{ fontFamily: "inter" }}>
|
||||||
|
Last logged in at{" "}
|
||||||
|
{lastLoginDetails
|
||||||
|
? new Date(lastLoginDetails).toLocaleString()
|
||||||
|
: "N/A"}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
{view === "userConf" && <UserConfiguration />}
|
||||||
|
{view === "viewUser" && <ViewUserConfiguration />}
|
||||||
|
{view === "unlockUser" && <UnlockedUsers />}
|
||||||
|
{view === "ifscConfig" && (
|
||||||
|
<Text size="sm" c="gray">
|
||||||
|
IFSC Configuration Page Coming Soon
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{!view && (
|
||||||
|
<>
|
||||||
|
<Text size="xl" c="gray">
|
||||||
|
Welcome To The Internet Banking Admin Portal
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="black">
|
||||||
|
Choose the service from the menu.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider size="xs" color="#99c2ff" />
|
||||||
|
<Text size="xs" style={{ textAlign: "center" }}>
|
||||||
|
© 2025 The Kangra Central Co-Operative Bank
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ export default function Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile });
|
// await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: mobile });
|
||||||
// await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "7890544527" });
|
await sendOtp({ type: 'LOGIN_OTP', username: CIF, mobileNumber: "7890544527" });
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
title: 'OTP Required',
|
title: 'OTP Required',
|
||||||
@@ -67,8 +67,8 @@ export default function Login() {
|
|||||||
async function handleVerifyOtp(mobile?: string) {
|
async function handleVerifyOtp(mobile?: string) {
|
||||||
try {
|
try {
|
||||||
if (mobile) {
|
if (mobile) {
|
||||||
await verifyLoginOtp(otp, mobile);
|
// await verifyLoginOtp(otp, mobile);
|
||||||
// await verifyLoginOtp(otp, '7890544527');
|
await verifyLoginOtp(otp, '7890544527');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user