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