Merge branch 'master' of https://git.sentientgeeks.us/Amit/mui-demo-project
This commit is contained in:
commit
b8523bea88
12
package.json
12
package.json
@ -12,23 +12,33 @@
|
|||||||
"@emotion/cache": "^11.13.1",
|
"@emotion/cache": "^11.13.1",
|
||||||
"@emotion/react": "^11.13.0",
|
"@emotion/react": "^11.13.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
|
"@hookform/resolvers": "^3.9.0",
|
||||||
"@mui/icons-material": "^5.16.6",
|
"@mui/icons-material": "^5.16.6",
|
||||||
"@mui/material": "^5.16.6",
|
"@mui/material": "^5.16.6",
|
||||||
"@mui/material-nextjs": "^5.16.6",
|
"@mui/material-nextjs": "^5.16.6",
|
||||||
|
"@reduxjs/toolkit": "^2.2.7",
|
||||||
"@tanstack/react-table": "^8.20.1",
|
"@tanstack/react-table": "^8.20.1",
|
||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "14.2.5",
|
"next": "14.2.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"sass": "^1.77.8"
|
"react-hook-form": "^7.52.2",
|
||||||
|
"react-redux": "^9.1.2",
|
||||||
|
"sass": "^1.77.8",
|
||||||
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^6.4.8",
|
||||||
|
"@testing-library/react": "^16.0.0",
|
||||||
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"axios-mock-adapter": "^2.0.0",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.5",
|
"eslint-config-next": "14.2.5",
|
||||||
|
"jest": "^29.7.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
"use client"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styles from "./loginPage.module.scss";
|
import styles from "./loginPage.module.scss";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
@ -7,8 +8,46 @@ import { UTILITY_CONSTANT } from "@/utilities/utilityConstant";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import CustomizedInputsStyled from "@/ui/CustomizedInputsStyled";
|
import CustomizedInputsStyled from "@/ui/CustomizedInputsStyled";
|
||||||
import CustomizedButtons from "@/ui/customizedButtons";
|
import CustomizedButtons from "@/ui/customizedButtons";
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { loginApi } from "@/services/api/loginApi";
|
||||||
|
import { FormControl } from '@mui/material';
|
||||||
|
import CustomTextField from "@/ui/CustomTextField";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { RootState } from "@/services/store";
|
||||||
|
import { setAuthTokens, setUserDetails } from "@/services/store/authSlice";
|
||||||
|
|
||||||
|
const loginSchema = z.object({
|
||||||
|
username: z.string(),
|
||||||
|
password: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type LoginFormValues = z.infer<typeof loginSchema>;
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const { register, handleSubmit, formState: { errors }, control } = useForm<LoginFormValues>({
|
||||||
|
resolver: zodResolver(loginSchema),
|
||||||
|
});
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const onSubmit = async (data: LoginFormValues) => {
|
||||||
|
try {
|
||||||
|
const response = await loginApi(data);
|
||||||
|
localStorage.setItem('token', response.token);
|
||||||
|
localStorage.setItem('refreshToken', response.refreshToken);
|
||||||
|
dispatch(setAuthTokens({ token: response.token, refreshToken: response.refreshToken }));
|
||||||
|
dispatch(setUserDetails(response));
|
||||||
|
|
||||||
|
router.push('/home');
|
||||||
|
} catch (err) {
|
||||||
|
setError('Invalid credentials');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={styles.loginPage}>
|
<main className={styles.loginPage}>
|
||||||
<Grid container sx={{ height: "100%" }}>
|
<Grid container sx={{ height: "100%" }}>
|
||||||
@ -40,7 +79,7 @@ export default function LoginPage() {
|
|||||||
paddingBlock={5}
|
paddingBlock={5}
|
||||||
>
|
>
|
||||||
<img src={UTILITY_CONSTANT.IMAGES.AUTH.HEADER_LOG} alt="logo" />
|
<img src={UTILITY_CONSTANT.IMAGES.AUTH.HEADER_LOG} alt="logo" />
|
||||||
<Box width={"100%"}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Typography variant="h4">Welcome to Convexsol</Typography>
|
<Typography variant="h4">Welcome to Convexsol</Typography>
|
||||||
<Typography variant="h4">Sign in to your account</Typography>
|
<Typography variant="h4">Sign in to your account</Typography>
|
||||||
<Box
|
<Box
|
||||||
@ -51,17 +90,26 @@ export default function LoginPage() {
|
|||||||
flexDirection={"column"}
|
flexDirection={"column"}
|
||||||
gap={3}
|
gap={3}
|
||||||
>
|
>
|
||||||
<CustomizedInputsStyled label="User Name" type="text" />
|
<CustomTextField
|
||||||
<CustomizedInputsStyled
|
name="username"
|
||||||
|
control={control}
|
||||||
|
label="Username"
|
||||||
|
error={!!errors.username}
|
||||||
|
helperText={errors.username?.message}
|
||||||
|
/>
|
||||||
|
<CustomTextField
|
||||||
|
name="password"
|
||||||
|
control={control}
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
endAdornmentBoolean
|
error={!!errors.password}
|
||||||
|
helperText={errors.password?.message}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width={"100%"} marginBlockStart={"20px"}>
|
<Box width={"100%"} marginBlockStart={"20px"}>
|
||||||
<CustomizedButtons label={"Sign In"} />
|
<CustomizedButtons btnType="submit" label={"Sign In"} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</form>
|
||||||
<Box
|
<Box
|
||||||
width={"100%"}
|
width={"100%"}
|
||||||
display={"flex"}
|
display={"flex"}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
"use client"
|
||||||
import DashboardWrapper from "@/components/wrapper/dashboardWrapper";
|
import DashboardWrapper from "@/components/wrapper/dashboardWrapper";
|
||||||
|
import AuthGuard from "@/hoc/authGuard/authGuard";
|
||||||
|
|
||||||
interface RootLayoutProps {
|
interface RootLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout(props: RootLayoutProps): JSX.Element {
|
function RootLayout(props: RootLayoutProps): JSX.Element {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
return <DashboardWrapper>{children}</DashboardWrapper>;
|
return <DashboardWrapper>{children}</DashboardWrapper>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AuthGuard(RootLayout);
|
@ -1,7 +1,10 @@
|
|||||||
|
"use client"
|
||||||
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
|
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
import theme from "../theme";
|
import theme from "../theme";
|
||||||
import "./globals.scss";
|
import "./globals.scss";
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import store from "@/services/store";
|
||||||
|
|
||||||
interface RootLayoutProps {
|
interface RootLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -12,9 +15,11 @@ export default function RootLayout(props: RootLayoutProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<AppRouterCacheProvider>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
<AppRouterCacheProvider>
|
||||||
</AppRouterCacheProvider>
|
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
||||||
|
</AppRouterCacheProvider>
|
||||||
|
</Provider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"use client"; // This ensures the file is a client component
|
"use client"; // This ensures the file is a client component
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import AppBar, { AppBarProps } from "@mui/material/AppBar";
|
import AppBar, { AppBarProps } from "@mui/material/AppBar";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
@ -12,7 +11,6 @@ import ListItem from "@mui/material/ListItem";
|
|||||||
import ListItemButton from "@mui/material/ListItemButton";
|
import ListItemButton from "@mui/material/ListItemButton";
|
||||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||||
import ListItemText from "@mui/material/ListItemText";
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
import MailIcon from "@mui/icons-material/Mail";
|
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
@ -22,6 +20,7 @@ import styles from "./DashboardWrapper.module.scss";
|
|||||||
import styled from "@emotion/styled";
|
import styled from "@emotion/styled";
|
||||||
import { BUILDING, GEAR } from "@/utilities/svgConstant";
|
import { BUILDING, GEAR } from "@/utilities/svgConstant";
|
||||||
import LogoutIcon from "@mui/icons-material/Logout";
|
import LogoutIcon from "@mui/icons-material/Logout";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
const drawerWidth = 260;
|
const drawerWidth = 260;
|
||||||
|
|
||||||
@ -75,7 +74,7 @@ export default function DashboardWrapper(props: Props) {
|
|||||||
const { children: Children } = props;
|
const { children: Children } = props;
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
|
const router = useRouter()
|
||||||
const handleDrawerClose = () => {
|
const handleDrawerClose = () => {
|
||||||
setIsClosing(true);
|
setIsClosing(true);
|
||||||
setMobileOpen(false);
|
setMobileOpen(false);
|
||||||
@ -125,6 +124,12 @@ export default function DashboardWrapper(props: Props) {
|
|||||||
const container =
|
const container =
|
||||||
window !== undefined ? () => window().document.body : undefined;
|
window !== undefined ? () => window().document.body : undefined;
|
||||||
|
|
||||||
|
const logoutHandler = () => {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
router.push('/log-in');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{ display: "flex" }}
|
sx={{ display: "flex" }}
|
||||||
@ -176,15 +181,8 @@ export default function DashboardWrapper(props: Props) {
|
|||||||
alt="Remy Sharp"
|
alt="Remy Sharp"
|
||||||
src="/static/images/avatar/1.jpg"
|
src="/static/images/avatar/1.jpg"
|
||||||
/>
|
/>
|
||||||
{/* <GEAR
|
|
||||||
sx={{
|
|
||||||
color: "var(--primary_Active_text)",
|
|
||||||
fontSize: "1.8rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
<IconButton aria-label="log out" size="medium" color="error">
|
<IconButton aria-label="log out" size="medium" color="error">
|
||||||
<LogoutIcon fontSize="small" />
|
<LogoutIcon fontSize="small" onClick={logoutHandler} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
31
src/hoc/authGuard/authGuard.tsx
Normal file
31
src/hoc/authGuard/authGuard.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
// hoc/withAuth.tsx
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const AuthGuard = (WrappedComponent: any) => {
|
||||||
|
const AuthGuardComponent = (props: any) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
router.push('/log-in');
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
return <WrappedComponent {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set display name for the component
|
||||||
|
AuthGuardComponent.displayName = `AuthGuard(${getDisplayName(WrappedComponent)})`;
|
||||||
|
|
||||||
|
return AuthGuardComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthGuard;
|
||||||
|
|
||||||
|
// Helper function to get the display name of a component
|
||||||
|
function getDisplayName(WrappedComponent: any) {
|
||||||
|
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
||||||
|
}
|
24
src/services/api/loginApi.ts
Normal file
24
src/services/api/loginApi.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// api.ts
|
||||||
|
import axiosInstance from '../axios/axiosInstance';
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
gender: string;
|
||||||
|
image: string;
|
||||||
|
token: string;
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoginData {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loginApi = async (data: LoginData): Promise<LoginResponse> => {
|
||||||
|
const response = await axiosInstance.post<LoginResponse>('/auth/login', data);
|
||||||
|
return response.data;
|
||||||
|
};
|
88
src/services/axios/axiosInstance.ts
Normal file
88
src/services/axios/axiosInstance.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// axiosInstance.ts
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let isRefreshing = false;
|
||||||
|
let failedQueue: any[] = [];
|
||||||
|
|
||||||
|
const processQueue = (error: any, token: string | null = null) => {
|
||||||
|
failedQueue.forEach((prom) => {
|
||||||
|
if (token) {
|
||||||
|
prom.resolve(token);
|
||||||
|
} else {
|
||||||
|
prom.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
failedQueue = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error)
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosInstance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
async (error) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
if (error.response.status === 401 && !originalRequest._retry) {
|
||||||
|
if (isRefreshing) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
failedQueue.push({ resolve, reject });
|
||||||
|
})
|
||||||
|
.then((token) => {
|
||||||
|
originalRequest.headers['Authorization'] = 'Bearer ' + token;
|
||||||
|
return axiosInstance(originalRequest);
|
||||||
|
})
|
||||||
|
.catch((err) => Promise.reject(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRequest._retry = true;
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken');
|
||||||
|
if (!refreshToken) {
|
||||||
|
window.location.href = '/log-in';
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/auth/refresh`,
|
||||||
|
{ token: refreshToken }
|
||||||
|
);
|
||||||
|
localStorage.setItem('token', data.token);
|
||||||
|
localStorage.setItem('refreshToken', data.refreshToken);
|
||||||
|
axiosInstance.defaults.headers['Authorization'] =
|
||||||
|
'Bearer ' + data.token;
|
||||||
|
processQueue(null, data.token);
|
||||||
|
return axiosInstance(originalRequest);
|
||||||
|
} catch (err) {
|
||||||
|
processQueue(err, null);
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
window.location.href = '/login';
|
||||||
|
return Promise.reject(err);
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default axiosInstance;
|
57
src/services/store/authSlice.ts
Normal file
57
src/services/store/authSlice.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// store/slices/authSlice.ts
|
||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { LoginResponse } from '../api/loginApi';
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
token: string | null;
|
||||||
|
refreshToken: string | null;
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
gender: string;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: AuthState = {
|
||||||
|
token: null,
|
||||||
|
refreshToken: null,
|
||||||
|
id: 0,
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
gender: '',
|
||||||
|
image: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const authSlice = createSlice({
|
||||||
|
name: 'auth',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setAuthTokens: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ token: string; refreshToken: string }>
|
||||||
|
) => {
|
||||||
|
state.token = action.payload.token;
|
||||||
|
state.refreshToken = action.payload.refreshToken;
|
||||||
|
localStorage.setItem('token', action.payload.token);
|
||||||
|
localStorage.setItem('refreshToken', action.payload.refreshToken);
|
||||||
|
},
|
||||||
|
clearAuthTokens: (state) => {
|
||||||
|
state.token = null;
|
||||||
|
state.refreshToken = null;
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
},
|
||||||
|
setUserDetails: (state, action: PayloadAction<LoginResponse>) => {
|
||||||
|
state = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setAuthTokens, clearAuthTokens, setUserDetails } =
|
||||||
|
authSlice.actions;
|
||||||
|
|
||||||
|
export default authSlice.reducer;
|
15
src/services/store/index.tsx
Normal file
15
src/services/store/index.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
// store/index.ts
|
||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import authReducer from './authSlice';
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
auth: authReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
||||||
|
export default store;
|
58
src/ui/CustomTextField.tsx
Normal file
58
src/ui/CustomTextField.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// components/CustomTextField.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import { Controller } from 'react-hook-form';
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
|
||||||
|
interface CustomTextFieldProps {
|
||||||
|
name: string;
|
||||||
|
control: any;
|
||||||
|
label: string;
|
||||||
|
type?: string;
|
||||||
|
error?: boolean;
|
||||||
|
helperText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define your styled component
|
||||||
|
const CssTextField = styled(TextField)({
|
||||||
|
width: "100%",
|
||||||
|
"& label.Mui-focused": {
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
"& fieldset": {
|
||||||
|
borderColor: "var(--input-border-default)",
|
||||||
|
},
|
||||||
|
"&:hover fieldset": {
|
||||||
|
borderColor: "var(--input-border-hover)",
|
||||||
|
},
|
||||||
|
"&.Mui-focused fieldset": {
|
||||||
|
borderColor: "var(--primary)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const CustomTextField: React.FC<CustomTextFieldProps> = ({ name, control, label, type = 'text', error, helperText }) => {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={name}
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<CssTextField
|
||||||
|
{...field}
|
||||||
|
label={label}
|
||||||
|
type={type}
|
||||||
|
error={error}
|
||||||
|
helperText={helperText}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomTextField;
|
@ -47,6 +47,7 @@ export default function CustomizedInputsStyled({
|
|||||||
type={type}
|
type={type}
|
||||||
id="custom-css-outlined-input"
|
id="custom-css-outlined-input"
|
||||||
InputProps={{ endAdornment: endAdornmentBoolean && <VisibilityOff /> }}
|
InputProps={{ endAdornment: endAdornmentBoolean && <VisibilityOff /> }}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -16,12 +16,14 @@ const ColorButton = styled(Button)<ButtonProps>(() => ({
|
|||||||
type InputType = {
|
type InputType = {
|
||||||
label: string;
|
label: string;
|
||||||
statIcon?: React.ReactNode;
|
statIcon?: React.ReactNode;
|
||||||
|
onPress?: () => void;
|
||||||
|
btnType?: "submit" | "button";
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CustomizedButtons({ label, statIcon }: InputType) {
|
export default function CustomizedButtons({ label, statIcon, onPress, btnType = "button" }: InputType) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ColorButton variant="contained">
|
<ColorButton type={btnType} onClick={onPress} variant="contained">
|
||||||
{statIcon && statIcon}
|
{statIcon && statIcon}
|
||||||
{label}
|
{label}
|
||||||
</ColorButton>
|
</ColorButton>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user