| @@ -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); | |||
| } | |||
| `, | |||
| ); | |||
| ` | |||
| ); | |||