feat : connection made between login and home page
This commit is contained in:
@@ -1,80 +0,0 @@
|
|||||||
.title {
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
|
||||||
font-family:
|
|
||||||
Greycliff CF,
|
|
||||||
var(--mantine-font-family);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbarMain {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding-bottom: var(--mantine-spacing-md);
|
|
||||||
margin-bottom: calc(var(--mantine-spacing-md) * 1.5);
|
|
||||||
border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
|
||||||
padding: var(--mantine-spacing-md);
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
padding-top: var(--mantine-spacing-md);
|
|
||||||
margin-top: var(--mantine-spacing-md);
|
|
||||||
border-top: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: var(--mantine-font-size-sm);
|
|
||||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
|
|
||||||
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
|
||||||
border-radius: var(--mantine-radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
|
||||||
|
|
||||||
.linkIcon {
|
|
||||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-active] {
|
|
||||||
&,
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--mantine-primary-color-light);
|
|
||||||
color: var(--mantine-primary-color-light-color);
|
|
||||||
|
|
||||||
.linkIcon {
|
|
||||||
color: var(--mantine-primary-color-light-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkIcon {
|
|
||||||
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
|
|
||||||
margin-right: var(--mantine-spacing-sm);
|
|
||||||
width: rem(25px);
|
|
||||||
height: rem(25px);
|
|
||||||
}
|
|
||||||
|
|
@@ -1,159 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import React, { useContext, useState } from "react";
|
|
||||||
import { UserContext } from "../_components/user-context";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import { AppShell, Burger, Group, Image, Text, LoadingOverlay, Avatar, UnstyledButton, Title, Skeleton, Center, Switch, NavLink, ScrollArea, Code } from '@mantine/core';
|
|
||||||
import { IconLogout, IconExclamationCircle, IconNotes, IconCategory, IconTicket, IconEye } from '@tabler/icons-react';
|
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
|
||||||
import classes from './layout.module.css';
|
|
||||||
import { KccbTheme } from "../_themes/KccbTheme";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import axios, { AxiosError } from "axios";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
try {
|
|
||||||
await axios.post('/api/auth/logout');
|
|
||||||
} catch (error: AxiosError | any) {
|
|
||||||
notifications.show({
|
|
||||||
color: 'red',
|
|
||||||
title: error.code,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomeLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
|
|
||||||
const user = useContext(UserContext);
|
|
||||||
if (user === null) redirect('/login');
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [burgerOpened, { toggle: toggleBurger }] = useDisclosure();
|
|
||||||
const [activeNavItem, setActiveNavItem] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const logoutMutation = useMutation({
|
|
||||||
mutationKey: ['logout'],
|
|
||||||
mutationFn: logout
|
|
||||||
})
|
|
||||||
|
|
||||||
async function handleLogout(event: React.MouseEvent) {
|
|
||||||
event.preventDefault();
|
|
||||||
await logoutMutation.mutateAsync();
|
|
||||||
await queryClient.refetchQueries({ queryKey: ['user'] })
|
|
||||||
}
|
|
||||||
|
|
||||||
let navItems: React.ReactNode[] = [];
|
|
||||||
|
|
||||||
navItems.push(<>
|
|
||||||
<NavLink key={'raised-ticket'} label={
|
|
||||||
<>
|
|
||||||
<IconTicket size={20} style={{ marginRight: '5px', transform: 'translateY(5px)' }} />
|
|
||||||
<span style={{ fontSize: '18px' }}>Create Ticket </span>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
active={activeNavItem === 'raised-ticket' || undefined}
|
|
||||||
onClick={event => {
|
|
||||||
event.preventDefault();
|
|
||||||
setActiveNavItem('raised-ticket')
|
|
||||||
router.push('/home/user/raised-ticket')
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<NavLink key={'view-ticket'} label={<><IconEye size={20} style={{ marginRight: '8px', transform: 'translateY(5px)' }} />
|
|
||||||
<span style={{ fontSize: '18px' }}>View Ticket </span>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
active={activeNavItem === 'view-ticket' || undefined}
|
|
||||||
onClick={event => {
|
|
||||||
event.preventDefault();
|
|
||||||
setActiveNavItem('view-ticket')
|
|
||||||
router.push('/home/user/view-ticket');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LoadingOverlay
|
|
||||||
visible={!user || logoutMutation.isPending || logoutMutation.isSuccess}
|
|
||||||
overlayProps={{ backgroundOpacity: 1 }} />
|
|
||||||
|
|
||||||
{user && <AppShell
|
|
||||||
header={{ height: { base: 60, md: 70, lg: 80 } }}
|
|
||||||
navbar={{
|
|
||||||
width: { base: 200, md: 250, lg: 300 },
|
|
||||||
breakpoint: 'sm',
|
|
||||||
collapsed: { mobile: !burgerOpened },
|
|
||||||
}}
|
|
||||||
padding="lg"
|
|
||||||
>
|
|
||||||
<AppShell.Header>
|
|
||||||
<Group h="100%" px="md" justify="space-between">
|
|
||||||
<Group>
|
|
||||||
<Burger opened={burgerOpened} onClick={toggleBurger} hiddenFrom="sm" size="sm" />
|
|
||||||
<Image
|
|
||||||
radius='md'
|
|
||||||
h={50}
|
|
||||||
w='auto'
|
|
||||||
fit='contain'
|
|
||||||
src={'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR-ZljkQXsXUP1-EwnIPZtVq_JWhwGUW7b0_eSMno-bag&s'}
|
|
||||||
/>
|
|
||||||
<Title order={2} className={classes.title}>Customer Request Management</Title>
|
|
||||||
</Group>
|
|
||||||
{/* <Group>
|
|
||||||
|
|
||||||
<UnstyledButton className={classes.user} onClick={() => {
|
|
||||||
setActiveNavItem(null);
|
|
||||||
router.push('/home/user');
|
|
||||||
}}>
|
|
||||||
</UnstyledButton>
|
|
||||||
</Group> */}
|
|
||||||
<Group>
|
|
||||||
<Avatar radius="xl" src={null} alt={user.bank_account_no} color={KccbTheme.primaryColor}>
|
|
||||||
</Avatar>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<Text size="sm" fw={500}>
|
|
||||||
Account No: [{user.bank_account_no}]
|
|
||||||
</Text>
|
|
||||||
{/* <Text c='dimmed' size="sm" component="span">User: </Text>
|
|
||||||
<Code c='dimmed'>{user.id}</Code> */}
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</AppShell.Header>
|
|
||||||
<AppShell.Navbar p="md">
|
|
||||||
<Text>Menu</Text>
|
|
||||||
<nav className={classes.navbar}>
|
|
||||||
<div className={classes.navbarMain}>
|
|
||||||
<ScrollArea>
|
|
||||||
{navItems}
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div className={classes.footer}>
|
|
||||||
<a className={classes.link} onClick={handleLogout}>
|
|
||||||
<IconLogout className={classes.linkIcon} stroke={1.5} />
|
|
||||||
<Text>Logout</Text>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</AppShell.Navbar>
|
|
||||||
<AppShell.Main>
|
|
||||||
{children}
|
|
||||||
</AppShell.Main>
|
|
||||||
</AppShell>}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@@ -1,18 +1,38 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { UserContextConsumer } from "../_components/user-context";
|
import { Button } from "@mantine/core";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
const[authorized,SetAuthorized] =useState<boolean|null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function handleLogout(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
localStorage.removeItem("access_token");
|
||||||
|
router.push("/login")
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem("access_token");
|
||||||
|
if (!token) {
|
||||||
|
SetAuthorized(false);
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
SetAuthorized(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(authorized){
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<p>Welcome to IB Portal</p>
|
<p>Welcome to IB portal</p>
|
||||||
<UserContextConsumer>
|
<Button onClick={handleLogout}>Logout</Button>
|
||||||
{
|
|
||||||
user => user && <p><b>Your Present Login Account No: {user.bank_account_no}</b></p>
|
|
||||||
}
|
|
||||||
</UserContextConsumer>
|
|
||||||
<p><li>For raise a complain or assistance ,please click on <b>Create Ticket</b></li></p>
|
|
||||||
<p><li>For track a ticket ,please click on <b>View Ticket</b></li></p>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,263 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import axios, { AxiosError } from "axios";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
const ComplaintForm: React.FC = () => {
|
|
||||||
const [category, setCategory] = useState("");
|
|
||||||
const [description, setDescription] = useState("");
|
|
||||||
const [additionalFields, setAdditionalFields] = useState<string[]>([]);
|
|
||||||
const [additionalFieldValues, setAdditionalFieldValues] = useState<Record<string, string>>({});
|
|
||||||
const [message, setMessage] = useState("");
|
|
||||||
|
|
||||||
const handleCategoryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
const selectedCategory = e.target.value;
|
|
||||||
setCategory(selectedCategory);
|
|
||||||
setDescription(""); // Reset description when category changes
|
|
||||||
setAdditionalFields([]);
|
|
||||||
setAdditionalFieldValues({});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDescriptionClick = () => {
|
|
||||||
if (!category) {
|
|
||||||
alert("Please select the Category of Complaint first.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDescriptionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
if (!category) {
|
|
||||||
setDescription(""); // Prevent changing description without category
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedDescription = e.target.value;
|
|
||||||
setDescription(selectedDescription);
|
|
||||||
|
|
||||||
// Add additional fields for specific descriptions
|
|
||||||
if (selectedDescription === "Transaction Issue") {
|
|
||||||
setAdditionalFields([
|
|
||||||
"Debit Card",
|
|
||||||
"Transaction Amount",
|
|
||||||
"Transaction Reference Number ",
|
|
||||||
"Transaction Date",
|
|
||||||
"ATM ID/Terminal ID",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else if(selectedDescription === "Fund Transfer failure"){
|
|
||||||
setAdditionalFields([
|
|
||||||
"Transaction Amount",
|
|
||||||
"Transaction ID/Transaction Reference Number",
|
|
||||||
"Transaction Date"
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setAdditionalFields([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAdditionalFieldChange = (field: string, value: string) => {
|
|
||||||
if(field === "Transaction Amount" && isNaN(Number(value)))
|
|
||||||
{
|
|
||||||
alert ('Please enter a valid number');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(field === "Debit Card" && isNaN(Number(value)))
|
|
||||||
{
|
|
||||||
alert ('Please Enter Valid Debit Card Number');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAdditionalFieldValues((prevValues) => ({
|
|
||||||
...prevValues,
|
|
||||||
[field]: value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent) {
|
|
||||||
e.preventDefault();
|
|
||||||
for (const field of additionalFields) {
|
|
||||||
if (!additionalFieldValues[field]) {
|
|
||||||
alert(`Please fill in the ${field}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!message.trim()) {
|
|
||||||
alert("Message field is required.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalInformation: string[] = [];
|
|
||||||
if (additionalFieldValues['ATM ID/Terminal ID']) {
|
|
||||||
additionalInformation.push(`ATM ID: ${additionalFieldValues['ATM ID/Terminal ID']}`);
|
|
||||||
}
|
|
||||||
if (additionalFieldValues['Debit Card']) {
|
|
||||||
additionalInformation.push(`Debit Card: ${additionalFieldValues['Debit Card']}`);
|
|
||||||
}
|
|
||||||
if (additionalFieldValues['Transaction Amount']) {
|
|
||||||
additionalInformation.push(`Transaction Amount: ${additionalFieldValues['Transaction Amount']}`);
|
|
||||||
}
|
|
||||||
if (additionalFieldValues['Transaction Reference Number ']) {
|
|
||||||
additionalInformation.push(`Transaction Reference Number : ${additionalFieldValues['Transaction Reference Number ']}`);
|
|
||||||
}
|
|
||||||
if (additionalFieldValues['Transaction Date']) {
|
|
||||||
additionalInformation.push(`Transaction Date: ${additionalFieldValues['Transaction Date']}`);
|
|
||||||
}
|
|
||||||
if(additionalFieldValues['Transaction ID/Transaction Reference Number']){
|
|
||||||
additionalInformation.push(`Transaction ID/Transaction Reference Number: ${additionalFieldValues['Transaction ID/Transaction Reference Number']}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestBody = {
|
|
||||||
summary: category,
|
|
||||||
description: description,
|
|
||||||
additional_information: additionalInformation.join(", "),
|
|
||||||
steps_to_reproduce: message,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.post("/api/ticket", requestBody);
|
|
||||||
const data = await response.data;
|
|
||||||
alert(data.message);
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
catch (error: AxiosError | any) {
|
|
||||||
notifications.show({
|
|
||||||
color: 'red',
|
|
||||||
title: error.response.status,
|
|
||||||
message: error.response.data.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "20px", maxWidth: "500px", textAlign: "left" }}>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<h2 style={{ marginBottom: "8px" }}> Create New Ticket </h2>
|
|
||||||
|
|
||||||
<label style={{ marginTop: "8px" }}>
|
|
||||||
Category of Complaint <span style={{ color: "red" }}>*</span>
|
|
||||||
<select
|
|
||||||
value={category}
|
|
||||||
onChange={handleCategoryChange}
|
|
||||||
required
|
|
||||||
style={{ display: "block", marginBottom: "10px", width: "100%" }}
|
|
||||||
>
|
|
||||||
<option value="" disabled>
|
|
||||||
Select Category
|
|
||||||
</option>
|
|
||||||
<option value="ATM Related">ATM Related</option>
|
|
||||||
<option value="Internet Banking">Internet Banking</option>
|
|
||||||
<option value="Mobile Banking">Mobile Banking</option>
|
|
||||||
<option value="Others">Others</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Description <span style={{ color: "red" }}>*</span>
|
|
||||||
<select
|
|
||||||
value={description}
|
|
||||||
onClick={handleDescriptionClick}
|
|
||||||
onChange={handleDescriptionChange}
|
|
||||||
required
|
|
||||||
style={{ display: "block", marginBottom: "10px", width: "100%" }}
|
|
||||||
>
|
|
||||||
<option value="" disabled>
|
|
||||||
Select Description
|
|
||||||
</option>
|
|
||||||
{category === "ATM Related" && <option value="Transaction Issue">Transaction Issue</option>}
|
|
||||||
{/* {category === "UPI" && <option value="UPI Issue">UPI Issue</option>} */}
|
|
||||||
{category === "Internet Banking" && (
|
|
||||||
<>
|
|
||||||
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
|
||||||
<option value="Fund Transfer failure">Fund Transfer Failure</option>
|
|
||||||
<option value="Network Issue">Network Issue</option>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{category === "Mobile Banking" && (
|
|
||||||
<>
|
|
||||||
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
|
||||||
<option value="Fund Transfer failure">Fund Transfer Failure</option>
|
|
||||||
<option value="Network Issue">Network Issue</option>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{category === "Others" && (
|
|
||||||
<>
|
|
||||||
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
|
||||||
<option value="Others">Others</option>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{additionalFields.map((field, index) => (
|
|
||||||
<div key={index}>
|
|
||||||
{field === "Transaction Date" ? (
|
|
||||||
<label>
|
|
||||||
{field}: <span style={{ color: "red" }}>*</span>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={additionalFieldValues[field] || ""}
|
|
||||||
onChange={(e) => handleAdditionalFieldChange(field, e.target.value)}
|
|
||||||
required
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
marginBottom: "10px",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
) : (
|
|
||||||
<label>
|
|
||||||
{field}: <span style={{ color: "red" }}>*</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={`Enter ${field}`}
|
|
||||||
value={additionalFieldValues[field] || ""}
|
|
||||||
onChange={(e) => handleAdditionalFieldChange(field, e.target.value)}
|
|
||||||
required
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
marginBottom: "10px",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<label>
|
|
||||||
Message (Max 500 characters) <span style={{ color: "red" }}>*</span>
|
|
||||||
<textarea
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
maxLength={500}
|
|
||||||
rows={5}
|
|
||||||
required
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
marginBottom: "10px",
|
|
||||||
width: "100%",
|
|
||||||
resize: "none",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
width: "100%",
|
|
||||||
padding: "10px",
|
|
||||||
backgroundColor: "#4CAF50",
|
|
||||||
color: "white",
|
|
||||||
border: "none",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ComplaintForm;
|
|
@@ -1,332 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
Button,
|
|
||||||
Container,
|
|
||||||
Flex,
|
|
||||||
Group,
|
|
||||||
Modal,
|
|
||||||
Space,
|
|
||||||
Table,
|
|
||||||
TextInput,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
import { IconDisabled, IconEye, IconMessage, IconSearch, IconTrash ,IconArrowsUpDown} from "@tabler/icons-react";
|
|
||||||
import axios, { AxiosError } from "axios";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
|
|
||||||
interface Message {
|
|
||||||
note: string;
|
|
||||||
time:string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Ticket {
|
|
||||||
ticket_id: string;
|
|
||||||
category_of_request: string;
|
|
||||||
nature_of_request: string;
|
|
||||||
created_date: string;
|
|
||||||
status: string;
|
|
||||||
message: Message[];
|
|
||||||
}
|
|
||||||
interface TicketDetails {
|
|
||||||
ticket_id: string;
|
|
||||||
category_of_request: string;
|
|
||||||
nature_of_request: string;
|
|
||||||
additional_info: string;
|
|
||||||
message : string;
|
|
||||||
created_date: string;
|
|
||||||
|
|
||||||
}
|
|
||||||
export default function Page() {
|
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
|
||||||
const [tickets, setTickets] = useState<Ticket[]>([]);
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
const [filteredTickets, setFilteredTickets] = useState<Ticket[]>([]);
|
|
||||||
const [activeMessages, setActiveMessages] = useState<Message[] | null>(null);
|
|
||||||
const [ticketDetails, setTicketDetails] = useState<TicketDetails| null>(null);
|
|
||||||
const [ticketStatus, setTicketStatus] = useState<string| null>(null);
|
|
||||||
const [sortOrder,setSortOrder]= useState<"asc" |"desc">("desc");
|
|
||||||
|
|
||||||
const [detailsOpened, { open: openDetails, close: closeDetails }] = useDisclosure(false);
|
|
||||||
|
|
||||||
// For view ticket details
|
|
||||||
const handleOpenDetails = async (ticketId: string) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get<TicketDetails>(`/api/ticket/${ticketId}`); // Assuming API provides details
|
|
||||||
setTicketDetails(response.data);
|
|
||||||
openDetails();
|
|
||||||
}
|
|
||||||
catch(error: AxiosError | any) {
|
|
||||||
console.error("Failed to fetch ticket details:", error);
|
|
||||||
notifications.show({
|
|
||||||
color: 'red',
|
|
||||||
title: error.response.status,
|
|
||||||
message: error.response.data.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// For parsing additional_info
|
|
||||||
const parseAdditionalInfo = (info: string) => {
|
|
||||||
return info.split(", ").map((item) => {
|
|
||||||
const [key, value] = item.split(": ");
|
|
||||||
return { key: key.trim(), value: value?.trim() || "" };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// For delete a ticket
|
|
||||||
const handleDelete = async (ticketId: string) => {
|
|
||||||
const isConfirm =window.confirm("Do you want to delete the ticket?");
|
|
||||||
if(!isConfirm)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
const response = await axios.delete<TicketDetails>(`/api/ticket/${ticketId}`); // Assuming API provides details
|
|
||||||
const data = await response.data;
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
catch(error: AxiosError | any) {
|
|
||||||
console.error("Failed to delete ticket:", error);
|
|
||||||
notifications.show({
|
|
||||||
color: 'red',
|
|
||||||
title: error.response.status,
|
|
||||||
message: error.response.data.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
finally{
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Sort and filter tickets based on the time
|
|
||||||
useEffect(() => {
|
|
||||||
const sortedTickets = [...tickets].sort(
|
|
||||||
// (a, b) => new Date(b.created_date).getTime() - new Date(a.created_date).getTime()
|
|
||||||
(a, b) => parseInt(b.ticket_id) - parseInt(a.ticket_id)
|
|
||||||
);
|
|
||||||
const results = sortedTickets.filter((ticket) =>
|
|
||||||
ticket.category_of_request.toLowerCase().includes(searchQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
setFilteredTickets(results);
|
|
||||||
}, [searchQuery, tickets]);
|
|
||||||
|
|
||||||
// Fetch tickets from API
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchTickets() {
|
|
||||||
try {
|
|
||||||
const response = await axios.get<Ticket[]>("/api/ticket");
|
|
||||||
setTickets(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch tickets: ", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchTickets();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Sorting the ticket when any ticket resolved the go to the end of the list
|
|
||||||
useEffect(() => {
|
|
||||||
const sortedTickets = [...tickets].sort((a, b) => {
|
|
||||||
if (a.status.toLowerCase() === "resolved" && b.status.toLowerCase() !== "resolved") return 1;
|
|
||||||
if (b.status.toLowerCase() === "resolved" && a.status.toLowerCase() !== "resolved") return -1;
|
|
||||||
return parseInt(b.ticket_id) - parseInt(a.ticket_id); // Sort by ticket ID otherwise
|
|
||||||
});
|
|
||||||
const results = sortedTickets.filter(ticket =>
|
|
||||||
ticket.category_of_request.toLowerCase().includes(searchQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
setFilteredTickets(results);
|
|
||||||
}, [searchQuery, tickets]);
|
|
||||||
|
|
||||||
// For sorting ticket history according to time
|
|
||||||
const sortedMessages = activeMessages
|
|
||||||
? [...activeMessages].sort((a, b) => {
|
|
||||||
const timeA = new Date(a.time).getTime();
|
|
||||||
const timeB = new Date(b.time).getTime();
|
|
||||||
return sortOrder === "asc" ? timeA - timeB : timeB - timeA;
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const handleOpenMessage = (messages: Message[],status:string) => {
|
|
||||||
console.log(messages);
|
|
||||||
setActiveMessages(messages);
|
|
||||||
setTicketStatus(status);
|
|
||||||
open();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Add conditional row styles
|
|
||||||
const getRowStyle = (status: string) => {
|
|
||||||
switch (status.toLowerCase()) {
|
|
||||||
case "resolved":
|
|
||||||
// return { backgroundColor: "#d9d9d9", color: "#a6a6a6"}; // Grey for closed
|
|
||||||
return {color: "#a6a6a6"}; // Grey for closed
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rows = filteredTickets.map((ticket) => (
|
|
||||||
<Table.Tr key={ticket.ticket_id} style={getRowStyle(ticket.status)}> {/* added for coloured grey */}
|
|
||||||
{/* <Table.Tr key={ticket.ticket_id}> */}
|
|
||||||
<Table.Td>{ticket.ticket_id}</Table.Td>
|
|
||||||
<Table.Td>{ticket.category_of_request}</Table.Td>
|
|
||||||
<Table.Td>{ticket.nature_of_request}</Table.Td>
|
|
||||||
<Table.Td>{ticket.created_date}</Table.Td>
|
|
||||||
<Table.Td>{ticket.status}</Table.Td>
|
|
||||||
<Table.Td>
|
|
||||||
<Group>
|
|
||||||
<ActionIcon style={getRowStyle(ticket.status)}
|
|
||||||
variant="subtle" color="blue"
|
|
||||||
onClick={() => handleOpenMessage(ticket.message,ticket.status)}
|
|
||||||
>
|
|
||||||
<Tooltip label="Ticket History">
|
|
||||||
<IconMessage />
|
|
||||||
</Tooltip>
|
|
||||||
{/* design for badge */}
|
|
||||||
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
</Table.Td>
|
|
||||||
|
|
||||||
<Table.Td>
|
|
||||||
<Group gap="md" wrap="nowrap" justify="center">
|
|
||||||
<ActionIcon style={getRowStyle(ticket.status)}
|
|
||||||
variant="subtle"
|
|
||||||
onClick={() => handleOpenDetails(ticket.ticket_id)}>
|
|
||||||
<Tooltip label="Details of the Ticket">
|
|
||||||
<IconEye />
|
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon>
|
|
||||||
|
|
||||||
{/* Remove delete icon */}
|
|
||||||
{/* <ActionIcon style={getRowStyle(ticket.status)}
|
|
||||||
disabled={ticket.status.toLowerCase()==='resolved'}
|
|
||||||
variant="subtle" color="red"
|
|
||||||
onClick={() => handleDelete(ticket.ticket_id)}>
|
|
||||||
<Tooltip label="Delete the Ticket">
|
|
||||||
<IconTrash/>
|
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon> */}
|
|
||||||
</Group>
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
|
|
||||||
));
|
|
||||||
return (
|
|
||||||
<Container fluid>
|
|
||||||
<Title order={3}>View Ticket</Title>
|
|
||||||
<Space h="1rem" />
|
|
||||||
<Flex align="center" justify="space-between" w={720}>
|
|
||||||
<Flex align="center">
|
|
||||||
<Title order={5}>Tickets</Title>
|
|
||||||
<Space w="1rem" />
|
|
||||||
<TextInput
|
|
||||||
placeholder="Search"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
|
||||||
radius="md"
|
|
||||||
w={250}
|
|
||||||
leftSection={<IconSearch size={16} />}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
<Space h="1.5rem" />
|
|
||||||
<Table
|
|
||||||
w={720}
|
|
||||||
stickyHeader
|
|
||||||
stickyHeaderOffset={60}
|
|
||||||
withTableBorder
|
|
||||||
highlightOnHover
|
|
||||||
horizontalSpacing="lg"
|
|
||||||
>
|
|
||||||
<Table.Thead>
|
|
||||||
<Table.Tr>
|
|
||||||
<Table.Th>Ticket ID</Table.Th>
|
|
||||||
<Table.Th>Category</Table.Th>
|
|
||||||
<Table.Th>Description</Table.Th>
|
|
||||||
<Table.Th>Timestamp</Table.Th>
|
|
||||||
<Table.Th>Status</Table.Th>
|
|
||||||
<Table.Th>History</Table.Th>
|
|
||||||
<Table.Th>Action</Table.Th>
|
|
||||||
</Table.Tr>
|
|
||||||
</Table.Thead>
|
|
||||||
<Table.Tbody>{rows}</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
<Modal opened={opened} onClose={close} title="">
|
|
||||||
<Flex justify="space-between" align="center" >
|
|
||||||
<Title order={4}>Ticket History</Title>
|
|
||||||
<ActionIcon
|
|
||||||
variant="subtle"
|
|
||||||
onClick={() => setSortOrder(sortOrder === "asc" ? "desc" : "asc")}
|
|
||||||
>
|
|
||||||
<Tooltip label={`Sort by ${sortOrder === "asc" ? "Newest " : "Oldest"}`} position="right">
|
|
||||||
<IconArrowsUpDown size={18} />
|
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon>
|
|
||||||
</Flex>
|
|
||||||
<Flex direction="column" gap="sm" style={{ maxHeight: "400px", overflowY: "auto" }}>
|
|
||||||
{sortedMessages?.map((msg, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
style={{
|
|
||||||
padding: "10px",
|
|
||||||
background: index % 2 === 0 ? "#f1f3f5" : "#dce6f2",
|
|
||||||
borderRadius: "10px",
|
|
||||||
maxWidth: "80%",
|
|
||||||
alignSelf: "flex-start",
|
|
||||||
boxShadow: "0 2px 5px rgba(0, 0, 0, 0.1)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p style={{ margin: 0, fontSize: "14px", color: "black",textAlign: "left" }}>{msg.note}</p>
|
|
||||||
{/* //Fetch the note IST time */}
|
|
||||||
<p style={{ margin: "6px 0 0", fontSize: "10px", color: "grey", textAlign: "right" }}>Internal team send by {new Date(Number(msg.time)*1000).toLocaleString('en-IN',{timeZone: 'Asia/Kolkata'})}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{ticketStatus?.toLowerCase()=== "resolved" && (<div style={{ textAlign: "center", fontSize: "14px", color: "red", marginTop: "10px" }}>
|
|
||||||
<p>----- Ended Chat ------</p>
|
|
||||||
</div>)}
|
|
||||||
{/* Fixed Close Button */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "sticky",
|
|
||||||
bottom: 0,
|
|
||||||
background: "white",
|
|
||||||
padding: "10px",
|
|
||||||
textAlign: "center",
|
|
||||||
boxShadow: "0px -5px 4px rgb(255, 255, 255)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button onClick={close}>Close</Button>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal opened={detailsOpened} onClose={closeDetails}>
|
|
||||||
{ticketDetails ? (
|
|
||||||
<Flex direction="column" gap="md">
|
|
||||||
<div><strong>Ticket ID:</strong> {ticketDetails.ticket_id}</div>
|
|
||||||
<div><strong>Category of Complaint:</strong> {ticketDetails.category_of_request}</div>
|
|
||||||
<div><strong>Description:</strong> {ticketDetails.nature_of_request}</div>
|
|
||||||
{/* <div><strong>Additional Information:</strong> {ticketDetails.additional_info}</div> */}
|
|
||||||
{/* Additional Info Table */}
|
|
||||||
<div><strong>Additional Info:</strong></div>
|
|
||||||
<Table withTableBorder withColumnBorders highlightOnHover>
|
|
||||||
<Table.Tbody>
|
|
||||||
{parseAdditionalInfo(ticketDetails.additional_info).map((item, index) => (
|
|
||||||
<Table.Tr key={index}>
|
|
||||||
<Table.Td>{item.key}</Table.Td>
|
|
||||||
<Table.Td>{item.value}</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
))}
|
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
<div><strong>Message:</strong> {ticketDetails.message}</div>
|
|
||||||
<div><strong>Created Date:</strong> {ticketDetails.created_date}</div>
|
|
||||||
<Button onClick={closeDetails} mt="sm">Close</Button>
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
<p>Loading details...</p>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,195 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import classes from '../page.module.css';
|
|
||||||
import React, { useContext, useEffect, useState } from "react";
|
|
||||||
import { TextInput, Button, Container, Title, Image, SimpleGrid, LoadingOverlay, Avatar, Paper, Text, Group, Center } from "@mantine/core";
|
|
||||||
import image from '../helpdesk.png';
|
|
||||||
import { useForm, SubmitHandler } from "react-hook-form";
|
|
||||||
import axios, { AxiosError } from "axios";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
|
||||||
|
|
||||||
|
|
||||||
type OtpInput = {
|
|
||||||
OTP: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result = {
|
|
||||||
ok?: boolean
|
|
||||||
message?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleResendOTP() {
|
|
||||||
let otp;
|
|
||||||
try {
|
|
||||||
const response = await axios.get("/api/otp");
|
|
||||||
otp = response.data;
|
|
||||||
console.log('OTP resent successfully:', response.data);
|
|
||||||
// Object.assign(response.data);
|
|
||||||
// alert(response.data.message);
|
|
||||||
notifications.show({
|
|
||||||
color: 'green',
|
|
||||||
// title: error.code,
|
|
||||||
message: response.data.message
|
|
||||||
})
|
|
||||||
} catch (error: AxiosError | any) {
|
|
||||||
notifications.show({
|
|
||||||
color: 'red',
|
|
||||||
title: error.code,
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return otp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function handleValidateOTP(OtpInput: OtpInput) {
|
|
||||||
let Result: Result = { ok: false }
|
|
||||||
try {
|
|
||||||
const response = await axios.post("/api/otp", OtpInput);
|
|
||||||
{
|
|
||||||
window.location.href = '/home'
|
|
||||||
}
|
|
||||||
Object.assign(Result, response.data);
|
|
||||||
} catch (error: AxiosError | any) {
|
|
||||||
// alert(error.response.data.error);
|
|
||||||
notifications.show({
|
|
||||||
withBorder: true,
|
|
||||||
color: 'red',
|
|
||||||
title: error.response.status,
|
|
||||||
message: error.response.data.error,
|
|
||||||
autoClose: 4000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// For countdown reset every time after successful verification
|
|
||||||
sessionStorage.removeItem('countdown');
|
|
||||||
sessionStorage.removeItem('timerStart');
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CheckOTP() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { register, handleSubmit, formState: { errors } } = useForm<OtpInput>();
|
|
||||||
const [countdown, setCountdown] = useState<number>(90);
|
|
||||||
const [timerActive, setTimerActive] = useState<boolean>(true);
|
|
||||||
|
|
||||||
const validateOTPMutation = useMutation<Result, AxiosError, OtpInput>({
|
|
||||||
mutationKey: ['validateOtp'],
|
|
||||||
mutationFn: handleValidateOTP,
|
|
||||||
})
|
|
||||||
const resendOTPMutation = useQuery({
|
|
||||||
queryKey: ['resendOTP'],
|
|
||||||
queryFn: handleResendOTP,
|
|
||||||
enabled: false,
|
|
||||||
})
|
|
||||||
////
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const savedCountdown = sessionStorage.getItem('countdown');
|
|
||||||
const savedStartTime = sessionStorage.getItem('timerStart');
|
|
||||||
|
|
||||||
if (savedCountdown && savedStartTime) {
|
|
||||||
const elapsedTime = Math.floor((Date.now() - parseInt(savedStartTime, 10)) / 1000);
|
|
||||||
const remainingTime = parseInt(savedCountdown, 10) - elapsedTime;
|
|
||||||
|
|
||||||
if (remainingTime > 0) {
|
|
||||||
setCountdown(remainingTime);
|
|
||||||
setTimerActive(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setCountdown(0);
|
|
||||||
setTimerActive(false);
|
|
||||||
sessionStorage.removeItem('countdown');
|
|
||||||
sessionStorage.removeItem('timerStart');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sessionStorage.setItem('countdown', '90');
|
|
||||||
sessionStorage.setItem('timerStart', Date.now().toString());
|
|
||||||
setCountdown(90);
|
|
||||||
setTimerActive(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Effect to manage the countdown timer
|
|
||||||
useEffect(() => {
|
|
||||||
if (!timerActive) return;
|
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setCountdown((prev) => {
|
|
||||||
if (prev <= 1) {
|
|
||||||
clearInterval(interval);
|
|
||||||
setTimerActive(false);
|
|
||||||
sessionStorage.removeItem('countdown');
|
|
||||||
sessionStorage.removeItem('timerStart');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return prev - 1;
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval); // Cleanup interval on unmount
|
|
||||||
}, [timerActive]);
|
|
||||||
/////
|
|
||||||
const onSubmit: SubmitHandler<OtpInput> = async (OtpInput) => {
|
|
||||||
const Result = await validateOTPMutation.mutateAsync(OtpInput);
|
|
||||||
if (Result.ok)
|
|
||||||
await queryClient.refetchQueries({ queryKey: ['OTP'] });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResend = async () => {
|
|
||||||
resendOTPMutation.refetch();
|
|
||||||
setCountdown(90);
|
|
||||||
setTimerActive(true);
|
|
||||||
sessionStorage.setItem('countdown', '90');
|
|
||||||
sessionStorage.setItem('timerStart', Date.now().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className={classes.root}>
|
|
||||||
<SimpleGrid spacing={{ base: 40, sm: 80 }} cols={{ base: 1, sm: 2 }}>
|
|
||||||
<Image src={image.src} className={classes.mobileImage} />
|
|
||||||
<div>
|
|
||||||
<LoadingOverlay visible={validateOTPMutation.isPending || validateOTPMutation.data?.ok || resendOTPMutation.isLoading} />
|
|
||||||
<Paper component='form' className={classes.form} radius={0} p={30} onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<Avatar
|
|
||||||
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR-ZljkQXsXUP1-EwnIPZtVq_JWhwGUW7b0_eSMno-bag&s"
|
|
||||||
alt="Jacob Warnhalter"
|
|
||||||
radius="xxxl"
|
|
||||||
/>
|
|
||||||
<Title order={2} className={classes.title} ta="center" mt="md" mb={50}>
|
|
||||||
Customer Request Management
|
|
||||||
</Title>
|
|
||||||
<TextInput label="Enter One-time password(OTP) *"
|
|
||||||
placeholder="e.g. XXXXX"
|
|
||||||
size="md"
|
|
||||||
maxLength={6}
|
|
||||||
{...register('OTP', { required: true })} />
|
|
||||||
{errors.OTP && <Text c='red'>*Required</Text>}
|
|
||||||
<Center mt="lg">
|
|
||||||
<Text >{timerActive ? `Resend OTP in ${countdown}s` : 'Resend OTP Available'} </Text>
|
|
||||||
</Center>
|
|
||||||
<Group justify="center">
|
|
||||||
<Button mt="xl" size="md" type='submit' >
|
|
||||||
Validate OTP
|
|
||||||
</Button>
|
|
||||||
{validateOTPMutation.data?.message && <Text c='red' ta='center' pt={30}>{validateOTPMutation.data.message}</Text>}
|
|
||||||
<Button mt="xl" size="md" type='submit' color='blue' onClick={handleResend} disabled={timerActive} >
|
|
||||||
Resend OTP
|
|
||||||
</Button>
|
|
||||||
{/* {resendOTPMutation?.data?.message && <Text c='red' ta='center' pt={30}>{resendOTPMutation.data.message}</Text>} */}
|
|
||||||
</Group>
|
|
||||||
</Paper>
|
|
||||||
</div>
|
|
||||||
<Image src={image.src} className={classes.desktopImage} />
|
|
||||||
</SimpleGrid>
|
|
||||||
<footer justify-content='center' color='green'>Copyright © 2025 Tata Consultancy Services, All rights reserved.</footer>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -7,12 +7,35 @@ import bgImage from '@/app/image/media.jpg';
|
|||||||
import frontPage from '@/app/image/ib_front_page.jpg'
|
import frontPage from '@/app/image/ib_front_page.jpg'
|
||||||
import NextImage from 'next/image';
|
import NextImage from 'next/image';
|
||||||
import { Providers } from "@/app/providers";
|
import { Providers } from "@/app/providers";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [CIF, SetCIF] = useState('');
|
const [CIF, SetCIF] = useState('');
|
||||||
const [psw, SetPsw] = useState('');
|
const [psw, SetPsw] = useState('');
|
||||||
async function handleLogin() {
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function handleLogin(e:React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
// const login_password =localStorage.setItem("password",psw);
|
||||||
|
if (CIF === "30022497139" && psw === "SecurePass123!")
|
||||||
|
{
|
||||||
|
const token ="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30";
|
||||||
|
localStorage.setItem("customerNumber",CIF);
|
||||||
|
localStorage.setItem("password",psw);
|
||||||
|
localStorage.setItem("access_token",token);
|
||||||
|
router.push("/home");
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
notifications.show({
|
||||||
|
withBorder: true,
|
||||||
|
color: 'red',
|
||||||
|
title: "Wrong User Id or Password",
|
||||||
|
message: "Wrong User Id or Password",
|
||||||
|
autoClose: 5000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -83,7 +106,7 @@ export default function Login() {
|
|||||||
<br></br>
|
<br></br>
|
||||||
<Box style={{ width: "370px", padding: "20px", border: "1px solid #e0e0d1", marginLeft: 30 }}>
|
<Box style={{ width: "370px", padding: "20px", border: "1px solid #e0e0d1", marginLeft: 30 }}>
|
||||||
<Flex justify="center" gap="md" >
|
<Flex justify="center" gap="md" >
|
||||||
<form>
|
<form onSubmit={handleLogin}>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="User ID"
|
label="User ID"
|
||||||
placeholder="Enter your CIF no"
|
placeholder="Enter your CIF no"
|
||||||
@@ -93,11 +116,10 @@ export default function Login() {
|
|||||||
pattern="\d*"
|
pattern="\d*"
|
||||||
value={CIF}
|
value={CIF}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const input =e.currentTarget.value.replace(/\D/g,'');
|
const input = e.currentTarget.value.replace(/\D/g, '');
|
||||||
if(input.length<=11)
|
if (input.length <= 11) {
|
||||||
{
|
SetCIF(input);
|
||||||
SetCIF(input);
|
}
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
@@ -107,11 +129,13 @@ export default function Login() {
|
|||||||
<PasswordInput
|
<PasswordInput
|
||||||
label="Password"
|
label="Password"
|
||||||
placeholder="Enter your password"
|
placeholder="Enter your password"
|
||||||
|
value={psw}
|
||||||
|
onChange={(e) => SetPsw(e.currentTarget.value)}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{/* <br></br> */}
|
{/* <br></br> */}
|
||||||
<Button fullWidth variant="filled" mt="md" style={{ width: 100, align: "center", marginLeft: 40 }}>Login</Button>
|
<Button type="submit" fullWidth variant="filled" mt="md" style={{ width: 100, align: "center", marginLeft: 40 }}>Login</Button>
|
||||||
</form>
|
</form>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
|
Reference in New Issue
Block a user