Skip to content

Commit

Permalink
feat: add admin role
Browse files Browse the repository at this point in the history
  • Loading branch information
Shchepotin committed Oct 11, 2023
1 parent 045f936 commit 11baa4a
Show file tree
Hide file tree
Showing 33 changed files with 1,729 additions and 19 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.45.4",
"react-i18next": "^13.2.2",
"react-virtuoso": "^4.6.0",
"typescript": "5.2.2",
"yup": "^1.2.0"
},
Expand Down
27 changes: 27 additions & 0 deletions src/app/[language]/admin-panel/page-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { RoleEnum } from "@/services/api/types/role";
import withPageRequiredAuth from "@/services/auth/with-page-required-auth";
import { useTranslation } from "@/services/i18n/client";
import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";

function AdminPanel() {
const { t } = useTranslation("admin-panel-home");

return (
<Container maxWidth="md">
<Grid container spacing={3} wrap="nowrap" pt={3}>
<Grid item>
<Typography variant="h3" gutterBottom>
{t("title")}
</Typography>
<Typography>{t("description")}</Typography>
</Grid>
</Grid>
</Container>
);
}

export default withPageRequiredAuth(AdminPanel, { roles: [RoleEnum.ADMIN] });
17 changes: 17 additions & 0 deletions src/app/[language]/admin-panel/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Metadata } from "next";
import { getServerTranslation } from "@/services/i18n";
import AdminPanel from "./page-content";

