@@ -8,13 +8,15 @@ import { useTranslation } from "react-i18next"; | |||
import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | |||
import { Check, Close, Error } from "@mui/icons-material"; | |||
import ChagnePasswordForm from "./ChangePasswordForm"; | |||
import { passwordRule } from "@/app/api/user"; | |||
// interface Props { | |||
// // auth?: auth[] | |||
// // users?: UserResult[] | |||
// } | |||
export interface PasswordRulesProps { | |||
pwRules: passwordRule | |||
} | |||
const ChangePassword: React.FC = () => { | |||
const ChangePassword: React.FC<PasswordRulesProps> = ({ | |||
pwRules | |||
}) => { | |||
const formProps = useForm<PasswordInputs>(); | |||
const [serverError, setServerError] = useState(""); | |||
const router = useRouter(); | |||
@@ -30,19 +32,23 @@ const ChangePassword: React.FC = () => { | |||
if (data.newPassword.length < 8 || data.newPassword.length > 20) { | |||
haveError = true | |||
formProps.setError("newPassword", { message: "The password requires 8-20 characters", type: "required" }) | |||
formProps.setError("newPassword", { type: "required" }) | |||
// formProps.setError("newPassword", { message: "The password requires 8-20 characters", type: "required" }) | |||
} | |||
if (!regex.test(data.newPassword)) { | |||
haveError = true | |||
formProps.setError("newPassword", { message: "A combination of uppercase letters, lowercase letters, numbers, and symbols is required.", type: "required" }) | |||
formProps.setError("newPassword", { type: "required" }) | |||
// formProps.setError("newPassword", { message: "A combination of uppercase letters, lowercase letters, numbers, and symbols is required.", type: "required" }) | |||
} | |||
if (data.password == data.newPassword) { | |||
haveError = true | |||
formProps.setError("newPassword", { message: "The new password cannot be the same as the old password", type: "required" }) | |||
formProps.setError("newPassword", { type: "required" }) | |||
// formProps.setError("newPassword", { message: "The new password cannot be the same as the old password", type: "required" }) | |||
} | |||
if (data.newPassword != data.newPasswordCheck) { | |||
haveError = true | |||
formProps.setError("newPassword", { message: "The new password has to be the same as the new password", type: "required" }) | |||
formProps.setError("newPassword", { type: "required" }) | |||
// formProps.setError("newPassword", { message: "The new password has to be the same as the new password", type: "required" }) | |||
formProps.setError("newPasswordCheck", { message: "The new password has to be the same as the new password", type: "required" }) | |||
} | |||
if (haveError) { | |||
@@ -80,7 +86,7 @@ const ChangePassword: React.FC = () => { | |||
component="form" | |||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||
> | |||
<ChagnePasswordForm /> | |||
<ChagnePasswordForm pwRules={pwRules}/> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
variant="outlined" | |||
@@ -11,11 +11,20 @@ import { useTranslation } from "react-i18next"; | |||
import { useCallback, useState } from "react"; | |||
import { PasswordInputs } from "@/app/api/user/actions"; | |||
import { Visibility, VisibilityOff } from "@mui/icons-material"; | |||
import { IconButton, InputAdornment } from "@mui/material"; | |||
import { | |||
FormHelperText, | |||
IconButton, | |||
InputAdornment, | |||
createTheme, | |||
} from "@mui/material"; | |||
import { ThemeProvider } from "../../../node_modules/@mui/system/index"; | |||
import { PW_RULE_THEME as defaultTheme } from "@/theme/colorConst"; | |||
import Check from "@mui/icons-material/Check"; | |||
import Clear from "@mui/icons-material/Clear"; | |||
import { PasswordRulesProps } from "./ChangePassword"; | |||
const ChagnePasswordForm: React.FC = () => { | |||
const ChagnePasswordForm: React.FC<PasswordRulesProps> = ({ pwRules:rules }) => { | |||
const { t } = useTranslation("changePassword"); | |||
const [showNewPassword, setShowNewPassword] = useState(false); | |||
const handleClickShowNewPassword = () => setShowNewPassword(!showNewPassword); | |||
const handleMouseDownNewPassword = () => setShowNewPassword(!showNewPassword); | |||
@@ -24,6 +33,30 @@ const ChagnePasswordForm: React.FC = () => { | |||
const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
const [helperTextColors, setHelperTextColors] = useState<string[]>([ | |||
"red", | |||
"green", | |||
"blue", | |||
]); | |||
const [inputValue, setInputValue] = useState(""); | |||
const [theme, setTheme] = | |||
useState<ReturnType<typeof createTheme>>(defaultTheme); | |||
const pwlengthString = `${rules.min || 0}-${rules.max || " "} characters` | |||
let msgList = [ | |||
pwlengthString, | |||
] | |||
switch (true) { | |||
case rules.upperEng: | |||
msgList.push("Uppercase letters") | |||
case rules.lowerEng: | |||
msgList.push("Lowercase letters") | |||
case rules.number: | |||
msgList.push("Numbers") | |||
case rules.specialChar: | |||
msgList.push("Symbols") | |||
default: | |||
break | |||
} | |||
const { | |||
register, | |||
formState: { errors, defaultValues }, | |||
@@ -31,8 +64,62 @@ const ChagnePasswordForm: React.FC = () => { | |||
reset, | |||
resetField, | |||
setValue, | |||
setError | |||
} = useFormContext<PasswordInputs>(); | |||
const getColorFromInput = ( | |||
inputValue: string, | |||
helperTextLines: string[] | |||
): string[] => { | |||
// Determine the color for each line based on the input value | |||
return helperTextLines.map((_, index) => { | |||
if ((index === 0 && inputValue.length < rules.min) || inputValue.length > rules.max) { | |||
// setError("newPassword", { type: "required" }) | |||
return "red"; | |||
} else if (index === 1 && rules.upperEng &&!/[A-Z]/.test(inputValue)) { | |||
//testing for uppercase letters | |||
return "red"; | |||
} else if (index === 2 && rules.lowerEng && !/[a-z]/.test(inputValue)) { | |||
//testing for lowercase letters | |||
return "red"; | |||
} else if (index === 3 && rules.number && !/[0-9]/.test(inputValue)) { | |||
//testing for numbers | |||
return "red"; | |||
} else if (index === 4 && rules.specialChar && !/[!@#$%^&*(),.?":{}|<>]/.test(inputValue)) { | |||
//testing for special characters | |||
return "red"; | |||
} else { | |||
return "green"; | |||
} | |||
}); | |||
}; | |||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
// Update the theme with the new color based on the input value | |||
const newInputValue = event.target.value; | |||
setInputValue(newInputValue); | |||
const newTheme = createTheme({ | |||
...theme, | |||
components: { | |||
MuiFormHelperText: { | |||
styleOverrides: { | |||
root: { | |||
color: getColorFromInput(newInputValue, msgList), | |||
fontFamily: "Roboto", | |||
"& .icon": { | |||
alignItems: "center", | |||
AlignHorizontalCenter: "center", | |||
marginRight: "4px", | |||
fontSize: "1rem", // Adjust the font size as needed | |||
}, | |||
}, | |||
}, | |||
}, | |||
}, | |||
}); | |||
setTheme(newTheme); | |||
}; | |||
return ( | |||
<Card sx={{ display: "block" }}> | |||
<CardContent component={Stack} spacing={4}> | |||
@@ -57,7 +144,7 @@ const ChagnePasswordForm: React.FC = () => { | |||
{showPassword ? <Visibility /> : <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
) | |||
), | |||
}} | |||
{...register("password", { | |||
required: true, | |||
@@ -73,39 +160,72 @@ const ChagnePasswordForm: React.FC = () => { | |||
</Grid> | |||
<Grid item xs={6} /> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("Input New Password")} | |||
fullWidth | |||
type={showNewPassword ? "text" : "password"} | |||
InputProps={{ | |||
endAdornment: ( | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowNewPassword} | |||
onMouseDown={handleMouseDownNewPassword} | |||
> | |||
{showNewPassword ? <Visibility /> : <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
) | |||
}} | |||
{...register("newPassword")} | |||
error={Boolean(errors.newPassword)} | |||
helperText={ | |||
Boolean(errors.newPassword) && | |||
(errors.newPassword?.message | |||
? t(errors.newPassword.message) | |||
: t("Please input correct newPassword")) | |||
} | |||
/> | |||
<ThemeProvider theme={theme}> | |||
<TextField | |||
label={t("Input New Password")} | |||
fullWidth | |||
type={showNewPassword ? "text" : "password"} | |||
InputProps={{ | |||
endAdornment: ( | |||
<InputAdornment position="end"> | |||
<IconButton | |||
aria-label="toggle password visibility" | |||
onClick={handleClickShowNewPassword} | |||
onMouseDown={handleMouseDownNewPassword} | |||
> | |||
{showNewPassword ? <Visibility /> : <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
), | |||
}} | |||
{...register("newPassword")} | |||
onChange={handleInputChange} | |||
error={Boolean(errors.newPassword)} | |||
// error={Boolean(errors.newPassword)} | |||
helperText={msgList} | |||
FormHelperTextProps={{ | |||
component: (props) => { | |||
return ( | |||
<FormHelperText {...props}> | |||
{(props.children as string[]).map((line, index) => ( | |||
<span | |||
key={index} | |||
style={{ | |||
color: getColorFromInput(inputValue, msgList)[ | |||
index | |||
], | |||
}} | |||
> | |||
{getColorFromInput(inputValue, msgList)[index] === | |||
"red" ? ( | |||
<Clear className="icon" /> | |||
) : ( | |||
<Check className="icon" color="success" /> | |||
)} | |||
{line} | |||
{index < | |||
(props.children as string[]).length - 1 && ( | |||
<br /> | |||
)} | |||
</span> | |||
))} | |||
</FormHelperText> | |||
); | |||
}, | |||
}} | |||
// (Boolean(errors.newPassword) && | |||
// (errors.newPassword?.message | |||
// ? t(errors.newPassword.message) | |||
// : t("Please input correct newPassword"))) | |||
/> | |||
</ThemeProvider> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("Input New Password Again")} | |||
fullWidth | |||
type={showNewPassword ? "text" : "password"} | |||
InputProps={{ | |||
InputProps={{ | |||
endAdornment: ( | |||
<InputAdornment position="end"> | |||
<IconButton | |||
@@ -116,7 +236,7 @@ const ChagnePasswordForm: React.FC = () => { | |||
{showNewPassword ? <Visibility /> : <VisibilityOff />} | |||
</IconButton> | |||
</InputAdornment> | |||
) | |||
), | |||
}} | |||
{...register("newPasswordCheck")} | |||
error={Boolean(errors.newPassword)} | |||
@@ -1,14 +1,16 @@ | |||
import React from "react"; | |||
import ChangePasswordLoading from "./ChangePasswordLoading"; | |||
import ChangePassword from "./ChangePassword"; | |||
import { fetchPwRules } from "@/app/api/user"; | |||
interface SubComponents { | |||
Loading: typeof ChangePasswordLoading; | |||
} | |||
const ChangePasswordWrapper: React.FC & SubComponents = async () => { | |||
return <ChangePassword />; | |||
const pwRules = await fetchPwRules() | |||
console.log(pwRules) | |||
return <ChangePassword pwRules={pwRules}/>; | |||
}; | |||
ChangePasswordWrapper.Loading = ChangePasswordLoading; | |||
@@ -1,14 +1,14 @@ | |||
import { createTheme } from "@mui/material"; | |||
import { createTheme, makeStyles } from "@mui/material"; | |||
import { aborted } from "util"; | |||
import { styled } from '@mui/system'; | |||
import { styled } from "@mui/system"; | |||
import { | |||
Unstable_NumberInput as BaseNumberInput, | |||
NumberInputProps, | |||
numberInputClasses, | |||
} from '@mui/base/Unstable_NumberInput'; | |||
} from "@mui/base/Unstable_NumberInput"; | |||
import { AlignHorizontalCenter } from "@mui/icons-material"; | |||
// - - - - - - WORK IN PROGRESS - - - - - - // | |||
export const chartColor = [ | |||
"#CB4047", | |||
"#ED3A41", | |||
@@ -157,6 +157,25 @@ export const TAB_THEME = { | |||
}; | |||
// copy from MTMS | |||
export const PW_RULE_THEME = createTheme({ | |||
components: { | |||
MuiFormHelperText: { | |||
styleOverrides: { | |||
root: { | |||
color: "green", | |||
fontFamily: "Roboto", | |||
'& .icon': { | |||
alignItems: "center", | |||
AlignHorizontalCenter: "center", | |||
marginRight: '4px', | |||
fontSize: '1rem', // Adjust the font size as needed | |||
}, | |||
}, | |||
}, | |||
}, | |||
}, | |||
}); | |||
export const TSMS_BUTTON_THEME = createTheme({ | |||
palette: { | |||
primary: { | |||
@@ -423,7 +442,7 @@ export const TSMS_LONG_BUTTON_THEME = createTheme({ | |||
}); | |||
export default function NumberInputBasic() { | |||
const [value, setValue] = React.useState<number | null>(null); | |||
const [value, setValue] = (React.useState < number) | (null > null); | |||
return ( | |||
<NumberInput | |||
aria-label="Demo number input" | |||
@@ -434,34 +453,36 @@ export default function NumberInputBasic() { | |||
); | |||
} | |||
const blue = { | |||
100: '#DAECFF', | |||
200: '#80BFFF', | |||
400: '#3399FF', | |||
500: '#007FFF', | |||
600: '#0072E5', | |||
100: "#DAECFF", | |||
200: "#80BFFF", | |||
400: "#3399FF", | |||
500: "#007FFF", | |||
600: "#0072E5", | |||
}; | |||
const grey = { | |||
50: '#F3F6F9', | |||
100: '#E5EAF2', | |||
200: '#DAE2ED', | |||
300: '#C7D0DD', | |||
400: '#B0B8C4', | |||
500: '#9DA8B7', | |||
600: '#6B7A90', | |||
700: '#434D5B', | |||
800: '#303740', | |||
900: '#1C2025', | |||
50: "#F3F6F9", | |||
100: "#E5EAF2", | |||
200: "#DAE2ED", | |||
300: "#C7D0DD", | |||
400: "#B0B8C4", | |||
500: "#9DA8B7", | |||
600: "#6B7A90", | |||
700: "#434D5B", | |||
800: "#303740", | |||
900: "#1C2025", | |||
}; | |||
export const StyledInputRoot = styled('div')( | |||
export const StyledInputRoot = styled("div")( | |||
({ theme }) => ` | |||
font-family: 'IBM Plex Sans', sans-serif; | |||
font-weight: 400; | |||
border-radius: 8px; | |||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | |||
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; | |||
border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; | |||
box-shadow: 0px 2px 2px ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; | |||
color: ${theme.palette.mode === "dark" ? grey[300] : grey[900]}; | |||
background: ${theme.palette.mode === "dark" ? grey[900] : "#fff"}; | |||
border: 1px solid ${theme.palette.mode === "dark" ? grey[700] : grey[200]}; | |||
box-shadow: 0px 2px 2px ${ | |||
theme.palette.mode === "dark" ? grey[900] : grey[50] | |||
}; | |||
display: grid; | |||
grid-template-columns: 1fr 19px; | |||
grid-template-rows: 1fr 1fr; | |||
@@ -471,7 +492,9 @@ export const StyledInputRoot = styled('div')( | |||
&.${numberInputClasses.focused} { | |||
border-color: ${blue[400]}; | |||
box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; | |||
box-shadow: 0 0 0 3px ${ | |||
theme.palette.mode === "dark" ? blue[600] : blue[200] | |||
}; | |||
} | |||
&:hover { | |||
@@ -482,10 +505,10 @@ export const StyledInputRoot = styled('div')( | |||
&:focus-visible { | |||
outline: 0; | |||
} | |||
`, | |||
` | |||
); | |||
export const StyledInputElement = styled('input')( | |||
export const StyledInputElement = styled("input")( | |||
({ theme }) => ` | |||
font-size: 0.875rem; | |||
font-family: inherit; | |||
@@ -493,16 +516,16 @@ export const StyledInputElement = styled('input')( | |||
line-height: 1.5; | |||
grid-column: 1/2; | |||
grid-row: 1/3; | |||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | |||
color: ${theme.palette.mode === "dark" ? grey[300] : grey[900]}; | |||
background: inherit; | |||
border: none; | |||
border-radius: inherit; | |||
padding: 8px 12px; | |||
outline: 0; | |||
`, | |||
` | |||
); | |||
export const StyledButton = styled('button')( | |||
export const StyledButton = styled("button")( | |||
({ theme }) => ` | |||
display: flex; | |||
flex-flow: row nowrap; | |||
@@ -516,16 +539,16 @@ export const StyledButton = styled('button')( | |||
font-size: 0.875rem; | |||
line-height: 1; | |||
box-sizing: border-box; | |||
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; | |||
background: ${theme.palette.mode === "dark" ? grey[900] : "#fff"}; | |||
border: 0; | |||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | |||
color: ${theme.palette.mode === "dark" ? grey[300] : grey[900]}; | |||
transition-property: all; | |||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |||
transition-duration: 120ms; | |||
&:hover { | |||
background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; | |||
border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; | |||
background: ${theme.palette.mode === "dark" ? grey[800] : grey[50]}; | |||
border-color: ${theme.palette.mode === "dark" ? grey[600] : grey[300]}; | |||
cursor: pointer; | |||
} | |||
@@ -542,9 +565,9 @@ export const StyledButton = styled('button')( | |||
color: ${grey[50]}; | |||
} | |||
border-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; | |||
background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; | |||
color: ${theme.palette.mode === 'dark' ? grey[200] : grey[900]}; | |||
border-color: ${theme.palette.mode === "dark" ? grey[800] : grey[200]}; | |||
background: ${theme.palette.mode === "dark" ? grey[900] : grey[50]}; | |||
color: ${theme.palette.mode === "dark" ? grey[200] : grey[900]}; | |||
} | |||
&.${numberInputClasses.decrementButton} { | |||
@@ -559,12 +582,12 @@ export const StyledButton = styled('button')( | |||
color: ${grey[50]}; | |||
} | |||
border-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[200]}; | |||
background: ${theme.palette.mode === 'dark' ? grey[900] : grey[50]}; | |||
color: ${theme.palette.mode === 'dark' ? grey[200] : grey[900]}; | |||
border-color: ${theme.palette.mode === "dark" ? grey[800] : grey[200]}; | |||
background: ${theme.palette.mode === "dark" ? grey[900] : grey[50]}; | |||
color: ${theme.palette.mode === "dark" ? grey[200] : grey[900]}; | |||
} | |||
& .arrow { | |||
transform: translateY(-1px); | |||
} | |||
`, | |||
); | |||
` | |||
); |