Skip to content

Commit 7415119

Browse files
committed
feat: add admin pages
1 parent 0c8c04a commit 7415119

22 files changed

Lines changed: 640 additions & 45 deletions

apps/web/src/Routes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { RouteObject } from 'react-router-dom';
55

66
import { Layout } from './components/Layout';
77
import { aboutRoute } from './features/about';
8+
import { adminRoute } from './features/admin';
89
import { authRoutes } from './features/auth';
910
import { contactRoute } from './features/contact';
1011
import { dashboardRoute } from './features/dashboard';
@@ -36,6 +37,7 @@ const protectedRoutes: RouteObject[] = [
3637
</DisclaimerProvider>
3738
),
3839
children: [
40+
adminRoute,
3941
aboutRoute,
4042
contactRoute,
4143
datahubRoute,

apps/web/src/components/Navbar/Navbar.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ export const Navbar = () => {
9797
</div>
9898
</Sheet.Footer>
9999
</Sheet.Content>
100-
<Sheet.Overlay />
101100
</Sheet>
102101
);
103102
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2+
import type { CreateGroupData } from '@opendatacapture/schemas/group';
3+
import { useMutation, useQueryClient } from '@tanstack/react-query';
4+
import axios from 'axios';
5+
6+
export function useCreateGroupMutation() {
7+
const queryClient = useQueryClient();
8+
const addNotification = useNotificationsStore((store) => store.addNotification);
9+
return useMutation({
10+
mutationFn: ({ data }: { data: CreateGroupData }) => axios.post('/v1/groups', data),
11+
onSuccess() {
12+
addNotification({ type: 'success' });
13+
void queryClient.invalidateQueries({ queryKey: ['groups'] });
14+
}
15+
});
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2+
import type { CreateUserData } from '@opendatacapture/schemas/user';
3+
import { useMutation, useQueryClient } from '@tanstack/react-query';
4+
import axios from 'axios';
5+
6+
export function useCreateUserMutation() {
7+
const queryClient = useQueryClient();
8+
const addNotification = useNotificationsStore((store) => store.addNotification);
9+
return useMutation({
10+
mutationFn: ({ data }: { data: CreateUserData }) => axios.post('/v1/users', data),
11+
onSuccess() {
12+
addNotification({ type: 'success' });
13+
void queryClient.invalidateQueries({ queryKey: ['users'] });
14+
}
15+
});
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2+
import { useMutation, useQueryClient } from '@tanstack/react-query';
3+
import axios from 'axios';
4+
5+
export function useDeleteGroupMutation() {
6+
const queryClient = useQueryClient();
7+
const addNotification = useNotificationsStore((store) => store.addNotification);
8+
return useMutation({
9+
mutationFn: ({ id }: { id: string }) => axios.delete(`/v1/groups/${id}`),
10+
onSuccess() {
11+
addNotification({ type: 'success' });
12+
void queryClient.invalidateQueries({ queryKey: ['groups'] });
13+
}
14+
});
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
2+
import { useMutation, useQueryClient } from '@tanstack/react-query';
3+
import axios from 'axios';
4+
5+
export function useDeleteUserMutation() {
6+
const queryClient = useQueryClient();
7+
const addNotification = useNotificationsStore((store) => store.addNotification);
8+
return useMutation({
9+
mutationFn: ({ id }: { id: string }) => axios.delete(`/v1/users/${id}`),
10+
onSuccess() {
11+
addNotification({ type: 'success' });
12+
void queryClient.invalidateQueries({ queryKey: ['users'] });
13+
}
14+
});
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { $Group } from '@opendatacapture/schemas/group';
2+
import { useSuspenseQuery } from '@tanstack/react-query';
3+
import axios from 'axios';
4+
5+
export function useGroupsQuery() {
6+
return useSuspenseQuery({
7+
queryFn: async () => {
8+
const response = await axios.get('/v1/groups');
9+
return $Group.array().parse(response.data);
10+
},
11+
queryKey: ['groups']
12+
});
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { $User } from '@opendatacapture/schemas/user';
2+
import { useSuspenseQuery } from '@tanstack/react-query';
3+
import axios from 'axios';
4+
5+
export function useUsersQuery() {
6+
return useSuspenseQuery({
7+
queryFn: async () => {
8+
const response = await axios.get('/v1/users');
9+
return $User.array().parse(response.data);
10+
},
11+
queryKey: ['users']
12+
});
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* eslint-disable perfectionist/sort-objects */
2+
3+
import type { RouteObject } from 'react-router-dom';
4+
5+
import { CreateGroupPage } from './pages/CreateGroupPage';
6+
import { CreateUserPage } from './pages/CreateUserPage';
7+
import { ManageGroupsPage } from './pages/ManageGroupsPage';
8+
import { ManageUsersPage } from './pages/ManageUsersPage';
9+
10+
export const adminRoute: RouteObject = {
11+
path: 'admin',
12+
children: [
13+
{
14+
path: 'groups',
15+
children: [
16+
{
17+
index: true,
18+
element: <ManageGroupsPage />
19+
},
20+
{
21+
path: 'create',
22+
element: <CreateGroupPage />
23+
}
24+
]
25+
},
26+
{
27+
path: 'users',
28+
children: [
29+
{
30+
index: true,
31+
element: <ManageUsersPage />
32+
},
33+
{
34+
path: 'create',
35+
element: <CreateUserPage />
36+
}
37+
]
38+
}
39+
]
40+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Form, Heading } from '@douglasneuroinformatics/libui/components';
2+
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
3+
import { $CreateGroupData, type CreateGroupData } from '@opendatacapture/schemas/group';
4+
import { useNavigate } from 'react-router-dom';
5+
6+
import { PageHeader } from '@/components/PageHeader';
7+
8+
import { useCreateGroupMutation } from '../hooks/useCreateGroupMutation';
9+
10+
export const CreateGroupPage = () => {
11+
const { t } = useTranslation();
12+
const navigate = useNavigate();
13+
const createGroupMutation = useCreateGroupMutation();
14+
15+
const handleSubmit = (data: CreateGroupData) => {
16+
createGroupMutation.mutate({ data });
17+
navigate('..');
18+
};
19+
20+
return (
21+
<div>
22+
<PageHeader>
23+
<Heading className="text-center" variant="h2">
24+
{t({
25+
en: 'Add Group',
26+
fr: 'Ajouter un groupe'
27+
})}
28+
</Heading>
29+
</PageHeader>
30+
<Form
31+
className="mx-auto max-w-3xl"
32+
content={{
33+
name: {
34+
kind: 'string',
35+
label: t('common.groupName'),
36+
variant: 'input'
37+
},
38+
type: {
39+
kind: 'string',
40+
label: t('common.groupType'),
41+
options: {
42+
CLINICAL: t('common.clinical'),
43+
RESEARCH: t('common.research')
44+
},
45+
variant: 'select'
46+
}
47+
}}
48+
validationSchema={$CreateGroupData}
49+
onSubmit={handleSubmit}
50+
/>
51+
</div>
52+
);
53+
};

0 commit comments

Comments
 (0)