type Props = {
params: { language: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { t } = await getServerTranslation(params.language, "admin-panel-home");

return {
title: t("title"),
};
}

export default AdminPanel;
253 changes: 253 additions & 0 deletions src/app/[language]/admin-panel/users/create/page-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
"use client";

import Button from "@mui/material/Button";
import { useForm, FormProvider, useFormState } from "react-hook-form";
import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import FormTextInput from "@/components/form/text-input/form-text-input";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import withPageRequiredAuth from "@/services/auth/with-page-required-auth";
import { useSnackbar } from "notistack";
import Link from "@/components/link";
import FormAvatarInput from "@/components/form/avatar-input/form-avatar-input";
import { FileEntity } from "@/services/api/types/file-entity";
import useLeavePage from "@/services/leave-page/use-leave-page";
import Box from "@mui/material/Box";
import HTTP_CODES_ENUM from "@/services/api/types/http-codes";
import { useTranslation } from "@/services/i18n/client";
import { usePostUserService } from "@/services/api/services/users";
import { useRouter } from "next/navigation";
import { Role, RoleEnum } from "@/services/api/types/role";
import FormSelectInput from "@/components/form/select/form-select";

type CreateUserFormData = {
email: string;
firstName: string;
lastName: string;
password: string;
passwordConfirmation: string;
photo?: FileEntity;
role: Role;
};

const useValidationSchema = () => {
const { t } = useTranslation("admin-panel-users-create");

return yup.object().shape({
email: yup
.string()
.email(t("admin-panel-users-create:inputs.email.validation.invalid"))
.required(
t("admin-panel-users-create:inputs.firstName.validation.required")
),
firstName: yup
.string()
.required(
t("admin-panel-users-create:inputs.firstName.validation.required")
),
lastName: yup
.string()
.required(
t("admin-panel-users-create:inputs.lastName.validation.required")
),
password: yup
.string()
.min(6, t("admin-panel-users-create:inputs.password.validation.min"))
.required(
t("admin-panel-users-create:inputs.password.validation.required")
),
passwordConfirmation: yup
.string()
.oneOf(
[yup.ref("password")],
t(
"admin-panel-users-create:inputs.passwordConfirmation.validation.match"
)
)
.required(
t(
"admin-panel-users-create:inputs.passwordConfirmation.validation.required"
)
),
role: yup
.object()
.shape({
id: yup.number().required(),
name: yup.string(),
})
.required(t("admin-panel-users-create:inputs.role.validation.required")),
});
};

function CreateUserFormActions() {
const { t } = useTranslation("admin-panel-users-create");
const { isSubmitting, isDirty } = useFormState();
useLeavePage(isDirty);

return (
<Button
variant="contained"
color="primary"
type="submit"
disabled={isSubmitting}
>
{t("admin-panel-users-create:actions.submit")}
</Button>
);
}

function FormCreateUser() {
const router = useRouter();
const fetchPostUser = usePostUserService();
const { t } = useTranslation("admin-panel-users-create");
const validationSchema = useValidationSchema();

const { enqueueSnackbar } = useSnackbar();

const methods = useForm<CreateUserFormData>({
resolver: yupResolver(validationSchema),
defaultValues: {
email: "",
firstName: "",
lastName: "",
password: "",
passwordConfirmation: "",
role: {
id: RoleEnum.USER,
},
photo: undefined,
},
});

const { handleSubmit, setError } = methods;

const onSubmit = async (formData: CreateUserFormData) => {
const { data, status } = await fetchPostUser(formData);
if (status === HTTP_CODES_ENUM.UNPROCESSABLE_ENTITY) {
(Object.keys(data.errors) as Array<keyof CreateUserFormData>).forEach(
(key) => {
setError(key, {
type: "manual",
message: t(
`admin-panel-users-create:inputs.${key}.validation.server.${data.errors[key]}`
),
});
}
);
return;
}
if (status === HTTP_CODES_ENUM.CREATED) {
enqueueSnackbar(t("admin-panel-users-create:alerts.user.success"), {
variant: "success",
});
router.push("/admin-panel/users");
}
};

return (
<FormProvider {...methods}>
<Container maxWidth="xs">
<form onSubmit={handleSubmit(onSubmit)} autoComplete="create-new-user">
<Grid container spacing={2} mb={3} mt={3}>
<Grid item xs={12}>
<Typography variant="h6">
{t("admin-panel-users-create:title")}
</Typography>
</Grid>
<Grid item xs={12}>
<FormAvatarInput<CreateUserFormData> name="photo" />
</Grid>

<Grid item xs={12}>
<FormTextInput<CreateUserFormData>
name="email"
testId="new-user-email"
autoComplete="new-user-email"
label={t("admin-panel-users-create:inputs.email.label")}
/>
</Grid>

<Grid item xs={12}>
<FormTextInput<CreateUserFormData>
name="password"
type="password"
testId="new-user-password"
autoComplete="new-user-password"
label={t("admin-panel-users-create:inputs.password.label")}
/>
</Grid>

<Grid item xs={12}>
<FormTextInput<CreateUserFormData>
name="passwordConfirmation"
testId="new-user-password-confirmation"
label={t(
"admin-panel-users-create:inputs.passwordConfirmation.label"
)}
type="password"
/>
</Grid>

<Grid item xs={12}>
<FormTextInput<CreateUserFormData>
name="firstName"
testId="firstName"
label={t("admin-panel-users-create:inputs.firstName.label")}
/>
</Grid>

<Grid item xs={12}>
<FormTextInput<CreateUserFormData>
name="lastName"
testId="lastName"
label={t("admin-panel-users-create:inputs.lastName.label")}
/>
</Grid>

<Grid item xs={12}>
<FormSelectInput<CreateUserFormData, Pick<Role, "id">>
name="role"
testId="role"
label={t("admin-panel-users-create:inputs.role.label")}
options={[
{
id: RoleEnum.ADMIN,
},
{
id: RoleEnum.USER,
},
]}
keyValue="id"
renderOption={(option) =>
t(`admin-panel-users-create:inputs.role.options.${option.id}`)
}
/>
</Grid>

<Grid item xs={12}>
<CreateUserFormActions />
<Box ml={1} component="span">
<Button
variant="contained"
color="inherit"
LinkComponent={Link}
href="/admin-panel/users"
>
{t("admin-panel-users-create:actions.cancel")}
</Button>
</Box>
</Grid>
</Grid>
</form>
</Container>
</FormProvider>
);
}

function CreateUser() {
return <FormCreateUser />;
}

export default withPageRequiredAuth(CreateUser);
20 changes: 20 additions & 0 deletions src/app/[language]/admin-panel/users/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Metadata } from "next";
import CreateUser from "./page-content";
import { getServerTranslation } from "@/services/i18n";

type Props = {
params: { language: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { t } = await getServerTranslation(
params.language,
"admin-panel-users-create"
);

return {
title: t("title"),
};
}

export default CreateUser;
Loading

0 comments on commit 11baa4a

Please sign in to comment.