Skip to content

Commit 350eabb

Browse files
committed
feat: add CRUD generation
1 parent cde7de8 commit 350eabb

19 files changed

+1475
-66
lines changed

.hygen.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
templates: `${__dirname}/.hygen`,
3+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
to: src/services/i18n/locales/en/admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>-create.json
3+
---
4+
{
5+
"title": "Create",
6+
"actions": { "submit": "Save", "cancel": "Cancel" },
7+
"inputs": {
8+
"description": {
9+
"label": "Description",
10+
"validation": { "required": "Description is required" }
11+
}
12+
},
13+
"alerts": { "success": "<%= name %> has been created successfully" }
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
to: src/services/i18n/locales/en/admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>-edit.json
3+
---
4+
{
5+
"title": "Edit",
6+
"actions": { "submit": "Save", "cancel": "Cancel" },
7+
"inputs": {
8+
"description": {
9+
"label": "Description",
10+
"validation": { "required": "Description is required" }
11+
}
12+
},
13+
"alerts": { "success": "<%= name %> has been updated successfully" }
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
to: src/services/i18n/locales/en/admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>.json
3+
---
4+
{
5+
"title": "<%= h.inflection.transform(name, ['pluralize']) %>",
6+
"table": {
7+
"column1": "ID"
8+
},
9+
"actions": {
10+
"add": "Add <%= name %>",
11+
"edit": "Edit",
12+
"delete": "Delete",
13+
"create": "Create <%= name %>"
14+
},
15+
"confirm": {
16+
"delete": {
17+
"title": "Delete <%= name %>",
18+
"message": "Are you sure you want to delete this <%= name %>?"
19+
}
20+
}
21+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
to: src/services/api/services/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>.ts
3+
---
4+
import { useCallback } from "react";
5+
import useFetch from "../use-fetch";
6+
import { API_URL } from "../config";
7+
import wrapperFetchJsonResponse from "../wrapper-fetch-json-response";
8+
import { InfinityPaginationType } from "../types/infinity-pagination";
9+
import { RequestConfigType } from "./types/request-config";
10+
import { <%= name %> as Entity } from "../types/<%= h.inflection.transform(name, ['underscore', 'dasherize']) %>";
11+
12+
export type Get<%= h.inflection.transform(name, ['pluralize']) %>Request = {
13+
page: number;
14+
limit: number;
15+
};
16+
17+
export type Get<%= h.inflection.transform(name, ['pluralize']) %>Response = InfinityPaginationType<Entity>;
18+
19+
export function useGet<%= h.inflection.transform(name, ['pluralize']) %>Service() {
20+
const fetch = useFetch();
21+
22+
return useCallback(
23+
(data: Get<%= h.inflection.transform(name, ['pluralize']) %>Request, requestConfig?: RequestConfigType) => {
24+
const requestUrl = new URL(`${API_URL}/v1/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>`);
25+
requestUrl.searchParams.append("page", data.page.toString());
26+
requestUrl.searchParams.append("limit", data.limit.toString());
27+
28+
return fetch(requestUrl, {
29+
method: "GET",
30+
...requestConfig,
31+
}).then(wrapperFetchJsonResponse<Get<%= h.inflection.transform(name, ['pluralize']) %>Response>);
32+
},
33+
[fetch]
34+
);
35+
}
36+
37+
export type Get<%= name %>Request = {
38+
id: Entity["id"];
39+
};
40+
41+
export type Get<%= name %>Response = Entity;
42+
43+
export function useGet<%= name %>Service() {
44+
const fetch = useFetch();
45+
46+
return useCallback(
47+
(data: Get<%= name %>Request, requestConfig?: RequestConfigType) => {
48+
return fetch(`${API_URL}/v1/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>/${data.id}`, {
49+
method: "GET",
50+
...requestConfig,
51+
}).then(wrapperFetchJsonResponse<Get<%= name %>Response>);
52+
},
53+
[fetch]
54+
);
55+
}
56+
57+
export type Create<%= name %>Request = Pick<Entity, "description">;
58+
59+
export type Create<%= name %>Response = Entity;
60+
61+
export function useCreate<%= name %>Service() {
62+
const fetch = useFetch();
63+
64+
return useCallback(
65+
(data: Create<%= name %>Request, requestConfig?: RequestConfigType) => {
66+
return fetch(`${API_URL}/v1/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>`, {
67+
method: "POST",
68+
body: JSON.stringify(data),
69+
...requestConfig,
70+
}).then(wrapperFetchJsonResponse<Create<%= name %>Response>);
71+
},
72+
[fetch]
73+
);
74+
}
75+
76+
export type Edit<%= name %>Request = {
77+
id: Entity["id"];
78+
data: Partial<Pick<Entity, "description">>;
79+
};
80+
81+
export type Edit<%= name %>Response = Entity;
82+
83+
export function useEdit<%= name %>Service() {
84+
const fetch = useFetch();
85+
86+
return useCallback(
87+
(data: Edit<%= name %>Request, requestConfig?: RequestConfigType) => {
88+
return fetch(`${API_URL}/v1/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>/${data.id}`, {
89+
method: "PATCH",
90+
body: JSON.stringify(data.data),
91+
...requestConfig,
92+
}).then(wrapperFetchJsonResponse<Edit<%= name %>Response>);
93+
},
94+
[fetch]
95+
);
96+
}
97+
98+
export type Delete<%= name %>Request = {
99+
id: Entity["id"];
100+
};
101+
102+
export type Delete<%= name %>Response = undefined;
103+
104+
export function useDelete<%= name %>Service() {
105+
const fetch = useFetch();
106+
107+
return useCallback(
108+
(data: Delete<%= name %>Request, requestConfig?: RequestConfigType) => {
109+
return fetch(`${API_URL}/v1/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>/${data.id}`, {
110+
method: "DELETE",
111+
...requestConfig,
112+
}).then(wrapperFetchJsonResponse<Delete<%= name %>Response>);
113+
},
114+
[fetch]
115+
);
116+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
inject: true
3+
to: src/components/app-bar.tsx
4+
before: desktop-menu-items
5+
---
6+
<Button
7+
onClick={handleCloseNavMenu}
8+
sx={{ my: 2, color: "white", display: "block" }}
9+
component={Link}
10+
href="/admin-panel/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>"
11+
>
12+
{t("common:navigation.<%= h.inflection.camelize(h.inflection.pluralize(name), true) %>")}
13+
</Button>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
inject: true
3+
to: src/components/app-bar.tsx
4+
before: mobile-menu-items
5+
---
6+
<MenuItem
7+
key="<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>"
8+
onClick={handleCloseNavMenu}
9+
component={Link}
10+
href="/admin-panel/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>"
11+
>
12+
<Typography textAlign="center">
13+
{t("common:navigation.<%= h.inflection.camelize(h.inflection.pluralize(name), true) %>")}
14+
</Typography>
15+
</MenuItem>,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
inject: true
3+
to: src/services/i18n/locales/en/common.json
4+
after: "navigation"
5+
---
6+
"<%= h.inflection.camelize(h.inflection.pluralize(name), true) %>": "<%= h.inflection.transform(name, ['pluralize', 'underscore', 'humanize']) %>",
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
to: src/app/[language]/admin-panel/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>/create/page-content.tsx
3+
---
4+
"use client";
5+
6+
import Button from "@mui/material/Button";
7+
import { useForm, FormProvider, useFormState } from "react-hook-form";
8+
import Container from "@mui/material/Container";
9+
import Grid from "@mui/material/Grid";
10+
import Typography from "@mui/material/Typography";
11+
import FormTextInput from "@/components/form/text-input/form-text-input";
12+
import * as yup from "yup";
13+
import { yupResolver } from "@hookform/resolvers/yup";
14+
import withPageRequiredAuth from "@/services/auth/with-page-required-auth";
15+
import { useSnackbar } from "notistack";
16+
import Link from "@/components/link";
17+
import useLeavePage from "@/services/leave-page/use-leave-page";
18+
import Box from "@mui/material/Box";
19+
import HTTP_CODES_ENUM from "@/services/api/types/http-codes";
20+
import { useTranslation } from "@/services/i18n/client";
21+
import { useRouter } from "next/navigation";
22+
import { useCreate<%= name %>Service } from "@/services/api/services/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>";
23+
24+
type CreateFormData = {
25+
description: string;
26+
};
27+
28+
const defaultValues: CreateFormData = {
29+
description: "",
30+
};
31+
32+
const useValidationSchema = () => {
33+
const { t } = useTranslation("admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>-create");
34+
35+
return yup.object().shape({
36+
description: yup
37+
.string()
38+
.required(t("inputs.description.validation.required")),
39+
});
40+
};
41+
42+
function CreateFormActions() {
43+
const { t } = useTranslation("admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>-create");
44+
const { isSubmitting, isDirty } = useFormState();
45+
useLeavePage(isDirty);
46+
47+
return (
48+
<Button
49+
variant="contained"
50+
color="primary"
51+
type="submit"
52+
disabled={isSubmitting}
53+
>
54+
{t("actions.submit")}
55+
</Button>
56+
);
57+
}
58+
59+
function FormCreate() {
60+
const router = useRouter();
61+
const fetchCreate<%= name %> = useCreate<%= name %>Service();
62+
const { t } = useTranslation("admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>-create");
63+
const validationSchema = useValidationSchema();
64+
65+
const { enqueueSnackbar } = useSnackbar();
66+
67+
const methods = useForm<CreateFormData>({
68+
resolver: yupResolver(validationSchema),
69+
defaultValues,
70+
});
71+
72+
const { handleSubmit, setError } = methods;
73+
74+
const onSubmit = handleSubmit(async (formData) => {
75+
const { data, status } = await fetchCreate<%= name %>({
76+
description: formData.description,
77+
});
78+
79+
if (status === HTTP_CODES_ENUM.UNPROCESSABLE_ENTITY) {
80+
(Object.keys(data.errors) as Array<keyof CreateFormData>).forEach(
81+
(key) => {
82+
setError(key, {
83+
type: "manual",
84+
message: t(`inputs.${key}.validation.server.${data.errors[key]}`),
85+
});
86+
}
87+
);
88+
89+
return;
90+
}
91+
92+
if (status === HTTP_CODES_ENUM.CREATED) {
93+
enqueueSnackbar(t("alerts.success"), {
94+
variant: "success",
95+
});
96+
router.push("/admin-panel/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>");
97+
}
98+
});
99+
100+
return (
101+
<FormProvider {...methods}>
102+
<Container maxWidth="md">
103+
<form onSubmit={onSubmit}>
104+
<Grid container spacing={2} mb={3} mt={3}>
105+
<Grid item xs={12}>
106+
<Typography variant="h6">{t("title")}</Typography>
107+
</Grid>
108+
109+
<Grid item xs={12}>
110+
<FormTextInput<CreateFormData>
111+
name="description"
112+
testId="description"
113+
label={t("inputs.description.label")}
114+
multiline
115+
/>
116+
</Grid>
117+
118+
<Grid item xs={12}>
119+
<CreateFormActions />
120+
<Box ml={1} component="span">
121+
<Button
122+
variant="contained"
123+
color="inherit"
124+
LinkComponent={Link}
125+
href="/admin-panel/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>"
126+
>
127+
{t("actions.cancel")}
128+
</Button>
129+
</Box>
130+
</Grid>
131+
</Grid>
132+
</form>
133+
</Container>
134+
</FormProvider>
135+
);
136+
}
137+
138+
function Create() {
139+
return <FormCreate />;
140+
}
141+
142+
export default withPageRequiredAuth(Create);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
to: src/app/[language]/admin-panel/<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>/create/page.tsx
3+
---
4+
import type { Metadata } from "next";
5+
import Create<%= name %> from "./page-content";
6+
import { getServerTranslation } from "@/services/i18n";
7+
8+
type Props = {
9+
params: { language: string };
10+
};
11+
12+
export async function generateMetadata({ params }: Props): Promise<Metadata> {
13+
const { t } = await getServerTranslation(
14+
params.language,
15+
"admin-panel-<%= h.inflection.transform(name, ['pluralize', 'underscore', 'dasherize']) %>-create"
16+
);
17+
18+
return {
19+
title: t("title"),
20+
};
21+
}
22+
23+
export default Create<%= name %>;

0 commit comments

Comments
 (0)