Sfoglia il codice sorgente

update change password

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 anno fa
parent
commit
909fc0d91e
4 ha cambiato i file con 238 aggiunte e 87 eliminazioni
  1. +16
    -10
      src/components/ChangePassword/ChangePassword.tsx
  2. +152
    -32
      src/components/ChangePassword/ChangePasswordForm.tsx
  3. +4
    -2
      src/components/ChangePassword/ChangePasswordWrapper.tsx
  4. +66
    -43
      src/theme/colorConst.js

+ 16
- 10
src/components/ChangePassword/ChangePassword.tsx Vedi File

@@ -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"


+ 152
- 32
src/components/ChangePassword/ChangePasswordForm.tsx Vedi File

@@ -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)}


+ 4
- 2
src/components/ChangePassword/ChangePasswordWrapper.tsx Vedi File

@@ -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;


+ 66
- 43
src/theme/colorConst.js Vedi File

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

Caricamento…
Annulla
Salva