@@ -0,0 +1,53 @@ | |||||
import { preloadClaims } from "@/app/api/claims"; | |||||
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; | |||||
import ChangePassword from "@/components/ChangePassword"; | |||||
import StaffSearch from "@/components/StaffSearch"; | |||||
import TeamSearch from "@/components/TeamSearch"; | |||||
import UserGroupSearch from "@/components/UserGroupSearch"; | |||||
import UserSearch from "@/components/UserSearch"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import Add from "@mui/icons-material/Add"; | |||||
import Button from "@mui/material/Button"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import { Metadata } from "next"; | |||||
import Link from "next/link"; | |||||
import { Suspense } from "react"; | |||||
export const metadata: Metadata = { | |||||
title: "Change Password", | |||||
}; | |||||
const ChangePasswordPage: React.FC = async () => { | |||||
const { t } = await getServerI18n("User Group"); | |||||
// preloadTeamLeads(); | |||||
// preloadStaff(); | |||||
return ( | |||||
<> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent="space-between" | |||||
flexWrap="wrap" | |||||
rowGap={2} | |||||
> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Change Password")} | |||||
</Typography> | |||||
</Stack> | |||||
{/* <I18nProvider namespaces={["User Group", "common"]}> | |||||
<Suspense fallback={<UserGroupSearch.Loading />}> | |||||
<UserGroupSearch /> | |||||
</Suspense> | |||||
</I18nProvider> */} | |||||
<I18nProvider namespaces={["User Group", "common"]}> | |||||
<Suspense fallback={<ChangePassword.Loading />}> | |||||
<ChangePassword /> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default ChangePasswordPage; |
@@ -0,0 +1,22 @@ | |||||
import { Edit } from "@mui/icons-material"; | |||||
import { Metadata } from "next"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import EditUser from "@/components/EditUser"; | |||||
import { Typography } from "@mui/material"; | |||||
import { Suspense } from "react"; | |||||
const User: React.FC = async () => { | |||||
const { t } = await getServerI18n("user"); | |||||
return ( | |||||
<> | |||||
<Typography variant="h4">{t("Edit User")}</Typography> | |||||
<I18nProvider namespaces={["user", "common"]}> | |||||
<Suspense fallback={<EditUser.Loading />}> | |||||
<EditUser /> | |||||
</Suspense> | |||||
</I18nProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default User; |
@@ -0,0 +1,107 @@ | |||||
"use client"; | |||||
import { PasswordInputs, changePassword } from "@/app/api/user/actions"; | |||||
import { Grid } from "@mui/material"; | |||||
import { useRouter } from "next/navigation"; | |||||
import { useCallback, useState } from "react"; | |||||
import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; | |||||
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 { ServerFetchError } from "@/app/utils/fetchUtil"; | |||||
// interface Props { | |||||
// // auth?: auth[] | |||||
// // users?: UserResult[] | |||||
// } | |||||
const ChangePassword: React.FC = () => { | |||||
const formProps = useForm<PasswordInputs>(); | |||||
const [serverError, setServerError] = useState(""); | |||||
const router = useRouter(); | |||||
// const [tabIndex, setTabIndex] = useState(0); | |||||
const { t } = useTranslation(); | |||||
const onSubmit = useCallback<SubmitHandler<PasswordInputs>>( | |||||
async (data) => { | |||||
try { | |||||
let haveError = false; | |||||
// Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character: | |||||
let regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/ | |||||
if (data.newPassword.length < 8 || data.newPassword.length > 20) { | |||||
haveError = true | |||||
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" }) | |||||
} | |||||
if (data.password == data.newPassword) { | |||||
haveError = true | |||||
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("newPasswordCheck", { message: "The new password has to be the same as the new password", type: "required" }) | |||||
} | |||||
if (haveError) { | |||||
return | |||||
} | |||||
const postData = { | |||||
password: data.password, | |||||
newPassword: data.newPassword | |||||
} | |||||
// await changePassword(postData) | |||||
// router.replace("/home") | |||||
} catch (e) { | |||||
console.log(e) | |||||
setServerError(t("An error has occurred. Please try again later.")); | |||||
} | |||||
}, | |||||
[router] | |||||
); | |||||
const handleCancel = () => { | |||||
router.push(`/home`); | |||||
}; | |||||
const onSubmitError = useCallback<SubmitErrorHandler<PasswordInputs>>( | |||||
(errors) => { | |||||
console.log(errors); | |||||
}, | |||||
[] | |||||
); | |||||
return ( | |||||
<FormProvider {...formProps}> | |||||
<Stack | |||||
spacing={2} | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
> | |||||
<ChagnePasswordForm /> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
variant="outlined" | |||||
startIcon={<Close />} | |||||
onClick={handleCancel} | |||||
> | |||||
{t("Cancel")} | |||||
</Button> | |||||
<Button | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
type="submit" | |||||
// disabled={Boolean(formProps.watch("isGridEditing"))} | |||||
> | |||||
{t("Confirm")} | |||||
</Button> | |||||
</Stack> | |||||
</Stack> | |||||
</FormProvider> | |||||
); | |||||
}; | |||||
export default ChangePassword; |
@@ -0,0 +1,144 @@ | |||||
"use client"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import Box from "@mui/material/Box"; | |||||
import Card from "@mui/material/Card"; | |||||
import CardContent from "@mui/material/CardContent"; | |||||
import Grid from "@mui/material/Grid"; | |||||
import TextField from "@mui/material/TextField"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import { useFormContext } from "react-hook-form"; | |||||
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"; | |||||
const ChagnePasswordForm: React.FC = () => { | |||||
const { t } = useTranslation(); | |||||
const [showNewPassword, setShowNewPassword] = useState(false); | |||||
const handleClickShowNewPassword = () => setShowNewPassword(!showNewPassword); | |||||
const handleMouseDownNewPassword = () => setShowNewPassword(!showNewPassword); | |||||
const [showPassword, setShowPassword] = useState(false); | |||||
const handleClickShowPassword = () => setShowPassword(!showPassword); | |||||
const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||||
const { | |||||
register, | |||||
formState: { errors, defaultValues }, | |||||
control, | |||||
reset, | |||||
resetField, | |||||
setValue, | |||||
} = useFormContext<PasswordInputs>(); | |||||
// const resetGroup = useCallback(() => { | |||||
// console.log(defaultValues); | |||||
// if (defaultValues !== undefined) { | |||||
// resetField("description"); | |||||
// } | |||||
// }, [defaultValues]); | |||||
return ( | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent component={Stack} spacing={4}> | |||||
<Box> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Group Info")} | |||||
</Typography> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Input Old Password")} | |||||
fullWidth | |||||
type={showPassword ? "text" : "password"} | |||||
InputProps={{ | |||||
endAdornment: ( | |||||
<InputAdornment position="end"> | |||||
<IconButton | |||||
aria-label="toggle password visibility" | |||||
onClick={handleClickShowPassword} | |||||
onMouseDown={handleMouseDownPassword} | |||||
> | |||||
{showPassword ? <Visibility /> : <VisibilityOff />} | |||||
</IconButton> | |||||
</InputAdornment> | |||||
) | |||||
}} | |||||
{...register("password", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.password)} | |||||
helperText={ | |||||
Boolean(errors.password) && | |||||
(errors.password?.message | |||||
? t(errors.password.message) | |||||
: t("Please input correct password")) | |||||
} | |||||
/> | |||||
</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")) | |||||
} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Input New Password Again")} | |||||
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("newPasswordCheck")} | |||||
error={Boolean(errors.newPassword)} | |||||
helperText={ | |||||
Boolean(errors.newPassword) && | |||||
(errors.newPassword?.message | |||||
? t(errors.newPassword.message) | |||||
: t("Please input correct newPassword")) | |||||
} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Box> | |||||
</CardContent> | |||||
</Card> | |||||
); | |||||
}; | |||||
export default ChagnePasswordForm; |
@@ -0,0 +1,40 @@ | |||||
import Card from "@mui/material/Card"; | |||||
import CardContent from "@mui/material/CardContent"; | |||||
import Skeleton from "@mui/material/Skeleton"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import React from "react"; | |||||
// Can make this nicer | |||||
export const ChangePasswordLoading: React.FC = () => { | |||||
return ( | |||||
<> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton | |||||
variant="rounded" | |||||
height={50} | |||||
width={100} | |||||
sx={{ alignSelf: "flex-end" }} | |||||
/> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
<Card>Change Password | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
</> | |||||
); | |||||
}; | |||||
export default ChangePasswordLoading; |
@@ -0,0 +1,20 @@ | |||||
import React from "react"; | |||||
import ChangePasswordLoading from "./ChangePasswordLoading"; | |||||
import ChangePassword from "./ChangePassword"; | |||||
interface SubComponents { | |||||
Loading: typeof ChangePasswordLoading; | |||||
} | |||||
const ChangePasswordWrapper: React.FC & SubComponents = async () => { | |||||
// const records = await fetchAuth() | |||||
// const users = await fetchUser() | |||||
// console.log(users) | |||||
// const auth = records.records as auth[] | |||||
return <ChangePassword />; | |||||
}; | |||||
ChangePasswordWrapper.Loading = ChangePasswordLoading; | |||||
export default ChangePasswordWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./ChangePasswordWrapper"; |