This commit is contained in:
YasinShaikh123
2025-02-10 12:29:50 +05:30
12 changed files with 348 additions and 58 deletions

View File

@@ -5,7 +5,7 @@ import GlobalStateContext from './GlobalStateContext';
const GlobalStateProvider = ({ children }:{children:ReactNode}) => {
const [isAuthenticate, setIsAuthenticate] = useState<boolean>(true);
const [isAuthenticate, setIsAuthenticate] = useState<boolean>(false);
return (
<GlobalStateContext.Provider value={{ isAuthenticate, setIsAuthenticate }}>

View File

@@ -3,32 +3,29 @@ import {
createListCollection,
Heading,
HStack,
Stack,
Status,
Tabs,
Text,
} from "@chakra-ui/react";
import MainFrame from "../../components/MainFrame";
import BarChart from "../../components/Charts/BarChart";
import {
SelectContent,
SelectItem,
SelectLabel,
SelectRoot,
SelectTrigger,
SelectValueText,
} from "../../components/ui/select";
import CircularApp from "../../components/Charts/CircularProgress";
import SemiDoughnutChart from "../../components/Charts/SemiDoughnutChart";
import { Button } from "../../components/ui/button";
import MainFrame from "../../components/MainFrame";
import {
AccordionItem,
AccordionItemContent,
AccordionItemTrigger,
AccordionRoot,
} from "../../components/ui/accordion";
import { Button } from "../../components/ui/button";
import {
SelectContent,
SelectItem,
SelectRoot,
SelectTrigger,
SelectValueText
} from "../../components/ui/select";
import AgencyName from "./AgencyName";
import CircularProgress from "../../components/Charts/CircularProgress";
import CircularApp from "../../components/Charts/CircularProgress";
const Dashboard = () => {
const frameworks = createListCollection({

View File

@@ -1,18 +1,22 @@
import { Center, HStack, Image, Input, Text, VStack } from "@chakra-ui/react"
import axios from "axios"
import { useContext, useState } from "react"
import { useForm } from "react-hook-form"
import { useDispatch } from "react-redux"
import GlobalStateContext from "../Contexts/GlobalStateContext"
import { setToken } from "../Redux/Service/authSlice"
import logo from '../assets/logo.svg'
import { Button } from "../components/ui/button"
import { Field } from "../components/ui/field"
import { Toaster, toaster } from "../components/ui/toaster"
import { Toaster } from "../components/ui/toaster"
interface FormValues {
mobileNumber: number
password: string
}
const Login = () => {
const dispatch = useDispatch()
const [isLoading, setIsLoading] = useState<boolean>(false)
const context = useContext(GlobalStateContext);
if (!context) {
@@ -26,24 +30,27 @@ const Login = () => {
} = useForm<FormValues>()
const onSubmit = handleSubmit((data) => {
const onSubmit = handleSubmit(async (data) => {
setIsLoading(true)
if (data?.mobileNumber === 1234567890) {
setTimeout(() => {
setIsAuthenticate(true);
setIsLoading(false)
}, 3000); // 3-second delay
try {
const response = await axios.post(`${import.meta.env.VITE_API_URL}/v1/login`, {
mobile_number: data.mobileNumber,
password: data.password,
});
console.log('====================================');
console.log(response);
console.log('====================================');
dispatch(setToken(String(response.data["access-token"])));
} else {
toaster.create({
title: `Invalid Credentials`,
type: "error",
})
setIsLoading(false)
} catch (error) {
console.error("Login failed", error);
}
});
return (
@@ -79,7 +86,11 @@ const Login = () => {
<Input ps={3} {...register("mobileNumber", { required: "Mobile Number address is required" })} placeholder="Mobile Number Address" />
{/* <Text as={'span'} w={'100%'} fontSize={'xs'} fontWeight={'normal'} color={'#686677'}>Forget password</Text> */}
</Field>
<Button loading={isLoading} mt={4} size={'sm'} bg={'#02A0A0'} rounded={'md'} w={'100%'} color={'#ffffff'} type="submit">Send OTP</Button>
<Field color={'#313039'} label={'Enter Mobile Number'} w={'100%'} invalid={!!errors.password} errorText={errors.password?.message} >
<Input ps={3} {...register("password", { required: "Pasword is required" })} type="password" placeholder="Enter password" />
{/* <Text as={'span'} w={'100%'} fontSize={'xs'} fontWeight={'normal'} color={'#686677'}>Forget password</Text> */}
</Field>
<Button loading={isLoading} mt={4} size={'sm'} bg={'#02A0A0'} rounded={'md'} w={'100%'} color={'#ffffff'} type="submit">Login</Button>
<Text>Forgot password</Text>
</VStack>

View File

@@ -0,0 +1,55 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { BaseQueryFn, FetchArgs, FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { logout } from "./authSlice"; // Import logout action from authSlice
import { RootState } from "../Store";
const baseQuery = fetchBaseQuery({
baseUrl: "https://api.example.com",
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token; // Get token from Redux store
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
headers.set("Content-Type", "application/json");
return headers;
},
});
// ✅ Handle 401 Errors (Auto Logout)
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
const result = await baseQuery(args, api, extraOptions);
if (result.error && result.error.status === 401) {
api.dispatch(logout()); // Logout user on 401 error
}
return result;
};
export const apiSlice = createApi({
reducerPath: "api",
baseQuery: baseQueryWithReauth, // Use enhanced baseQuery with error handling
endpoints: (builder) => ({
getPosts: builder.query<Post[], void>({ query: () => "/posts" }),
}),
});
export const { useGetPostsQuery } = apiSlice;
export type Post = {
id: number;
title: string;
body: string;
};

View File

@@ -0,0 +1,27 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type AuthState = {
token: string | null;
};
const initialState: AuthState = {
token: localStorage.getItem("token"), // Load token from localStorage
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setToken: (state, action: PayloadAction<string>) => {
state.token = action.payload;
localStorage.setItem("token", action.payload);
},
logout: (state) => {
state.token = null;
localStorage.removeItem("token");
},
},
});
export const { setToken, logout } = authSlice.actions;
export default authSlice.reducer;

15
src/Redux/Store.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { configureStore } from "@reduxjs/toolkit";
import { apiSlice } from "./Service/apiSlice";
import authReducer from "./Service/authSlice"
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
auth: authReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

View File

@@ -1,4 +1,4 @@
import { Box, Text, VStack } from "@chakra-ui/react"
import { Box, VStack } from "@chakra-ui/react"
import { motion } from "framer-motion"
import React, { FC } from "react"
import { OPACITY_ON_LOAD } from "../Layouts/animations"
@@ -11,7 +11,7 @@ interface MainFrameProps {
title?: string
}
const MainFrame: FC<MainFrameProps> = ({ children, title }) => {
const MainFrame: FC<MainFrameProps> = ({ children }) => {
return (
<MotionVStack {...OPACITY_ON_LOAD} w="100%" h="90%" p={0} pb={0}>
<Box

View File

@@ -1,17 +1,23 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { Provider as ReduxProvider } from "react-redux";
import { Provider } from './components/ui/provider'
import GlobalStateProvider from './Contexts/GlobalStateProvider'
import './index.css'
import { Theme } from '@chakra-ui/react'
import { store } from './Redux/Store'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<GlobalStateProvider>
<Provider>
<App />
</Provider>
</GlobalStateProvider>
</React.StrictMode>,
<React.StrictMode>
<ReduxProvider store={store}> {/* ✅ Wrap with Redux Provider */}
<GlobalStateProvider>
<Provider> {/* ✅ Wrap with Provider */}
<Theme appearance='light'>
<App />
</Theme>
</Provider>
</GlobalStateProvider>
</ReduxProvider>
</React.StrictMode>
)