Skip to content

Commit 67c7fae

Browse files
authored
Merge pull request #22 from foyzulkarim/feature/rbac/role-crud
Add role management functionality and refactor navigation
2 parents 3db5915 + 0359f02 commit 67c7fae

21 files changed

+887
-8111
lines changed

package-lock.json

Lines changed: 43 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@emotion/react": "^11.11.4",
2626
"@emotion/styled": "^11.11.5",
2727
"@faker-js/faker": "^8.1.0",
28+
"@hookform/resolvers": "^3.9.1",
2829
"@iconify/react": "^4.1.1",
2930
"@mui/icons-material": "^5.15.16",
3031
"@mui/lab": "^5.0.0-alpha.147",
@@ -41,9 +42,10 @@
4142
"react-apexcharts": "^1.4.1",
4243
"react-dom": "^18.2.0",
4344
"react-helmet-async": "^1.3.0",
44-
"react-hook-form": "^7.51.3",
45+
"react-hook-form": "^7.53.2",
4546
"react-router-dom": "^6.16.0",
46-
"simplebar-react": "^3.2.4"
47+
"simplebar-react": "^3.2.4",
48+
"yup": "^1.4.0"
4749
},
4850
"devDependencies": {
4951
"@chromatic-com/storybook": "^1.2.25",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import PropTypes from 'prop-types';
2+
import { FormProvider as Form } from 'react-hook-form';
3+
4+
// ----------------------------------------------------------------------
5+
6+
export default function FormProvider({ children, methods, onSubmit }) {
7+
return (
8+
<Form {...methods}>
9+
<form onSubmit={onSubmit}>{children}</form>
10+
</Form>
11+
);
12+
}
13+
14+
FormProvider.propTypes = {
15+
children: PropTypes.node,
16+
methods: PropTypes.object,
17+
onSubmit: PropTypes.func,
18+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import PropTypes from 'prop-types';
2+
import { Controller, useFormContext } from 'react-hook-form';
3+
4+
import Box from '@mui/material/Box';
5+
import Chip from '@mui/material/Chip';
6+
import Select from '@mui/material/Select';
7+
import MenuItem from '@mui/material/MenuItem';
8+
import InputLabel from '@mui/material/InputLabel';
9+
import FormControl from '@mui/material/FormControl';
10+
11+
export default function RHFMultiSelect({ name, label, options, ...other }) {
12+
const { control } = useFormContext();
13+
14+
return (
15+
<Controller
16+
name={name}
17+
control={control}
18+
render={({ field, fieldState: { error } }) => (
19+
<FormControl fullWidth error={!!error}>
20+
<InputLabel>{label}</InputLabel>
21+
<Select
22+
{...field}
23+
multiple
24+
renderValue={(selected) => (
25+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
26+
{selected.map((value) => (
27+
<Chip key={value} label={value} />
28+
))}
29+
</Box>
30+
)}
31+
{...other}
32+
>
33+
{options.map((option) => (
34+
<MenuItem key={option.value} value={option.value}>
35+
{option.label}
36+
</MenuItem>
37+
))}
38+
</Select>
39+
</FormControl>
40+
)}
41+
/>
42+
);
43+
}
44+
45+
RHFMultiSelect.propTypes = {
46+
name: PropTypes.string.isRequired,
47+
label: PropTypes.string.isRequired,
48+
options: PropTypes.arrayOf(
49+
PropTypes.shape({
50+
value: PropTypes.string.isRequired,
51+
label: PropTypes.string.isRequired,
52+
})
53+
).isRequired,
54+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import PropTypes from 'prop-types';
2+
import { Controller, useFormContext } from 'react-hook-form';
3+
4+
import TextField from '@mui/material/TextField';
5+
6+
// ----------------------------------------------------------------------
7+
8+
export default function RHFTextField({ name, helperText, ...other }) {
9+
const { control } = useFormContext();
10+
11+
return (
12+
<Controller
13+
name={name}
14+
control={control}
15+
render={({ field, fieldState: { error } }) => (
16+
<TextField
17+
{...field}
18+
fullWidth
19+
error={!!error}
20+
helperText={error ? error.message : helperText}
21+
{...other}
22+
/>
23+
)}
24+
/>
25+
);
26+
}
27+
28+
RHFTextField.propTypes = {
29+
name: PropTypes.string,
30+
helperText: PropTypes.string,
31+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './text-highlight';
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import PropTypes from 'prop-types';
2+
import { useState, useEffect } from 'react';
3+
4+
import Stack from '@mui/material/Stack';
5+
import Typography from '@mui/material/Typography';
6+
7+
function TextHighlight({ text, searchKeyword }) {
8+
const [highlightedText, setHighlightedText] = useState(text);
9+
10+
useEffect(() => {
11+
if (searchKeyword) {
12+
const regex = new RegExp(`(${searchKeyword})`, 'gi');
13+
const parts = text?.split(regex) ?? [];
14+
const newHighlightedText = parts.map((part, i) =>
15+
part.toLowerCase() === searchKeyword.toLowerCase() ? (
16+
<mark key={i}>{part}</mark>
17+
) : (
18+
part
19+
)
20+
);
21+
setHighlightedText(newHighlightedText);
22+
} else {
23+
setHighlightedText(text); // Reset to original text
24+
}
25+
}, [text, searchKeyword]);
26+
27+
return (
28+
<Stack direction="row" spacing={1}>
29+
<Typography variant="subtitle2">
30+
{highlightedText}
31+
</Typography>
32+
</Stack>
33+
);
34+
}
35+
36+
TextHighlight.propTypes = {
37+
text: PropTypes.string.isRequired,
38+
searchKeyword: PropTypes.string,
39+
}
40+
41+
export default TextHighlight;
Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import CodeOutlinedIcon from '@mui/icons-material/CodeOutlined';
2-
import SourceOutlinedIcon from '@mui/icons-material/SourceOutlined';
3-
import RssFeedOutlinedIcon from '@mui/icons-material/RssFeedOutlined';
4-
51
import SvgColor from 'src/components/svg-color';
62

73
// ----------------------------------------------------------------------
@@ -12,30 +8,30 @@ const icon = (name) => (
128

139
const navConfig = [
1410
{
15-
title: 'feed',
11+
title: 'Feed',
1612
path: '/',
17-
icon: <RssFeedOutlinedIcon />,
13+
icon: icon('ic_feed'),
1814
},
1915
{
20-
title: 'Users',
21-
path: '/user',
22-
icon: icon('ic_user'),
16+
title: 'repositories',
17+
path: '/repositories',
18+
icon: icon('ic_repository'),
2319
},
2420
{
25-
title: 'Add repository',
26-
path: '/add-repository',
27-
icon: <CodeOutlinedIcon />,
21+
title: 'users',
22+
path: '/user',
23+
icon: icon('ic_user'),
2824
},
2925
{
30-
title: 'Repositories',
31-
path: '/repositories',
32-
icon: <SourceOutlinedIcon />,
26+
title: 'roles',
27+
path: '/role',
28+
icon: icon('ic_role'),
3329
},
3430
{
3531
title: 'analytics',
3632
path: '/analytics',
3733
icon: icon('ic_analytics'),
38-
}
34+
},
3935
];
4036

4137
export default navConfig;

src/pages/role.jsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Helmet } from 'react-helmet-async';
2+
3+
import { RoleView } from 'src/sections/role/view';
4+
5+
// ----------------------------------------------------------------------
6+
7+
export default function RolePage() {
8+
return (
9+
<>
10+
<Helmet>
11+
<title> Roles | CommitStreams </title>
12+
</Helmet>
13+
14+
<RoleView />
15+
</>
16+
);
17+
}

src/routes/paths.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const paths = {
2+
feed: '/',
3+
repositories: '/repositories',
4+
user: '/user',
5+
role: '/role',
6+
analytics: '/analytics',
7+
};

src/routes/sections.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const RepositoriesView = lazy(() => import('src/pages/repositories'));
1616
export const FeedPage = lazy(() => import('src/pages/feed'));
1717
export const Page404 = lazy(() => import('src/pages/page-not-found'));
1818
export const RegisterPage = lazy(() => import('src/pages/register'));
19+
export const RolePage = lazy(() => import('src/pages/role'));
1920

2021
// ----------------------------------------------------------------------
2122

@@ -51,6 +52,7 @@ export default function Router() {
5152
{ path: 'repositories', element: <RepositoriesView /> },
5253
{ path: 'analytics', element: <IndexPage /> },
5354
{ element: <FeedPage />, index: true },
55+
{ path: 'role', element: <RolePage /> },
5456
],
5557
},
5658
{

0 commit comments

Comments
 (0)